diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8cad368 --- /dev/null +++ b/.gitignore @@ -0,0 +1,175 @@ +# This gitignore has been specially created by the WPILib team. +# If you remove items from this file, intellisense might break. + +### C++ ### +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### Gradle ### +.gradle +/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties + +# # VS Code Specific Java Settings +# DO NOT REMOVE .classpath and .project +.classpath +.project +.settings/ +bin/ + +# IntelliJ +*.iml +*.ipr +*.iws +.idea/ +out/ + +# Fleet +.fleet + +# Simulation GUI and other tools window save file +*-window.json +*.wpilog +monologue/build/ +!monologue/build/generated/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..5b804e8 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,21 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "type": "wpilib", + "name": "WPILib Desktop Debug", + "request": "launch", + "desktop": true, + }, + { + "type": "wpilib", + "name": "WPILib roboRIO Debug", + "request": "launch", + "desktop": false, + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..8be11f2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,29 @@ +{ + "java.configuration.updateBuildConfiguration": "automatic", + "java.server.launchMode": "Standard", + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "bin/": true, + "**/.classpath": true, + "**/.project": true, + "**/.settings": true, + "**/.factorypath": true, + "**/*~": true + }, + "java.test.config": [ + { + "name": "WPIlibUnitTests", + "workingDirectory": "${workspaceFolder}/build/jni/release", + "vmargs": [ "-Djava.library.path=${workspaceFolder}/build/jni/release" ], + "env": { + "LD_LIBRARY_PATH": "${workspaceFolder}/build/jni/release" , + "DYLD_LIBRARY_PATH": "${workspaceFolder}/build/jni/release" + } + }, + ], + "java.test.defaultConfig": "WPIlibUnitTests" +} diff --git a/.wpilib/wpilib_preferences.json b/.wpilib/wpilib_preferences.json new file mode 100644 index 0000000..3acf127 --- /dev/null +++ b/.wpilib/wpilib_preferences.json @@ -0,0 +1,6 @@ +{ + "enableCppIntellisense": false, + "currentLanguage": "java", + "projectYear": "2024beta", + "teamNumber": 6995 +} \ No newline at end of file diff --git a/monologue/.gitignore b/monologue/.gitignore new file mode 100644 index 0000000..d163863 --- /dev/null +++ b/monologue/.gitignore @@ -0,0 +1 @@ +build/ \ No newline at end of file diff --git a/monologue/WPILib-License.md b/monologue/WPILib-License.md new file mode 100644 index 0000000..8434944 --- /dev/null +++ b/monologue/WPILib-License.md @@ -0,0 +1,24 @@ +Copyright (c) 2009-2023 FIRST and other WPILib contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of FIRST, WPILib, nor the names of other WPILib + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY FIRST AND OTHER WPILIB CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY NONINFRINGEMENT AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL FIRST OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/monologue/build.gradle b/monologue/build.gradle new file mode 100644 index 0000000..1cb3cd1 --- /dev/null +++ b/monologue/build.gradle @@ -0,0 +1,122 @@ +buildscript { + repositories { + maven { + url = 'https://frcmaven.wpi.edu/artifactory/ex-mvn' + } + } + dependencies { + classpath 'com.hubspot.jinjava:jinjava:2.7.1' + } +} + + +plugins { + id "maven-publish" + id "java-library" + id "edu.wpi.first.GradleRIO" version "2024.1.1-beta-3" + id 'com.diffplug.spotless' version '6.21.0' apply false +} + +publishing { + publications { + maven(MavenPublication) { + from components.java + groupId = "com.github.shueja-personal" + artifactId = "Monologue" + version = "v1.0.0-alpha1" + } + } +} + +sourceCompatibility = JavaVersion.VERSION_11 +targetCompatibility = JavaVersion.VERSION_11 + +// Set to true to use debug for JNI. +wpi.java.debugJni = false + +// Set this to true to enable desktop support. +def includeDesktopSupport = true + +// Defining my dependencies. In this case, WPILib (+ friends), and vendor libraries. +// Also defines JUnit 5. + +dependencies { + api wpi.java.deps.wpilib() + + nativeDebug wpi.java.deps.wpilibJniDebug(wpi.platforms.desktop) + nativeDebug wpi.java.vendor.jniDebug(wpi.platforms.desktop) + simulationDebug wpi.sim.enableDebug() + + nativeRelease wpi.java.deps.wpilibJniRelease(wpi.platforms.desktop) + nativeRelease wpi.java.vendor.jniRelease(wpi.platforms.desktop) + simulationRelease wpi.sim.enableRelease() + + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.2' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2' +} + +test { + useJUnitPlatform() + systemProperty 'junit.jupiter.extensions.autodetection.enabled', 'true' +} +wpi.java.configureTestTasks(test) + +// Configure string concat to always inline compile +tasks.withType(JavaCompile) { + options.compilerArgs.add '-XDstringConcat=inline' +} + +import groovy.json.JsonSlurper; +import com.hubspot.jinjava.Jinjava; +import com.hubspot.jinjava.JinjavaConfig; + +def monologueTypesInputFile = file("src/generate/types.json") +def geomTypesInputFile = file("src/generate/geomTypes.json") +def monologueJavaTypesInputDir = file("src/generate/java") +def monologueJavaTypesOutputDir = file("$buildDir/generated/main/java/monologue") + +task monologueGenerateClasses() { + description = "Generates Monologue Java classes" + group = "Monologue" + + inputs.file monologueTypesInputFile + inputs.file geomTypesInputFile + inputs.dir monologueJavaTypesInputDir + outputs.dir monologueJavaTypesOutputDir + + doLast { + def jsonSlurper = new JsonSlurper() + def jsonTypes = jsonSlurper.parse(monologueTypesInputFile) + def geomTypes = jsonSlurper.parse(geomTypesInputFile) + + monologueJavaTypesOutputDir.deleteDir() + monologueJavaTypesOutputDir.mkdirs() + + def config = new JinjavaConfig() + def jinjava = new Jinjava(config) + + monologueJavaTypesInputDir.listFiles().each { File file -> + def template = file.text + def outfn = file.name.substring(0, file.name.length() - 6) + if (file.name.startsWith("NTLogger") + || file.name.startsWith("DataLogger") + || file.name.startsWith("GenericLogger") + || file.name.startsWith("DataType") + || file.name.startsWith("Logged")) { + def replacements = new HashMap() + replacements.put("types", jsonTypes) + replacements.put("geomTypes", geomTypes) + def output = jinjava.render(template, replacements) + new File(monologueJavaTypesOutputDir, outfn).write(output) + } + } + } +} + +sourceSets.main.java.srcDir "${buildDir}/generated/main/java" +compileJava.dependsOn(monologueGenerateClasses) + +allprojects { + apply from: "$rootProject.projectDir/format.gradle" +} diff --git a/monologue/format.gradle b/monologue/format.gradle new file mode 100644 index 0000000..3232bfd --- /dev/null +++ b/monologue/format.gradle @@ -0,0 +1,55 @@ + +apply plugin: 'com.diffplug.spotless' + +spotless { + enforceCheck = false + java { + target fileTree('.') { + include '**/*.java' + exclude '**/build/**', '**/build-*/**', '**/bin/**' + } + toggleOffOn() + googleJavaFormat() + removeUnusedImports() + trimTrailingWhitespace() + endWithNewline() + } + groovyGradle { + target fileTree('.') { + include '**/*.gradle' + exclude '**/build/**', '**/build-*/**', '**/bin/**' + } + greclipse() + indentWithSpaces(4) + trimTrailingWhitespace() + endWithNewline() + } + json { + target fileTree('.') { + include '**/*.json' + exclude '**/build/**', '**/build-*/**', '**/bin/**', '**/.vscode/**' + exclude '**/simgui-ds.json', '**/simgui-window.json', '**/simgui.json', '**/networktables.json' + } + gson() + .indentWithSpaces(2) + } + format 'xml', { + target fileTree('.') { + include '**/*.xml' + exclude '**/build/**', '**/build-*/**', '**/bin/**' + } + eclipseWtp('xml') + trimTrailingWhitespace() + indentWithSpaces(2) + endWithNewline() + } + format 'misc', { + target fileTree('.') { + include '**/*.md', '**/.gitignore' + exclude '**/build/**', '**/build-*/**', '**/bin/**' + } + trimTrailingWhitespace() + indentWithSpaces(2) + endWithNewline() + } +} diff --git a/monologue/gradle/wrapper/gradle-wrapper.jar b/monologue/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7f93135 Binary files /dev/null and b/monologue/gradle/wrapper/gradle-wrapper.jar differ diff --git a/monologue/gradle/wrapper/gradle-wrapper.properties b/monologue/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..c5c2706 --- /dev/null +++ b/monologue/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=permwrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=permwrapper/dists diff --git a/monologue/gradlew b/monologue/gradlew new file mode 100644 index 0000000..1aa94a4 --- /dev/null +++ b/monologue/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original 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 POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# 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 ;; #( + MSYS* | 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 + if ! command -v java >/dev/null 2>&1 + then + 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 +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# 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"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/monologue/gradlew.bat b/monologue/gradlew.bat new file mode 100644 index 0000000..6689b85 --- /dev/null +++ b/monologue/gradlew.bat @@ -0,0 +1,92 @@ +@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=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@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% equ 0 goto execute + +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 execute + +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 + +: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 %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 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! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/monologue/settings.gradle b/monologue/settings.gradle new file mode 100644 index 0000000..3e30f84 --- /dev/null +++ b/monologue/settings.gradle @@ -0,0 +1,30 @@ +import org.gradle.internal.os.OperatingSystem + +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + String frcYear = '2024' + File frcHome + if (OperatingSystem.current().isWindows()) { + String publicFolder = System.getenv('PUBLIC') + if (publicFolder == null) { + publicFolder = "C:\\Users\\Public" + } + def homeRoot = new File(publicFolder, "wpilib") + frcHome = new File(homeRoot, frcYear) + } else { + def userFolder = System.getProperty("user.home") + def homeRoot = new File(userFolder, "wpilib") + frcHome = new File(homeRoot, frcYear) + } + def frcHomeMaven = new File(frcHome, 'maven') + maven { + name 'frcHome' + url frcHomeMaven + } + } +} + +Properties props = System.getProperties(); +props.setProperty("org.gradle.internal.native.headers.unresolved.dependencies.ignore", "true"); diff --git a/monologue/src/generate/geomTypes.json b/monologue/src/generate/geomTypes.json new file mode 100644 index 0000000..46fafaf --- /dev/null +++ b/monologue/src/generate/geomTypes.json @@ -0,0 +1,26 @@ +[ + { + "TypeName": "Translation2d" + }, + { + "TypeName": "Translation3d" + }, + { + "TypeName": "Rotation2d" + }, + { + "TypeName": "Rotation3d" + }, + { + "TypeName": "Pose2d" + }, + { + "TypeName": "Pose3d" + }, + { + "TypeName": "Transform2d" + }, + { + "TypeName": "Transform3d" + } +] diff --git a/monologue/src/generate/java/DataLogger.java.jinja b/monologue/src/generate/java/DataLogger.java.jinja new file mode 100644 index 0000000..51e1891 --- /dev/null +++ b/monologue/src/generate/java/DataLogger.java.jinja @@ -0,0 +1,132 @@ +package monologue; + + +{%for t in geomTypes%} +import edu.wpi.first.math.geometry.{{t.TypeName}}; +{%endfor%} +import edu.wpi.first.networktables.NetworkTable; +import edu.wpi.first.networktables.NetworkTableInstance; +import edu.wpi.first.networktables.NTSendable; +import edu.wpi.first.wpilibj.smartdashboard.SendableBuilderImpl; +import edu.wpi.first.util.datalog.*; +import edu.wpi.first.util.sendable.Sendable; +import edu.wpi.first.wpilibj.DataLogManager; +import monologue.DataLogSendableBuilder; +import java.util.function.Supplier; +import monologue.util.GeomPacker; +import java.util.function.*; +import edu.wpi.first.util.function.FloatSupplier; +import edu.wpi.first.util.struct.Struct; + +public class DataLogger extends GenericLogger { + public DataLogger(){super(); + DataLogManager.logNetworkTables(false); + log = DataLogManager.getLog(); + }; + private DataLog log; + + {%for t in types%} + public void put(String entryName, {{t.java.ValueType}} value) { + {%if t.TypeName == 'IntegerArray'%} + long[] array = new long[value.length]; + for (int i = 0; i < value.length; i++) { + array[i] = (long) value[i]; + } + new {{t.java.EntryName}}LogEntry(log, entryName).append(array); + {%else%} + new {{t.java.EntryName}}LogEntry(log, entryName).append(value); + {%endif%} + } + + public void add{{t.TypeName}}(String entryName, {{t.java.Supplier}} valueSupplier, int level) { + var entry = new {{t.java.EntryName}}LogEntry(log, entryName); + {%if t.TypeName == 'IntegerArray'%} + map.put(entryName, field((timestamp)->{ + int[] value = valueSupplier.get(); + long[] array = new long[value.length]; + for (int i = 0; i < value.length; i++) { + array[i] = (long) value[i]; + } + entry.append(array, timestamp); + }, ()->{}, level)); + {%else%} + map.put(entryName, field((timestamp)->entry.append(valueSupplier.get(), timestamp), ()->{}, level)); + {%endif%} + } + @Override + public void add{{t.TypeName}}(String entryName, {{t.java.Supplier}} valueSupplier) { + add{{t.TypeName}}(entryName, valueSupplier, 0); + } + {%endfor%} + + public void addNetworkTable(NetworkTable table) { + NetworkTableInstance.getDefault() + .startEntryDataLog(log, table.getPath(), table.getPath()); + } + + public void addNetworkTable(NetworkTable table, String dlPath) { + NetworkTableInstance.getDefault() + .startEntryDataLog(log, table.getPath(), dlPath); + } + + public void addSendable(String path, Sendable sendable) { + System.out.println("logging regular sendable at " + path); + var builder = new DataLogSendableBuilder(path); + sendable.initSendable(builder); + sendables.add(builder); + } + + public void addSendable(String path, NTSendable sendable) { + System.out.println("logging ntsendable at " + path); + var table = NetworkTableInstance.getDefault().getTable(path); + var builder = new SendableBuilderImpl(); + builder.setTable(table); + sendable.initSendable(builder); + builder.startListeners(); + table.getEntry(".controllable").setBoolean(false); + sendables.add(builder); + addNetworkTable(table, path); + } + + public void addStruct(String entryName, Struct struct, Supplier valueSupplier, int level) { + var entry = StructLogEntry.create(log, entryName, struct); + map.put(entryName, field((timestamp->entry.append(valueSupplier.get(), timestamp)), ()->{}, level)); + } + + public void putStruct(String entryName, Struct struct, R value) { + StructLogEntry.create(log, entryName, struct).append(value); + } + + public void startLog() { + log = DataLogManager.getLog(); + } + + public void helper(Supplier supplier, DataType type, String path, boolean oneShot, int level) { + switch (type) { + {%for t in types%} + + case {{t.TypeName}}: + if (oneShot) { + put(path, ({{t.java.ValueType}}) supplier.{{t.java.SupplierGet}}()); + } else { + add{{t.TypeName}}(path, () -> ({{t.java.ValueType}}) supplier.{{t.java.SupplierGet}}(), level); + } + break; + {%endfor%} + case Sendable: + addSendable(path, (Sendable) supplier.get()); + break; + {%for t in geomTypes%} + case {{t.TypeName}}: + if (oneShot) { + put(path, ({{t.TypeName}}) supplier.get()); + } else { + add{{t.TypeName}}(path, ()->({{t.TypeName}}) supplier.get(), level); + } + break; + {%endfor%} + default: + throw new IllegalArgumentException("Invalid data type"); + } + } +} \ No newline at end of file diff --git a/monologue/src/generate/java/DataType.java.jinja b/monologue/src/generate/java/DataType.java.jinja new file mode 100644 index 0000000..d53d19c --- /dev/null +++ b/monologue/src/generate/java/DataType.java.jinja @@ -0,0 +1,132 @@ +package monologue; + +{%for t in geomTypes%} +import edu.wpi.first.math.geometry.{{t.TypeName}}; +{%endfor%} +import edu.wpi.first.util.sendable.Sendable; +import edu.wpi.first.networktables.NTSendable; +public enum DataType { + {%for t in types%} + {{t.TypeName}}, + {%endfor%} + {%for t in geomTypes%} + {{t.TypeName}}, + {%endfor%} + Sendable, + NTSendable; + + + public static DataType fromClass(Class clazz) throws IllegalArgumentException { + // if clazz has NTSendable interface + for (Class cls : clazz.getInterfaces()) { + if (cls.equals(NTSendable.class)) { + return NTSendable; + } + } + // if clazz does not have NTSendable, but does have Sendable + for (Class cls : clazz.getInterfaces()) { + if (cls.equals(Sendable.class)) { + return Sendable; + } + } + clazz = complexFromPrim(clazz); + {%for t in types%} + {%if t.java.WrapValueType is defined%} + if (clazz.equals({{t.java.WrapValueType}}.class)) { + {%else%} + if (clazz.equals({{t.java.ValueType}}.class)) { + {%endif%} + return {{t.TypeName}}; + } + {%endfor%} + {%for t in geomTypes%} + if (clazz.equals({{t.TypeName}}.class)) { + return {{t.TypeName}}; + } else + {%endfor%} + // if (clazz.equals(Float.class)) { + // return FloatArray; + // } else if (clazz.equals(Boolean.class)) { + // return Boolean; + // } else if (clazz.equals(String.class)) { + // return String; + // } else if (clazz.equals(Integer.class)) { + // return Integer; + // } else if (clazz.equals(Byte[].class)) { + // return Raw; + // } else if (clazz.equals(Double[].class)) { + // return DoubleArray; + // } else if (clazz.equals(Float[].class)) { + // return FloatArray; + // } else if (clazz.equals(Boolean[].class)) { + // return BooleanArray; + // } else if (clazz.equals(String[].class)) { + // return StringArray; + // } else if (clazz.equals(Integer[].class)) { + // return IntegerArray; + // } else if (clazz.equals(Translation2d.class)) { + // return Translation2d; + + /* } else */ + if (NTSendable.class.isAssignableFrom(clazz)) { + return NTSendable; + } else if (Sendable.class.isAssignableFrom(clazz)) { + return Sendable; + } else { + throw new IllegalArgumentException("Invalid datatype"); + } + } + + private static Class complexFromPrim(Class clazz) { + // if (clazz.equals(double.class)) { + // return Double.class; + // } else if (clazz.equals(float.class)) { + // return Float.class; + // } else if (clazz.equals(boolean.class)) { + // return Boolean.class; + // } else if (clazz.equals(String.class)) { + // return String.class; + // } else if (clazz.equals(int.class)) { + // return Integer.class; + // } else if (clazz.equals(byte[].class)) { + // return Byte[].class; + // } else if (clazz.equals(double[].class)) { + // return Double[].class; + // } else if (clazz.equals(float[].class)) { + // return Float[].class; + // } else if (clazz.equals(boolean[].class)) { + // return Boolean[].class; + // } else if (clazz.equals(String[].class)) { + // return String[].class; + // } else if (clazz.equals(int[].class)) { + // return Integer[].class; + // } else if (clazz.equals(Translation2d.class)) { + // return Translation2d.class; + // } else { + // return clazz; + // } + + {%for t in types%} + + if (clazz.equals({{t.java.ValueType}}.class)) { + + {%if t.java.WrapValueType is defined%} + return {{t.java.WrapValueType}}.class; + {%else%} + return {{t.java.ValueType}}.class; + {%endif%} + } else + {%endfor%} + if (clazz.equals(int.class)) { + return Integer.class; + } else + {%for t in geomTypes%} + if (clazz.equals({{t.TypeName}}.class)) { + return {{t.TypeName}}.class; + } else + {%endfor%} + { + return clazz; + } + } +} diff --git a/monologue/src/generate/java/GenericLogger.java.jinja b/monologue/src/generate/java/GenericLogger.java.jinja new file mode 100644 index 0000000..f8a50bd --- /dev/null +++ b/monologue/src/generate/java/GenericLogger.java.jinja @@ -0,0 +1,147 @@ +package monologue; + +{%for t in geomTypes%} +import edu.wpi.first.math.geometry.{{t.TypeName}}; +{%endfor%} +import edu.wpi.first.networktables.NTSendable; +import edu.wpi.first.networktables.NetworkTable; +import edu.wpi.first.networktables.NetworkTableInstance; +import edu.wpi.first.util.datalog.*; +import edu.wpi.first.networktables.*; +import edu.wpi.first.util.struct.Struct; +import edu.wpi.first.util.sendable.Sendable; +import edu.wpi.first.util.sendable.SendableBuilder; +import edu.wpi.first.wpilibj.DataLogManager; +import edu.wpi.first.wpilibj.smartdashboard.SendableBuilderImpl; +import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; +import java.util.function.*; +import edu.wpi.first.util.function.FloatSupplier; + +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.function.Supplier; + +import edu.wpi.first.wpilibj.Timer; +import monologue.util.GeomPacker; + +public abstract class GenericLogger { + public interface LogRunnable extends LongConsumer { + void close(); + } + protected GenericLogger() {} + + protected int logLevel; + + protected LogRunnable field(LongConsumer run, Runnable close) { + return new LogRunnable() { + @Override + public void accept(long timestamp) { + run.accept(timestamp); + } + @Override + public void close() { + close.run(); + } + }; + } + + protected LogRunnable field(LongConsumer run, Runnable close, int level) { + IntSupplier logLevelSupplier = this::getLevel; + return new LogRunnable() { + @Override + public void accept(long timestamp) { + if (logLevelSupplier.getAsInt() <= level) { + run.accept(timestamp); + } + } + @Override + public void close() { + close.run(); + } + }; + } + protected final Map map = new HashMap<>(); + protected final Collection sendables = new LinkedHashSet<>(); + + {%for t in types%} + public void put(String entryName, {{t.java.ValueType}} value) {} + + public void add{{t.TypeName}}(String entryName, {{t.java.Supplier}} valueSupplier, int level) {} + + public void add{{t.TypeName}}(String entryName, {{t.java.Supplier}} valueSupplier) { + add{{t.TypeName}}(entryName, valueSupplier, 0); + } + {%endfor%} + + {%for t in geomTypes%} + public void put(String entryName, {{t.TypeName}} value) { + putStruct(entryName, {{t.TypeName}}.struct, value); + } + public void add{{t.TypeName}}(String entryName, Supplier<{{t.TypeName}}> valueSupplier, int level) { + addStruct(entryName, {{t.TypeName}}.struct, valueSupplier, level); + } + public void add{{t.TypeName}}(String entryName, Supplier<{{t.TypeName}}> valueSupplier) { + add{{t.TypeName}}(entryName, valueSupplier, 0); + } + {%endfor%} + + public void addStruct(String entryName, Struct struct, Supplier valueSupplier, int level) {} + public void putStruct(String entryName, Struct struct, R value) {} + + // public void put(String entryName, Translation2d value) { + // var topic = table.getDoubleArrayTopic(entryName); + // topic.setRetained(true); + // var publisher = new Translation2dPublisher(topic); + // publisher.set(value); + // publisher.close(); + // } + + public void addNetworkTable(NetworkTable table) {}; + + public void addNetworkTable(NetworkTable table, String dlPath) {}; + + public void addSendable(String pathPrefix, String name, Sendable sendable) { + String prefix; + if (!pathPrefix.endsWith("/")) { + prefix = pathPrefix + "/" + name + "/"; + } else { + prefix = pathPrefix + name + "/"; + } + addSendable(prefix, sendable); + } + + public void addSendable(String path, Sendable sendable) {}; + + public void addSendable(String pathPrefix, String name, NTSendable sendable) { + String prefix; + if (!pathPrefix.endsWith("/")) { + prefix = pathPrefix + "/" + name + "/"; + } else { + prefix = pathPrefix + name + "/"; + } + addSendable(prefix, sendable); + } + + public void addSendable(String path, NTSendable sendable) { + addSendable(path, (Sendable) sendable); + }; + + public void setLevel(int level) { + logLevel = level; + } + + public int getLevel() { + return logLevel; + } + public void update() { + long timestamp = (long) (Timer.getFPGATimestamp() * 1e6); + for (Map.Entry entry : map.entrySet()) { + var key = entry.getKey(); + var val = entry.getValue(); + val.accept(timestamp); + } + sendables.forEach(SendableBuilder::update); + } +} \ No newline at end of file diff --git a/monologue/src/generate/java/Logged.java.jinja b/monologue/src/generate/java/Logged.java.jinja new file mode 100644 index 0000000..0935083 --- /dev/null +++ b/monologue/src/generate/java/Logged.java.jinja @@ -0,0 +1,24 @@ +package monologue; + +{%for t in geomTypes%} import edu.wpi.first.math.geometry.{{t.TypeName}}; {%endfor%} + +public interface Logged { + public default String getPath() { + return ""; + } + public default String getFullPath() { + return Monologue.loggedRegistry.getOrDefault(this, "notfound"); + } + {%for t in types%} + public default void put(String key, {{t.java.ValueType}} value) { + Monologue.dataLogger.put(getFullPath() + "/" + key, value); + Monologue.ntLogger.put(getFullPath() + "/" + key, value); + } + {%endfor%} + {%for t in geomTypes%} + public default void put(String key, {{t.TypeName}} value) { + Monologue.dataLogger.put(getFullPath() + "/" + key, value); + Monologue.ntLogger.put(getFullPath() + "/" + key, value); + } + {%endfor%} +} diff --git a/monologue/src/generate/java/NTLogger.java.jinja b/monologue/src/generate/java/NTLogger.java.jinja new file mode 100644 index 0000000..ef8fb6b --- /dev/null +++ b/monologue/src/generate/java/NTLogger.java.jinja @@ -0,0 +1,166 @@ +package monologue; + + +{%for t in geomTypes%} +import edu.wpi.first.math.geometry.{{t.TypeName}}; +{%endfor%} +import edu.wpi.first.networktables.NTSendable; +import edu.wpi.first.networktables.NetworkTable; +import edu.wpi.first.networktables.NetworkTableInstance; +import edu.wpi.first.util.datalog.*; +import edu.wpi.first.networktables.*; +import edu.wpi.first.util.struct.Struct; +import edu.wpi.first.util.sendable.Sendable; +import edu.wpi.first.util.sendable.SendableBuilder; +import edu.wpi.first.wpilibj.DataLogManager; +import edu.wpi.first.wpilibj.smartdashboard.SendableBuilderImpl; +import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; +import java.util.function.*; +import edu.wpi.first.util.function.FloatSupplier; + +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.function.Supplier; + +import edu.wpi.first.wpilibj.Timer; + +public class NTLogger extends GenericLogger { + public NTLogger() {super();} + + private final NetworkTableInstance table = NetworkTableInstance.getDefault(); + + private final Map publishers = new HashMap(); + + {%for t in types%} + public void put(String entryName, {{t.java.ValueType}} value) { + var genericPublisher = publishers.get(entryName); + {{t.java.EntryName}}Publisher publisher = null; + if (genericPublisher == null){ + var topic = table.get{{t.java.EntryName}}Topic(entryName); + {%if t.TypeName == 'Raw'%} + publisher = topic.publish("raw"); + {%else%} + publisher = topic.publish();{%endif%} + publishers.put(entryName, publisher); + } else if (genericPublisher instanceof {{t.java.EntryName}}Publisher) { + publisher = ({{t.java.EntryName}}Publisher) genericPublisher; + } + if (publisher != null) { + {%if t.TypeName == 'IntegerArray'%} + long[] array = new long[value.length]; + for (int i = 0; i < value.length; i++) { + array[i] = (long) value[i]; + } + publisher.set(array); + {%else%} + publisher.set(value); + {%endif%} + } + } + + public void add{{t.TypeName}}(String entryName, {{t.java.Supplier}} valueSupplier, int level) { + var topic = table.get{{t.java.EntryName}}Topic(entryName); + {%if t.TypeName == 'Raw'%} + var publisher = topic.publish("raw"); + {%else%} + var publisher = topic.publish(); + {%endif%} + {%if t.TypeName == 'IntegerArray'%} + map.put(entryName, field((timestamp)->{ + int[] value = valueSupplier.get(); + long[] array = new long[value.length]; + for (int i = 0; i < value.length; i++) { + array[i] = (long) value[i]; + } + publisher.set(array, timestamp); + }, publisher::close, level)); + {%else%} + map.put(entryName, field((timestamp)->publisher.set(valueSupplier.get(), timestamp), publisher::close, level)); + {%endif%} + + } + + @Override + public void add{{t.TypeName}}(String entryName, {{t.java.Supplier}} valueSupplier) { + add{{t.TypeName}}(entryName, valueSupplier, 0); + } + {%endfor%} + + public void addStruct(String entryName, Struct struct, Supplier valueSupplier, int level) { + var topic = table.getStructTopic(entryName, struct); + var publisher = topic.publish(); + map.put(entryName, field((timestamp->publisher.set(valueSupplier.get(), timestamp)), publisher::close, level)); + } + + public void putStruct(String entryName, Struct struct, R value) { + var genericPublisher = publishers.get(entryName); + StructPublisher publisher = null; + if (genericPublisher == null){ + var topic = table.getStructTopic(entryName, struct); + publisher = topic.publish(); + publishers.put(entryName, publisher); + } else if (genericPublisher instanceof StructPublisher) { + publisher = (StructPublisher) genericPublisher; + } + if (publisher != null) { + publisher.set(value); + } + } + + // public void put(String entryName, Translation2d value) { + // var topic = table.getDoubleArrayTopic(entryName); + // topic.setRetained(true); + // var publisher = new Translation2dPublisher(topic); + // publisher.set(value); + // publisher.close(); + // } + + public void addNetworkTable(NetworkTable table) { + // NetworkTableInstance.getDefault() + // .startEntryDataLog(table, table.getPath(), table.getPath()); + } + + public void addNetworkTable(NetworkTable table, String dlPath) { + // NetworkTableInstance.getDefault() + // .startEntryDataLog(table, table.getPath(), dlPath); + } + + public void addSendable(String path, Sendable sendable) { + var builder = new SendableBuilderImpl(); + builder.setTable(table.getTable(path)); + sendable.initSendable(builder); + builder.startListeners(); + table.getTable(path).getEntry(".controllable").setBoolean(false); + sendables.add(builder); + } + + public void helper(Supplier supplier, DataType type, String path, boolean oneShot, int level) { + switch (type) { + {%for t in types%} + case {{t.TypeName}}: + if (oneShot) { + put(path, ({{t.java.ValueType}}) supplier.{{t.java.SupplierGet}}()); + } else { + add{{t.TypeName}}(path, () -> ({{t.java.ValueType}}) supplier.{{t.java.SupplierGet}}(), level); + } + break; + {%endfor%} + case Sendable: + addSendable(path, (Sendable) supplier.get()); + break; + {%for t in geomTypes%} + case {{t.TypeName}}: + if (oneShot) { + put(path, ({{t.TypeName}}) supplier.get()); + } else { + add{{t.TypeName}}(path, ()->({{t.TypeName}}) supplier.get(), level); + } + break; + {%endfor%} + default: + throw new IllegalArgumentException("Invalid data type"); + } + } +} \ No newline at end of file diff --git a/monologue/src/generate/types.json b/monologue/src/generate/types.json new file mode 100644 index 0000000..82dec4e --- /dev/null +++ b/monologue/src/generate/types.json @@ -0,0 +1,173 @@ +[ + { + "TypeName": "Boolean", + "TypeString": "\"boolean\"", + "java": { + "ValueType": "boolean", + "WrapValueType": "Boolean", + "Supplier": "Supplier", + "EmptyValue": "false", + "ConsumerFunctionPackage": "edu.wpi.first.util.function", + "FunctionTypePrefix": "Boolean", + "EntryName": "Boolean", + "SupplierGet" : "get" + } + }, + { + "TypeName": "Integer", + "TypeString": "\"int\"", + "java": { + "ValueType": "int", + "WrapValueType": "Integer", + "Supplier": "Supplier", + "EmptyValue": "0", + "FunctionTypePrefix": "Integer", + "EntryName": "Integer", + "SupplierGet" : "get" + } + }, + + { + "TypeName": "Long", + "TypeString": "\"int\"", + "java": { + "ValueType": "long", + "WrapValueType": "Long", + "Supplier": "Supplier", + "EmptyValue": "0", + "FunctionTypePrefix": "Long", + "EntryName": "Integer", + "SupplierGet" : "get" + } + }, + { + "TypeName": "Float", + "TypeString": "\"float\"", + "java": { + "ValueType": "float", + "WrapValueType": "Float", + "EmptyValue": "0", + "Supplier": "Supplier", + "ConsumerFunctionPackage": "edu.wpi.first.util.function", + "SupplierFunctionPackage": "edu.wpi.first.util.function", + "FunctionTypePrefix": "Float", + "EntryName": "Float", + "SupplierGet" : "get" + } + }, + { + "TypeName": "Double", + "TypeString": "\"double\"", + "java": { + "Supplier": "Supplier", + "WrapValueType": "Double", + "ValueType": "double", + "EmptyValue": "0", + "FunctionTypePrefix": "Double", + "EntryName": "Double", + "SupplierGet" : "get" + } + }, + { + "TypeName": "String", + "TypeString": "\"string\"", + + "java": { + "ValueType": "String", + "Supplier": "Supplier", + "EmptyValue": "\"\"", + "FunctionTypeSuffix": "", + "EntryName": "String", + "SupplierGet" : "get" + } + }, + { + "TypeName": "Raw", + "java": { + "Supplier": "Supplier", + "WrapValueType": "Byte[]", + "ValueType": "byte[]", + "EmptyValue": "new byte[] {}", + "FunctionTypeSuffix": "", + "EntryName": "Raw", + "SupplierGet" : "get" + } + }, + { + "TypeName": "BooleanArray", + "TypeString": "\"boolean[]\"", + "java": { + "Supplier": "Supplier", + "ValueType": "boolean[]", + "WrapValueType": "Boolean[]", + "EmptyValue": "new boolean[] {}", + "FunctionTypeSuffix": "", + "EntryName": "BooleanArray", + "SupplierGet" : "get" + } + }, + { + "TypeName": "IntegerArray", + "TypeString": "\"int[]\"", + "java": { + "Supplier": "Supplier", + "ValueType": "int[]", + "WrapValueType": "Integer[]", + "EmptyValue": "new int[] {}", + "FunctionTypeSuffix": "", + "EntryName": "IntegerArray", + "SupplierGet" : "get" + } + }, + { + "TypeName": "LongArray", + "TypeString": "\"long[]\"", + "java": { + "Supplier": "Supplier", + "ValueType": "long[]", + "WrapValueType": "Long[]", + "EmptyValue": "new long[] {}", + "FunctionTypeSuffix": "", + "EntryName": "IntegerArray", + "SupplierGet" : "get" + } + }, + { + "TypeName": "FloatArray", + "TypeString": "\"float[]\"", + "java": { + "Supplier": "Supplier", + "ValueType": "float[]", + "WrapValueType": "Float[]", + "EmptyValue": "new float[] {}", + "FunctionTypeSuffix": "", + "EntryName": "FloatArray", + "SupplierGet" : "get" + } + }, + { + "TypeName": "DoubleArray", + "TypeString": "\"double[]\"", + "java": { + "Supplier": "Supplier", + "ValueType": "double[]", + "WrapValueType": "Double[]", + "EmptyValue": "new double[] {}", + "FunctionTypeSuffix": "", + "EntryName": "DoubleArray", + "SupplierGet" : "get" + } + }, + { + "TypeName": "StringArray", + "TypeString": "\"string[]\"", + "java": { + "Supplier": "Supplier", + "ValueType": "String[]", + "EmptyValue": "new String[] {}", + "FunctionTypeSuffix": "", + "EntryName": "StringArray", + "SupplierGet" : "get" + } + } +] diff --git a/monologue/src/main/deploy/example.txt b/monologue/src/main/deploy/example.txt new file mode 100644 index 0000000..bb82515 --- /dev/null +++ b/monologue/src/main/deploy/example.txt @@ -0,0 +1,3 @@ +Files placed in this directory will be deployed to the RoboRIO into the +'deploy' directory in the home folder. Use the 'Filesystem.getDeployDirectory' wpilib function +to get a proper path relative to the deploy directory. \ No newline at end of file diff --git a/monologue/src/main/java/monologue/DataLogSendableBuilder.java b/monologue/src/main/java/monologue/DataLogSendableBuilder.java new file mode 100644 index 0000000..08ba5ef --- /dev/null +++ b/monologue/src/main/java/monologue/DataLogSendableBuilder.java @@ -0,0 +1,302 @@ +package monologue; + +import edu.wpi.first.networktables.NTSendableBuilder; +import edu.wpi.first.networktables.NetworkTable; +import edu.wpi.first.networktables.NetworkTableInstance; +import edu.wpi.first.networktables.Topic; +import edu.wpi.first.util.datalog.BooleanArrayLogEntry; +import edu.wpi.first.util.datalog.BooleanLogEntry; +import edu.wpi.first.util.datalog.DataLog; +import edu.wpi.first.util.datalog.DataLogEntry; +import edu.wpi.first.util.datalog.DoubleArrayLogEntry; +import edu.wpi.first.util.datalog.DoubleLogEntry; +import edu.wpi.first.util.datalog.FloatArrayLogEntry; +import edu.wpi.first.util.datalog.FloatLogEntry; +import edu.wpi.first.util.datalog.IntegerArrayLogEntry; +import edu.wpi.first.util.datalog.IntegerLogEntry; +import edu.wpi.first.util.datalog.RawLogEntry; +import edu.wpi.first.util.datalog.StringArrayLogEntry; +import edu.wpi.first.util.datalog.StringLogEntry; +import edu.wpi.first.util.function.BooleanConsumer; +import edu.wpi.first.util.function.FloatConsumer; +import edu.wpi.first.util.function.FloatSupplier; +import edu.wpi.first.wpilibj.DataLogManager; +import edu.wpi.first.wpilibj.Timer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; +import java.util.function.DoubleConsumer; +import java.util.function.DoubleSupplier; +import java.util.function.LongConsumer; +import java.util.function.LongSupplier; +import java.util.function.Supplier; + +public class DataLogSendableBuilder implements NTSendableBuilder { + private static final DataLog log = DataLogManager.getLog(); + private static final NetworkTable rootTable = + NetworkTableInstance.getDefault().getTable("DataLogSendable"); + + private static Optional networkTable = Optional.empty(); + + private final Map> dataLogMap = new HashMap<>(); + private final List updateTables = new ArrayList<>(); + private final List closeables = new ArrayList<>(); + private String prefix; + + public DataLogSendableBuilder(String prefix) { + if (!prefix.endsWith("/")) { + this.prefix = prefix + "/"; + } else { + this.prefix = prefix; + } + } + + @Override + public void setSafeState(Runnable func) {} + + @Override + public void setActuator(boolean value) {} + + @Override + public void setSmartDashboardType(String type) {} + + @Override + public BackendKind getBackendKind() { + return BackendKind.kUnknown; + } + + @Override + public boolean isPublished() { + return true; + } + + @Override + public void clearProperties() { + dataLogMap.clear(); + } + + @Override + public void close() { + clearProperties(); + for (AutoCloseable c : closeables) { + try { + c.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + @Override + public void addCloseable(AutoCloseable c) { + closeables.add(c); + } + + @Override + public void addBooleanProperty(String key, BooleanSupplier getter, BooleanConsumer setter) { + if (getter != null) { + dataLogMap.put(new BooleanLogEntry(log, prefix + key), () -> getter.getAsBoolean()); + } + } + + @Override + public void addDoubleProperty(String key, DoubleSupplier getter, DoubleConsumer setter) { + if (getter != null) { + dataLogMap.put(new DoubleLogEntry(log, prefix + key), () -> getter.getAsDouble()); + } + } + + @Override + public void addStringProperty(String key, Supplier getter, Consumer setter) { + if (getter != null) { + dataLogMap.put(new StringLogEntry(log, prefix + key), getter); + } + } + + @Override + public void addRawProperty( + String key, String typeString, Supplier getter, Consumer setter) { + if (getter != null) { + dataLogMap.put(new RawLogEntry(log, prefix + key + "(" + typeString + ")"), getter); + } + } + + @Override + public void addFloatProperty(String key, FloatSupplier getter, FloatConsumer setter) { + if (getter != null) { + dataLogMap.put(new FloatLogEntry(log, prefix + key), () -> getter.getAsFloat()); + } + } + + @Override + public void addIntegerProperty(String key, LongSupplier getter, LongConsumer setter) { + if (getter != null) { + dataLogMap.put(new IntegerLogEntry(log, prefix + key), (Supplier) getter); + } + } + + @Override + public void addBooleanArrayProperty( + String key, Supplier getter, Consumer setter) { + if (getter != null) { + dataLogMap.put(new BooleanArrayLogEntry(log, prefix + key), getter); + } + } + + @Override + public void addDoubleArrayProperty( + String key, Supplier getter, Consumer setter) { + if (getter != null) { + dataLogMap.put(new DoubleArrayLogEntry(log, prefix + key), getter); + } + } + + @Override + public void addStringArrayProperty( + String key, Supplier getter, Consumer setter) { + if (getter != null) { + dataLogMap.put(new StringArrayLogEntry(log, prefix + key), getter); + } + } + + @Override + public void addFloatArrayProperty( + String key, Supplier getter, Consumer setter) { + if (getter != null) { + dataLogMap.put(new FloatArrayLogEntry(log, prefix + key), getter); + } + } + + @Override + public void addIntegerArrayProperty( + String key, Supplier getter, Consumer setter) { + if (getter != null) { + dataLogMap.put(new IntegerArrayLogEntry(log, prefix + key), getter); + } + } + + @Override + public NetworkTable getTable() { + if (networkTable.isPresent()) { + return networkTable.get(); + } else { + networkTable = Optional.of(rootTable.getSubTable(prefix)); + /// NTLogger.addNetworkTable(networkTable.get(), prefix); + return networkTable.get(); + } + } + + @Override + public void setUpdateTable(Runnable func) { + updateTables.add(func); + } + + @Override + public Topic getTopic(String key) { + return getTable().getTopic(key); + } + + @Override + public void update() { + long timestamp = (long) (Timer.getFPGATimestamp() * 1e6); + for (Map.Entry> entry : dataLogMap.entrySet()) { + var key = entry.getKey(); + var val = entry.getValue().get(); + if (key instanceof BooleanArrayLogEntry) { + ((BooleanArrayLogEntry) key).append((boolean[]) val, timestamp); + + } else if (key instanceof BooleanLogEntry) { + ((BooleanLogEntry) key).append((boolean) val, timestamp); + + } else if (key instanceof DoubleArrayLogEntry) { + ((DoubleArrayLogEntry) key).append((double[]) val, timestamp); + + } else if (key instanceof DoubleLogEntry) { + ((DoubleLogEntry) key).append((double) val, timestamp); + + } else if (key instanceof FloatArrayLogEntry) { + ((FloatArrayLogEntry) key).append((float[]) val, timestamp); + + } else if (key instanceof FloatLogEntry) { + ((FloatLogEntry) key).append((float) val, timestamp); + + } else if (key instanceof IntegerArrayLogEntry) { + ((IntegerArrayLogEntry) key).append((long[]) val, timestamp); + + } else if (key instanceof IntegerLogEntry) { + ((IntegerLogEntry) key).append((long) val, timestamp); + + } else if (key instanceof RawLogEntry) { + ((RawLogEntry) key).append((byte[]) val, timestamp); + + } else if (key instanceof StringArrayLogEntry) { + ((StringArrayLogEntry) key).append((String[]) val, timestamp); + + } else if (key instanceof StringLogEntry) { + ((StringLogEntry) key).append((String) val, timestamp); + } + } + for (Runnable updateTable : updateTables) { + updateTable.run(); + } + } + + @Override + public void publishConstBoolean(String key, boolean value) { + new BooleanLogEntry(log, prefix+key).append(value); + } + + @Override + public void publishConstInteger(String key, long value) { + new IntegerLogEntry(log, prefix+key).append(value); + } + + @Override + public void publishConstFloat(String key, float value) { + new FloatLogEntry(log, prefix+key).append(value); + } + + @Override + public void publishConstDouble(String key, double value) { + new DoubleLogEntry(log, prefix+key).append(value); + } + + @Override + public void publishConstString(String key, String value) { + new StringLogEntry(log, prefix+key).append(value); + } + + @Override + public void publishConstBooleanArray(String key, boolean[] value) { + new BooleanArrayLogEntry(log, prefix+key).append(value); + } + + @Override + public void publishConstIntegerArray(String key, long[] value) { + new IntegerArrayLogEntry(log, prefix+key).append(value); + } + + @Override + public void publishConstFloatArray(String key, float[] value) { + new FloatArrayLogEntry(log, prefix+key).append(value); + } + + @Override + public void publishConstDoubleArray(String key, double[] value) { + new DoubleArrayLogEntry(log, prefix+key).append(value); + } + + @Override + public void publishConstStringArray(String key, String[] value) { + new StringArrayLogEntry(log, prefix+key).append(value); + } + + @Override + public void publishConstRaw(String key, String typeString, byte[] value) { + new RawLogEntry(log, prefix+key).append(value); + } +} diff --git a/monologue/src/main/java/monologue/Monologue.java b/monologue/src/main/java/monologue/Monologue.java new file mode 100644 index 0000000..34970a2 --- /dev/null +++ b/monologue/src/main/java/monologue/Monologue.java @@ -0,0 +1,351 @@ +package monologue; + +import edu.wpi.first.networktables.NTSendable; +import edu.wpi.first.util.sendable.Sendable; +import edu.wpi.first.wpilibj.DriverStation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +public class Monologue { + + public static final NTLogger ntLogger = new NTLogger(); + public static final DataLogger dataLogger = new DataLogger(); + public static final Map loggedRegistry = new HashMap(); + + private static String camelToNormal(String camelCase) { + StringBuilder sb = new StringBuilder(); + for (char c : camelCase.toCharArray()) { + if (Character.isUpperCase(c)) { + sb.append(' '); + } + sb.append(c); + } + sb.setCharAt(0, Character.toUpperCase(sb.charAt(0))); + return sb.toString(); + } + + private static String methodNameFix(String name) { + if (name.startsWith("get")) { + name = name.substring(3); + } else if (name.endsWith("getter")) { + name = name.substring(0, name.length() - 6); + } + name = name.substring(0, 1).toLowerCase() + name.substring(1); + return name; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.METHOD}) + public @interface BothLog { + public String path() default ""; + + public boolean once() default false; + + public int level() default 0; + } + + /** + * Annotate a field or method IN A SUBSYSTEM with this to log it to SmartDashboard + * + *

