Initial import from CVS.

This commit is contained in:
Jordan Bancino 2024-01-02 18:14:26 -05:00
commit e594f42c69
87 changed files with 11215 additions and 0 deletions

13
.gitignore vendored Executable file
View file

@ -0,0 +1,13 @@
# Gradle
.gradle/
build/
# VS Code
.classpath
.project
.settings/
bin/
# diffutils
*.orig
*.reg

3
.idea/.gitignore vendored Executable file
View file

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

12
.idea/SwerveIO.iml Executable file
View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="SwerveIO" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="net.bancino.robotics" external.system.module.version="8.0.0-rc1" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/compiler.xml Executable file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>

29
.idea/gradle.xml Executable file
View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="delegatedBuild" value="true" />
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/kit" />
<option value="$PROJECT_DIR$/kit/sds-mk2" />
<option value="$PROJECT_DIR$/kit/sds-mk3" />
<option value="$PROJECT_DIR$/misc" />
<option value="$PROJECT_DIR$/misc/virt" />
<option value="$PROJECT_DIR$/vendor" />
<option value="$PROJECT_DIR$/vendor/ctre" />
<option value="$PROJECT_DIR$/vendor/kauai" />
<option value="$PROJECT_DIR$/vendor/rev" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

45
.idea/jarRepositories.xml Executable file
View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="https://frcmaven.wpi.edu/artifactory/release" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven3" />
<option name="name" value="maven3" />
<option name="url" value="https://www.revrobotics.com/content/sw/max/sdk/maven" />
</remote-repository>
<remote-repository>
<option name="id" value="maven2" />
<option name="name" value="maven2" />
<option name="url" value="https://devsite.ctr-electronics.com/maven/release" />
</remote-repository>
<remote-repository>
<option name="id" value="maven2" />
<option name="name" value="maven2" />
<option name="url" value="https://maven.ctr-electronics.com/release" />
</remote-repository>
<remote-repository>
<option name="id" value="maven3" />
<option name="name" value="maven3" />
<option name="url" value="https://maven.revrobotics.com/" />
</remote-repository>
</component>
</project>

8
.idea/misc.xml Executable file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="JavadocGenerationManager">
<option name="OUTPUT_DIRECTORY" value="$PROJECT_DIR$/build/tmp" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK" />
</project>

8
.idea/modules.xml Executable file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/SwerveIO.iml" filepath="$PROJECT_DIR$/.idea/SwerveIO.iml" />
</modules>
</component>
</project>

10
.idea/runConfigurations.xml Executable file
View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
</set>
</option>
</component>
</project>

124
.idea/uiDesigner.xml Executable file
View file

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

6
.idea/vcs.xml Executable file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

23
.vscode/settings.json vendored Executable file
View file

@ -0,0 +1,23 @@
{
/* Exclude temporary files from the explorer. */
"files.exclude": {
"**/.gradle": true,
"**/bin": true,
"**/.classpath": true,
"**/.project": true,
"**/.settings": true,
"**/.factorypath": true
},
/* Always add a newline to the end of files. */
"files.insertFinalNewline": true,
/* Aggressive formatting */
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.formatOnType": true,
/* Git settings */
"git.autofetch": true,
"git.confirmSync": false,
/* Java settings */
"java.refactor.renameFromFileExplorer": "autoApply",
"java.configuration.updateBuildConfiguration": "automatic"
}

628
LICENSE Executable file
View file

@ -0,0 +1,628 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

323
README.md Executable file
View file

