diff --git a/build-lib.bat b/build-lib.bat index d0bf11a..06629ed 100644 --- a/build-lib.bat +++ b/build-lib.bat @@ -6,6 +6,7 @@ cd lib @REM Build the distribution zip (used for the driverstation client) call gradlew.bat distZip @REM Build the maven repo (used for the test-bot and for any vendor dependencies) +rmdir /Q /S app\build\maven-repo\org\frc\raptors1711\raptors-claw call gradlew.bat publish cd .. diff --git a/lib/app/build.gradle b/lib/app/build.gradle index 8361394..fd0b5b4 100644 --- a/lib/app/build.gradle +++ b/lib/app/build.gradle @@ -85,6 +85,8 @@ dependencies { // Used by other dependencies implementation "edu.wpi.first.wpimath:wpimath-java:2023.1.1" + implementation "edu.wpi.first.hal:hal-java:2023.1.1" + implementation "edu.wpi.first.hal:hal-jni:2023.1.1" // Add org.fusesource.jansi // Used for console ANSI processing for driverstation-side RCT diff --git a/lib/app/build/distributions/driverstation-rct-client.zip b/lib/app/build/distributions/driverstation-rct-client.zip index 2a462df..59dbf7f 100644 Binary files a/lib/app/build/distributions/driverstation-rct-client.zip and b/lib/app/build/distributions/driverstation-rct-client.zip differ diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0-sources.jar b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0-sources.jar index d6b55c7..d9212a1 100644 Binary files a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0-sources.jar and b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0-sources.jar differ diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0-sources.jar.md5 b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0-sources.jar.md5 index a3fc1d5..2fd27e2 100644 --- a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0-sources.jar.md5 +++ b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0-sources.jar.md5 @@ -1 +1 @@ -f7be92f5ac76d7f011f43a4fbab9352a \ No newline at end of file +2a98f4603c63168315cd902fdea8ed8c \ No newline at end of file diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0-sources.jar.sha1 b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0-sources.jar.sha1 index ea4dee1..91bdeed 100644 --- a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0-sources.jar.sha1 +++ b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0-sources.jar.sha1 @@ -1 +1 @@ -94c6057efb26087e59d62bc642d50770cae0f2f3 \ No newline at end of file +45408af577b0daeb320a4061a63a1c8358d865ef \ No newline at end of file diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0-sources.jar.sha256 b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0-sources.jar.sha256 index d879608..cb57406 100644 --- a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0-sources.jar.sha256 +++ b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0-sources.jar.sha256 @@ -1 +1 @@ -cc0de2620d8b5d26842e4091d44d9848c9782fc261dbdc893eaecb36e666ff1c \ No newline at end of file +cf51b16c07af45d32ca8cf75785f093af125c18a334ce74c4154d450207f046c \ No newline at end of file diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0-sources.jar.sha512 b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0-sources.jar.sha512 index 30de1ed..d6afdba 100644 --- a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0-sources.jar.sha512 +++ b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0-sources.jar.sha512 @@ -1 +1 @@ -fe7f1e6c62a01bfd8d2d4ef4c7b814db7e818bb0ea0552dc80b6278d41765fc5955627bd1843083a43bf1b6db817afbd51a077ef496f82fd37bfb50ecb5f6e4e \ No newline at end of file +7a49dc68e04b31347589e9d450515505c6ad259c068d85b00536ac8369124278e500ea479201c7d3413d9121f729aeec87684e69225b1b2829544b443ef6ba49 \ No newline at end of file diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.jar b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.jar index 6b1039b..999dc31 100644 Binary files a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.jar and b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.jar differ diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.jar.md5 b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.jar.md5 index 8895cbf..d550802 100644 --- a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.jar.md5 +++ b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.jar.md5 @@ -1 +1 @@ -c64437c4e3855dfdbbc48ce9634a60bb \ No newline at end of file +69c7395c4e981d1c9876168dfbad12e3 \ No newline at end of file diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.jar.sha1 b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.jar.sha1 index 58eb966..85ad585 100644 --- a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.jar.sha1 +++ b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.jar.sha1 @@ -1 +1 @@ -bb5fa500ca0622333860118f2ad73d77621d4ad6 \ No newline at end of file +fa571001cca7fc6b1b6a3a5cb904b877c00dccf3 \ No newline at end of file diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.jar.sha256 b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.jar.sha256 index a76d8fd..683158f 100644 --- a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.jar.sha256 +++ b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.jar.sha256 @@ -1 +1 @@ -41050bf4db962f80a1e2456ad2def64d4851aac0173987f0cfec1b3a65c7aa3d \ No newline at end of file +5639e1fcd244f5a278d31d39521d48fa862b7fdd5c52c20a5fff82e9f694ab67 \ No newline at end of file diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.jar.sha512 b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.jar.sha512 index 107bcb6..393dec9 100644 --- a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.jar.sha512 +++ b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.jar.sha512 @@ -1 +1 @@ -25bee8a2b2221c1544b9860de60442c8cf77efed71e73f6f168c05ec812308b9c78262debc086cdf77426e26544b10be4af823118e25fc820b0f5962f65c3db7 \ No newline at end of file +cc5581befe8bad65b269aab28a848429ff746c62df122529eb02ef629d61803213c7c3139e9b2a091db3b3c5aec5aa5b35eaa0a98c3d7ee4c36cc3458cae1d21 \ No newline at end of file diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.module b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.module index 7d5ab99..c96f3a3 100644 --- a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.module +++ b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.module @@ -27,11 +27,11 @@ { "name": "raptors-claw-0.jar", "url": "raptors-claw-0.jar", - "size": 113205, - "sha512": "25bee8a2b2221c1544b9860de60442c8cf77efed71e73f6f168c05ec812308b9c78262debc086cdf77426e26544b10be4af823118e25fc820b0f5962f65c3db7", - "sha256": "41050bf4db962f80a1e2456ad2def64d4851aac0173987f0cfec1b3a65c7aa3d", - "sha1": "bb5fa500ca0622333860118f2ad73d77621d4ad6", - "md5": "c64437c4e3855dfdbbc48ce9634a60bb" + "size": 143380, + "sha512": "cc5581befe8bad65b269aab28a848429ff746c62df122529eb02ef629d61803213c7c3139e9b2a091db3b3c5aec5aa5b35eaa0a98c3d7ee4c36cc3458cae1d21", + "sha256": "5639e1fcd244f5a278d31d39521d48fa862b7fdd5c52c20a5fff82e9f694ab67", + "sha1": "fa571001cca7fc6b1b6a3a5cb904b877c00dccf3", + "md5": "69c7395c4e981d1c9876168dfbad12e3" } ] }, @@ -87,6 +87,20 @@ "requires": "2023.1.1" } }, + { + "group": "edu.wpi.first.hal", + "module": "hal-java", + "version": { + "requires": "2023.1.1" + } + }, + { + "group": "edu.wpi.first.hal", + "module": "hal-jni", + "version": { + "requires": "2023.1.1" + } + }, { "group": "org.fusesource.jansi", "module": "jansi", @@ -120,11 +134,11 @@ { "name": "raptors-claw-0.jar", "url": "raptors-claw-0.jar", - "size": 113205, - "sha512": "25bee8a2b2221c1544b9860de60442c8cf77efed71e73f6f168c05ec812308b9c78262debc086cdf77426e26544b10be4af823118e25fc820b0f5962f65c3db7", - "sha256": "41050bf4db962f80a1e2456ad2def64d4851aac0173987f0cfec1b3a65c7aa3d", - "sha1": "bb5fa500ca0622333860118f2ad73d77621d4ad6", - "md5": "c64437c4e3855dfdbbc48ce9634a60bb" + "size": 143380, + "sha512": "cc5581befe8bad65b269aab28a848429ff746c62df122529eb02ef629d61803213c7c3139e9b2a091db3b3c5aec5aa5b35eaa0a98c3d7ee4c36cc3458cae1d21", + "sha256": "5639e1fcd244f5a278d31d39521d48fa862b7fdd5c52c20a5fff82e9f694ab67", + "sha1": "fa571001cca7fc6b1b6a3a5cb904b877c00dccf3", + "md5": "69c7395c4e981d1c9876168dfbad12e3" } ] }, @@ -140,11 +154,11 @@ { "name": "raptors-claw-0-sources.jar", "url": "raptors-claw-0-sources.jar", - "size": 71904, - "sha512": "fe7f1e6c62a01bfd8d2d4ef4c7b814db7e818bb0ea0552dc80b6278d41765fc5955627bd1843083a43bf1b6db817afbd51a077ef496f82fd37bfb50ecb5f6e4e", - "sha256": "cc0de2620d8b5d26842e4091d44d9848c9782fc261dbdc893eaecb36e666ff1c", - "sha1": "94c6057efb26087e59d62bc642d50770cae0f2f3", - "md5": "f7be92f5ac76d7f011f43a4fbab9352a" + "size": 87408, + "sha512": "7a49dc68e04b31347589e9d450515505c6ad259c068d85b00536ac8369124278e500ea479201c7d3413d9121f729aeec87684e69225b1b2829544b443ef6ba49", + "sha256": "cf51b16c07af45d32ca8cf75785f093af125c18a334ce74c4154d450207f046c", + "sha1": "45408af577b0daeb320a4061a63a1c8358d865ef", + "md5": "2a98f4603c63168315cd902fdea8ed8c" } ] } diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.module.md5 b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.module.md5 index 00cb944..ab5f13a 100644 --- a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.module.md5 +++ b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.module.md5 @@ -1 +1 @@ -b0fc064dd4e57caccd34e492db097cd4 \ No newline at end of file +2b7c30da23792f8bb8e99cac0d2fcce8 \ No newline at end of file diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.module.sha1 b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.module.sha1 index 5ce806b..77e058b 100644 --- a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.module.sha1 +++ b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.module.sha1 @@ -1 +1 @@ -da4aa36ea816d3804b06ebf152431494f655656a \ No newline at end of file +dbe5d06ba94a5718b942b99ad8b1775ca3ab2bab \ No newline at end of file diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.module.sha256 b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.module.sha256 index 26a0f10..b0fd3be 100644 --- a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.module.sha256 +++ b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.module.sha256 @@ -1 +1 @@ -09b794a92a2a4d97c16d99ae7071b4007290f898bed7f4781b0253fb5b4011b9 \ No newline at end of file +4e145cf6795a3cbb0ca471c72fe20ef7bba3cf7ac7551095eded4b73f1cfa883 \ No newline at end of file diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.module.sha512 b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.module.sha512 index 1619a77..892f21e 100644 --- a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.module.sha512 +++ b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.module.sha512 @@ -1 +1 @@ -0476cd79862afcdb30762d775b5d37753cd58fe3c35439e2198fa915b396284ff45ebe73121f7a0fc413d15fe6e7ef15e8967bb1ba2c67978c9bd66ef125344e \ No newline at end of file +99dd9f4b385f74ed29298a341963ca0ebf14e447454713f0cf7db679b18ed8e38665129b3a8181bcc03970429a5287a8a83fe3069303179894ee0f5098f5315a \ No newline at end of file diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.pom b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.pom index f1a0d52..eb3f063 100644 --- a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.pom +++ b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.pom @@ -49,6 +49,18 @@ 2023.1.1 runtime + + edu.wpi.first.hal + hal-java + 2023.1.1 + runtime + + + edu.wpi.first.hal + hal-jni + 2023.1.1 + runtime + org.fusesource.jansi jansi diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.pom.md5 b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.pom.md5 index ff6b772..91c8ee9 100644 --- a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.pom.md5 +++ b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.pom.md5 @@ -1 +1 @@ -a875c60bcc81513eb2da3f571a22fc25 \ No newline at end of file +e02ff13651d2c72425dd2e1317c719fb \ No newline at end of file diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.pom.sha1 b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.pom.sha1 index 178fabd..9562a0e 100644 --- a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.pom.sha1 +++ b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.pom.sha1 @@ -1 +1 @@ -e53e55f8df7b668d00bc63f023be1fa87e3341c3 \ No newline at end of file +e0aa31056f00965a658913cd0727081b875e0be8 \ No newline at end of file diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.pom.sha256 b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.pom.sha256 index 7890ef8..32c13bc 100644 --- a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.pom.sha256 +++ b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.pom.sha256 @@ -1 +1 @@ -814fad6c4d5e1204b77f01926947061d9974cec316dd58847c537463719e7bb9 \ No newline at end of file +ba87785d734ee69db697ddccabc4b796704d2d83808dfa7c66df5546d371c8e2 \ No newline at end of file diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.pom.sha512 b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.pom.sha512 index 5266655..369e832 100644 --- a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.pom.sha512 +++ b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/0/raptors-claw-0.pom.sha512 @@ -1 +1 @@ -fb1c6d465f43f2a19a96e10689739116a2d9db345fc148eb21b3e98a9ad7da829911208386cd9c31ff3e4adbc1eb54bcf3eaae4fdf6bee7f0e2376ae5fcf8c3a \ No newline at end of file +b9fb86f462200fac15cebace6413dcf3887c768c4a3cd5d12065ea41ce4a59a794a026bd00b201d2e9ab747c1f1cff800cbdf9afc03b265fe7edd032d03fc4b9 \ No newline at end of file diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/maven-metadata.xml b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/maven-metadata.xml index 0f2c98e..876ccf9 100644 --- a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/maven-metadata.xml +++ b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/maven-metadata.xml @@ -8,6 +8,6 @@ 0 - 20230223211415 + 20230313202232 diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/maven-metadata.xml.md5 b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/maven-metadata.xml.md5 index dd61e5b..1e83158 100644 --- a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/maven-metadata.xml.md5 +++ b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/maven-metadata.xml.md5 @@ -1 +1 @@ -ddd5917af0b7d7cb8178add75c5eac89 \ No newline at end of file +db1d12f0bc9179176520bd51c4e5d25e \ No newline at end of file diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/maven-metadata.xml.sha1 b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/maven-metadata.xml.sha1 index fb32640..705ad0d 100644 --- a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/maven-metadata.xml.sha1 +++ b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/maven-metadata.xml.sha1 @@ -1 +1 @@ -0bfb9526335c17b0cdc65c3192f8f22c8dd0077c \ No newline at end of file +1254df09615b3cd2a0b31493ecafc7cf4eb8c613 \ No newline at end of file diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/maven-metadata.xml.sha256 b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/maven-metadata.xml.sha256 index 85ab47a..4145cfb 100644 --- a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/maven-metadata.xml.sha256 +++ b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/maven-metadata.xml.sha256 @@ -1 +1 @@ -d2a64a5cfac32dca1136e654e98f6d1cf7d8b80d5e7f1c4acd1f21a1802c7744 \ No newline at end of file +a0550bca32e3a3204a52d8e2d91839ff3254ba7b2f5f801c23deed2cd8f68831 \ No newline at end of file diff --git a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/maven-metadata.xml.sha512 b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/maven-metadata.xml.sha512 index f35b560..242fe1e 100644 --- a/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/maven-metadata.xml.sha512 +++ b/lib/app/build/maven-repo/org/frc/raptors1711/raptors-claw/maven-metadata.xml.sha512 @@ -1 +1 @@ -7dd20974a1222fbfd1ee6291cd9eb11a0bbe17e0406cd85eb756ddd9c6a4237a99f4d81e9616bfa1c5d7d01d6318c4a3908dc0c58aa84661968781881a09e3c1 \ No newline at end of file +55e9af188bf935923a37bdf033f332be519dcf2e18b53ee1ffc189c2fef1100966f46df9b7c782119d832161a324f5c7a75903f5c3a9adb34d414e4fdbe3a91b \ No newline at end of file diff --git a/lib/app/src/main/java/claw/CLAWRobot.java b/lib/app/src/main/java/claw/CLAWRobot.java index 183fb22..8da9376 100644 --- a/lib/app/src/main/java/claw/CLAWRobot.java +++ b/lib/app/src/main/java/claw/CLAWRobot.java @@ -8,45 +8,55 @@ import claw.logs.LogHandler; import claw.rct.commands.CommandLineInterpreter; import claw.rct.remote.RCTServer; +import edu.wpi.first.wpilibj.Preferences; import edu.wpi.first.wpilibj.TimedRobot; import edu.wpi.first.wpilibj2.command.Command; import edu.wpi.first.wpilibj2.command.CommandScheduler; public class CLAWRobot { + // Runtime execution determined by preferences so that the user can control + // this through any NetorkTables client (so that if you turn the server off, + // you can still control this execution) + private static final String RUN_RCT_SERVER = "CLAW.RUN_RCT_SERVER", RCT_SERVER_PORT = "CLAW.RCT_SERVER_PORT"; + private static final int DEFAULT_SERVER_PORT = 5800; + private static final CommandLineInterpreter EXTENSIBLE_COMMAND_INTERPRETER = new CommandLineInterpreter(); + private static boolean hasStartedCompetition = false; + public static void startCompetition (TimedRobot robot, Runnable robotStartCompetition) { + // Do not call startCompetition more than once + if (hasStartedCompetition) + throw new RuntimeException("Cannot call startCompetition more than once"); + hasStartedCompetition = true; - initializeRuntime(); - robot.addPeriodic(CLAWRobot::robotPeriodic, TimedRobot.kDefaultPeriod); - - try { - robotStartCompetition.run(); - } catch (Throwable exception) { - handleFatalUncaughtException(exception); - onRobotCodeFinish(); - throw exception; + // Start the RCT server if indicated by preferences to do so + Preferences.initBoolean(RUN_RCT_SERVER, true); + Preferences.initInt(RCT_SERVER_PORT, DEFAULT_SERVER_PORT); + if (Preferences.getBoolean(RUN_RCT_SERVER, true)) { + startThread(CLAWRobot::initializeRCTServer); } + // Run until robot code finishes + runRobotCode(robot, robotStartCompetition); + } - - public static CommandLineInterpreter getExtensibleCommandInterpreter () { - return EXTENSIBLE_COMMAND_INTERPRETER; + + private static void startThread (Runnable thread) { + new Thread(thread).start(); } - + private static final CLAWLogger COMMANDS_LOG = CLAWLogger.getLogger("claw.commands"), RUNTIME_LOG = CLAWLogger.getLogger("claw.runtime"); - + private static RCTServer server; - private static boolean initialized = false; - private static void initializeRuntime () { - - if (initialized) return; - initialized = true; - + /** + * Start the robot code and the CLAW robot code runtime necessary for robot code functioning + */ + private static void runRobotCode (TimedRobot robot, Runnable robotStartCompetition) { // Put a message into the console indicating that the CLAWRobot runtime has started System.out.println("\n -- CLAW is running -- \n"); @@ -59,23 +69,34 @@ private static void initializeRuntime () { CommandScheduler.getInstance().onCommandFinish(CLAWRobot::onCommandFinish); CommandScheduler.getInstance().onCommandInterrupt(CLAWRobot::onCommandInterrupt); - // Start RCT server thread - new Thread(() -> { - try { - server = new RCTServer(5800, EXTENSIBLE_COMMAND_INTERPRETER); - server.start(); - } catch (IOException e) { - System.err.println("Failed to start RCT server."); - e.printStackTrace(); - } - }).start(); - + try { + // Add the periodic method for the CLAWRobot and call start competition within a try-catch + // loop to catch any exceptions + robot.addPeriodic(CLAWRobot::robotPeriodic, TimedRobot.kDefaultPeriod); + robotStartCompetition.run(); + } catch (Throwable exception) { + // Catch any uncaught robot exceptions + handleFatalUncaughtException(exception); + throw exception; + } } + /** + * Initialize the RCT server allowing for advanced debugging and control from the driver station + */ + private static void initializeRCTServer () { + // Start RCT server + try { + server = new RCTServer(Preferences.getInt(RCT_SERVER_PORT, DEFAULT_SERVER_PORT), EXTENSIBLE_COMMAND_INTERPRETER); + server.start(); + } catch (IOException e) { + System.err.println("Failed to start RCT server."); + e.printStackTrace(); + } + } - private static void onRobotCodeFinish () { - if (server != null) - LogHandler.getInstance().sendData(server); + public static CommandLineInterpreter getExtensibleCommandInterpreter () { + return EXTENSIBLE_COMMAND_INTERPRETER; } private static void robotPeriodic () { @@ -101,7 +122,12 @@ private static void onCommandInterrupt (Command command) { private static void handleUncaughtException (Thread thread, Throwable exception) { // Print to the driver station - System.err.println("Caught an uncaught exception: " + exception.getMessage()); + System.err.println( + "Caught an exception in the robot code: " + exception.getMessage()+". Use the " + + "'errlog' command in the Robot Control Terminal to examine it." + ); + + RobotErrorLog.logThreadError(exception); // Put to the logger RUNTIME_LOG.err("Uncaught exception in a thread '"+thread.getName()+"':\n"+getStackTrace(exception)); @@ -110,6 +136,8 @@ private static void handleUncaughtException (Thread thread, Throwable exception) private static void handleFatalUncaughtException (Throwable exception) { // Put to the logger RUNTIME_LOG.err("Fatal uncaught exception in robot code:\n"+getStackTrace(exception)); + + RobotErrorLog.logFatalError(exception); } private static String getStackTrace (Throwable e) { diff --git a/lib/app/src/main/java/claw/LiveValues.java b/lib/app/src/main/java/claw/LiveValues.java new file mode 100644 index 0000000..8bb36f2 --- /dev/null +++ b/lib/app/src/main/java/claw/LiveValues.java @@ -0,0 +1,135 @@ +package claw; + +import java.util.ArrayList; +import java.util.HashSet; + +import claw.rct.network.low.ConsoleManager; + +/** + * A class allowing for the live updating of fields to be displayed in the console. + */ +public class LiveValues { + + /** + * An object lock to be synchronized on before making any modifications to the fields, + * values, etc. + */ + private final Object fieldsLock = new Object(); + + private final ArrayList fields = new ArrayList<>(); + private final ArrayList values = new ArrayList<>(); + private final HashSet newFieldNames = new HashSet<>(); + private final HashSet updatedFields = new HashSet<>(); + + /** + * Set a live field to a string value to be displayed in the console. + * @param fieldName The name of the field under which the value should be displayed. + * @param value The value to put to the console. + */ + public void setField (String fieldName, String value) { + synchronized (fieldsLock) { + // Get the index of the field in the fields and values arrays + int fieldIndex = fields.indexOf(fieldName); + + if (fieldIndex == -1) { + + // Add a new field and value + newFieldNames.add(fieldName); + fields.add(fieldName); + values.add(value); + + } else if (!values.get(fieldIndex).equals(value)) { + + // Update the value only, but first check if it's the same as it was before + values.set(fieldIndex, value); + updatedFields.add(fieldName); + + } + } + } + + /** + * Set a live field to a double value to be displayed in the console. + * @param fieldName The name of the field under which the value should be displayed. + * @param value The value to put to the console. + */ + public void setField (String fieldName, double value) { + setField(fieldName, Double.toString(value)); + } + + /** + * Set a live field to a boolean value to be displayed in the console. + * @param fieldName The name of the field under which the value should be displayed. + * @param value The value to put to the console. + */ + public void setField (String fieldName, boolean value) { + setField(fieldName, Boolean.toString(value)); + } + + /** + * Set a live field to an integer value to be displayed in the console. + * @param fieldName The name of the field under which the value should be displayed. + * @param value The value to put to the console. + */ + public void setField (String fieldName, int value) { + setField(fieldName, Integer.toString(value)); + } + + /** + * Update all the fields in the console to display the latest values. + * @param console + */ + public void update (ConsoleManager console) { + synchronized (fieldsLock) { + + // Do nothing if no fields have been changed + if (updatedFields.size() == 0 && newFieldNames.size() == 0) { + return; + } + + // Move up to the top of the preexisting lines + int preexistingLines = fields.size() - newFieldNames.size(); + console.moveUp(preexistingLines); + + // Iterate through all existing fields + for (int i = 0; i < fields.size(); i ++) { + String fieldName = fields.get(i); + String value = values.get(i); + + // Check for no-change vs. updated vs. new field + if (newFieldNames.contains(fieldName)) { + + // New field + printField(console, fieldName, value); + + } else if (updatedFields.contains(fieldName)) { + + // Updated field + console.clearLine(); + printField(console, fieldName, value); + + } else { + + // No change to field + console.moveUp(-1); + + } + + } + + // Clear updated fields and new fields + newFieldNames.clear(); + updatedFields.clear(); + + } + } + + /** + * Print a single field to the console + */ + private static void printField (ConsoleManager console, String fieldName, String value) { + String space = " ".repeat(Math.max(0, 18 - fieldName.length())); + console.println(fieldName + " : " + space + value); + } + +} \ No newline at end of file diff --git a/lib/app/src/main/java/claw/RobotErrorLog.java b/lib/app/src/main/java/claw/RobotErrorLog.java new file mode 100644 index 0000000..e72b144 --- /dev/null +++ b/lib/app/src/main/java/claw/RobotErrorLog.java @@ -0,0 +1,114 @@ +package claw; + +import java.io.PrintWriter; +import java.io.Serializable; +import java.io.StringWriter; +import java.sql.Date; +import java.text.SimpleDateFormat; +import java.util.ArrayList; + +import claw.rct.commands.CommandProcessor; +import claw.rct.commands.CommandReader; +import claw.rct.commands.CommandProcessor.BadCallException; +import claw.rct.network.low.ConsoleManager; + +public class RobotErrorLog { + + private static record LoggableError (long timestamp, String text, ErrorType type) implements Serializable { } + + public static final CommandProcessor ERROR_LOG_COMMAND_PROCESSOR = new CommandProcessor( + "errlog", + "errlog", + "Use 'errlog' to view logged robot errors and warnings through the RobotErrorLog class.", + RobotErrorLog::errorLogCommand + ); + + private static final Setting> ERROR_LOG_SETTING = new Setting<>("CLAW.ERROR_LOG", () -> new ArrayList<>()); + private static final Object ERROR_LOG_LOCK = new Object(); + + private static void errorLogCommand (ConsoleManager console, CommandReader reader) throws BadCallException { + // Copy an array of errors from the error log setting + LoggableError[] errors; + synchronized (ERROR_LOG_LOCK) { + errors = ERROR_LOG_SETTING.get().toArray(new LoggableError[0]); + } + + if (errors.length > 0) { + console.println("There are "+errors.length+" logged error(s)."); + + for (LoggableError error : errors) { + console.println(" -- " + error.type.name + " -- "); + console.println(new SimpleDateFormat("MM/dd/yyyy hh:mm:ss a").format(new Date(error.timestamp))); + + String text = ConsoleManager.formatMessage(error.text); + + switch (error.type) { + case FATAL_ERROR: + case THREAD_ERROR: + console.printlnErr(text); + break; + case WARNING: + console.printlnSys(text); + break; + } + + console.println("\n"); + + } + + } else { + console.println("There are no logged errors."); + } + } + + private static void logError (LoggableError error) { + synchronized (ERROR_LOG_LOCK) { + ERROR_LOG_SETTING.get().add(error); + ERROR_LOG_SETTING.save(); + } + } + + /** + * Log a fatal error (an error which kills the robot code). This method for logging an error + * should only be used internally in CLAW. + * @param exception The exception to log. + */ + public static void logFatalError (Throwable exception) { + logError(new LoggableError(System.currentTimeMillis(), getStackTrace(exception), ErrorType.FATAL_ERROR)); + } + + /** + * Log an error which kills some thread of the robot code. This method for logging an error + * should only be used internally in CLAW. + * @param exception The exception to log. + */ + public static void logThreadError (Throwable exception) { + logError(new LoggableError(System.currentTimeMillis(), getStackTrace(exception), ErrorType.THREAD_ERROR)); + } + + /** + * Log a warning with a given description. + * @param description The warning's description. + */ + public static void logWarning (String description) { + logError(new LoggableError(System.currentTimeMillis(), description, ErrorType.WARNING)); + } + + private static String getStackTrace (Throwable e) { + StringWriter stringWriter = new StringWriter(); + e.printStackTrace(new PrintWriter(stringWriter)); + return stringWriter.toString(); + } + + private enum ErrorType { + FATAL_ERROR ("Fatal Error"), + THREAD_ERROR ("Thread Error"), + WARNING ("Warning"); + + private final String name; + private ErrorType (String name) { + this.name = name; + } + } + +} diff --git a/lib/app/src/main/java/claw/hardware/AbsoluteEncoderDevice.java b/lib/app/src/main/java/claw/hardware/AbsoluteEncoderDevice.java deleted file mode 100644 index af932d2..0000000 --- a/lib/app/src/main/java/claw/hardware/AbsoluteEncoderDevice.java +++ /dev/null @@ -1,68 +0,0 @@ -package claw.hardware; - -import claw.Setting; -import edu.wpi.first.math.geometry.Rotation2d; - -/** - * A device wrapper for an absolute encoder, allowing for reading from the encoder and configuring its - * offset from zero. - */ -public class AbsoluteEncoderDevice extends Device { - - private final Setting offsetSetting; - private final EncoderReader encoderReader; - - /** - * Create a new {@link AbsoluteEncoderDevice}. - * @param deviceName The unique identifier for the device. - * @param initializer The initializer for the device. - * @param finalizer The finalizer for the device. - * @param offsetSetting A {@link Setting} field which can be used to store the encoder's absolute offset. - * @param encoderReader A function which can read a {@link Rotation2d} from a provided encoder device. - * @see Device#Device(String, DeviceInitializer, DeviceFinalizer) - */ - public AbsoluteEncoderDevice ( - String deviceName, - DeviceInitializer initializer, - DeviceFinalizer finalizer, - Setting offsetSetting, - EncoderReader encoderReader - ) { - super(deviceName, initializer, finalizer); - this.offsetSetting = offsetSetting; - this.encoderReader = encoderReader; - } - - /** - * An functional interface which reads a rotation from an encoder. - */ - @FunctionalInterface - public static interface EncoderReader { - /** - * Read a rotation from a given encoder device. - * @param encoderDevice The encoder which the rotation should be read from. - * @return The {@link Rotation2d} representing the encoder's current reading. - */ - public Rotation2d getRotation (T encoderDevice); - } - - /** - * Get the absolute rotation of the encoder after accounting for the configured offset. - * @return A {@link Rotation2d} describing the state of the encoder. - */ - public Rotation2d getRotationMeasurement () { - // Reading = Supplier + Offset - return Rotation2d.fromDegrees(encoderReader.getRotation(get()).getDegrees() + offsetSetting.get()); - } - - /** - * Configures the saved offset so that the reading of the absolute encoder using {@link #getRotationMeasurement()} will, - * at this position, be equal to the provided {@code targetRotation}. - * @param targetRotation What this encoder should be reading at the current position. - */ - public void configureOffset (Rotation2d targetRotation) { - // Offset = Reading - Supplier - offsetSetting.set(targetRotation.getDegrees() - encoderReader.getRotation(get()).getDegrees()); - } - -} diff --git a/lib/app/src/main/java/claw/hardware/DIOReadCommand.java b/lib/app/src/main/java/claw/hardware/DIOReadCommand.java new file mode 100644 index 0000000..f1e103d --- /dev/null +++ b/lib/app/src/main/java/claw/hardware/DIOReadCommand.java @@ -0,0 +1,78 @@ +package claw.hardware; + +import java.util.Optional; + +import claw.LiveValues; +import claw.rct.commands.CommandProcessor; +import claw.rct.commands.CommandReader; +import claw.rct.commands.CommandProcessor.BadCallException; +import claw.rct.network.low.ConsoleManager; +import edu.wpi.first.hal.util.AllocationException; +import edu.wpi.first.wpilibj.DigitalInput; + +public class DIOReadCommand { + + private static final int DIO_PORTS_TOTAL = 10; + + public static final CommandProcessor DIO_RAW_COMMAND_PROCESSOR = new CommandProcessor( + "dioraw", + "dioraw", + "Use 'dioraw' to enumerate DIO ports and list their current values. This may be helpful to distinguish between different " + + "devices if cables are poorly labeled on the roboRIO, or to quickly tell if a device is functioning properly.", + DIOReadCommand::dioRawCommandFunction + ); + + private static void dioRawCommandFunction (ConsoleManager console, CommandReader reader) throws BadCallException { + reader.allowNone(); + + DIOPort[] ports = new DIOPort[DIO_PORTS_TOTAL]; + for (int i = 0; i < ports.length; i ++) { + ports[i] = new DIOPort(i); + } + + LiveValues values = new LiveValues(); + + while (!console.hasInputReady()) { + + for (int i = 0; i < ports.length; i ++) { + values.setField("DIO["+i+"]", ports[i].toString()); + } + + values.update(console); + + } + + for (int i = 0; i < ports.length; i ++) { + ports[i].free(); + } + + } + + private static class DIOPort { + + private Optional digitalInput = Optional.empty(); + + private DIOPort (int port) { + try { + digitalInput = Optional.of(new DigitalInput(port)); + } catch (AllocationException e) { + // Ignore AllocationExceptions + } + } + + @Override + public String toString () { + if (digitalInput.isEmpty()) { + return "Port unavailable (already allocated)"; + } else { + return Boolean.toString(digitalInput.get().get()); + } + } + + public void free () { + digitalInput.ifPresent(DigitalInput::close); + } + + } + +} diff --git a/lib/app/src/main/java/claw/hardware/Device.java b/lib/app/src/main/java/claw/hardware/Device.java deleted file mode 100644 index 6e07087..0000000 --- a/lib/app/src/main/java/claw/hardware/Device.java +++ /dev/null @@ -1,252 +0,0 @@ -package claw.hardware; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import claw.Setting; -import claw.logs.CLAWLogger; - -/** - * A thread-safe wrapper around any hardware components which are connected to the roboRIO on a certain port or with a given ID. - * The core functionality of the {@code Device} wrapper is initializing a device given an ID. IDs will be read from CLAW settings, - * so they can be set via the Robot Control Terminal. - *
- * Examples of good components to use with this device wrapper could be resources connected to the roboRIO via CAN, DIO, or PWM (e.g. - * motor controllers, limit switches, CANCoders, or anything else which has some sort of integer ID). - */ -public class Device { - - private static final CLAWLogger LOG = CLAWLogger.getLogger("claw.devices"); - - /** - * A map of unique device names onto their set IDs, which is saved to the roboRIO so that the IDs are persistent. - */ - private static final Setting> SAVED_DEVICE_NAMES_TO_IDS = new Setting<>("claw.devices", HashMap::new); - - /** - * A map of existing device names onto devices. - */ - private static final HashMap> NAMES_TO_DEVICES = new HashMap<>(); - - private final String deviceName; - private final DeviceInitializer initializer; - private final DeviceFinalizer finalizer; - - /** - * The currently instantiated device resource (like a motor controller or digital input, for example). - */ - private Optional device = Optional.empty(); - - /** - * A lock for {@code device} so that it isn't used in more than one thread at a time. - */ - private final Object deviceLock = new Object(); - - /** - * Create a new device wrapper with a given initializer, finalizer, and name. - * @param deviceName A unique, user-friendly string which can be used to identify this device. - * No two {@link Device}s should be initialized with the same {@code deviceName} throughout the runtime - * of a program. Also, it should be noted that if this name changes for a particular device, you will need - * to reset its ID through the robot control terminal. It is common to use the format {@code "PORTTYPE.DEVTYPE.SUBSYSTEM.IDENTIFIER"}, where - * PORTTYPE is the ID system to use (e.g. CAN, DIO, PWM), DEVTYPE is the type of device used (e.g. motor controller, limit switch), - * SUBSYSTEM is the name of the subsystem the device belongs to, and IDENTIFIER is name which will distinguish the - * device from other similar devices in the subsystem (based on location or purpose). - * A sample {@code deviceName} could be {@code "CAN.ENCODER.SWERVE.FRONT_LEFT"}. - * @param initializer A {@link DeviceInitializer} which accepts an integer device ID and returns the underlying - * device object (e.g. a motor controller or digital input). It may be useful to also apply some basic settings to - * the device in this initializer, such as setting a motor controller's default brake mode to coast, or resetting - * a relative encoder. - * @param finalizer A {@link DeviceFinalizer} which finalizes a device before and puts it in a safe state before - * the resource is released (e.g. stopping a motor controller). It may be possible that the finalizer will perform no - * operations on the device. - */ - public Device (String deviceName, DeviceInitializer initializer, DeviceFinalizer finalizer) { - - // Handle the NAMES_TO_DEVICES set - synchronized (NAMES_TO_DEVICES) { - // Throw an exception if a device already has the provided name - if (NAMES_TO_DEVICES.containsKey(deviceName)) - throw new IllegalArgumentException("A device has already been instantiated with the name '"+deviceName+"'"); - - // Add the device name to the set - NAMES_TO_DEVICES.put(deviceName, this); - } - - // Set instance fields - this.deviceName = deviceName; - this.initializer = initializer; - this.finalizer = finalizer; - } - - /** - * Get all instantiated devices' names. This may or may not differ from the - * set of device names associated with a saved ID. - * @return The {@code Set} of all instantiated {@link Device}s' names. - * @see #getAllSavedDeviceIDs() - */ - @SuppressWarnings("unchecked") - public static Set getAllDeviceNames () { - synchronized (NAMES_TO_DEVICES) { - return ((HashMap>)NAMES_TO_DEVICES.clone()).keySet(); - } - } - - /** - * Get a map of device names onto device IDs representing what is currently saved to the roboRIO. The - * device names may or may not differ from the set of names belonging to devices which have actually - * been instantiated. - * @return A {@code Map} read from the roboRIO, mapping device names onto - * saved device IDs. - * @see #getAllDeviceNames() - */ - @SuppressWarnings("unchecked") - public static Map getAllSavedDeviceIDs () { - synchronized (SAVED_DEVICE_NAMES_TO_IDS) { - return (Map)SAVED_DEVICE_NAMES_TO_IDS.get().clone(); - } - } - - /** - * Clear all device names and IDs saved to the roboRIO. - * @return {@code true} if the save was successful, {@code false} otherwise. - */ - public static boolean clearAllSavedIDs () { - synchronized (SAVED_DEVICE_NAMES_TO_IDS) { - SAVED_DEVICE_NAMES_TO_IDS.get().clear(); - return SAVED_DEVICE_NAMES_TO_IDS.save(); - } - } - - /** - * Attempt to save a new integer ID to a particular device. This will be saved to the roboRIO such that the - * device ID will be persistent between program instances (i.e. if you restart your robot code or turn the robot off, - * the device ID will still be saved with the given value). Notably, the change in ID will not take effect on any existing - * {@link Device} instances. In order for devices IDs to be refreshed according to their saved values, use {@link #reinitialize()}. - * If {@code id} is {@code Optional.empty()}, then the device's saved ID will be cleared. - * @param deviceName The device's unique name. - * @param id An {@link Optional} containing The new device ID. If the optional is empty, the device's saved ID will be cleared. - * @return {@code true} if the ID was successfully saved, {@code false} otherwise. - */ - public static boolean saveDeviceID (String deviceName, Optional id) { - synchronized (SAVED_DEVICE_NAMES_TO_IDS) { - - if (id.isPresent()) { - // If an ID was supplied, save the new value - SAVED_DEVICE_NAMES_TO_IDS.get().put(deviceName, id.get()); - } else { - // If no ID was supplied, clear the entry - SAVED_DEVICE_NAMES_TO_IDS.get().remove(deviceName); - } - - return SAVED_DEVICE_NAMES_TO_IDS.save(); - } - } - - /** - * Get the saved ID for a particular device's name. - * @param deviceName The device name to retrieve the ID for. - * @return The device's saved ID, if it could be found. - */ - public static Optional getDeviceId (String deviceName) { - synchronized (SAVED_DEVICE_NAMES_TO_IDS) { - // Retrieve the boxed Integer ID from the settings, and convert to an optional - return Optional.ofNullable(SAVED_DEVICE_NAMES_TO_IDS.get().get(deviceName)); - } - } - - /** - * Call {@link #reinitialize()} on all existing devices. Note that this could cause damage - * to the robot if called while devices are being operated. Ensure the robot is disabled before - * calling this method. - */ - public static void reinitializeAllDevices () { - synchronized (NAMES_TO_DEVICES) { - for (Device device : NAMES_TO_DEVICES.values()) { - device.reinitialize(); - } - } - } - - /** - * Initialize a new device according to the ID in settings - */ - private T initializeDevice () { - Optional id = getDeviceId(deviceName); - - // If the ID was not found in the settings, log a warning - if (id.isEmpty()) - LOG.out("Warning: No device ID was found for device name '"+deviceName+"', defaulting to 0"); - - // Return the device initialized with the given ID - return initializer.initializeDevice(id.orElse(0)); - } - - /** - * Retrieve the underlying resource belonging to this {@link Device}. If the device resource has already been initialized according - * to the ID saved to the CLAW settings, then it will not be reinitialized. To reinitialize the device, use {@link #reinitialize()}. - * @return The underlying resource (e.g. a motor controller or digital input). - */ - public T get () { - synchronized (deviceLock) { - // If the device has not yet been initialized, initialize it - if (device.isEmpty()) - device = Optional.of(initializeDevice()); - - // Return the new device - return device.get(); - } - } - - /** - * Reinitialize the device resource according to the ID saved to the CLAW settings. This should only be called - * when the device isn't actively being operated. - */ - public void reinitialize () { - synchronized (deviceLock) { - // Finalize the device if it exists - if (device.isPresent()) - finalizer.finalizeDevice(device.get()); - - // Initialize the new device - device = Optional.of(initializeDevice()); - } - } - - /** - * Get the unique name associated with this device. - * @return The device's name. - */ - public String getName () { - return deviceName; - } - - /** - * A functional interface which handles initializing a particular device given an integer ID. - * Generally, this is something like a device connected to the roboRIO via CAN, DIO, or PWM. - */ - @FunctionalInterface - public static interface DeviceInitializer { - /** - * Retrieve and initialize a device of type {@code T} given an integer ID. - * @param id The ID of the device (potentially a CAN ID, DIO port, etc.) - * @return The device. - */ - public T initializeDevice (int id); - } - - /** - * A functional interface which handles finalizing a device (i.e. putting it into a safe state - * and releasing any underlying resources). - */ - @FunctionalInterface - public static interface DeviceFinalizer { - /** - * Finalize the device such that it is in a safe state and any associated resources are released. - * @param device The device to finalize. - */ - public void finalizeDevice (T device); - } - -} diff --git a/lib/app/src/main/java/claw/hardware/LimitSwitchDevice.java b/lib/app/src/main/java/claw/hardware/LimitSwitchDevice.java index a68529c..bb2a14f 100644 --- a/lib/app/src/main/java/claw/hardware/LimitSwitchDevice.java +++ b/lib/app/src/main/java/claw/hardware/LimitSwitchDevice.java @@ -3,19 +3,19 @@ import edu.wpi.first.wpilibj.DigitalInput; /** - * An implementation of {@link Device} for limit switches connected via DIO. + * A wrapper around {@link DigitalInput} for reading a value from a limit switch. */ -public class LimitSwitchDevice extends Device { +public class LimitSwitchDevice implements AutoCloseable { + private final DigitalInput limitSwitchInput; private final NormalState normalState; /** * Create a new {@link LinkSwitchDevice}. - * @param deviceName The unique device name. * @param normalState The {@link NormalState} of the limit switch's circuit. */ - public LimitSwitchDevice (String deviceName, NormalState normalState) { - super(deviceName, DigitalInput::new, DigitalInput::close); + public LimitSwitchDevice (DigitalInput limitSwitchInput, NormalState normalState) { + this.limitSwitchInput = limitSwitchInput; this.normalState = normalState; } @@ -24,7 +24,7 @@ public LimitSwitchDevice (String deviceName, NormalState normalState) { * @return {@code true} if the limit switch is pressed. */ public boolean isPressed () { - return (normalState == NormalState.NORMALLY_OPEN) == get().get(); + return (normalState == NormalState.NORMALLY_OPEN) != limitSwitchInput.get(); } /** @@ -33,17 +33,22 @@ public boolean isPressed () { public enum NormalState { /** * For a normally open limit switch, if the limit switch is not pressed, the circuit will be open, - * and so a low signal will be received through DIO. Because of this, if the DIO is unplugged, - * the input to the roboRIO will indicate that the limit switch is not pressed. + * and so a high signal (5V) will be received through DIO. Because of this, if the DIO is unplugged, + * the input to the roboRIO will indicate that the limit switch is pressed. */ NORMALLY_OPEN, /** * For a normally closed limit switch, if the limit switch is not pressed, the circuit will be closed, - * and so a high signal will be received through DIO. Because of this, if the DIO is unplugged, - * the input to the roboRIO will indicate that the limit switch is pressed. + * and so a low signal (0V) will be received through DIO. Because of this, if the DIO is unplugged, + * the input to the roboRIO will indicate that the limit switch is not pressed. */ NORMALLY_CLOSED, } + @Override + public void close () { + limitSwitchInput.close(); + } + } diff --git a/lib/app/src/main/java/claw/hardware/can/CANMessageID.java b/lib/app/src/main/java/claw/hardware/can/CANMessageID.java new file mode 100644 index 0000000..f10c08e --- /dev/null +++ b/lib/app/src/main/java/claw/hardware/can/CANMessageID.java @@ -0,0 +1,267 @@ +package claw.hardware.can; + +/** + * Represents the arbitration ID of a message received over CAN. See the FRC CAN Device Specifications for details on formatting: + * https://docs.wpilib.org/en/stable/docs/software/can-devices/can-addressing.html + */ +public record CANMessageID (byte deviceNum, byte apiIndex, byte apiClass, ManufacturerCode manufacturer, DeviceType deviceType) { + + /** + * Parse a {@link CANMessageID} from an integer message ID. + * @param fullArbitrationId The full message ID / arbitration ID. + * @return The parsed {@code CANMessageID}. + */ + public static CANMessageID fromMessageId (int fullArbitrationId) { + + // Break apart the arbitration ID into its components: + int[] bitSliceSizes = new int[] { + 6, // Device number + 4, // API Index + 6, // API Class + 8, // Manufacturer Code + 5, // Device Type + }; + + // Get the binary slices of the message ID corresponding with each useful segment (device number, api index, etc.) + int[] bitSlices = getBinarySlices(fullArbitrationId, bitSliceSizes); + + // Get the byte fields + byte deviceNum = (byte)bitSlices[0]; + byte apiIndex = (byte)bitSlices[1]; + byte apiClass = (byte)bitSlices[2]; + + // Parse a ManufacturerCode from bitSlices[3] + ManufacturerCode manufacturer = getEnumFromId( + ManufacturerCode.class, + bitSlices[3], + ManufacturerCode.UNKNOWN + ); + + // Parse a DeviceType from bitSlices[4] + DeviceType deviceType = getEnumFromId( + DeviceType.class, + bitSlices[4], + DeviceType.UNKNOWN + ); + + return new CANMessageID(deviceNum, apiIndex, apiClass, manufacturer, deviceType); + + } + + /** + * Get an array of binary slices from a value. + * @param value The value to take binary slices from + * @param sliceSizes The size of each slice. {@code slice[0]} will be taken from the far right of the value. + * @return An array of binary slices corresponding with the given {@code sliceSizes} array + */ + private static int[] getBinarySlices (int value, int[] sliceSizes) { + int[] bitSlices = new int[sliceSizes.length]; + + int startIndex = 0; + for (int i = 0; i < bitSlices.length; i ++) { + bitSlices[i] = getBinarySliceFrom(value, startIndex, sliceSizes[i]); + startIndex += sliceSizes[i]; + } + + return bitSlices; + } + + /** + * Gets a binary slice from a given value. + * @param value The value to retrieve the binary slice from. + * @param startIndex The index of the first bit to retrieve, starting at the 1's place (index 0). + * @param length The number of bits to retrieve from the given {@code value}. + * @return The section of bits retrieved from the {@code value}, shifted so the 1's place + * contains the rightmost bit in the slice. + */ + private static int getBinarySliceFrom (int value, int startIndex, int length) { + // This creates an int bitMask which, when viewed in binary representation, + // looks like 1111110000, where the parameter "length" describes the number of + // 1 bits, and the parameter "startIndex" describes the number of 0 bits + int bitMask = ~(-1 << length) << startIndex; + + // The bitMask can be ANDed with "value" to get only the bits we want from it. + // We shift the bits back to the right to undo the "startIndex" + int maskedNum = (bitMask & value) >>> startIndex; + + return maskedNum; + } + + /** + * Internal, helper interface. Useful for finding enum options that correspond with some given integer ID + */ + private interface EnumWithID { + public int getMinId (); + public int getMaxId (); + } + + /** + * Helps to find an enum option which corresponds with some given integer ID + */ + private static T getEnumFromId (Class enumClass, int id, T defaultOption) { + T[] enumOptions = enumClass.getEnumConstants(); + for (int i = 0; i < enumOptions.length; i ++) { + if (id >= enumOptions[i].getMinId() && id <= enumOptions[i].getMaxId()) { + return enumOptions[i]; + } + } + + return defaultOption; + } + + /** + * A named device type according to the FRC CAN Device Specifications. + */ + public static enum DeviceType implements EnumWithID { + BROADCAST_MESSAGES (0), + ROBOT_CONTROLLER (1), + MOTOR_CONTROLLER (2), + RELAY_CONTROLLER (3), + GYRO_SENSOR (4), + ACCELEROMETER (5), + ULTRASONIC_SENSOR (6), + GEAR_TOOTH_SENSOR (7), + POWER_DISTRIBUTION_MODULE (8), + PNEUMATICS_CONTROLLER (9), + MISCELLANEOUS (10), + IO_BREAKOUT (11), + RESERVED (12, 30), + FIRMWARE_UPDATE (31), + UNKNOWN (-1); + + @Override + public int getMinId () { + return minId; + } + + @Override + public int getMaxId () { + return maxId; + } + + private final int minId, maxId; + private DeviceType (int num) { + minId = num; + maxId = num; + } + + private DeviceType (int minId, int maxId) { + this.minId = minId; + this.maxId = maxId; + } + } + + /** + * A manufacturer according to the FRC CAN Device Specifications. + */ + public static enum ManufacturerCode implements EnumWithID { + BROADCAST ( + "[Broadcast]", + 0 + ), + + NI ( + "National Instruments", + 1 + ), + + LUMINARY_MICRO ( + "Luminary Micro", + 2 + ), + + DEKA ( + "DEKA Research and Development", + 3 + ), + + CTR_ELECTRONICS ( + "Cross the Road Electronics", + 4 + ), + + REV_ROBOTICS ( + "REV Robotics", + 5 + ), + + GRAPPLE ( + "Grapple", + 6 + ), + + MIND_SENSORS ( + "MindSensors", + 7 + ), + + TEAM_USE ( + "[Team Use]", + 8 + ), + + KAUAI_LABS ( + "Kauai Labs", + 9 + ), + + COPPERFORGE ( + "Copperforge", + 10 + ), + + PLAYING_WITH_FUSION ( + "Playing With Fusion", + 11 + ), + + STUDICA ( + "Studica", + 12 + ), + + THE_THRIFTY_BOT ( + "The Thrifty Bot", + 13 + ), + + RESERVED ( + "[Reserved]", + 14, 255 + ), + + UNKNOWN ( + "[Unknown]", + -1 + ); + + @Override + public int getMinId () { + return minId; + } + + @Override + public int getMaxId () { + return maxId; + } + + private final int minId, maxId; + + /** + * A more readable name for the device type than the enum name. + */ + public final String friendlyName; + private ManufacturerCode (String name, int num) { + this.friendlyName = name; + minId = num; + maxId = num; + } + + private ManufacturerCode (String name, int minId, int maxId) { + this.friendlyName = name; + this.minId = minId; + this.maxId = maxId; + } + } + +} diff --git a/lib/app/src/main/java/claw/hardware/can/CANScanner.java b/lib/app/src/main/java/claw/hardware/can/CANScanner.java new file mode 100644 index 0000000..fd72ba9 --- /dev/null +++ b/lib/app/src/main/java/claw/hardware/can/CANScanner.java @@ -0,0 +1,215 @@ +package claw.hardware.can; + +import java.nio.ByteBuffer; + +import java.nio.ByteOrder; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import claw.LiveValues; +import claw.hardware.can.CANMessageID.DeviceType; +import claw.hardware.can.CANMessageID.ManufacturerCode; +import claw.rct.commands.CommandProcessor; +import claw.rct.commands.CommandReader; +import claw.rct.commands.CommandProcessor.BadCallException; +import claw.rct.network.low.ConsoleManager; +import edu.wpi.first.hal.can.CANJNI; +import edu.wpi.first.hal.can.CANMessageNotFoundException; +import edu.wpi.first.hal.can.CANStatus; +import edu.wpi.first.math.filter.Debouncer; +import edu.wpi.first.math.filter.LinearFilter; +import edu.wpi.first.math.filter.Debouncer.DebounceType; + +/** + * A utility class which can detect devices on, send messages to, and read messages from the CAN bus. + */ +public class CANScanner { + + /** + * A message read from the CAN bus. + */ + public static record CANMessage (CANMessageID messageID, byte[] messageData, long timestamp) { } + + /** + * A trace for a device connected to the roboRIO via CAN. + */ + public static record CANDeviceTrace (DeviceType deviceType, ManufacturerCode manufacturer, int deviceNum) { } + + /** + * A command processor for the {@code canscan} command. + */ + public static final CommandProcessor CAN_SCAN_COMMAND_PROCESSOR = new CommandProcessor( + "canscan", + "canscan [status | devices]", + "Use 'canscan status' to get the status of the CAN bus (bus utilization and presence of errors). " + + "'canscan devices' will scan to detect devices on the CAN bus. It reads manufacturers, device types, " + + "and device numbers (IDs).", + CANScanner::canScanCommand + ); + + private static double roundTo (double value, int precision) { + return Math.round(value * precision) / precision; + } + + private static String padToSize (String str, int size) { + int totalPadding = Math.max(size - str.length(), 0); + + int leftPadding = totalPadding / 2; + int rightPadding = totalPadding - leftPadding; + + return " ".repeat(leftPadding) + str + " ".repeat(rightPadding); + } + + private static void canScanCommand (ConsoleManager console, CommandReader reader) throws BadCallException { + String scanType = reader.readArgOneOf("scan type", "Expected a scan type of 'status' or 'devices'.", "status", "devices"); + reader.noMoreArgs(); + reader.allowNoOptions(); + reader.allowNoFlags(); + + LiveValues values = new LiveValues(); + + if (scanType.equals("status")) { + + LinearFilter canUtilizationFilter = LinearFilter.movingAverage(35); + Debouncer receiveErrorDebouncer = new Debouncer(0.1, DebounceType.kFalling); + Debouncer transmitErrorDebouncer = new Debouncer(0.1, DebounceType.kFalling); + + while (!console.hasInputReady()) { + + CANStatus canStatusReading = getCANStatus(); + double percentBusUtilization = canUtilizationFilter.calculate(canStatusReading.percentBusUtilization) * 100; + + boolean hasReceiveError = receiveErrorDebouncer.calculate(canStatusReading.receiveErrorCount > 0); + boolean hasTransmitError = transmitErrorDebouncer.calculate(canStatusReading.transmitErrorCount > 0); + + values.setField("Bus Utilization", roundTo(percentBusUtilization, 1000) + "%"); + values.setField("Receive Error", hasReceiveError ? "Present" : "None"); + values.setField("Transmit Error", hasTransmitError ? "Present" : "None"); + + values.update(console); + } + + } else if (scanType.equals("devices")) { + + // TODO: Send an "enumerate" CAN frame, write a wrapper around the FRC_Net_Comm_Mux functions, + // filter out bad data, remove System.out printing, and clean up CANMessageIDs + + console.printlnSys("Scanning..."); + console.flush(); + + console.printlnSys( + padToSize("Manufacturer", 35) + + padToSize("Device Type", 35) + + padToSize("Device Number", 20) + ); + + Set devices = scanCANDevices(600); + for (CANDeviceTrace device : devices) { + console.println( + padToSize(device.manufacturer.friendlyName, 35) + + padToSize(device.deviceType+"", 35) + + padToSize(device.deviceNum+"", 20) + ); + } + + } + + } + + /** + * Get the status {@link CANStatus} of the CAN bus. + * @return The {@code CANStatus} describing the state of the CAN bus. + */ + public static CANStatus getCANStatus () { + CANStatus status = new CANStatus(); + CANJNI.getCANStatus(status); + return status; + } + + /** + * Read a single message from the CAN bus (from any device). + * @return A new message received from the CAN bus. + */ + public static Optional readMessage () { + return readMessage(0, 0); + } + + /** + * Read a single message from the CAN bus. See the FRC CAN Device Specifications for + * the specifications on the message ID. + * https://docs.wpilib.org/en/stable/docs/software/can-devices/can-addressing.html + *

+ * Note that there are generally better options through WPILib for reading messages + * from the CAN bus. This would only be useful if you need to read a broad range of + * messages from varying IDs for some reason. + * @param messageID The arbitration ID of the messages to read. See the specifications. + * @param messageIDMask A bit mask to apply to messages IDs received from the CAN bus. + * The received message ID, after applying a bitwise {@code &} with the given mask, will be checked + * against the {@code messageID} argument to see if it should be intercepted. + * @return The CAN message intercepted from the bus, if one could be read. + */ + public static Optional readMessage (int messageID, int messageIDMask) { + + // Create a message ID buffer for distinguishing which messages to intercept + // This buffer will also be filled with the ID of the retrieved message once done + ByteBuffer messageIDBuffer = ByteBuffer.allocateDirect(4); + messageIDBuffer.order(ByteOrder.LITTLE_ENDIAN); + messageIDBuffer.clear(); + messageIDBuffer.asIntBuffer().put(messageID, 0); + + // Create a buffer to store the timestamp of the received message. This will be filled + // after receiving the message + ByteBuffer timestampBuffer = ByteBuffer.allocate(4); + + byte[] messageContent; + + try { + // Receive a message from the CAN bus and fill the messageIDBuffer and timestampBuffer + messageContent = CANJNI.FRCNetCommCANSessionMuxReceiveMessage( + messageIDBuffer.asIntBuffer(), + messageIDMask, + timestampBuffer + ); + } catch (CANMessageNotFoundException e) { + // If no message was found, return the empty optional + return Optional.empty(); + } + + // TODO: Use timestamp ByteBuffer to get message timestamp + return Optional.of(new CANMessage(CANMessageID.fromMessageId(messageIDBuffer.asIntBuffer().get()), messageContent, 0)); + + } + + public static Set scanCANDevices (int messagesToScan) { + + // Create a set of CAN device traces to return + HashSet devices = new HashSet<>(); + + // TODO: Send the enumerate message or something else to encourage devices to identify themselves + + // Scan through the given number of messages to find all device traces + for (int i = 0; i < messagesToScan; i ++) { + readMessage().ifPresent(msg -> { + + // TODO: Filter out devices with non-device manufacturers or device types + + // Add the device trace to the set + CANDeviceTrace deviceTrace = new CANDeviceTrace( + msg.messageID().deviceType(), + msg.messageID().manufacturer(), + msg.messageID().deviceNum() + ); + + devices.add(deviceTrace); + + }); + } + + return devices; + + } + + private CANScanner () { } + +} diff --git a/lib/app/src/main/java/claw/logs/LogHandler.java b/lib/app/src/main/java/claw/logs/LogHandler.java index d514f11..f09dca6 100644 --- a/lib/app/src/main/java/claw/logs/LogHandler.java +++ b/lib/app/src/main/java/claw/logs/LogHandler.java @@ -6,8 +6,8 @@ import java.util.List; import java.util.Set; -import claw.rct.network.low.Waiter; -import claw.rct.network.low.Waiter.NoValueReceivedException; +import claw.rct.network.low.concurrency.Waiter; +import claw.rct.network.low.concurrency.Waiter.NoValueReceivedException; import claw.rct.network.messages.LogDataMessage; import claw.rct.network.messages.LogDataMessage.LogData; import claw.rct.remote.RCTServer; diff --git a/lib/app/src/main/java/claw/math/DualDebouncer.java b/lib/app/src/main/java/claw/math/DualDebouncer.java new file mode 100644 index 0000000..58a9800 --- /dev/null +++ b/lib/app/src/main/java/claw/math/DualDebouncer.java @@ -0,0 +1,99 @@ +package claw.math; + +import edu.wpi.first.math.filter.Debouncer; + +/** + * An extension of the WPILib {@link Debouncer} allowing for a different rising edge and falling edge debounce times. + * This {@code DualDebouncer} also allows for greater control over the internal state of the debouncer. For this reason, + * it may be useful to use a {@code DualDebouncer} even if different rising edge and falling edge debounce times are not + * required. + */ +public class DualDebouncer extends Debouncer { + + private final double fallingEdgeTime, risingEdgeTime; + + /** + * The current state of the debouncer (the "baseline", because the internalDebouncer is set up to + * only debounce the edge which would change this baselineState) + */ + private boolean baselineState; + + /** + * The internal debouncer, which is reset whenever the baseline changes (i.e. there will be a different + * internalDebouncer for falling edge vs. rising edge debouncing) + */ + private Debouncer internalDebouncer; + + /** + * Create a new {@link DualDebouncer} with separate falling edge and rising edge debounce times. + * @param baselineState The initial state of this debouncer. + * @param fallingEdgeTime The number of seconds the input must be changed to {@code false} when the + * baseline is {@code true} in order for the baseline to change. + * @param risingEdgeTime The number of seconds the input must be changed to {@code true} when the + * baseline is {@code false} in order for the baseline to change. + */ + public DualDebouncer (boolean baselineState, double fallingEdgeTime, double risingEdgeTime) { + // This class only extends Debouncer so that it can be used in place + // of a WPILib Debouncer. Otherwise, it actually doesn't use inheritance + // to control the debounce filter at all. + super(0); + + this.fallingEdgeTime = fallingEdgeTime; + this.risingEdgeTime = risingEdgeTime; + + // Reset to the given baselineState + resetToBaseline(baselineState); + } + + /** + * Create a new {@link DualDebouncer} with a given debounceTime to use for both the rising and falling edges. + * @param baselineState The initial state of this debouncer. + * @param debounceTime The number of seconds the input must be different than the baseline for before the + * baseline changes. + */ + public DualDebouncer (boolean baselineState, double debounceTime) { + this(baselineState, debounceTime, debounceTime); + } + + /** + * Reset the timer of the debouncer without changing the current baseline state. + */ + public void resetTimer () { + resetToBaseline(baselineState); + } + + /** + * Reset the timer of the debouncer and change the baseline state. + * @param newBaseline The new baseline state of the debouncer. + */ + public void resetToBaseline (boolean newBaseline) { + // Reset the baseline state + baselineState = newBaseline; + + // Set the internalDebouncer for the new baseline + internalDebouncer = newBaseline + // If the newBaseline is true, we debounce the falling edge + ? new Debouncer(fallingEdgeTime, DebounceType.kFalling) + + // If the newBaseline is false, we debounce the rising edge + : new Debouncer(risingEdgeTime, DebounceType.kRising); + } + + @Override + public boolean calculate (boolean input) { + + // Again, we actually don't use any details from the superclass implementation of the debouncer. + // All debounce control is internal. We only extend the WPILib Debouncer so this DualDebouncer + // can be used in any place the WPILib Debouncer can be used + + // If the internalDebouncer indicates that the baseline state must change, + // reset to the new baseline + if (internalDebouncer.calculate(input) != baselineState) { + resetToBaseline(!baselineState); + } + + return baselineState; + + } + +} diff --git a/lib/app/src/main/java/claw/math/LinearInterpolator.java b/lib/app/src/main/java/claw/math/LinearInterpolator.java new file mode 100644 index 0000000..39a4715 --- /dev/null +++ b/lib/app/src/main/java/claw/math/LinearInterpolator.java @@ -0,0 +1,115 @@ +package claw.math; + +import java.util.Arrays; + +/** + * A linear interpolator {@link Transform} which can take in x-coordinates and provide a corresponding y-coordinate + * according to a provided dataset. + */ +public class LinearInterpolator implements Transform { + + /** + * A record representing a point on the x-y plane, which can be used to perform interpolation calculations. + */ + public static record Point (double x, double y) { } + + private final Point[] points; + + /** + * Create a linear interpolator from the given points to interpolate between. + * @param points A varargs list of points to interpolate beween. + */ + public LinearInterpolator (Point... points) { + // Create an array of the points, sorted by x value + Point[] sortedPoints = points.clone(); + Arrays.sort(sortedPoints, (Point a, Point b) -> + a.x < b.x ? -1 : (a.x > b.x ? 1 : 0) + ); + + this.points = sortedPoints; + } + + /** + * Create a linear interpolator from the given x and y coordinates representing points to interpolate between. + * @param xyPairs Pairs of {@code double} coordinates (x, y), (x, y), (x, y)... + * Because the given {@code xyPairs} represents pairs of x and y coordinates, an odd number of {@code xyPairs} + * values is invalid. For example, the input {@code new LinearInterpolator(1, 2, 3, 4, 5, 6)} would represent + * the points (1, 2), (3, 4) and (5, 6). + */ + public LinearInterpolator (double... xyPairs) { + this(xyPairsToPoints(xyPairs)); + } + + private static Point[] xyPairsToPoints (double... xyPairs) { + // Only allow an even number of coordinates (otherwise, we'd be missing a y coordinate for the last point) + if (xyPairs.length % 2 != 0) { + throw new IllegalArgumentException("Missing a y coordinate for the last point (number of coordinates given must be even)"); + } + + // Create an array of points from the xyPairs list + Point[] points = new Point[xyPairs.length / 2]; + for (int i = 0; i < points.length; i ++) { + points[i] = new Point(xyPairs[2*i], xyPairs[2*i + 1]); + } + + return points; + } + + /** + * Perform a linear interpolation on two points {@code a} and {@code b} in order to estimate a reasonable + * y-coordinate corresponding with the input x-coordinate {@code x}. + * @param x The input x-coordinate. + * @param a The first of two points which form the basis of the linear interpolation. + * @param b The second of two points which form the basis of the linear interpolation. + * @return An output y-coordinate corresponding to the input {@code x}. + */ + public static double interpolate (double x, Point a, Point b) { + // If a and b have exactly the same x value, just get the mean of their y values + if (a.x == b.x) return (a.y + b.y) / 2; + + // Calculate p. If p=0, (x,y) should be on point a, if p=1, (x,y) should be on point b + double p = (x - a.x) / (b.x - a.x); + + // Given p, perform the linear interpolation between points a and b + return p * (b.y - a.y) + a.y; + } + + /** + * Get a corresponding y coordinate for the given x coordinate by performing a linear interpolation betwen + * the points provided to this {@link LinearInterpolator}. + * @param x The x input coordinate. + * @return The corresponding y output coordinate. + */ + @Override + public double apply (double x) { + + // Return a constant value if there are exactly 0 or 1 given points, as all later calculations + // depends on there being at least two points + if (points.length == 0) return 0; + if (points.length == 1) return points[0].y; + final int lastIndex = points.length - 1; + + // If x is further to the left than the first point, we interpolate between the first two points + if (x < points[0].x) return interpolate(x, points[0], points[1]); + + // Find the first point which is further to the right than the given x + for (int i = 0; i <= lastIndex; i ++) { + + // Return the y coordinate of this point if it exactly matches the given x coordinate + if (points[i].x == x) return points[i].y; + + // Because points[i] is the first point further to the right than x, points[i-1] must + // also be the last point further to the left than x, so we interpolate between the two + if (points[i].x > x) { + return interpolate(x, points[i-1], points[i]); + } + + } + + // Because no point was further to the right than x, x is beyond the last point and we must + // interpolate between the last two points + return interpolate(x, points[lastIndex - 1], points[lastIndex]); + + } + +} diff --git a/lib/app/src/main/java/claw/math/State.java b/lib/app/src/main/java/claw/math/State.java new file mode 100644 index 0000000..6b8e425 --- /dev/null +++ b/lib/app/src/main/java/claw/math/State.java @@ -0,0 +1,15 @@ +package claw.math; + +/** + * An interface for the internal state of some {@link StateMachine}. + */ +public interface State, I> { + + /** + * Get the next {@link State} given the {@link StateMachine} input {@code input}. + * @param input The input event or data to some state machine. + * @return The state the input leads to. + */ + public S getNewState (I input); + +} \ No newline at end of file diff --git a/lib/app/src/main/java/claw/math/StateMachine.java b/lib/app/src/main/java/claw/math/StateMachine.java new file mode 100644 index 0000000..263fedf --- /dev/null +++ b/lib/app/src/main/java/claw/math/StateMachine.java @@ -0,0 +1,36 @@ +package claw.math; + +/** + * A generic state machine which allows for transitioning and storing {@link State}. + */ +public class StateMachine , I> { + + private S currentState; + + /** + * Create a new {@link StateMachine} with some initial state. + * @param initialState The initial {@link State} of this state machine. + */ + public StateMachine (S initialState) { + currentState = initialState; + } + + /** + * Transition the state of this {@link StateMachine} according to a given {@code input}. + * @param input The input which will be used to get a new state. + * @return This state machine, so that several transition inputs can easily be chained together. + */ + public StateMachine transition (I input) { + currentState = currentState.getNewState(input); + return this; + } + + /** + * Get the internal {@link State} of this {@link StateMachine}. + * @return The internal state. + */ + public S getState () { + return currentState; + } + +} diff --git a/lib/app/src/main/java/claw/math/Transform.java b/lib/app/src/main/java/claw/math/Transform.java index abfa820..65e4329 100644 --- a/lib/app/src/main/java/claw/math/Transform.java +++ b/lib/app/src/main/java/claw/math/Transform.java @@ -30,6 +30,17 @@ public static Transform clamp (double low, double high) { return input -> Math.min(Math.max(input, low), high); } + /** + * Gets a linear {@link Transform} which applies the equation {@code y = mx + b} + * to the parameter for the given {@code m} and {@code b}. + * @param m The slope of the line represented by this linear transform. + * @param b The y-intercept of the line represented by this linear transform. + * @return The linear transform. + */ + public static Transform linear (double m, double b) { + return x -> m*x + b; + } + /** * Turns a transform into an even function by reflecting all x values less than zero across the y-axis. * @param transform The base {@link Transform} to turn into an even function. diff --git a/lib/app/src/main/java/claw/math/Vector.java b/lib/app/src/main/java/claw/math/Vector.java index 4e176f6..f1a8e37 100644 --- a/lib/app/src/main/java/claw/math/Vector.java +++ b/lib/app/src/main/java/claw/math/Vector.java @@ -4,21 +4,20 @@ import edu.wpi.first.math.Nat; import edu.wpi.first.math.Num; +import edu.wpi.first.math.numbers.N1; +import edu.wpi.first.math.numbers.N2; +import edu.wpi.first.math.numbers.N3; +import edu.wpi.first.math.numbers.N4; /** - * A type-safe vector class which can be used for calculations. + * A vector class which can be used for calculations. */ public class Vector { private Optional magnitude = Optional.empty(); + private Optional angle = Optional.empty(); private final Nat dimensionality; - - /** - * An array of components backed by the {@link Vector} (meaning these components should never be modified). - * For example, for a two-dimensional vector {@code Vector}, this array would - * have a length of two and would contain the x and y components of the vector as {@code [x, y]}. - */ - public final double[] components; + private final double[] components; /** * Gets a {@link Vector} from a list of components and the dimensionality of the vector. @@ -30,10 +29,116 @@ public Vector (Nat dim, double... components) { if (dim.getNum() != components.length) throw new IllegalArgumentException("The number of components given for this Vector does not match the dimensionality of the Vector"); dimensionality = dim; - this.components = components.clone(); } + /** + * Create a new one-dimensional vector {@code } from the given x component. + * @param x The x component of the vector. + * @return The vector {@code }. + */ + public static Vector from (double x) { + return new Vector<>(Nat.N1(), x); + } + + /** + * Create a new two-dimensional vector {@code } from the given components. + * @param x The x component of the vector. + * @param y The y component of the vector. + * @return The vector {@code }. + */ + public static Vector from (double x, double y) { + return new Vector<>(Nat.N2(), x, y); + } + + /** + * Create a new three-dimensional vector {@code } from the given components. + * @param x The x component of the vector. + * @param y The y component of the vector. + * @param z The z component of the vector. + * @return The vector {@code }. + */ + public static Vector from (double x, double y, double z) { + return new Vector<>(Nat.N3(), x, y, z); + } + + /** + * Create a new four-dimensional vector {@code } from the given components. + * @param x The x component of the vector. + * @param y The y component of the vector. + * @param z The z component of the vector. + * @param w The w component of the vector. + * @return The vector {@code }. + */ + public static Vector from (double x, double y, double z, double w) { + return new Vector<>(Nat.N4(), x, y, z, w); + } + + /** + * Get the dimensionality of this vector (i.e. the number of components it has), as an integer. + * @return The dimensionality of this vector. + */ + public int getDimensionality () { + return dimensionality.getNum(); + } + + /** + * Get the {@code i+1}th component of this vector. For example, + * {@code getComponent(2)} would return the third component. + * @param i The index of the component to retrieve. + * @return The {@code i+1}th component of this vector. + * @throws IllegalArgumentException If the given component index {@code i} is invalid for this vector + * (i.e. no {@code i+1}th component exists for this vector). + */ + public double getComponent (int i) throws IllegalArgumentException { + if (i < 0) { + throw new IllegalArgumentException("Cannot retrieve a component with index less than zero"); + } else if (i >= getDimensionality()) { + throw new IllegalArgumentException( + "Cannot retrieve component with index "+i+" from a " + + getDimensionality()+"-dimensional vector" + ); + } else { + return components[i]; + } + } + + /** + * Get the x (1st) component of this vector. + * If this vector's dimensionality is less than one, an exception will be thrown. + * @return The x component of this vector. + */ + public double getX () { + return getComponent(0); + } + + /** + * Get the y (2nd) component of this vector. + * If this vector's dimensionality is less than two, an exception will be thrown. + * @return The y component of this vector. + */ + public double getY () { + return getComponent(1); + } + + /** + * Get the z (3rd) component of this vector. + * If this vector's dimensionality is less than three, an exception will be thrown. + * @return The z component of this vector. + */ + public double getZ () { + return getComponent(2); + } + + /** + * Get the w (4th) component of this vector. + * If this vector's dimensionality is less than four, an exception will be thrown. + * @return The w component of this vector. + */ + public double getW () { + return getComponent(3); + } + /** * Gets the magnitude of the vector. * @return The magnitude. @@ -74,17 +179,16 @@ public Vector scale (double k) { } /** - * Applies a {@link Transform} to the magnitude of this vector. - * @param transform The {@code Transform} to apply to this vector's magnitude. - * @return This vector scaled such that the magnitude of the resulting vector is equal to the result - * of the transform applied to this vector's magnitude. + * Returns this vector scaled so that the new magnitude is equal to the given magnitude. If this vector's magnitude + * is zero, a zero vector will be returned. + * @param magnitude The magnitude of the vector after scaling. + * @return This vector, scale such that the magnitude equals the provided magnitude. */ - public Vector applyScale (Transform transform) { + public Vector scaleToMagnitude (double magnitude) { if (getMagnitude() == 0) return this.scale(0); - - double newMagnitude = transform.apply(getMagnitude()); - return this.scale(newMagnitude / getMagnitude()); + else + return this.scale(magnitude / getMagnitude()); } /** @@ -133,7 +237,7 @@ public double dotProduct (Vector other) { * @return The sum of the two vectors. */ public Vector add (Vector other) { - return this.apply(other, (a, b) -> a * b); + return this.apply(other, (a, b) -> a + b); } /** @@ -145,4 +249,46 @@ public Vector subtract (Vector other) { return this.apply(other, (a, b) -> a - b); } + /** + * Returns the angle formed between a two-dimensional vector and the x-axis, in radians. This angle + * increases counterclockwise. For example, a vector facing in the +y direction will return {@code pi/2}. + * @param vector The two-dimensional vector to retrieve the direction angle of. + * @return The angle of the vector, in radians. + */ + public static double getAngle (Vector vector) { + if (vector.angle.isEmpty()) { + + // Set vector.angle as a cache + + if (vector.getMagnitude() == 0) { + + // Return 0 if the magnitude is 0 to prevent dividing by zero (or yielding a nonsensical answer) + vector.angle = Optional.of(0.); + + } else { + + // Get the X component of the vector if it were on the unit circle so we can use arccos + double unitX = vector.components[0] / vector.getMagnitude(); + + // Get the angle if the vector were above the x-axis + double angle = Math.acos(unitX); + + // Use the angle directly if the vector is above the x-axis, or modify it to flip across the x-axis otherwise + vector.angle = Optional.of(vector.components[1] >= 0 ? (angle) : (2*Math.PI - angle)); + + } + + } + + return vector.angle.get(); + } + + @Override + public String toString () { + String[] componentsStrings = new String[components.length]; + for (int i = 0; i < components.length; i ++) + componentsStrings[i] = Double.toString(components[i]); + return "<" + String.join(", ", componentsStrings) + ">"; + } + } diff --git a/lib/app/src/main/java/claw/math/InputTransform.java b/lib/app/src/main/java/claw/math/input/InputTransform.java similarity index 63% rename from lib/app/src/main/java/claw/math/InputTransform.java rename to lib/app/src/main/java/claw/math/input/InputTransform.java index 61c79b9..de6973b 100644 --- a/lib/app/src/main/java/claw/math/InputTransform.java +++ b/lib/app/src/main/java/claw/math/input/InputTransform.java @@ -1,6 +1,12 @@ -package claw.math; +package claw.math.input; -public class InputTransform { +import claw.math.Transform; + +/** + * A class which helps to process input from a scalar or vector input on a controller (trigger or joystick), + * handling a deadband zone and applying a curve to refine control. + */ +public class InputTransform implements Transform { private static final Transform INPUT_CLAMP = Transform.clamp(-1, 1); @@ -20,7 +26,7 @@ public class InputTransform { public static final Transform SQUARE_CURVE = Transform.toOdd(x -> x*x); /** - * Creates {@link Transform} representing a deadband's application to some input value. + * Creates a {@link Transform} representing a deadband's application to some input value. * @param deadband A deadband value on the interval [0, 1). * @return The deadband {@code Transform}. */ @@ -38,27 +44,36 @@ public static final Transform makeDeadband (double deadband) { })); } + private final Transform innerTransform; + /** - * Create a {@link Transform} which can be applied to any input (but designed for handling human input - * from an axis on a controller). The transform applies a deadband + * Create an {@link InputTransform} which can be applied to any input (but designed for handling human input + * from one (scalar) or two (vector) axes on a controller). The transform applies a deadband * so that any input with a magnitude less than the deadband value will be ignored. * A given input map is then applied to the output from this deadband, and finally * the output is clamped to the range [-1, 1]. * * @param inputMap Any {@code Transform}. It is recommended but not required that this transform map -1 to -1, - * 0 to 0, and 1 to 1. See {@link Transform#toOdd(Transform)} for a easy way to convert an existing input curve which + * 0 to 0, and 1 to 1. See {@link Transform#toOdd(Transform)} for a easy way to convert an existing transform which * works for positive numbers into one which will also properly handle negative numbers. If you're not sure where * to start, try out {@link InputTransform#THREE_HALVES_CURVE}. * @param deadbandValue Any input with a magnitude of less than this deadband value will be mapped to zero. - * @return The final {@code Transform} which can be applied directly to input from a controller axis. */ - public static Transform getInputTransform (Transform inputMap, double deadbandValue) { - return + public InputTransform (Transform inputMap, double deadbandValue) { + innerTransform = makeDeadband(deadbandValue) .then(inputMap) .then(INPUT_CLAMP); } - private InputTransform () { } + /** + * Applies this {@link InputTransform} to the given input. The input is assumed to be on the range [-1, 1], but + * this transform will still clamp input to that range in case it isn't already. The input transform + * is best used when applying input directly to a value taken from a joystick axis on a controller. + */ + @Override + public double apply (double input) { + return innerTransform.apply(input); + } } diff --git a/lib/app/src/main/java/claw/math/input/RaptorsXboxController.java b/lib/app/src/main/java/claw/math/input/RaptorsXboxController.java new file mode 100644 index 0000000..ee088d6 --- /dev/null +++ b/lib/app/src/main/java/claw/math/input/RaptorsXboxController.java @@ -0,0 +1,200 @@ +package claw.math.input; + +import java.util.Optional; + +import claw.math.Transform; +import claw.math.Vector; +import edu.wpi.first.math.geometry.Rotation2d; +import edu.wpi.first.math.numbers.N2; +import edu.wpi.first.wpilibj.XboxController; +import edu.wpi.first.wpilibj2.command.InstantCommand; +import edu.wpi.first.wpilibj2.command.SequentialCommandGroup; +import edu.wpi.first.wpilibj2.command.WaitCommand; + +/** + * An extension of the {@link XboxController} providing better control over the D-pad, {@link InputTransform} + * applications to joysticks, and other quality-of-life improvements. + */ +public class RaptorsXboxController extends XboxController { + + private final Transform leftStickTransform, rightStickTransform; + + /** + * Create a new {@link RaptorsXboxController}. + * @param port The port index of this Xbox controller on the Driver Station. + */ + public RaptorsXboxController (int port) { + this(port, Transform.clamp(-1, 1)); + } + + /** + * Create a new {@link RaptorsXboxController} with a {@link Transform} to apply to both joysticks when retrieving + * their positions as vectors. + * @param port The port index of this Xbox controller on the Driver Station. + * @param stickVectorTransform The transform to apply to both joystick vectors' magnitudes. This is recommended + * to be an {@link InputTransform}. + */ + public RaptorsXboxController (int port, Transform stickVectorTransform) { + this(port, stickVectorTransform, stickVectorTransform); + } + + /** + * Create a new {@link RaptorsXboxController} with {@link Transform}s to apply to the left and right joysticks when retrieving + * their positions as vectors. The given {@code Transform}s are recommended to be {@link InputTransform}s. + * @param port The port index of this Xbox controller on the Driver Station. + * @param leftStickVectorTransform The transform to apply to the left joystick's position vector (magnitude). + * @param rightStickVectorTransform The transform to apply to the right joystick's position vector (magnitude). + */ + public RaptorsXboxController (int port, Transform leftStickVectorTransform, Transform rightStickVectorTransform) { + super(port); + leftStickTransform = leftStickVectorTransform; + rightStickTransform = rightStickVectorTransform; + } + + private Vector getStickVector (double x, double y, Transform transform) { + // Invert y because the input from the driver station is also inverted + Vector rawVector = Vector.from(x, -y); + + // Re-scale the vector to a new magnitude + double rawMagnitude = rawVector.getMagnitude(); + return rawVector.scaleToMagnitude(transform.apply(rawMagnitude)); + } + + /** + * Retrieves the left joystick's position as a {@link Vector}. The left joystick transform + * given through the constructor method will be applied to the vector's magnitude, + * if one was provided. Also, the vector will be normalized so that up on the joystick corresponds + * with a positive y value. + * @return A {@code Vector} representing the left joystick's position. + */ + public Vector getLeftStickAsVector () { + return getStickVector(getLeftX(), getLeftY(), leftStickTransform); + } + + /** + * Retrieves the right joystick's position as a {@link Vector}. The right joystick transform + * given through the constructor method will be applied to the vector's magnitude, + * if one was provided. Also, the vector will be normalized so that up on the joystick corresponds + * with a positive y value. + * @return A {@code Vector} representing the right joystick's position. + */ + public Vector getRightStickAsVector () { + return getStickVector(getRightX(), getRightY(), rightStickTransform); + } + + /** + * Set the controller's rumble. + * @param leftRumble A value on the interval [0, 1] representing the rumble to apply to the left side of the controller. + * @param rightRumble A value on the interval [0, 1] representing the rumble to apply to the right side of the controller. + */ + public void setRumble (double leftRumble, double rightRumble) { + setRumble(RumbleType.kLeftRumble, leftRumble); + setRumble(RumbleType.kRightRumble, rightRumble); + } + + /** + * Set the controller's rumble. + * @param rumbleValue A value on the interval [0, 1] representing the rumble to apply to the controller. + */ + public void setRumble (double rumbleValue) { + setRumble(rumbleValue, rumbleValue); + } + + /** + * Stop all controller rumble. + */ + public void stopRumble () { + setRumble(0); + } + + /** + * Set the controller to rumble for a given duration. + * @param durationSecs The duration of the rumble, in seconds. + * @param leftRumble A value on the interval [0, 1] representing the rumble to apply to the left side of the controller. + * @param rightRumble A value on the interval [0, 1] representing the rumble to apply to the right side of the controller. + */ + public void setTimedRumble (double durationSecs, double leftRumble, double rightRumble) { + new SequentialCommandGroup( + new InstantCommand(() -> this.setRumble(leftRumble, rightRumble)), + new WaitCommand(durationSecs), + new InstantCommand(this::stopRumble) + ).schedule(); + } + + /** + * Set the controller to rumble for a given duration. + * @param durationSecs The duration of the rumble, in seconds. + * @param rumbleValue A value on the interval [0, 1] representing the rumble to apply to the controller. + */ + public void setTimedRumble (double durationSecs, double rumbleValue) { + setTimedRumble(durationSecs, rumbleValue, rumbleValue); + } + + /** + * Get the {@link DPadDirection} currently being pressed (if one is being pressed). + * @return An {@link Optional} containing the pressed {@code DPadDirection}. + */ + public Optional getDPad () { + + // Get the DPad direction as an integer + int directionInt = getPOV(); + + // Search through the DPadDirection enum to find one which corresponds with the current directionInt + for (DPadDirection direction : DPadDirection.values()) { + if (directionInt == direction.direction) return Optional.of(direction); + } + + // If none matched, then return the empty optional + return Optional.empty(); + + } + + /** + * Check whether a given {@link DPadDirection} is actively being pressed. + * @param direction The {@code DPadDirection} to check against. + * @return Whether the given direction is held down on the D-pad. + */ + public boolean isDPadPressed (DPadDirection direction) { + Optional currentDirection = getDPad(); + return currentDirection.isPresent() && currentDirection.get().equals(direction); + } + + /** + * An enum representing the buttons or orientations of the D-pad. + * @see RaptorsXboxController#getDPad() + */ + public enum DPadDirection { + + TOP (0), + TOP_RIGHT (45), + RIGHT (90), + BOTTOM_RIGHT (135), + BOTTOM (180), + BOTTOM_LEFT (225), + LEFT (270), + TOP_LEFT (315); + + /** + * Direction in degrees, starting with 0 for {@link DPadDirection#TOP} and progressing clockwise. + */ + public final int direction; + + /** + * The {@link Rotation2d} of this direction, as if it were placed on the unit circle. + * This conforms to the standard WPILib understanding of rotation, as zero rotation corresponds + * with {@link DPadDirection#RIGHT}, and the rotation progresses counterclockwise. + */ + public final Rotation2d rotation; + + private DPadDirection (int direction) { + this.direction = direction; + + // Convert the direction degrees into rotation degrees on the unit circle + int unitCircleDegrees = 90 - direction; + if (unitCircleDegrees < 0) unitCircleDegrees += 360; + rotation = Rotation2d.fromDegrees(unitCircleDegrees); + } + + } + +} diff --git a/lib/app/src/main/java/claw/rct/local/LocalCommandInterpreter.java b/lib/app/src/main/java/claw/rct/local/LocalCommandInterpreter.java index 410c571..fc4c09b 100644 --- a/lib/app/src/main/java/claw/rct/local/LocalCommandInterpreter.java +++ b/lib/app/src/main/java/claw/rct/local/LocalCommandInterpreter.java @@ -66,10 +66,6 @@ private void addCommands () { "Displays the current status of the connection to the remote (the roboRIO), automatically updating over time. Press enter to stop.", this::commsCommand); - addCommand("ip", "ip [static | dynamic]", - "Change the IP address to use for the roboRIO to be either static (use for connection via radio) or dynamic (ethernet or usb).", - this::ipAddressCommand); - addCommand("ssh", "ssh [user]", "Launches an Secure Socket Shell for the roboRIO, using either the user 'lvuser' or 'admin'.", this::sshCommand); @@ -77,6 +73,10 @@ private void addCommands () { addCommand("log", "log", "Print data with CLAWLoggers to the terminal when it is received from the robot.", this::logCommand); + + addCommand("config", "config [team number] [remote port]", + "Configure the connection to the RCT server, setting the team number and server port.", + this::configCommand); } /** @@ -111,30 +111,6 @@ private void addCommand (String command, String usage, String helpDescription, C // Command methods: - private void ipAddressCommand (ConsoleManager console, CommandReader reader) throws BadCallException { - reader.allowNoOptions(); - reader.allowNoFlags(); - - String addressType = reader.readArgOneOf( - "address type", - "The given IP address type must be either 'static' or 'dynamic'.", - "static", "dynamic"); - - if (addressType.equals("static")) { - system.setUseStaticRoborioAddress(true); - } else { - system.setUseStaticRoborioAddress(false); - } - - try { - console.println("Attempting to connect to " + system.getRoborioHost() + "..."); - system.establishNewConnection(); - console.println("Successfully connected."); - } catch (IOException e) { - console.printlnErr("Failed to establish a new connection."); - } - } - private void clearCommand (ConsoleManager console, CommandReader reader) throws BadCallException { reader.allowNone(); console.clear(); @@ -145,6 +121,25 @@ private void exitCommand (ConsoleManager console, CommandReader reader) throws B System.exit(0); } + private void configCommand (ConsoleManager console, CommandReader reader) throws BadCallException { + int teamNum = reader.readArgInt("team number"); + int port = reader.readArgInt("RCT server port"); + reader.noMoreArgs(); + reader.allowNoOptions(); + reader.allowNoFlags(); + + system.setServerPort(port); + system.setTeamNum(teamNum); + + try { + system.establishNewConnection(); + console.printlnSys("Successfully connected to the RCT server at " + system.getRoborioHost().orElse("[connection failed]")); + } catch (IOException e) { + console.printlnErr("Failed to connect to the RCT server."); + } + + } + private void helpCommand (ConsoleManager console, CommandReader reader) throws BadCallException { reader.allowNoOptions(); reader.allowNoFlags(); @@ -257,12 +252,16 @@ private void sshCommand (ConsoleManager console, CommandReader reader) throws Ba reader.noMoreArgs(); // Get host for ssh and generate command - String host = system.getRoborioHost(); + Optional host = system.getRoborioHost(); + if (host.isEmpty()) { + console.printlnErr("There is no connection to the roboRIO."); + return; + } String[] puttyCommand = new String[] { "putty", "-ssh", - user+"@"+host, + user+"@"+host.get(), }; // Attempt to run the command to start PuTTY diff --git a/lib/app/src/main/java/claw/rct/local/LocalSystem.java b/lib/app/src/main/java/claw/rct/local/LocalSystem.java index 5841400..325d864 100644 --- a/lib/app/src/main/java/claw/rct/local/LocalSystem.java +++ b/lib/app/src/main/java/claw/rct/local/LocalSystem.java @@ -10,8 +10,8 @@ import claw.rct.network.low.DriverStationSocketHandler; import claw.rct.network.low.InstructionMessage; import claw.rct.network.low.ResponseMessage; -import claw.rct.network.low.Waiter; -import claw.rct.network.low.Waiter.NoValueReceivedException; +import claw.rct.network.low.concurrency.Waiter; +import claw.rct.network.low.concurrency.Waiter.NoValueReceivedException; import claw.rct.network.messages.CommandsListingMessage; import claw.rct.network.messages.ConnectionCheckMessage; import claw.rct.network.messages.ConnectionResponseMessage; @@ -45,14 +45,12 @@ public class LocalSystem implements ResponseMessageHandler { private final LogDataStorage logDataStorage; // Socket handling - private final int teamNum, remotePort; + private int teamNum, remotePort; private DriverStationSocketHandler socket = null; private final Thread requireNewConnectionThread = new Thread(this::requireNewConnectionThreadRunnable); private boolean requireNewConnection = false; - private boolean useStaticRoboRIOAddress; - private Optional remoteHelpMessages = Optional.empty(); private final Object remoteHelpMessagesLock = new Object(); @@ -69,28 +67,28 @@ public class LocalSystem implements ResponseMessageHandler { * @param console */ public LocalSystem ( - int teamNum, - boolean useStaticAddress, - int remotePort, - LogDataStorage logDataStorage, - ConsoleManager console) { + int teamNum, + int remotePort, + LogDataStorage logDataStorage, + ConsoleManager console + ) { // Instantiating final fields this.teamNum = teamNum; - this.useStaticRoboRIOAddress = useStaticAddress; this.remotePort = remotePort; this.logDataStorage = logDataStorage; this.console = console; interpreter = new LocalCommandInterpreter(this, logDataStorage); - // Start the requireNewConnectionThread, which will attempt to establish a new connection - // whenever it is interrupted - requireNewConnectionThread.start(); - // Attempt to establish socket connection try { establishNewConnection(); } catch (IOException e) { } + + // Start the requireNewConnectionThread, which will attempt to establish a new connection + // whenever it is interrupted + requireNewConnectionThread.start(); + } /** @@ -108,12 +106,15 @@ public synchronized void establishNewConnection () throws IOException { try { socket = new DriverStationSocketHandler( teamNum, - useStaticRoboRIOAddress, remotePort, this::receiveMessage, this::handleSocketReceiverException ); + lastConnectionException = null; + + updateConnectionStatus(ConnectionStatus.OK); + } catch (IOException exception) { // If there's an IOException (one that's different from the previous exception), @@ -130,19 +131,20 @@ public synchronized void establishNewConnection () throws IOException { } } - public void setUseStaticRoborioAddress (boolean useStaticAddress) { - useStaticRoboRIOAddress = useStaticAddress; + public Optional getRoborioHost () { + DriverStationSocketHandler s = socket; + + if (s != null) { + return Optional.of(s.getRoborioHost()); + } else return Optional.empty(); } - public String getRoborioHost () { - return DriverStationSocketHandler.getRoborioHost(useStaticRoboRIOAddress, teamNum) + ":" + remotePort; + public void setTeamNum (int newTeamNum) { + teamNum = newTeamNum; } - /** - * Gets the team number passed in through the constructor. - */ - public int getTeamNum () { - return teamNum; + public void setServerPort (int newPort) { + remotePort = newPort; } /** @@ -150,6 +152,7 @@ public int getTeamNum () { * @return The {@link ConnectionStatus} describing the current connection to the server. */ public ConnectionStatus checkServerConnection () { + // Attempt to send a connection check message to remote try { DriverStationSocketHandler s = throwIfNullSocket(); @@ -305,6 +308,7 @@ private void requireNewConnectionThreadRunnable () { while (true) { // If a new connection needs to be established, try to do that checkServerConnection(); // This will update the requireNewConnection flag + if (requireNewConnection) { try { // Try to establish a new connection diff --git a/lib/app/src/main/java/claw/rct/local/RemoteProcessHandler.java b/lib/app/src/main/java/claw/rct/local/RemoteProcessHandler.java index f150aeb..5c1fdbe 100644 --- a/lib/app/src/main/java/claw/rct/local/RemoteProcessHandler.java +++ b/lib/app/src/main/java/claw/rct/local/RemoteProcessHandler.java @@ -5,9 +5,9 @@ import claw.rct.network.low.ConsoleManager; import claw.rct.network.low.InstructionMessage; -import claw.rct.network.low.KeepaliveWatcher; -import claw.rct.network.low.Waiter; -import claw.rct.network.low.Waiter.NoValueReceivedException; +import claw.rct.network.low.concurrency.KeepaliveWatcher; +import claw.rct.network.low.concurrency.Waiter; +import claw.rct.network.low.concurrency.Waiter.NoValueReceivedException; import claw.rct.network.messages.commands.CommandInputMessage; import claw.rct.network.messages.commands.CommandOutputMessage; import claw.rct.network.messages.commands.ProcessKeepaliveLocal; diff --git a/lib/app/src/main/java/claw/rct/local/RobotControlTerminal.java b/lib/app/src/main/java/claw/rct/local/RobotControlTerminal.java index 1a98001..f60f72e 100644 --- a/lib/app/src/main/java/claw/rct/local/RobotControlTerminal.java +++ b/lib/app/src/main/java/claw/rct/local/RobotControlTerminal.java @@ -7,7 +7,6 @@ import claw.rct.local.LocalSystem.ConnectionStatus; import claw.rct.local.console.LocalConsoleManager; import claw.rct.network.low.ConsoleManager; -import claw.rct.network.low.DriverStationSocketHandler; /** * Represents the main robot control terminal program which starts the driverstation side of the server @@ -33,18 +32,10 @@ public RobotControlTerminal () throws IOException { */ public void start () { - // Check whether to use the static or dynamic address for the roboRIO - boolean useStaticAddress = getUseStaticAddress(); - console.clear(); - - // Display of helpful message with roboRIO host url - String host = DriverStationSocketHandler.getRoborioHost(useStaticAddress, TEAM_NUMBER) + ":" + REMOTE_PORT; - console.printlnSys("Attempting to connect to server at " + host + "..."); - // LocalSystem setup + console.printlnSys("Attempting to connect to roboRIO server..."); LocalSystem system = new LocalSystem( TEAM_NUMBER, - useStaticAddress, REMOTE_PORT, new LogDataStorage(), console @@ -83,31 +74,6 @@ public void start () { } - private boolean getUseStaticAddress () { - console.printlnSys("Use the static (radio) or dynamic (ethernet/usb) IP address for the roboRIO? "); - console.printlnSys("s - Static: " + DriverStationSocketHandler.getRoborioHost(true, TEAM_NUMBER)); - console.printlnSys("d - Dynamic: " + DriverStationSocketHandler.getRoborioHost(false, TEAM_NUMBER)); - - String input = null; - boolean useStaticAddress = true; - - console.printSys("(s | d): "); - - while (input == null) { - input = console.readInputLine().trim().toLowerCase(); - if (input.equals("s")) { - useStaticAddress = true; - } else if (input.equals("d")) { - useStaticAddress = false; - } else { - input = null; - console.printSys("Use 's' for static or 'd' for dynamic. "); - } - } - - return useStaticAddress; - } - private void processCommand (LocalSystem system, String line) { try { system.processCommand(line); diff --git a/lib/app/src/main/java/claw/rct/network/low/DriverStationSocketHandler.java b/lib/app/src/main/java/claw/rct/network/low/DriverStationSocketHandler.java index 1f06d72..6da18a9 100644 --- a/lib/app/src/main/java/claw/rct/network/low/DriverStationSocketHandler.java +++ b/lib/app/src/main/java/claw/rct/network/low/DriverStationSocketHandler.java @@ -1,6 +1,7 @@ package claw.rct.network.low; import java.io.IOException; +import java.net.InetSocketAddress; import java.net.Socket; import java.util.function.Consumer; @@ -24,31 +25,50 @@ public class DriverStationSocketHandler { * @throws IOException If the socket throws an i/o exception. */ public DriverStationSocketHandler ( - int teamNum, - boolean useStaticAddress, - int port, - Consumer responseReader, - Consumer excHandler) - throws IOException { + int teamNum, + int port, + Consumer responseReader, + Consumer excHandler + ) throws IOException { - Socket socket = null; + // Get a socket handler, trying both static and dynamic addresses + SocketHandler handler; try { - socket = new Socket(getRoborioHost(useStaticAddress, teamNum), port); - socketHandler = new SocketHandler(socket, this::receiveMessage, this::handleReceiverIOException); + handler = getSocketHandler(teamNum, false, port); } catch (IOException e) { - if (socket != null) socket.close(); - throw e; + handler = getSocketHandler(teamNum, true, port); } + + this.socketHandler = handler; + this.responseReader = responseReader; this.excHandler = excHandler; } + private SocketHandler getSocketHandler (int teamNum, boolean useStaticAddress, int port) throws IOException { + Socket socket = null; + try { + socket = new Socket(); + socket.connect(new InetSocketAddress(getRoborioHost(useStaticAddress, teamNum), port), 500); + return new SocketHandler(socket, this::receiveMessage, this::handleReceiverIOException); + } catch (IOException e) { + try { + if (socket != null) socket.close(); + } catch (IOException ex) { } + throw e; + } + } + + public String getRoborioHost () { + return socketHandler.getHostname(); + } + /** * Gets the roboRIO host URL. * @param teamNum The team number associated with the roboRIO. * @return The hostname for connecting to the rorboRIO */ - public static String getRoborioHost (boolean useStaticAddress, int teamNum) { + private static String getRoborioHost (boolean useStaticAddress, int teamNum) { if (useStaticAddress) { int first = teamNum / 100; int second = teamNum % 100; @@ -94,7 +114,7 @@ private void handleReceiverIOException (IOException ioException) { * @throws IOException If the socket failed to send the message. */ public void sendInstructionMessage (InstructionMessage message) throws IOException { - socketHandler.sendMessage(message); + socketHandler.sendMessage(message, 3000); } /** diff --git a/lib/app/src/main/java/claw/rct/network/low/RobotSocketHandler.java b/lib/app/src/main/java/claw/rct/network/low/RobotSocketHandler.java index afe1950..f71d9da 100644 --- a/lib/app/src/main/java/claw/rct/network/low/RobotSocketHandler.java +++ b/lib/app/src/main/java/claw/rct/network/low/RobotSocketHandler.java @@ -84,7 +84,7 @@ private void handleReceiverIOException (IOException ioException) { public void sendResponseMessage (ResponseMessage responseMessage) throws IOException { if (clientSocketHandler == null) throw new IOException("No socket to send the response message through"); - clientSocketHandler.sendMessage(responseMessage); + clientSocketHandler.sendMessage(responseMessage, 4000); } /** diff --git a/lib/app/src/main/java/claw/rct/network/low/SocketHandler.java b/lib/app/src/main/java/claw/rct/network/low/SocketHandler.java index 12fa9e9..634bb59 100644 --- a/lib/app/src/main/java/claw/rct/network/low/SocketHandler.java +++ b/lib/app/src/main/java/claw/rct/network/low/SocketHandler.java @@ -5,6 +5,9 @@ import java.net.Socket; import java.util.function.Consumer; +import claw.rct.network.low.concurrency.Waiter; +import claw.rct.network.low.concurrency.Waiter.NoValueReceivedException; + /** * A simple wrapper around {@code Socket} specialized for sending and receiving serializable {@link Message} objects. */ @@ -33,13 +36,47 @@ public SocketHandler ( beginReceivingMessages(); } + public String getHostname () { + return socket.getInetAddress().getHostAddress(); + } + /** * Sends a message through the socket. * @param sendMessage The {@link Message} to send. * @throws IOException If the socket failed to send the {@code Message}. */ - public void sendMessage (Message sendMessage) throws IOException { + public void sendMessage (Message sendMessage, int millisTimeout) throws IOException { + + // Create a timeout waiter + Waiter timeoutWaiter = new Waiter<>(); + + // TODO: Remove the output waiter killer + + // In a separate thread, wait for the timeout + new Thread(() -> { + try { + + // Close the socket output after the millisTimeout + try { + + // Try to wait for the duration of the given timeout + timeoutWaiter.waitForValue(millisTimeout); + + } catch (NoValueReceivedException e) { + + // If the message isn't sent by the timeout, shut down the socket output stream + socket.getOutputStream().close(); + } + + } catch (Throwable t) { } + }); + + // Try to write the message to the output stream this.socket.getOutputStream().write(sendMessage.getData()); + + // After the message is sent (if it's sent before the timeout), send a message to the timeout waiter + // so the socket output stream isn't shut down + timeoutWaiter.receive(new Object()); } private void beginReceivingMessages () throws IOException { diff --git a/lib/app/src/main/java/claw/rct/network/low/KeepaliveWatcher.java b/lib/app/src/main/java/claw/rct/network/low/concurrency/KeepaliveWatcher.java similarity index 97% rename from lib/app/src/main/java/claw/rct/network/low/KeepaliveWatcher.java rename to lib/app/src/main/java/claw/rct/network/low/concurrency/KeepaliveWatcher.java index de636fb..74c02f6 100644 --- a/lib/app/src/main/java/claw/rct/network/low/KeepaliveWatcher.java +++ b/lib/app/src/main/java/claw/rct/network/low/concurrency/KeepaliveWatcher.java @@ -1,4 +1,4 @@ -package claw.rct.network.low; +package claw.rct.network.low.concurrency; import java.time.Instant; diff --git a/lib/app/src/main/java/claw/rct/network/low/Waiter.java b/lib/app/src/main/java/claw/rct/network/low/concurrency/Waiter.java similarity index 65% rename from lib/app/src/main/java/claw/rct/network/low/Waiter.java rename to lib/app/src/main/java/claw/rct/network/low/concurrency/Waiter.java index 3910eee..8090369 100644 --- a/lib/app/src/main/java/claw/rct/network/low/Waiter.java +++ b/lib/app/src/main/java/claw/rct/network/low/concurrency/Waiter.java @@ -1,4 +1,6 @@ -package claw.rct.network.low; +package claw.rct.network.low.concurrency; + +import java.util.Optional; /** * A helper class which allows for {@link Waiter#receive(Object)} and {@link Waiter#waitForValue(long)} to be called on @@ -6,11 +8,13 @@ * through {@code receive}. */ public class Waiter { - + // TODO: Split into ResponseWaiter and Waiter classes for waiting for objects vs. waiting in general + private final Object waiterObject = new Object(); private boolean isWaiting = false; - private boolean hasReceived = false; - private T valueReceived; + + private Optional valueReceived; + private boolean stopWaiting = false; /** * Receives a value so that {@link Waiter#waitForValue()} will finish and return the given value. @@ -21,15 +25,10 @@ public void receive (T value) { if (!isWaiting) return; // Set the value field to match the provided value - valueReceived = value; + valueReceived = Optional.of(value); - // Set the hasReceived field to indicate a value has been received - hasReceived = true; - - // Notify the waiter object so that threads waiting for this value will start up again - synchronized (waiterObject) { - waiterObject.notifyAll(); - } + // Start up waiting threads + startUpWaitingThreads(); } /** @@ -40,8 +39,13 @@ public void kill () { // Do nothing if we are not waiting for a new value if (!isWaiting) return; - // Set hasReceived field to indicate that no value has been received - hasReceived = false; + // Start up waiting threads + startUpWaitingThreads(); + } + + private void startUpWaitingThreads () { + // Set stopWaiting so awoken threads will not start waiting again + stopWaiting = true; // Notify the waiter object so that threads waiting for this value will start up again synchronized (waiterObject) { @@ -72,27 +76,46 @@ public synchronized T waitForValue (long millis) throws NoValueReceivedException isWaiting = true; // Set the hasReceived field to false so that we can later check if it has been set to true by Waiter.receive() - hasReceived = false; + valueReceived = Optional.empty(); // Wait for the given timeout period, or until notified by Waiter.receive() - synchronized (waiterObject) { - try { - if (millis == -1) - waiterObject.wait(); - else - waiterObject.wait(millis); - } catch (InterruptedException e) { } + stopWaiting = false; + + // Calculate an end timestamp for the wait (-1 if the millis wait duration is -1) + long endWaitingTime = millis == -1 ? -1 : System.currentTimeMillis() + millis; + + // Continue waiting until stopped + while (!stopWaiting) { + + // Attempt to wait for the remaining duration, but is is possible an interruption will happen + // without the time going off or anything being received by the waiter + synchronized (waiterObject) { + try { + if (endWaitingTime == -1) { + waiterObject.wait(); + } else { + if (endWaitingTime - System.currentTimeMillis() > 0) { + waiterObject.wait(endWaitingTime - System.currentTimeMillis()); + } + } + } catch (InterruptedException e) { } + } + + // Stop waiting if we have passed the end waiting time (assuming the endWaitingTime is not -1) + if (endWaitingTime != -1 && System.currentTimeMillis() > endWaitingTime) { + stopWaiting = true; + } } // Set isWaiting to false so that new values passed in to Waiter.receive() will not be processed isWaiting = false; // If no value has been received, throw an exception - if (!hasReceived) + if (valueReceived.isEmpty()) throw new NoValueReceivedException(); // Otherwise, the new value can be returned - return valueReceived; + return valueReceived.get(); } /** diff --git a/lib/app/src/main/java/claw/rct/remote/CommandProcessHandler.java b/lib/app/src/main/java/claw/rct/remote/CommandProcessHandler.java index 5db3e5d..be59981 100644 --- a/lib/app/src/main/java/claw/rct/remote/CommandProcessHandler.java +++ b/lib/app/src/main/java/claw/rct/remote/CommandProcessHandler.java @@ -5,10 +5,10 @@ import java.util.function.Consumer; import claw.rct.network.low.ConsoleManager; -import claw.rct.network.low.KeepaliveWatcher; +import claw.rct.network.low.concurrency.KeepaliveWatcher; import claw.rct.network.low.ResponseMessage; -import claw.rct.network.low.Waiter; -import claw.rct.network.low.Waiter.NoValueReceivedException; +import claw.rct.network.low.concurrency.Waiter; +import claw.rct.network.low.concurrency.Waiter.NoValueReceivedException; import claw.rct.network.messages.commands.CommandInputMessage; import claw.rct.network.messages.commands.CommandOutputMessage; import claw.rct.network.messages.commands.ProcessKeepaliveLocal; diff --git a/lib/app/src/main/java/claw/rct/remote/RCTServer.java b/lib/app/src/main/java/claw/rct/remote/RCTServer.java index 2b2c160..a25db98 100644 --- a/lib/app/src/main/java/claw/rct/remote/RCTServer.java +++ b/lib/app/src/main/java/claw/rct/remote/RCTServer.java @@ -1,8 +1,6 @@ package claw.rct.remote; import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; import java.util.ArrayList; import claw.logs.CLAWLogger; @@ -48,6 +46,8 @@ private void waitForConnection () throws IOException { // Establish a new connection serverSocket.getNewConnection(); + System.out.println("DriverStation Robot Control Terminal connected."); + // Try to send a commands listing message try { sendCommandsListingMessage(); @@ -158,12 +158,7 @@ private void handleReceiverException (IOException e) { private void handleNonFatalServerException (IOException e) { // Try to get a new connection to the driverstation try { - StringWriter stringWriter = new StringWriter(); - e.printStackTrace(new PrintWriter(stringWriter)); - String message = "Nonfatal RCT server exception:\n" + stringWriter.toString(); - - System.err.println(message); - LOG.err(message); + System.out.println("DriverStation Robot Control Terminal disconnected."); waitForConnection(); } catch (IOException fatalEx) { diff --git a/lib/app/src/main/java/claw/rct/remote/RemoteCommandInterpreter.java b/lib/app/src/main/java/claw/rct/remote/RemoteCommandInterpreter.java index 2fddd7b..5566785 100644 --- a/lib/app/src/main/java/claw/rct/remote/RemoteCommandInterpreter.java +++ b/lib/app/src/main/java/claw/rct/remote/RemoteCommandInterpreter.java @@ -1,13 +1,11 @@ package claw.rct.remote; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import claw.hardware.Device; +import claw.RobotErrorLog; +import claw.hardware.DIOReadCommand; +import claw.hardware.can.CANScanner; import claw.logs.LogHandler; import claw.rct.commands.CommandLineInterpreter; import claw.rct.commands.CommandProcessor; @@ -15,7 +13,7 @@ import claw.rct.commands.CommandProcessor.BadCallException; import claw.rct.commands.CommandProcessor.CommandFunction; import claw.rct.network.low.ConsoleManager; -import edu.wpi.first.wpilibj.DriverStation; +import claw.subsystems.CLAWSubsystem; /** * This class is meant for CLAW's internal use only. @@ -27,20 +25,15 @@ public RemoteCommandInterpreter () { } private void addCommands () { - addCommand("device", - "device [ list | set | rm | update ]", - "device list : List all devices and their IDs.\n" + - "device set NAME ID : Save a new ID for a particular device NAME.\n" + - "device rm [ NAME | --all | -a ] : Clear a saved ID for a particular device NAME, " + - "or clear all saved IDs with '-a' or '--all'.\n" + - "device update : Update all devices on the robot to use their saved IDs according to 'device list'. " + - "This may only be done when the robot is disabled.", - this::deviceCommand); addCommand("watch", "watch [ --all | --none | log name...]", "Use -a or --all to watch all logs. Use -n or --none to watch no logs. " + "Use 'watch [name]...' to watch only a set of specific logs.", this::watchCommand); + addCommandProcessor(CLAWSubsystem.COMMAND_PROCESSOR); + addCommandProcessor(DIOReadCommand.DIO_RAW_COMMAND_PROCESSOR); + addCommandProcessor(CANScanner.CAN_SCAN_COMMAND_PROCESSOR); + addCommandProcessor(RobotErrorLog.ERROR_LOG_COMMAND_PROCESSOR); } private void addCommand (String command, String usage, String helpDescription, CommandFunction function) { @@ -88,134 +81,4 @@ private void watchCommand (ConsoleManager console, CommandReader reader) throws }); } - private void deviceCommand (ConsoleManager console, CommandReader reader) throws BadCallException { - String operation = reader.readArgOneOf( - "operation", - "The given operation is invalid. Use 'list', 'set', 'rm', or 'update'.", - "list", "set", "rm", "update" - ); - - if (operation.equals("list")) { - reader.noMoreArgs(); - reader.allowNoOptions(); - reader.allowNoFlags(); - - int idColumn = 45; - - // Get all instantiated devices' names and sort them alphabetically - Set deviceNames = Device.getAllDeviceNames(); - ArrayList deviceNamesList = new ArrayList<>(deviceNames); - deviceNamesList.sort(String::compareTo); - - // Message if there are no devices - if (deviceNamesList.size() == 0) { - console.println("No instantiated devices."); - } - - // Loop through all devices and list their saved IDs - for (String deviceName : deviceNamesList) { - // The device name and spacing before the ID - String prefix = deviceName + " : "; - String space = " ".repeat(Math.max(idColumn - prefix.length(), 0)); - - // String representation of the saved device ID - Optional id = Device.getDeviceId(deviceName); - String idString = id.isPresent() ? id.get().toString() : "No saved ID"; - - // Printing a line for the device - console.println(prefix + space + idString); - } - - // Get the set of all device names which have a saved ID but aren't instantiated - Map savedDeviceNamesToIDs = Device.getAllSavedDeviceIDs(); - Set unusedSavedDeviceNames = new HashSet<>(savedDeviceNamesToIDs.keySet()); - unusedSavedDeviceNames.removeIf(deviceNames::contains); - - // List any unused device IDs saved to the roboRIO - if (unusedSavedDeviceNames.size() > 0) { - console.printlnSys(ConsoleManager.formatMessage( - "\nThe following device names and IDs are saved to the roboRIO but " + - "have no matching (instantiated) devices in the robot code. " + - "Unused device settings can be cleared with 'device rm NAME'. " + - "Note that this may happen if you're incorrectly instantiating devices. " + - "All devices should be instantiated as soon as the robot program starts." - )); - - for (String deviceName : unusedSavedDeviceNames) - console.println(deviceName + " ("+savedDeviceNamesToIDs.get(deviceName)+")"); - } - - } else if (operation.equals("set")) { - - // Read the device name and ID to save - String deviceName = reader.readArgString("device name"); - int newID = reader.readArgInt("new ID"); - reader.noMoreArgs(); - reader.allowNoOptions(); - reader.allowNoFlags(); - - // Save the device name and ID - boolean success = Device.saveDeviceID(deviceName, Optional.of(newID)); - if (success) { - console.println("Saved successfully: set device ID."); - } else { - console.printlnErr("Error saving: failed to set device ID."); - } - - } else if (operation.equals("rm")) { - - // Check if all device IDs should be removed - reader.allowFlags('a'); - reader.allowOptions("all"); - boolean removeAll = reader.getFlag('a') || reader.getOptionMarker("all"); - - if (removeAll) { - // Clear all saved IDs - reader.noMoreArgs(); - if (Device.clearAllSavedIDs()) { - console.println("Saved successfully: cleared all saved device IDs."); - } else { - console.printlnErr("Error saving: failed to clear all device IDs."); - } - } else { - // Get the device name to clear the ID from - Set savedDeviceNames = Device.getAllSavedDeviceIDs().keySet(); - String deviceName = reader.readArgOneOf( - "device name", - "Expected the name of a device name associated with a saved ID.", - savedDeviceNames - ); - - reader.noMoreArgs(); - - // Clear the device ID from the save - if (Device.saveDeviceID(deviceName, Optional.empty())) { - console.println("Saved successfully: cleared device ID."); - } else { - console.printlnErr("Error saving: failed to clear device ID."); - } - } - - } else if (operation.equals("update")) { - - reader.noMoreArgs(); - reader.allowNoOptions(); - reader.allowNoFlags(); - - // Ensure the robot is disabled - if (DriverStation.isEnabled()) { - console.printlnErr(ConsoleManager.formatMessage( - "The robot must be disabled before reinitializing devices. " + - "Reinitializing a device while enabled could cause damage to " + - "the robot or unexpected behavior." - )); - return; - } - - Device.reinitializeAllDevices(); - console.println("Devices have been reinitialized with their saved IDs."); - - } - } - } diff --git a/lib/app/src/main/java/claw/subsystems/CLAWSubsystem.java b/lib/app/src/main/java/claw/subsystems/CLAWSubsystem.java new file mode 100644 index 0000000..17f6ff5 --- /dev/null +++ b/lib/app/src/main/java/claw/subsystems/CLAWSubsystem.java @@ -0,0 +1,126 @@ +package claw.subsystems; + +import java.util.HashMap; +import java.util.Set; + +import claw.rct.commands.CommandProcessor; +import claw.rct.commands.CommandReader; +import claw.rct.commands.CommandProcessor.BadCallException; +import claw.rct.network.low.ConsoleManager; +import edu.wpi.first.wpilibj2.command.SubsystemBase; + +public abstract class CLAWSubsystem extends SubsystemBase { + + private static final HashMap subsystems = new HashMap<>(); + + public static final CommandProcessor COMMAND_PROCESSOR = new CommandProcessor( + "subsystem", + "subsystem [ list | inspect | test]", + "Use 'subsystem list' to list all subsystems. 'subsystem inspect NAME' will retrieve the available tests for a given " + + "subsystem. 'subsystem test SUBNAME TESTNAME' will run a test named TESTNAME on the given subsystem SUBNAME.", + CLAWSubsystem::subsystemCommand + ); + + private final HashMap subsystemTests = new HashMap<>(); + + public CLAWSubsystem () { + synchronized (subsystems) { + // Both necessary for the subsystems map and for helping protect users from multiple subsystem instantiations + if (subsystems.containsKey(getName())) + throw new IllegalArgumentException("Subsystem with name '"+getName()+"' cannot be instantiated more than once."); + subsystems.put(getName(), this); + } + } + + private static void subsystemCommand (ConsoleManager console, CommandReader reader) throws BadCallException { + reader.allowNoOptions(); + reader.allowNoFlags(); + String operation = reader.readArgOneOf("operation", "Expected 'list', 'inspect' or 'test'.", "list", "inspect", "test"); + + if (operation.equals("list")) { + + // List all the instantiated subsystems + + reader.noMoreArgs(); + + synchronized (subsystems) { + subsystems.keySet().forEach(console::println); + if (subsystems.size() == 0) + console.println("There are no instantiated CLAWSubsystems."); + } + + } else { + + // Operations which are performed on a specific subsystem + + // Get the CLAWSubsystem + CLAWSubsystem subsystem; + synchronized (subsystems) { + // Get the subsystem name + String subsystemName = reader.readArgOneOf("subsystem name", "Expected the name of an exsiting subsystem.", subsystems.keySet()); + + // Get the subsystem and print its supported tests + subsystem = subsystems.get(subsystemName); + } + + // Switch based on operation + if (operation.equals("inspect")) { + + reader.noMoreArgs(); + + // Show available tests for the subsystem + if (subsystem.subsystemTests.size() == 0) { + console.println(subsystem.getName()+" supports no tests."); + } else { + console.println(subsystem.getName()+" supports the following tests:"); + synchronized (subsystem.subsystemTests) { + subsystem.subsystemTests.keySet().forEach(console::println); + } + } + + } else if (operation.equals("test")) { + + // Run a subsystem test + + // Get the test specified by the command + SubsystemTest test; + synchronized (subsystem.subsystemTests) { + // Get a test name to run + Set testNames = subsystem.subsystemTests.keySet(); + String testName = reader.readArgOneOf( + "test name", + "Expected a test name from the set of tests supported by the subsystem.", + testNames + ); + reader.noMoreArgs(); + + test = subsystem.subsystemTests.get(testName); + } + + // Run the subsystem test + test.run(console, subsystem); + + } + + } + + } + + /** + * Bind some {@link SubsystemTest}s to this subsystem so they can be easily run through the Robot Control Terminal. + * Make sure that only this subsystem is every controlled by these tests. + * @param tests The {@code SubsystemTest}s to bind to this subsystem. + */ + protected void addTests (SubsystemTest... tests) { + synchronized (subsystemTests) { + for (SubsystemTest test : tests) + subsystemTests.put(test.getName(), test); + } + } + + /** + * Stop all subsystem actuation and put the subsystem into a safe state. + */ + public abstract void stop (); + +} diff --git a/lib/app/src/main/java/claw/subsystems/SubsystemTest.java b/lib/app/src/main/java/claw/subsystems/SubsystemTest.java new file mode 100644 index 0000000..ef7fe89 --- /dev/null +++ b/lib/app/src/main/java/claw/subsystems/SubsystemTest.java @@ -0,0 +1,142 @@ +package claw.subsystems; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; + +import claw.LiveValues; +import claw.rct.network.low.ConsoleManager; +import edu.wpi.first.wpilibj.DriverStation; +import edu.wpi.first.wpilibj2.command.Command; +import edu.wpi.first.wpilibj2.command.Subsystem; +import edu.wpi.first.wpilibj2.command.Command.InterruptionBehavior; + +/** + * Represents a simple test bound to a subsystem which can be run through the Robot Control Terminal. + * @see CLAWSubsystem#addTests(SubsystemTest...) + */ +public class SubsystemTest { + + private final String name, description; + private final Consumer periodicExecute; + + /** + * Create a new {@link SubsystemTest}. A subsystem test should only ever control one subsystem to which it belongs. + * Never add a subsystem test to a subsystem which it does not belong to. + * @param name A name to identify the particular test by. + * @param description A description of what the test does and how it can be used. + * @param periodicExecute A {@link LiveValues} consumer which will be run periodically (generally around once every 20ms) + * to control the subsystem and perform the test's operations. The {@code LiveValues} can be used to display telemetry fields + * in the console as the test runs. + */ + public SubsystemTest (String name, String description, Consumer periodicExecute) { + this.name = name; + this.description = description; + this.periodicExecute = periodicExecute; + } + + /** + * Get the {@link SubsystemTest}'s name. + * @return The name of this test. + */ + public String getName () { + return name; + } + + private class SubsystemTestCommand implements Command { + + private final LiveValues values = new LiveValues(); + private final CLAWSubsystem subsystem; + + public SubsystemTestCommand (CLAWSubsystem subsystem) { + this.subsystem = subsystem; + } + + @Override + public String getName () { + return "SubsystemTestCommand<"+subsystem.getName()+">(\""+name+"\")"; + } + + @Override + public Set getRequirements () { + HashSet reqs = new HashSet<>(); + reqs.add(subsystem); + return reqs; + } + + @Override + public void initialize () { + subsystem.stop(); + } + + @Override + public void execute () { + periodicExecute.accept(values); + } + + @Override + public void end (boolean interrupted) { + subsystem.stop(); + } + } + + private static boolean getYesNo (ConsoleManager console, String prompt) { + Optional answer = Optional.empty(); + + console.println(""); + + while (answer.isEmpty()) { + console.moveUp(1); + console.clearLine(); + console.print(prompt + "(yes | no) "); + + String input = console.readInputLine().strip().toUpperCase(); + + answer = input.equals("YES") ? Optional.of(true) + : (input.equals("NO") ? Optional.of(false) + : Optional.empty()); + } + + return answer.get(); + } + + /** + * Run the subsystem command through a given console. + * @param console + */ + void run (ConsoleManager console, CLAWSubsystem subsystem) { + + // Display description and an important safety warning + console.println("Double-tap enter to disable the robot and stop the test command at any time."); + + console.println("Usage and description:"); + console.println(ConsoleManager.formatMessage(description, 2)); + + // Wait until input after the driverstation is enabled + boolean runCommand = getYesNo(console, "Run the command? "); + if (!runCommand) return; + + while (DriverStation.isDisabled()) { + console.printlnErr("Enable the robot and try again."); + runCommand = getYesNo(console, "Run the command? "); + if (!runCommand) return; + } + + // Run the command + SubsystemTestCommand command = new SubsystemTestCommand(subsystem); + command.withInterruptBehavior(InterruptionBehavior.kCancelIncoming).schedule(); + console.printlnSys("\nRunning test command"); + + while (DriverStation.isEnabled()) { + command.values.update(console); + console.flush(); + } + + // Stop the command + console.printlnSys("\nStopping command"); + command.cancel(); + + } + +} diff --git a/test-bot/src/main/java/frc/robot/Robot.java b/test-bot/src/main/java/frc/robot/Robot.java index 6526c4f..98f92dc 100644 --- a/test-bot/src/main/java/frc/robot/Robot.java +++ b/test-bot/src/main/java/frc/robot/Robot.java @@ -4,6 +4,8 @@ package frc.robot; +import com.ctre.phoenix.motorcontrol.can.WPI_TalonSRX; + import claw.CLAWRobot; import edu.wpi.first.wpilibj.TimedRobot; import edu.wpi.first.wpilibj2.command.Command; @@ -19,6 +21,7 @@ public class Robot extends TimedRobot { private Command m_autonomousCommand; private RobotContainer m_robotContainer; + private final WPI_TalonSRX talon = new WPI_TalonSRX(25); @Override public void startCompetition () { @@ -72,7 +75,11 @@ public void teleopInit() { /** This function is called periodically during operator control. */ @Override - public void teleopPeriodic() {} + public void teleopPeriodic() { + talon.set(0.4); + talon.isFwdLimitSwitchClosed(); + talon.clearStickyFaults(); + } @Override public void testInit() { diff --git a/test-bot/src/main/java/frc/robot/commands/TestCommand.java b/test-bot/src/main/java/frc/robot/commands/TestCommand.java index 8627bc1..a5ef407 100644 --- a/test-bot/src/main/java/frc/robot/commands/TestCommand.java +++ b/test-bot/src/main/java/frc/robot/commands/TestCommand.java @@ -1,6 +1,7 @@ package frc.robot.commands; import claw.logs.CLAWLogger; +import claw.RobotErrorLog; import edu.wpi.first.wpilibj2.command.CommandBase; import frc.robot.subsystems.TestSubsystem; @@ -18,18 +19,17 @@ public TestCommand (TestSubsystem subsystem) { @Override public void initialize () { LOG.out("Initializing TestCommand"); - subsystem.stop(); + RobotErrorLog.logWarning("This is a warning"); } @Override public void execute () { - subsystem.set(0.4); + } @Override public void end (boolean interrupted) { LOG.out("Ending TestCommand"); - subsystem.stop(); } @Override diff --git a/test-bot/src/main/java/frc/robot/subsystems/TestSubsystem.java b/test-bot/src/main/java/frc/robot/subsystems/TestSubsystem.java index 4e10cf7..801fe20 100644 --- a/test-bot/src/main/java/frc/robot/subsystems/TestSubsystem.java +++ b/test-bot/src/main/java/frc/robot/subsystems/TestSubsystem.java @@ -1,39 +1,8 @@ package frc.robot.subsystems; -import claw.hardware.Device; import edu.wpi.first.wpilibj2.command.SubsystemBase; -import frc.robot.util.CustomMotorController; -import frc.robot.util.TestDigitalInput; public class TestSubsystem extends SubsystemBase { - private final Device armMotor = new Device<>( - "CAN.MOTOR_CONTROLLER.TEST_SYSTEM.ARM", - id -> new CustomMotorController(id), - motor -> motor.stopMotor() - ); - - private final Device armUpperLimitSwitch = new Device<>( - "DIO.LIMIT_SWITCH.TEST_SYSTEM.UPPER_ARM", - id -> new TestDigitalInput(id), - digitalInput -> digitalInput.close() - ); - - private final Device armLowerLimitSwitch = new Device<>( - "DIO.LIMIT_SWITCH.TEST_SYSTEM.LOWER_ARM", - id -> new TestDigitalInput(id), - digitalInput -> digitalInput.close() - ); - - public void set (double speed) { - if (armUpperLimitSwitch.get().get()) - armMotor.get().stopMotor(); - else - armMotor.get().set(speed); - } - - public void stop () { - armMotor.get().stopMotor(); - } } diff --git a/test-bot/vendordeps/Phoenix.json b/test-bot/vendordeps/Phoenix.json new file mode 100644 index 0000000..848dedc --- /dev/null +++ b/test-bot/vendordeps/Phoenix.json @@ -0,0 +1,423 @@ +{ + "fileName": "Phoenix.json", + "name": "CTRE-Phoenix (v5)", + "version": "5.30.4+23.0.10", + "frcYear": 2023, + "uuid": "ab676553-b602-441f-a38d-f1296eff6537", + "mavenUrls": [ + "https://maven.ctr-electronics.com/release/" + ], + "jsonUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix/Phoenix5-frc2023-latest.json", + "javaDependencies": [ + { + "groupId": "com.ctre.phoenix", + "artifactId": "api-java", + "version": "5.30.4" + }, + { + "groupId": "com.ctre.phoenix", + "artifactId": "wpiapi-java", + "version": "5.30.4" + } + ], + "jniDependencies": [ + { + "groupId": "com.ctre.phoenix", + "artifactId": "cci", + "version": "5.30.4", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxathena" + ], + "simMode": "hwsim" + }, + { + "groupId": "com.ctre.phoenix.sim", + "artifactId": "cci-sim", + "version": "5.30.4", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenixpro", + "artifactId": "tools", + "version": "23.0.10", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxathena" + ], + "simMode": "hwsim" + }, + { + "groupId": "com.ctre.phoenixpro.sim", + "artifactId": "tools-sim", + "version": "23.0.10", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenixpro.sim", + "artifactId": "simTalonSRX", + "version": "23.0.10", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenixpro.sim", + "artifactId": "simTalonFX", + "version": "23.0.10", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenixpro.sim", + "artifactId": "simVictorSPX", + "version": "23.0.10", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenixpro.sim", + "artifactId": "simPigeonIMU", + "version": "23.0.10", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenixpro.sim", + "artifactId": "simCANCoder", + "version": "23.0.10", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenixpro.sim", + "artifactId": "simProTalonFX", + "version": "23.0.10", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenixpro.sim", + "artifactId": "simProCANcoder", + "version": "23.0.10", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenixpro.sim", + "artifactId": "simProPigeon2", + "version": "23.0.10", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + } + ], + "cppDependencies": [ + { + "groupId": "com.ctre.phoenix", + "artifactId": "wpiapi-cpp", + "version": "5.30.4", + "libName": "CTRE_Phoenix_WPI", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxathena" + ], + "simMode": "hwsim" + }, + { + "groupId": "com.ctre.phoenix", + "artifactId": "api-cpp", + "version": "5.30.4", + "libName": "CTRE_Phoenix", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxathena" + ], + "simMode": "hwsim" + }, + { + "groupId": "com.ctre.phoenix", + "artifactId": "cci", + "version": "5.30.4", + "libName": "CTRE_PhoenixCCI", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxathena" + ], + "simMode": "hwsim" + }, + { + "groupId": "com.ctre.phoenixpro", + "artifactId": "tools", + "version": "23.0.10", + "libName": "CTRE_PhoenixTools", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxathena" + ], + "simMode": "hwsim" + }, + { + "groupId": "com.ctre.phoenix.sim", + "artifactId": "wpiapi-cpp-sim", + "version": "5.30.4", + "libName": "CTRE_Phoenix_WPISim", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix.sim", + "artifactId": "api-cpp-sim", + "version": "5.30.4", + "libName": "CTRE_PhoenixSim", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix.sim", + "artifactId": "cci-sim", + "version": "5.30.4", + "libName": "CTRE_PhoenixCCISim", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenixpro.sim", + "artifactId": "tools-sim", + "version": "23.0.10", + "libName": "CTRE_PhoenixTools_Sim", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenixpro.sim", + "artifactId": "simTalonSRX", + "version": "23.0.10", + "libName": "CTRE_SimTalonSRX", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenixpro.sim", + "artifactId": "simTalonFX", + "version": "23.0.10", + "libName": "CTRE_SimTalonFX", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenixpro.sim", + "artifactId": "simVictorSPX", + "version": "23.0.10", + "libName": "CTRE_SimVictorSPX", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenixpro.sim", + "artifactId": "simPigeonIMU", + "version": "23.0.10", + "libName": "CTRE_SimPigeonIMU", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenixpro.sim", + "artifactId": "simCANCoder", + "version": "23.0.10", + "libName": "CTRE_SimCANCoder", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenixpro.sim", + "artifactId": "simProTalonFX", + "version": "23.0.10", + "libName": "CTRE_SimProTalonFX", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenixpro.sim", + "artifactId": "simProCANcoder", + "version": "23.0.10", + "libName": "CTRE_SimProCANcoder", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenixpro.sim", + "artifactId": "simProPigeon2", + "version": "23.0.10", + "libName": "CTRE_SimProPigeon2", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + } + ] +} \ No newline at end of file diff --git a/vendordeps/CLAW-2023.1.2-alpha.json b/vendordeps/CLAW-2023.1.3-beta.json similarity index 60% rename from vendordeps/CLAW-2023.1.2-alpha.json rename to vendordeps/CLAW-2023.1.3-beta.json index 565bb56..564d1b3 100644 --- a/vendordeps/CLAW-2023.1.2-alpha.json +++ b/vendordeps/CLAW-2023.1.3-beta.json @@ -1,17 +1,17 @@ { - "fileName": "CLAW-2023.1.2-alpha.json", + "fileName": "CLAW-2023.1.3-beta.json", "name": "CLAW", - "version": "2023.1.2-alpha", - "uuid": "935d25f6-5774-4148-b87a-9f803cc91b00", - "jsonUrl": "https://raw.githubusercontent.com/frc1711/CLAW/v2023.1.2-alpha/vendordeps/CLAW-2023.1.2-alpha.json", + "version": "2023.1.3-beta", + "uuid": "57a8af13-8c4a-4143-98bf-0906db40bb07", + "jsonUrl": "https://raw.githubusercontent.com/frc1711/CLAW/v2023.1.3-beta/vendordeps/CLAW-2023.1.3-beta.json", "mavenUrls": [ - "https://raw.githubusercontent.com/frc1711/CLAW/v2023.1.2-alpha/lib/app/build/maven-repo" + "https://raw.githubusercontent.com/frc1711/CLAW/v2023.1.3-beta/lib/app/build/maven-repo" ], "javaDependencies": [ { "groupId": "org.frc.raptors1711", "artifactId": "raptors-claw", - "version": "0" + "version": "0" } ], "jniDependencies": [], diff --git a/vendordeps/CLAW-testing.json b/vendordeps/CLAW-testing.json new file mode 100644 index 0000000..93bc4fe --- /dev/null +++ b/vendordeps/CLAW-testing.json @@ -0,0 +1,17 @@ +{ + "fileName": "CLAW-testing.json", + "name": "CLAW", + "uuid": "935d25f6-5774-4148-b87a-9f803cc91b00", + "mavenUrls": [ + "https://raw.githubusercontent.com/frc1711/CLAW/feature/subsystems-testing-gabriel/lib/app/build/maven-repo" + ], + "javaDependencies": [ + { + "groupId": "org.frc.raptors1711", + "artifactId": "raptors-claw", + "version": "0" + } + ], + "jniDependencies": [], + "cppDependencies": [] +}