Supported Types(primitive or not): Double, Boolean, String, Integer,
+ * Double[], Boolean[], String[], Integer[], Sendable + * + * @param oneShot [optional] whether or not to only log once + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.METHOD}) + public @interface DataLog { + public boolean once() default false; + + public int level() default 0; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.METHOD}) + public @interface NTLog { + public boolean once() default false; + + public int level() default 0; + } + + private static Supplier getSupplier(Field field, Logged loggable) { + field.setAccessible(true); + return () -> { + try { + return field.get(loggable); + } catch (IllegalArgumentException | IllegalAccessException e) { + DriverStation.reportWarning(field.getName() + " supllier is erroring", false); + e.printStackTrace(); + return null; + } + }; + } + + private static Supplier getSupplier(Method method, Logged loggable) { + return () -> { + try { + return method.invoke(loggable); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + DriverStation.reportWarning(method.getName() + " supllier is erroring", false); + return null; + } + }; + } + + public static void setupLogging(Logged loggable, String rootPath, boolean createDataLog) { + System.out.println(rootPath); + loggedRegistry.put(loggable, rootPath); + String ss_name = rootPath; + for (Field field : getInheritedPrivateFields(loggable.getClass())) { + + field.setAccessible(true); + if (isNull(field, loggable)) { + + continue; + } + // if the field is of type Logged + if (Logged.class.isAssignableFrom(field.getType())) { + try { + String pathOverride = ((Logged) field.get(loggable)).getPath(); + if (pathOverride.equals("")) { + pathOverride = field.getName(); + } + // recursion for the Logged field + Monologue.setupLogging( + (Logged) field.get(loggable), ss_name + "/" + pathOverride, createDataLog); + continue; + } catch (IllegalArgumentException | IllegalAccessException e) { + DriverStation.reportWarning(field.getName() + " supllier is erroring", false); + e.printStackTrace(); + continue; + } + } + + if (field.getType().isArray()) { + try { + // If primitive array + if (Object.class.isAssignableFrom(field.get(loggable).getClass().getComponentType())) { + + // Include all elements whose runtime class is Loggable + for (Object obj : (Object[]) field.get(loggable)) { + if (obj instanceof Logged) { + try { + String pathOverride = ((Logged) obj).getPath(); + if (pathOverride.equals("")) { + pathOverride = obj.getClass().getSimpleName(); + } + Monologue.setupLogging( + (Logged) obj, + ss_name + "/" + field.getName() + "/" + pathOverride, + createDataLog); + continue; + } catch (IllegalArgumentException e) { + DriverStation.reportWarning(field.getName() + " supllier is erroring", false); + e.printStackTrace(); + } + } + } + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + // Proceed on all valid elements + // Handle collections similarly + } else if (Collection.class.isAssignableFrom(field.getType())) { + try { + int idx = 0; + // Include all elements whose runtime class is Loggable + for (Object obj : (Collection) field.get(loggable)) { + System.out.println(obj); + if (obj instanceof Logged) { + try { + String pathOverride = ((Logged) obj).getPath(); + if (pathOverride.equals("")) { + pathOverride = obj.getClass().getSimpleName() + "[" + idx++ + "]"; + } + Monologue.setupLogging( + (Logged) obj, + ss_name + "/" + field.getName() + "/" + pathOverride, + createDataLog); + continue; + } catch (IllegalArgumentException e) { + e.printStackTrace(); + DriverStation.reportWarning(field.getName() + " supllier is erroring", true); + } + } + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + if (field.getAnnotations().length == 0) { + continue; + } + // setup the annotation. + String annotationPath = ""; + boolean oneShot; + int level; + String name = field.getName(); + DataType type; + try{ + type = DataType.fromClass(field.getType()); + } catch (IllegalArgumentException e) { + DriverStation.reportWarning("Tried to log invalid type " + name + "(" + field.getType() + ") in " + ss_name, false); + continue; + } + if ((field.isAnnotationPresent(DataLog.class) || field.isAnnotationPresent(BothLog.class)) + && createDataLog) { + dataLogger.startLog(); + + DataLog annotation = field.getAnnotation(DataLog.class); + if (annotation == null) { + BothLog logAnnotation = field.getAnnotation(BothLog.class); + annotationPath = logAnnotation.path(); + oneShot = logAnnotation.once(); + level = logAnnotation.level(); + } else { + oneShot = annotation.once(); + level = annotation.level(); + } + String key = annotationPath.equals("") ? ss_name + "/" + name : annotationPath; + if (type == DataType.NTSendable) { + dataLogger.addSendable(key, (NTSendable) getSupplier(field, loggable).get()); + } else if (type == DataType.Sendable) { + dataLogger.addSendable(key, (Sendable) getSupplier(field, loggable).get()); + } else { + dataLogger.helper(getSupplier(field, loggable), type, key, oneShot, level); + } + } + if (field.isAnnotationPresent(NTLog.class) || field.isAnnotationPresent(BothLog.class)) { + + NTLog annotation = field.getAnnotation(NTLog.class); + if (annotation == null) { + BothLog logAnnotation = field.getAnnotation(BothLog.class); + annotationPath = logAnnotation.path(); + oneShot = logAnnotation.once(); + level = logAnnotation.level(); + } else { + + oneShot = annotation.once(); + level = annotation.level(); + } + String key = annotationPath.equals("") ? ss_name + "/" + field.getName() : annotationPath; + if (type == DataType.Sendable || type == DataType.NTSendable) { + ntLogger.addSendable(key, (Sendable) getSupplier(field, loggable).get()); + } else { + ntLogger.helper(getSupplier(field, loggable), type, key, oneShot, level); + } + } + } + + for (Method method :getInheritedMethods(loggable.getClass())) { + if ((method.isAnnotationPresent(DataLog.class) || method.isAnnotationPresent(BothLog.class)) + && createDataLog) { + dataLogger.startLog(); + method.setAccessible(true); + String annotationPath = ""; + boolean oneShot; + int level; + DataLog annotation = method.getAnnotation(DataLog.class); + if (annotation == null) { + BothLog logAnnotation = method.getAnnotation(BothLog.class); + annotationPath = logAnnotation.path(); + oneShot = logAnnotation.once(); + level = logAnnotation.level(); + } else { + oneShot = annotation.once(); + level = annotation.level(); + } + String name = method.getName(); // methodNameFix(method.getName()); + String path = annotationPath.equals("") ? ss_name + "/" + name : annotationPath; + + DataType type = DataType.fromClass(method.getReturnType()); + if (method.getParameterCount() > 0) { + throw new IllegalArgumentException("Cannot have parameters on a DataLog method"); + } + dataLogger.helper(getSupplier(method, loggable), type, path, oneShot, level); + } + if (method.isAnnotationPresent(NTLog.class) || method.isAnnotationPresent(BothLog.class)) { + method.setAccessible(true); + String annotationPath = ""; + boolean oneShot; + int level; + NTLog annotation = method.getAnnotation(NTLog.class); + if (annotation == null) { + BothLog logAnnotation = method.getAnnotation(BothLog.class); + annotationPath = logAnnotation.path(); + oneShot = logAnnotation.once(); + level = logAnnotation.level(); + } else { + oneShot = annotation.once(); + level = annotation.level(); + } + String key = annotationPath.equals("") ? ss_name + "/" + method.getName() : annotationPath; + DataType type = DataType.fromClass(method.getReturnType()); + if (method.getParameterCount() > 0) { + throw new IllegalArgumentException("Cannot have parameters on a DataLog method"); + } + ntLogger.helper(getSupplier(method, loggable), type, key, oneShot, level); + } + } + } + + public static void update() { + ntLogger.update(); + dataLogger.update(); + } + + public static void updateNT() { + ntLogger.update(); + } + + public static void updateDataLog() { + dataLogger.update(); + } + + private static boolean isNull(Field field, Object obj) { + field.setAccessible(true); + boolean isNull = true; + try { + isNull = field.get(obj) == null; + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return isNull; + } + + private static List getInheritedPrivateFields(Class type) { + List result = new ArrayList(); + + Class i = type; + while (i != null && i != Object.class) { + Collections.addAll(result, i.getDeclaredFields()); + i = i.getSuperclass(); + } + + return result; +} + private static List getInheritedMethods(Class type) { + List result = new ArrayList(); + + Class i = type; + while (i != null && i != Object.class) { + Collections.addAll(result, i.getDeclaredMethods()); + i = i.getSuperclass(); + } + + return result; + } +} diff --git a/monologue/src/main/java/monologue/util/GeomPacker.java b/monologue/src/main/java/monologue/util/GeomPacker.java new file mode 100644 index 0000000..86075a2 --- /dev/null +++ b/monologue/src/main/java/monologue/util/GeomPacker.java @@ -0,0 +1,113 @@ +package monologue.util; + +import edu.wpi.first.math.geometry.Pose2d; +import edu.wpi.first.math.geometry.Pose3d; +import edu.wpi.first.math.geometry.Rotation2d; +import edu.wpi.first.math.geometry.Rotation3d; +import edu.wpi.first.math.geometry.Transform2d; +import edu.wpi.first.math.geometry.Transform3d; +import edu.wpi.first.math.geometry.Translation2d; +import edu.wpi.first.math.geometry.Translation3d; + +public class GeomPacker { + public static double[] pack(Translation2d obj) { + double[] arr = new double[2]; + arr[0] = obj.getX(); + arr[1] = obj.getY(); + return arr; + } + + public static Translation2d unpackTranslation2d(double[] arr) { + return new Translation2d(arr[0], arr[1]); + } + + public static double[] pack(Rotation2d obj) { + double[] arr = new double[1]; + arr[0] = obj.getRadians(); + return arr; + } + + public static Rotation2d unpackRotation2d(double[] arr) { + return new Rotation2d(arr[0]); + } + + public static double[] pack(Pose2d obj) { + double[] arr = new double[3]; + arr[0] = obj.getTranslation().getX(); + arr[1] = obj.getTranslation().getY(); + arr[2] = obj.getRotation().getRadians(); + return arr; + } + + public static Pose2d unpackPose2d(double[] arr) { + return new Pose2d(arr[0], arr[1], new Rotation2d(arr[2])); + } + + public static double[] pack(Transform2d obj) { + double[] arr = new double[3]; + arr[0] = obj.getTranslation().getX(); + arr[1] = obj.getTranslation().getY(); + arr[2] = obj.getRotation().getRadians(); + return arr; + } + + public static Transform2d unpackTransform2d(double[] arr) { + return new Transform2d(new Translation2d(arr[0], arr[1]), new Rotation2d(arr[2])); + } + + public static double[] pack(Translation3d obj) { + double[] arr = new double[3]; + arr[0] = obj.getX(); + arr[1] = obj.getY(); + arr[2] = obj.getZ(); + return arr; + } + + public static Translation3d unpackTranslation3d(double[] arr) { + return new Translation3d(arr[0], arr[1], arr[2]); + } + + public static double[] pack(Rotation3d obj) { + double[] arr = new double[3]; + arr[0] = obj.getX(); + arr[1] = obj.getY(); + arr[2] = obj.getZ(); + return arr; + } + + public static Rotation3d unpackRotation3d(double[] arr) { + return new Rotation3d(arr[0], arr[1], arr[2]); + } + + public static double[] pack(Pose3d obj) { + double[] arr = new double[7]; + arr[0] = obj.getTranslation().getX(); + arr[1] = obj.getTranslation().getY(); + arr[2] = obj.getTranslation().getZ(); + arr[3] = obj.getRotation().getX(); + arr[4] = obj.getRotation().getY(); + arr[5] = obj.getRotation().getZ(); + return arr; + } + + public static Pose3d unpackPose3d(double[] arr) { + return new Pose3d( + new Translation3d(arr[0], arr[1], arr[2]), new Rotation3d(arr[3], arr[4], arr[5])); + } + + public static double[] pack(Transform3d obj) { + double[] arr = new double[7]; + arr[0] = obj.getTranslation().getX(); + arr[1] = obj.getTranslation().getY(); + arr[2] = obj.getTranslation().getZ(); + arr[3] = obj.getRotation().getX(); + arr[4] = obj.getRotation().getY(); + arr[5] = obj.getRotation().getZ(); + return arr; + } + + public static Transform3d unpackTransform3d(double[] arr) { + return new Transform3d( + new Translation3d(arr[0], arr[1], arr[2]), new Rotation3d(arr[3], arr[4], arr[5])); + } +} diff --git a/test-project/.gitignore b/test-project/.gitignore new file mode 100644 index 0000000..3325f5c --- /dev/null +++ b/test-project/.gitignore @@ -0,0 +1,172 @@ +# This gitignore has been specially created by the WPILib team. +# If you remove items from this file, intellisense might break. + +### C++ ### +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### Gradle ### +.gradle +/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties + +# # VS Code Specific Java Settings +# DO NOT REMOVE .classpath and .project +.classpath +.project +.settings/ +bin/ + +# IntelliJ +*.iml +*.ipr +*.iws +.idea/ +out/ + +# Fleet +.fleet + +# Simulation GUI and other tools window save file +*-window.json diff --git a/test-project/.vscode/launch.json b/test-project/.vscode/launch.json new file mode 100644 index 0000000..5b804e8 --- /dev/null +++ b/test-project/.vscode/launch.json @@ -0,0 +1,21 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "type": "wpilib", + "name": "WPILib Desktop Debug", + "request": "launch", + "desktop": true, + }, + { + "type": "wpilib", + "name": "WPILib roboRIO Debug", + "request": "launch", + "desktop": false, + } + ] +} diff --git a/test-project/.vscode/settings.json b/test-project/.vscode/settings.json new file mode 100644 index 0000000..8be11f2 --- /dev/null +++ b/test-project/.vscode/settings.json @@ -0,0 +1,29 @@ +{ + "java.configuration.updateBuildConfiguration": "automatic", + "java.server.launchMode": "Standard", + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "bin/": true, + "**/.classpath": true, + "**/.project": true, + "**/.settings": true, + "**/.factorypath": true, + "**/*~": true + }, + "java.test.config": [ + { + "name": "WPIlibUnitTests", + "workingDirectory": "${workspaceFolder}/build/jni/release", + "vmargs": [ "-Djava.library.path=${workspaceFolder}/build/jni/release" ], + "env": { + "LD_LIBRARY_PATH": "${workspaceFolder}/build/jni/release" , + "DYLD_LIBRARY_PATH": "${workspaceFolder}/build/jni/release" + } + }, + ], + "java.test.defaultConfig": "WPIlibUnitTests" +} diff --git a/test-project/.wpilib/wpilib_preferences.json b/test-project/.wpilib/wpilib_preferences.json new file mode 100644 index 0000000..3acf127 --- /dev/null +++ b/test-project/.wpilib/wpilib_preferences.json @@ -0,0 +1,6 @@ +{ + "enableCppIntellisense": false, + "currentLanguage": "java", + "projectYear": "2024beta", + "teamNumber": 6995 +} \ No newline at end of file diff --git a/test-project/FRC_20231107_081556.wpilog b/test-project/FRC_20231107_081556.wpilog new file mode 100644 index 0000000..5f15673 Binary files /dev/null and b/test-project/FRC_20231107_081556.wpilog differ diff --git a/test-project/WPILib-License.md b/test-project/WPILib-License.md new file mode 100644 index 0000000..8434944 --- /dev/null +++ b/test-project/WPILib-License.md @@ -0,0 +1,24 @@ +Copyright (c) 2009-2023 FIRST and other WPILib contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of FIRST, WPILib, nor the names of other WPILib + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY FIRST AND OTHER WPILIB CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY NONINFRINGEMENT AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL FIRST OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/test-project/build.gradle b/test-project/build.gradle new file mode 100644 index 0000000..9a378a4 --- /dev/null +++ b/test-project/build.gradle @@ -0,0 +1,109 @@ +plugins { + id "java" + id "edu.wpi.first.GradleRIO" version "2024.1.1-beta-3" +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + +def ROBOT_MAIN_CLASS = "frc.robot.Main" + +// Define my targets (RoboRIO) and artifacts (deployable files) +// This is added by GradleRIO's backing project DeployUtils. +deploy { + targets { + roborio(getTargetTypeClass('RoboRIO')) { + // Team number is loaded either from the .wpilib/wpilib_preferences.json + // or from command line. If not found an exception will be thrown. + // You can use getTeamOrDefault(team) instead of getTeamNumber if you + // want to store a team number in this file. + team = project.frc.getTeamNumber() + debug = project.frc.getDebugOrDefault(false) + + artifacts { + // First part is artifact name, 2nd is artifact type + // getTargetTypeClass is a shortcut to get the class type using a string + + frcJava(getArtifactTypeClass('FRCJavaArtifact')) { + } + + // Static files artifact + frcStaticFileDeploy(getArtifactTypeClass('FileTreeArtifact')) { + files = project.fileTree('src/main/deploy') + directory = '/home/lvuser/deploy' + } + } + } + } +} + +def deployArtifact = deploy.targets.roborio.artifacts.frcJava + +// Set to true to use debug for JNI. +wpi.java.debugJni = false + +// Set this to true to enable desktop support. +def includeDesktopSupport = true + +// Defining my dependencies. In this case, WPILib (+ friends), and vendor libraries. +// Also defines JUnit 5. +dependencies { + implementation wpi.java.deps.wpilib() + implementation wpi.java.vendor.java() + + roborioDebug wpi.java.deps.wpilibJniDebug(wpi.platforms.roborio) + roborioDebug wpi.java.vendor.jniDebug(wpi.platforms.roborio) + + roborioRelease wpi.java.deps.wpilibJniRelease(wpi.platforms.roborio) + roborioRelease wpi.java.vendor.jniRelease(wpi.platforms.roborio) + + nativeDebug wpi.java.deps.wpilibJniDebug(wpi.platforms.desktop) + nativeDebug wpi.java.vendor.jniDebug(wpi.platforms.desktop) + simulationDebug wpi.sim.enableDebug() + + nativeRelease wpi.java.deps.wpilibJniRelease(wpi.platforms.desktop) + nativeRelease wpi.java.vendor.jniRelease(wpi.platforms.desktop) + simulationRelease wpi.sim.enableRelease() + + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.2' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2' + + implementation "com.github.Oblarg:Oblog:5.1.0" + implementation 'com.github.shueja-personal:Monologue:v0.0.4-alpha8' +} + +repositories { + mavenLocal() + maven { url 'https://jitpack.io' } +} + +test { + useJUnitPlatform() + systemProperty 'junit.jupiter.extensions.autodetection.enabled', 'true' +} + +// Simulation configuration (e.g. environment variables). +wpi.sim.addGui().defaultEnabled = true +wpi.sim.addDriverstation() + +// Setting up my Jar File. In this case, adding all libraries into the main jar ('fat jar') +// in order to make them all available at runtime. Also adding the manifest so WPILib +// knows where to look for our Robot Class. +jar { + from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } + manifest edu.wpi.first.gradlerio.GradleRIOPlugin.javaManifest(ROBOT_MAIN_CLASS) + duplicatesStrategy = DuplicatesStrategy.INCLUDE +} + +// Configure jar and deploy tasks +deployArtifact.jarTask = jar +wpi.java.configureExecutableTasks(jar) +wpi.java.configureTestTasks(test) + +// Configure string concat to always inline compile +tasks.withType(JavaCompile) { + options.compilerArgs.add '-XDstringConcat=inline' +} diff --git a/test-project/gradle/wrapper/gradle-wrapper.jar b/test-project/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7f93135 Binary files /dev/null and b/test-project/gradle/wrapper/gradle-wrapper.jar differ diff --git a/test-project/gradle/wrapper/gradle-wrapper.properties b/test-project/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..c5c2706 --- /dev/null +++ b/test-project/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=permwrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=permwrapper/dists diff --git a/test-project/gradlew b/test-project/gradlew new file mode 100644 index 0000000..1aa94a4 --- /dev/null +++ b/test-project/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original 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 POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# 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 ;; #( + MSYS* | 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 + if ! command -v java >/dev/null 2>&1 + then + 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 +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# 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"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/test-project/gradlew.bat b/test-project/gradlew.bat new file mode 100644 index 0000000..6689b85 --- /dev/null +++ b/test-project/gradlew.bat @@ -0,0 +1,92 @@ +@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=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@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% equ 0 goto execute + +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 execute + +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 + +: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 %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 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! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/test-project/networktables.json b/test-project/networktables.json new file mode 100644 index 0000000..60b0742 --- /dev/null +++ b/test-project/networktables.json @@ -0,0 +1 @@ +[] diff --git a/test-project/settings.gradle b/test-project/settings.gradle new file mode 100644 index 0000000..3e30f84 --- /dev/null +++ b/test-project/settings.gradle @@ -0,0 +1,30 @@ +import org.gradle.internal.os.OperatingSystem + +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + String frcYear = '2024' + File frcHome + if (OperatingSystem.current().isWindows()) { + String publicFolder = System.getenv('PUBLIC') + if (publicFolder == null) { + publicFolder = "C:\\Users\\Public" + } + def homeRoot = new File(publicFolder, "wpilib") + frcHome = new File(homeRoot, frcYear) + } else { + def userFolder = System.getProperty("user.home") + def homeRoot = new File(userFolder, "wpilib") + frcHome = new File(homeRoot, frcYear) + } + def frcHomeMaven = new File(frcHome, 'maven') + maven { + name 'frcHome' + url frcHomeMaven + } + } +} + +Properties props = System.getProperties(); +props.setProperty("org.gradle.internal.native.headers.unresolved.dependencies.ignore", "true"); diff --git a/test-project/simgui-ds.json b/test-project/simgui-ds.json new file mode 100644 index 0000000..73cc713 --- /dev/null +++ b/test-project/simgui-ds.json @@ -0,0 +1,92 @@ +{ + "keyboardJoysticks": [ + { + "axisConfig": [ + { + "decKey": 65, + "incKey": 68 + }, + { + "decKey": 87, + "incKey": 83 + }, + { + "decKey": 69, + "decayRate": 0.0, + "incKey": 82, + "keyRate": 0.009999999776482582 + } + ], + "axisCount": 3, + "buttonCount": 4, + "buttonKeys": [ + 90, + 88, + 67, + 86 + ], + "povConfig": [ + { + "key0": 328, + "key135": 323, + "key180": 322, + "key225": 321, + "key270": 324, + "key315": 327, + "key45": 329, + "key90": 326 + } + ], + "povCount": 1 + }, + { + "axisConfig": [ + { + "decKey": 74, + "incKey": 76 + }, + { + "decKey": 73, + "incKey": 75 + } + ], + "axisCount": 2, + "buttonCount": 4, + "buttonKeys": [ + 77, + 44, + 46, + 47 + ], + "povCount": 0 + }, + { + "axisConfig": [ + { + "decKey": 263, + "incKey": 262 + }, + { + "decKey": 265, + "incKey": 264 + } + ], + "axisCount": 2, + "buttonCount": 6, + "buttonKeys": [ + 260, + 268, + 266, + 261, + 269, + 267 + ], + "povCount": 0 + }, + { + "axisCount": 0, + "buttonCount": 0, + "povCount": 0 + } + ] +} diff --git a/test-project/simgui.json b/test-project/simgui.json new file mode 100644 index 0000000..be3e4b0 --- /dev/null +++ b/test-project/simgui.json @@ -0,0 +1,26 @@ +{ + "NTProvider": { + "types": { + "/FMSInfo": "FMSInfo", + "/Robot/field": "Field2d", + "/Robot/mech": "Mechanism2d" + } + }, + "NetworkTables": { + "retained": { + "Robot": { + "open": true + } + }, + "transitory": { + "Robot": { + "Transform2d##v_/Robot/imperative": { + "Translation2d##v_translation": { + "open": true + } + }, + "open": true + } + } + } +} diff --git a/test-project/src/main/java/frc/robot/Geometry.java b/test-project/src/main/java/frc/robot/Geometry.java new file mode 100644 index 0000000..dc7eade --- /dev/null +++ b/test-project/src/main/java/frc/robot/Geometry.java @@ -0,0 +1,73 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package frc.robot; + +import monologue.Monologue.BothLog; +import monologue.Logged; +import edu.wpi.first.math.geometry.*; + +/** Add your docs here. */ +public class Geometry implements Logged { + @BothLog + public String getStringPath() + { + return getFullPath(); + } + @BothLog private Pose2d m_pose2d = new Pose2d(); + + @BothLog + private Pose2d getPose2d() { + return m_pose2d; + } + + @BothLog private Translation2d m_translation2d = new Translation2d(); + + @BothLog + private Translation2d getTranslation2d() { + return m_translation2d; + } + + @BothLog private Transform2d m_transform2d = new Transform2d(); + + @BothLog + private Transform2d getTransform2d() { + return m_transform2d; + } + + @BothLog private Rotation2d m_rotation2d = new Rotation2d(); + + @BothLog + private Rotation2d getRotation2d() { + return m_rotation2d; + } + + @BothLog private Pose3d m_pose3d = new Pose3d(); + + @BothLog + private Pose3d getPose3d() { + return m_pose3d; + } + + @BothLog private Translation3d m_translation3d = new Translation3d(); + + @BothLog + private Translation3d getTranslation3d() { + return m_translation3d; + } + + @BothLog private Transform3d m_transform3d = new Transform3d(); + + @BothLog + private Transform3d getTransform3d() { + return m_transform3d; + } + + @BothLog private Rotation3d m_rotation3d = new Rotation3d(); + + @BothLog + private Rotation3d getRotation3d() { + return m_rotation3d; + } +} diff --git a/test-project/src/main/java/frc/robot/Internal.java b/test-project/src/main/java/frc/robot/Internal.java new file mode 100644 index 0000000..46b76c9 --- /dev/null +++ b/test-project/src/main/java/frc/robot/Internal.java @@ -0,0 +1,52 @@ +package frc.robot; + +import monologue.Monologue.BothLog; +import monologue.Logged; +import io.github.oblarg.oblog.Loggable; +import io.github.oblarg.oblog.annotations.Log; + +public class Internal implements Logged, Loggable { + + private int calls = 0; + private String name; + private String showName; + + public Internal(String name) { + this.name = name + "asdfghjklqwertyuiopzxcvbnm,qwertyuio"; + showName = this.name; + } + ; + + @Override + public String getPath() { + // TODO Auto-generated method stub + return name; + } + + @Override + public String configureLogName() { + // TODO Auto-generated method stub + return name; + } + + // @Log + // @NTLog + // public String name="name"; + // @Log + // @DataLog + public String getName() { + calls++; + return showName; + } + + @Log + @BothLog + public double getNumber() { + calls++; + return calls + calls / 1000.0; + } + + public void update() { + showName = name + calls; + } +} diff --git a/test-project/src/main/java/frc/robot/Main.java b/test-project/src/main/java/frc/robot/Main.java new file mode 100644 index 0000000..8776e5d --- /dev/null +++ b/test-project/src/main/java/frc/robot/Main.java @@ -0,0 +1,25 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package frc.robot; + +import edu.wpi.first.wpilibj.RobotBase; + +/** + * Do NOT add any static variables to this class, or any initialization at all. Unless you know what + * you are doing, do not modify this file except to change the parameter class to the startRobot + * call. + */ +public final class Main { + private Main() {} + + /** + * Main initialization function. Do not perform any initialization here. + * + *

If you change your main robot class, change the parameter type. + */ + public static void main(String... args) { + RobotBase.startRobot(Robot::new); + } +} diff --git a/test-project/src/main/java/frc/robot/Robot.java b/test-project/src/main/java/frc/robot/Robot.java new file mode 100644 index 0000000..4bae577 --- /dev/null +++ b/test-project/src/main/java/frc/robot/Robot.java @@ -0,0 +1,142 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package frc.robot; + +import monologue.Monologue; +import monologue.Monologue.BothLog; +import monologue.Monologue.DataLog; +import monologue.Monologue.NTLog; +import monologue.Logged; +import edu.wpi.first.math.filter.LinearFilter; +import edu.wpi.first.math.geometry.Pose2d; +import edu.wpi.first.math.geometry.Rotation2d; +import edu.wpi.first.math.geometry.Transform2d; +import edu.wpi.first.networktables.NetworkTableInstance; +import edu.wpi.first.wpilibj.TimedRobot; +import edu.wpi.first.wpilibj.Timer; +import edu.wpi.first.wpilibj.smartdashboard.Field2d; +import edu.wpi.first.wpilibj.smartdashboard.Mechanism2d; +import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; +import io.github.oblarg.oblog.Loggable; +import io.github.oblarg.oblog.Logger; +import java.util.ArrayList; + +/** + * The VM is configured to automatically run this class, and to call the functions corresponding to + * each mode, as described in the TimedRobot documentation. If you change the name of this class or + * the package after creating this project, you must also update the build.gradle file in the + * project. + */ +public class Robot extends TimedRobot implements Logged, Loggable { + @BothLog(once=true) private int samples = 0; + boolean useOblog = false; + boolean dataLog = false; + + ArrayList m_internals = new ArrayList<>(); + private LinearFilter filter = LinearFilter.movingAverage(50); + double totalOfAvgs = 0; + double avgsTaken = 0; + + private Geometry m_geometry = new Geometry(); + + @NTLog @DataLog private Field2d field = new Field2d(); + + @BothLog private Mechanism2d mech = new Mechanism2d(1, 1); + @BothLog private long[] array = {0, 1, 2}; + + /** + * This function is run when the robot is first started up and should be used for any + * initialization code. + */ + @Override + public void robotInit() { + SmartDashboard.putBoolean("bool", true); + Monologue.dataLogger.addNetworkTable( + NetworkTableInstance.getDefault().getTable("SmartDashboard")); + // for (int i = 0; i < 100; i++) { + // m_internals.add(new Internal(i + "")); + // } + // DataLogManager.start(); + NetworkTableInstance.getDefault().getTopic("name").getGenericEntry(); + if (useOblog) { + Logger.configureLoggingAndConfig(this, false); + } else { + Monologue.setupLogging(this, "/Robot", true); + } + put("imperative", new Transform2d()); + } + + @BothLog + public String getStringPath() + { + return getFullPath(); + } + @Override + public void robotPeriodic() { + var timeBefore = Timer.getFPGATimestamp() * 1e6; + if (useOblog) { + Logger.updateEntries(); + } else { + if (true) { + Monologue.updateDataLog(); + Monologue.updateNT(); + } else { + Monologue.updateNT(); + } + } + var timeAfter = Timer.getFPGATimestamp() * 1e6; + samples++; + double avg = filter.calculate(timeAfter - timeBefore); + if (samples % 500 == 0 && samples < (500 * 8) + 50) { + System.out.println(avg); + totalOfAvgs += avg; + avgsTaken++; + } + if (samples == 500 * 8) { + System.out.println("Final Result: Oblog:" + useOblog + " DataLog:" + dataLog); + System.out.println(totalOfAvgs / avgsTaken); + } + field.getRobotObject().setPose(new Pose2d(samples / 100.0, 0, new Rotation2d())); + // m_internals.forEach(Internal::update); + put("stringValue", samples + ""); + SmartDashboard.putBoolean(getPath(), dataLog); + } + + @Override + public void autonomousInit() {} + + @Override + public void autonomousPeriodic() {} + + @Override + public void teleopInit() {} + + @Override + public void teleopPeriodic() {} + + @Override + public void disabledInit() {} + + @Override + public void disabledPeriodic() {} + + @Override + public void testInit() {} + + @Override + public void testPeriodic() {} + + @Override + public void simulationInit() {} + + @Override + public void simulationPeriodic() {} + + @Override + public String getPath() { + // TODO Auto-generated method stub + return "Robot"; + } +} diff --git a/test-project/vendordeps/WPILibNewCommands.json b/test-project/vendordeps/WPILibNewCommands.json new file mode 100644 index 0000000..67bf389 --- /dev/null +++ b/test-project/vendordeps/WPILibNewCommands.json @@ -0,0 +1,38 @@ +{ + "fileName": "WPILibNewCommands.json", + "name": "WPILib-New-Commands", + "version": "1.0.0", + "uuid": "111e20f7-815e-48f8-9dd6-e675ce75b266", + "frcYear": "2024", + "mavenUrls": [], + "jsonUrl": "", + "javaDependencies": [ + { + "groupId": "edu.wpi.first.wpilibNewCommands", + "artifactId": "wpilibNewCommands-java", + "version": "wpilib" + } + ], + "jniDependencies": [], + "cppDependencies": [ + { + "groupId": "edu.wpi.first.wpilibNewCommands", + "artifactId": "wpilibNewCommands-cpp", + "version": "wpilib", + "libName": "wpilibNewCommands", + "headerClassifier": "headers", + "sourcesClassifier": "sources", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "linuxathena", + "linuxarm32", + "linuxarm64", + "windowsx86-64", + "windowsx86", + "linuxx86-64", + "osxuniversal" + ] + } + ] +}