@ -0,0 +1,323 @@
SwerveIO
========
The Open Source Swerve Drive Library
------------------------------------
SwerveIO is an open-source swerve drive library written for FRC in Java.
It is pronounced "Swerve - ee - oh", which rhymes with "Oreo".
You are currently viewing the **developer documentation**. The upstream
user documentation is available as JavaDoc documentation in the source
tree and can be easily generated using the `javadoc` Gradle task. The
change log is available below.
Project Status
--------------
I am no longer on an FRC team, and my old FRC team refuses to use
SwerveIO anymore. Suffice it to say that SwerveIO's future doesn't look
too good. That being said, if you are interested in SwerveIO, please
reach out to me and let me know. I am more than happy to keep working
on SwerveIO, but right now I don't see a lot of interest, so I don't
want to waste my time on it.
This project is as good as dead, but it doesn't have to be! Seriously,
an email telling me you want to see this thing going again is all it
would take. Until I get that email though, you likely won't see any
changes to this code. This message was written on February 17, 2022.
Project Layout
--------------
This project is a standard Gradle project with multiple subprojects. It
strives to follow the Gradle-prescribed file system hierarchy as closely
as possible.
Building
--------
SwerveIO can be built using a simple `./gradlew build`. Documentation
can be built with `./gradlew javadoc`. If you don't have an internet
connection or are at a competition, you can specify the `--offline`
Gradle flag. This will speed up builds dramatically because a lot of
non-essential functionality will be disabled.
Contributing
------------
The contribution workflow for SwerveIO may be different from what you
might be used to. Please see my [homepage](http://bancino.net) for
instructions.
SwerveIO is a highly specialized library designed to meet the
programming standards enforced by the style recommendations for the Java
language. The
[Google Style Guide](https://google.github.io/styleguide/javaguide.html)
is a good guide to follow, however exact standards enforced in this
library will be at the discretion of its maintainers. I personally just
use my IDE's formatting tool call it good. I do, however, have the
following rules specific to this project:
* Always explicitly declare the type of a variable, even in lambda
expressions. In other words, don't use the `var` keyword, and if you
have a lambda that looks like this:
`(param) -> doSomethingWith(param)`, change it to this:
`(String param) -> doSomethingWith(param)`, where `String` is the
actual type of `param`.
The goal for SwerveIO is to be the best possible library that can be
used by the most people. Contributors should write their highest quality
code. This means that SwerveIO should be:
* **Stable:** SwerveIO should work consistently no matter what.
Whenever possible, the public-facing API should be preserved so that
users can upgrade without much difficult. Additionally, every
individual release should perform consistently every time.
* **Standardized:** All the Java sandards should be followed. This is
a Java library, it should look and work like one.
* **Efficient:** Code should be clean and efficient.
* **Versatile:** Remember that the design goals of SwerveIO intend to
cater to the most swerve drive users as possible. Code should not be
specialized or dependent on any hardware or 3rd-partly library
behavior when possible. Great care has been taken to ensure the
library is abstract enough that new hardware can be easily added.
* **Documented:** Always keep the documentation up to date and write
comments wherever they are needed. Currently, there are more lines
of comment in SwerveIO than there are lines of code. I am proud of
that.
Before contributing to the development of SwerveIO, please be sure of
the following:
* You accept the terms of the license that your contributions will be
released under. See the `LICENSE` file.
* You agree to the contributor code of conduct, detailed below.
* You took a look at the `TODO` file. That file contains what's
planned for the future of SwerveIO, so maybe you can help out with
some of the items listed there.
When making your changes, be sure to add an appropriate entry to the
changelog.
Environment Setup
-----------------
You need the following pieces of software to develop and contribute to
SwerveIO:
* A Java Development Kit (JDK), version 11 or greater
* CVS
* An IDE or text editor
I don't have any hard requirements for the IDE or text editor you use,
but I would highly recommend using VSCode or IntelliJ because I have
both configurations bundled to set up the project automatically for you.
Code of Conduct
---------------
SwerveIO's development code of conduct is simple:
* Be human. We're all people here and we may all be different and at
different stages of life. Be understanding of others and know that
they may be going through something you aren't aware of.
* Don't get political. There is a place for politics, and if you know
me personally, you know I am quite opinionated. But SwerveIO's
development cycle is not the place for politics. The focus should
just be on writing code with fellow programmers and helping to
create the best library possible.
Release Checklist
-----------------
This is just to help me when I'm releasing new versions of SwerveIO.
* Increment version in `build.gradle`
* Add changelog entry to this readme.
* Tag the release with the version number and publish it to my
website.
Release Model
-------------
SwerveIO may be under heavy development during robotics seasons. Because
of this, it featured an aggressive release model in its early days that
wasn't afraid to break existing code. However, as I want SwerveIO to be
stable, and I have grown more conservative over the years, I want to
slow things down and keep things stable. I am open to the idea of
maintaining long term support (LTS) releases along side the primary
development cycle. I want SwerveIO to work for its users, so please
contact me so we can discuss SwerveIO's release model.
SwerveIO strives to adhere to [Semantic Versioning](https://semver.org):
> Given a version number MAJOR.MINOR.PATCH, increment the:
>
> 1. MAJOR version when you make incompatible API changes,
> 2. MINOR version when you add functionality in a backwards compatible
> manner, and
> 3. PATCH version when you make backwards compatible bug fixes.
Change Log
----------
8.0.2
-----
- Updated the contributing instructions to note the usage of CVS
instead of Git.
- Added a notice to this README announcing the deprecation of SwerveIO.
I'm sad to see this project go, and maybe I'll revive it at some
point later in life, but if nobody expresses any interest in what
I've done here, then I'll lay this project to rest. I'll of course
still host all the source code here, and if you're considering forking
SwerveIO, I'd say reach out to me and email me because I want to be
involved!
**Note:** This release makes no code or build changes. It simply bumps
the version number so that I could update this README. For that reason,
I have no idea if SwerveIO still builds with current tooling, but I know
that if for some reason it doesn't, we can get it going again in no
time, just send me an email.
8.0.1
-----
- Updated CTRE Phoenix to the latest kickoff release. This fixes a build
error that some Windows users were encountering when trying to build
against the local Maven repository.
8.0.0
-----
Welcome to the 2022 Kickoff! This release includes all the changes
from `8.0.0-rc1` and `8.0.0-rc2`, so if you're upgrading from SwerveIO
7x, make sure to read the release notes for those too!
- Updated WPILib to '2022.1.1', the kickoff release of WPILib.
- Upgraded the SparkMax API to REVLib 2022.
- The `SparkMaxEncoder` class constructors were updated to reflect
the changes made to how REV does encoders. Some breaking changes
were made for users that weren't using the default constructor. If
you were using the default constructor, you should notice no
changes.
- Removed the `LegacySpeedController` class introduced in `-rc1` because
all vendors now support WPILib's `SpeedController`.
8.0.0-rc2
---------
- Updated WPILib to `2022.1.1-rc-1`.
- Updated OpenCV to `4.5.2-1`.
- Made some internal variables `final`.
SwerveIO 8.0.0 will be released when WPILib 2022.1.1 is, which will
most likely occur before or during the 2022 kickoff. At this time, I
am considering breaking all WPILib support off into a separate Gradle
subproject, because of the recent breaking changes. SwerveIO will still
fully support WPILib, but will also provide all its own interfaces so
that it can be used entirely separate if necessary.
8.0.0-rc1
---------
SwerveIO 8x brings SwerveIO up to date with WPILib 2022.x. This release
should look a lot like the previous releases. However, WPILib made a lot
of breaking user-facing API changes. This update is simply to reflect
those changes and make SwerveIO compatible with future releases of
WPILib and vendor libraries.
**Note:** This release of SwerveIO is not compatible with WPILib 2021 or
earlier. It *requires* WPILib 2022.1.1 or later.
The breaking changes are as follows:
* Replaced all references to the deprecated `SpeedController`
with the new `MotorController`. All interfaces and implementations
were adjusted accordingly.
Other non-breaking changes are also included in this release:
* Replaced a reference to the deprecated `DriverStation.getInstance()`
with just `DriverStation`.
* Added the `LegacySpeedController` class to support vendor APIs that
haven't updated their APIs to use the latest WPILib. Note that this
class is already deprecated and schedule for removal as soon as all
supported hardware vendors update their APIs or WPILib removes the
`SpeedController` interface, whichever happens first.
* Cleaned up this README document to reflect the current state of
SwerveIO.
* Updated the copyright headers throughout the code.
7.0.4
-----
* Updated the IntelliJ configuration to use the system's JDK instead
of a hard-coded JDK.
* Updated Gradle from `7.2` to `7.3.3`.
* Updated WPILib to `2022.1.1-beta-4`.
* `[vendor/kauai]` Updated NavX library to `4.0.435`.
* As far as I can tell, it looks like CTRE Phoenix is working again.
As of the date of this release, SwerveIO can be built normally.
7.0.3
-----
* Added an IntelliJ `.idea` folder for developing SwerveIO with
IntelliJ. Since I'm no longer on the robotics team, I don't use VS
Code much anymore, so I've become much more familiar with IntelliJ.
Obviously IntelliJ cannot be used by students, because students
require the WPILib VS Code plugins, but since I'm not going to be
directly deploying SwerveIO to a robot, I can use IntelliJ.
* Updated Gradle from 7.1.1 to 7.2.
* Updated CTRE Phoenix from 5.19.4 to 5.20.0. Note that at this time,
neither version will build with SwerveIO because the CTRE Maven
repository is broken. It does not match what is in the vendor JSON
description file, and indexing is not allowed, so I'm unable to
troubleshoot. If anyone knows what's up with this, or has a fix,
please contact me at
[jordan@bancino.net](mailto:jordan@bancino.net).
* Documented the LogIO classes.
7.0.2
-----
Updated Gradle from 7.0.1 to 7.1.1, and fixed some broken links.
7.0.1
-----
Fixed some errors in the Javadoc home page that prevented the
documentation from building correctly on JDK 11.
7.0.0
-----
SwerveIO 7.0.0 cleans up a lot of the API, making for neater and less
fragmented code. This release also simplifies the build system and
documentation, and attempts to decouple both from Jordan Bancino's
infrastructure so that SwerveIO is fully self-contained.
* Removed all methods and classes that were deprecated as of the
previous release.
* Removed all kit module constructors that referenced angle offsets,
because angle offsets are now handled at the `SwerveDrive` level
using `saveAngleOffsets()` and `loadAngleOffets()`
* Merge LogIO directly into the SwerveIO source tree. LogIO is now a
part of SwerveIO Core, instead of being it's own artifact. The
package name will stay the same and this change will be reversed if
LogIO becomes needed for future projects.
* Update dependencies:
* **Gradle:** 7.0 -> 7.0.1
6.1.2
-----
This release doesn't make any changes directly to SwerveIO; it is simply
a dependency update:
* **WPILib:** 2021.2.2 -> 2021.3.1
* **Gradle:** 6.8.3 -> 7.0
* JCenter is being shut down, so now certain dependencies are
loaded from Maven Central.

328
build.gradle Executable file
View file

@ -0,0 +1,328 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
/*
* SwerveIO core build script. This is the master build script that holds the common
* project configuration for all SwerveIO subprojects, as well as the main SwerveIO
* core project.
*/
import org.apache.tools.ant.taskdefs.condition.Os
def versions = [
'SwerveIO': '8.0.2',
'WPILib': '2022.1.1',
'OpenCV': '4.5.2-1',
'Jackson': '2.10.0'
]
/* Not all "projects" are actual projects. This filters out the ones that aren't. */
def matchProject = { Project proj ->
return file("${proj.getProjectDir()}/build.gradle").exists()
}
/*
* Check if there is an internet connection. This is used to dynamically
* disable build functionality that requires a connection, because those
* functions will fail when we are at a competition.
*
* This function checks if the --offline was specified before attempting to
* establish a connection.
*/
def isOnline = {
/* If we know we're in offline mode, we already know the answer. */
if (project.getGradle().startParameter.isOffline()) {
return false
} else {
/* There may or may not be an available internet connection. */
try {
final URL url = new URL("http://wpilib.org")
final URLConnection conn = url.openConnection()
conn.connect()
conn.getInputStream().close()
return true
} catch (MalformedURLException e) {
throw new RuntimeException(e)
} catch (IOException e) {
return false
}
}
}
/* Add external javadoc links if an internet connection is available. */
def javadocAddLinks = { Project proj, String... urls ->
if (isOnline()) {
configure (proj) {
javadoc {
for (String url : urls) {
options.getLinks().add(url)
}
}
}
}
}
/* Execute a command on the operating system. */
def osExec = { String command, List arguments ->
new ByteArrayOutputStream().withStream { OutputStream os ->
exec {
executable = command
args = arguments
standardOutput = os
}
return os.toString()
}
}
def lsRecursive
lsRecursive = {File dir, String matches ->
def list = []
dir.listFiles().sort().each { File file ->
if (file.isFile()) {
if (file.getName().matches(matches)) {
list += file
}
} else {
list += lsRecursive(file, matches)
}
}
return list
}
/* Export methods and variables to subprojects. */
rootProject.ext.versions = versions
rootProject.ext.isOnline = isOnline
rootProject.ext.javadocAddLinks = javadocAddLinks
/* Root project tasks */
task updateHeaders() {
doLast {
def header = file("${projectDir}/header.txt")
def patch = file("${projectDir}/header.patch")
patch.text = osExec('git', ['diff', "$header"])
if (!patch.text.isEmpty()) {
def patchFiles = lsRecursive(file("${projectDir}"), '^.*\\.(gradle|java)$')
for (File file : patchFiles) {
println "Patching '$file'..."
osExec('patch', ["$file", "$patch"])
}
def deleteFiles = lsRecursive(file("${projectDir}"), '^.*\\.orig$')
for (File file : deleteFiles) {
file.delete()
}
osExec('git', ['commit', "$header", '-m', 'Update header.'])
} else {
println "Headers up to date."
}
patch.delete()
}
}
/* Common configuration for all projects. */
configure(allprojects.findAll { matchProject(it) }) {
/* Java plugins */
apply plugin: 'java-library'
apply plugin: 'maven-publish'
/* Maven metadata */
group = 'net.bancino.robotics'
version = versions.SwerveIO
/* Java 11 */
sourceCompatibility = 11
targetCompatibility = 11
/*
* SwerveIO doesn't need any of this stuff without internet. This
* will dramatically speed up the time it takes to build SwerveIO,
* which can be helpful at competitions when things need to happen
* quickly.
*/
if (isOnline()) {
/* Maven jars - for documentation and IDE support. */
java {
withJavadocJar()
withSourcesJar()
}
/* All Javadocs should link to the standard Javadoc, and the WPILib Javadoc */
javadocAddLinks(project,
'https://docs.oracle.com/en/java/javase/11/docs/api/',
'https://first.wpi.edu/wpilib/allwpilib/docs/release/java/'
)
}
/* All artifacts go to the root repo. */
def repoUrl = "${rootProject.buildDir}/repo"
/* Publish to a local repo that can be published separately. */
publishing {
publications {
/* Artifact is derived from the project name. */
mavenJava(MavenPublication) {
artifactId = project.getName()
from project.components.java
}
}
repositories {
maven {
url = repoUrl
}
}
}
/* Common Javadoc configuration. */
javadoc {
options.overview = "${projectDir}/src/javadoc/overview.html"
options.setWindowTitle("${project.getName()} ${project.version} User Documentation")
/* All javadocs go to the root directory. */
options.destinationDirectory(file("${rootProject.docsDir}/${project.getName()}"))
}
/*
* Dependency repositories. All repositories must be shared across all projects so that they can include
* each other without duplicating the repository requirements.
*/
repositories {
/* Jackson XML & Vendor: KauaiLabs */
mavenCentral()
/* WPILib */
maven {
url 'https://frcmaven.wpi.edu/artifactory/release'
}
/* Vendor: CTRE */
maven {
url 'https://maven.ctr-electronics.com/release'
}
/* Vendor: RevRobotics */
maven {
url 'https://maven.revrobotics.com/'
}
}
/* Generate GradleRIO vendor Json files that can be dropped into vendordeps/ */
task generateVendorJson() {
def jsonFile = "${project.getName()}.json"
def repoList = {
def str = ""
repositories.each {
str += "\"${it.url}\", "
}
return str.substring(0, str.length() - 2)
}
def outputDir = file(repoUrl)
doLast {
outputDir.mkdirs()
file("${outputDir}/${jsonFile}").text =
"""{
"cppDependencies": [],
"fileName": "${jsonFile}",
"javaDependencies": [
{
"artifactId": "${project.getName()}",
"groupId": "${project.group}",
"version": "${version}"
}
],
"jniDependencies": [],
"jsonUrl": "",
"mavenUrls": [
${repoList()}
],
"name": "${project.getName()}",
"uuid": "${project.group}:${project.getName()}",
"version": "${version}"
}
"""
}
}
/* When publishing Maven artifacts, also generate vendor JSON. */
publish.dependsOn generateVendorJson
/* All common dependencies go here. */
dependencies {
/* WPILib */
api "edu.wpi.first.wpilibj:wpilibj-java:${versions.WPILib}"
api "edu.wpi.first.wpilibNewCommands:wpilibNewCommands-java:${versions.WPILib}"
api "edu.wpi.first.wpimath:wpimath-java:${versions.WPILib}"
/*
* The SwerveIO core itself doesn't use these artifacts, but the vendor dependencies,
* included in the subprojects may, so we include them here to ensure that they are the
* same version across the board. I noticed, for example, that the NavX library would
* pull older versions of these artifacts than what SwerveIO was using, so we do this to
* make everything line up.
*/
api "edu.wpi.first.cscore:cscore-java:${versions.WPILib}"
api "edu.wpi.first.cameraserver:cameraserver-java:${versions.WPILib}"
api "edu.wpi.first.ntcore:ntcore-java:${versions.WPILib}"
api "edu.wpi.first.wpiutil:wpiutil-java:${versions.WPILib}"
api "edu.wpi.first.hal:hal-java:${versions.WPILib}"
/* Required by WPILib - Make sure this lines up with the version specified by WPILib */
api "edu.wpi.first.thirdparty.frc2022.opencv:opencv-java:${versions.OpenCV}"
/* Required by WPILib - Gradle emits warnings if this isn't included. */
implementation "com.fasterxml.jackson.core:jackson-annotations:${versions.Jackson}"
/* Unit testing */
testImplementation 'org.junit.jupiter:junit-jupiter-api:+'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:+'
/* Convert OS into WPILib platform */
def platform
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
platform = 'windowsx86-64'
} else if (Os.isFamily(Os.FAMILY_MAC)) {
platform = 'osxx86-64'
} else if (Os.isFamily(Os.FAMILY_UNIX)) {
platform = 'linuxx86-64'
} else {
throw new GradleException("Unrecognized operating system.")
}
/* JNI stuff so WPILib can run on the desktop. */
testImplementation "edu.wpi.first.thirdparty.frc2022.opencv:opencv-jni:${versions.OpenCV}:${platform}@jar"
testImplementation "edu.wpi.first.hal:hal-jni:${versions.WPILib}:${platform}@jar"
testImplementation "edu.wpi.first.wpiutil:wpiutil-jni:${versions.WPILib}:${platform}@jar"
testImplementation "edu.wpi.first.ntcore:ntcore-jni:${versions.WPILib}:${platform}@jar"
testImplementation "edu.wpi.first.cscore:cscore-jni:${versions.WPILib}:${platform}@jar"
}
test {
useJUnitPlatform()
testLogging {
showStandardStreams = true
}
}
}
/* Common configuration for subprojects only. */
configure(subprojects.findAll { matchProject(it) }) {
/* All subprojects depend on the core SwerveIO project */
dependencies {
api rootProject
}
}

BIN
gradle/wrapper/gradle-wrapper.jar vendored Executable file

Binary file not shown.

5
gradle/wrapper/gradle-wrapper.properties vendored Executable file
View file

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

183
gradlew vendored Executable file
View file

@ -0,0 +1,183 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
java --version >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

100
gradlew.bat vendored Executable file
View file

@ -0,0 +1,100 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

16
header.txt Executable file
View file

@ -0,0 +1,16 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/

24
kit/sds-mk2/build.gradle Executable file
View file

@ -0,0 +1,24 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
/*
* Swerve Drive Specialties' MK2 Swerve Module Kit
*
* This kit uses the RevRobotics NEO
*/
dependencies {
api project(":vendor:${rootProject.name}-RevRobotics")
}

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<body>
<p>This API adds official SwerveIO support for the <a
href="https://www.swervedrivespecialties.com/products/mk2-module-kit">Swerve Drive Specialties MK2 Swerve
Module</a> kit. You should have been directed here from the official SwerveIO documentation. If you weren't,
please go there now for instructions on getting this API set up for use.
</p>
</body>
</html>

View file

@ -0,0 +1,121 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.module;
import com.revrobotics.CANSparkMax;
import com.revrobotics.CANSparkMax.IdleMode;
import com.revrobotics.CANSparkMaxLowLevel.MotorType;
import net.bancino.log.Log;
import net.bancino.log.Loggable;
import net.bancino.robotics.swerveio.encoder.AnalogEncoder;
import net.bancino.robotics.swerveio.encoder.Encoder;
import net.bancino.robotics.swerveio.encoder.SparkMaxEncoder;
import net.bancino.robotics.swerveio.geometry.Length;
import net.bancino.robotics.swerveio.geometry.Length.Unit;
/**
* A swerve module implementation that uses RevRobotics Neo motors and Spark Max
* motor controllers. This was designed for Swerve Drive Specialties' MK2
* Module.
*
* <p>
* To use this module implementation, you must have the following hardware
* setup:
* </p>
* <ul>
* <li>2 RevRobotics' NEO brushless motors</li>
* <li>8.33:1 gear ratio drive gearbox</li>
* <li>4-inch wheels</li>
* <li>A 1:1 (analog) encoder</li>
* </ul>
*
* <p>
* This implementation <b>requires</b> a 1:1 encoder. You <b>must</b> attach an
* encoder to the dedicated location on the MK2 kit in order to use this module
* implementation. The Neo internal encoders are absolute garbage.
* </p>
*
* @author Jordan Bancino
* @version 5.0.1
* @since 1.0.0
*/
@Loggable
public class MK2SwerveModule extends GenericSwerveModule {
private final CANSparkMax pivotMotor;
/**
* The swerve module is constructed to allow the pivot motor to coast, this
* allows for adjustments, but as soon as the module is driven, it switches to
* brake mode to prevent outside modifications.
*/
@Log
private boolean setPivotIdleMode = false;
/**
* Create a new MK2 swerve module composed of Neo brushless motors that use the
* Spark Max motor controllers.
*
* @param driveCanId The CAN ID of the drive motor controller for this module.
* @param pivotCanId The CAN ID of the pivot motor controller for this module.
* @param pivotEncoder The encoder to use as the pivot encoder.
*/
public MK2SwerveModule(int driveCanId, int pivotCanId, Encoder pivotEncoder) {
super(new GenericSwerveModule.Builder()
.setDriveMotor(new CANSparkMax(driveCanId, MotorType.kBrushless),
(GenericSwerveModule.Builder builder, CANSparkMax motor) -> {
motor.setIdleMode(IdleMode.kBrake);
builder.setDriveEncoder(new SparkMaxEncoder(motor));
}).setPivotMotor(new CANSparkMax(pivotCanId, MotorType.kBrushless),
(GenericSwerveModule.Builder builder, CANSparkMax motor) -> {
motor.setIdleMode(IdleMode.kCoast); // This is set to brake before we drive
builder.setPivotEncoder(pivotEncoder);
})
// Constants for the MK2 kit and Neo
.setDriveGearRatio(8.33).setDriveMaxRPM(5680).setWheelDiameter(new Length(4, Unit.INCHES)));
pivotMotor = (CANSparkMax) getPivotMotor();
}
/**
* Create a new MK2 swerve module composed of Neo brushless motors that use the
* Spark Max motor controllers.
*
* @param driveCanId The CAN ID of the drive motor for this module.
* @param pivotCanId The CAN ID of the pivot motor for this module.
* @param analogEncoderPort The port on the roboRIO that the encoder to use as
* the pivot encoder is on.
*/
public MK2SwerveModule(int driveCanId, int pivotCanId, int analogEncoderPort) {
this(driveCanId, pivotCanId, new AnalogEncoder(analogEncoderPort));
}
@Override
public void setAngle(double angle) {
/*
* Enable brake mode so that the module doesn't move due to external force after
* starting.
*/
if (!setPivotIdleMode) {
pivotMotor.setIdleMode(IdleMode.kBrake);
setPivotIdleMode = true;
}
super.setAngle(angle);
}
}

24
kit/sds-mk3/build.gradle Executable file
View file

@ -0,0 +1,24 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
/*
* Swerve Drive Specialties' MK3 Swerve Module Kit
*
* This kit uses the CTRE Falcon 500
*/
dependencies {
api project(":vendor:${rootProject.name}-CTRE")
}

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<body>
<p>This API adds official SwerveIO support for the <a
href="https://www.swervedrivespecialties.com/products/mk3-swerve-module">Swerve Drive Specialties MK3 Swerve
Module</a> kit. You should have been directed here from the official SwerveIO documentation. If you weren't,
please go there now for instructions on getting this API set up for use.
</p>
</body>
</html>

View file

@ -0,0 +1,214 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.module;
import com.ctre.phoenix.motorcontrol.can.WPI_TalonFX;
import com.ctre.phoenix.sensors.SensorInitializationStrategy;
import net.bancino.robotics.swerveio.pid.PIDController;
import com.ctre.phoenix.motorcontrol.FeedbackDevice;
//import com.ctre.phoenix.motorcontrol.ControlMode;
//import com.ctre.phoenix.motorcontrol.StatusFrameEnhanced;
import com.ctre.phoenix.motorcontrol.NeutralMode;
import net.bancino.robotics.swerveio.encoder.Encoder;
import net.bancino.robotics.swerveio.encoder.PhoenixCANCoder;
import net.bancino.robotics.swerveio.encoder.PhoenixEncoder;
import net.bancino.robotics.swerveio.geometry.Length;
import net.bancino.robotics.swerveio.geometry.Length.Unit;
/**
* A swerve module implementation that used CTRE Falcon 500 motors and TalonFX
* motor controllers. This implementation was designed for Swerve Drive
* Specialties' MK3 Module, and provides support for the internal Falcon 500
* encoder as well as the external 1:1 encoder recommended by Swerve Drive
* Specialties.
*
* <p>
* To use this module implementation, you must have the following hardware
* setup:
* </p>
* <ul>
* <li>2 CTRE Falcon 500 brushless motors</li>
* <li>8.16:1 gear ratio gearbox</li>
* <li>4-inch wheels</li>
* </ul>
*
* <p>
* Optional hardware:
* </p>
*
* <ul>
* <li>CTRE CANCoder or other external encoder</li>
* </ul>
*
* @author Jordan Bancino
* @version 6.0.2
* @since 4.2.0
*/
public class MK3SwerveModule extends GenericSwerveModule {
private final WPI_TalonFX pivotMotor;
private final WPI_TalonFX driveMotor;
private boolean setPivotIdleMode = false;
/**
* Create a new MK3 swerve module. This constructor assumes a profile slot of
* zero and that you're using the CTRE CANCoder.
*
* @param driveCanId The CAN ID of the drive motor controller for this
* module.
* @param pivotCanId The CAN ID of the pivot motor controller for this
* module.
* @param pivotEncoderCanId The CAN ID of the pivot motor encoder (CANCoder).
*/
public MK3SwerveModule(int driveCanId, int pivotCanId, int pivotEncoderCanId) {
this(driveCanId, pivotCanId, new PhoenixCANCoder(pivotEncoderCanId));
}
/**
* Create a new MK3 swerve module. This constructor assumes a profile slot of
* zero and the encoder is 1:1.
*
* @param driveCanId The CAN ID of the drive motor controller for this module.
* @param pivotCanId The CAN ID of the pivot motor controller for this module.
* @param pivotEncoder The encoder that monitors the pivot motor at a ratio of
* 1:1.
*/
public MK3SwerveModule(int driveCanId, int pivotCanId, Encoder pivotEncoder) {
this(driveCanId, pivotCanId, pivotEncoder, 1);
}
/**
* Create a new MK3 swerve module. This constructor assumes a profile slot of
* zero and uses the internal encoder.
*
* @param driveCanId The CAN ID of the drive motor controller for this module.
* @param pivotCanId The CAN ID of the pivot motor controller for this module.
*/
public MK3SwerveModule(int driveCanId, int pivotCanId) {
this(driveCanId, pivotCanId, null, 6);
}
/**
* Create a new MK3 swerve module. This constructor assumes a profile slot of
* zero.
*
* @param driveCanId The CAN ID of the drive motor controller for this
* module.
* @param pivotCanId The CAN ID of the pivot motor controller for this
* module.
* @param pivotEncoder The encoder that monitors the pivot motor.
* @param pivotGearRatio The gear ratio between the pivot motor and the pivot
* encoder.
*/
public MK3SwerveModule(int driveCanId, int pivotCanId, Encoder pivotEncoder, double pivotGearRatio) {
this(driveCanId, pivotCanId, 0, pivotEncoder, pivotGearRatio);
}
/**
* Create a new MK3 swerve module composed of Falcon 500 motors that use the
* TalonFX motor controllers.
*
* @param driveCanId The CAN ID of the drive motor controller for this
* module.
* @param pivotCanId The CAN ID of the pivot motor controller for this
* module.
* @param pidSlot CTRE motor controllers support multiple PID controller
* slots. Specify the slot you want to use for both the
* drive and pivot encoders, as well as the drive and
* pivot PID controllers. This value is used in this
* module implementation wherever Phoenix requires a slot
* number, so make sure all your motors and encoders use
* the same slot number.
* @param pivotEncoder The encoder that monitors the pivot motor.
* @param pivotGearRatio The gear ratio between the pivot motor and the pivot
* encoder.
*/
public MK3SwerveModule(int driveCanId, int pivotCanId, int pidSlot, Encoder pivotEncoder, double pivotGearRatio) {
super(new GenericSwerveModule.Builder().setDriveMotor(new WPI_TalonFX(driveCanId),
(GenericSwerveModule.Builder builder, WPI_TalonFX motor) -> {
motor.configFactoryDefault();
builder.setDriveEncoder(new PhoenixEncoder(motor, FeedbackDevice.IntegratedSensor, pidSlot));
motor.setNeutralMode(NeutralMode.Brake);
}).setPivotMotor(new WPI_TalonFX(pivotCanId),
(GenericSwerveModule.Builder builder, WPI_TalonFX motor) -> {
motor.configIntegratedSensorInitializationStrategy(
SensorInitializationStrategy.BootToAbsolutePosition);
motor.setNeutralMode(NeutralMode.Coast); // This is set to brake before we drive
if (pivotEncoder != null) {
builder.setPivotEncoder(pivotEncoder);
} else {
builder.setPivotEncoder(
new PhoenixEncoder(motor, FeedbackDevice.IntegratedSensor, pidSlot));
}
motor.setInverted(true);
})
.setDriveGearRatio(8.16).setPivotGearRatio(pivotGearRatio)
/* 6380 RPM x 75% = 4785 */
.setDriveMaxRPM(4785).setWheelDiameter(new Length(3.875, Unit.INCHES)));
pivotMotor = (WPI_TalonFX) getPivotMotor();
driveMotor = (WPI_TalonFX) getDriveMotor();
/* Sensible defaults for the Falcon 500 using the integrated encoder. */
PIDController drivePIDController = getDrivePIDController();
drivePIDController.setP(0.00008);
drivePIDController.setI(0.000007);
drivePIDController.setD(0.0);
drivePIDController.setF(0.00002);
drivePIDController.setOutputLimits(1);
}
@Override
public void setAngle(double angle) {
/*
* Enable brake mode so that the module doesn't move due to external force after
* starting.
*/
if (!setPivotIdleMode) {
pivotMotor.setNeutralMode(NeutralMode.Brake);
setPivotIdleMode = true;
}
super.setAngle(angle);
}
@Override
public void setSpeed(double speed) {
/*
* The CTRE motor controller expects a velocity in encoder counts per 100ms.
* This converts RPMS to counts per 100ms based on the number of encoder counts
* there are in a revolution of the motor shaft, then scales it on the percent
* output reference we're given.
*
* In hindsight, we should have converted the sensor velocity into RPMs so we
* could create a unified velocity loop API, but once upon a time we tried to
* use the motor controller's PID loop, hence this conversion.
*/
double setpoint = speed * (getDriveMaxRPM() * getDriveEncoder().countsPerRevolution()) / 600.0;
double feedback = driveMotor.getSelectedSensorVelocity();
double output = getDrivePIDController().getOutput(feedback, setpoint);
driveMotor.set(output);
}
@Override
public double getOutputThreshhold() {
return 0.02;
}
}

16
misc/virt/build.gradle Executable file
View file

@ -0,0 +1,16 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<body>
<p>
This is a virtual swerve drive plugin for SwerveIO. It is designed to be used for unit tests and simulations.
The classes provided by this library are intended to be as minimal as possible and they rely heavily on the
SwerveIO library. This is so that all the SwerveIO code can still run during unit tests and simulations, to
achieve the most realistic effect.
</p>
</body>
</html>

View file

@ -0,0 +1,71 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio;
import edu.wpi.first.wpilibj.motorcontrol.MotorController;
import net.bancino.log.Log;
import net.bancino.log.LogLevel;
import net.bancino.log.Loggable;
/**
* A virtual motor that implements the WPILib {@code MotorController} interface.
* This does not map to any hardware so it can be used for testing. Note,
* however, that this will not change any encoders or gyro angles.
*
* @author Jordan Bancino
* @version 6.1.0
* @since 6.1.0
*/
@Loggable
public class VirtualMotor implements MotorController {
@Log(as = "rawValue", atLevel = LogLevel.IMPORTANT)
private double speed;
@Log(atLevel = LogLevel.IMPORTANT)
private boolean inverted;
@Override
public void disable() {
stopMotor();
}
@Override
public double get() {
return speed;
}
@Override
public boolean getInverted() {
return inverted;
}
@Override
public void set(double speed) {
this.speed = speed;
}
@Override
public void setInverted(boolean isInverted) {
this.inverted = isInverted;
}
@Override
public void stopMotor() {
set(0);
}
}

View file

@ -0,0 +1,56 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio;
import net.bancino.robotics.swerveio.module.SwerveModule;
import java.util.Map;
import net.bancino.robotics.swerveio.geometry.ChassisDimension;
import net.bancino.robotics.swerveio.geometry.Length;
import net.bancino.robotics.swerveio.geometry.Length.Unit;
import net.bancino.robotics.swerveio.module.VirtualSwerveModule;
/**
* Static methods dealing with a virtual swerve drive (see {@link #create()}).
*
* @author Jordan Bancino
* @version 6.1.0
* @since 6.1.0
*/
public class VirtualSwerveDrive {
/**
* Create a virtual swerve drive that utilizes all SwerveIO code, but doesn't
* map to any hardware. This is used primarily for testing SwerveIO code. It
* directs all outputs to an entirely virtual swerve module, which is backed by
* virtual motors and encoders that are updated using arbitrary characteristics.
*
* @return A swerve drive that does not map to any hardware. It can be used in
* unit tests.
*/
public static SwerveDrive create() {
return new SwerveDrive.Builder().useDefaultKinematics(new ChassisDimension(new Length(25, Unit.INCHES)))
.setModuleMap((Map<SwerveModule.Location, SwerveModule> map) -> {
map.put(SwerveModule.Location.FRONT_RIGHT, new VirtualSwerveModule());
map.put(SwerveModule.Location.FRONT_LEFT, new VirtualSwerveModule());
map.put(SwerveModule.Location.REAR_LEFT, new VirtualSwerveModule());
map.put(SwerveModule.Location.REAR_RIGHT, new VirtualSwerveModule());
}).build();
}
}

View file

@ -0,0 +1,62 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.encoder;
import net.bancino.log.Log;
import net.bancino.log.LogLevel;
import net.bancino.log.Loggable;
/**
* A virtual encoder that implements the SwerveIO encoder interface. This does
* not map to any hardware so it can be used for testing. Note, however, that
* this must be manually updated by some method using {@code set()} because
* otherwise, the reading will not change.
*
* <p>
* This emulates a quadrature encoder, so there are 4096 counts per revolution
* as reflected by {@link #countsPerRevolution()}.
* </p>
*
* @author Jordan Bancino
* @version 6.1.0
* @since 6.1.0
*/
@Loggable
public class VirtualEncoder implements Encoder {
@Log(as = "rawValue", atLevel = LogLevel.IMPORTANT)
private double value;
/**
* Set this encoder's value to an arbitary number.
*
* @param value The raw value to set the encoder to.
*/
public void set(double value) {
this.value = value;
}
@Override
public double get() {
return value;
}
@Override
public double countsPerRevolution() {
return 4096;
}
}

View file

@ -0,0 +1,70 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.module;
import net.bancino.robotics.swerveio.geometry.Length;
import net.bancino.robotics.swerveio.geometry.Length.Unit;
import net.bancino.robotics.swerveio.VirtualMotor;
import net.bancino.robotics.swerveio.encoder.VirtualEncoder;
/**
* A virtual swerve module that implements the SwerveIO swerve module interface.
* THis does not map to any hardware so it can be used for testing. The methods
* in this interface automatically updates the encoders based on the motor
* speeds.
*
* @author Jordan Bancino
* @version 6.1.0
* @since 6.1.0
*/
public class VirtualSwerveModule extends GenericSwerveModule {
/**
* Create an virtual swerve module with arbitrary parameters using virtual
* motors and encoders.
*/
public VirtualSwerveModule() {
super(new GenericSwerveModule.Builder().setDriveMotor(new VirtualMotor()).setDriveEncoder(new VirtualEncoder())
.setPivotMotor(new VirtualMotor()).setPivotEncoder(new VirtualEncoder()).setAngleOffset(0)
.setDriveGearRatio(10).setPivotGearRatio(5).setDriveMaxRPM(5000)
.setWheelDiameter(new Length(4, Unit.INCHES)));
}
@Override
public void setAngle(double angle) {
super.setAngle(angle);
/* Update the encoder based on the motor speed. */
VirtualEncoder encoder = (VirtualEncoder) getPivotEncoder();
encoder.set(encoder.get() + (encoder.countsPerRevolution() * getPivotMotor().get()));
}
@Override
public void setSpeed(double speed) {
super.setSpeed(speed);
/* Update the encoder based on the motor speed. */
VirtualEncoder encoder = (VirtualEncoder) getDriveEncoder();
encoder.set(encoder.get() + (encoder.countsPerRevolution() * getDriveMotor().get()));
}
@Override
public double getOutputThreshhold() {
return 0.0;
}
}

39
settings.gradle Executable file
View file

@ -0,0 +1,39 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
rootProject.name = 'SwerveIO'
/*
* The Maven artifact name is the project name. This allows us
* to include subprojects with a given name, to make the Maven artifacts
* more descriptive and user-friendly.
*/
def includeAs = { String proj, String name ->
include proj
project(proj).name = "${rootProject.name}-${name}"
}
/* Hardware Vendors */
includeAs(':vendor:ctre', 'CTRE')
includeAs(':vendor:kauai', "KauaiLabs")
includeAs(':vendor:rev', "RevRobotics")
/* Module Kits */
includeAs(':kit:sds-mk2', 'SDS-MK2')
includeAs(':kit:sds-mk3', 'SDS-MK3')
/* Misc. addons/plugins */
includeAs(':misc:virt', "Virtual")

744
src/javadoc/overview.html Executable file
View file

@ -0,0 +1,744 @@
<!DOCTYPE html>
<html>
<head>
<title>SwerveIO User Documentation Overview</title>
</head>
<body>
<h1 id="user-documentation">User Documentation</h1>
<p>
SwerveIO is an open-source swerve drive library written for FRC in Java. It is pronounced "Swerve - ee - oh",
which rhymes with "Oreo". You are currently viewing the official, complete user documentation for this release.
Developer documentation is available on the project source page.
</p>
<p>
SwerveIO is intended to be capable of driving every FRC swerve drive that exists. It features an extremely
standard collection of interfaces that all modules, PID controllers, and encoders must adhere to. This makes
porting your robot code from swerve drive to swerve drive incredibly simple.
</p>
<p>
If you need additional help beyond this documentation, please reach out! I would be more than willing to help
you and your team. You can always
email me at <a href="mailto:jordan@bancino.net">jordan@bancino.net</a>.
</p>
<h2 id="contents">Table of Contents</h2>
<ul>
<li><a href="#introduction">Introduction</a></li>
<li><a href="#features">Features</a></li>
<li><a href="#dependencies">Dependencies</a></li>
<li><a href="#compatibility">Compatibility</a></li>
<li><a href="#organization">Organization</a></li>
<li><a href="#getting-started">Getting Started</a></li>
<li>
<a href="#supported-hardware">Supported Hardware</a>
<ul>
<li><a href="#hardware-vendors">Hardware Vendors</a></li>
<li><a href="#kits">Kits</a></li>
</ul>
</li>
<li><a href="#terminology">Terminology</a></li>
<li>
<a href="#appendix">Appendix</a>
<ul>
<li><a href="#adding-new-hardware">Adding New Hardware</a></li>
<li><a href="#finding-angle-offsets">Finding Angle Offsets</a></li>
<li><a href="#vendor-specific-tuning">Vendor-Specific Tuning</a></li>
<li><a href="#show-me-code">Show Me Code!</a></li>
<li><a href="#dashboard">Dashboard</a></li>
</ul>
</li>
<li><a href="#license">License</a></li>
<li><a href="#packages">Packages</a></li>
</ul>
<h2 id="introduction">Introduction</h2>
<p>
SwerveIO aims to be as simple and easy to use as possible, yet also powerful, so that rookies and veterans alike
can get the most out of their swerve drive. Everything in this library can be extended and built upon to fit the
widest variety of needs. SwerveIO handles all the complex math in an object-oriented way that Java programmers
should be familiar with. All you need to do is describe your hardware setup, and SwerveIO will do the rest.
</p>
<p>
SwerveIO is extremely modular. As you will notice, there are many packages, classes and interfaces that make
up the entire SwerveIO API. It is designed to be extensible both in it's current form, and as development
progresses. The design goals of SwerveIO are as follows:
</p>
<ul>
<li>To provide an easy start for swerve drive beginners</li>
<li>To allow customization at levels not found in other swerve drive libraries</li>
<li>To give complete control over the swerve drive to the programmer, no matter his/her level of experience
</li>
<li>
To provide a feature-complete API that handles everything from the motors and encoders to a functional
autonomous.
</li>
</ul>
<p>
Whether you're a first-year student, or 11<sup>th</sup>-year mentor, SwerveIO is designed to meet your
needs. The internals of the API are masked by default so as not to scare rookies off, but can be easily exposed
and customized by experienced programmers. The target audience of this documentation is anyone interested in
learning how to program an FRC swerve drive with SwerveIO. Ultimately, the math and science behind swerve drive
is beyond the scope of this documentation, though the kinematics package does provide a brief overview of why
things work the way they do, and links to documents containing the math that is implemented in this library.
</p>
<p>
This documentation is standard JavaDoc documentation, so please familiarize yourself with JavaDoc before
continuing. This documentation assumes you are profficient in the Java programming language, the JavaDoc
documentation system, and a build system with which you can comfortably build and deploy Java-based
projects. A basic understanding of the git version control system may also be required for some sections of this
document. Each component of the SwerveIO API will have thorough comments on the relevant pages of
this documentation.
</p>
<h2 id="features">Features</h2>
<p>
SwerveIO
</p>
<ul>
<li>is <strong>expandable</strong>: A collection of interfaces allow the use of any motor controllers and
encoders, in any combination. All the classes used in this library are open and can be used independently if
desired,
such as <code>DefaultSwerveKinematics</code> or the PID controller (<code>DefaultPIDController</code>).
Almost every component of SwerveIO can be replaced with custom implementations as well.</li>
<li>has <strong>sensible defaults</strong>: SwerveIO strives to provide swerve module implementations
for popular hardware configurations, including REVRobotics NEOs and CTRE Falcon 500s. If Team 6090 has
experience with it on swerve, there will be a working implementation here. You're also more than welcome to
add your own module implementations to this library if you wish to officially support them. Please don't add
custom modules, but if it's a kit, by all means, please help support it!</li>
<li>is written in <strong>Java</strong>: Written in Java by Java developers, SwerveIO takes advantage of the
Java language and follows all the conventions of Java libraries. This makes for seamless integration with
your Java robotics projects.</li>
<li>
is <strong>simple</strong>: All the hard work is done beneath the abstraction layers of this library, so all
you need to do is describe the hardware using the declarative API.
</li>
<li>
offers <strong>automatic control</strong>: Just input your swerve drive specs. SwerveIO performs all the
calculations needed for driving, and runs it's own control loops.
</li>
<li>provides an <strong>advanced logging API</strong>: SwerveIO uses a home-grown logging library to log the
complete state of the swerve drive as it moves. This API allows you to output data to either a file, the
NetworkTables, a database, a server, or more! Default implementations include:
<ul>
<li><strong>DashboardLog</strong>: Log all swerve data to the dashboard so that they can
be viewed live as you're running.</li>
<li><strong>JSON Logger</strong>: Log swerve data to a JSON file that can then be parsed and graphed if
desired.
</li>
<li><strong>Build your own</strong>: Take a look at the default implementations to give you some
inspiration, then write your own logger, and if you'd like, submit it to the SwerveIO project.</li>
</ul>
</li>
</ul>
<h2 id="dependencies">Dependencies</h2>
<p>
The main SwerveIO API, called SwerveIO Core, is very lightweight. There are no dependencies beyond the official
FRC WPILib project. There is no reliance on any other third-party libraries for implementing any part of
SwerveIO; SwerveIO simply supplements the dependencies listed here. As of this release, the complete list of
dependencies that SwerveIO declares is as follows:
</p>
<ul>
<li>
<b>WPILib</b> &mdash; SwerveIO depends on various parts of the WPILibJ library, including but not
limited to the following components:
<ul>
<li>
The "new" command framework provided by the <code>edu.wpi.first.wpilibj2</code> package
</li>
<li>
The driver station and dashboard
</li>
<li>
The kinematics and trajectory packages
</li>
<li>
The filesystem (for PathWeaver trajectories)
</li>
<li>
Various components like speed controllers and encoders
</li>
</ul>
</li>
<li>
Vendor libraries for driving supported hardware.
</li>
</ul>
<h2 id="compatibility">Compatibility</h2>
<p>This release of SwerveIO is intended to be compatible with the following software:</p>
<ul>
<li><b>WPILib</b> 2022.1.1+</li>
<li><b>Gradle</b> 7.3.3+</li>
<li><b>Java</b> 11.0.12+</li>
</ul>
<p>
This release of SwerveIO is intended to be compatible with the hardware implementations
provided by the official hardware artifacts. All the subpackages of SwerveIO Core provide generic interfaces
that you can use to support your own hardware. If support for your hardware is not provided by an official
artifact, adding support for it is trivial and can be accomplished by any Java developer using this
documentation. One can easily add custom motor controllers, encoders, PID controllers, loggers, and more using
the SwerveIO interfaces present and documented in their relevant subpackages.
</p>
<p>
For a list of kit assemblies that are officially supported by SwerveIO, see the <a
href="#supported-hardware">Supported Hardware</a> section. If you don't see your kit listed, supporting it
should be easy, but feel free to reach out to us so we can help you.
</p>
<h2 id="organization">Organization</h2>
SwerveIO's API is structured in such a way that the <code>SwerveDrive</code> class is the high-level
glue that binds the following lower-level components together:
<ol>
<li>
<b>Swerve Module</b> &mdash; A collection of two motor controllers and two encoders. Modules are also
responsible for providing position loop controllers to achieve the angles assigned to them. Hardware
implementations are provided by external artifacts not included with SwerveIO Core.
</li>
<li>
<b>Kinematics Provider</b> &mdash; Vector computation. Given a a 3-dimensional input,
the kinematics provider generates a vector for every swerve module.
</li>
<li>
<b>Gyro</b> &mdash; The gyro is optional, but it provides field-centric navigation and closed-loop anglular
control if it is present. Gyro support is provided by external artifacts not included with SwerveIO Core.
</li>
</ol>
<p>
SwerveIO also provides some additional higher-level features as well:
</p>
<ul>
<li>
<b>Commands</b> &mdash; Commands utilize the swerve drive API to provide fully-functional
waypoint autonomous using WPILib Pathweaver, and a joystick command that generates vectors
to provide an intuitive operator interface.
</li>
<li>
<b>Loggers</b> &mdash; SwerveIO's <code>LogHandler</code> interface is intuitive and helpful in debugging.
It allows you to monitor the state of the swerve drive over time. You can log to files, the dashboard, and
more.
</li>
</ul>
<p>
SwerveIO embraces "functional programming", which allows the programmer to pass a function in
as a parameter to a method or constructor. Functional programming is an efficient way program,
and it may not be possible to avoid it when working with the SwerveIO API. Please make sure
you understand functional programming in Java, and be familiar with lambda expressions, as these
greatly simplify the code that needs to be written. Some of the design choices that were made were done so to
provide a declarative API with which you simply describe your swerve drive configuration.
</p>
<h2 id="getting-started">Getting Started</h2>
<p>
This documentation is not intended to be read sequentially, but it would be adventagous to take
a top-down approach to it. Begin with the base package, the commands, and the loggers, all of
which make up the high-level API, then work your way down to the kinematics provider and module
interfaces. It is necessary to have a deep understanding of the way SwerveIO works and how it
is used by the high-level programmer before any attempt to extend hardware support or other
low-level functionality is made. If you are new to SwerveIO, it would be beneficial to start
with the
<a href="net/bancino/robotics/swerveio/SwerveDrive.Builder.html"><code>SwerveDrive.Builder</code></a>
class documentation, available in the
<a href="net/bancino/robotics/swerveio/package-summary.html"><code>net.bancino.robotics.swerveio</code></a>
package. That page will explain what is necessary to create a swerve drive subsystem. Next,
it would be wise to check out the command package, available in
<a href="net/bancino/robotics/swerveio/command/package-summary.html">
<code>net.bancino.robotics.swerveio.command</code></a>,
which contains code for driving the swerve drive with a joystick and autonomously.
</p>
<p>
If you aren't obtaining SwerveIO from a repository using the standard WPILib vendor library procedure, you can
use SwerveIO in a Gradle composite build. Note that there are no official binary distributions of SwerveIO; it
is only available as source code. If you are using a binary distribution, refer to the distribution
documentation to use SwerveIO with your project. To use SwerveIO in a Gradle composite build, download and
extract the code to a subdirectory of your robot code.. Then add the following line to your
<code>settings.grade</code> to include the local copy of SwerveIO:
</p>
<pre>
includBuild "SwerveIO-vX.X.X"
</pre>
<p>
Note that <code>X.X.X</code> is the version of SwerveIO you downloaded. You may want to build your robot code
with the following command to ensure it works properly:
</p>
<pre>
$ gradle build
</pre>
<p>
Note that using a composite build still requires you to have the vendor dependency files installed, or you'll
have to manually add the maven artifacts to your <code>build.gradle</code>.
</p>
<p>
If you're at a competition, or just want faster builds, after running the above command to ensure all
dependencies are resolved, you can add the <code>--offline</code> flag to disable all build features that
require the internet.
</p>
<p>
This documentation assumes that you have successfully configured SwerveIO for use in your robot code
by adding it to your build system of choice. SwerveIO is a <a href="https://gradle.org">Gradle</a>
project, which uses the <a href="https://maven.apache.org">Maven</a> repository model. FRC switched
from <a href="https://ant.apache.org">Apache Ant</a> to Gradle a few years ago. Whatever your build
system, it is assumed that you have configured it to allow the use of SwerveIO. This documentation
only details the SwerveIO API. Anything upon which the understanding and usage of this API depends
is considered a prerequisite that is to be fulfilled by the reader.
</p>
<h2 id="supported-hardware">Supported Hardware</h2>
<p>
Starting with SwerveIO 6.0.0, the entire SwerveIO API is broken up into three types of components:
</p>
<ul>
<li>
<b>SwerveIO Core</b> &mdash; The main API. This contains all the hardware interfaces, the main
<code>SwerveDrive</code> subsystem, and commands that drive the SwerveIO drive system.
</li>
<li>
<b>Hardware Vendors</b> &mdash; Support for encoders and motor controllers. These components bind the
hardware vendor APIs to the SwerveIO API and are the mediators between the SwerveIO core API and the kit
implementations.
</li>
<li>
<b>Kits</b> &mdash; Kits use the hardware vendor APIs and provide constants such as gear ratios and motor
rotations These components also provide sensible tuning parameters as defaults.
</li>
</ul>
<p>
Each component is maintained as a Gradle subproject in the SwerveIO repository and may be published as a
separate Maven artifact. Below is an exhaustive list of all the officially supported hardware. You can add this
hardware support to your code by dropping the vendor JSON files generated by the <code>publish</code> Gradle
task into your <code>vendordeps</code> folder.
</p>
<h3 id="hardware-vendors">Hardware Vendors</h3>
<table>
<caption style="display: none;">Supported Hardware Vendors</caption>
<tr>
<th>Vendor</th>
<th>Hardware</th>
</tr>
<tr>
<td>Cross The Road Electronics</td>
<td>All motor-attached encoders, and the CANCoder</td>
</tr>
<tr>
<td>RevRobotics</td>
<td>Spark Max-attached encoders</td>
</tr>
<tr>
<td>Kauai Labs</td>
<td>NavX Gyro</td>
</tr>
</table>
<h3 id="kits">Kits</h3>
<table>
<caption style="display: none;">Supported Kits table</caption>
<tr>
<th>Vendor</th>
<th>Kits</th>
</tr>
<tr>
<td>Swerve Drive Specialties</td>
<td>MK2, MK3</td>
</tr>
</table>
<p>
These kits will automatically pull in the required vendor dependencies, so you don't have to include hardware
libraries explicitly unless you're using them outside of the kit implementation.
</p>
<p>
If you want to mock up a swerve drive configuration for unit testing or other experimental purposes, you can
pull in the <code>SwerveIO-Virtual</code> project, which provides a swerve module implementation that is not
tied to any hardware at all.
</p>
<h2 id="terminology">Terminology</h2>
<p>
There are a number of different terms that this library uses. It is important to understand them
to understand how this library works. The terms used throughout this documentation and the SwerveIO
code are listed below, and defined how they are used in this library.
</p>
<ul>
<li>
A <b>swerve module</b> is a single unit of the swerve drive, comprised of a pivot motor and a drive motor
attached to a gearbox and a wheel. A SwerveIO swerve drive is made up of exactly four swerve modules.
</li>
<li>
<b>Field-Centric Navigation</b> refers to using a gyroscope to determine the orientation of the
robot and driving in a consistent way. This is commonly used when the driver is not inside
the swerve vessel because it allows the driver to drive the robot without any regard to the
orientation of it. Due to the nature of swerve drive, the robot rotates a lot, so it can be
a burden on the driver to conciously recall the "front" orientation in order to properly drive
it. By using field-centric navigation, the robot is driven the same way no matter it's orientation.
When configured properly, whether the robot is forward, backward, or at any angle in between,
pressing the joystick away from the driver will result in the robot moving away from the
driver.
</li>
<li>
<b>Pivot</b> is the term used to describe the motor and motion of the individual swerve modules
when they rotate in place. Some refer to this as "azimuth", but "pivot" is easier to say and
is thus more likely to be discussed. The word "steering" may also be used in place of "pivot", though
"pivot" is more appropriate because the pivot motors do not "steer" the swerve drive in any sense, they
merely "pivot" the drive wheels.
</li>
<li>
A <b>vector</b> in SwerveIO is an multi-dimensional data object. Vectors contain a magnitude and
a direction, but magnitudes and directions may be represented differently. In the case of
<code>SwerveVector</code>, direction is indicated with the sign of the values, but <code>ModuleVector</code>
has an <code>angle</code> field that represents the direction. It all depends on the use of the vector. All
vectors also have very specific bounds, so please read the documentation on them before using them.
</li>
</ul>
<!-----------------------------------------------------------------------------------------------------------------
APPENDIX
------------------------------------------------------------------------------------------------------------------>
<h2 id="appendix">Appendix</h2>
<p>
This appendix provides small help documents to get you started working with SwerveIO. Everything covered in this
appendix is available in the standard Java documentation, but these entries are designed to provide "tips and
tricks" for using SwerveIO. Things that may not be immediately obvious from reading the documentation but are
common enough tasks are documented here.
</p>
<h3 id="adding-new-hardware">Adding New Hardware</h3>
<p>
Adding new hardware support for SwerveIO is quite simple using <code>GenericSwerveModule</code>.
<code>GenericSwerveModule</code> allows you to declare the hardware properties of your swerve module.
The point of adding hardware-specific classes to SwerveIO is to provide a convenient place for tuning parameters
and direct hardware configuration. End users should be able to simply instantiate a new module object, say
<code>MK3SwerveModule</code> without caring what the gear ratios or PID parameters are. Hardware-specific swerve
module classes make this possible by hard-coding in all the required properties such as gear ratios, motor RPMS,
and wheel diameters. It is also highly recommended to provide sensible default PID tuning parameters. Any
module-specific initialization that needs to occur (such as reversing the direction of a motor) should also
happen here so that users can simply instantiate the module and start running.
</p>
<p>
SwerveIO provides "official" support for multiple swerve modules, which means that all the numbers have been
crunched and hard-coded for you if you choose to use those modules. If you choose not to use "official" modules,
you'll have to write your own class for them. This appendix entry aims to assist you in this task, which is easy
from a programming perspective, but can be difficult because there are lots of numbers that need to be
calculated or discovered by experimentation.
</p>
<p>
At the very least, you'll need the following measurements:
</p>
<ul>
<li>
The drive gear ratio (input / output)
</li>
<li>
The drive motor's maximum theoretical (or actual) rotations per minute.
</li>
<li>
The module wheel's diameter.
</li>
</ul>
<p>
You also need to consider your encoder source. If you are <i>not</i> using an external 1:1 encoder for the pivot
motor, you must also provide a pivot gear ratio. This most likely isn't documented well if you're using a kit,
because kit builders often push you to use the 1:1 encoder, so you may need to verify this ratio experimentally.
</p>
<p>
If you're using an integrated encoder, you may need to reverse the direction of the pivot motor by calling
<code>setInverted()</code> on it. If you get your swerve drive running but the wheels don't go to the correct
angles, try this before changing anything else.
</p>
<p>
Regardless of what encoder you're using, be it external or integrated, it has to implement the
<code>Encoder</code> interface. The <code>Encoder</code> interface is a simple wrapper for hardware
vendor-specific encoder reading classes that enables any encoder to be used with SwerveIO. See the <a
href="net/bancino/robotics/swerveio/encoder/Encoder.html">Encoder</a> documentation for the
specifications. SwerveIO already has support for multiple encoders, so check the encoder package before
implementing the interface for yourself.
</p>
<p>
Once you have all the required information, as well as an <code>Encoder</code> class, create a new class that
extends <code>GenericSwerveModule</code>. Consider accepting the following in your constructor:
</p>
<ul>
<li>
The drive motor CAN ID.
</li>
<li>
The pivot motor CAN ID.
</li>
</ul>
<p>
These things will differ from robot to robot and even module to module, so you don't want to make any
assumptions on them. You may also consider accepting a pivot encoder in your constructor as well if you aren't
using the encoder integrated into the pivot motor (if equipped). If you want to support both CAN and PWM, you
may also provide constructors for these, or add an enumeration to allow users to specify the mode of operation.
</p>
<p>
The next step is to call the super constructor with an annonymous builder object. This is precisely what the
"offical" modules do. Consider the following example:
</p>
<pre>
import net.bancino.robotics.swerveio.module.GenericSwerveModule;
import net.bancino.robotics.swerveio.geometry.Length;
import net.bancino.robotics.swerveio.geometry.Length.Unit;
public class MySwerveModule extends GenericSwerveModule {
public MySwerveModule(int driveCanId, int pivotCanId) {
super(new GenericSwerveModule.Builder()
.setDriveMotor(/* Create your drive motor here */, (builder, motor) -&gt; {
/* Configure/initialize the drive motor in this block */
builder.setDriveEncoder(/* Create your drive encoder here */);
})
.setPivotMotor(/* Create your pivot motor here */, (builder, motor) -&gt; {
/* Configure/initialize the pivot motor in this block */
builder.setPivotEncoder(/* Create your pivot encoder here */);
})
/* Declare the module specifications */
.setDriveGearRatio(/* Set the drive gear ratio here */)
.setPivotGearRatio(/* Set the pivot gear ratio here */) /* Only required if encoder isn't 1:1 */
.setWheelDiameter(/* Set the wheel diameter here */)
.setMaxDriveRPM(/* Set the drive motor's max drive RPMs here */)
);
}
}
</pre>
<p>
It is highly recommended to follow the constructor conventions of the modules implemented in this class, so you
don't confuse other potential users. If you did everything properly, that is a complete swerve module
implementation. As you can see, it declaratively configures and initializes the hardware. SwerveIO will take
care of the rest. You should now be able to add instances of your swerve module implementation to the SwerveIO
module map. See <a href="#show-me-code">Show Me Code</a> and <a
href="net/bancino/robotics/swerveio/SwerveDrive.Builder.html">SwerveDrive.Builder</a> for more details.
</p>
<h3 id="finding-angle-offsets">Finding Angle Offsets</h3>
<p>
Generating the swerve module angle offsets necessary for the pivot functionality can be the most difficult
part of setting up your swerve drive. This appendix entry provides detailed instructions for successfully
completing this task with SwerveIO. <b>These instructions only apply for <i>absolute</i> encoders. If you use
relative encoders, you will have to align your swerve drive each time you power on your robot.</b>
</p>
<p>
The first step is to ensure that your swerve modules are placed in their forward position. This means making
sure all the wheels are aligned and pointed to the front of your robot. You can use a custom alignment frame, or
a couple pieces of long wood, metal, or pipe. Team 6090 uses thin blocks of metal that are long enough to reach
across both modules on one side. We first rotate the modules by hand until they're generally in the forward
position, then we press the long metal into both wheels, ensuring that they are even with each other. We then
repeat on the other side of the swerve chassis.
</p>
<p>
As of SwerveIO 6.1.0, you no longer need to manually calculate the angle offsets. At this point, once you're
aligned, you can call <a
href="net/bancino/robotics/swerveio/SwerveDrive.html#saveAngleOffsets()">saveAngleOffsets()</a>, either on
code initialization or via a command&mdash;see the <code>SaveSwerveAngleOffsets</code> command. It's up to you
how you want to do this. Team 6090 recommends mapping a
dashboard button for this purpose, and this functionality is provided in the <code>SaveSwerveAngleOffsets</code>
command. After the initial save, you can instruct SwerveIO to initialize with these
offsets using <a
href="net/bancino/robotics/swerveio/SwerveDrive.html#loadAngleOffsets()">loadAngleOffsets()</a>.
</p>
<p>
If you wish to manually set the offsets, you need some kind of feedback to the driver station. See <a
href="#shuffleboard">Shuffleboard</a> for instructions on using Shuffleboard to get information on your
swerve drive.
</p>
<p>
In your robot code, ensure that no existing offsets are applied. This means setting all your module offsets to
zero. This ensures that you are getting an accurate angle reading. If all your modules are aligned, write down
the current angle readings for each module. Be as precise as possible. These angles are arbitrary because they
are calculated from the encoder, which is most likely not "zero" at the forward position, which is why this
angle offset is necessary <b>The current reported angle of the swerve module when it is aligned is the offset
for that module.</b> You can provide these numbers wherever an "angle offset" value is required.
</p>
<p>
You don't have to perform this process every time you use your robot; absolute encoders store their position
across power losses, so your swerve drive will remember these angles. The offset basically just tells SwerveIO
the encoder reading at which each swerve module is in it's forward position. You <b>will</b> have to repeat this
process every time you disassemble the swerve module, because the motor, motor shaft, gears, or wheel alignment
may be modified during assembly or repairs. Team 6090 also "re-calibrates" the swerve drive before every
competition to ensure accuracy.
</p>
<h3 id="vendor-specific-tuning">Vendor-Specific Tuning</h3>
<p>
Wherever possible, SwerveIO provides a unified interface for all vendor-specific classes and methods. This
method is not flawless, because often features don't line up exactly, which can result in some PID controller
methods throwing an exception if a particular feature isn't supported by the underlying vendor library. On the
flip side, sometimes vendors offer functionality not explicitly required for SwerveIO's operation, which means
these features are inaccessible through the SwerveIO interfaces. Luckily, it is not that difficult to access
these vendor-specific features through casting. This requires knowledge of the underlying vendor libraries and a
particular swerve module implementation. If needed, please take a look at the source code so you can understand
how the following example works in order to apply it to your own situation.
</p>
<p>
Consider that you are using <code>MK3SwerveModules</code> and you are running a velocity loop on the drive
motors. You can set PIDF values using SwerveIO's <code>PhoenixPIDController</code>, but that only provides basic
configuration. The CTRE Phoenix library provides a large amount of configuration options that SwerveIO can't
possibly hope to provide support for. To access these options, familarize yourself with
<code>SwerveDrive.Builder</code>'s <a
href="net/bancino/robotics/swerveio/SwerveDrive.Builder.html#setModuleMap(java.util.function.Consumer,java.util.function.Consumer)">setModuleMap()</a>
methods. One implementation of <code>setModuleMap()</code> takes a module initializer, which can be used to
perform additional initialization on a swerve module before passing it to the swerve drive controller. It is
through this method that you can access all the methods of the <code>SwerveModule</code> interface, and thus
access vendor-specific methods as well.
</p>
<p>
Here's an example snippet. Refer to <a href="#show-me-code">Show Me Code</a> to see where this fits into your
swerve drive setup code.
</p>
<pre>
/* swerve is a reference to a SwerveDrive */
swerve.forEachModule((location, module) -&gt; {
/* The code in this block is run on all the swerve modules in the module map. */
/* Use SwerveIO interface methods to set tuning parameters. This will run on any module. */
PIDController pid = module.getPivotPIDController();
pid.setP(0.1);
pid.setI(0.001);
pid.setD(0);
/* Use vendor-specific method via casting. This will only run on modules that use TalonFX controllers. */
WPI_TalonFX driveMotor = (WPI_TalonFX) module.getDriveMotor();
driveMotor.clearStickyFaults();
driveMotor.setIntegralAccumulator(0.001);
});
</pre>
<p>
We are most interested in the last few lines. Here, you can see that we cast the swerve module's drive motor to
a <code>WPI_TalonFX</code> (remember that we're using an <code>MK3SwerveModule</code>, which uses those motor
controllers) to access additional configuration parameters. This is the easiest way to set custom configuration
parameters on vendor-specific hardware for every swerve module. This method depends on you knowing the
underlying motor controller implementation being used by a swerve module. This information is all documented in
the respective documentation.
</p>
<h3 id="show-me-code">Show Me Code!</h3>
<p>
It's up to you to read the relevant documentation for getting SwerveIO up and running, but if you're really
struggling putting the pieces together, here's an example that uses SwerveIO with <code>MK3SwerveModule</code>.
You should get the general idea, and indeed you should be able to copy-paste this example, making only minor
adjustments, but it is not recommended you do so because you won't learn how SwerveIO works. This listing is for
reference only, and even though you may use a different setup, you'll find this to be a good template. Also
remember that this is not a complete reference; it does not showcase all SwerveIO features in the slightest. For
that you'll have to read the documentation.
</p>
<pre>
package frc.robot.subsystems;
import net.bancino.robotics.swerveio.SwerveDrive;
import net.bancino.robotics.swerveio.module.SwerveModule;
import net.bancino.robotics.swerveio.module.MK3SwerveModule;
import net.bancino.robotics.swerveio.gyro.Gyro;
import net.bancino.robotics.swerveio.geometry.Length;
import net.bancino.robotics.swerveio.geometry.Length.Unit;
import net.bancino.robotics.swerveio.geometry.ChassisDimension;
import net.bancino.robotics.swerveio.pid.PIDController;
import net.bancino.robotics.swerveio.log.DashboardLog;
/**
* The drivetrain subsystem drives the robot. Call create() to
* get a new instance of a SwerveIO drivetrain subsystem.
*
* This subsystem consists of the following components:
* - Swerve module (4x drive + pivot motor)
*
* This subsystem should provide an interface for the
* following functions:
*
* - Running the drivetrain with joystick
* - Running the drivetrain autonomously
*
* @author Jordan Bancino
*/
public class DriveTrain {
/* Your robot chassis dimensions - measured from wheel to wheel. */
private static final ChassisDimension CHASSIS_DIMENSIONS = new ChassisDimension(new Length(22.5, Unit.INCHES));
/* CAN IDs - Set these according to your configuration in Phoenix Tuner */
private static final int FRONT_RIGHT_DRIVE_MOTOR = 5; /* Module 1 */
private static final int FRONT_LEFT_DRIVE_MOTOR = 6; /* Module 2 */
private static final int REAR_LEFT_DRIVE_MOTOR = 7; /* Module 3 */
private static final int REAR_RIGHT_DRIVE_MOTOR = 8; /* Module 4 */
private static final int FRONT_RIGHT_PIVOT_MOTOR = 1; /* Module 1 */
private static final int FRONT_LEFT_PIVOT_MOTOR = 2; /* Module 2 */
private static final int REAR_LEFT_PIVOT_MOTOR = 3; /* Module 3 */
private static final int REAR_RIGHT_PIVOT_MOTOR = 4; /* Module 4 */
private static final int FRONT_RIGHT_ENCODER = 9; /* Module 1 */
private static final int FRONT_LEFT_ENCODER = 10; /* Module 2 */
private static final int REAR_LEFT_ENCODER = 11; /* Module 3 */
private static final int REAR_RIGHT_ENCODER = 12; /* Module 4 */
/* Angle offsets in degrees - See SwerveIO appendix for setting these. */
public static final double FRONT_RIGHT_ANGLE_OFFSET = 146.27;
public static final double FRONT_LEFT_ANGLE_OFFSET = 134.60;
public static final double REAR_LEFT_ANGLE_OFFSET = 59.34;
public static final double REAR_RIGHT_ANGLE_OFFSET = 267.2;
public static SwerveDrive create(Gyro gyro) {
return new SwerveDrive.Builder()
.useDefaultKinematics(CHASSIS_DIMENSIONS)
.setGyro(gyro)
.setModuleMap((map) -&gt; {
map.put(SwerveModule.Location.FRONT_RIGHT, new MK3SwerveModule(
FRONT_RIGHT_DRIVE_MOTOR, FRONT_RIGHT_PIVOT_MOTOR,
FRONT_RIGHT_ENCODER, FRONT_RIGHT_ANGLE_OFFSET
));
map.put(SwerveModule.Location.FRONT_LEFT, new MK3SwerveModule(
FRONT_LEFT_DRIVE_MOTOR, FRONT_LEFT_PIVOT_MOTOR,
FRONT_LEFT_ENCODER, FRONT_LEFT_ANGLE_OFFSET
));
map.put(SwerveModule.Location.REAR_LEFT, new MK3SwerveModule(
REAR_LEFT_DRIVE_MOTOR, REAR_LEFT_PIVOT_MOTOR,
REAR_LEFT_ENCODER, REAR_LEFT_ANGLE_OFFSET
));
map.put(SwerveModule.Location.REAR_RIGHT, new MK3SwerveModule(
REAR_RIGHT_DRIVE_MOTOR, REAR_RIGHT_PIVOT_MOTOR,
REAR_RIGHT_ENCODER, REAR_RIGHT_ANGLE_OFFSET
));
})
.build((swerve) -&gt; {
swerve.getLogger().outputTo(new DashboardLog());
});
}
}
</pre>
<p>
If you're looking for more examples, check out <a href="https://github.com/Team6090">Team 6090's Github
Organization</a>. There we have a <code>SwerveIOTestBase</code> and our <code>InfiniteRecharge</code> code,
both of which utilize SwerveIO. It is up to you to integrate SwerveIO into your robot code; that is beyond the
scope of this documentation. We find that this static <code>create()</code> method works the best for us because
it isolates our swerve drive configuration in its own file or method, but you may use SwerveIO however works
best for you. SwerveIO is designed to be usable in a wide variety of configurations. While it was designed to be
command-based, there is certainty nothing stopping anyone from using it in other ways.
</p>
<h3 id="dashboard">Dashboard</h3>
<p>
You can have SwerveIO automatically output a plethora of information on your swerve drive using the
<code>DashboardLog</code>. This class writes useful information&mdash;including encoder positions and module
angles&mdash;to the Smart Dashboard. To enable this feature on your swerve drive, use the following code:
</p>
<pre>
import net.bancino.robotics.swerveio.SwerveDrive;
import net.bancino.robotics.swerveio.log.DashboardLog;
/* ... */
SwerveDrive swerve = /* Fetch or create your swerve drive here. */;
swerve.getLogger().outputTo(new DashboardLog());
</pre>
<p>
You simply call <code>outputTo()</code> on your swerve drive's logger with a new <code>DashboardLog</code>
object. When your robot code runs, you should see live feedback from your swerve drive under the "SwerveIO"
section of your dashboard.
</p>
<h3 id="license">License</h3>
<p>
Copyright (C) 2019-2021 Jordan Bancino &lt;jordan@bancino.net&gt;
</p>
<p>
All parts of SwerveIO, including the source code, build scripts, and documentation, are licensed under the GNU
GPL V3. Please read it carefully before using SwerveIO for any purpose. While you do have the right to use and
modify SwerveIO for most purposes, you absolutely cannot use SwerveIO in proprietary software. Any software that
uses SwerveIO or a derivative of SwerveIO MUST be open source in the sense that anyone can <i>easily</i> obtain
the source code that uses SwerveIO. Furthermore, any derivative of SwerveIO must also be open source and bound
by the GNU GPL V3. This is to foster an open development environment. SwerveIO is graciously released free of
charge to the community, so please adhere to the terms of the GNU GPL V3, and before forking SwerveIO, consider
contributing to the upstream project. I want SwerveIO to be the best that it can be for everyone, so if you have
an improvement to share, it would be greatly appreciated if you offered it to the main project instead of making
the changes privately in your own fork.
</p>
<p>
You must not remove any copyright notices that appear in this code, either in comments or in console output.
</p>
<!-- Provide an anchor to the end of this documentation where the package list begins. -->
<div id="packages"></div>
</body>
</html>

View file

@ -0,0 +1,118 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.log;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.util.List;
/**
* A log hander that generates JSON output directly mirroring
* the code hierarchy.
*
* @author Jordan Bancino
* @version 7.0.0
* @since 7.0.0
*/
public class JsonLog implements LogHandler {
public static String toJson(LogEntry entry) {
StringBuilder json = new StringBuilder();
if (entry != null) {
switch (entry.getType()) {
case LIST:
List<LogEntry> values = entry.getAsList();
json.append('[');
for (int i = 0; i < values.size(); i++) {
json.append(toJson(values.get(i)));
if (i < values.size() - 1) {
json.append(',');
}
}
json.append(']');
break;
case NODE:
json.append('{');
for (int i = 0; i < entry.getChildren().size(); i++) {
LogEntry child = entry.getChildren().get(i);
json.append('"').append(child.getName()).append("\":");
json.append(toJson(child));
if (i < entry.getChildren().size() - 1) {
json.append(',');
}
}
json.append('}');
break;
case VALUE:
Object raw = entry.getValue();
String str;
if (raw == null) {
str = "null";
} else if (raw instanceof Number || raw instanceof Boolean) {
str = raw.toString();
} else {
/* TODO: do proper JSON escaping. */
str = String.format("\"%s\"", raw.toString());
}
json.append(str);
break;
default:
break;
}
}
return json.toString();
}
private final PrintStream out;
private boolean doComma = false;
public JsonLog(String out) throws FileNotFoundException {
this(new File(out));
}
public JsonLog(File out) throws FileNotFoundException {
this(new PrintStream(out));
}
public JsonLog(PrintStream out) {
this.out = out;
}
@Override
public void open() {
out.print('[');
}
@Override
public void accept(LogEntry entry) {
String json = toJson(entry);
if (doComma && !json.isBlank()) {
out.print(',');
} else {
doComma = true;
}
out.print(json);
}
@Override
public void close() {
out.println(']');
out.close();
}
}

View file

@ -0,0 +1,53 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.log;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* The Log annotation. This is used to annotate fields that should have their values
* logged. LogIO will automatically collect primitives and basic object information.
* If a field is of a custom type, make sure that custom type is annotated with
* {@link Loggable} to ensure that it actually produces useful log information.
*
* @author Jordan bancino
* @version 7.0.0
* @since 7.0.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Log {
/**
* Set the key name for this field. By default, the key name is the name of
* the field variable, but this can be used to change that if the field variable
* name is not appropriate or descriptive enough.
* @return The key name for this field, or a blank string if the name of the field
* variable should be used.
*/
public String as() default "";
/**
* Set the level at which this field should be logged.
* @return The log level at which this field should be logged.
* The default is {@code TRACE}.
*/
public LogLevel atLevel() default LogLevel.TRACE;
}

View file

@ -0,0 +1,180 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.log;
import java.util.ArrayList;
import java.util.List;
/**
* A representation of a log enry. LogIO log entries are of a certain type, and
* may optionally have a parent and any number of children. This class creates
* the log tree hierarchy.
*
* @author Jordan Bancino
* @version 7.0.0
* @since 7.0.0
*/
public class LogEntry {
/**
* The possible log entry types.
*/
public static enum Type {
/**
* Indicates a log entry is a list of loggable objects.
*/
LIST,
/**
* Indicates a log entry is the parent to a number of child
* log entries.
*/
NODE,
/**
* Indicates a log entry is a value that should be logged.
*/
VALUE
}
private final LogEntry parent;
private final List<LogEntry> children;
private String name;
private Object value;
/**
* Create a new log entry. This constructor should only be used by
* {@link Logger}. Any other uses may result in undefined behavior.
*
* @param parent The parent log entry, or {@code null} if this is the
* root entry.
* @param name The name or "key" of this entry.
* @param value The value of this entry.
*/
public LogEntry(LogEntry parent, String name, Object value) {
this.parent = parent;
this.children = new ArrayList<>();
this.name = name;
this.value = value;
if (parent != null) {
parent.getChildren().add(this);
}
}
/**
* Get the parent log entry of this log entry.
*
* @return A parent log entry, or {@code null} if this log entry is a root entry.
*/
public LogEntry getParent() {
return parent;
}
/**
* Get the child log entries of this log entry.
*
* @return A list of child log entries. This may be an empty list if there
* are no children, but it will never be null.
*/
public List<LogEntry> getChildren() {
return children;
}
/**
* Get the name of this log entry.
*
* @return The name of this log entry.
*/
public String getName() {
return name;
}
/**
* Set the name of this log entry.
*
* @param name The new name of this log entry.
*/
public void setName(String name) {
this.name = name;
}
/**
* Get the value of this log entry.
*
* @return The raw value of this log entry.
*/
public Object getValue() {
return value;
}
/**
* Set the value of this log entry.
*
* @param value The new value of this log entry.
*/
public void setValue(Object value) {
this.value = value;
}
/**
* Get entry type of this log entry. This is determined by evaluating the type of
* the current value at the time of calling, and should be used to guide decisions
* of what to do with the value.
*
* <ul>
* <li>
* If this returns {@code Type.LIST}, then this log entry's value should be
* processed by calling {@link #getAsList()}.
* </li>
* <li>
* If this returns {@code Type.NODE}, then this log entry's value should be
* processed by calling {@link #getChildren()}.
* </li>
* <li>
* If this returns {@code Type.VALUE}, then this log entry's value should be
* processed by calling {@link #getValue()}.
* </li>
* </ul>
*
* Any other invocation usage of this log entry object is undefined.
*
* @return The type that this log entry's value represents.
*/
public Type getType() {
if (getValue() != null) {
if (getValue() instanceof List<?>) {
return Type.LIST;
} else if (getValue().getClass().getAnnotation(Loggable.class) != null || getChildren().size() > 0) {
return Type.NODE;
}
}
return Type.VALUE;
}
/**
* Get the value of this log entry as a list. This will only with if the type
* of this log entry is set to {@code Type.LIST}.
*
* @return The value of this log entry as a list of log entries, or null if this
* log entry does not represent a list.
*/
@SuppressWarnings("unchecked")
public List<LogEntry> getAsList() {
return getType() == Type.LIST ? (List<LogEntry>) getValue() : null;
}
}

View file

@ -0,0 +1,52 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.log;
import java.io.Closeable;
import java.util.function.Consumer;
/**
* An interface for generating human-readable logs from {@link LogEntry} trees.
* This interface provides a way for LogIO users to generate their own custom
* logs. It is extremely basic in definition, but implementing it can be quite
* tricky, due to the recursive nature of LogIO. To understand a bit more how
* this interface works, you may wish to look at the code for {@link JsonLog}.
*
* @author Jordan Bancino
* @version 7.0.0
* @since 7.0.0
*/
/* Consumer provides accept(LogEntry), Closeable provides close() */
/* This is still technically a functional interface because it only has one method that is mandatory. */
@FunctionalInterface
public interface LogHandler extends Consumer<LogEntry>, Closeable {
/**
* This method is called upon the first invocation of {@link Logger#log(Object)}.
* It can be used for setting up log files, by, for instance, writing log headers
* or other information.
*/
public default void open() {
}
/**
* This method is called upon the invocation of {@link Logger#close()}.
* It can be used for writing log footers and closing resource streams.
*/
@Override
public default void close() {
}
}

View file

@ -0,0 +1,65 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.log;
/**
* An enumeration that defines the support log levels. LogIO allows every field
* to declare the verbosity level at which it is logged. The user can then set the
* log level to include or omit log information depending on his or her needs.
*
* @author Jordan Bancino
* @version 7.0.0
* @since 7.0.0
*/
public enum LogLevel {
/**
* Log nothing. This effectively disables LogIO altogether by bypassing all
* the log routines. This can be useful for enhancing the performance of
* production code when no log data is needed.
*/
NOTHING,
/**
* Log only the most important data. Fields that are set to log at this
* level will be logged every time, no matter the log level, unless it
* is set to {@code NOTHING}.
*/
IMPORTANT,
/**
* This value indicates a field is informational in nature, and can be useful
* in many scenarios. This value just below {@code IMPORTANT}, which means
* that fields that are set to log at this level will be logged in every scenario
* that the log level is not set to {@code IMPORTANT} or {@code NOTHING}.
*/
INFO,
/**
* Log most data. This value indicates a verbose log, which can be useful for
* debugging. Fields that are set to log at this level are generally unimportant
* to the normal user and only useful for developers. They are only logged if the
* log level is set to {@code DEBUG} or {@code TRACE}.
*/
DEBUG,
/**
* Log everything. By default, {@link Log} sets the field log level to {@code TRACE}.
* Setting the log level to {@code TRACE} guarantees that all fields annotated with
* {@link Log} and are reachable from an object are indeed logged.
*/
TRACE
}

View file

@ -0,0 +1,32 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.log;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* The Loggable annotation indicates that a type is loggable. If this annotation is
* present on a type, LogIO will decend into instances of that type and search for
* fields annotated with {@link Log}. This allows recursive log trees to be constructed.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Loggable {
}

View file

@ -0,0 +1,227 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.log;
import java.io.Closeable;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* The main LogIO class that is responsible for using reflection to recursively
* process all {@link Log} and {@link Loggable} annotations to generate a tree
* of {@link LogEntry}s.
*
* @author Jordan Bancino
* @version 7.0.0
* @since 7.0.0
*/
public class Logger implements Closeable {
private LogLevel level = LogLevel.INFO;
private List<LogHandler> to = new ArrayList<>();
private boolean calledOpen = false;
/**
* Get whether or not the field annotated with the provided annotation
* instance should be logged or not. This takes in to account the currently
* set log level, and the log level of the annotation.
*
* @param annot The log annotation to check.
* @return Whether or not the field annotated with this annotation should be
* added to the {@link LogEntry} tree.
*/
private boolean shouldLog(Log annot) {
return level != LogLevel.NOTHING && annot.atLevel().ordinal() <= level.ordinal();
}
/**
* Set the log level on this logger. This determines what fields get logged
* and what fields are skipped, based on each field's log level.
*
* @param level The level, above which, fields should be logged at.
* @return This instance, for method chaining.
*/
public Logger setLevel(LogLevel level) {
this.level = level;
return this;
}
/**
* Add log handers to process the log entries created by this logger.
*
* @param outputs Custom log output handlers.
* @return This instance, for method chaining.
*/
public Logger outputTo(LogHandler... outputs) {
for (LogHandler output : outputs) {
if (output != null && !to.contains(output)) {
to.add(output);
}
}
return this;
}
/**
* Get the log level that is currently set.
*
* @return The current log level.
*/
public LogLevel getLevel() {
return level;
}
/**
* Recursively log an object. This method processes all annotated fields
* and generates a log entry tree. It then automatically calls all the
* log handlers that are associated with this class.
*
* @param o The object to log all values for.
* @throws UnsupportedOperationException If a reflection operation fails.
*/
public void log(Object o) throws UnsupportedOperationException {
if (level != LogLevel.NOTHING) {
try {
LogEntry rootEntry = recursiveLog(null, null, o);
for (LogHandler output : to) {
if (output != null) {
if (!calledOpen) {
output.open();
calledOpen = true;
}
output.accept(rootEntry);
}
}
} catch (IllegalAccessException e) {
throw new UnsupportedOperationException(e);
}
}
}
private void extractFields(LogEntry parent, Class<?> clazz, Object obj) throws IllegalAccessException {
/*
* If the annotation is not null, the user wants us to log values in this
* object's class, not just the object itself.
*/
if (clazz.getAnnotation(Loggable.class) != null) {
/* Iterate over all the fields of the class */
for (Field field : clazz.getDeclaredFields()) {
/* Get the field annotation. */
Log fieldAnnot = field.getAnnotation(Log.class);
/*
* If the field annotation is not null, the user wants us to log the value of
* this field.
*/
if (fieldAnnot != null && shouldLog(fieldAnnot)) {
/* If the field is private, this will allow us to retrieve it. */
field.setAccessible(true);
/* Get the name of the field as it is declared in code. */
String name = field.getName();
/* If the user provided a custom name for this field, use that instead. */
if (!fieldAnnot.as().trim().isBlank()) {
name = fieldAnnot.as().trim();
}
/* Get the field's value on the current object. */
Object fieldVal = field.get(obj);
LogEntry fieldEntry = recursiveLog(parent, name, fieldVal);
if (fieldVal == null) {
/* Do nothing, the assignment above appends a null object. */
} else if (fieldVal instanceof List<?>) {
/* If the field is a list, log each item in the list. */
List<LogEntry> itemEntries = new ArrayList<>();
for (Object item : (List<?>) fieldVal) {
itemEntries.add(recursiveLog(null, null, item));
}
fieldEntry.setValue(itemEntries);
} else if (fieldVal.getClass().isArray()) {
/* If the field is an array, log each item in the list. */
List<LogEntry> itemEntries = new ArrayList<>();
for (int i = 0; i < Array.getLength(fieldVal); i++) {
Object item = Array.get(fieldVal, i);
itemEntries.add(recursiveLog(null, null, item));
}
fieldEntry.setValue(itemEntries);
} else if (fieldVal instanceof Map) {
/* If the field is a map, log each item in the map. */
Map<?, ?> fieldMap = (Map<?, ?>) fieldVal;
for (Object k : fieldMap.keySet()) {
Object v = fieldMap.get(k);
recursiveLog(fieldEntry, k.toString(), v);
}
} else if (fieldVal instanceof Enum<?>) {
fieldEntry.setValue(fieldVal.toString());
fieldEntry.getChildren().clear();
}
}
}
}
}
private LogEntry recursiveLog(LogEntry parent, String thisName, Object obj) throws IllegalAccessException {
/* If the object is null, return a null entry. */
if (obj == null) {
return new LogEntry(parent, thisName, null);
}
/* Get this object's class. */
Class<?> clazz = obj.getClass();
String className = clazz.getSimpleName();
LogEntry entry = new LogEntry(parent, thisName != null ? thisName : className, obj);
/* Iterate over superclasses */
Class<?> superClass = clazz.getSuperclass();
while (superClass != null) {
/* The conditions for including this superclass's fields. */
boolean include = true;
include &= !superClass.isInterface();
include &= !superClass.equals(AbstractMap.class);
include &= !superClass.equals(Number.class);
include &= !superClass.equals(Object.class);
if (include) {
LogEntry superclassEntry = new LogEntry(entry, superClass.getSimpleName(), obj);
extractFields(superclassEntry, superClass, obj);
}
superClass = superClass.getSuperclass();
}
extractFields(entry, clazz, obj);
return entry;
}
/**
* Calls {@link LogHandler#close()} on all handlers associated with this
* logger.
*/
@Override
public void close() {
if (level != LogLevel.NOTHING) {
for (LogHandler output : to) {
output.close();
}
}
}
}

View file

@ -0,0 +1,52 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
/**
* LogIO: A reflection-based logging library built specifically for SwerveIO.
*
* <p>
* LogIO attempts to solve SwerveIO's logging situation. In previous versions
* of SwerveIO, an interface had to be implemented to log certain aspects of
* the SwerveIO runtime. However, this was clunky, because it could only data
* and objects that were accessible via the public API. This led to a number of
* variables being exposed via the public API in a way that could allow users to
* seriously abuse the way SwerveIO works. Furthermore, it made maintaining SwerveIO
* very difficult because so much of it was exposed to the public API that hardly
* any of it could be modified as needed in any meaningful way.
* </p>
* <p>
* LogIO solves this situation by using reflection. Although reflection has it's
* own flaws, it better suits SwerveIO because it allows SwerveIO authors to define
* how and at what level particular data should be logged without giving SwerveIO
* users the ability to modify that data in undefined ways. It does this by
* wrapping all values gathered via reflection in {@link LogEntry}.
* </p>
* <p>
* LogIO is a tree-based logging facility. This means that it recursively processes
* log targets and generates the log representation in such a way that a log entry may
* have any number of parent and child enries, depending on where it is in the
* hierarchy. The generated hierarchy directly mirrors the code from which the
* hierarchy is derived. LogIO is thus best suited to generating JSON logs that
* accurately represent the state of a program at a given time, although any
* situation in which tree-based logging is appropriate can utilize LogIO by implementing
* {@link LogHandler}.
* </p>
*
* @author Jordan Bancino
* @version 7.0.0
* @since 7.0.0
*/
package net.bancino.log;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,324 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.command;
import edu.wpi.first.wpilibj.Filesystem;
import edu.wpi.first.math.trajectory.Trajectory;
import edu.wpi.first.math.trajectory.TrajectoryUtil;
import edu.wpi.first.math.kinematics.ChassisSpeeds;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import net.bancino.robotics.swerveio.SwerveDrive;
import net.bancino.robotics.swerveio.module.SwerveModule;
/**
* Execute a PathWeaver trajectory on a SwerveIO Swerve Drive. This is intended
* to greatly simplify autonomous development, because you can use the standard
* WPILib PathWeaver to generate your trajectory and then just pass it here.
*
* <p>
* PathWeaver trajectories are fully supported as of version 5.0.0.
* </p>
*
* <p>
* This command will execute until it is interrupted, or until the path has been
* completed.
* </p>
*
* @author Jordan Bancino
* @version 6.0.0
* @version 2.1.0
*/
public class PathweaverSwerveDrive extends SwerveDriveCommand {
/**
* Convert a Pathweaver state object into a ChassisSpeeds vector that can be
* passed into a swerve drive.
*
* @param pathweaverState The trajectory state to convert to the chassis speed
* object.
* @param angularVelocity The angular velocity to apply to the resultant vector.
* @return A ChassisSpeeds object that has the velocity components of the
* current state object, and the constant angular velocity component.
*/
protected static ChassisSpeeds convertToChassisSpeeds(Trajectory.State pathweaverState, double angularVelocity) {
double angle = pathweaverState.poseMeters.getRotation().getRadians();
double velocity = pathweaverState.velocityMetersPerSecond;
double velocityX = velocity * Math.cos(angle);
double velocityY = velocity * Math.sin(angle);
return new ChassisSpeeds(velocityX, velocityY, angularVelocity);
}
/**
* Compute the average angular velocity between two trajectory states.
* Theoretically, we could and should take the derivative of each state to get
* the angular velocity, but it simply isn't worth the computing power. If the
* states passed to this function are close enough, you'll get an extremely
* close approximation of the angular velocity at the given point.
*
* @param start The starting trajectory state to read the rotation vector from.
* @param end The ending trajectory state to read the rotation vector from.
*
* @return The average angular velocity in radians per second that will
* accomplish the given rotation between the two states.
*/
protected static double getAngularVelocity(Trajectory.State start, Trajectory.State end) {
double r1 = end.poseMeters.getRotation().getRadians();
double r2 = start.poseMeters.getRotation().getRadians();
double t1 = end.timeSeconds;
double t2 = start.timeSeconds;
return (r1 - r2) / (t1 - t2);
}
/**
* PathweaverSwerveDrive supports multiple path execution modes. These modes
* allow you to alter the way the Pathweaver trajectory is executed, based on
* the orientation of the robot, or the side of the field you are on.
*/
public static enum PathExecutionMode {
/**
* Run the path without modification. This will execute the raw Pathweaver
* trajectory without any altering of the {@code ChassisSpeeds} objects that it
* generates from {@link #convertToChassisSpeeds(Trajectory.State, double)}.
*/
NORMAL,
/**
* Run the path on the assumption that the robot is oriented backwards, or
* rotated 180 degrees from the front position. This will invert the forward
* parameter on the chassis speed vector.
*/
ROBOT_BACKWARDS,
/**
* Run the entire path backwards. This will invert both the forward and strafe
* parameters on the chassis speed vector.
*/
FULLY_BACKWARDS
}
private final Trajectory trajectory;
private final PathExecutionMode executionMode;
private Trajectory.State previousState;
private long startTime;
private double calTime = 0.75;
private final boolean useGyro;
private final boolean doRotation;
/**
* Create a Pathweaver swerve drive command that will execute the given
* pathweaver path on the given swerve drive.
*
* @param swerve The swerve drive to drive.
* @param pathweaverJson The pathweaver file to load, relative to the deploy
* directory.
* @param executionMode The mode to execute the path in.
* @throws IOException If there is an IO error when reading the pathweaver json
* file.
*/
public PathweaverSwerveDrive(SwerveDrive swerve, String pathweaverJson, PathExecutionMode executionMode)
throws IOException {
this(swerve, pathweaverJson, executionMode, true);
}
/**
* Create a Pathweaver swerve drive command that will execute the given
* pathweaver path on the given swerve drive.
*
* @param swerve The swerve drive to drive.
* @param pathweaverJson The pathweaver file to load, relative to the deploy
* directory.
* @param executionMode The mode to execute the path in.
* @param doRotation Whether or not to enable the rotation component of the
* path. Setting this to false will prohibit the swerve
* drive from rotating while executing the path.
* @throws IOException If there is an IO error when reading the pathweaver json
* file.
*/
public PathweaverSwerveDrive(SwerveDrive swerve, File pathweaverJson, PathExecutionMode executionMode,
boolean doRotation) throws IOException {
super(swerve);
if (pathweaverJson != null) {
Path jsonPath = pathweaverJson.toPath();
trajectory = TrajectoryUtil.fromPathweaverJson(jsonPath);
} else {
throw new IllegalArgumentException("Pathweaver JSON file name cannot be null.");
}
if (executionMode != null) {
this.executionMode = executionMode;
} else {
throw new IllegalArgumentException("Execution mode cannot be null.");
}
this.doRotation = doRotation;
this.useGyro = swerve.getGyro() != null && swerve.getAnglePID() != null;
}
/**
* Create a Pathweaver swerve drive command that will execute the given
* pathweaver path on the given swerve drive.
*
* @param swerve The swerve drive to drive.
* @param pathweaverJson The pathweaver file to load, relative to the deploy
* directory.
* @param executionMode The mode to execute the path in.
* @param doRotation Whether or not to enable the rotation component of the
* path. Setting this to false will prohibit the swerve
* drive from rotating while executing the path.
* @throws IOException If there is an IO error when reading the pathweaver json
* file.
*/
public PathweaverSwerveDrive(SwerveDrive swerve, String pathweaverJson, PathExecutionMode executionMode,
boolean doRotation) throws IOException {
this(swerve, new File(Filesystem.getDeployDirectory(), pathweaverJson), executionMode, doRotation);
}
/**
* Create a Pathweaver swerve drive command that will execute the given
* pathweaver path on the given swerve drive.
*
* @param swerve The swerve drive to drive.
* @param pathweaverJson The pathweaver file to load, relative to the deploy
* directory.
* @param doRotate Whether or not to enable the rotation component of the
* path. Setting this to false will prohibit the swerve
* drive from rotating while executing the path.
* @throws IOException If there is an IO error when reading the pathweaver json
* file.
*/
public PathweaverSwerveDrive(SwerveDrive swerve, String pathweaverJson, boolean doRotate) throws IOException {
this(swerve, pathweaverJson, PathExecutionMode.NORMAL, doRotate);
}
/**
* Create a Pathweaver swerve drive that uses the normal path execution mode.
*
* @param swerve The swerve drive object to drive.
* @param pathweaverJson The pathweaver file to load, relative to the deploy
* directory.
* @throws IOException If there is an IO error when reading the pathweaver json
* file.
*/
public PathweaverSwerveDrive(SwerveDrive swerve, String pathweaverJson) throws IOException {
this(swerve, pathweaverJson, true);
}
/**
* Set the calibration time on this path. This parameter controls how long this
* command will instruct the swerve drive modules to pivot to the forward
* position before the path is run. This is helpful for autonomous because it
* ensures the swerve modules are aligned (or roughly aligned) before attempting
* to drive. This will result in drastically more accurate paths.
*
* @param calTime The calibration time, <b>in seconds</b>. The swerve drive will
* not move during this time, instead the control loops will be
* used to drive the pivot motors to the starting position. After
* this time is up, the full path is run. The default calibration
* time is 0.75 seconds, which is quite generous for a well-tuned
* pivot PID loop.
*/
public void setCalibrationTime(double calTime) {
this.calTime = Math.abs(calTime);
}
@Override
protected void initialize(SwerveDrive swerve) {
startTime = System.currentTimeMillis();
}
private double getTimeSeconds() {
long currentTime = System.currentTimeMillis();
double timeSeconds = (double) (currentTime - startTime) / 1000d;
return timeSeconds - this.calTime;
}
@Override
protected void execute(SwerveDrive swerve) {
double timeSeconds = getTimeSeconds();
/*
* If our current time is less than zero, we are in the calibration period. To
* calibrate, we simply set all the module angles to zero. The calibration time
* is important because it will take a few scans to actually achieve the angle.
*/
if (timeSeconds < 0) {
swerve.forEachModule((SwerveModule.Location location, SwerveModule module) -> {
module.setAngle(0);
});
return;
}
/* Get the current trajectory state from the trajectory. */
Trajectory.State currentState = trajectory.sample(timeSeconds);
/*
* Approximate the angular velocity by using the previous state we passed. It
* would not be efficient to compute the derivative to get the actual
* instantanious velocity, but this gets us close enough.
*
* This code is only used if the gyro is null, because if a gyro is present, we
* will be using a PID controller to ensure the angle is actually set right.
*/
double angularVelocity;
if (doRotation && !useGyro && previousState != null) {
angularVelocity = getAngularVelocity(previousState, currentState);
} else {
angularVelocity = 0;
}
ChassisSpeeds swerveVector = convertToChassisSpeeds(currentState, angularVelocity);
/* Flip the chassis speed axes based on the direction the path should go. */
switch (executionMode) {
case NORMAL:
swerveVector.vyMetersPerSecond *= -1;
break;
case ROBOT_BACKWARDS:
swerveVector.vyMetersPerSecond *= -1;
swerveVector.vxMetersPerSecond *= -1;
break;
case FULLY_BACKWARDS:
swerveVector.vxMetersPerSecond *= -1;
break;
default:
throw new IllegalStateException("Unknown execution mode.");
}
if (useGyro) {
double targetAngle = 0;
if (doRotation) {
targetAngle = currentState.poseMeters.getRotation().getDegrees();
}
swerve.drive(swerveVector, targetAngle);
} else {
swerve.drive(swerveVector);
}
previousState = currentState;
}
@Override
public boolean isFinished() {
return trajectory.getTotalTimeSeconds() <= getTimeSeconds();
}
}

View file

@ -0,0 +1,72 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.command;
import java.io.IOException;
import edu.wpi.first.wpilibj.DriverStation;
import edu.wpi.first.wpilibj2.command.InstantCommand;
import net.bancino.robotics.swerveio.SwerveDrive;
/**
* Execute {@link net.bancino.robotics.swerveio.SwerveDrive#saveAngleOffsets()}
* on a swerve drive. This simple command is set up to properly save the angle
* offsets while the robot is disabled. You can add this command to the smart
* dashboard, or even a joystick button (not recommended) to easily calibrate
* your swerve drive.
*
* <p>
* The general usage of this command is simple: align your swerve modules so
* that they are all facing perfectly straight in the forward position, then
* invoke this command to save their positions. You can then call
* {@link net.bancino.robotics.swerveio.SwerveDrive#loadAngleOffsets()} in your
* swerve drive's initialization code to load the saved offsets.
* </p>
*
* @author Jordan Bancino
* @version 6.1.0
* @since 6.1.0
*/
public class SaveSwerveAngleOffsets extends InstantCommand {
/**
* Construct a new offset-saving command.
*
* @param swerve The given swerve drive to save the angle offsets for.
*/
public SaveSwerveAngleOffsets(SwerveDrive swerve) {
super(() -> {
try {
swerve.saveAngleOffsets();
DriverStation.reportWarning("Saved swerve drive angle offsets.", false);
} catch (IOException e) {
DriverStation.reportError("Unable to save swerve angle offsets", e.getStackTrace());
}
}, swerve);
}
/**
* This signals to the command scheduler that this command is allowed to run
* when the robot is disabled.
*/
@Override
public boolean runsWhenDisabled() {
return true;
}
}

View file

@ -0,0 +1,125 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.command;
import edu.wpi.first.wpilibj2.command.CommandBase;
import net.bancino.robotics.swerveio.SwerveDrive;
/**
* A simple wrapper for the WPILib {@code CommandBase} that runs a command on a
* SwerveIO swerve drive. This class provides minimal functionality. It simply
* accepts a swerve drive in a constructor and then iteratively delivers that
* swerve drive into the abstract methods for convenience.
*
* <p>
* This class was written with the intention of easing development of commands
* that operate on a swerve drive by removing boilerplate swerve drive code from
* those commands. Wherever possible, it is recommended that all commands that
* operate on a SwerveIO swerve drive extend this class. This class provides the
* following functions:
* </p>
* <ul>
* <li>It automatically stores the swerve drive object so you don't have to and
* passes it into the command functions you need.</li>
* <li>It automatically adds the swerve drive as a subsystem requirement so you
* don't have to.</li>
* </ul>
*
* @author Jordan Bancino
* @version 5.0.4
* @since 5.0.4
*/
public abstract class SwerveDriveCommand extends CommandBase {
private final SwerveDrive swerve;
/**
* Construct a new swerve drive command with the given swerve drive. This
* command will operate on this swerve drive.
*
* @param swerve The swerve drive object that this command will use for
* performing swerve-related functions.
*/
protected SwerveDriveCommand(SwerveDrive swerve) {
if (swerve != null) {
this.swerve = swerve;
addRequirements(swerve);
} else {
throw new NullPointerException("Swerve drive cannot be null.");
}
}
@Override
public final void initialize() {
super.initialize();
initialize(swerve);
}
@Override
public final void execute() {
super.execute();
execute(swerve);
}
@Override
public final void end(boolean interrupted) {
super.end(interrupted);
end(swerve, interrupted);
}
/**
* Initialize this command. This method is called once when the command is
* initially scheduled. Use this for any state setting you need to do, such as
* disabling field-centric drive if it is enabled.
*
* @param swerve The swerve drive to initialize this command with. This is the
* same swerve drive passed into the constructor.
*/
protected abstract void initialize(SwerveDrive swerve);
/**
* The main body of the command. This method is called iteratively while this
* command is scheduled. You should put your swerve driving code here. The idea
* is that eventually you'll call a {@code drive()} method on the passed swerve
* drive, but swerve drive commands can also be state-setting commands.
*
* @param swerve The swerve drive to execute this command with. This is the same
* swerve drive passed into the constructor.
*/
protected abstract void execute(SwerveDrive swerve);
/**
* The action to take when the command ends. Called when either the command
* finishes normally, or when it is interrupted/canceled.
*
* @param swerve The swerve drive that was passed into the constructor.
* @param interrupted Whether the command was interrupted/canceled
*/
protected void end(SwerveDrive swerve, boolean interrupted) {
/*
* It isn't common to implement this, so provide a default implementation that
* does nothing.
*/
}
/*
* Overriding this method to make it abstract requires subclasses to implement
* it, which will help prevent undesired behavior.
*/
@Override
public abstract boolean isFinished();
}

View file

@ -0,0 +1,200 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.command;
import edu.wpi.first.wpilibj.GenericHID;
import edu.wpi.first.wpilibj.Joystick;
import edu.wpi.first.wpilibj.XboxController;
import net.bancino.robotics.swerveio.SwerveDrive;
import net.bancino.robotics.swerveio.geometry.SwerveVector;
/**
* A simple command for driving a swerve drive with any WPILib-supported
* joystick.
*
* <p>
* This command also demonstrates how simple it is to drive a SwerveIO swerve
* drive with a joystick. It simply extends {@link SwerveDriveTeleopCommand},
* which provides support for many useful features.
* </p>
*
* <p>
* This command runs until it is interrupted; there is no condition under which
* this command will stop on it's own. The command scheduler should resume it
* after autonomous commands are executed.
* </p>
*
* <p>
* As of SwerveIO 5.0.2, this joystick command uses the gyro for angular
* movement if it is available and an angle PID controller is set up. This will
* prevent any drifting that may occur because a position loop will be running
* as the rotational parameter. See
* {@link net.bancino.robotics.swerveio.SwerveDrive#drive(SwerveVector, double)}
* and
* {@link net.bancino.robotics.swerveio.SwerveDrive.Builder#setAnglePID(PIDController,int,int,Consumer)}
* for more information on this.
* </p>
*
* @author Jordan Bancino
* @version 6.1.0
* @since 2.0.0
*/
public final class SwerveDriveTeleop extends SwerveDriveTeleopCommand {
private final boolean useGyro;
private double angleSetpoint;
private double degreeIncrement = 8;
/**
* Create a new teleop command using a generic joystick and the default
* {@link edu.wpi.first.wpilibj.Joystick.AxisType} axes. The defaults are
* {@code kY} for forward, {@code kX} for strafe, and {@code kZ} for rotation.
*
* <p>
* See
* {@link SwerveDriveTeleop#SwerveDriveTeleop(SwerveDrive, Joystick, Joystick.AxisType, Joystick.AxisType, Joystick.AxisType)}
* if you want to use custom axes.
* </p>
*
* @param swerve The swerve drive to drive.
* @param joystick The joystick to read values from.
*/
public SwerveDriveTeleop(SwerveDrive swerve, Joystick joystick) {
this(swerve, joystick, Joystick.AxisType.kY, Joystick.AxisType.kX, Joystick.AxisType.kZ);
}
/**
* Create a new teleop command using an Xbox controller and the default
* {@link edu.wpi.first.wpilibj.XboxController.Axis} axes. The defaults are
* {@code kLeftY} for forward, {@code kLeftX} for strafe, and {@code kRightX}
* for rotation.
*
* <p>
* See
* {@link SwerveDriveTeleop#SwerveDriveTeleop(SwerveDrive, XboxController, XboxController.Axis, XboxController.Axis, XboxController.Axis)}
* if you want to use custom axes.
* </p>
*
* @param swerve The swerve drive to drive.
* @param joystick The Xbox controller to read values from.
*/
public SwerveDriveTeleop(SwerveDrive swerve, XboxController joystick) {
this(swerve, joystick, XboxController.Axis.kLeftY, XboxController.Axis.kLeftX, XboxController.Axis.kRightX);
}
/**
* Create a new teleop command using an Xbox controller and custom axes.
*
* @param swerve The swerve drive to drive.
* @param joystick The Xbox controller to read from.
* @param fwdAxis The axis that controls the foward parameter.
* @param strAxis The axis that controls the strafe parameter.
* @param rcwAxis The axis that controls the rotation parameter.
*/
public SwerveDriveTeleop(SwerveDrive swerve, XboxController joystick, XboxController.Axis fwdAxis,
XboxController.Axis strAxis, XboxController.Axis rcwAxis) {
this(swerve, joystick, fwdAxis.value, strAxis.value, rcwAxis.value);
}
/**
* Create a new teleop command using a generic joystick and custom axes.
*
* @param swerve The swerve drive to drive.
* @param joystick The joystick to read from.
* @param fwdAxis The axis that controls the foward parameter.
* @param strAxis The axis that controls the strafe parameter.
* @param rcwAxis The axis that controls the rotation parameter.
*/
public SwerveDriveTeleop(SwerveDrive swerve, Joystick joystick, Joystick.AxisType fwdAxis,
Joystick.AxisType strAxis, Joystick.AxisType rcwAxis) {
this(swerve, joystick, fwdAxis.value, strAxis.value, rcwAxis.value);
}
/**
* Create a new teleop command using any generic human interaction device and
* custom axes.
*
* @param swerve The swerve drive to drive.
* @param joystick The Xbox controller to read from.
* @param fwdAxis The axis that controls the foward parameter.
* @param strAxis The axis that controls the strafe parameter.
* @param rcwAxis The axis that controls the rotation parameter.
*/
public SwerveDriveTeleop(SwerveDrive swerve, GenericHID joystick, int fwdAxis, int strAxis, int rcwAxis) {
super(swerve, joystick, fwdAxis, strAxis, rcwAxis);
this.useGyro = swerve.getGyro() != null && swerve.getAnglePID() != null;
}
/**
* Set the degree increment that the joystick modifies. This is the maximum
* number of degrees that the angle setpoint will change per scan if the
* joystick is at 100% rotation. Note that this is applied after the joystick
* ramp rate.
*
* <p>
* <b>Note:</b> This is only applicable if the swerve drive angle PID controller
* is enabled. This increments the angle setpoint, but if there is no PID
* controller, the angle setpoint is not used.
* </p>
*
* @param degreeIncrement The number of degrees, per scan, to change by when the
* robot joystick's rotational axis is at 100%. If the
* rotation is less than this, the increment actual will
* be proportional. Lower values mean the swerve drive
* will be less responsive to rotation, whereas higher
* values will result in a touchier robot. The default
* value is 8 degrees.
*/
public void setAngleIncrement(double degreeIncrement) {
this.degreeIncrement = Math.abs(degreeIncrement);
}
@Override
protected void initialize(SwerveDrive swerve) {
/*
* Set the angle setpoint to the current gyro angle so this command doesn't move
* the robot upon starting.
*/
if (useGyro) {
angleSetpoint = swerve.getGyro().getAngle();
}
}
@Override
protected void execute(SwerveDrive swerve, SwerveVector joystickVector) {
if (useGyro) {
/* Modify the angle setpoint based on the rotational joystick parameter. */
angleSetpoint += joystickVector.getRcw() * this.degreeIncrement;
angleSetpoint %= 360;
if (angleSetpoint < 0) {
angleSetpoint += 360;
}
/* Drive the swerve drive with the angle setpoint. */
swerve.drive(joystickVector, angleSetpoint);
} else {
/* Simply drive the swerve drive with the joystick vector */
swerve.drive(joystickVector);
}
}
@Override
public boolean isFinished() {
return false;
}
}

View file

@ -0,0 +1,268 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.command;
import edu.wpi.first.wpilibj.GenericHID;
import net.bancino.robotics.swerveio.SwerveDrive;
import net.bancino.robotics.swerveio.geometry.SwerveVector;
/**
* A simple abstract class for writing swerve drive teleop commands. This class
* eliminates all the boilerplate code involved with writing a joystick command
* but still allows you to utilize and modify joysick input as needed before
* passing it to the swerve drive. This class is useful for writing commands
* that drive a swerve drive based on input from a joystick. It automatically
* takes care of throttling and deadbanding the joystick, as well as generating
* a swerve vector, so you can directly pass the vector down to the swerve
* drive, or perform modifications based on sensor readings, such as a gyro or
* or Limelight.
*
* <p>
* The options for setting the deadband around zero, as well as the throttle,
* can be great for training users.
* </p>
*
* <p>
* When writing commands for a SwerveIO swerve drive that involve user joystick
* input, it is highly recommended to use this class, because it automatically
* handles many useful features and allows you to inherit the axes, throttle,
* and deadband used in another joystick command, allowing for a seemless
* transition across commands that the joystick operator won't even notice.
* </p>
*
* @author Jordan Bancino
* @version 5.0.4
* @since 5.0.4
*/
public abstract class SwerveDriveTeleopCommand extends SwerveDriveCommand {
private final GenericHID joystick;
private final int fwdAxis, strAxis, rcwAxis;
private double deadband, throttle, rampRate;
private SwerveVector lastVector = new SwerveVector(0, 0, 0);
/**
* Construt a new swerve drive teleop command that inherits all the properties
* of an already-existing joystick command. This allows you to configure your
* joystick once with one command, then have all the other ones automatically
* inherit the settings set on that "master" command. Most commonly, you'll use
* this constructor to inherit a {@link SwerveDriveTeleop} command.
*
* @param swerve The swerve drive to pass to the superclass constructor and the
* abstract execute method. This is the swerve drive your
* joystick command will drive.
* @param inherit The swerve drive teleop command to inherit the joystick,
* joystick axes, deadband, and throttle from. The joystick is
* inherited by-reference, but the axes, deadband, and throttle
* are inherited by-value. This means that modifying the joystick
* in another command will cause it to be modified in this
* command too, but changing the deadband or throttle on another
* command will not affect this command.
*/
protected SwerveDriveTeleopCommand(SwerveDrive swerve, SwerveDriveTeleopCommand inherit) {
super(swerve);
this.joystick = inherit.joystick;
this.fwdAxis = inherit.fwdAxis;
this.strAxis = inherit.strAxis;
this.rcwAxis = inherit.rcwAxis;
this.deadband = inherit.deadband;
this.throttle = inherit.throttle;
this.rampRate = inherit.rampRate;
}
/**
* Create a new teleop command using any generic human interaction device and
* custom axes. This sets a default throttle of 90%, a default deadband of 20%,
* and a default ramp rate of 0.25% per scan. See the relevant documentation for
* throttle and deadband to override these defaults.
*
* @param swerve The swerve drive to pass to the superclass constructor and
* the abstract execute method. This is the swerve drive your
* joystick command will drive.
* @param joystick The joystick to read from.
* @param fwdAxis The axis to read from that will provide values for the Y
* movement of the swerve drive.
* @param strAxis The axis to read from that will provide values for the X
* movement of the swerve drive.
* @param rcwAxis The axis to read from that will provide values for the
* angular movement of the swerve drive.
*/
protected SwerveDriveTeleopCommand(SwerveDrive swerve, GenericHID joystick, int fwdAxis, int strAxis, int rcwAxis) {
super(swerve);
if (joystick != null) {
this.joystick = joystick;
this.fwdAxis = fwdAxis;
this.strAxis = strAxis;
this.rcwAxis = rcwAxis;
} else {
throw new IllegalArgumentException("Xbox controller cannot be null.");
}
setDeadband(0.1);
setThrottle(0.9);
setRampRate(0.025);
}
@Override
public final void execute(SwerveDrive swerve) {
/* Grab the joystick parameters */
double fwd = -throttle(deadband(joystick.getRawAxis(fwdAxis)));
double str = throttle(deadband(joystick.getRawAxis(strAxis)));
double rcw = throttle(deadband(joystick.getRawAxis(rcwAxis)));
/* Create a swerve vector and ramp it. */
SwerveVector joystickVector = ramp(new SwerveVector(fwd, str, rcw));
/* We have to duplicate the vector because it may be modified down the road. */
this.lastVector = new SwerveVector(joystickVector);
/* Pass the vector to the abstract execute method. */
execute(swerve, joystickVector);
}
/**
* Set a deadband on the joystick. This is a little bit of range around the zero
* mark that does absolutely nothing. This is helpful for joysticks that are
* overly sensitive or don't always read zero in the neutral position.
*
* @param deadband The deadband to set, between 0 and 1.
*/
public final void setDeadband(double deadband) {
this.deadband = deadband;
}
/**
* Set a throttle on the joystick. This is helpful for limiting the top speed of
* the swerve drive, which can be useful for training.
*
* @param throttle The throttle to set. This is the maximum speed the joystick
* should output. So, to throttle this command at 50% power,
* you'd put in 0.5 for the throttle.
*/
public final void setThrottle(double throttle) {
this.throttle = throttle;
}
/**
* Set a ramp rate on the joystick. This is helpful for preventing sharp changes
* in speed or direction.
*
* @param rampRate The ramp rate to set. This is the maximum amount that the
* joystick vector is allowed to change by per scan. A value of
* zero or less disables the ramp rate.
*/
public final void setRampRate(double rampRate) {
this.rampRate = rampRate;
}
/**
* Throttle a raw value based on the currently set throttle.
*
* @param raw The raw joystick value to scale.
* @return A scaled joystick value.
*/
private final double throttle(double raw) {
return raw * throttle;
}
/**
* Calculate a deadband
*
* @param raw The input on the joystick to mod
* @return The result of the mod.
*/
private final double deadband(double raw) {
/* Compute the deadband mod */
if (raw < 0.0d) {
if (raw <= -deadband) {
return (raw + deadband) / (1 - deadband);
} else {
return 0.0d;
}
} else {
if (raw >= deadband) {
return (raw - deadband) / (1 - deadband);
} else {
return 0.0d;
}
}
}
/**
* Calculate a ramp rate, using the history vector.
*
* @param v The vector to ramp. All parameters are ramped the same.
* @return The same vector, modified with the ramp rate.
*/
private final SwerveVector ramp(SwerveVector v) {
if (this.rampRate > 0) {
double maxFwd = this.lastVector.getFwd() + this.rampRate;
double minFwd = this.lastVector.getFwd() - this.rampRate;
if (v.getFwd() > maxFwd) {
v.setFwd(maxFwd);
}
if (v.getFwd() < minFwd) {
v.setFwd(minFwd);
}
double maxStr = this.lastVector.getStr() + this.rampRate;
double minStr = this.lastVector.getStr() - this.rampRate;
if (v.getStr() > maxStr) {
v.setStr(maxStr);
}
if (v.getStr() < minStr) {
v.setStr(minStr);
}
double maxRcw = this.lastVector.getRcw() + this.rampRate;
double minRcw = this.lastVector.getRcw() - this.rampRate;
if (v.getRcw() > maxRcw) {
v.setRcw(maxRcw);
}
if (v.getRcw() < minRcw) {
v.setRcw(minRcw);
}
}
return v;
}
/**
* The main body of the command. This method is called iteratively while this
* command is scheduled. You should put your swerve driving code here. The idea
* is that you'll perform any modifications necessary on the vector, then
* eventually call a {@code drive()} method on the pasted swerve drive with the
* modified vector.
*
* @param swerve The swerve drive to execute this command with. This is
* the same swerve drive passed into the constructor.
* @param joystickVector A swerve drive vector from the joystick. This vector
* has been throttled and deadbanded according to the
* settings before it is passed here.
*/
protected abstract void execute(SwerveDrive swerve, SwerveVector joystickVector);
}

View file

@ -0,0 +1,32 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
/**
* WPILib "new" commands for driving a SwerveIO Swerve Drive. These commands
* are designed to be customizable, and demonstrate the usage of SwerveIO
* in autonomous and user-operated modes. They are intended to be
* competition-ready and to complete the SwerveIO API, allowing users to go
* from just a collection of hardware to full joystick and autonomous control
* without any external tools. SwerveIO is fully self-contained in this
* regard; it is complete because these commands allow you to set up your
* swerve drive without writing much code. The only code you have to write is
* the configuration of the hardware and of these commands.
*
* @author Jordan Bancino
* @version 5.0.4
* @since 2.0.3
*/
package net.bancino.robotics.swerveio.command;

View file

@ -0,0 +1,70 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.encoder;
import edu.wpi.first.wpilibj.AnalogInput;
import edu.wpi.first.wpilibj.RobotController;
import net.bancino.log.Log;
import net.bancino.log.LogLevel;
import net.bancino.log.Loggable;
/**
* An analog encoder that connects to the RoboRIO. This class can be used to
* access 5-volt encoders that connect to the analog IO ports on the RoboRIO.
*
* <p>
* This is a wrapper for WPILib's {@code AnalogInput}, which reads a 12-bit
* number representing the voltage (that is, 0-5 volts).
* </p>
*
* @author Jordan Bancino
* @version 5.0.0
* @since 1.0.0
*/
@Loggable
public class AnalogEncoder implements Encoder {
private AnalogInput encoder;
@Log(as = "rawValue", atLevel = LogLevel.IMPORTANT)
private double lastValue;
/**
* Create an analog encoder.
*
* @param port The analog port on the roboRIO that this encoder is connected to.
*/
public AnalogEncoder(int port) {
encoder = new AnalogInput(port);
}
@Override
public double get() {
lastValue = encoder.getVoltage();
return lastValue;
}
@Override
public double countsPerRevolution() {
/*
* The "full" voltage may not always be the same due to load on the battery and
* other factors, so instead of hard-coding "5", we pull the actual voltage on
* the 5v rail.
*/
return RobotController.getVoltage5V();
}
}

View file

@ -0,0 +1,59 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.encoder;
/**
* The encoder representation used by SwerveIO. This is a complete definition of
* the encoder API. Every encoder that is used with SwerveIO must implement this
* interface.
*
* <p>
* The interface itself is fairly simplistic because there are only a few
* methods and they can usually be implemented in a just a few lines of code.
* </p>
*
* @author Jordan Bancino
* @version 5.0.0
* @since 1.0.0
*/
public interface Encoder {
/**
* Get the encoder's current position. Note that this may not always have double
* precision. It could be an integer as well. There is no limit on the value of
* the encoder, as long as it fits within the {@code double} data type.
*
* @return The encoder's current position, directly from the hardware. This
* value should not be modified at all; it is a raw reading specified in
* arbitrary units representing an arbitrary position.
*/
public double get();
/**
* Get the number of counts it takes this encoder to do one full revolution.
* This is a number of arbitrary units that represents how many units it takes
* to travel one full revolution of the encoder shaft.
*
* @return How many counts it takes to perform one full revolution of the
* encoder shaft, <b>in the units of {@link #get()}</b>. This should
* always come from the encoder documentation; don't use an experimental
* value. Also remember that this revolutions of the encoder, not
* revolutions of the pivot mechanism, though these will be the same if
* the encoder is 1:1.
*/
public double countsPerRevolution();
}

View file

@ -0,0 +1,63 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
/**
* Encoder iterfaces and helper classes. See {@link Encoder} for details on how
* an encoder is expected to behave.
*
* <p>
* Beginning users of SwerveIO will probably want to use an existing encoder
* implementation, but they can also easily create their own implementations by
* directly implementing {@link Encoder}.
* </p>
* <p>
* The following encoders are officially supported by SwerveIO Core:
* </p>
* <table border="1">
* <caption style="display: none;">Supported Hardare Table</caption>
* <tr>
* <th>Encoder</th>
* <th>SwerveIO Class</th>
* </tr>
* <tr>
* <td>Any 5-volt analog encoder that plugs into the RoboRIO analog IO port</td>
* <td>{@link AnalogEncoder}</td>
* </tr>
* </table>
* <p>
* Additional encoders are supported by the official hardware vendor support
* libraries. Please refer to the documentation home page for adding hardware
* support for your encoder.
* </p>
* <p>
* If your encoder is listed as supported, either in the list above or on the
* home page, use the hardware-specific classes to access them. The
* documentation for each individual encoder will provide you with all the
* specifics. If your encoder is not officially supported by SwerveIO, you will
* have to do a little more work to get SwerveIO to play nice with it, but due
* to the nature of SwerveIO and the design principles it was built with, adding
* your own encoder implementation should be trivial.
* </p>
* <p>
* Using the interfaces and classes in this package, you can add support for any
* encoder.
* </p>
*
* @author Jordan Bancino
* @version 6.0.0
* @since 1.0.0
*/
package net.bancino.robotics.swerveio.encoder;

View file

@ -0,0 +1,149 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.geometry;
import net.bancino.log.Log;
import net.bancino.log.LogLevel;
import net.bancino.log.Loggable;
import net.bancino.robotics.swerveio.geometry.Length.Unit;
/**
* An abstract representation of a physical swerve drive chassis. Objects of this class
* store the dimensions of the SwerveIO swerve base and provide methods for calculating
* the diagonal ({@link #getDiagonal()}), as well as the radius ({@link #getRadius()}).
* See the {@link #getRadius()} documentation for what the radius is.
*
* <p>
* Note that the chassis dimensions are measured from <b>module to module</b>, not
* necessarily from physical end to physical end. A chassis may have physical dimensions
* of 30 inches, but this class should represent the measurement of center of the module
* wheel in one corner to the center of the module wheel in each adjacent corner, which
* will most likely be less; say, 24.3 inches, for example.
* </p>
*
* <p>
* The chassis dimensions should be accurate because they are used in the computation of
* the swerve module vectors, as well as in the computation of the angular velocity of
* the swerve drive itself. Innacurate chassis dimensions will result in undefined and
* most likely undesirable behavior.
* </p>
*
* @author Jordan Bancino
* @version 5.0.0
* @since 2.0.0
*/
@Loggable
public class ChassisDimension {
@Log(atLevel = LogLevel.IMPORTANT)
private final Length trackWidth, trackLength;
/**
* Construct a chassis dimension with two lengths. This should be used for rectangular
* chassis that have different side lengths, even if only slightly.
*
* @param trackWidth The track width of the chassis, measured from left to
* right.
* @param trackLength The track length of the chassis, measured from front to
* back.
*/
public ChassisDimension(Length trackWidth, Length trackLength) {
if (trackWidth != null) {
this.trackWidth = trackWidth;
} else {
throw new IllegalArgumentException("A chassis must have a track width.");
}
if (trackLength != null) {
this.trackLength = trackLength;
} else {
throw new IllegalArgumentException("A chassis must have a track length.");
}
}
/**
* Construct a square chassis. This can be used for a chassis that is confirmed to
* be a perfect square.
*
* @param length The length of both the track width and length.
*/
public ChassisDimension(Length length) {
this(length, length);
}
/**
* Get the track width of this chassis.
*
* @return The width, measured from left to right.
*/
public Length getWidth() {
return trackWidth;
}
/**
* Get the track length of this chassis.
*
* @return the length, measured from back to front.
*/
public Length getLength() {
return trackLength;
}
/**
* Get the radius of the circle that this base will produce when rotating.
* This value is used in calculating the maximum angular velocity of a swerve drive.
* This is the same as {@code {@link #getDiagonal()} / 2}.
*
* @return The radius, that is, the length from the center of the chassis to one
* of the corners. This will work even if the base isn't a square.
*/
public Length getRadius() {
double halfLength = trackLength.get(Unit.BASE_UNIT) / 2;
double halfWidth = trackWidth.get(Unit.BASE_UNIT) / 2;
double hypotenuse = Math.sqrt(Math.pow(halfLength, 2) + Math.pow(halfWidth, 2));
return new Length(hypotenuse, Unit.BASE_UNIT);
}
/**
* Get the diagonal length that this base has, measured from opposite corners
* of the base. This value is used in the kinematics calculations.
*
* @return The diagonal length, that is, the length from one corner of the
* chassis to the diagonal corner. This will work even if the base isn't
* a square.
*/
public Length getDiagonal() {
double length = trackLength.get(Unit.BASE_UNIT);
double width = trackWidth.get(Unit.BASE_UNIT);
double hypotenuse = Math.sqrt(Math.pow(length, 2) + Math.pow(width, 2));
return new Length(hypotenuse, Unit.BASE_UNIT);
}
@Override
public boolean equals(Object o) {
if (o instanceof ChassisDimension) {
ChassisDimension cd = (ChassisDimension) o;
return cd.getLength().equals(getLength()) && cd.getWidth().equals(getWidth());
} else {
return false;
}
}
@Override
public String toString() {
return getLength() + " x " + getWidth();
}
}

View file

@ -0,0 +1,200 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.geometry;
import net.bancino.log.Log;
import net.bancino.log.LogLevel;
import net.bancino.log.Loggable;
/**
* An abstract length that can be retrieved in any units. This class makes use
* of {@link Unit}, which was designed specifically to provide units of length
* for use with this class. {@code Length} exists to provide an easy mechanism
* for allowing users to specify physical lengths in any units they want,
* because it allows the length to be retrieved in any units required, no matter
* what units it ws instantiated with. For example, if swerve module
* implementation specifies that the diameter of a wheel is 4 inches, the module
* method that calculates the linear velocity in meters per second can easily
* get the wheel diameter in meters without having to do any extra work.
*
* <p>
* Length objects are immutable, but can be operated in very clean an efficient
* ways.
* </p>
*
* @author Jordan Bancino
* @version 2.1.0
* @since 1.3.0
*/
@Loggable
public class Length {
/**
* A representation of a unit with conversion factors. This primarily intended
* for use with {@link Length}, which represents arbitrary-unit lengths. This
* class really makes no sense outside of {@link Length}. Units represented here
* are sensible; this class is not intended to represent all the possible units.
* Units are also based in terms of centimeters, so the conversion factor is
* always in terms of centimeters. This makes conversions between units simple.
*
* @author Jordan Bancino
* @version 1.3.0
* @since 1.3.0
*/
public static enum Unit {
/**
* US Inches &mdash; Approximately 2.54 Centimeters.
*/
INCHES(2.54),
/**
* US Feet &mdash; Approximately 30.48 Centimeters
*/
FEET(30.48),
/**
* Metric Centimeters, the base unit of this class.
*/
CENTIMETERS(1),
/**
* Metric Meters &mdash; Exactly 100 Centimeters.
*/
METERS(100);
/**
* An alias to the base unit, which is centimeters. Programs should use this
* instead of directly referencing centimeters to preserve compatibility with
* new versions. I don't know that this will ever change, but if it does,
* programs that use this will still work as intended.
*/
public static final Unit BASE_UNIT = CENTIMETERS;
private double conversionFactor;
/**
* Construct a new unit with the given conversion factor.
*
* @param conversionFactor How many centimeters make up this unit.
*/
private Unit(double conversionFactor) {
this.conversionFactor = conversionFactor;
}
/**
* Get the conversion factor of this unit. This is how many centimeters are
* equivalent to this unit.
*
* @return The number of centimeters in this unit.
*/
public double conversionFactor() {
return conversionFactor;
}
}
@Log(as = "unit", atLevel = LogLevel.IMPORTANT)
private final Unit baseUnit;
@Log(as = "value", atLevel = LogLevel.IMPORTANT)
private final double baseValue;
/**
* Create an immutable length object with a given length and unit measure. 1
* foot could be specified as {@code new Length(1, Unit.FEET)}, or as
* {@code new Length(12, Unit.INCHES)}. Both represent the same value, and
* demonstrate proper usage of this constructor.
*
* @param value The numerical value of this length in terms of the second
* parameter, {@code unit}.
* @param unit The unit that the value is in.
*
* @throws IllegalArgumentException If the length is negative. Lengths of zero
* are allowed.
*/
public Length(double value, Unit unit) {
if (value >= 0) {
this.baseValue = value;
this.baseUnit = unit;
} else {
throw new IllegalArgumentException("Lengths cannot be negative.");
}
}
/**
* Get this length in the specifed units.
*
* @param unit The unit to retrieve the length in.
* @return The value of the length in the specified units.
*/
public double get(Unit unit) {
return (baseValue * baseUnit.conversionFactor()) / unit.conversionFactor();
}
/**
* Add two lengths.
*
* @param length The length to add.
* @return The resulting length from the operation.
*/
public Length plus(Length length) {
return new Length(baseValue + length.get(baseUnit), baseUnit);
}
/**
* Subtract two lengths.
*
* @param length The length to subract.
* @return The resulting length from the operation.
*/
public Length minus(Length length) {
return new Length(baseValue - length.get(baseUnit), baseUnit);
}
/**
* Multiply two lengths.
*
* @param length The length to multiply.
* @return The resulting length from the operation.
*/
public Length times(Length length) {
return new Length(baseValue * length.get(baseUnit), baseUnit);
}
/**
* Divide two lengths.
*
* @param length The length to divide.
* @return The resulting length from the operation.
*/
public Length dividedBy(Length length) {
return new Length(baseValue / length.get(baseUnit), baseUnit);
}
@Override
public boolean equals(Object o) {
if (o instanceof Length) {
return ((Length) o).get(Unit.BASE_UNIT) == get(Unit.BASE_UNIT);
} else {
return false;
}
}
@Override
public String toString() {
return String.format("%.2d %s", baseValue, baseUnit.toString().toLowerCase());
}
}

View file

@ -0,0 +1,214 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.geometry;
import net.bancino.log.Log;
import net.bancino.log.LogLevel;
import net.bancino.log.Loggable;
/**
* A mutable container for a module vector that holds the module angle and speed
* for a single iteration. This is an important core class for driving a
* SwerveIO swerve drive. it is required to drive any swerve drive because,
* through the kinematics API, all {@link SwerveVector}s are converted to
* individual module vectors. This class represents a <b>module</b> vector; in
* other words, it describes the motion of a single swerve module only.
*
* <p>
* The module vector is intended to be used only by SwerveIO, because SwerveIO
* aims to define a complete API that abstracts everything behind just a few
* WPILib commands and a subsystem. There should be no case in which you are
* manually feeding module vectors to a swerve module.
* </p>
*
* <p>
* When using this vector, keep in mind that you are assigning a speed in units
* of percentage output, from -1 to 1, 0 being 0% output, 1 being 100% output in
* the "normal" direction, and -1 being 100% output in the "backwards"
* direction. The angle component should be in <b>degrees</b>, ranging from -360
* to 360, the direction specifying the direction of rotation.
* </p>
*
* @author Jordan Bancino
* @version 5.0.0
* @since 1.0.0
*/
@Loggable
public class ModuleVector {
/**
* Check that a given speed value is within the acceptable bounds, as described
* in this documentation.
*
* @param speed The speed to check.
* @return Whether or not {@code speed} should be accepted as motor controller
* input or not.
*/
public static boolean checkSpeed(double speed) {
return speed <= 1 && speed >= -1;
}
/**
* Check that a given angle value is within the acceptable bounds, as described
* in this documentation.
*
* @param angle The angle to check.
* @return Whether or not {@code angle} should be accepted as a swerve module
* angle reference or not.
*/
public static boolean checkAngle(double angle) {
return angle <= 360 && angle >= -360;
}
@Log(atLevel = LogLevel.IMPORTANT)
private double speed, angle;
/**
* Construct a module vector. This is a convenience constructor for the setter
* methods, and ensures that a module vector is initialized.
*
* @param angle The desired angle of the swerve module, in degrees from 0 to
* 360.
* @param speed The desired speed of the swerve module, in percentage output
* from 0 to 1.
* @throws IllegalArgumentException If the speed or the angle is out of bounds.
*/
public ModuleVector(double angle, double speed) {
setAngle(angle);
setSpeed(speed);
}
/**
* Duplicate a module vector using {@link #ModuleVector(double, double)}. This
* can be useful when you need to store a vector before passing it down a logic
* chain where it could possibly be modified.
*
* @param v The vector to duplicate.
*/
public ModuleVector(ModuleVector v) {
this(v.getAngle(), v.getSpeed());
}
/**
* Set the speed parameter of this module vector.
*
* @param speed The speed parameter, as described above.
*
* @return This instance, for chaining purposes.
*/
public ModuleVector setSpeed(double speed) {
if (checkSpeed(speed)) {
this.speed = speed;
} else {
throw new IllegalArgumentException("Speed out of bounds: " + speed);
}
return this;
}
/**
* Set the angle parameter of this module vecctor.
*
* @param angle The angle parameter, as described above.
*
* @return This instance, for chaining purposes.
*/
public ModuleVector setAngle(double angle) {
if (checkAngle(angle)) {
this.angle = angle;
} else {
throw new IllegalArgumentException("Angle out of bounds: " + angle);
}
return this;
}
/**
* Get the speed parameter of this module vector that was set with
* {@link #setSpeed(double)}.
*
* @return The speed value for this vector.
*/
public double getSpeed() {
return speed;
}
/**
* Get the angle parameter of this module vector that was set with
* {@link #setAngle(double)}.
*
* @return The angle value for this vector.
*/
public double getAngle() {
return angle;
}
/**
* Add a swerve vector to this one by summing each component.
*
* @param v The vector to add to this one.
* @return The resulting vector from adding all the components together.
*/
public ModuleVector plus(ModuleVector v) {
return new ModuleVector(getAngle() + v.getAngle(), getSpeed() + v.getSpeed());
}
/**
* Subtract a swerve vector from this one by finding the difference between each
* component.
*
* @param v The vector to subtract.
* @return The resulting vector from subtracting all the components.
*/
public ModuleVector minus(ModuleVector v) {
return new ModuleVector(getAngle() - v.getAngle(), getSpeed() - v.getSpeed());
}
/**
* Multiply a swerve vector by this one by finding the product of each
* component.
*
* @param v The vector to multiply by.
* @return The resulting vector from multiplying all the components.
*/
public ModuleVector times(ModuleVector v) {
return new ModuleVector(getAngle() * v.getAngle(), getSpeed() * v.getSpeed());
}
/**
* Divide this swerve vector by another one by dividing each component.
*
* @param v The vector to divide by.
* @return The resulting vector from dividing all the components.
*/
public ModuleVector dividedBy(ModuleVector v) {
return new ModuleVector(getAngle() / v.getAngle(), getSpeed() / v.getSpeed());
}
@Override
public String toString() {
return String.format("ModuleVector(angle: %.2f, speed: %.2f)", angle, speed);
}
@Override
public boolean equals(Object o) {
if (o instanceof ModuleVector) {
ModuleVector v = (ModuleVector) o;
return getSpeed() == v.getSpeed() && getAngle() == getAngle();
} else {
return false;
}
}
}

View file

@ -0,0 +1,302 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.geometry;
import net.bancino.log.Log;
import net.bancino.log.LogLevel;
import net.bancino.log.Loggable;
import net.bancino.robotics.swerveio.SwerveDrive;
import edu.wpi.first.math.kinematics.ChassisSpeeds;
/**
* A mutable container for a swerve drive vector that holds the forward, strafe,
* and rotation values for a single iteration. This is the core class for
* driving a SwerveIO swerve drive. it is required to drive any swerve drive,
* even autonomously, because WPILib {@code ChassisSpeeds} objects are converted
* to swerve vectors internally. This class represents the <b>chassis</b>
* vector; in other words, it describes the motion of the entire swerve drive as
* one system.
*
* <p>
* The swerve vector is intended to be used only by SwerveIO, because SwerveIO
* aims to define a complete API that abstracts everything behind just a few
* WPILib commands and a subsystem. However, there may be cases, such as in
* basic autonomous commands, that you may wish to generate your own swerve
* vectors to pass into the swerve drive.
* </p>
*
* <p>
* The is a <b>vector</b>, which means it provides velocity, not position.
* SwerveIO has basic positioning support, but nothing beyond what is trivial.
* When using this vector, keep in mind that you are assigning a speed in units
* of percentage output, from -1 to 1, -1 being 100% output in reverse, 0 being
* 0% output, and 1 being 100% output in the positive direction.
* </p>
*
* <p>
* The directions are defined as follows:
* </p>
*
* <ul>
* <li><b>FWD:</b> A positive value moves the swerve drive <b>forward</b></li>
* <li><b>STR:</b> A positive value moves the swerve drive <b>right</b></li>
* <li><b>RCW:</b> A positive value rotates the swerve drive
* <b>clockwise</b></li>
* </ul>
*
* <p>
* Negative values do the opposite. So, for example, if you wish to move your
* swerve drive to the left and downward, you would provide negative numbers for
* the forward and strafe parameters.
* </p>
* <p>
* All three axes of this vector are fully independent, which means they can be
* combined in any combination. You should be able to apply all three axes at
* the same time, and the kinematics provider should handle them. See
* {@link net.bancino.robotics.swerveio.kinematics} for how swerve vector values
* are used. Ultimately, while these vectors are passed into the
* {@link net.bancino.robotics.swerveio.SwerveDrive}, they will eventually make
* their way down to a kinematics provider, which is responsible for computing
* individuial module vectors from this overall swerve vector.
* </p>
*
* @author Jordan Bancino
* @version 4.2.0
* @since 1.0.0
*/
@Loggable
public class SwerveVector {
/**
* Convert a chassis speed object into a raw swerve vector based on the maximum
* velocity that a swerve drive can achieve. If the passed vector exceeds the
* limits of this swerve drive, driving it with this vector will result in a
* full-throttle.
*
* @param v A WPILib ChassisSpeeds vector that represents the velocity to
* apply to this swerve base. See
* {@link net.bancino.robotics.swerveio.command.PathweaverSwerveDrive}.
* @param swerve A swerve drive. This is required to obtain the maximum speed
* angular velocity, so that the ChassisSpeeds can be scaled to
* the specific swerve.
* @return A swerve vector designed to drive the specified swerve drive at the
* speeds given by the ChassisSpeeds object.
*/
public static SwerveVector fromChassisSpeeds(ChassisSpeeds v, SwerveDrive swerve) {
double maxMPS = swerve.getChassisMaxSpeed();
SwerveVector swerveVector = new SwerveVector(v.vxMetersPerSecond / maxMPS, v.vyMetersPerSecond / maxMPS,
v.omegaRadiansPerSecond / swerve.getChassisMaxOmega());
return swerveVector;
}
/**
* A convenience method that simply calls
* {@link #convertToChassisSpeeds(SwerveDrive)} on the passed vector. This
* method exists solely to remain consistent with
* {@link #fromChassisSpeeds(ChassisSpeeds, SwerveDrive)}. It might be useful
* for querying the absolute velocity in meters per second or radians per second
* that a particular swerve vector will cause the swerve drive to go.
*
* @param v The swerve vector to convert to a ChassisSpeeds object for the
* given swerve drive.
* @param swerve The swerve drive object; used to properly scale the resulting
* speed object.
* @return See {@link #convertToChassisSpeeds(SwerveDrive)}
*/
public static ChassisSpeeds toChassisSpeeds(SwerveVector v, SwerveDrive swerve) {
return v.convertToChassisSpeeds(swerve);
}
@Log(atLevel = LogLevel.IMPORTANT)
private double fwd, str, rcw;
/**
* Construct a swerve vector. No checks are done on the passed parameters, so it
* is possible to overrun the boundaries of the swerve drive. However, WPILib's
* motor controllers&mdash;where these values will ultimately end up&mdash; will
* not overrun the motors, so any value that is out of bounds will effectively
* be treated as 100%.
*
* <p>
* This constructor is just a convenience method for calling all the setter
* methods, and prevent uninitialized swerve vectors.
* </p>
*
* @param fwd The forward (Y) velocity for this vector.
* @param str The strafe (X) velocity for this vector.
* @param rcw The rotation (Z) velocity for this vector.
*/
public SwerveVector(double fwd, double str, double rcw) {
setFwd(fwd);
setStr(str);
setRcw(rcw);
}
/**
* Duplicate an existing swerve vector using
* {@link #SwerveVector(double, double, double)}. This can be useful when you
* need to store a vector before passing it down a logic chain where it could
* possibly be modified.
*
* @param v The vector to duplicate.
*/
public SwerveVector(SwerveVector v) {
this(v.getFwd(), v.getStr(), v.getRcw());
}
/**
* Set the forward parameter of this swerve vector.
*
* @param fwd The forward parameter, as described above.
*
* @return This instance, for chaining purposes.
*/
public SwerveVector setFwd(double fwd) {
this.fwd = fwd;
return this;
}
/**
* Set the strafe parameter of this swerve vector.
*
* @param str The strafe parameter, as described above.
*
* @return This instance, for chaining purposes.
*/
public SwerveVector setStr(double str) {
this.str = str;
return this;
}
/**
* Set the rotational parameter of this swerve vector.
*
* @param rcw The rotational parameter, as described above.
*
* @return This instance, for chaining purposes.
*/
public SwerveVector setRcw(double rcw) {
this.rcw = rcw;
return this;
}
/**
* Get the forward parameter of this swerve vector that was set with
* {@link #setFwd(double)}.
*
* @return The fwd value for this vector.
*/
public double getFwd() {
return fwd;
}
/**
* Get the strafe parameter of this swerve vector that set with
* {@link #setStr(double)}.
*
* @return The str value for this vector.
*/
public double getStr() {
return str;
}
/**
* Get the angular parameter of this swerve vector that set with
* {@link #setRcw(double)}.
*
* @return The rcw value for this vector.
*/
public double getRcw() {
return rcw;
}
/**
* Convert this vector into a chassis speed vector using a specific swerve
* drive, because the absolute speed of the chassis depends on the maximum
* linear and angular velocity of the swerve drive, since this class merely
* represents percentage of full output.
*
* @param drive The swerve drive to use when converting. This is required
* because the maximum speed and angular speed are used in the
* calculation.
* @return A WPILib chassis speed object that represents the swerve vector
* provided.
*/
public ChassisSpeeds convertToChassisSpeeds(SwerveDrive drive) {
double maxOutputMPS = drive.getChassisMaxSpeed();
double maxChassisRadiansPerSecond = drive.getChassisMaxOmega();
return new ChassisSpeeds(getFwd() * maxOutputMPS, getStr() * maxOutputMPS,
getRcw() * maxChassisRadiansPerSecond);
}
/**
* Add a swerve vector to this one by summing each component.
*
* @param v The vector to add to this one.
* @return The resulting vector from adding all the components together.
*/
public SwerveVector plus(SwerveVector v) {
return new SwerveVector(fwd + v.getFwd(), str + v.getStr(), rcw + v.getRcw());
}
/**
* Subtract a swerve vector from this one by finding the difference between each
* component.
*
* @param v The vector to subtract.
* @return The resulting vector from subtracting all the components.
*/
public SwerveVector minus(SwerveVector v) {
return new SwerveVector(fwd - v.getFwd(), str - v.getStr(), rcw - v.getRcw());
}
/**
* Multiply a swerve vector by this one by finding the product of each
* component.
*
* @param v The vector to multiply by.
* @return The resulting vector from multiplying all the components.
*/
public SwerveVector times(SwerveVector v) {
return new SwerveVector(fwd * v.getFwd(), str * v.getStr(), rcw * v.getRcw());
}
/**
* Divide this swerve vector by another one by dividing each component.
*
* @param v The vector to divide by.
* @return The resulting vector from dividing all the components.
*/
public SwerveVector dividedBy(SwerveVector v) {
return new SwerveVector(fwd / v.getFwd(), str / v.getStr(), rcw / v.getRcw());
}
@Override
public String toString() {
return String.format("SwerveVector(fwd: %.3f, str: %.3f, rcw: %.3f)", getFwd(), getStr(), getRcw());
}
@Override
public boolean equals(Object o) {
if (o instanceof SwerveVector) {
SwerveVector v = (SwerveVector) o;
return getFwd() == v.getFwd() && getStr() == v.getStr() && getRcw() == v.getRcw();
} else {
return false;
}
}
}

View file

@ -0,0 +1,35 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
/**
* Length, Dimensions, and Vectors. The geometry package provides a number of classes
* that represent various geometric properties required in driving a swerve drive.
* The classes in this package are use throughout SwerveIO and are often dependent
* on one another, so it is important to understand them.
*
* <p>
* This package does <b>not</b> offer any advanced computations, it merely deals with
* representing geometric and physical structures in a way that is logical and cohesive
* for SwerveIO. For details on how the physics and math of SwerveIO works, see the
* {@link net.bancino.robotics.swerveio.kinematics} package, which details the exact
* implementations used.
* </p>
*
* @author Jordan Bancino
* @version 5.0.0
* @since 2.0.0
*/
package net.bancino.robotics.swerveio.geometry;

View file

@ -0,0 +1,64 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.gyro;
/**
* The gyro representation used by SwerveIO. This is a complete definition of
* the gyro API. Every gyro that is used with SwerveIO must implement this
* interface.
*
* <p>
* The interface itself is fairly simplistic because there are only a few
* methods and some of them can usually be implemented in a just a few lines of
* code. However, the output of the methods documented below has strict rules.
* Pay careful attention to the documentation for each method and be sure to
* follow the specifications, otherwise things may misbehave in unexpected ways.
* </p>
*
* @author Jordan Bancino
* @since 6.0.2
* @version 2.0.0
*/
public interface Gyro {
/**
* Get the gyro's current yaw. Note that this may not always be double
* precision. It could be an integer as well. In most cases, this should be a
* raw value straight from the hardware, however in some obscure cases,
* scaling may need to occur to convert a JNI reading into a degree measure in
* terms of 360.
*
* <p>
* This should go from 0 to 360. If the sensor does not return the angle in
* terms of 0 to 360, then this function should convert it. For instance, if the
* sensor returns 0 to -180 and 0 to 180, it should be scaled to 360 here. If
* the gyro angle is continuous&mdash;that is, it doesn't reset at all and just
* continues in the positive or negative direction&mdash;it should be converted
* to a 0-360 reading.
* </p>
*
* @return The yaw angle, in degrees.
*/
public double getAngle();
/**
* Zero the gyro. If the gyro hardware does not support this, a software offset
* should be performed in this method so that subsequent calls to
* {@link #getAngle()} are adjusted. This method is not allowed to fail.
*/
public void zero();
}

View file

@ -0,0 +1,78 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.gyro;
import net.bancino.log.Log;
import net.bancino.log.LogLevel;
import net.bancino.log.Loggable;
/**
* Convert a WPILib gyro to a SwerveIO Gyro. This class wraps a WPILib gyro
* object in an implementation of the SwerveIO gyro class so that it can be used
* with SwerveIO. This can be used as an easy way to add gyro support for any
* FRC gyro that implements the WPILib interface. The reason that SwerveIO does
* not use the WPILib gyro interface is because SwerveIO has strict gyro
* requirements that the WPILib interface does not specify. For example, the
* WPILib interface specifies a method that reports an angle measure without any
* discontinuities. This is fine for some uses, but SwerveIO does not operate on
* angles less than zero or greater than 360, so SwerveIO must scale continuous
* values outside of that range down. That's what this class does. It wraps the
* WPILib continuous angle in a SwerveIO discontinuous angle. It also renames
* the reset() method specified by WPILib Gyro to {@link #zero()}, which more
* accurately describes the purpose of the method.
*
* @author Jordan Bancino
* @version 6.0.2
* @since 6.0.2
*
*/
@Loggable
public class WPILibGyro implements Gyro {
private final edu.wpi.first.wpilibj.interfaces.Gyro gyro;
@Log(as = "angle", atLevel = LogLevel.IMPORTANT)
private double lastAngle;
/**
* Construct a new SwerveIO gyro with a WPILib gyro.
*
* @param gyro The WPILib to wrap.
*/
public WPILibGyro(edu.wpi.first.wpilibj.interfaces.Gyro gyro) {
if (gyro != null) {
this.gyro = gyro;
} else {
throw new IllegalArgumentException("Gyro cannot be null.");
}
}
@Override
public double getAngle() {
double angle = gyro.getAngle() % 360;
if (angle < 0) {
angle += 360;
}
lastAngle = angle;
return angle;
}
@Override
public void zero() {
gyro.reset();
}
}

View file

@ -0,0 +1,48 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
/**
* Gyro interfaces and helper classes. This package provides an interface for
* adding your own custom gyros. See {@link Gyro} for details on how a gyro is
* expected to behave.
*
* <p>
* Beginning users of SwerveIO will probably want to an existing gyro
* implementation, but they can also easily create their own implementations by
* directly implementing {@link Gyro}.
* <p>
* This package does not provide any module hardware support itself. Please
* refer to the documentation home page for adding hardware support for your kit
* module.
* </p>
* <p>
* If your gyro is listed as supported, use the hardware-specific classes to
* access them. The documentation for each individual gyro will provide you with
* all the specifics. If your gyro is not officially supported by SwerveIO, you
* will have to do a little more work to get SwerveIO to play nice with it, but
* due to the nature of SwerveIO and the design principles it was built with,
* adding your own gyro implementation should be trivial.
* </p>
* <p>
* Using the interfaces and classes in this package, you can add support for any
* gyro.
* </p>
*
* @author Jordan Bancino
* @version 6.0.0
* @since 2.0.0
*/
package net.bancino.robotics.swerveio.gyro;

View file

@ -0,0 +1,137 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.kinematics;
import net.bancino.robotics.swerveio.module.SwerveModule;
import net.bancino.robotics.swerveio.geometry.SwerveVector;
import net.bancino.log.Log;
import net.bancino.log.LogLevel;
import net.bancino.log.Loggable;
import net.bancino.robotics.swerveio.geometry.ChassisDimension;
import net.bancino.robotics.swerveio.geometry.ModuleVector;
import net.bancino.robotics.swerveio.geometry.Length.Unit;
import static java.lang.Math.toDegrees;
import static java.lang.Math.toRadians;
import static java.lang.Math.atan2;
import static java.lang.Math.sin;
import static java.lang.Math.cos;
import static java.lang.Math.sqrt;
import static java.lang.Math.pow;
import java.util.EnumMap;
import java.util.Map;
/**
* The default kinematics provider for SwerveIO. These kinematics will drive the
* swerve drive in what's often called "crab mode". This means that all four
* modules are driven independently by four different vectors.
*
* <p>
* The exact implementation of this kinematics calculator is beyond the scope of
* this documentation, but the implementation generally follows the pseudocode
* contained in popular, web-searchable swerve drive documents.
*
* @author Jordan Bancino
* @version 4.2.0
* @since 2.1.0
*/
@Loggable
public class DefaultSwerveKinematics implements SwerveKinematicsProvider {
@Log(as = "chassisDimensions")
private ChassisDimension chassis;
@Log(as = "calculatedVectors", atLevel = LogLevel.IMPORTANT)
private final Map<SwerveModule.Location, ModuleVector> lastVectors = new EnumMap<>(SwerveModule.Location.class);
/**
* Create a default swerve drive kinematics calculator. This will drive the
* swerve drive using default, sensible behavior.
*
* @param chassis The chassis dimensions to use for these calculations. This is
* required to ensure that the vectors are accurate. This class
* uses these dimensions solely for the ratio of the width to the
* height, but please ensure that the units are correct as well,
* because this dimension object is returned by
* {@link #getChassisDimensions()}, which is used throughout
* SwerveIO.
*/
public DefaultSwerveKinematics(ChassisDimension chassis) {
if (chassis != null) {
this.chassis = chassis;
} else {
throw new IllegalArgumentException("ChassisDimensions cannot be null.");
}
}
@Override
public ChassisDimension getChassisDimensions() {
return chassis;
}
@Override
public ModuleVector getModuleVector(SwerveModule.Location module, SwerveVector vector, double gyroAngle) {
double cosAngle = cos(toRadians(gyroAngle));
double sinAngle = sin(toRadians(gyroAngle));
double fwd = vector.getFwd();
double str = vector.getStr();
double rcw = vector.getRcw();
// Field-centric drive adjustments
double modFwd = (fwd * cosAngle) + (str * sinAngle);
double modStr = (-fwd * sinAngle) + (str * cosAngle);
fwd = modFwd;
str = modStr;
double vX, vY;
switch (module) {
case FRONT_RIGHT:
vX = str + rcw;
vY = fwd - rcw;
break;
case FRONT_LEFT:
vX = str + rcw;
vY = fwd + rcw;
break;
case REAR_LEFT:
vX = str - rcw;
vY = fwd + rcw;
break;
case REAR_RIGHT:
vX = str - rcw;
vY = fwd - rcw;
break;
default: // Should never happen.
throw new IllegalArgumentException("Unknown swerve module location: " + module);
}
double baseLength = chassis.getLength().get(Unit.BASE_UNIT);
double baseWidth = chassis.getWidth().get(Unit.BASE_UNIT);
double baseDiagonal = chassis.getDiagonal().get(Unit.BASE_UNIT);
vX *= baseLength / baseDiagonal;
vY *= baseWidth / baseDiagonal;
double angle = toDegrees(atan2(vX, vY));
double speed = sqrt(pow(vX, 2) + pow(vY, 2));
ModuleVector moduleVector = new ModuleVector(angle, (speed > 1) ? 1 : speed);
lastVectors.put(module, moduleVector);
return moduleVector;
}
}

View file

@ -0,0 +1,109 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.kinematics;
import net.bancino.robotics.swerveio.module.SwerveModule;
import net.bancino.robotics.swerveio.geometry.SwerveVector;
import net.bancino.robotics.swerveio.geometry.ChassisDimension;
import net.bancino.robotics.swerveio.geometry.ModuleVector;
/**
* The complete definition of the SwerveIO kinematics API. This interface must
* be implemented by all calculator classes that wish to supply a SwerveIO
* swerve drive with wheel angles and speeds. As described in the package
* documentation, the kinematics API is only responsible for performing the
* theoretical calculations; leave the execution of those calculations to the
* main swerve drive class.
*
* <p>
* The method implementations required by this interface should be stateless;
* they should have no memory of past or future data. Each method should merely
* calculate the target angle for the given function call.
* </p>
*
* @author Jordan Bancino
* @version 2.1.0
* @since 2.1.0
*/
public interface SwerveKinematicsProvider {
/**
* Provide the chassis dimensions of the swerve drive. These dimensions are
* usually required to effectively drive a swerve drive. Most implementations
* require the dimensions be provided in the constructor; if that is the case,
* the dimensions from the constructor can be returned here.
*
* <p>
* If the kinematics provider does not require the chassis dimensions to
* operate, this method still <b>must</b> provide accurate chassis dimensions,
* because the result of this function is used in numerous places throughout
* SwerveIO.
* </p>
*
* @return The chassis dimensions that are being used to compute the inverse
* kinematics, or can be used in the future to compute angular velocity.
*/
public ChassisDimension getChassisDimensions();
/**
* Compute the module vector for a given swerve module and swerve vector. This
* method is also field-centric aware; it should adjust the vector based on the
* provided gyro angle.
*
* <p>
* See the {@link net.bancino.robotics.swerveio.geometry.ModuleVector}
* documentation for the module vector requirements.
* </p>
*
* @param module The swerve module to perform the computation for.
* @param swerveVector The swerve drive vector used to calculate the module
* vector.
* @param gyroAngle The gyro angle to modify the vector calculations with.
* This value will always come from
* {@link net.bancino.robotics.swerveio.gyro.Gyro#getAngle()},
* so be sure to check the documentation for that method for
* the exact standards that this method should adhere to.
* You can reasonably assume that the output of that
* function is correct, and if it isn't, it isn't your
* problem here, it's a gyro implementation problem. It is
* not the job of the kinematics provider to correct invalid
* gyro input.
* @return A valid module vector for driving the provided swerve module.
*/
public ModuleVector getModuleVector(SwerveModule.Location module, SwerveVector swerveVector, double gyroAngle);
/**
* Compute the module vector for a given swerve module and swerve vector. The
* default implementation of this function calls
* {@link #getModuleVector(Location, SwerveVector, double)} with a gyro
* angle of zero. For most cases, this should behavior should not need to be
* overridden.
*
* <p>
* See the {@link net.bancino.robotics.swerveio.geometry.ModuleVector}
* documentation for the module vector requirements.
* </p>
*
* @param module The swerve module to perform the computation for.
* @param swerveVector The swerve drive vector used to calculate the module
* vector.
* @return A valid module vector for driving the provided swerve module.
*/
public default ModuleVector getModuleVector(SwerveModule.Location module, SwerveVector swerveVector) {
return getModuleVector(module, swerveVector, 0);
}
}

View file

@ -0,0 +1,45 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
/**
* Kinematics providers implement the theoretical physics behind the Swerve Drive.
* It is the job of {@link net.bancino.robotics.swerveio.SwerveDrive} to interpret
* the vectors produced by the classes in this package, and it is not the job of
* the classes in this package to determin how the vectors it generates should
* be achieved.
*
* <p>
* The term "kinematics", as used throughout this package's documentation, actually
* refers to "inverse kinematics". While kinematics takes individual vectors and
* generates a vector for the whole, the classes in this package do the opposite:
* they take a swerve drive vector
* ({@link net.bancino.robotics.swerveio.geometry.SwerveVector}) and convert it
* into individual module speeds and angles.
* </p>
*
* <p>
* Kinematics providers can have any level of complexity, and can be defined to
* drive a SwerveIO swerve drive in any number of ways. Each implementation should
* be thoroughly documented in it's own class documentation. Implementations can
* be fully "crab mode", or can theoretically drive the swerve drive like a
* tank-drive.
* <p>
*
* @author Jordan Bancino
* @version 2.1.0
* @since 2.1.0
*/
package net.bancino.robotics.swerveio.kinematics;

View file

@ -0,0 +1,109 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.log;
import java.util.List;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
import net.bancino.log.LogEntry;
import net.bancino.log.LogHandler;
/**
* Log to the smart dashboard. This class is used with {@link RobotLogger} to
* log the state of the swerve drive to the dashboard in real time.
*
* @author Jordan Bancino
* @version 6.1.0
* @since 6.1.0
*/
public class DashboardLog implements LogHandler {
private final String rootKey;
/**
* Construct a new dashboard log with the default root key, "SwerveIO".
*/
public DashboardLog() {
this("SwerveIO");
}
/**
* Construct a new dashboard log with a custom root key.
* @param rootKey The root key that all dashboard entries are placed under.
*/
public DashboardLog(String rootKey) {
this.rootKey = rootKey;
}
@Override
public void accept(LogEntry entry) {
recursiveLog(rootKey, entry);
SmartDashboard.updateValues();
}
private void recursiveLog(String parentKey, LogEntry entry) {
if (entry != null) {
parentKey += "/" + entry.getName();
switch (entry.getType()) {
case LIST:
List<LogEntry> list = entry.getAsList();
Object testVal = list.get(0).getValue();
if (testVal instanceof Number) {
double[] arr = new double[list.size()];
for (int i = 0; i < list.size(); i++) {
arr[i] = ((Number) list.get(i).getValue()).doubleValue();
}
SmartDashboard.putNumberArray(parentKey, arr);
} else if (testVal instanceof Boolean) {
boolean[] arr = new boolean[list.size()];
for (int i = 0; i < list.size(); i++) {
arr[i] = (boolean) list.get(i).getValue();
}
SmartDashboard.putBooleanArray(parentKey, arr);
} else {
String[] arr = new String[list.size()];
for (int i = 0; i < list.size(); i++) {
arr[i] = list.get(i).getValue().toString();
}
SmartDashboard.putStringArray(parentKey, arr);
}
break;
case NODE:
for (LogEntry child : entry.getChildren()) {
recursiveLog(parentKey, child);
}
break;
case VALUE:
Object val = entry.getValue();
if (val != null) {
if (val instanceof Boolean) {
SmartDashboard.putBoolean(parentKey, (Boolean) val);
} else if (val instanceof Number) {
SmartDashboard.putNumber(parentKey, ((Number) val).doubleValue());
} else {
SmartDashboard.putString(parentKey, val.toString());
}
} else {
SmartDashboard.putString(parentKey, "(null)");
}
break;
default:
break;
}
}
}
}

View file

@ -0,0 +1,77 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.log;
import net.bancino.log.Log;
import net.bancino.log.LogLevel;
import net.bancino.log.Loggable;
import net.bancino.log.Logger;
/**
* A simple logger designed for iterative logging. As robot code runs
* iteratively, so does the logging method. This class provides a simple wrapper
* around the logger class to ensure that logging occurs at a set interval.
*
* @author Jordan Bancino
* @version 6.1.0
* @since 6.1.0
*/
@Loggable
public class RobotLogger extends Logger {
@Log(atLevel = LogLevel.INFO)
private long interval = 0;
@Log
private long lastTime = 0;
/**
* Set the interval to log at. Note that this is a fuzzy interval; the actual
* period depends on how often {@link #log(Object)} is called.
*
* @param interval The minimum amount of time, in milliseconds, to place between
* each log. Set this to zero to log as fast as possible.
* @return This logger, for method chaining.
*
* @throws IllegalArgumentException If {@code interval} is less than zero.
*/
public RobotLogger setInterval(long interval) {
if (interval >= 0) {
this.interval = interval;
} else {
throw new IllegalArgumentException("Interval " + interval + " is less than zero ms.");
}
return this;
}
/**
* Get the logging interval that this logger is using.
*
* @return An interval, in milliseconds, or zero for as fast as possible.
*/
public long getInterval() {
return interval;
}
@Override
public void log(Object o) throws UnsupportedOperationException {
long time = System.currentTimeMillis();
if (time - lastTime >= interval) {
super.log(o);
lastTime = time;
}
}
}

View file

@ -0,0 +1,24 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
/**
* The SwerveIO logging API that handles outputting log data.
*
* @author Jordan Bancino
* @version 5.0.0
* @since 1.4.0
*/
package net.bancino.robotics.swerveio.log;

View file

@ -0,0 +1,546 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.module;
import java.util.function.Consumer;
import java.util.function.BiConsumer;
import edu.wpi.first.wpilibj.motorcontrol.MotorController;
import net.bancino.log.Log;
import net.bancino.log.LogLevel;
import net.bancino.log.Loggable;
import net.bancino.robotics.swerveio.encoder.Encoder;
import net.bancino.robotics.swerveio.pid.PIDController;
import net.bancino.robotics.swerveio.pid.DefaultPIDController;
import net.bancino.robotics.swerveio.geometry.Length;
/**
* A generic swerve module class that provides boilerplate module code. This
* class is designed to greatly simplify module implementation development
* because it implements the methods required by {@link SwerveModule} in a way
* that is reusable.
*
* <p>
* This class can be instantiated with {@link GenericSwerveModule.Builder}, but
* this should be for prototyping purposes only. While GenericSwerveModule is
* complete and usable on its own, the recommended way to use it is to extend
* it, providing your PID constants and physical property data within the class
* itself, and declaring a constructor that takes CAN IDs, encoder information,
* or other fields that aren't hard-coded.
* </p>
* <p>
* Swerve module hardware should have dedicated classes that abstract all the
* necessary information from the user. A module kit class provides all the
* constant information for that kit hard-coded into it. Its constructor
* requires only the CAN IDs and encoder IDs to use.
* </p>
*
* @author Jordan Bancino
* @version 6.0.0
* @version 1.0.0
*/
@Loggable
public class GenericSwerveModule implements SwerveModule {
/**
* Build a generic swerve module object. This can be used on its own, but as
* described in {@link GenericSwerveModule}, it is primarily intended to be
* instantiated anonymously and passed into the superclass constructor of an
* extending class.
*
* <p>
* Because instances of this class are primarily intended to be passed into a
* superclass constructor, it is designed with complete encapsulation in mind.
* Various functions support consumers so that lambda expressions can be used
* for initialization.
* </p>
*
*/
public static class Builder {
private MotorController driveMotor = null, pivotMotor = null;
private Encoder pivotEncoder = null, driveEncoder = null;
private double driveGearRatio = 0, driveMaxRPM = 0;
private Length wheelDiameter = null;
private PIDController drivePid, pivotPid;
private double pivotGearRatio = 1;
private double angleOffset = 0;
/**
* Set the drive motor for this swerve drive module, optionally providing an
* initializer function that can initialize the motor before it is added. The
* initializer function takes the builder instance that this method was called
* on, as well as the motor that is passed as the first parameter.
*
* @param <S> The WPILib speed controller type. This is automatically
* inferred from the first parameter provided to this function
* and will be the type that is provided to the
* {@code BiConsumer}, so casting is not necessary.
* @param driveMotor The the drive motor add to this builder.
* @param initialize A consumer function that takes this builder, as well as the
* motor that was passed as the first parameter. This is
* primarily intended to be used as the motor initializer.
* There may be times when all initialization needs to occur
* inside the builder, such as when passing an anonymous
* builder into a superclass constructor.
* @return This builder, for command chaining.
*/
public <S extends MotorController> Builder setDriveMotor(S driveMotor, BiConsumer<Builder, S> initialize) {
if (initialize != null) {
initialize.accept(this, driveMotor);
}
this.driveMotor = driveMotor;
return this;
}
/**
* Set the drive motor for this builder. This does not provide an initialization
* interface, it merely assigns the drive motor to the builder. For advanced
* usage, such as performing initialization in the superclass constructor, see
* {@link #setDriveMotor(MotorController, BiConsumer)}.
*
* @param driveMotor The drive motor to assign to this swerve module builder.
* @return This builder, for command chaining.
*/
public Builder setDriveMotor(MotorController driveMotor) {
return setDriveMotor(driveMotor, null);
}
/**
* Set the pivot motor for this swerve drive module, optionally providing an
* initializer function that can initialize the motor before it is added. The
* initializer function takes the builder instance that this method was called
* on, as well as the motor that is passed as the first parameter.
*
* @param <S> The WPILib speed controller type. This is automatically
* inferred from the first parameter provided to this function
* and will be the type that is provided to the
* {@code BiConsumer}, so casting is not necessary.
* @param pivotMotor The the pivot motor add to this builder.
* @param initialize A consumer function that takes this builder, as well as the
* motor that was passed as the first parameter. This is
* primarily intended to be used as the motor initializer.
* There may be times when all initialization needs to occur
* inside the builder, such as when passing an anonymous
* builder into a superclass constructor.
* @return This builder, for command chaining.
*/
public <S extends MotorController> Builder setPivotMotor(S pivotMotor, BiConsumer<Builder, S> initialize) {
if (initialize != null) {
initialize.accept(this, pivotMotor);
}
this.pivotMotor = pivotMotor;
return this;
}
/**
* Set the pivot motor for this builder. This does not provide an initialization
* interface, it merely assigns the drive motor to the builder. For advanced
* usage, such as performing initialization in the superclass constructor, see
* {@link #setDriveMotor(MotorController, BiConsumer)}.
*
* @param pivotMotor The drive motor to assign to this swerve module builder.
* @return This builder, for command chaining.
*/
public Builder setPivotMotor(MotorController pivotMotor) {
return setPivotMotor(pivotMotor, null);
}
/**
* Provide the encoder connected to the drive motor.
*
* @param driveEncoder The encoder that monitors the drive motor.
* @return This builder, for command chaining.
*/
public Builder setDriveEncoder(Encoder driveEncoder) {
return setDriveEncoder(driveEncoder, null);
}
/**
* Provide the encoder connected to the drive motor and initialize it.
*
* @param <E> The encoder type. This is automatically inferred from the
* first parameter and allows you to use the initializer
* function without having to cast the object to your
* encoder type.
* @param driveEncoder The encoder that monitors the drive motor.
* @param initialize The function to run on the encoder. It also provides this
* builder function.
* @return This instance for chaining.
*/
public <E extends Encoder> Builder setDriveEncoder(E driveEncoder, BiConsumer<Builder, E> initialize) {
if (initialize != null) {
initialize.accept(this, driveEncoder);
}
this.driveEncoder = driveEncoder;
return this;
}
/**
* Provide the encoder connected to the pivot motor.
*
* @param pivotEncoder The encoder that monitors the pivot motor.
* @return This builder, for command chaining.
*/
public Builder setPivotEncoder(Encoder pivotEncoder) {
return setPivotEncoder(pivotEncoder, null);
}
/**
* Provide the encoder connected to the pivot motor and initialize it.
*
* @param <E> The encoder type. This is automatically inferred from the
* first parameter and allows you to use the initializer
* function without having to cast the object to your
* encoder type.
* @param pivotEncoder The encoder that monitors the pivot motor.
* @param initialize The function to run on the encoder. It also provides this
* builder function.
* @return This instance for chaining.
*/
public <E extends Encoder> Builder setPivotEncoder(E pivotEncoder, BiConsumer<Builder, E> initialize) {
if (initialize != null) {
initialize.accept(this, pivotEncoder);
}
this.pivotEncoder = pivotEncoder;
return this;
}
/**
* Set a custom PID controller that is connected to the drive motor. This should
* be used for hardware-level PID controllers, or PID controllers implemented in
* hardware vendor libraries. Calls to
* {@link GenericSwerveModule#getDrivePIDController()} will return this
* controller, so it can be beneficial to properly set this up if you want your
* robot code to be able to modify the PID controller properties.
*
* @param drivePid The PID controller attached to the drive motor.
* @return This builder, for chaining.
*/
public Builder setDrivePIDController(PIDController drivePid) {
this.drivePid = drivePid;
return this;
}
/**
* Set a custom PID controller that is connected to the pivot motor. This should
* be used for hardware-level PID controllers, or PID controllers implemented in
* hardware vendor libraries. Calls to
* {@link GenericSwerveModule#getPivotPIDController()} will return this
* controller, so it can be beneficial to properly set this up if you want your
* robot code to be able to modify the PID controller properties.
*
* @param pivotPid The PID controller attached to the pivot motor.
* @return This builder, for chaining.
*/
public Builder setPivotPIDController(PIDController pivotPid) {
this.pivotPid = pivotPid;
return this;
}
/**
* Set the input/output ratio of the drive motor gearbox. This is used for
* determining the maximum velocity of this module.
*
* @param driveGearRatio The decimal value of the gear ratio of the gearbox that
* connects the drive motor to the wheel.
* @return This builder, for command chaining.
*/
public Builder setDriveGearRatio(double driveGearRatio) {
this.driveGearRatio = driveGearRatio;
return this;
}
/**
* Set the input/output ratio of the pivot motor gearbox. This is used for
* determining how many encoder counts there are per one module revolution, so
* you can ignore this method if you are using a 1:1 encoder.
*
* @param pivotGearRatio The decimal value of the gear ratio of the gearbox that
* connects the pivot motor to the output rotating
* mechanism that controls the swerve module's rotation.
* @return This builder, for command chaining.
*/
public Builder setPivotGearRatio(double pivotGearRatio) {
this.pivotGearRatio = pivotGearRatio;
return this;
}
/**
* Set the maximum drive motor rotations per minute. This is used for
* determining the maximum velocity of this module.
*
* @param driveMaxRPM The decimal value of the maximum drive rotations per
* minute. This is usually discovered by experimentation
* @return This builder, for command chaining.
*/
public Builder setDriveMaxRPM(double driveMaxRPM) {
this.driveMaxRPM = driveMaxRPM;
return this;
}
/**
* Set the diameter of the drive wheel. This is used for determining the maximum
* velocity of this module.
*
* @param wheelDiameter The diameter of the wheel, given as a length object so
* you can choose your units.
* @return This builder, for command chaining.
*/
public Builder setWheelDiameter(Length wheelDiameter) {
this.wheelDiameter = wheelDiameter;
return this;
}
/**
* Get the angle offset that this module should use. This is useful for setting
* the "forward" position of the swerve module. When using absolute encoders,
* you're going to get an angle measure that is aligned with the values those
* encoders output. If your encoder is not zero at the forward position, which
* is nearly impossible, then this should be the angle value at which the swerve
* module is considered to be in the "forward" position.
*
* @param angleOffset The angle measure, in degrees, that represents the
* "forward" position of the swerve module. This is used in
* the calculation performed by
* {@link SwerveModule#getAngle()}, so be sure that it is
* accurate for every swerve module, because
* {@link SwerveModule#getAngle()} is adjusted according to
* this value so that it reports a correct reading.
* @return This builder, for command chaining.
*/
public Builder setAngleOffset(double angleOffset) {
this.angleOffset = angleOffset;
return this;
}
/**
* Build the generic swerve module using an initialization function.
*
* @param initializer A function that accepts the newly-constructed swerve
* module and initializes it or modifies it before the build
* returns. This is useful for applying a custom setup when
* bulding the module.
* @return An object that represents the swerve module using the provided
* information.
* @throws IllegalArgumentException If there is a configuration error.
*/
public GenericSwerveModule build(Consumer<GenericSwerveModule> initializer) throws IllegalArgumentException {
GenericSwerveModule module = new GenericSwerveModule(this);
if (initializer != null) {
initializer.accept(module);
}
return module;
}
/**
* Build the generic swerve module without using an initializer function.
*
* @return An object that represents the swerve module using the provided
* information.
* @throws IllegalArgumentException If there is a configuration error.
*/
public GenericSwerveModule build() throws IllegalArgumentException {
return build(null);
}
}
private MotorController driveMotor, pivotMotor;
@Log(atLevel = LogLevel.INFO)
private Encoder pivotEncoder, driveEncoder;
@Log
private double driveGearRatio, driveMaxRPM;
@Log
private Length wheelDiameter;
@Log(atLevel = LogLevel.DEBUG)
private PIDController pivotPid = new DefaultPIDController(), drivePid = new DefaultPIDController();
@Log
private double pivotGearRatio;
@Log
private double angleOffset;
/**
* Construct a generic swerve module. This can only be used in subclasses. The
* public way to build a generic swerve module is with the builder class.
*
* @param builder The builder to use to construct this swerve module. This will
* most likely be anonymous because the call to this superclass
* constructor must be the first line in the constructor, unless
* you pass a static builder in.
*/
protected GenericSwerveModule(Builder builder) {
if (builder.driveMotor != null) {
this.driveMotor = builder.driveMotor;
} else {
throw new IllegalArgumentException("Drive motor cannot be null.");
}
if (builder.pivotMotor != null) {
this.pivotMotor = builder.pivotMotor;
} else {
throw new IllegalArgumentException("Pivot motor cannot be null.");
}
if (builder.driveEncoder != null) {
this.driveEncoder = builder.driveEncoder;
} else {
throw new IllegalArgumentException("Drive encoder cannot be null");
}
if (builder.pivotEncoder != null) {
this.pivotEncoder = builder.pivotEncoder;
} else {
throw new IllegalArgumentException("Pivot encoder cannot be null.");
}
if (builder.driveGearRatio > 0) {
this.driveGearRatio = builder.driveGearRatio;
} else {
throw new IllegalArgumentException("Gear ratio cannot be less than or equal to zero.");
}
if (builder.driveMaxRPM > 0) {
this.driveMaxRPM = builder.driveMaxRPM;
} else {
throw new IllegalArgumentException("Maximum RPMs cannot be less than or equal to zero.");
}
if (builder.wheelDiameter != null) {
this.wheelDiameter = builder.wheelDiameter;
} else {
throw new IllegalArgumentException("Wheel diameter cannot be null.");
}
if (builder.drivePid != null) {
this.drivePid = builder.drivePid;
}
if (builder.pivotPid != null) {
this.pivotPid = builder.pivotPid;
} else {
/*
* Set sensible defaults for this module, but only if we're using the default
* PID controller.
*/
this.pivotPid.setOutputLimits(1);
this.pivotPid.setP(0.0052);
this.pivotPid.setI(0.000069);
this.pivotPid.setD(0);
}
if (builder.pivotGearRatio > 0) {
this.pivotGearRatio = builder.pivotGearRatio;
} else {
throw new IllegalArgumentException("Pivot gear ratio must be greater than zero.");
}
setAngleOffset(builder.angleOffset);
/*
* Don't let the PID loop output anything greater than the limits of the motor.
*/
pivotPid.setOutputLimits(1);
drivePid.setOutputLimits(1);
/* Tell the PID loop what the maximum range of the encoder is. */
pivotPid.setSetpointRange(pivotEncoder.countsPerRevolution());
}
@Override
public MotorController getDriveMotor() {
return driveMotor;
}
@Override
public MotorController getPivotMotor() {
return pivotMotor;
}
@Override
public Encoder getDriveEncoder() {
return driveEncoder;
}
@Override
public Encoder getPivotEncoder() {
return pivotEncoder;
}
@Override
public PIDController getDrivePIDController() {
return drivePid;
}
@Override
public PIDController getPivotPIDController() {
return pivotPid;
}
@Override
public double getDriveGearRatio() {
return driveGearRatio;
}
@Override
public double getPivotGearRatio() {
return pivotGearRatio;
}
@Override
public double getDriveMaxRPM() {
return driveMaxRPM;
}
@Override
public Length getWheelDiameter() {
return wheelDiameter;
}
@Override
public double getAngleOffset() {
return angleOffset;
}
@Override
public void setAngleOffset(double offset) {
this.angleOffset = offset;
}
@Override
public boolean equals(Object o) {
if (o instanceof SwerveModule) {
SwerveModule asm = (SwerveModule) o;
return asm.getDriveMaxRPM() == getDriveMaxRPM() && asm.getDriveGearRatio() == getDriveGearRatio()
&& asm.getOutputThreshhold() == getOutputThreshhold()
&& asm.getWheelDiameter() == getWheelDiameter();
} else {
return false;
}
}
@Override
public String toString() {
return "SwerveModule(Drive: " + driveMotor.getClass().getSimpleName() + ", Pivot: "
+ pivotMotor.getClass().getSimpleName() + ")";
}
}

View file

@ -0,0 +1,527 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.module;
import net.bancino.robotics.swerveio.pid.PIDController;
import edu.wpi.first.wpilibj.motorcontrol.MotorController;
import net.bancino.robotics.swerveio.encoder.Encoder;
import net.bancino.robotics.swerveio.geometry.Length;
import net.bancino.robotics.swerveio.geometry.ModuleVector;
import net.bancino.robotics.swerveio.geometry.Length.Unit;
/**
* The complete definition of a SwerveIO swerve module. Every swerve module
* implementation must implement this interface. A SwerveIO swerve drive
* contains a module map of modules that can be driven using the methods
* described in this interface.
*
* <p>
* This interface binds all the components of the a single swerve module
* together. A swerve module consists of the following components, all available
* through this interface:
* </p>
* <ul>
* <li>Pivot Motor</li>
* <li>Pivot Encoder</li>
* <li>Pivot PID controller</li>
* <li>Drive Motor</li>
* <li>Drive Encoder</li>
* <li>Drive PID Controller</li>
* </ul>
* <p>
* This interface is also responsible for providing certain metadata for the
* swerve module. The following information is required for proper operation of
* the swerve drive, because it is used in the kinematic calculations:
* </p>
* <ul>
* <li>Drive gear ratio (input/output)</li>
* <li>Drive output threshhold</li>
* <li>Wheel diameter</li>
* <li>Drive motor max speed in rotations per minute (RPM)</li>
* </ul>
* <p>
* These are all used by the default implementation of {@link #getMaxSpeed()}
* and should be reasonably accurate. For information on how to implement the
* relevant getter methods, see their individual documentation.
* </p>
* <p>
* This interface is not designed to be implemented directly. See
* {@link GenericSwerveModule} for a basic implementation that provides
* boilerplate code.
* </p>
*
* @author Jordan Bancino
* @version 6.0.0
* @since 1.0.0
*/
public interface SwerveModule {
/**
* When manually driving the PID loop, we must modify our feedback to output the
* correct the distance and orientation of PID output.
*
* <p>
* Normally, one would not modify the feedback, however this is necessary to
* ensure the PID loop behaves properly, providing the correct outputs in the
* correct direction, since we have to deal with crossing over 180 and of course
* 0.
* </p>
*
* @param currentPos The current position, that is, the feedback.
* @param targetPos The setpoint, the desired position.
* @param countsPerPivotRevolution The number of encoder counts in a single
* pivot revolution (360 degrees)
* @return A corrected "feedback" value that should be put into the pivot pid
* loop.
*/
public static double correctPivotFeedback(double currentPos, double targetPos, double countsPerPivotRevolution) {
/* Set up some convenience variables for processing. */
double quarterPivot = countsPerPivotRevolution / 4.0;
double halfPivot = countsPerPivotRevolution / 2.0;
/*
* Normally, one would not modify the feedback, however this is necessary to
* ensure the PID loop behaves properly, providing the correct outputs in the
* correct direction, since we have to deal with crossing over 180 and of course
* 0.
*/
double feedbackMod = currentPos;
/* Calculate the error between the setpoint and the feedback. */
double positionDiff = targetPos - currentPos;
/*
* Modify the feedback to produce the correct PID output.
*/
if (targetPos < currentPos) { /* If setpoint is less than feedback. */
if (positionDiff > -halfPivot) { /* If error is greater than -180 degrees. */
feedbackMod = currentPos; /* Do not modify the feedback. */
} else { /* Setpoint is greater than feedback. */
/*
* If setpoint is greater than 90 degrees and the position difference is not
* equal to -180
*/
if (targetPos > quarterPivot && positionDiff != -halfPivot) {
feedbackMod = -1 * (countsPerPivotRevolution + positionDiff);
} else {
feedbackMod = currentPos - countsPerPivotRevolution;
}
}
}
/* Setpoint is greater than feedback. */
if (targetPos > currentPos) {
if (positionDiff > halfPivot) { /* error is greater than 180 */
feedbackMod = currentPos + countsPerPivotRevolution;
} else {
feedbackMod = currentPos;
}
}
return feedbackMod;
}
/**
* An enumeration that represents the four swerve modules. This is used in
* {@link net.bancino.robotics.swerveio.SwerveDrive}'s module map to map swerve
* drive module implementations to a human-readable and easily-accessible name.
*
* <p>
* Because of the nature of swerve drive, it technically doesn't matter which
* side of the robot is the "front" and which is the "back", but all robots have
* fronts and backs, so just make your swerve drive matches. The physical
* locations are important because the kinematics providers generate per-module
* vectors, so we need some way to differentiate the modules. Referring to
* modules by their location is the easiest way to refer to them. Some swerve
* drive libraries literally refer to modules as 1, 2, 3, and 4, but that is
* unintuitive.
* </p>
*
* <p>
* This enumeration can be used by methods that operate on one or more swerve
* modules, such as
* {@link net.bancino.robotics.swerveio.SwerveDrive#drive(net.bancino.robotics.swerveio.geometry.SwerveVector)}
* for example. The constant value represents the module that is being operated
* on.
* </p>
*
* @see net.bancino.robotics.swerveio.SwerveDrive#getModule(Location)
*/
public static enum Location {
/**
* The swerve module located in the front left corner of the robot.
*/
FRONT_LEFT,
/**
* The swerve module located in the front right corner of the robot.
*/
FRONT_RIGHT,
/**
* The swerve module located in the rear left corner of the robot.
*/
REAR_LEFT,
/**
* The swerve module located in the rear right corner of the robot.
*/
REAR_RIGHT
}
/**
* Drive the swerve module using the provide module vector. The module vector
* provides bounded and checked speed and angle values. See the documentation
* for it to know how to implement this method, though the default
* implementation should be fine for the overwhelming majority of cases.
*
* <p>
* This function can directly pass the speed component of the module vector into
* the drive motor, but it will most likely need to pass the angle into a PID
* controller, and then pass the output of the PID controller into the pivot
* motor.
* </p>
*
* @param vector The vector to be used to set the motor speeds in this module.
*/
public default void drive(ModuleVector vector) {
setSpeed(vector.getSpeed());
setAngle(vector.getAngle());
}
/**
* Read the pivot encoder and report the angle that this module is currently set
* to. The default implementation uses the following methods:
*
* <ul>
* <li>{@link #getPivotGearRatio()}</li>
* <li>{@link #getPivotEncoder()}</li>
* <li>{@link #getAngleOffset()}</li>
* </ul>
*
* There is no need to override this default implementation because it
* automatically takes care of scaling the raw encoder reading based on the
* number of counts per pivot motor shaft revolution, as well as the pivot
* <i>encoder</i> gear ratio.
*
* @return The angle measure, in degrees, that this swerve module is currently
* facing. This will be within the interval [0,360).
*/
public default double getAngle() {
/* Calculate the actual angle based on the encoder reading. */
double countsPerPivotRevolution = getPivotEncoder().countsPerRevolution() * getPivotGearRatio();
double angle = ((360 / countsPerPivotRevolution) * getPivotEncoder().get()) % 360;
/*
* If the encoder reading is continuous, the angle will go below zero. This will
* fix the angle within the interval [0,360)
*/
if (angle < 0) {
angle += 360;
}
/* Apply the angle offset. */
double offset = getAngleOffset();
if (angle - offset < 0) {
angle += 360;
}
angle -= offset;
return angle;
}
/**
* Set the pivot angle of this swerve module. The default implementation uses
* the following methods:
*
* <ul>
* <li>{@link #getAngle()}</li>
* <li>{@link #getPivotPIDController()}</li>
* <li>{@link #getPivotMotor()}</li>
* </ul>
*
* There usually is no need to override this default implementation because it
* automatically takes care of adjusting the angle reference as needed before
* passing it into the PID controller, the output of which is passed into the
* pivot motor.
*
* @param angle The angle measure, in degrees, that this swerve module should be
* facing facing. This should be within the interval (-360,360), or
* [0,360). Both behave the same.
*
* @throws IllegalArgumentException If {@code angle} is out of bounds.
*/
public default void setAngle(double angle) {
if (ModuleVector.checkAngle(angle)) {
/*
* If the kinematics provider gives a negative angle, we should convert it to a
* positive angle that is equivalent, because that's what correctPivotFeedback()
* expects.
*/
if (angle < 0) {
angle += 360;
}
/* Calculate the angle feedback and setpoint */
double setpoint = angle;
double feedback = getAngle();
double feedbackMod = correctPivotFeedback(feedback, setpoint, 360);
/* Pass the feedback and setpoint into the PID controller */
double outRef = getPivotPIDController().getOutput(feedbackMod, setpoint);
/* Pass the PID controller output into the pivot motor. */
getPivotMotor().set(outRef);
} else {
throw new IllegalArgumentException("Angle out of bounds: " + angle);
}
}
/**
* Get the angle offset that this module should use. This is useful for setting
* the "forward" position of the swerve module. When using absolute encoders,
* you're going to get an angle measure that is aligned with the values those
* encoders output. If your encoder is not zero at the forward position, which
* is nearly impossible, then this should be the angle value at which the swerve
* module is considered to be in the "forward" position.
*
* @return The angle measure, in degrees, that represents the "forward" position
* of the swerve module. This is used in the calculation performed by
* {@link #getAngle()}, so be sure that it is accurate for every swerve
* module, because {@link #getAngle()} is adjusted according to this
* value so that it reports a correct reading.
*/
public double getAngleOffset();
/**
* Set the angle offset that this angle should use. This is useful for setting
* the "forward" position of the swerve module. When using absolute encoders,
* you're going to get an angle measure that is aligned with the values those
* encoders output. If your encoder is not zero at the forward position, which
* is nearly impossible, then this should be the angle value at which the swerve
* module is considered to be in the "forward" position.
*
* <p>
* This is used for adjusting the offset "on the fly" in the sense that the
* robot program can be running for any duration of time before an offset change
* is requested. Most likely, this will be for re-calibrating the encoders. It
* would not be wise to call this method more than once per robot code lifetime.
* </p>
*
* @param offset The angle measure, in degrees, that represents the "forward"
* position of the swerve module. This is used in the calculation
* performed by {@link #getAngle()}, so be sure that it is
* accurate for every swerve module, because {@link #getAngle()}
* is adjusted according to this value so that it reports a
* correct reading.
*/
public void setAngleOffset(double offset);
/**
* Get the speed of the drive motor for this swerve module.
*
* @return The speed of the drive motor in percent output. This will be within
* the bounds [-1,1].
*/
public default double getSpeed() {
return getDriveMotor().get();
}
/**
* Set the speed of the drive motor for this swerve module.
*
* @param speed The speed of the drive motor in percent output. This should be
* within the bounds [-1, 1].
*
* @throws IllegalArgumentException If {@code speed} is out of bounds.s
*/
public default void setSpeed(double speed) {
if (ModuleVector.checkSpeed(speed)) {
getDriveMotor().set(speed);
} else {
throw new IllegalArgumentException("Speed out of bounds: " + speed);
}
}
/**
* Get the drive motor associated with this swerve module. This allows you to
* collect information on it, but you should never modify it's state, as doing
* so could mess up the operation of the swerve drive.
*
* @return A WPILib SpeedController that represents the drive motor of this
* module, never null.
*/
public MotorController getDriveMotor();
/**
* Get the pivot motor associated with this swerve module. This allows you to
* collect information on it, but you should never modify it's state, as doing
* so could mess up the operation of the swerve drive.
*
* @return A WPILib SpeedController that represents the pivot motor of this
* module, never null.
*/
public MotorController getPivotMotor();
/**
* Retrieve the encoder attached to the pivot motor.
*
* @return The encoder that is monitoring the pivot motor output, never null,
* because pivot encoders are required.
* @see net.bancino.robotics.swerveio.encoder.Encoder
*/
public Encoder getPivotEncoder();
/**
* Retrieve the encoder attached to the drive motor. Since this is not used by
* SwerveIO directly, module implementations may return null if no encoder is
* present, but it is common practice to provide an interface for the integrated
* encoder, if the drive motor is equipped with one.
*
* @return The encoder that is monitoring the drive motor output, or null if
* there isn't one.
* @see net.bancino.robotics.swerveio.encoder.Encoder
*/
public Encoder getDriveEncoder();
/**
* Get the PID controller driving the drive motor. Note that this may not always
* be in use if open-loop control is in effect.
*
* @return The PID controller associated with this swerve module's drive motor,
* or null if there isn't one.
*/
public PIDController getDrivePIDController();
/**
* Get the PID controller driving the pivot motor. Note that this may not always
* be in use if open-loop control is in effect.
*
* @return The PID controller driving the pivot motor, never null, because a
* position loop is used to set the pivot angle.
*/
public PIDController getPivotPIDController();
/**
* The output threshold is the absolute minimum current percentage that a motor
* controller can run on that produces motion. You'll only ever want to override
* this if your module misbehaves at extremely low speeds. The output of this
* method is used to compute the "idle angle" of the pivot motors. A swerve
* module is considered to be "idling" if it's percent output is below this
* threshhold.
*
* @return The absolute minimum current percentage. This value is usually
* discovered by experimentation. The default is 1% current, as this
* seems to be a safe average for FRC motor controllers and motors.
* @see net.bancino.robotics.swerveio.SwerveDrive#setIdleAngle(double, boolean)
*/
public default double getOutputThreshhold() {
return 0.01;
}
/**
* The gear ratio of the <b>drive</b> motor. This is the ratio of the input
* shaft rotations to the output shaft rotations. For an example, if your drive
* gearbox outputs 1 revolution of the shaft for every 8 motor revolutions, the
* gear ratio is 8.
*
* <p>
* This value is used to compute the maximum velocity of this swerve module.
* </p>
*
* @return The gear ratio used to drive this module, (input / output)
*/
public double getDriveGearRatio();
/**
* The gear ratio of the <b>pivot</b> motor. This is the ratio of the input
* shaft rotations to the output steering rotations. For an example, if your
* pivot gearbox outputs 1 complete swerve module revolution for every 6 motor
* revolutions, the gear ratio is 6.
*
* <p>
* This value is used to compute the encoder counts per revolution if the
* encoder is on the input shaft. You can safely use the default value (1) if
* you are using a 1:1 encoder, even if the actual gear ratio isn't 1:1.
* </p>
*
* @return The gear ratio of the pivot motor and encoder, (input / output).
*/
public default double getPivotGearRatio() {
return 1;
}
/**
* Get the maximum realistic output in rotations per minute that this module's
* drive motor can accomplish. Note that this is the rotations of the drive
* motor itself, <b>not</b> the output wheel. In other words, this is rotations
* per minute of the input shaft to the drive gearbox. This can usually be found
* on your motor's spec page, or observed with a tachometer.
*
* <p>
* This value is used to compute the maximum velocity of this swerve module.
* </p>
*
* @return The maximum rotations per minute of the drive motor's shaft.
*/
public double getDriveMaxRPM();
/**
* Get the diameter of the wheel attached to this module. This is used for
* computing the maximum velocity of this swerve module, and should be found
* from a specifications sheet, or measured accurately.
*
* @return The diameter of the wheel attached to this swerve module.
*/
public Length getWheelDiameter();
/**
* Get the maximum speed drive speed of this module based on the maximum drive
* RPMs, the wheel diameter, and the gear ratio. The following functions are
* used in the default implementation, and should thus be accurate:
*
* <ul>
* <li>{@link #getDriveMaxRPM()}</li>
* <li>{@link #getDriveGearRatio()}</li>
* <li>{@link #getWheelDiameter()}</li>
* </ul>
*
* @return The maximum linear velocity that this module can achieve in meters
* per second, using the provided module specifications. There is no
* reason to override this method, because as long as the values
* provided by the listed functions are accurate, this will accurately
* compute the maximum velocity.
*/
public default double getMaxSpeed() {
/* Rotations per minute -> rotations per second. */
double maxInputRPS = getDriveMaxRPM() / 60;
/* Convert input revolutions to output revolutions. */
double maxOutputRPS = maxInputRPS / getDriveGearRatio();
/* Get the circumference of the wheel. */
double wheelCircumference = getWheelDiameter().get(Unit.METERS) * Math.PI;
return maxOutputRPS * wheelCircumference;
}
/**
* Stop the entire module instantly. This just calls {@code stopMotor()} on the
* pivot motor and the drive motor.There is generally no need to override this
* default implementation.
*/
public default void stop() {
getPivotMotor().stopMotor();
getDriveMotor().stopMotor();
}
}

View file

@ -0,0 +1,67 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
/**
* Swerve Module interfaces and helper classes. See {@link SwerveModule} for
* details on how a swerve module is expected to behave.
*
* <p>
* Beginning users of SwerveIO will want to use an existing swerve module
* implementation, or create their own modules with
* {@link GenericSwerveModule.Builder}, which creates
* {@link GenericSwerveModule} instances that implement {@link SwerveModule},
* allowing them to be used in the construction of a swerve drive. For details
* on how to construct a swerve drive, see the
* {@link net.bancino.robotics.swerveio} package overview.
* </p>
* <p>
* This package does not provide any module hardware support itself. Please
* refer to the documentation home page for adding hardware support for your kit
* module.
* </p>
* <p>
* If your swerve module is listed as supported, use the hardware-specific
* classes to implement your swerve modules. The documentation for each
* individual module class will provide you with all the specifics. If your
* module is not officially supported by SwerveIO, you will have to do a little
* bit more work to get SwerveIO to play nice with it, but due to the nature of
* SwerveIO and the design principles it was build with, adding your own module
* implementation should be trivial.
* </p>
* <p>
* Using the interfaces and classes in this package, you can add support for
* <i>any</i> swerve module, be it a kit we haven't heard of, or a custom-built
* module. If you go this route, you would be most benefited by extending the
* {@link GenericSwerveModule} class, passing a builder object into the
* superclass constructor using what was provided by your constructor.
* {@link GenericSwerveModule} is a fully-supported implementation of
* {@link SwerveModule}, and the base on which the officially supported modules
* are built, so you can be sure it will work reliably.
* </p>
* <p>
* {@link GenericSwerveModule} may have some limitations that make it unsuitable
* for your purposes. If, after trying it, you've decided that this is the case,
* you are encouraged to directly implement {@link SwerveModule}. This will
* require most of the boilerplate code that {@link GenericSwerveModule} was
* intended to eliminate, but it certainly provides much greater control over
* your module.
* </p>
*
* @author Jordan Bancino
* @version 6.0.0
* @since 1.0.0
*/
package net.bancino.robotics.swerveio.module;

View file

@ -0,0 +1,44 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
/**
* The base SwerveIO package that contains the core functionality of SwerveIO.
* <p>
* The classes in the package make up the entry point of the SwerveIO API. They
* bind together all the components present in the subpackages and present an
* abstract representation of a swerve drive. There are absolutely zero hardware
* dependencies at this level; everything is abstract.
* </p>
* <p>
* The SwerveDrive class is the programmer's first contact with SwerveIO. It
* provides a builder for constructing a new swerve drive object capable of
* driving any hardware configuration without modification.
* </p>
* <p>
* This package also contains all the enumerations used in this library, which
* are used primarily for setting up maps and custom configurations.
* </p>
* <p>
* If you are new to SwerveIO and are looking for documentation on setting up
* your first swerve drive, head over to the {@code SwerveDrive.Builder} class.
* </p>
*
* @author Jordan Bancino
* @version 5.0.0
* @since 1.0.0
* @see SwerveDrive.Builder
*/
package net.bancino.robotics.swerveio;

View file

@ -0,0 +1,455 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.pid;
import net.bancino.log.Log;
import net.bancino.log.LogLevel;
import net.bancino.log.Loggable;
/**
* The default PID controller implementation for SwerveIO. This controller
* implements all the PID controller interface functionality and provides some
* useful features that prevent integral windup and overshoot. It provides sane
* default behavior that should be easy to tune.
*
* @author Jordan Bancino
* @version 6.0.0
* @since 1.0.0
*/
@Loggable
public class DefaultPIDController implements PIDController {
/*
* This is the absolute minimum number of slots a SwerveIO PID controller is
* required to implement.
*/
private static final int SLOT_COUNT = 4;
@Log
private double[] p = new double[SLOT_COUNT];
@Log
private double[] i = new double[SLOT_COUNT];
@Log
private double[] d = new double[SLOT_COUNT];
@Log
private double[] f = new double[SLOT_COUNT];
@Log(atLevel = LogLevel.DEBUG)
private double[] maxIOutput = new double[SLOT_COUNT];
@Log(atLevel = LogLevel.DEBUG)
private double[] maxError = new double[SLOT_COUNT];
@Log(atLevel = LogLevel.INFO)
private int slot = 0;
@Log(atLevel = LogLevel.INFO)
private double errorSum[] = new double[SLOT_COUNT];
@Log(atLevel = LogLevel.IMPORTANT)
private double acceptableError = -1;
@Log
private double maxOutput = 0;
@Log
private double minOutput = 0;
@Log
private double lastActual = 0;
@Log
private boolean firstRun = true;
@Log(atLevel = LogLevel.DEBUG)
private boolean reversed = false;
@Log(atLevel = LogLevel.INFO)
private double outputRampRate = 0;
@Log(atLevel = LogLevel.DEBUG)
private double lastOutput = 0;
@Log
private double outputFilter = 0;
@Log
private double setpointRange = 0;
/**
* Create a new PID controller. This initializes all the controller's gains to
* zero.
*/
public DefaultPIDController() {
this(0, 0, 0);
}
/**
* Create a PID controller. See setP, setI, setD methods for more detailed
* parameters.
*
* @param p Proportional gain. Large if large difference between setpoint and
* target.
* @param i Integral gain. Becomes large if setpoint cannot reach target
* quickly.
* @param d Derivative gain. Responds quickly to large changes in error. Small
* values prevents P and I terms from causing overshoot.
*/
public DefaultPIDController(double p, double i, double d) {
this(p, i, d, 0);
}
/**
* Create a DefaultPIDConroller class object. See setP, setI, setD, setF methods
* for more detailed parameters.
*
* @param p Proportional gain. Large if large difference between setpoint and
* target.
* @param i Integral gain. Becomes large if setpoint cannot reach target
* quickly.
* @param d Derivative gain. Responds quickly to large changes in error. Small
* values prevents P and I terms from causing overshoot.
* @param f Feed-forward gain. Open loop "best guess" for the output should be.
* Only useful if setpoint represents a rate.
*/
public DefaultPIDController(double p, double i, double d, double f) {
this.p[slot] = p;
this.i[slot] = i;
this.d[slot] = d;
this.f[slot] = f;
checkSigns(slot);
}
@Override
public void setP(int slot, double p) {
this.p[slot] = p;
checkSigns(slot);
}
@Override
public void setI(int slot, double i) {
/*
* This scales the accumulated error to avoid output errors and ensure a
* consistent transition.
*/
if (this.i[slot] != 0) {
errorSum[slot] = errorSum[slot] * this.i[slot] / i;
}
if (maxIOutput[slot] != 0) {
maxError[slot] = maxIOutput[slot] / i;
}
this.i[slot] = i;
checkSigns(slot);
}
@Override
public void setD(int slot, double d) {
this.d[slot] = d;
checkSigns(slot);
}
@Override
public void setF(int slot, double f) {
this.f[slot] = f;
checkSigns(slot);
}
@Override
public void setPID(int slot, double p, double i, double d) {
this.p[slot] = p;
this.d[slot] = d;
/*
* the I term has additional calculations, so we need to use it's specific
* method for setting it.
*/
setI(slot, i);
checkSigns(slot);
}
@Override
public void setPID(int slot, double p, double i, double d, double f) {
setPID(p, i, d);
this.f[slot] = f;
}
@Override
public void setMaxIOutput(int slot, double maximum) {
/*
* maxError and Izone are similar, but scaled for different purposes. The
* maxError is generated for simplifying math, since calculations against the
* max error are far more common than changing the I term or Izone.
*/
maxIOutput[slot] = maximum;
if (this.i[slot] != 0) {
maxError[slot] = maxIOutput[slot] / this.i[slot];
}
}
@Override
public void setOutputLimits(int slot, double minimum, double maximum) {
if (maximum < minimum)
return;
maxOutput = maximum;
minOutput = minimum;
/*
* Ensure the bounds of the I term are within the bounds of the allowable output
* swing
*/
if (maxIOutput[slot] == 0 || maxIOutput[slot] > (maximum - minimum)) {
setMaxIOutput(slot, maximum - minimum);
}
}
@Override
public void setAcceptableError(double acceptableError) {
this.acceptableError = acceptableError;
}
@Override
public double getAcceptableError() {
return this.acceptableError;
}
@Override
public void setDirection(boolean reversed) {
this.reversed = reversed;
/* Apply to all slots. */
for (int slot = 0; slot < SLOT_COUNT; slot++) {
checkSigns(slot);
}
}
@Override
public double getOutput(double actual, double setpoint) {
double output;
double Poutput;
double Ioutput;
double Doutput;
double Foutput;
/* Ramp the setpoint used for calculations if user has opted to do so */
if (setpointRange != 0) {
setpoint = constrain(setpoint, actual - setpointRange, actual + setpointRange);
}
/* Do the simple parts of the calculations */
double error = setpoint - actual;
/*
* If the error is within an acceptable amount, act as if there is no error.
*/
if (acceptableError > 0 && Math.abs(error) < acceptableError) {
error = 0;
}
/*
* Calculate F output. Notice, this depends only on the setpoint, and not the
* error.
*/
Foutput = f[slot] * setpoint;
/* Calculate P term */
Poutput = p[slot] * error;
/*
* If this is our first time running this, we don't actually _have_ a previous
* input or output. For sensor, sanely assume it was exactly where it is now.
* For last output, we can assume it's the current time-independent outputs.
*/
if (firstRun) {
lastActual = actual;
lastOutput = Poutput + Foutput;
firstRun = false;
}
/*
* Calculate D Term Note, this is negative. This actually "slows" the system if
* it's doing the correct thing, and small values helps prevent output spikes
* and overshoot
*/
Doutput = -d[slot] * (actual - lastActual);
lastActual = actual;
/*
* The Iterm is more complex. There's several things to factor in to make it
* easier to deal with.
*
* 1. maxIoutput restricts the amount of output contributed by the Iterm.
*
* 2. prevent windup by not increasing errorSum if we're already running against
* our max Ioutput
*
* 3. prevent windup by not increasing errorSum if output is output=maxOutput
*/
Ioutput = i[slot] * errorSum[slot];
if (maxIOutput[slot] != 0) {
Ioutput = constrain(Ioutput, -maxIOutput[slot], maxIOutput[slot]);
}
/* And, finally, we can just add the terms up */
output = Foutput + Poutput + Ioutput + Doutput;
/* Figure out what we're doing with the error. */
if (minOutput != maxOutput && !bounded(output, minOutput, maxOutput)) {
errorSum[slot] = error;
/*
* reset the error sum to a sane level Setting to current error ensures a smooth
* transition when the P term decreases enough for the I term to start acting
* upon the controller From that point the I term will build up as would be
* expected
*/
} else if (outputRampRate != 0 && !bounded(output, lastOutput - outputRampRate, lastOutput + outputRampRate)) {
errorSum[slot] = error;
} else if (maxIOutput[slot] != 0) {
errorSum[slot] = constrain(errorSum[slot] + error, -maxError[slot], maxError[slot]);
/*
* In addition to output limiting directly, we also want to prevent I term
* buildup, so restrict the error directly
*/
} else {
errorSum[slot] += error;
}
/* Restrict output to our specified output and ramp limits */
if (outputRampRate != 0) {
output = constrain(output, lastOutput - outputRampRate, lastOutput + outputRampRate);
}
if (minOutput != maxOutput) {
output = constrain(output, minOutput, maxOutput);
}
if (outputFilter != 0) {
output = lastOutput * outputFilter + output * (1 - outputFilter);
}
lastOutput = output;
return output;
}
@Override
public void reset() {
firstRun = true;
for (int i = 0; i < SLOT_COUNT; i++) {
errorSum[i] = 0;
}
}
@Override
public void setOutputRampRate(double rate) {
outputRampRate = rate;
}
@Override
public void setSetpointRange(double range) {
setpointRange = range;
}
@Override
public void setOutputFilter(double strength) {
if (strength == 0 || bounded(strength, 0, 1)) {
outputFilter = strength;
}
}
/**
* Forces a value into a specific range
*
* @param value input value
* @param min maximum returned value
* @param max minimum value in range
* @return Value if it's within provided range, min or max otherwise
*/
private double constrain(double value, double min, double max) {
if (value > max) {
return max;
}
if (value < min) {
return min;
}
return value;
}
/**
* Test if the value is within the min and max, inclusive
*
* @param value to test
* @param min Minimum value of range
* @param max Maximum value of range
* @return true if value is within range, false otherwise
*/
private boolean bounded(double value, double min, double max) {
/*
* Note, this is an inclusive range. This is so tests like
* `bounded(constrain(0,0,1),0,1)` will return false. This is more helpful for
* determining edge-case behaviour than <= is.
*/
return (min < value) && (value < max);
}
/**
* To operate correctly, all PID parameters require the same sign This should
* align with the {@literal}reversed value
*/
private void checkSigns(int slot) {
if (reversed) { /* all values should be below zero */
if (p[slot] > 0)
p[slot] *= -1;
if (i[slot] > 0)
i[slot] *= -1;
if (d[slot] > 0)
d[slot] *= -1;
if (f[slot] > 0)
f[slot] *= -1;
} else { /* all values should be above zero */
if (p[slot] < 0)
p[slot] *= -1;
if (i[slot] < 0)
i[slot] *= -1;
if (d[slot] < 0)
d[slot] *= -1;
if (f[slot] < 0)
f[slot] *= -1;
}
}
@Override
public double getPreviousOutput() {
return lastOutput;
}
@Override
public void setActiveProfile(int slot) {
if (slot >= 0 && slot < SLOT_COUNT) {
this.slot = slot;
} else {
throw new IllegalArgumentException("Profile slot out of bounds: " + slot);
}
}
@Override
public int getActiveProfile() {
return slot;
}
@Override
public int getProfileCount() {
return SLOT_COUNT;
}
}

View file

@ -0,0 +1,393 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.pid;
/**
* The PID Controller API used by SwerveIO. This is a complete definition, and
* every PID controller used with SwerveIO must implement this interface.
*
* <p>
* All PID controllers should generally behave the same way, but this API makes
* zero gaurantees. There is little reason to stray beyond the trusted and
* well-tested {@link DefaultPIDController} controller, but SwerveIO is designed
* to be as extensible as possible, providing interfaces for every pluggable
* aspect of it.
* </p>
*
* <p>
* This PID controller interface supports multiple profile slots, which allows
* you to set multiple gains on a single controller and switch between them at
* will. PID controllers should provide at least 4 profile slots, but the actual
* number of supported profiles may vary across implementations.
* </p>
*
* <p>
* It is <b>not</b> recommended to simply wrap hardware vendor-specific PID
* controllers such as on the Spark Max or Talon for pivot motors, because these
* controllers often don't provide the required functionality. Any
* vendor-specific PID controllers are intended to be used for velocity loops on
* the drive motors only.
* </p>
*
* @author Jordan Bancino
* @version 6.0.2
* @since 1.2.3
*/
public interface PIDController {
/**
* Set the proportional gain of the PID loop coefficient. (The motor will
* correct itself proportional to the offset of the measure compared to its
* targeted measure.) It's only weakness will be when it treats positive and
* negative offset equally, otherwise it narrows error to 0.
*
* @param p Proportional gain value. Must be positive. This gain is set on the
* active profile slot.
*/
public default void setP(double p) {
setP(getActiveProfile(), p);
}
/**
* Set the proportional gain of the PID loop coefficient. (The motor will
* correct itself proportional to the offset of the measure compared to its
* targeted measure.) It's only weakness will be when it treats positive and
* negative offset equally, otherwise it narrows error to 0.
*
* @param slot The profile slot to set the gain for.
* @param p Proportional gain value. Must be positive.
*/
public void setP(int slot, double p);
/**
* Set the integral gain of the PID loop coefficient. (The motor will correct
* itself based on past errors and integrates the history of offset to narrow
* error down to 0.) The downside of using a purely integral system is that it's
* slow to start, as it takes time to accumulate enough information to
* accurately form it's coefficient.
*
* @param i Integral gain value. Must be positive. This gain is set on the
* active profile slot.
*/
public default void setI(double i) {
setI(getActiveProfile(), i);
}
/**
* Set the integral gain of the PID loop coefficient. (The motor will correct
* itself based on past errors and integrates the history of offset to narrow
* error down to 0.) The downside of using a purely integral system is that it's
* slow to start, as it takes time to accumulate enough information to
* accurately form it's coefficient.
*
* @param slot The profile slot to set the gain for.
* @param i Integral gain value. Must be positive.
*/
public void setI(int slot, double i);
/**
* Set the derivative gain of the PID loop coefficient. (The motor will correct
* error based on it's rate of change, not seeking to bring the error to 0, but
* seeking to keep the error that the system is stable.) A derivative loop will
* never bring your error to 0, but will simply keep your error from growing
* larger by catching any change and correcting it.
*
* @param d Derivative gain value. Must be positive. This gain is set on the
* active profile slot.
*/
public default void setD(double d) {
setD(getActiveProfile(), d);
}
/**
* Set the derivative gain of the PID loop coefficient. (The motor will correct
* error based on it's rate of change, not seeking to bring the error to 0, but
* seeking to keep the error that the system is stable.) A derivative loop will
* never bring your error to 0, but will simply keep your error from growing
* larger by catching any change and correcting it.
*
* @param slot The profile slot to set the gain for.
* @param d Derivative gain value. Must be positive.
*/
public void setD(int slot, double d);
/**
* Sets the priority held in feed-forward augment. In a closed loop system, the
* feed-forward predicts the outcome of the next output from the motor
* controllers for better error correcting accuracy.
*
* @param f Feed-forward gain value. Must be positive. This gain is set on the
* active profile slot.
*/
public default void setF(double f) {
setF(getActiveProfile(), f);
}
/**
* Sets the priority held in feed-forward augment. In a closed loop system, the
* feed-forward predicts the outcome of the next output from the motor
* controllers for better error correcting accuracy.
*
* @param slot The profile slot to set the gain for.
* @param f Feed-forward gain value. Must be positive.
*/
public void setF(int slot, double f);
/**
* A convenience function for setting PID values all at once. See {@link #setP},
* {@link #setI}, and {@link #setD}. This sets the gains on the active profile
* slot.
*
* @param p The value to feed into {@link #setP}.
* @param i The value to feed into {@link #setI}.
* @param d The value to feed into {@link #setD}.
*/
public default void setPID(double p, double i, double d) {
setPID(getActiveProfile(), p, i, d);
}
/**
* A convenience function for setting PID values all at once. See
* {@link #setP(int,double)}, {@link #setI(int,double)}, and
* {@link #setD(int,double)}.
*
* @param slot The profile slot to set the gains on.
* @param p The value to feed into {@link #setP(int,double)}.
* @param i The value to feed into {@link #setI(int,double)}.
* @param d The value to feed into {@link #setD(int,double)}.
*/
public default void setPID(int slot, double p, double i, double d) {
setP(slot, p);
setI(slot, i);
setD(slot, d);
}
/**
* A convenience function for setting PID values all at once. See
* {@link #setP(int,double)}, {@link #setI(int,double)},
* {@link #setD(int,double)}, and {@link #setF(int,double)}. This sets the gains
* on the active profile slot.
*
* @param p The value to feed into {@link #setP(int,double)}.
* @param i The value to feed into {@link #setI(int,double)}.
* @param d The value to feed into {@link #setD(int,double)}.
* @param f The value to feed into {@link #setF(int,double)}.
*/
public default void setPID(double p, double i, double d, double f) {
setPID(getActiveProfile(), p, i, d, f);
}
/**
* A convenience function for setting PID values all at once. See
* {@link #setP(int,double)}, {@link #setI(int,double)},
* {@link #setD(int,double)}, and {@link #setF(int,double)}.
*
* @param slot The profile slot to set the gains on.
* @param p The value to feed into {@link #setP(int,double)}.
* @param i The value to feed into {@link #setI(int,double)}.
* @param d The value to feed into {@link #setD(int,double)}.
* @param f The value to feed into {@link #setF(int,double)}.
*/
public default void setPID(int slot, double p, double i, double d, double f) {
setPID(slot, p, i, d);
setF(slot, f);
}
/**
* Set an acceptable error. The PID controller should output nothing if the
* setpoint and feedback error is within +- the provided error.
*
* <p>
* A simple implementation is to take the absolute value of the difference
* between the setpoint and the feedback and check that that value is less than
* this acceptable error.
* </p>
*
* @param acceptableError The amount of error allowed by this PID controller.
* This value is in the units of the feedback device and
* applies to both sides of the setpoint. For example,
* when running a controller on a gyro, an acceptable
* error value of 0.5 would allow for 0.5 degrees on both
* sides of the setpoint, which means there is a total
* allowable error of 1 degree.
*/
public void setAcceptableError(double acceptableError);
/**
* Get the acceptable error that this PID controller allows.
*
* @return See {@link #setAcceptableError(double)}.
*/
public double getAcceptableError();
/**
* Set the maximum output value contributed by the I component of the system
* This can be used to prevent large windup issues and make tuning simpler
*
* @param slot The profile slot to get the gains for.
* @param max Units are the same as the expected output value
*/
public void setMaxIOutput(int slot, double max);
/**
* Set the maximum output value contributed by the I component of the system
* This can be used to prevent large windup issues and make tuning simpler
*
* @param max Units are the same as the expected output value. This is set on
* the active profile slot.
*/
public default void setMaxIOutput(double max) {
setMaxIOutput(getActiveProfile(), max);
}
/**
* Set the active profile slot on this PID controller. Future calls to methods
* that don't accept a slot parameter use this slot number.
*
* @param slot A profile slot number, greater than or equal to zero.
*
* @throws IllegalArgumentException if {@code slot} is out of bounds for the
* controller. Consult your controller's
* documentation, but the bounds should be
* <b>at minimum</b> 4 slots, giving you slots
* 0, 1, 2, and 3.
*/
public void setActiveProfile(int slot);
/**
* Get the active profile slot on this PID controller.
*
* @return The active profile slot that this PID controller is pulling
* parameters from.
*/
public int getActiveProfile();
/**
* Get the number of profile slots that this PID controller has. It should be at
* least 4, but can be more depending on the implementation.
*
* @return The number of profiles this PID controller supports.
*/
public int getProfileCount();
/**
* Set the output limits of the pivot PID controller.
*
* @param slot The profile slot to set the limit values on.
* @param min The minimum value the PID should output.
* @param max The maximum value the PID should output.
*/
public void setOutputLimits(int slot, double min, double max);
/**
* Set the output limits of the pivot PID controller.
*
* @param slot The profile slot to set the limit on.
* @param output Will produce an output limit of -output to +output.
*/
public default void setOutputLimits(int slot, double output) {
setOutputLimits(slot, -output, output);
}
/**
* Set the output limits of the pivot PID controller. This limit is applied to
* the active profile slot.
*
* @param output Will produce an output limit of -output to +output.
*/
public default void setOutputLimits(double output) {
setOutputLimits(getActiveProfile(), output);
}
/**
* A simple toggle for reversing the PID loop's output. This is applied to all
* profile slots.
*
* @param reversed Whether or not to flip the PID output.
*/
public void setDirection(boolean reversed);
/**
* This is the main function of the PID loop. It performs all the calculations
* to generate PID output. It uses the actively set profile slot's gains in the
* calculations, so make sure the profile you want is set with
* {@link #setActiveProfile(int)}
*
* @param feedback The output feedback; your current reference; where you are
* currently.
* @param setpoint The destination location. This is where you want to be.
* @return The output of the PID calculation given the input.
*/
public double getOutput(double feedback, double setpoint);
/**
* Get the previous output from the PID loop. This should store whatever the
* previous call to the output function generates.
*
* @return The previous output of the PID calculation.
*/
public double getPreviousOutput();
/**
* Reset the controller, erasing the previous output, feedback, and setpoints,
* if stored. This may not do anything at all on certain implementations, but is
* available for implementations to require when the system exits closed-loop
* control for a period of time and then re-enters it. Optionally, if the active
* profile slot changes, implementations may want to reset their internal state
* as well.
*/
public void reset();
/**
* Sets the ramp rate for the output. This is applied to all profile slots.
*
* @param rate The ramp rate for the output, in the same units as the output
* value. For each scan, the output value will jump no more than +
* or - this rate.
*/
public void setOutputRampRate(double rate);
/**
* Set the output range of this PID controller. This effectively scales the
* controller so it knows the bounds of what it will be fed. This is applied to
* all profile slots.
*
* @param range The setpoint input range. In relation to swerve drive, this will
* be the counts per revolution of your pivot encoder, and will
* have no limit on the drive motor.
*/
public void setSetpointRange(double range);
/**
* Set a filter on the output to reduce sharp oscillations. <br>
* 0.1 is likely a sane starting value. Larger values use historical data more
* heavily, with low values weigh newer data. 0 will disable, filtering, and use
* only the most recent value. <br>
* Increasing the filter strength will P and D oscillations, but force larger I
* values and increase I term overshoot.<br>
* Uses an exponential wieghted rolling sum filter, according to a simple <br>
* <code>output*(1-strength)*sum(0..n){output*strength^n}</code> algorithm.
*
* <p>
* This value is applied to all profile slots.
* </p>
*
* @param strength valid between [0..1), meaning [current output only..
* historical output only)
*/
public void setOutputFilter(double strength);
}

View file

@ -0,0 +1,38 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
/**
* PID controllers used in SwerveIO. This package provides an interface for
* adding your own custom PID controllers. See {@link PIDController} for details
* on how a PID controller is expected to behave.
*
* <p>
* Most users of SwerveIO will probably want to use the official PID controller
* of SwerveIO ({@link DefaultPIDController}), but you can also easily create
* your own implementation by directly implementing {@link PIDController}.
* </p>
*
* <p>
* The PID controller is generally used internally by swerve module
* implementors. The average user may never need to come into contact with a PID
* controller, except to tune it using the PID API specified by this package.
* </p>
*
* @author Jordan Bancino
* @version 5.0.0
* @since 1.2.0
*/
package net.bancino.robotics.swerveio.pid;

View file

@ -0,0 +1,66 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.geometry;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
import net.bancino.robotics.swerveio.geometry.Length.Unit;
/**
* Test the chassis dimension class.
*
* @author Jordan Bancino
* @version 6.1.0
* @since 6.1.0
*/
public class ChassisDimensionTest {
private ChassisDimension testDimension1 = new ChassisDimension(new Length(25, Unit.INCHES));
private ChassisDimension testDimension2 = new ChassisDimension(new Length(25, Unit.INCHES), new Length(25, Unit.INCHES));
private ChassisDimension testDimension3 = new ChassisDimension(new Length(0.5, Unit.METERS));
/**
* Test {@code equals()}.
*/
@Test
public void testEquals() {
assertEquals(testDimension1, testDimension2);
assertEquals(testDimension3, new ChassisDimension(new Length(50, Unit.CENTIMETERS)));
}
/**
* Test that the constructor thows errors on invalid input.
*/
@Test
public void testConstructor() {
assertThrows(IllegalArgumentException.class, () -> new ChassisDimension(null));
}
/**
* The the geometry functions.
*/
@Test
public void testGeometry() {
assertEquals(testDimension1.getRadius().get(Unit.BASE_UNIT), testDimension1.getDiagonal().get(Unit.BASE_UNIT) / 2);
assertEquals(Math.hypot(25, 25), testDimension1.getDiagonal().get(Unit.INCHES));
assertEquals(Math.hypot(0.5, 0.5) / 2.0, testDimension3.getRadius().get(Unit.METERS));
}
}

View file

@ -0,0 +1,90 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.geometry;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
import net.bancino.robotics.swerveio.geometry.Length.Unit;
/**
* Test the length class.
*
* @author Jordan Bancino
* @version 6.1.0
* @since 6.1.0
*/
public class LengthTest {
private Length testLength1 = new Length(0, Unit.CENTIMETERS);
private Length testLength2 = new Length(4, Unit.INCHES);
private Length testLength3 = new Length((double) 1 / 3, Unit.FEET);
private Length testLength4 = new Length(2, Unit.METERS);
private Length testLength5 = new Length(8, Unit.INCHES);
/**
* Test {@code equals()}.
*/
@Test
public void testEquals() {
assertEquals(testLength2, testLength2);
assertEquals(testLength2, new Length(4, Unit.INCHES));
assertEquals(testLength2, testLength3);
assertNotEquals(testLength1, testLength2);
assertNotEquals(testLength3, testLength4);
}
/**
* Test that the constructor throws errors in invalid input and accepts valid
* input.
*/
@Test
public void testBounds() {
assertThrows(IllegalArgumentException.class, () -> new Length(-1, Unit.FEET));
assertThrows(IllegalArgumentException.class, () -> new Length(1 - 1.000001, Unit.CENTIMETERS));
assertDoesNotThrow(() -> new Length(1, Unit.FEET));
assertDoesNotThrow(() -> new Length(0.00001, Unit.CENTIMETERS));
}
/**
* Test various conversions.
*/
@Test
public void testConversions() {
assertEquals(testLength2.get(Unit.INCHES), 4);
assertEquals(testLength2.get(Unit.FEET), (double) 1 / 3);
assertEquals(testLength3.get(Unit.INCHES), 4);
assertEquals(testLength4.get(Unit.CENTIMETERS), 200);
}
/**
* Test the mathematical functions.
*/
@Test
public void testMath() {
assertEquals(testLength1, testLength2.minus(testLength3));
assertEquals(testLength5, testLength3.plus(testLength2));
assertEquals(testLength5, testLength2.times(new Length(2, Unit.INCHES)));
assertEquals(testLength5, testLength2.dividedBy(new Length(0.5, Unit.INCHES)));
}
}

View file

@ -0,0 +1,77 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.geometry;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
/**
* Test the module vector class.
*
* @author Jordan Bancino
* @version 6.1.0
* @since 6.1.0
*/
public class ModuleVectorTest {
private ModuleVector testVector1 = new ModuleVector(0, 0);
private ModuleVector testVector2 = new ModuleVector(90, 0.25);
private ModuleVector testVector3 = new ModuleVector(180, 0.5);
/**
* Test that the bounding checks are working properly. The module vector should
* not allow values outside of the range [-360,+360] for angles and [-1,+1] for
* speeds.
*/
@Test
public void testBounds() {
assertDoesNotThrow(() -> new ModuleVector(0, 0));
assertDoesNotThrow(() -> new ModuleVector(90, 0.5));
assertDoesNotThrow(() -> new ModuleVector(358, 1));
assertDoesNotThrow(() -> new ModuleVector(-45, -0.4));
assertThrows(IllegalArgumentException.class, () -> new ModuleVector(-361, 0));
assertThrows(IllegalArgumentException.class, () -> new ModuleVector(180, -1.001));
assertThrows(IllegalArgumentException.class, () -> new ModuleVector(365, 1.1));
}
/**
* Test that basic math operators work as intended.
*/
@Test
public void testMath() {
assertEquals(testVector3, testVector2.plus(testVector2));
assertEquals(testVector2, testVector3.minus(testVector2));
}
/**
* Test {@code equals()}.
*/
@Test
public void testEquals() {
assertEquals(testVector1, testVector1);
assertEquals(testVector1, new ModuleVector(0, 0));
assertNotEquals(testVector1, testVector2);
assertNotEquals(testVector2, testVector1);
}
}

View file

@ -0,0 +1,62 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.geometry;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import org.junit.jupiter.api.Test;
/**
* Test the swerve vector class.
*
* @author Jordan Bancino
* @version 6.1.0
* @since 6.1.0
*/
public class SwerveVectorTest {
private SwerveVector testVector1 = new SwerveVector(0, 0, 0);
private SwerveVector testVector2 = new SwerveVector(0.25, 0.25, 0.25);
private SwerveVector testVector3 = new SwerveVector(0.5, 0.5, 0.5);
private SwerveVector testVector4 = new SwerveVector(2, 2, 2);
/**
* Test the math operators ({@code plus()}, {@code minus()}, {@code times()},
* {@code dividedBy()}).
*/
@Test
public void testMath() {
assertEquals(testVector3, testVector2.plus(testVector2));
assertEquals(testVector2, testVector3.minus(testVector2));
assertEquals(testVector3, testVector2.times(testVector4));
assertEquals(testVector2, testVector3.dividedBy(testVector4));
}
/**
* Test {@code equals()}.
*/
@Test
public void testEquals() {
assertEquals(testVector1, testVector1);
assertEquals(testVector1, new SwerveVector(0, 0, 0));
assertNotEquals(testVector1, testVector2);
assertNotEquals(testVector2, testVector1);
assertNotEquals(testVector3, testVector4);
}
}

29
vendor/ctre/build.gradle vendored Executable file
View file

@ -0,0 +1,29 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
/* The CTRE Phoenix API. */
dependencies {
def phoenixVersion = '5.20.2'
api "com.ctre.phoenix:api-java:${phoenixVersion}"
api "com.ctre.phoenix:wpiapi-java:${phoenixVersion}"
api "com.ctre.phoenix:cci:${phoenixVersion}"
api "com.ctre.phoenix.sim:cci-sim:${phoenixVersion}"
api "com.ctre.phoenix.sim:simTalonSRX:${phoenixVersion}"
api "com.ctre.phoenix.sim:simVictorSPX:${phoenixVersion}"
}
/* CTRE uses Doxygen instead of JavaDoc, so we can't link javadoc here. */

12
vendor/ctre/src/javadoc/overview.html vendored Executable file
View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<body>
<p>This API adds official SwerveIO support for <a href="https://www.ctr-electronics.com/">CTR Electronics</a>'
hardware.
You should have been directed here from the official SwerveIO documentation. If you weren't,
please go there now for instructions on getting this API set up for use.
</p>
</body>
</html>

View file

@ -0,0 +1,74 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.encoder;
import com.ctre.phoenix.sensors.AbsoluteSensorRange;
import com.ctre.phoenix.sensors.CANCoder;
import com.ctre.phoenix.sensors.SensorInitializationStrategy;
import net.bancino.log.Log;
import net.bancino.log.LogLevel;
import net.bancino.log.Loggable;
/**
* A wrapper for the CTRE CANCoder sensor. The encoder is automatically
* configured with the appropriate values for use with SwerveIO.
*
* @author Jordan Bancino
* @version 5.0.2
* @since 5.0.2
*/
@Loggable
public class PhoenixCANCoder implements Encoder {
private final CANCoder encoder;
@Log(as = "rawValue", atLevel = LogLevel.IMPORTANT)
private double lastValue;
/**
* Construct a new CANCoder at the given CAN ID.
*
* @param canId The CAN ID that this CANCoder is located at.
*/
public PhoenixCANCoder(int canId) {
encoder = new CANCoder(canId);
/* Use absolute positioning 0 -> 360 */
encoder.configSensorInitializationStrategy(SensorInitializationStrategy.BootToAbsolutePosition);
encoder.configAbsoluteSensorRange(AbsoluteSensorRange.Unsigned_0_to_360);
encoder.setPositionToAbsolute();
/*
* Reverse the sensor direction so it counts clockwise is positive.
* (Default is counterclockwise is positive).
*/
encoder.configSensorDirection(true);
}
@Override
public double get() {
lastValue = encoder.getAbsolutePosition();
return lastValue;
}
@Override
public double countsPerRevolution() {
return 360;
}
}

View file

@ -0,0 +1,115 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.encoder;
import com.ctre.phoenix.motorcontrol.FeedbackDevice;
import com.ctre.phoenix.motorcontrol.can.BaseMotorController;
import net.bancino.log.Log;
import net.bancino.log.LogLevel;
import net.bancino.log.Loggable;
/**
* A wrapper for a CTRE Motor Controller sensor. This class provides SwerveIO
* support for any encoder attached to a CTRE motor controller, whether it is
* the internal brushless motor encoder, or an external attached encoder. Refer
* to the CTRE Phoenix Java documentation for {@code FeedbackDevice} to see the
* supported sensors.
*
* <p>
* Note that only the CTRE MagEncoder, Quad encoders, and analog encoders
* support {@link #countsPerRevolution()}. Support for others can be added if we
* know their counts per rev value.
* </p>
*
* @author Jordan Bancino
* @version 5.0.1
* @since 4.2.0
*/
@Loggable
public class PhoenixEncoder implements Encoder {
private final BaseMotorController motor;
private final FeedbackDevice feedbackDevice;
@Log(as = "phoenixPidIdx")
private final int slot;
@Log(as = "rawValue", atLevel = LogLevel.IMPORTANT)
private double lastValue;
/**
* Create a new Phoenix encoder with a motor controller and a feedback mode.
* This constructor uses the default profile slot, which is 0.
*
* @param motor The motor controller to use.
* @param feedbackDevice The encoder mode to set on the motor at the given PID
* slot.
*/
public PhoenixEncoder(BaseMotorController motor, FeedbackDevice feedbackDevice) {
this(motor, feedbackDevice, 0);
}
/**
* Create a new Phoenix encoder with a motor controller, the feedback mode, and
* a PID slot.
*
* @param motor The motor controller to use.
* @param feedbackDevice The encoder mode to set on the motor at the given PID
* slot.
* @param slot The PID slot to fetch encoder values from.
*/
public PhoenixEncoder(BaseMotorController motor, FeedbackDevice feedbackDevice, int slot) {
this.motor = motor;
/* Set the sensor to use on this profile slot. */
motor.configSelectedFeedbackSensor(feedbackDevice, slot, 0);
/*
* Set the indicator light mode (Green == Increasing)
*
* This isn't required for the TalonFX using the integrated encoder, but is
* useful for other motors and controllers.
*/
motor.setSensorPhase(true);
this.feedbackDevice = feedbackDevice;
this.slot = slot;
}
@Override
public double get() {
lastValue = motor.getSelectedSensorPosition(slot);
return lastValue;
}
@Override
public double countsPerRevolution() {
switch (feedbackDevice) {
case CTRE_MagEncoder_Absolute:
case CTRE_MagEncoder_Relative:
case QuadEncoder:
return 4096;
case IntegratedSensor:
return 2048;
case Analog:
return 1024;
default:
throw new IllegalArgumentException("Unknown sensor: " + feedbackDevice);
}
}
}

22
vendor/kauai/build.gradle vendored Executable file
View file

@ -0,0 +1,22 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
/* The KauaiLabs NavX API*/
dependencies {
api 'com.kauailabs.navx.frc:navx-java:4.0.435'
}
rootProject.javadocAddLinks(project, 'https://www.kauailabs.com/public_files/navx-mxp/apidocs/java/')

12
vendor/kauai/src/javadoc/overview.html vendored Executable file
View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<body>
<p>This API adds official SwerveIO support for <a href="https://www.kauailabs.com/">Kauai Labs</a>'
hardware.
You should have been directed here from the official SwerveIO documentation. If you weren't,
please go there now for instructions on getting this API set up for use.
</p>
</body>
</html>

View file

@ -0,0 +1,102 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.gyro;
import edu.wpi.first.wpilibj.I2C;
import edu.wpi.first.wpilibj.SPI;
import edu.wpi.first.wpilibj.SerialPort;
import net.bancino.log.Log;
import net.bancino.log.LogLevel;
import net.bancino.log.Loggable;
import com.kauailabs.navx.frc.AHRS;
/**
* A Kauai Labs FRC NavX Gyro that implements the abstract gyro interface for
* use with the swerve drive.
*
* @author Jordan Bancino
* @version 4.1.1
* @since 2.0.0
*/
@Loggable
public class NavXGyro implements Gyro {
private final AHRS gyro;
@Log(atLevel = LogLevel.IMPORTANT)
private double angle;
/**
* Create a gyro object on the given port.
*
* @param port The SPI port that the gyro is on.
*/
public NavXGyro(SPI.Port port) {
this(new AHRS(port));
}
/**
* Create a gyro object on the given port.
*
* @param port The I2C port that the gyro is on.
*/
public NavXGyro(I2C.Port port) {
this(new AHRS(port));
}
/**
* Create a gyro object on the given port.
*
* @param port The SerialPort port that the gyro is on.
*/
public NavXGyro(SerialPort.Port port) {
this(new AHRS(port));
}
/**
* Wrap an existing NavX AHRS object. This can be used if none of the other
* constructors suit your needs. Because AHRS offers a number of constructors,
* it doesn't make sense to duplicate them all here. This way, you can
* instantiate your AHRS and configure it however you want before adding it to
* SwerveIO.
*
* @param ahrs The AHRS object to use.
*/
public NavXGyro(AHRS ahrs) {
if (ahrs != null) {
gyro = ahrs;
} else {
throw new IllegalArgumentException("NavX AHRS cannot be null.");
}
}
@Override
public double getAngle() {
double yaw = gyro.getYaw();
if (yaw < 0) {
yaw += 360;
}
angle = yaw;
return angle;
}
@Override
public void zero() {
gyro.zeroYaw();
}
}

25
vendor/rev/build.gradle vendored Executable file
View file

@ -0,0 +1,25 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
/* The RevRobotics REVLib API. */
dependencies {
/* https://software-metadata.revrobotics.com/REVLib.json */
def sparkMaxVersion = '2022.1.0'
api "com.revrobotics.frc:REVLib-java:${sparkMaxVersion}"
api "com.revrobotics.frc:REVLib-driver:${sparkMaxVersion}"
}
rootProject.javadocAddLinks(project, 'https://codedocs.revrobotics.com/java/')

12
vendor/rev/src/javadoc/overview.html vendored Executable file
View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<body>
<p>This API adds official SwerveIO support for <a href="https://www.revrobotics.com/">RevRobotics</a>'
hardware.
You should have been directed here from the official SwerveIO documentation. If you weren't,
please go there now for instructions on getting this API set up for use.
</p>
</body>
</html>

View file

@ -0,0 +1,106 @@
/*
* SwerveIO - A versatile open-source FRC swerve drive library.
*
* Copyright (C) 2019-2022 Jordan Bancino <jordan@bancino.net>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published with this
* source code. You should have received a copy of the GNU General Public
* License with this program. If you did not, contact the distributors of
* this program and view it here: https://bancino.net/licenses/gpl-3.0.txt
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
package net.bancino.robotics.swerveio.encoder;
import com.revrobotics.*;
import net.bancino.log.Log;
import net.bancino.log.LogLevel;
import net.bancino.log.Loggable;
/**
* A wrapper for the RevRobotics SparkMax encoder interface. This class provides
* SwerveIO support for any encoder attached to the Spark Max motor controller,
* whether it is the internal brushless motor encoder, an alternate encoder, or
* an analog encoder.
*
*
* @author Jordan Bancino
* @version 8.0.0
* @since 1.0.0
*/
@Loggable
public class SparkMaxEncoder implements Encoder {
private final Object encoder;
@Log(as = "rawValue", atLevel = LogLevel.IMPORTANT)
private double lastValue;
/**
* Create a new spark max encoder with a motor controller. This sets up the
* encoder to read from the internal NEO encoder data port.
*
* @param sparkMax The motor controller to use.
*/
public SparkMaxEncoder(CANSparkMax sparkMax) {
encoder = sparkMax.getEncoder();
if (encoder == null) {
throw new UnsupportedOperationException("No internal encoder attached to Spark Max.");
}
}
/**
* Create a new spark max encoder with a motor controller. This sets up the
* encode to read from an alternate encoder connected to a spark max.
*
* @param sparkMax The motor controller to use.
* @param encoderType The type of encoder that is connected.
* @param countsPerRev The counts per revolution of the encoder.
*/
public SparkMaxEncoder(CANSparkMax sparkMax, SparkMaxAlternateEncoder.Type encoderType, int countsPerRev) {
encoder = sparkMax.getAlternateEncoder(encoderType, countsPerRev);
if (encoder == null) {
throw new UnsupportedOperationException("No alternate encoder attached to Spark Max.");
}
}
/**
* Create a new spark max encoder with a motor controller. This sets up the
* encoder to read from an analog encoder connected to the spark max.
*
* @param sparkMax The motor controller to use.
* @param analogMode The mode of the analog sensor.
*/
public SparkMaxEncoder(CANSparkMax sparkMax, SparkMaxAnalogSensor.Mode analogMode) {
encoder = sparkMax.getAnalog(analogMode);
if (encoder == null) {
throw new UnsupportedOperationException("No analog encoder attached to Spark Max.");
}
}
@Override
public double get() {
if (encoder instanceof RelativeEncoder) {
lastValue = ((RelativeEncoder) encoder).getPosition();
} else if (encoder instanceof SparkMaxAnalogSensor) {
lastValue = ((SparkMaxAnalogSensor) encoder).getPosition();
} else {
throw new IllegalStateException("Something happened and I don't know what.");
}
return lastValue;
}
@Override
public double countsPerRevolution() {
if (encoder instanceof RelativeEncoder) {
return ((RelativeEncoder) encoder).getCountsPerRevolution();
} else {
throw new IllegalArgumentException("SparkMaxAnalogSensor does not support counts per revolution.");
}
}
}