mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-25 01:41:43 +00:00
Compare commits
212 Commits
v2024.1.1-
...
v2024.3.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3116f790ea | ||
|
|
0e013dc021 | ||
|
|
f74f6f1d42 | ||
|
|
11c60df3e0 | ||
|
|
3d9152a461 | ||
|
|
fbd239d15e | ||
|
|
7cd4a75323 | ||
|
|
973bb55e66 | ||
|
|
7bd8c44570 | ||
|
|
18e57f7872 | ||
|
|
ccb4cbed63 | ||
|
|
c19ee8b0fe | ||
|
|
e64c20346d | ||
|
|
f1a1ffd7fc | ||
|
|
c27ddf5ef9 | ||
|
|
8b669330eb | ||
|
|
ca6e307ea5 | ||
|
|
607682b687 | ||
|
|
4b94a64b06 | ||
|
|
63d9e945b8 | ||
|
|
0ad6b3acb3 | ||
|
|
02aed35c6e | ||
|
|
a8a352ed8c | ||
|
|
0cdab55e5b | ||
|
|
ba15844c28 | ||
|
|
6afff99640 | ||
|
|
d4d0545dc1 | ||
|
|
6b6a55b72e | ||
|
|
1e168f363e | ||
|
|
da3abade83 | ||
|
|
62cba9a4d3 | ||
|
|
3207795d0d | ||
|
|
e506e09a06 | ||
|
|
163f7ee704 | ||
|
|
e9c744c456 | ||
|
|
300419c151 | ||
|
|
1db3936965 | ||
|
|
6cc7e52de7 | ||
|
|
d4533a8900 | ||
|
|
90bb6cfffa | ||
|
|
cb094e4ff6 | ||
|
|
60c6ed9812 | ||
|
|
ee15cc172a | ||
|
|
1016e95242 | ||
|
|
19f1903959 | ||
|
|
53ebb6679e | ||
|
|
177132fa2a | ||
|
|
bbb230491a | ||
|
|
84ef71ace0 | ||
|
|
68736d802d | ||
|
|
d895a0c09f | ||
|
|
64a9d413bf | ||
|
|
a70e83ae2e | ||
|
|
47652d7a3c | ||
|
|
be78552db7 | ||
|
|
3acae550d6 | ||
|
|
9d55941ce5 | ||
|
|
51d92c7027 | ||
|
|
9206b47d67 | ||
|
|
6fc16264ce | ||
|
|
ad18f35477 | ||
|
|
0c6bd846bc | ||
|
|
19c1556472 | ||
|
|
3928ed5647 | ||
|
|
e408f3ad27 | ||
|
|
7957f4a625 | ||
|
|
1241dfdf68 | ||
|
|
4d109309c9 | ||
|
|
7560d18e09 | ||
|
|
f518e143d0 | ||
|
|
6fab87fa4c | ||
|
|
42c41785ac | ||
|
|
1a7eeb6282 | ||
|
|
98c0827236 | ||
|
|
57aa8ca0dd | ||
|
|
789af2ad26 | ||
|
|
9a5366bb83 | ||
|
|
77c09b9ce2 | ||
|
|
9ec27c1202 | ||
|
|
d653408873 | ||
|
|
24a24c9051 | ||
|
|
0e5eb3f35c | ||
|
|
4b15c73f64 | ||
|
|
a274e297cd | ||
|
|
d198605562 | ||
|
|
dfaad7ca22 | ||
|
|
2df82ec957 | ||
|
|
3661f485af | ||
|
|
7f9389f101 | ||
|
|
ca35bcd827 | ||
|
|
9227d09960 | ||
|
|
370126db38 | ||
|
|
1330235918 | ||
|
|
d392570659 | ||
|
|
a2d45dbca4 | ||
|
|
30965b20cf | ||
|
|
5bc942f532 | ||
|
|
97828bd325 | ||
|
|
6da21c4943 | ||
|
|
ecf1755e3e | ||
|
|
154d920e67 | ||
|
|
d2ee423749 | ||
|
|
7e3678b0a4 | ||
|
|
4a55d830e4 | ||
|
|
420020c0d5 | ||
|
|
077c8f4092 | ||
|
|
84e3a22baa | ||
|
|
b482321c0d | ||
|
|
d181e353a0 | ||
|
|
2386e44f3a | ||
|
|
fa5b604f16 | ||
|
|
67e8306819 | ||
|
|
1981b8debd | ||
|
|
ba9c21cf38 | ||
|
|
211c2a375c | ||
|
|
75b2fa1cc3 | ||
|
|
84b089b209 | ||
|
|
ce550705d7 | ||
|
|
3989617bde | ||
|
|
f1836e1321 | ||
|
|
d05f179a9a | ||
|
|
b1b03bed85 | ||
|
|
fa63fbf446 | ||
|
|
4809f3d0fc | ||
|
|
dd90965362 | ||
|
|
8659372d08 | ||
|
|
a2e4d0b15d | ||
|
|
0a46a3a618 | ||
|
|
7c26bc70ab | ||
|
|
f94e3d81b9 | ||
|
|
6bed82a18e | ||
|
|
4595f84719 | ||
|
|
707cb06105 | ||
|
|
3e40b9e5da | ||
|
|
106518c3f8 | ||
|
|
19cb2a8eb4 | ||
|
|
13f4460e00 | ||
|
|
4210f5635d | ||
|
|
0f060afb55 | ||
|
|
f29a7d2e50 | ||
|
|
6e58db398d | ||
|
|
4ac0720385 | ||
|
|
44db3e0ac0 | ||
|
|
73c7d87db7 | ||
|
|
25636b712f | ||
|
|
01fb98baaa | ||
|
|
5c424248c4 | ||
|
|
c486972c55 | ||
|
|
783acb9b72 | ||
|
|
99ab836894 | ||
|
|
ad0859a8c9 | ||
|
|
5579219716 | ||
|
|
98f06911c7 | ||
|
|
e1d49b975c | ||
|
|
8a0bf2b7a4 | ||
|
|
91d8837c11 | ||
|
|
e7c9f27683 | ||
|
|
8aca706217 | ||
|
|
7d3e4ddba9 | ||
|
|
ec3cb3dcba | ||
|
|
495585b25d | ||
|
|
f9aabc5ab2 | ||
|
|
c16946c0ec | ||
|
|
b7f4eb2811 | ||
|
|
f419a62b38 | ||
|
|
938bf45fd9 | ||
|
|
c34debe012 | ||
|
|
07183765de | ||
|
|
af46034b7f | ||
|
|
636ef58d94 | ||
|
|
cc631d2a69 | ||
|
|
09f76b32c2 | ||
|
|
47c5fd8620 | ||
|
|
24a76be694 | ||
|
|
9333951736 | ||
|
|
6a2d3c30a6 | ||
|
|
e07de37e64 | ||
|
|
141241d2d6 | ||
|
|
f2c2bab7dc | ||
|
|
5659038443 | ||
|
|
8aeee03626 | ||
|
|
55508706ff | ||
|
|
ab78b930e9 | ||
|
|
795d4be9fd | ||
|
|
7aa9ad44b8 | ||
|
|
92c81d0791 | ||
|
|
1ce617be07 | ||
|
|
2441b57156 | ||
|
|
21d1972d7a | ||
|
|
c29e8c66cf | ||
|
|
ab309e34ef | ||
|
|
22a322c9f3 | ||
|
|
1dba26c937 | ||
|
|
ef1cb3f41e | ||
|
|
aeb1a4aa33 | ||
|
|
c1178d5add | ||
|
|
4e4a468d4d | ||
|
|
d1793f077d | ||
|
|
43fb6e9f87 | ||
|
|
bcef6c5398 | ||
|
|
4059e0cd9f | ||
|
|
0b2cfb3abc | ||
|
|
df5e439b0c | ||
|
|
0ff7478968 | ||
|
|
6f23d32fe1 | ||
|
|
35a1c52788 | ||
|
|
e4e2bafdb1 | ||
|
|
3d201c71f7 | ||
|
|
f02984159f | ||
|
|
a004c9e05f | ||
|
|
0b4c6a1546 | ||
|
|
ab15dae887 |
@@ -145,6 +145,8 @@ LambdaBodyIndentation: Signature
|
||||
LineEnding: DeriveLF
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
Macros:
|
||||
- 'HAL_ENUM(name)=enum name'
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Never
|
||||
|
||||
@@ -49,7 +49,6 @@ Checks:
|
||||
google-build-namespaces,
|
||||
google-explicit-constructor,
|
||||
google-global-names-in-headers,
|
||||
google-readability-avoid-underscore-in-googletest-name,
|
||||
google-readability-casting,
|
||||
google-runtime-operator,
|
||||
misc-definitions-in-headers,
|
||||
|
||||
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -1,4 +1,7 @@
|
||||
*.cpp text eol=lf
|
||||
*.gradle text eol=lf
|
||||
*.h text eol=lf
|
||||
*.inc text eol=lf
|
||||
*.java text eol=lf
|
||||
*.json text eol=lf
|
||||
*.md text eol=lf
|
||||
|
||||
23
.github/workflows/cmake.yml
vendored
23
.github/workflows/cmake.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
|
||||
- name: Install QuickBuffers (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: wget https://github.com/HebiRobotics/QuickBuffers/releases/download/1.3.2/protoc-gen-quickbuf_1.3.2_amd64.deb && sudo apt install ./protoc-gen-quickbuf_1.3.2_amd64.deb
|
||||
run: wget https://github.com/HebiRobotics/QuickBuffers/releases/download/1.3.3/protoc-gen-quickbuf_1.3.3_amd64.deb && sudo apt install ./protoc-gen-quickbuf_1.3.3_amd64.deb
|
||||
|
||||
- name: Install opencv (macOS)
|
||||
run: brew install opencv protobuf@3 ninja
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
- name: Set up Python 3.8 (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.8
|
||||
|
||||
@@ -88,28 +88,13 @@ jobs:
|
||||
uses: lukka/run-vcpkg@v11.1
|
||||
with:
|
||||
vcpkgDirectory: ${{ runner.workspace }}/vcpkg
|
||||
vcpkgGitCommitId: 78b61582c9e093fda56a01ebb654be15a0033897 # HEAD on 2023-08-6
|
||||
vcpkgGitCommitId: 37c3e63a1306562f7f59c4c3c8892ddd50fdf992 # HEAD on 2024-02-24
|
||||
|
||||
- name: configure
|
||||
run: cmake -S . -B build -G "Ninja" -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache -DCMAKE_BUILD_TYPE=Release -DWITH_JAVA=OFF -DWITH_EXAMPLES=ON -DUSE_SYSTEM_FMTLIB=ON -DUSE_SYSTEM_LIBUV=ON -DUSE_SYSTEM_EIGEN=ON -DCMAKE_TOOLCHAIN_FILE=${{ runner.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_INSTALL_OPTIONS=--clean-after-build -DVCPKG_TARGET_TRIPLET=x64-windows-release -DVCPKG_HOST_TRIPLET=x64-windows-release
|
||||
run: cmake -S . -B build -G "Ninja" -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache -DCMAKE_BUILD_TYPE=Release -DWITH_JAVA=OFF -DWITH_EXAMPLES=ON -DUSE_SYSTEM_FMTLIB=ON -DUSE_SYSTEM_LIBUV=ON -DUSE_SYSTEM_EIGEN=OFF -DCMAKE_TOOLCHAIN_FILE=${{ runner.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_INSTALL_OPTIONS=--clean-after-build -DVCPKG_TARGET_TRIPLET=x64-windows-release -DVCPKG_HOST_TRIPLET=x64-windows-release
|
||||
env:
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
|
||||
# Build wpiutil at full speed, wpimath depends on wpiutil
|
||||
- name: build wpiutil
|
||||
working-directory: build
|
||||
run: cmake --build . --parallel $(nproc) --target wpiutil/all
|
||||
env:
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
|
||||
# Build wpimath slow to prevent OOM
|
||||
- name: build wpimath
|
||||
working-directory: build
|
||||
run: cmake --build . --parallel 1 --target wpimath/all
|
||||
env:
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
|
||||
# Build everything else fast
|
||||
- name: build
|
||||
working-directory: build
|
||||
run: cmake --build . --parallel $(nproc)
|
||||
|
||||
8
.github/workflows/comment-command.yml
vendored
8
.github/workflows/comment-command.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: React Rocket
|
||||
uses: actions/github-script@v6
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const {owner, repo} = context.issue
|
||||
@@ -34,14 +34,14 @@ jobs:
|
||||
GITHUB_TOKEN: "${{ secrets.COMMENT_COMMAND_PAT_TOKEN }}"
|
||||
NUMBER: ${{ github.event.issue.number }}
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
- name: Install wpiformat
|
||||
run: pip3 install wpiformat
|
||||
- name: Run wpiformat
|
||||
|
||||
2
.github/workflows/documentation.yml
vendored
2
.github/workflows/documentation.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-java@v3
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 13
|
||||
|
||||
26
.github/workflows/gradle.yml
vendored
26
.github/workflows/gradle.yml
vendored
@@ -54,7 +54,7 @@ jobs:
|
||||
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
- name: Check free disk space
|
||||
run: df .
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact-name }}
|
||||
path: build/allOutputs
|
||||
@@ -70,13 +70,13 @@ jobs:
|
||||
artifact-name: Win64Debug
|
||||
architecture: x64
|
||||
task: "build"
|
||||
build-options: "-PciDebugOnly --max-workers 1"
|
||||
build-options: "-PciDebugOnly"
|
||||
outputs: "build/allOutputs"
|
||||
build-dir: "c:\\work"
|
||||
- os: windows-2022
|
||||
artifact-name: Win64Release
|
||||
architecture: x64
|
||||
build-options: "-PciReleaseOnly --max-workers 1"
|
||||
build-options: "-PciReleaseOnly"
|
||||
task: "copyAllOutputs"
|
||||
outputs: "build/allOutputs"
|
||||
build-dir: "c:\\work"
|
||||
@@ -84,13 +84,13 @@ jobs:
|
||||
artifact-name: WinArm64Debug
|
||||
architecture: x64
|
||||
task: "build"
|
||||
build-options: "-PciDebugOnly -Pbuildwinarm64 -Ponlywindowsarm64 --max-workers 1"
|
||||
build-options: "-PciDebugOnly -Pbuildwinarm64 -Ponlywindowsarm64"
|
||||
outputs: "build/allOutputs"
|
||||
build-dir: "c:\\work"
|
||||
- os: windows-2022
|
||||
artifact-name: WinArm64Release
|
||||
architecture: x64
|
||||
build-options: "-PciReleaseOnly -Pbuildwinarm64 -Ponlywindowsarm64 --max-workers 1"
|
||||
build-options: "-PciReleaseOnly -Pbuildwinarm64 -Ponlywindowsarm64"
|
||||
task: "copyAllOutputs"
|
||||
outputs: "build/allOutputs"
|
||||
build-dir: "c:\\work"
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v3
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 17
|
||||
@@ -159,7 +159,7 @@ jobs:
|
||||
- name: Check disk free space (Windows)
|
||||
run: wmic logicaldisk get caption, freespace
|
||||
if: matrix.os == 'windows-2022'
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact-name }}
|
||||
path: ${{ matrix.build-dir }}/${{ matrix.outputs }}
|
||||
@@ -171,10 +171,10 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v3
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 13
|
||||
java-version: 17
|
||||
- name: Set release environment variable
|
||||
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
@@ -183,7 +183,7 @@ jobs:
|
||||
env:
|
||||
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Documentation
|
||||
path: docs/build/outputs
|
||||
@@ -212,7 +212,7 @@ jobs:
|
||||
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||||
with:
|
||||
repository: wpilibsuite/build-tools
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
if: |
|
||||
github.repository_owner == 'wpilibsuite' &&
|
||||
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||||
@@ -230,7 +230,7 @@ jobs:
|
||||
run: |
|
||||
cat combiner/products/build/allOutputs/version.txt
|
||||
test -s combiner/products/build/allOutputs/version.txt
|
||||
- uses: actions/setup-java@v3
|
||||
- uses: actions/setup-java@v4
|
||||
if: |
|
||||
github.repository_owner == 'wpilibsuite' &&
|
||||
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||||
@@ -255,7 +255,7 @@ jobs:
|
||||
RUN_AZURE_ARTIFACTORY_RELEASE: "TRUE"
|
||||
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: |
|
||||
github.repository_owner == 'wpilibsuite' &&
|
||||
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||||
|
||||
12
.github/workflows/lint-format.yml
vendored
12
.github/workflows/lint-format.yml
vendored
@@ -23,11 +23,11 @@ jobs:
|
||||
git checkout -b pr
|
||||
git branch -f main origin/main
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Install wpiformat
|
||||
run: pip3 install wpiformat==2023.36
|
||||
run: pip3 install wpiformat==2024.32
|
||||
- name: Run
|
||||
run: wpiformat
|
||||
- name: Check output
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
- name: Generate diff
|
||||
run: git diff HEAD > wpiformat-fixes.patch
|
||||
if: ${{ failure() }}
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wpiformat fixes
|
||||
path: wpiformat-fixes.patch
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
git checkout -b pr
|
||||
git branch -f main origin/main
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Install wpiformat
|
||||
@@ -105,9 +105,9 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v3
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 13
|
||||
java-version: 17
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew docs:zipDocs -PbuildServer -PdocWarningsAsErrors ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
|
||||
17
.github/workflows/pregenerate.yml
vendored
17
.github/workflows/pregenerate.yml
vendored
@@ -15,26 +15,23 @@ jobs:
|
||||
name: "Update"
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Fetch all history and metadata
|
||||
run: |
|
||||
git fetch --prune --unshallow
|
||||
git checkout -b pr
|
||||
git branch -f main origin/main
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install jinja
|
||||
run: python -m pip install jinja2
|
||||
- name: Install protobuf dependencies
|
||||
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler && wget https://github.com/HebiRobotics/QuickBuffers/releases/download/1.3.2/protoc-gen-quickbuf-1.3.2-linux-x86_64.exe && chmod +x protoc-gen-quickbuf-1.3.2-linux-x86_64.exe
|
||||
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler && wget https://github.com/HebiRobotics/QuickBuffers/releases/download/1.3.3/protoc-gen-quickbuf-1.3.3-linux-x86_64.exe && chmod +x protoc-gen-quickbuf-1.3.3-linux-x86_64.exe
|
||||
- name: Run hal
|
||||
run: ./hal/generate_usage_reporting.py
|
||||
- name: Run ntcore
|
||||
run: ./ntcore/generate_topics.py
|
||||
- name: Run wpimath
|
||||
run: ./wpimath/generate_numbers.py && ./wpimath/generate_quickbuf.py protoc protoc-gen-quickbuf-1.3.2-linux-x86_64.exe
|
||||
run: ./wpimath/generate_numbers.py && ./wpimath/generate_quickbuf.py protoc protoc-gen-quickbuf-1.3.3-linux-x86_64.exe
|
||||
- name: Add untracked files to index so they count as changes
|
||||
run: git add -A
|
||||
- name: Check output
|
||||
@@ -42,7 +39,7 @@ jobs:
|
||||
- name: Generate diff
|
||||
run: git diff HEAD > pregenerated-files-fixes.patch
|
||||
if: ${{ failure() }}
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: pregenerated-files-fixes
|
||||
path: pregenerated-files-fixes.patch
|
||||
|
||||
2
.github/workflows/sanitizers.yml
vendored
2
.github/workflows/sanitizers.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
|
||||
- name: Install QuickBuffers
|
||||
if: runner.os == 'Linux'
|
||||
run: wget https://github.com/HebiRobotics/QuickBuffers/releases/download/1.3.2/protoc-gen-quickbuf_1.3.2_amd64.deb && sudo apt install ./protoc-gen-quickbuf_1.3.2_amd64.deb
|
||||
run: wget https://github.com/HebiRobotics/QuickBuffers/releases/download/1.3.3/protoc-gen-quickbuf_1.3.3_amd64.deb && sudo apt install ./protoc-gen-quickbuf_1.3.3_amd64.deb
|
||||
|
||||
- name: Run sccache-cache
|
||||
uses: mozilla-actions/sccache-action@v0.0.3
|
||||
|
||||
2
.github/workflows/upstream-utils.yml
vendored
2
.github/workflows/upstream-utils.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
git checkout -b pr
|
||||
git branch -f main origin/main
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Configure committer identity
|
||||
|
||||
@@ -54,7 +54,7 @@ set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
|
||||
list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" isSystemDir)
|
||||
if("${isSystemDir}" STREQUAL "-1")
|
||||
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
|
||||
endif("${isSystemDir}" STREQUAL "-1")
|
||||
endif()
|
||||
|
||||
# Options for building certain parts of the repo. Everything is built by default.
|
||||
option(BUILD_SHARED_LIBS "Build with shared libs (needed for JNI)" ON)
|
||||
@@ -82,6 +82,9 @@ option(USE_SYSTEM_EIGEN "Use system eigen" OFF)
|
||||
# Options for location of OpenCV Java.
|
||||
set(OPENCV_JAVA_INSTALL_DIR "" CACHE PATH "Location to search for the OpenCV jar file")
|
||||
|
||||
# Options for compilation flags.
|
||||
option(NO_WERROR "Disable -Werror flag during compilation" OFF)
|
||||
|
||||
# Set default build type to release with debug info (i.e. release mode optimizations
|
||||
# are performed, but debug info still exists).
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
@@ -211,7 +214,6 @@ endif()
|
||||
find_package(LIBSSH 0.7.1)
|
||||
|
||||
find_package(Protobuf REQUIRED)
|
||||
find_program(Quickbuf_EXECUTABLE NAMES protoc-gen-quickbuf DOC "The Quickbuf protoc plugin")
|
||||
|
||||
set(APRILTAG_DEP_REPLACE "find_dependency(apriltag)")
|
||||
set(CAMERASERVER_DEP_REPLACE_IMPL "find_dependency(cameraserver)")
|
||||
|
||||
@@ -12,11 +12,11 @@ So you want to contribute your changes back to WPILib. Great! We have a few cont
|
||||
|
||||
## General Contribution Rules
|
||||
|
||||
- Everything in the library must work for the 3000+ teams that will be using it.
|
||||
- Everything in the library must work for the 4000+ teams that will be using it.
|
||||
- We need to be able to maintain submitted changes, even if you are no longer working on the project.
|
||||
- Tool suite changes must be generally useful to a broad range of teams
|
||||
- Excluding bug fixes, changes in one language generally need to have corresponding changes in other languages.
|
||||
- Some features, such the addition of C++11 for WPILibC or Functional Interfaces for WPILibJ, are specific to that version of WPILib only.
|
||||
- Some features, such the addition of C++23 for WPILibC or Functional Interfaces for WPILibJ, are specific to that version of WPILib only. New language features added to C++ must be wrappable in Python for [RobotPy](https://github.com/robotpy).
|
||||
- Substantial changes often need to have corresponding LabVIEW changes. To do this, we will work with NI on these large changes.
|
||||
- Changes should have tests.
|
||||
- Code should be well documented.
|
||||
@@ -27,7 +27,8 @@ So you want to contribute your changes back to WPILib. Great! We have a few cont
|
||||
- Bug reports and fixes
|
||||
- We will generally accept bug fixes without too much question. If they are only implemented for one language, we will implement them for any other necessary languages. Bug reports are also welcome, please submit them to our GitHub issue tracker.
|
||||
- While we do welcome improvements to the API, there are a few important rules to consider:
|
||||
- Features must be added to both WPILibC and WPILibJ, with rare exceptions.
|
||||
- Features must be added to Java (WPILibJ), C++ (WPILibC), with rare exceptions.
|
||||
- Most of Python (RobotPy) is created by wrapping WPILibC with pybind11 via robotpy-build. However, new features to the command framework should also be submitted to [robotpy-commands-v2](https://github.com/robotpy/robotpy-commands-v2) as the command framework is reimplemented in Python.
|
||||
- During competition season, we will not merge any new feature additions. We want to ensure that the API is stable during the season to help minimize issues for teams.
|
||||
- Ask about large changes before spending a bunch of time on them! You can create a new issue on our GitHub tracker for feature request/discussion and talk about it with us there.
|
||||
- Features that make it easier for teams with less experience to be more successful are more likely to be accepted.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2009-2023 FIRST and other WPILib contributors
|
||||
Copyright (c) 2009-2024 FIRST and other WPILib contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
## Publishing Third Party Dependencies
|
||||
Currently the 3rd party deps are imgui, opencv, and google test
|
||||
Currently the 3rd party deps are imgui, opencv, google test, libssh, and apriltaglib
|
||||
|
||||
For publishing these dependencies, the version needs to be manually updated in the publish.gradle file of their respective repository.
|
||||
Then, in the azure build for the dependency you want to build for, manually start a pipeline build (As of current, this is the `Run Pipeline` button).
|
||||
@@ -24,4 +24,4 @@ Upon pushing a tag, a release will be built, and the files will be uploaded to t
|
||||
Before publishing, make sure to update the version in build.gradle. Publishing must happen locally, using the command `./gradlew publishPlugin`. This does require your API key for publishing to be set.
|
||||
|
||||
## Building the installer
|
||||
Update the GradleRIO version in gradle.properties, and in the scripts folder in vscode, update the vscode extension. Then push, it will build the installer on azure.
|
||||
Update the GradleRIO version in gradle.properties, and in the scripts folder in vscode, update the vscode extension. To publish a release build, upload a new tag, and a release will automatically be built and published to artifactory and cloudflare.
|
||||
|
||||
@@ -145,6 +145,11 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
|
||||
* wpinet
|
||||
* wpiutil
|
||||
|
||||
* wpiunits
|
||||
|
||||
* apriltag
|
||||
* wpiutil
|
||||
* wpimath
|
||||
|
||||
### Third Party Artifacts
|
||||
|
||||
@@ -152,6 +157,7 @@ This repository provides the builds of the following third party software.
|
||||
|
||||
All artifacts are based at `edu.wpi.first.thirdparty.frcYEAR` in the repository.
|
||||
|
||||
* apriltaglib
|
||||
* googletest
|
||||
* imgui
|
||||
* opencv
|
||||
|
||||
@@ -63,6 +63,8 @@ The following build options are available:
|
||||
* TODO
|
||||
* `OPENCV_JAVA_INSTALL_DIR`
|
||||
* Set this option to the location of the archive of the OpenCV Java bindings (it should be called opencv-xxx.jar, with the x'es being version numbers). NOTE: set it to the LOCATION of the file, not the file itself!
|
||||
* `NO_WERROR` (OFF Default)
|
||||
* This option will disable the `-Werror` compilation flag for non-MSVC builds.
|
||||
|
||||
## Build Setup
|
||||
|
||||
|
||||
@@ -1204,3 +1204,8 @@ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
================
|
||||
2024 Field Image
|
||||
================
|
||||
2024 Field Image from MikLast: https://www.chiefdelphi.com/t/2024-crescendo-top-down-field-renders/447764
|
||||
|
||||
28
apriltag/README.md
Normal file
28
apriltag/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# AprilTag
|
||||
|
||||
## Adding new field to AprilTagFields
|
||||
|
||||
### Adding field JSON
|
||||
|
||||
1. Add a field layout CSV file to `src/main/native/resources/edu/wpi/first/apriltag`
|
||||
1. See docstring in `convert_apriltag_layouts.py` for more
|
||||
2. Run `convert_apriltag_layouts.py` in the same directory as this readme to generate the JSON
|
||||
3. That script overwrites all generated JSONs, so undo undesired changes if necessary
|
||||
4. Update the field dimensions at the bottom of the JSON
|
||||
1. Length should be in meters from alliance wall to alliance wall
|
||||
2. Width should be in meters from inside guardrail plastic to plastic
|
||||
|
||||
### Java updates
|
||||
|
||||
1. Update `src/main/java/edu/wpi/first/apriltag/AprilTagFields.java`
|
||||
1. Add enum value for new field to `AprilTagFields`
|
||||
2. Update `AprilTagFields.kDefaultField` if necessary
|
||||
|
||||
### C++ updates
|
||||
|
||||
1. Update `src/main/native/include/frc/apriltag/AprilTagFields.h`
|
||||
1. Add enum value for new field to `AprilTagFields`
|
||||
2. Update `AprilTagFields::kDefaultField` if necessary
|
||||
2. Update `src/main/native/cpp/AprilTagFields.cpp`
|
||||
1. Add resource getter prototype like `std::string_view GetResource_2024_crescendo_json()`
|
||||
2. Add case for new field to switch in `LoadAprilTagLayoutField()`
|
||||
89
apriltag/convert_apriltag_layouts.py
Executable file
89
apriltag/convert_apriltag_layouts.py
Executable file
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
This script converts all AprilTag field layout CSV files in
|
||||
src/main/native/resources/edu/wpi/first/apriltag to the JSON format
|
||||
AprilTagFields expects.
|
||||
|
||||
The input CSV has the following format:
|
||||
|
||||
* Columns: ID, X, Y, Z, Rotation
|
||||
* ID is a positive integer
|
||||
* X, Y, and Z are decimal inches
|
||||
* Rotation is yaw in degrees
|
||||
|
||||
The values come from a table in the layout marking diagram (e.g.,
|
||||
https://firstfrc.blob.core.windows.net/frc2024/FieldAssets/2024LayoutMarkingDiagram.pdf).
|
||||
"""
|
||||
|
||||
import csv
|
||||
import json
|
||||
import os
|
||||
|
||||
from wpimath import geometry, units
|
||||
import numpy as np
|
||||
|
||||
|
||||
def main():
|
||||
# Find AprilTag field layout CSVs
|
||||
filenames = [
|
||||
os.path.join(dp, f)
|
||||
for dp, dn, fn in os.walk("src/main/native/resources/edu/wpi/first/apriltag")
|
||||
for f in fn
|
||||
if f.endswith(".csv")
|
||||
]
|
||||
|
||||
for filename in filenames:
|
||||
json_data = {"tags": [], "field": {"length": 0.0, "width": 0.0}}
|
||||
|
||||
# Read CSV and fill in JSON data
|
||||
with open(filename, newline="") as csvfile:
|
||||
reader = csv.reader(csvfile, delimiter=",")
|
||||
|
||||
# Skip header
|
||||
next(reader)
|
||||
|
||||
for row in reader:
|
||||
# Unpack row elements
|
||||
id = int(row[0])
|
||||
x = float(row[1])
|
||||
y = float(row[2])
|
||||
z = float(row[3])
|
||||
rotation = float(row[4])
|
||||
|
||||
# Turn yaw into quaternion
|
||||
q = geometry.Rotation3d(
|
||||
units.radians(0.0),
|
||||
units.radians(0.0),
|
||||
units.degreesToRadians(rotation),
|
||||
).getQuaternion()
|
||||
|
||||
json_data["tags"].append(
|
||||
{
|
||||
"ID": id,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": units.inchesToMeters(x),
|
||||
"y": units.inchesToMeters(y),
|
||||
"z": units.inchesToMeters(z),
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": q.W(),
|
||||
"X": q.X(),
|
||||
"Y": q.Y(),
|
||||
"Z": q.Z(),
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
# Write JSON
|
||||
with open(filename.replace(".csv", ".json"), "w") as f:
|
||||
json.dump(json_data, f, indent=2)
|
||||
f.write("\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -11,14 +11,23 @@ import edu.wpi.first.math.geometry.Pose3d;
|
||||
import edu.wpi.first.util.RawFrame;
|
||||
import java.util.Objects;
|
||||
|
||||
/** Represents an AprilTag's metadata. */
|
||||
@SuppressWarnings("MemberName")
|
||||
public class AprilTag {
|
||||
/** The tag's ID. */
|
||||
@JsonProperty(value = "ID")
|
||||
public int ID;
|
||||
|
||||
/** The tag's pose. */
|
||||
@JsonProperty(value = "pose")
|
||||
public Pose3d pose;
|
||||
|
||||
/**
|
||||
* Constructs an AprilTag.
|
||||
*
|
||||
* @param ID The tag's ID.
|
||||
* @param pose The tag's pose.
|
||||
*/
|
||||
@SuppressWarnings("ParameterName")
|
||||
@JsonCreator
|
||||
public AprilTag(
|
||||
@@ -54,9 +63,9 @@ public class AprilTag {
|
||||
* @return A RawFrame containing the AprilTag image
|
||||
*/
|
||||
public static RawFrame generate16h5AprilTagImage(int id) {
|
||||
RawFrame generatedImage = new RawFrame();
|
||||
AprilTagJNI.generate16h5AprilTagImage(id, generatedImage.getDataPtr());
|
||||
return generatedImage;
|
||||
RawFrame frame = new RawFrame();
|
||||
AprilTagJNI.generate16h5AprilTagImage(frame, frame.getNativeObj(), id);
|
||||
return frame;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,8 +75,8 @@ public class AprilTag {
|
||||
* @return A RawFrame containing the AprilTag image
|
||||
*/
|
||||
public static RawFrame generate36h11AprilTagImage(int id) {
|
||||
RawFrame generatedImage = new RawFrame();
|
||||
AprilTagJNI.generate36h11AprilTagImage(id, generatedImage.getDataPtr());
|
||||
return generatedImage;
|
||||
RawFrame frame = new RawFrame();
|
||||
AprilTagJNI.generate36h11AprilTagImage(frame, frame.getNativeObj(), id);
|
||||
return frame;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,8 +57,21 @@ public class AprilTagDetector implements AutoCloseable {
|
||||
*/
|
||||
public boolean debug;
|
||||
|
||||
/** Default constructor. */
|
||||
public Config() {}
|
||||
|
||||
/**
|
||||
* Constructs a detector configuration.
|
||||
*
|
||||
* @param numThreads How many threads should be used for computation.
|
||||
* @param quadDecimate Quad decimation.
|
||||
* @param quadSigma What Gaussian blur should be applied to the segmented image (used for quad
|
||||
* detection).
|
||||
* @param refineEdges When true, the edges of the each quad are adjusted to "snap to" strong
|
||||
* gradients nearby.
|
||||
* @param decodeSharpening How much sharpening should be done to decoded images.
|
||||
* @param debug Debug mode.
|
||||
*/
|
||||
Config(
|
||||
int numThreads,
|
||||
float quadDecimate,
|
||||
@@ -139,8 +152,21 @@ public class AprilTagDetector implements AutoCloseable {
|
||||
*/
|
||||
public boolean deglitch;
|
||||
|
||||
/** Default constructor. */
|
||||
public QuadThresholdParameters() {}
|
||||
|
||||
/**
|
||||
* Constructs quad threshold parameters.
|
||||
*
|
||||
* @param minClusterPixels Threshold used to reject quads containing too few pixels.
|
||||
* @param maxNumMaxima How many corner candidates to consider when segmenting a group of pixels
|
||||
* into a quad.
|
||||
* @param criticalAngle Critical angle, in radians.
|
||||
* @param maxLineFitMSE When fitting lines to the contours, the maximum mean squared error
|
||||
* allowed.
|
||||
* @param minWhiteBlackDiff Minimum brightness offset.
|
||||
* @param deglitch Whether the thresholded image be should be deglitched.
|
||||
*/
|
||||
QuadThresholdParameters(
|
||||
int minClusterPixels,
|
||||
int maxNumMaxima,
|
||||
@@ -182,6 +208,7 @@ public class AprilTagDetector implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** Constructs an AprilTagDetector. */
|
||||
public AprilTagDetector() {
|
||||
m_native = AprilTagJNI.createDetector();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import edu.wpi.first.math.geometry.Translation3d;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -44,8 +45,11 @@ import java.util.Optional;
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
public class AprilTagFieldLayout {
|
||||
/** Common origin positions for the AprilTag coordinate system. */
|
||||
public enum OriginPosition {
|
||||
/** Blue alliance wall, right side. */
|
||||
kBlueAllianceWallRightSide,
|
||||
/** Red alliance wall, right side. */
|
||||
kRedAllianceWallRightSide,
|
||||
}
|
||||
|
||||
@@ -218,6 +222,22 @@ public class AprilTagFieldLayout {
|
||||
new ObjectMapper().writeValue(path.toFile(), this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an official {@link AprilTagFieldLayout}.
|
||||
*
|
||||
* @param field The loadable AprilTag field layout.
|
||||
* @return AprilTagFieldLayout of the field.
|
||||
* @throws UncheckedIOException If the layout does not exist.
|
||||
*/
|
||||
public static AprilTagFieldLayout loadField(AprilTagFields field) {
|
||||
try {
|
||||
return loadFromResource(field.m_resourceFile);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(
|
||||
"Could not load AprilTagFieldLayout from " + field.m_resourceFile, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a field layout from a resource within a internal jar file.
|
||||
*
|
||||
|
||||
@@ -4,18 +4,24 @@
|
||||
|
||||
package edu.wpi.first.apriltag;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
|
||||
/** Loadable AprilTag field layouts. */
|
||||
public enum AprilTagFields {
|
||||
/** 2022 Rapid React. */
|
||||
k2022RapidReact("2022-rapidreact.json"),
|
||||
k2023ChargedUp("2023-chargedup.json");
|
||||
/** 2023 Charged Up. */
|
||||
k2023ChargedUp("2023-chargedup.json"),
|
||||
/** 2024 Crescendo. */
|
||||
k2024Crescendo("2024-crescendo.json");
|
||||
|
||||
/** Base resource directory. */
|
||||
public static final String kBaseResourceDir = "/edu/wpi/first/apriltag/";
|
||||
|
||||
/** Alias to the current game. */
|
||||
public static final AprilTagFields kDefaultField = k2023ChargedUp;
|
||||
public static final AprilTagFields kDefaultField = k2024Crescendo;
|
||||
|
||||
/** Resource filename. */
|
||||
public final String m_resourceFile;
|
||||
|
||||
AprilTagFields(String resourceFile) {
|
||||
@@ -29,11 +35,6 @@ public enum AprilTagFields {
|
||||
* @throws UncheckedIOException If the layout does not exist
|
||||
*/
|
||||
public AprilTagFieldLayout loadAprilTagLayoutField() {
|
||||
try {
|
||||
return AprilTagFieldLayout.loadFromResource(m_resourceFile);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(
|
||||
"Could not load AprilTagFieldLayout from " + m_resourceFile, e);
|
||||
}
|
||||
return AprilTagFieldLayout.loadField(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,10 +29,19 @@ public class AprilTagPoseEstimator {
|
||||
this.cy = cy;
|
||||
}
|
||||
|
||||
/** Tag size, in meters. */
|
||||
public double tagSize;
|
||||
|
||||
/** Camera horizontal focal length, in pixels. */
|
||||
public double fx;
|
||||
|
||||
/** Camera vertical focal length, in pixels. */
|
||||
public double fy;
|
||||
|
||||
/** Camera horizontal focal center, in pixels. */
|
||||
public double cx;
|
||||
|
||||
/** Camera vertical focal center, in pixels. */
|
||||
public double cy;
|
||||
|
||||
@Override
|
||||
|
||||
@@ -8,25 +8,41 @@ import edu.wpi.first.apriltag.AprilTagDetection;
|
||||
import edu.wpi.first.apriltag.AprilTagDetector;
|
||||
import edu.wpi.first.apriltag.AprilTagPoseEstimate;
|
||||
import edu.wpi.first.math.geometry.Transform3d;
|
||||
import edu.wpi.first.util.RawFrame;
|
||||
import edu.wpi.first.util.RuntimeLoader;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/** AprilTag JNI. */
|
||||
public class AprilTagJNI {
|
||||
static boolean libraryLoaded = false;
|
||||
|
||||
static RuntimeLoader<AprilTagJNI> loader = null;
|
||||
|
||||
/** Sets whether JNI should be loaded in the static block. */
|
||||
public static class Helper {
|
||||
private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(true);
|
||||
|
||||
/**
|
||||
* Returns true if the JNI should be loaded in the static block.
|
||||
*
|
||||
* @return True if the JNI should be loaded in the static block.
|
||||
*/
|
||||
public static boolean getExtractOnStaticLoad() {
|
||||
return extractOnStaticLoad.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the JNI should be loaded in the static block.
|
||||
*
|
||||
* @param load Whether the JNI should be loaded in the static block.
|
||||
*/
|
||||
public static void setExtractOnStaticLoad(boolean load) {
|
||||
extractOnStaticLoad.set(load);
|
||||
}
|
||||
|
||||
/** Utility class. */
|
||||
private Helper() {}
|
||||
}
|
||||
|
||||
static {
|
||||
@@ -190,7 +206,24 @@ public class AprilTagJNI {
|
||||
double cx,
|
||||
double cy);
|
||||
|
||||
public static native void generate16h5AprilTagImage(int id, long nativeAddr);
|
||||
/**
|
||||
* Generates a RawFrame containing the apriltag with the id with family 16h5 passed in.
|
||||
*
|
||||
* @param frameObj generated frame (output parameter).
|
||||
* @param frame raw frame handle
|
||||
* @param id id
|
||||
*/
|
||||
public static native void generate16h5AprilTagImage(RawFrame frameObj, long frame, int id);
|
||||
|
||||
public static native void generate36h11AprilTagImage(int id, long nativeAddr);
|
||||
/**
|
||||
* Generates a RawFrame containing the apriltag with the id with family 36h11 passed in.
|
||||
*
|
||||
* @param frameObj generated frame (output parameter).
|
||||
* @param frame raw frame handle
|
||||
* @param id id
|
||||
*/
|
||||
public static native void generate36h11AprilTagImage(RawFrame frameObj, long frame, int id);
|
||||
|
||||
/** Utility class. */
|
||||
private AprilTagJNI() {}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#include "frc/apriltag/AprilTag.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <wpi/json.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -20,40 +22,33 @@
|
||||
|
||||
using namespace frc;
|
||||
|
||||
wpi::RawFrame AprilTag::Generate36h11AprilTagImage(int id) {
|
||||
apriltag_family_t* tagFamily = tag36h11_create();
|
||||
image_u8_t* image = apriltag_to_image(tagFamily, id);
|
||||
wpi::RawFrame markerFrame{};
|
||||
size_t totalDataSize = image->height * image->stride * sizeof(char);
|
||||
markerFrame.data = static_cast<char*>(
|
||||
std::calloc(image->height * image->stride, sizeof(char)));
|
||||
std::memcpy(markerFrame.data, image->buf, totalDataSize);
|
||||
markerFrame.dataLength = image->width;
|
||||
markerFrame.height = image->height;
|
||||
markerFrame.pixelFormat = WPI_PIXFMT_GRAY;
|
||||
markerFrame.width = image->stride;
|
||||
markerFrame.totalData = totalDataSize;
|
||||
static bool FamilyToImage(wpi::RawFrame* frame, apriltag_family_t* family,
|
||||
int id) {
|
||||
image_u8_t* image = apriltag_to_image(family, id);
|
||||
size_t totalDataSize = image->height * image->stride;
|
||||
bool rv = frame->Reserve(totalDataSize);
|
||||
std::memcpy(frame->data, image->buf, totalDataSize);
|
||||
frame->size = totalDataSize;
|
||||
frame->width = image->width;
|
||||
frame->height = image->height;
|
||||
frame->stride = image->stride;
|
||||
frame->pixelFormat = WPI_PIXFMT_GRAY;
|
||||
image_u8_destroy(image);
|
||||
tag36h11_destroy(tagFamily);
|
||||
return markerFrame;
|
||||
return rv;
|
||||
}
|
||||
|
||||
wpi::RawFrame AprilTag::Generate16h5AprilTagImage(int id) {
|
||||
bool AprilTag::Generate36h11AprilTagImage(wpi::RawFrame* frame, int id) {
|
||||
apriltag_family_t* tagFamily = tag36h11_create();
|
||||
bool rv = FamilyToImage(frame, tagFamily, id);
|
||||
tag36h11_destroy(tagFamily);
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool AprilTag::Generate16h5AprilTagImage(wpi::RawFrame* frame, int id) {
|
||||
apriltag_family_t* tagFamily = tag16h5_create();
|
||||
image_u8_t* image = apriltag_to_image(tagFamily, id);
|
||||
wpi::RawFrame markerFrame{};
|
||||
size_t totalDataSize = image->height * image->stride * sizeof(char);
|
||||
markerFrame.data = static_cast<char*>(
|
||||
std::calloc(image->height * image->stride, sizeof(char)));
|
||||
std::memcpy(markerFrame.data, image->buf, totalDataSize);
|
||||
markerFrame.dataLength = image->width;
|
||||
markerFrame.height = image->height;
|
||||
markerFrame.pixelFormat = WPI_PIXFMT_GRAY;
|
||||
markerFrame.width = image->stride;
|
||||
markerFrame.totalData = totalDataSize;
|
||||
image_u8_destroy(image);
|
||||
bool rv = FamilyToImage(frame, tagFamily, id);
|
||||
tag16h5_destroy(tagFamily);
|
||||
return markerFrame;
|
||||
return rv;
|
||||
}
|
||||
|
||||
void frc::to_json(wpi::json& json, const AprilTag& apriltag) {
|
||||
|
||||
@@ -125,3 +125,37 @@ void frc::from_json(const wpi::json& json, AprilTagFieldLayout& layout) {
|
||||
layout.m_fieldWidth =
|
||||
units::meter_t{json.at("field").at("width").get<double>()};
|
||||
}
|
||||
|
||||
// Use namespace declaration for forward declaration
|
||||
namespace frc {
|
||||
|
||||
// C++ generated from resource files
|
||||
std::string_view GetResource_2022_rapidreact_json();
|
||||
std::string_view GetResource_2023_chargedup_json();
|
||||
std::string_view GetResource_2024_crescendo_json();
|
||||
|
||||
} // namespace frc
|
||||
|
||||
AprilTagFieldLayout AprilTagFieldLayout::LoadField(AprilTagField field) {
|
||||
std::string_view fieldString;
|
||||
switch (field) {
|
||||
case AprilTagField::k2022RapidReact:
|
||||
fieldString = GetResource_2022_rapidreact_json();
|
||||
break;
|
||||
case AprilTagField::k2023ChargedUp:
|
||||
fieldString = GetResource_2023_chargedup_json();
|
||||
break;
|
||||
case AprilTagField::k2024Crescendo:
|
||||
fieldString = GetResource_2024_crescendo_json();
|
||||
break;
|
||||
case AprilTagField::kNumFields:
|
||||
throw std::invalid_argument("Invalid Field");
|
||||
}
|
||||
|
||||
wpi::json json = wpi::json::parse(fieldString);
|
||||
return json.get<AprilTagFieldLayout>();
|
||||
}
|
||||
|
||||
AprilTagFieldLayout frc::LoadAprilTagLayoutField(AprilTagField field) {
|
||||
return AprilTagFieldLayout::LoadField(field);
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "frc/apriltag/AprilTagFields.h"
|
||||
|
||||
#include <wpi/json.h>
|
||||
|
||||
namespace frc {
|
||||
|
||||
// C++ generated from resource files
|
||||
std::string_view GetResource_2022_rapidreact_json();
|
||||
std::string_view GetResource_2023_chargedup_json();
|
||||
|
||||
AprilTagFieldLayout LoadAprilTagLayoutField(AprilTagField field) {
|
||||
std::string_view fieldString;
|
||||
switch (field) {
|
||||
case AprilTagField::k2022RapidReact:
|
||||
fieldString = GetResource_2022_rapidreact_json();
|
||||
break;
|
||||
case AprilTagField::k2023ChargedUp:
|
||||
fieldString = GetResource_2023_chargedup_json();
|
||||
break;
|
||||
case AprilTagField::kNumFields:
|
||||
throw std::invalid_argument("Invalid Field");
|
||||
}
|
||||
|
||||
wpi::json json = wpi::json::parse(fieldString);
|
||||
return json.get<AprilTagFieldLayout>();
|
||||
}
|
||||
|
||||
} // namespace frc
|
||||
@@ -2,9 +2,13 @@
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#define WPI_RAWFRAME_JNI
|
||||
#include <wpi/RawFrame.h>
|
||||
#include <wpi/jni_util.h>
|
||||
|
||||
#include "edu_wpi_first_apriltag_jni_AprilTagJNI.h"
|
||||
@@ -25,6 +29,7 @@ static JClass quaternionCls;
|
||||
static JClass rotation3dCls;
|
||||
static JClass transform3dCls;
|
||||
static JClass translation3dCls;
|
||||
static JClass rawFrameCls;
|
||||
static JException illegalArgEx;
|
||||
static JException nullPointerEx;
|
||||
|
||||
@@ -37,7 +42,8 @@ static const JClassInit classes[] = {
|
||||
{"edu/wpi/first/math/geometry/Quaternion", &quaternionCls},
|
||||
{"edu/wpi/first/math/geometry/Rotation3d", &rotation3dCls},
|
||||
{"edu/wpi/first/math/geometry/Transform3d", &transform3dCls},
|
||||
{"edu/wpi/first/math/geometry/Translation3d", &translation3dCls}};
|
||||
{"edu/wpi/first/math/geometry/Translation3d", &translation3dCls},
|
||||
{"edu/wpi/first/util/RawFrame", &rawFrameCls}};
|
||||
|
||||
static const JExceptionInit exceptions[] = {
|
||||
{"java/lang/IllegalArgumentException", &illegalArgEx},
|
||||
@@ -591,27 +597,38 @@ Java_edu_wpi_first_apriltag_jni_AprilTagJNI_estimatePose
|
||||
/*
|
||||
* Class: edu_wpi_first_apriltag_jni_AprilTagJNI
|
||||
* Method: generate16h5AprilTagImage
|
||||
* Signature: (IJ)V
|
||||
* Signature: (Ljava/lang/Object;JI)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_apriltag_jni_AprilTagJNI_generate16h5AprilTagImage
|
||||
(JNIEnv* env, jclass, jint id, jlong framePtr)
|
||||
(JNIEnv* env, jclass, jobject frameObj, jlong framePtr, jint id)
|
||||
{
|
||||
wpi::RawFrame* javaRawFrame = (wpi::RawFrame*)framePtr;
|
||||
*javaRawFrame = AprilTag::Generate16h5AprilTagImage(id);
|
||||
auto* frame = reinterpret_cast<wpi::RawFrame*>(framePtr);
|
||||
if (!frame) {
|
||||
nullPointerEx.Throw(env, "frame is null");
|
||||
return;
|
||||
}
|
||||
bool newData = AprilTag::Generate16h5AprilTagImage(frame, id);
|
||||
wpi::SetFrameData(env, rawFrameCls, frameObj, *frame, newData);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_apriltag_jni_AprilTagJNI
|
||||
* Method: generate36h11AprilTagImage
|
||||
* Signature: (IJ)V
|
||||
* Signature: (Ljava/lang/Object;JI)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_apriltag_jni_AprilTagJNI_generate36h11AprilTagImage
|
||||
(JNIEnv* env, jclass, jint id, jlong framePtr)
|
||||
(JNIEnv* env, jclass, jobject frameObj, jlong framePtr, jint id)
|
||||
{
|
||||
wpi::RawFrame* javaRawFrame = (wpi::RawFrame*)framePtr;
|
||||
*javaRawFrame = AprilTag::Generate36h11AprilTagImage(id);
|
||||
auto* frame = reinterpret_cast<wpi::RawFrame*>(framePtr);
|
||||
if (!frame) {
|
||||
nullPointerEx.Throw(env, "frame is null");
|
||||
return;
|
||||
}
|
||||
// function might reallocate
|
||||
bool newData = AprilTag::Generate36h11AprilTagImage(frame, id);
|
||||
wpi::SetFrameData(env, rawFrameCls, frameObj, *frame, newData);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@@ -12,18 +12,20 @@
|
||||
|
||||
namespace frc {
|
||||
|
||||
/**
|
||||
* Represents an AprilTag's metadata.
|
||||
*/
|
||||
struct WPILIB_DLLEXPORT AprilTag {
|
||||
/// The tag's ID.
|
||||
int ID;
|
||||
|
||||
/// The tag's pose.
|
||||
Pose3d pose;
|
||||
|
||||
/**
|
||||
* Checks equality between this AprilTag and another object.
|
||||
*/
|
||||
bool operator==(const AprilTag&) const = default;
|
||||
|
||||
static wpi::RawFrame Generate36h11AprilTagImage(int id);
|
||||
static wpi::RawFrame Generate16h5AprilTagImage(int id);
|
||||
static bool Generate36h11AprilTagImage(wpi::RawFrame* frame, int id);
|
||||
static bool Generate16h5AprilTagImage(wpi::RawFrame* frame, int id);
|
||||
};
|
||||
|
||||
WPILIB_DLLEXPORT
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <wpi/json_fwd.h>
|
||||
|
||||
#include "frc/apriltag/AprilTag.h"
|
||||
#include "frc/apriltag/AprilTagFields.h"
|
||||
#include "frc/geometry/Pose3d.h"
|
||||
|
||||
namespace frc {
|
||||
@@ -38,11 +39,24 @@ namespace frc {
|
||||
* towards the opposing alliance). */
|
||||
class WPILIB_DLLEXPORT AprilTagFieldLayout {
|
||||
public:
|
||||
/**
|
||||
* Common origin positions for the AprilTag coordinate system.
|
||||
*/
|
||||
enum class OriginPosition {
|
||||
/// Blue alliance wall, right side.
|
||||
kBlueAllianceWallRightSide,
|
||||
/// Red alliance wall, right side.
|
||||
kRedAllianceWallRightSide,
|
||||
};
|
||||
|
||||
/**
|
||||
* Loads an AprilTagFieldLayout from a predefined field
|
||||
*
|
||||
* @param field The predefined field
|
||||
* @return AprilTagFieldLayout of the field
|
||||
*/
|
||||
static AprilTagFieldLayout LoadField(AprilTagField field);
|
||||
|
||||
AprilTagFieldLayout() = default;
|
||||
|
||||
/**
|
||||
@@ -147,4 +161,13 @@ void to_json(wpi::json& json, const AprilTagFieldLayout& layout);
|
||||
WPILIB_DLLEXPORT
|
||||
void from_json(const wpi::json& json, AprilTagFieldLayout& layout);
|
||||
|
||||
/**
|
||||
* Loads an AprilTagFieldLayout from a predefined field
|
||||
*
|
||||
* @param field The predefined field
|
||||
* @return AprilTagFieldLayout of the field
|
||||
*/
|
||||
WPILIB_DLLEXPORT AprilTagFieldLayout
|
||||
LoadAprilTagLayoutField(AprilTagField field);
|
||||
|
||||
} // namespace frc
|
||||
|
||||
@@ -8,25 +8,22 @@
|
||||
|
||||
#include <wpi/SymbolExports.h>
|
||||
|
||||
#include "frc/apriltag/AprilTagFieldLayout.h"
|
||||
|
||||
namespace frc {
|
||||
|
||||
/**
|
||||
* Loadable AprilTag field layouts.
|
||||
*/
|
||||
enum class AprilTagField {
|
||||
/// 2022 Rapid React.
|
||||
k2022RapidReact,
|
||||
/// 2023 Charged Up.
|
||||
k2023ChargedUp,
|
||||
/// 2024 Crescendo.
|
||||
k2024Crescendo,
|
||||
|
||||
// This is a placeholder for denoting the last supported field. This should
|
||||
// always be the last entry in the enum and should not be used by users
|
||||
kNumFields,
|
||||
};
|
||||
|
||||
/**
|
||||
* Loads an AprilTagFieldLayout from a predefined field
|
||||
*
|
||||
* @param field The predefined field
|
||||
*/
|
||||
WPILIB_DLLEXPORT AprilTagFieldLayout
|
||||
LoadAprilTagLayoutField(AprilTagField field);
|
||||
|
||||
} // namespace frc
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
ID,X,Y,Z,Rotation
|
||||
1,593.68,9.68,53.38,120
|
||||
2,637.21,34.79,53.38,120
|
||||
3,652.73,196.17,57.13,180
|
||||
4,652.73,218.42,57.13,180
|
||||
5,578.77,323.00,53.38,270
|
||||
6,72.5,323.00,53.38,270
|
||||
7,-1.50,218.42,57.13,0
|
||||
8,-1.50,196.17,57.13,0
|
||||
9,14.02,34.79,53.38,60
|
||||
10,57.54,9.68,53.38,60
|
||||
11,468.69,146.19,52.00,300
|
||||
12,468.69,177.10,52.00,60
|
||||
13,441.74,161.62,52.00,180
|
||||
14,209.48,161.62,52.00,0
|
||||
15,182.73,177.10,52.00,120
|
||||
16,182.73,146.19,52.00,240
|
||||
|
@@ -0,0 +1,296 @@
|
||||
{
|
||||
"tags": [
|
||||
{
|
||||
"ID": 1,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 15.079471999999997,
|
||||
"y": 0.24587199999999998,
|
||||
"z": 1.355852
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 0.5000000000000001,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.8660254037844386
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 2,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 16.185134,
|
||||
"y": 0.883666,
|
||||
"z": 1.355852
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 0.5000000000000001,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.8660254037844386
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 3,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 16.579342,
|
||||
"y": 4.982717999999999,
|
||||
"z": 1.4511020000000001
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 6.123233995736766e-17,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 4,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 16.579342,
|
||||
"y": 5.547867999999999,
|
||||
"z": 1.4511020000000001
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 6.123233995736766e-17,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 5,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 14.700757999999999,
|
||||
"y": 8.2042,
|
||||
"z": 1.355852
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": -0.7071067811865475,
|
||||
"X": -0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.7071067811865476
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 6,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 1.8415,
|
||||
"y": 8.2042,
|
||||
"z": 1.355852
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": -0.7071067811865475,
|
||||
"X": -0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.7071067811865476
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 7,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": -0.038099999999999995,
|
||||
"y": 5.547867999999999,
|
||||
"z": 1.4511020000000001
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 1.0,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 8,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": -0.038099999999999995,
|
||||
"y": 4.982717999999999,
|
||||
"z": 1.4511020000000001
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 1.0,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 9,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 0.356108,
|
||||
"y": 0.883666,
|
||||
"z": 1.355852
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 0.8660254037844387,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.49999999999999994
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 10,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 1.4615159999999998,
|
||||
"y": 0.24587199999999998,
|
||||
"z": 1.355852
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 0.8660254037844387,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.49999999999999994
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 11,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 11.904726,
|
||||
"y": 3.7132259999999997,
|
||||
"z": 1.3208
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": -0.8660254037844387,
|
||||
"X": -0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.49999999999999994
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 12,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 11.904726,
|
||||
"y": 4.49834,
|
||||
"z": 1.3208
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 0.8660254037844387,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.49999999999999994
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 13,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 11.220196,
|
||||
"y": 4.105148,
|
||||
"z": 1.3208
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 6.123233995736766e-17,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 14,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 5.320792,
|
||||
"y": 4.105148,
|
||||
"z": 1.3208
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 1.0,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 15,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 4.641342,
|
||||
"y": 4.49834,
|
||||
"z": 1.3208
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 0.5000000000000001,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.8660254037844386
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 16,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 4.641342,
|
||||
"y": 3.7132259999999997,
|
||||
"z": 1.3208
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": -0.4999999999999998,
|
||||
"X": -0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.8660254037844387
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"field": {
|
||||
"length": 16.541,
|
||||
"width": 8.211
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.apriltag;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import edu.wpi.first.util.PixelFormat;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class AprilTagGenerationTest {
|
||||
@Test
|
||||
void test36h11() {
|
||||
var frame = AprilTag.generate36h11AprilTagImage(1);
|
||||
assertEquals(PixelFormat.kGray, frame.getPixelFormat());
|
||||
assertEquals(10, frame.getWidth());
|
||||
assertEquals(10, frame.getHeight());
|
||||
int stride = frame.getStride();
|
||||
assertEquals(stride * 10, frame.getSize());
|
||||
// check the diagonal values
|
||||
var data = frame.getData();
|
||||
assertEquals(-1, data.get(stride * 0 + 0)); // outer border is white
|
||||
assertEquals(0, data.get(stride * 1 + 1)); // inner border is black
|
||||
assertEquals(-1, data.get(stride * 2 + 2));
|
||||
assertEquals(-1, data.get(stride * 3 + 3));
|
||||
assertEquals(-1, data.get(stride * 4 + 4));
|
||||
assertEquals(0, data.get(stride * 5 + 5));
|
||||
assertEquals(0, data.get(stride * 6 + 6));
|
||||
assertEquals(-1, data.get(stride * 7 + 7));
|
||||
assertEquals(0, data.get(stride * 8 + 8)); // inner border
|
||||
assertEquals(-1, data.get(stride * 9 + 9)); // outer border
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,14 @@ class LoadConfigTest {
|
||||
@ParameterizedTest
|
||||
@EnumSource(AprilTagFields.class)
|
||||
void testLoad(AprilTagFields field) {
|
||||
AprilTagFieldLayout layout = Assertions.assertDoesNotThrow(field::loadAprilTagLayoutField);
|
||||
AprilTagFieldLayout layout =
|
||||
Assertions.assertDoesNotThrow(() -> AprilTagFieldLayout.loadField(field));
|
||||
assertNotNull(layout);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test2022RapidReact() {
|
||||
AprilTagFieldLayout layout = AprilTagFields.k2022RapidReact.loadAprilTagLayoutField();
|
||||
AprilTagFieldLayout layout = AprilTagFieldLayout.loadField(AprilTagFields.k2022RapidReact);
|
||||
|
||||
// Blue Hangar Truss - Hub
|
||||
Pose3d expectedPose =
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "frc/apriltag/AprilTagFieldLayout.h"
|
||||
#include "frc/apriltag/AprilTagFields.h"
|
||||
|
||||
namespace frc {
|
||||
@@ -20,7 +21,7 @@ std::vector<AprilTagField> GetAllFields() {
|
||||
|
||||
TEST(AprilTagFieldsTest, TestLoad2022RapidReact) {
|
||||
AprilTagFieldLayout layout =
|
||||
LoadAprilTagLayoutField(AprilTagField::k2022RapidReact);
|
||||
AprilTagFieldLayout::LoadField(AprilTagField::k2022RapidReact);
|
||||
|
||||
// Blue Hangar Truss - Hub
|
||||
auto expectedPose =
|
||||
@@ -53,7 +54,7 @@ class AllFieldsFixtureTest : public ::testing::TestWithParam<AprilTagField> {};
|
||||
|
||||
TEST_P(AllFieldsFixtureTest, CheckEntireEnum) {
|
||||
AprilTagField field = GetParam();
|
||||
EXPECT_NO_THROW(LoadAprilTagLayoutField(field));
|
||||
EXPECT_NO_THROW(AprilTagFieldLayout::LoadField(field));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(ValuesEnumTestInstTests, AllFieldsFixtureTest,
|
||||
|
||||
@@ -14,7 +14,6 @@ import edu.wpi.first.cscore.VideoEvent;
|
||||
import edu.wpi.first.cscore.VideoException;
|
||||
import edu.wpi.first.cscore.VideoListener;
|
||||
import edu.wpi.first.cscore.VideoMode;
|
||||
import edu.wpi.first.cscore.VideoMode.PixelFormat;
|
||||
import edu.wpi.first.cscore.VideoSink;
|
||||
import edu.wpi.first.cscore.VideoSource;
|
||||
import edu.wpi.first.networktables.BooleanEntry;
|
||||
@@ -27,6 +26,8 @@ import edu.wpi.first.networktables.StringArrayPublisher;
|
||||
import edu.wpi.first.networktables.StringArrayTopic;
|
||||
import edu.wpi.first.networktables.StringEntry;
|
||||
import edu.wpi.first.networktables.StringPublisher;
|
||||
import edu.wpi.first.util.PixelFormat;
|
||||
import java.lang.ref.Reference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
@@ -39,6 +40,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
* NetworkTables.
|
||||
*/
|
||||
public final class CameraServer {
|
||||
/** CameraServer base port. */
|
||||
public static final int kBasePort = 1181;
|
||||
|
||||
private static final String kPublishName = "/CameraPublisher";
|
||||
@@ -139,6 +141,7 @@ public final class CameraServer {
|
||||
if (m_choicesPublisher != null) {
|
||||
m_choicesPublisher.close();
|
||||
}
|
||||
Reference.reachabilityFence(m_videoListener);
|
||||
}
|
||||
|
||||
BooleanEntry m_booleanValueEntry;
|
||||
@@ -222,7 +225,7 @@ public final class CameraServer {
|
||||
// - "PropertyInfo/{Property}" - Property supporting information
|
||||
|
||||
// Listener for video events
|
||||
@SuppressWarnings({"PMD.UnusedPrivateField", "PMD.AvoidCatchingGenericException"})
|
||||
@SuppressWarnings("PMD.AvoidCatchingGenericException")
|
||||
private static final VideoListener m_videoListener =
|
||||
new VideoListener(
|
||||
event -> {
|
||||
@@ -686,7 +689,7 @@ public final class CameraServer {
|
||||
*/
|
||||
public static MjpegServer addSwitchedCamera(String name) {
|
||||
// create a dummy CvSource
|
||||
CvSource source = new CvSource(name, VideoMode.PixelFormat.kMJPEG, 160, 120, 30);
|
||||
CvSource source = new CvSource(name, PixelFormat.kMJPEG, 160, 120, 30);
|
||||
MjpegServer server = startAutomaticCapture(source);
|
||||
synchronized (CameraServer.class) {
|
||||
m_fixedSources.put(server.getHandle(), source.getHandle());
|
||||
@@ -801,7 +804,7 @@ public final class CameraServer {
|
||||
* @return OpenCV source for the MJPEG stream
|
||||
*/
|
||||
public static CvSource putVideo(String name, int width, int height) {
|
||||
CvSource source = new CvSource(name, VideoMode.PixelFormat.kMJPEG, width, height, 30);
|
||||
CvSource source = new CvSource(name, PixelFormat.kMJPEG, width, height, 30);
|
||||
startAutomaticCapture(source);
|
||||
return source;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
package edu.wpi.first.cameraserver;
|
||||
|
||||
/** CameraServer shared functions. */
|
||||
public interface CameraServerShared {
|
||||
/**
|
||||
* get the main thread id func.
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
package edu.wpi.first.cameraserver;
|
||||
|
||||
/** Storage for CameraServerShared instance. */
|
||||
public final class CameraServerSharedStore {
|
||||
private static CameraServerShared cameraServerShared;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.opencv.core.Mat;
|
||||
* code. The easiest way to use this is to run it in a {@link VisionThread} and use the listener to
|
||||
* take snapshots of the pipeline's outputs.
|
||||
*
|
||||
* @param <P> Vision pipeline type.
|
||||
* @see VisionPipeline
|
||||
* @see VisionThread
|
||||
* @see <a href="package-summary.html">vision</a>
|
||||
|
||||
@@ -22,10 +22,8 @@ namespace frc {
|
||||
*/
|
||||
class CameraServer {
|
||||
public:
|
||||
/// CameraServer base port.
|
||||
static constexpr uint16_t kBasePort = 1181;
|
||||
static constexpr int kSize640x480 = 0;
|
||||
static constexpr int kSize320x240 = 1;
|
||||
static constexpr int kSize160x120 = 2;
|
||||
|
||||
/**
|
||||
* Start automatically capturing images to send to the dashboard.
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
macro(wpilib_target_warnings target)
|
||||
if(NOT MSVC)
|
||||
target_compile_options(
|
||||
${target}
|
||||
PRIVATE -Wall -pedantic -Wextra -Werror -Wno-unused-parameter ${WPILIB_TARGET_WARNINGS}
|
||||
set(WARNING_FLAGS
|
||||
-Wall
|
||||
-pedantic
|
||||
-Wextra
|
||||
-Wno-unused-parameter
|
||||
${WPILIB_TARGET_WARNINGS}
|
||||
)
|
||||
if(NOT NO_WERROR)
|
||||
set(WARNING_FLAGS ${WARNING_FLAGS} -Werror)
|
||||
endif()
|
||||
|
||||
target_compile_options(${target} PRIVATE ${WARNING_FLAGS})
|
||||
else()
|
||||
target_compile_options(
|
||||
${target}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
include(CMakeFindDependencyMacro)
|
||||
@FILENAME_DEP_REPLACE@
|
||||
@WPIUTIL_DEP_REPLACE@
|
||||
@WPINET_DEP_REPLACE@
|
||||
find_dependency(OpenCV)
|
||||
|
||||
@FILENAME_DEP_REPLACE@
|
||||
|
||||
@@ -9,21 +9,36 @@ import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.opencv.core.Core;
|
||||
|
||||
/** CameraServer CV JNI. */
|
||||
public class CameraServerCvJNI {
|
||||
static boolean libraryLoaded = false;
|
||||
|
||||
static RuntimeLoader<Core> loader = null;
|
||||
|
||||
/** Sets whether JNI should be loaded in the static block. */
|
||||
public static class Helper {
|
||||
private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(true);
|
||||
|
||||
/**
|
||||
* Returns true if the JNI should be loaded in the static block.
|
||||
*
|
||||
* @return True if the JNI should be loaded in the static block.
|
||||
*/
|
||||
public static boolean getExtractOnStaticLoad() {
|
||||
return extractOnStaticLoad.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the JNI should be loaded in the static block.
|
||||
*
|
||||
* @param load Whether the JNI should be loaded in the static block.
|
||||
*/
|
||||
public static void setExtractOnStaticLoad(boolean load) {
|
||||
extractOnStaticLoad.set(load);
|
||||
}
|
||||
|
||||
/** Utility class. */
|
||||
private Helper() {}
|
||||
}
|
||||
|
||||
static {
|
||||
@@ -59,17 +74,64 @@ public class CameraServerCvJNI {
|
||||
libraryLoaded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a CV source.
|
||||
*
|
||||
* @param name Name.
|
||||
* @param pixelFormat OpenCV pixel format.
|
||||
* @param width Image width.
|
||||
* @param height Image height.
|
||||
* @param fps Frames per second.
|
||||
* @return CV source.
|
||||
*/
|
||||
public static native int createCvSource(
|
||||
String name, int pixelFormat, int width, int height, int fps);
|
||||
|
||||
/**
|
||||
* Put source frame.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @param imageNativeObj Image native object handle.
|
||||
*/
|
||||
public static native void putSourceFrame(int source, long imageNativeObj);
|
||||
|
||||
/**
|
||||
* Creates a CV sink.
|
||||
*
|
||||
* @param name Name.
|
||||
* @param pixelFormat OpenCV pixel format.
|
||||
* @return CV sink handle.
|
||||
*/
|
||||
public static native int createCvSink(String name, int pixelFormat);
|
||||
|
||||
// /**
|
||||
// * Creates a CV sink callback.
|
||||
// *
|
||||
// * @param name Name.
|
||||
// * @param processFrame Process frame callback.
|
||||
// */
|
||||
// public static native int createCvSinkCallback(String name,
|
||||
// void (*processFrame)(long time));
|
||||
|
||||
/**
|
||||
* Returns sink frame handle.
|
||||
*
|
||||
* @param sink Sink handle.
|
||||
* @param imageNativeObj Image native object handle.
|
||||
* @return Sink frame handle.
|
||||
*/
|
||||
public static native long grabSinkFrame(int sink, long imageNativeObj);
|
||||
|
||||
/**
|
||||
* Returns sink frame timeout in microseconds.
|
||||
*
|
||||
* @param sink Sink handle.
|
||||
* @param imageNativeObj Image native object handle.
|
||||
* @param timeout Timeout in seconds.
|
||||
* @return Sink frame timeout in microseconds.
|
||||
*/
|
||||
public static native long grabSinkFrameTimeout(int sink, long imageNativeObj, double timeout);
|
||||
|
||||
/** Utility class. */
|
||||
private CameraServerCvJNI() {}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
||||
|
||||
package edu.wpi.first.cscore;
|
||||
|
||||
import edu.wpi.first.cscore.VideoMode.PixelFormat;
|
||||
import edu.wpi.first.util.PixelFormat;
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
package edu.wpi.first.cscore;
|
||||
|
||||
import edu.wpi.first.util.PixelFormat;
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
/**
|
||||
@@ -32,7 +33,7 @@ public class CvSource extends ImageSource {
|
||||
* @param height height
|
||||
* @param fps fps
|
||||
*/
|
||||
public CvSource(String name, VideoMode.PixelFormat pixelFormat, int width, int height, int fps) {
|
||||
public CvSource(String name, PixelFormat pixelFormat, int width, int height, int fps) {
|
||||
super(CameraServerCvJNI.createCvSource(name, pixelFormat.getValue(), width, height, fps));
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,15 @@ package edu.wpi.first.cscore;
|
||||
|
||||
/** A source that represents a MJPEG-over-HTTP (IP) camera. */
|
||||
public class HttpCamera extends VideoCamera {
|
||||
/** HTTP camera kind. */
|
||||
public enum HttpCameraKind {
|
||||
/** Unknown camera kind. */
|
||||
kUnknown(0),
|
||||
/** MJPG Streamer camera. */
|
||||
kMJPGStreamer(1),
|
||||
/** CS Core camera. */
|
||||
kCSCore(2),
|
||||
/** Axis camera. */
|
||||
kAxis(3);
|
||||
|
||||
private final int value;
|
||||
@@ -18,6 +23,11 @@ public class HttpCamera extends VideoCamera {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HttpCameraKind value.
|
||||
*
|
||||
* @return HttpCameraKind value.
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,13 @@
|
||||
|
||||
package edu.wpi.first.cscore;
|
||||
|
||||
/** A base class for single image reading sinks. */
|
||||
public abstract class ImageSink extends VideoSink {
|
||||
/**
|
||||
* Constructs an ImageSink.
|
||||
*
|
||||
* @param handle The image sink handle.
|
||||
*/
|
||||
protected ImageSink(int handle) {
|
||||
super(handle);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,13 @@
|
||||
|
||||
package edu.wpi.first.cscore;
|
||||
|
||||
/** A base class for single image providing sources. */
|
||||
public abstract class ImageSource extends VideoSource {
|
||||
/**
|
||||
* Constructs an ImageSource.
|
||||
*
|
||||
* @param handle The image source handle.
|
||||
*/
|
||||
protected ImageSource(int handle) {
|
||||
super(handle);
|
||||
}
|
||||
|
||||
@@ -6,14 +6,32 @@ package edu.wpi.first.cscore;
|
||||
|
||||
/** A source that represents a video camera. */
|
||||
public class VideoCamera extends VideoSource {
|
||||
/** White balance. */
|
||||
public static class WhiteBalance {
|
||||
/** Fixed indoor white balance. */
|
||||
public static final int kFixedIndoor = 3000;
|
||||
|
||||
/** Fixed outdoor white balance 1. */
|
||||
public static final int kFixedOutdoor1 = 4000;
|
||||
|
||||
/** Fixed outdoor white balance 2. */
|
||||
public static final int kFixedOutdoor2 = 5000;
|
||||
|
||||
/** Fixed fluorescent white balance 1. */
|
||||
public static final int kFixedFluorescent1 = 5100;
|
||||
|
||||
/** Fixed fluorescent white balance 2. */
|
||||
public static final int kFixedFlourescent2 = 5200;
|
||||
|
||||
/** Default constructor. */
|
||||
public WhiteBalance() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a VideoCamera.
|
||||
*
|
||||
* @param handle The video camera handle.
|
||||
*/
|
||||
protected VideoCamera(int handle) {
|
||||
super(handle);
|
||||
}
|
||||
|
||||
@@ -7,27 +7,49 @@ package edu.wpi.first.cscore;
|
||||
/** Video event. */
|
||||
@SuppressWarnings("MemberName")
|
||||
public class VideoEvent {
|
||||
/** VideoEvent kind. */
|
||||
public enum Kind {
|
||||
/** Unknown video event. */
|
||||
kUnknown(0x0000),
|
||||
/** Source Created event. */
|
||||
kSourceCreated(0x0001),
|
||||
/** Source Destroyed event. */
|
||||
kSourceDestroyed(0x0002),
|
||||
/** Source Connected event. */
|
||||
kSourceConnected(0x0004),
|
||||
/** Source Disconnected event. */
|
||||
kSourceDisconnected(0x0008),
|
||||
/** Source Video Modes Updated event. */
|
||||
kSourceVideoModesUpdated(0x0010),
|
||||
/** Source VideoMode Changed event. */
|
||||
kSourceVideoModeChanged(0x0020),
|
||||
/** Source Property Created event. */
|
||||
kSourcePropertyCreated(0x0040),
|
||||
/** Source Property Value Updated event. */
|
||||
kSourcePropertyValueUpdated(0x0080),
|
||||
/** Source Property Choices Updated event. */
|
||||
kSourcePropertyChoicesUpdated(0x0100),
|
||||
/** Sink Source Changed event. */
|
||||
kSinkSourceChanged(0x0200),
|
||||
/** Sink Created event. */
|
||||
kSinkCreated(0x0400),
|
||||
/** Sink Destroyed event. */
|
||||
kSinkDestroyed(0x0800),
|
||||
/** Sink Enabled event. */
|
||||
kSinkEnabled(0x1000),
|
||||
/** Sink Disabled event. */
|
||||
kSinkDisabled(0x2000),
|
||||
/** Network Interfaces Changed event. */
|
||||
kNetworkInterfacesChanged(0x4000),
|
||||
/** Telemetry Updated event. */
|
||||
kTelemetryUpdated(0x8000),
|
||||
/** Sink Property Created event. */
|
||||
kSinkPropertyCreated(0x10000),
|
||||
/** Sink Property Value Updated event. */
|
||||
kSinkPropertyValueUpdated(0x20000),
|
||||
/** Sink Property Choices Updated event. */
|
||||
kSinkPropertyChoicesUpdated(0x40000),
|
||||
/** Usb Cameras Changed event. */
|
||||
kUsbCamerasChanged(0x80000);
|
||||
|
||||
private final int value;
|
||||
@@ -36,6 +58,11 @@ public class VideoEvent {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the kind value.
|
||||
*
|
||||
* @return The kind value.
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
@@ -118,39 +145,67 @@ public class VideoEvent {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/** The video event kind. */
|
||||
public Kind kind;
|
||||
|
||||
// Valid for kSource* and kSink* respectively
|
||||
/**
|
||||
* The source handle.
|
||||
*
|
||||
* <p>Valid for kSource* and kSink* respectively.
|
||||
*/
|
||||
public int sourceHandle;
|
||||
|
||||
/** The sink handle. */
|
||||
public int sinkHandle;
|
||||
|
||||
// Source/sink/property name
|
||||
/** Source/sink/property name. */
|
||||
public String name;
|
||||
|
||||
// Fields for kSourceVideoModeChanged event
|
||||
// Fields for kSourceVideoModeChanged event.
|
||||
|
||||
/** New source video mode. */
|
||||
public VideoMode mode;
|
||||
|
||||
// Fields for kSourceProperty* events
|
||||
// Fields for kSourceProperty* events.
|
||||
|
||||
/** Source property handle. */
|
||||
public int propertyHandle;
|
||||
|
||||
/** Source property kind. */
|
||||
public VideoProperty.Kind propertyKind;
|
||||
|
||||
/** Event value. */
|
||||
public int value;
|
||||
|
||||
/** Event value as a string. */
|
||||
public String valueStr;
|
||||
|
||||
// Listener that was triggered
|
||||
/** Listener that was triggered. */
|
||||
public int listener;
|
||||
|
||||
/**
|
||||
* Returns the source associated with the event (if any).
|
||||
*
|
||||
* @return The source associated with the event (if any).
|
||||
*/
|
||||
public VideoSource getSource() {
|
||||
return new VideoSource(CameraServerJNI.copySource(sourceHandle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sink associated with the event (if any).
|
||||
*
|
||||
* @return The sink associated with the event (if any).
|
||||
*/
|
||||
public VideoSink getSink() {
|
||||
return new VideoSink(CameraServerJNI.copySink(sinkHandle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the property associated with the event (if any).
|
||||
*
|
||||
* @return The property associated with the event (if any).
|
||||
*/
|
||||
public VideoProperty getProperty() {
|
||||
return new VideoProperty(propertyHandle, propertyKind);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,11 @@ package edu.wpi.first.cscore;
|
||||
public class VideoException extends RuntimeException {
|
||||
private static final long serialVersionUID = -9155939328084105145L;
|
||||
|
||||
/**
|
||||
* Constructs the exception with the given message.
|
||||
*
|
||||
* @param msg The exception message.
|
||||
*/
|
||||
public VideoException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
@@ -51,6 +51,11 @@ public class VideoListener implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the video listener handle is valid.
|
||||
*
|
||||
* @return True if the video listener handle is valid.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return m_handle != 0;
|
||||
}
|
||||
|
||||
@@ -4,38 +4,12 @@
|
||||
|
||||
package edu.wpi.first.cscore;
|
||||
|
||||
import edu.wpi.first.util.PixelFormat;
|
||||
import java.util.Objects;
|
||||
|
||||
/** Video mode. */
|
||||
@SuppressWarnings("MemberName")
|
||||
public class VideoMode {
|
||||
public enum PixelFormat {
|
||||
kUnknown(0),
|
||||
kMJPEG(1),
|
||||
kYUYV(2),
|
||||
kRGB565(3),
|
||||
kBGR(4),
|
||||
kGray(5),
|
||||
kY16(6),
|
||||
kUYVY(7);
|
||||
|
||||
private final int value;
|
||||
|
||||
PixelFormat(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private static final PixelFormat[] m_pixelFormatValues = PixelFormat.values();
|
||||
|
||||
public static PixelFormat getPixelFormatFromInt(int pixelFormat) {
|
||||
return m_pixelFormatValues[pixelFormat];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new video mode.
|
||||
*
|
||||
@@ -45,7 +19,7 @@ public class VideoMode {
|
||||
* @param fps The camera's frames per second.
|
||||
*/
|
||||
public VideoMode(int pixelFormat, int width, int height, int fps) {
|
||||
this.pixelFormat = getPixelFormatFromInt(pixelFormat);
|
||||
this.pixelFormat = PixelFormat.getFromInt(pixelFormat);
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.fps = fps;
|
||||
|
||||
@@ -6,11 +6,17 @@ package edu.wpi.first.cscore;
|
||||
|
||||
/** A source or sink property. */
|
||||
public class VideoProperty {
|
||||
/** VideoProperty property types. */
|
||||
public enum Kind {
|
||||
/** No specific property. */
|
||||
kNone(0),
|
||||
/** Boolean property. */
|
||||
kBoolean(1),
|
||||
/** Integer property. */
|
||||
kInteger(2),
|
||||
/** String property. */
|
||||
kString(4),
|
||||
/** Enum property. */
|
||||
kEnum(8);
|
||||
|
||||
private final int value;
|
||||
@@ -19,6 +25,11 @@ public class VideoProperty {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Kind value.
|
||||
*
|
||||
* @return The Kind value.
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
@@ -45,69 +56,152 @@ public class VideoProperty {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns property name.
|
||||
*
|
||||
* @return Property name.
|
||||
*/
|
||||
public String getName() {
|
||||
return CameraServerJNI.getPropertyName(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns property kind.
|
||||
*
|
||||
* @return Property kind.
|
||||
*/
|
||||
public Kind getKind() {
|
||||
return m_kind;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if property is valid.
|
||||
*
|
||||
* @return True if property is valid.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return m_kind != Kind.kNone;
|
||||
}
|
||||
|
||||
// Kind checkers
|
||||
/**
|
||||
* Returns true if property is a boolean.
|
||||
*
|
||||
* @return True if property is a boolean.
|
||||
*/
|
||||
public boolean isBoolean() {
|
||||
return m_kind == Kind.kBoolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if property is an integer.
|
||||
*
|
||||
* @return True if property is an integer.
|
||||
*/
|
||||
public boolean isInteger() {
|
||||
return m_kind == Kind.kInteger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if property is a string.
|
||||
*
|
||||
* @return True if property is a string.
|
||||
*/
|
||||
public boolean isString() {
|
||||
return m_kind == Kind.kString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if property is an enum.
|
||||
*
|
||||
* @return True if property is an enum.
|
||||
*/
|
||||
public boolean isEnum() {
|
||||
return m_kind == Kind.kEnum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns property value.
|
||||
*
|
||||
* @return Property value.
|
||||
*/
|
||||
public int get() {
|
||||
return CameraServerJNI.getProperty(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets property value.
|
||||
*
|
||||
* @param value Property value.
|
||||
*/
|
||||
public void set(int value) {
|
||||
CameraServerJNI.setProperty(m_handle, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns property minimum value.
|
||||
*
|
||||
* @return Property minimum value.
|
||||
*/
|
||||
public int getMin() {
|
||||
return CameraServerJNI.getPropertyMin(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns property maximum value.
|
||||
*
|
||||
* @return Property maximum value.
|
||||
*/
|
||||
public int getMax() {
|
||||
return CameraServerJNI.getPropertyMax(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns property step size.
|
||||
*
|
||||
* @return Property step size.
|
||||
*/
|
||||
public int getStep() {
|
||||
return CameraServerJNI.getPropertyStep(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns property default value.
|
||||
*
|
||||
* @return Property default value.
|
||||
*/
|
||||
public int getDefault() {
|
||||
return CameraServerJNI.getPropertyDefault(m_handle);
|
||||
}
|
||||
|
||||
// String-specific functions
|
||||
/**
|
||||
* Returns the string property value.
|
||||
*
|
||||
* <p>This function is string-specific.
|
||||
*
|
||||
* @return The string property value.
|
||||
*/
|
||||
public String getString() {
|
||||
return CameraServerJNI.getStringProperty(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the string property value.
|
||||
*
|
||||
* <p>This function is string-specific.
|
||||
*
|
||||
* @param value String property value.
|
||||
*/
|
||||
public void setString(String value) {
|
||||
CameraServerJNI.setStringProperty(m_handle, value);
|
||||
}
|
||||
|
||||
// Enum-specific functions
|
||||
/**
|
||||
* Returns the possible values for the enum property value.
|
||||
*
|
||||
* <p>This function is enum-specific.
|
||||
*
|
||||
* @return The possible values for the enum property value.
|
||||
*/
|
||||
public String[] getChoices() {
|
||||
return CameraServerJNI.getEnumPropertyChoices(m_handle);
|
||||
}
|
||||
|
||||
@@ -9,10 +9,15 @@ package edu.wpi.first.cscore;
|
||||
* (e.g. from a stereo or depth camera); these are called channels.
|
||||
*/
|
||||
public class VideoSink implements AutoCloseable {
|
||||
/** Video sink types. */
|
||||
public enum Kind {
|
||||
/** Unknown video sink type. */
|
||||
kUnknown(0),
|
||||
/** MJPEG video sink. */
|
||||
kMjpeg(2),
|
||||
/** CV video sink. */
|
||||
kCv(4),
|
||||
/** Raw video sink. */
|
||||
kRaw(8);
|
||||
|
||||
private final int value;
|
||||
@@ -21,6 +26,11 @@ public class VideoSink implements AutoCloseable {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Kind value.
|
||||
*
|
||||
* @return The Kind value.
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
@@ -43,6 +53,11 @@ public class VideoSink implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a VideoSink.
|
||||
*
|
||||
* @param handle The video sink handle.
|
||||
*/
|
||||
protected VideoSink(int handle) {
|
||||
m_handle = handle;
|
||||
}
|
||||
@@ -55,10 +70,20 @@ public class VideoSink implements AutoCloseable {
|
||||
m_handle = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the VideoSink is valid.
|
||||
*
|
||||
* @return True if the VideoSink is valid.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return m_handle != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the video sink handle.
|
||||
*
|
||||
* @return The video sink handle.
|
||||
*/
|
||||
public int getHandle() {
|
||||
return m_handle;
|
||||
}
|
||||
@@ -217,5 +242,6 @@ public class VideoSink implements AutoCloseable {
|
||||
return rv;
|
||||
}
|
||||
|
||||
/** The VideoSink handle. */
|
||||
protected int m_handle;
|
||||
}
|
||||
|
||||
@@ -4,16 +4,24 @@
|
||||
|
||||
package edu.wpi.first.cscore;
|
||||
|
||||
import edu.wpi.first.util.PixelFormat;
|
||||
|
||||
/**
|
||||
* A source for video that provides a sequence of frames. Each frame may consist of multiple images
|
||||
* (e.g. from a stereo or depth camera); these are called channels.
|
||||
*/
|
||||
public class VideoSource implements AutoCloseable {
|
||||
/** Video source kind. */
|
||||
public enum Kind {
|
||||
/** Unknown video source. */
|
||||
kUnknown(0),
|
||||
/** USB video source. */
|
||||
kUsb(1),
|
||||
/** HTTP video source. */
|
||||
kHttp(2),
|
||||
/** CV video source. */
|
||||
kCv(4),
|
||||
/** Raw video source. */
|
||||
kRaw(8);
|
||||
|
||||
private final int value;
|
||||
@@ -22,6 +30,11 @@ public class VideoSource implements AutoCloseable {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Kind value.
|
||||
*
|
||||
* @return The Kind value.
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
@@ -49,6 +62,11 @@ public class VideoSource implements AutoCloseable {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ConnectionStrategy value.
|
||||
*
|
||||
* @return The ConnectionStrategy value.
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
@@ -73,6 +91,11 @@ public class VideoSource implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a VideoSource.
|
||||
*
|
||||
* @param handle The video source handle.
|
||||
*/
|
||||
protected VideoSource(int handle) {
|
||||
m_handle = handle;
|
||||
}
|
||||
@@ -85,10 +108,20 @@ public class VideoSource implements AutoCloseable {
|
||||
m_handle = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the VideoSource is valid.
|
||||
*
|
||||
* @return True if the VideoSource is valid.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return m_handle != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the video source handle.
|
||||
*
|
||||
* @return The video source handle.
|
||||
*/
|
||||
public int getHandle() {
|
||||
return m_handle;
|
||||
}
|
||||
@@ -235,7 +268,7 @@ public class VideoSource implements AutoCloseable {
|
||||
* @param fps desired FPS
|
||||
* @return True if set successfully
|
||||
*/
|
||||
public boolean setVideoMode(VideoMode.PixelFormat pixelFormat, int width, int height, int fps) {
|
||||
public boolean setVideoMode(PixelFormat pixelFormat, int width, int height, int fps) {
|
||||
return CameraServerJNI.setSourceVideoMode(m_handle, pixelFormat.getValue(), width, height, fps);
|
||||
}
|
||||
|
||||
@@ -245,7 +278,7 @@ public class VideoSource implements AutoCloseable {
|
||||
* @param pixelFormat desired pixel format
|
||||
* @return True if set successfully
|
||||
*/
|
||||
public boolean setPixelFormat(VideoMode.PixelFormat pixelFormat) {
|
||||
public boolean setPixelFormat(PixelFormat pixelFormat) {
|
||||
return CameraServerJNI.setSourcePixelFormat(m_handle, pixelFormat.getValue());
|
||||
}
|
||||
|
||||
@@ -372,5 +405,6 @@ public class VideoSource implements AutoCloseable {
|
||||
return rv;
|
||||
}
|
||||
|
||||
/** Video source handle. */
|
||||
protected int m_handle;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public class RawSink extends ImageSink {
|
||||
* @return Frame time, or 0 on error (call getError() to obtain the error message); the frame time
|
||||
* is in the same time base as wpi::Now(), and is in 1 us increments.
|
||||
*/
|
||||
protected long grabFrame(RawFrame frame) {
|
||||
public long grabFrame(RawFrame frame) {
|
||||
return grabFrame(frame, 0.225);
|
||||
}
|
||||
|
||||
@@ -46,8 +46,8 @@ public class RawSink extends ImageSink {
|
||||
* @return Frame time, or 0 on error (call getError() to obtain the error message); the frame time
|
||||
* is in the same time base as wpi::Now(), and is in 1 us increments.
|
||||
*/
|
||||
protected long grabFrame(RawFrame frame, double timeout) {
|
||||
return CameraServerJNI.grabSinkFrameTimeout(m_handle, frame, timeout);
|
||||
public long grabFrame(RawFrame frame, double timeout) {
|
||||
return CameraServerJNI.grabRawSinkFrameTimeout(m_handle, frame, frame.getNativeObj(), timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,7 +58,7 @@ public class RawSink extends ImageSink {
|
||||
* @return Frame time, or 0 on error (call getError() to obtain the error message); the frame time
|
||||
* is in the same time base as wpi::Now(), and is in 1 us increments.
|
||||
*/
|
||||
protected long grabFrameNoTimeout(RawFrame frame) {
|
||||
return CameraServerJNI.grabSinkFrame(m_handle, frame);
|
||||
public long grabFrameNoTimeout(RawFrame frame) {
|
||||
return CameraServerJNI.grabRawSinkFrame(m_handle, frame, frame.getNativeObj());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ package edu.wpi.first.cscore.raw;
|
||||
import edu.wpi.first.cscore.CameraServerJNI;
|
||||
import edu.wpi.first.cscore.ImageSource;
|
||||
import edu.wpi.first.cscore.VideoMode;
|
||||
import edu.wpi.first.util.PixelFormat;
|
||||
import edu.wpi.first.util.RawFrame;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* A source for user code to provide video frames as raw bytes.
|
||||
@@ -36,7 +38,7 @@ public class RawSource extends ImageSource {
|
||||
* @param height height
|
||||
* @param fps fps
|
||||
*/
|
||||
public RawSource(String name, VideoMode.PixelFormat pixelFormat, int width, int height, int fps) {
|
||||
public RawSource(String name, PixelFormat pixelFormat, int width, int height, int fps) {
|
||||
super(CameraServerJNI.createRawSource(name, pixelFormat.getValue(), width, height, fps));
|
||||
}
|
||||
|
||||
@@ -45,35 +47,41 @@ public class RawSource extends ImageSource {
|
||||
*
|
||||
* @param image raw frame image
|
||||
*/
|
||||
protected void putFrame(RawFrame image) {
|
||||
CameraServerJNI.putRawSourceFrame(m_handle, image);
|
||||
public void putFrame(RawFrame image) {
|
||||
CameraServerJNI.putRawSourceFrame(m_handle, image.getNativeObj());
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a raw image and notify sinks.
|
||||
*
|
||||
* @param data raw frame data pointer
|
||||
* @param data raw frame native data pointer
|
||||
* @param size total size in bytes
|
||||
* @param width frame width
|
||||
* @param height frame height
|
||||
* @param stride size of each row in bytes
|
||||
* @param pixelFormat pixel format
|
||||
* @param totalData length of data in total
|
||||
*/
|
||||
protected void putFrame(long data, int width, int height, int pixelFormat, int totalData) {
|
||||
CameraServerJNI.putRawSourceFrame(m_handle, data, width, height, pixelFormat, totalData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a raw image and notify sinks.
|
||||
*
|
||||
* @param data raw frame data pointer
|
||||
* @param width frame width
|
||||
* @param height frame height
|
||||
* @param pixelFormat pixel format
|
||||
* @param totalData length of data in total
|
||||
*/
|
||||
protected void putFrame(
|
||||
long data, int width, int height, VideoMode.PixelFormat pixelFormat, int totalData) {
|
||||
CameraServerJNI.putRawSourceFrame(
|
||||
m_handle, data, width, height, pixelFormat.getValue(), totalData);
|
||||
long data, int size, int width, int height, int stride, PixelFormat pixelFormat) {
|
||||
CameraServerJNI.putRawSourceFrameData(
|
||||
m_handle, data, size, width, height, stride, pixelFormat.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a raw image and notify sinks.
|
||||
*
|
||||
* @param data raw frame native ByteBuffer
|
||||
* @param width frame width
|
||||
* @param height frame height
|
||||
* @param stride size of each row in bytes
|
||||
* @param pixelFormat pixel format
|
||||
*/
|
||||
public void putFrame(
|
||||
ByteBuffer data, int width, int height, int stride, PixelFormat pixelFormat) {
|
||||
if (!data.isDirect()) {
|
||||
throw new UnsupportedOperationException("ByteBuffer must be direct");
|
||||
}
|
||||
CameraServerJNI.putRawSourceFrameBB(
|
||||
m_handle, data, data.limit(), width, height, stride, pixelFormat.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,23 @@ class Image {
|
||||
return cv::Mat{height, width, type, m_data.data()};
|
||||
}
|
||||
|
||||
int GetStride() const {
|
||||
switch (pixelFormat) {
|
||||
case VideoMode::kYUYV:
|
||||
case VideoMode::kRGB565:
|
||||
case VideoMode::kY16:
|
||||
case VideoMode::kUYVY:
|
||||
return 2 * width;
|
||||
case VideoMode::kBGR:
|
||||
return 3 * width;
|
||||
case VideoMode::kGray:
|
||||
return width;
|
||||
case VideoMode::kMJPEG:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
cv::_InputArray AsInputArray() { return cv::_InputArray{m_data}; }
|
||||
|
||||
bool Is(int width_, int height_) {
|
||||
|
||||
@@ -109,10 +109,10 @@ uint64_t RawSinkImpl::GrabFrameImpl(WPI_RawFrame& rawFrame,
|
||||
WPI_AllocateRawFrameData(&rawFrame, newImage->size());
|
||||
rawFrame.height = newImage->height;
|
||||
rawFrame.width = newImage->width;
|
||||
rawFrame.stride = newImage->GetStride();
|
||||
rawFrame.pixelFormat = newImage->pixelFormat;
|
||||
rawFrame.totalData = newImage->size();
|
||||
std::copy(newImage->data(), newImage->data() + rawFrame.totalData,
|
||||
rawFrame.data);
|
||||
rawFrame.size = newImage->size();
|
||||
std::copy(newImage->data(), newImage->data() + rawFrame.size, rawFrame.data);
|
||||
|
||||
return incomingFrame.GetTime();
|
||||
}
|
||||
|
||||
@@ -39,10 +39,11 @@ void RawSourceImpl::PutFrame(const WPI_RawFrame& image) {
|
||||
type = CV_8UC1;
|
||||
break;
|
||||
}
|
||||
cv::Mat finalImage{image.height, image.width, type, image.data};
|
||||
cv::Mat finalImage{image.height, image.width, type, image.data,
|
||||
static_cast<size_t>(image.stride)};
|
||||
std::unique_ptr<Image> dest =
|
||||
AllocImage(static_cast<VideoMode::PixelFormat>(image.pixelFormat),
|
||||
image.width, image.height, image.totalData);
|
||||
image.width, image.height, image.size);
|
||||
finalImage.copyTo(dest->AsMat());
|
||||
|
||||
SourceImpl::PutFrame(std::move(dest), wpi::Now());
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <opencv2/core/core.hpp>
|
||||
|
||||
#define WPI_RAWFRAME_JNI
|
||||
#include <wpi/RawFrame.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/jni_util.h>
|
||||
|
||||
@@ -42,7 +45,8 @@ static JNIEnv* listenerEnv = nullptr;
|
||||
static const JClassInit classes[] = {
|
||||
{"edu/wpi/first/cscore/UsbCameraInfo", &usbCameraInfoCls},
|
||||
{"edu/wpi/first/cscore/VideoMode", &videoModeCls},
|
||||
{"edu/wpi/first/cscore/VideoEvent", &videoEventCls}};
|
||||
{"edu/wpi/first/cscore/VideoEvent", &videoEventCls},
|
||||
{"edu/wpi/first/util/RawFrame", &rawFrameCls}};
|
||||
|
||||
static const JExceptionInit exceptions[] = {
|
||||
{"edu/wpi/first/cscore/VideoException", &videoEx},
|
||||
@@ -1219,48 +1223,78 @@ Java_edu_wpi_first_cscore_CameraServerCvJNI_putSourceFrame
|
||||
}
|
||||
}
|
||||
|
||||
// int width, int height, int pixelFormat, int totalData
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_cscore_CameraServerJNI
|
||||
* Method: putRawSourceFrameBB
|
||||
* Signature: (ILjava/lang/Object;IIII)V
|
||||
* Method: putRawSourceFrame
|
||||
* Signature: (IJ)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_cscore_CameraServerJNI_putRawSourceFrameBB
|
||||
(JNIEnv* env, jclass, jint source, jobject byteBuffer, jint width,
|
||||
jint height, jint pixelFormat, jint totalData)
|
||||
Java_edu_wpi_first_cscore_CameraServerJNI_putRawSourceFrame
|
||||
(JNIEnv* env, jclass, jint source, jlong framePtr)
|
||||
{
|
||||
WPI_RawFrame rawFrame;
|
||||
rawFrame.data =
|
||||
reinterpret_cast<char*>(env->GetDirectBufferAddress(byteBuffer));
|
||||
rawFrame.totalData = totalData;
|
||||
rawFrame.pixelFormat = pixelFormat;
|
||||
rawFrame.width = width;
|
||||
rawFrame.height = height;
|
||||
auto* frame = reinterpret_cast<wpi::RawFrame*>(framePtr);
|
||||
if (!frame) {
|
||||
nullPointerEx.Throw(env, "frame is null");
|
||||
return;
|
||||
}
|
||||
CS_Status status = 0;
|
||||
cs::PutSourceFrame(source, rawFrame, &status);
|
||||
cs::PutSourceFrame(source, *frame, &status);
|
||||
CheckStatus(env, status);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_cscore_CameraServerJNI
|
||||
* Method: putRawSourceFrame
|
||||
* Signature: (IJIIII)V
|
||||
* Method: putRawSourceFrameBB
|
||||
* Signature: (ILjava/lang/Object;IIIII)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_cscore_CameraServerJNI_putRawSourceFrame
|
||||
(JNIEnv* env, jclass, jint source, jlong ptr, jint width, jint height,
|
||||
jint pixelFormat, jint totalData)
|
||||
Java_edu_wpi_first_cscore_CameraServerJNI_putRawSourceFrameBB
|
||||
(JNIEnv* env, jclass, jint source, jobject data, jint size, jint width,
|
||||
jint height, jint stride, jint pixelFormat)
|
||||
{
|
||||
WPI_RawFrame rawFrame;
|
||||
rawFrame.data = reinterpret_cast<char*>(static_cast<intptr_t>(ptr));
|
||||
rawFrame.totalData = totalData;
|
||||
rawFrame.pixelFormat = pixelFormat;
|
||||
rawFrame.width = width;
|
||||
rawFrame.height = height;
|
||||
WPI_RawFrame frame; // use WPI_Frame because we don't want the destructor
|
||||
frame.data = static_cast<uint8_t*>(env->GetDirectBufferAddress(data));
|
||||
if (!frame.data) {
|
||||
nullPointerEx.Throw(env, "data is null");
|
||||
return;
|
||||
}
|
||||
frame.freeFunc = nullptr;
|
||||
frame.freeCbData = nullptr;
|
||||
frame.size = size;
|
||||
frame.width = width;
|
||||
frame.height = height;
|
||||
frame.stride = stride;
|
||||
frame.pixelFormat = pixelFormat;
|
||||
CS_Status status = 0;
|
||||
cs::PutSourceFrame(source, rawFrame, &status);
|
||||
cs::PutSourceFrame(source, frame, &status);
|
||||
CheckStatus(env, status);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_cscore_CameraServerJNI
|
||||
* Method: putRawSourceFrameData
|
||||
* Signature: (IJIIIII)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_cscore_CameraServerJNI_putRawSourceFrameData
|
||||
(JNIEnv* env, jclass, jint source, jlong data, jint size, jint width,
|
||||
jint height, jint stride, jint pixelFormat)
|
||||
{
|
||||
WPI_RawFrame frame; // use WPI_Frame because we don't want the destructor
|
||||
frame.data = reinterpret_cast<uint8_t*>(data);
|
||||
if (!frame.data) {
|
||||
nullPointerEx.Throw(env, "data is null");
|
||||
return;
|
||||
}
|
||||
frame.freeFunc = nullptr;
|
||||
frame.freeCbData = nullptr;
|
||||
frame.size = size;
|
||||
frame.width = width;
|
||||
frame.height = height;
|
||||
frame.stride = stride;
|
||||
frame.pixelFormat = pixelFormat;
|
||||
CS_Status status = 0;
|
||||
cs::PutSourceFrame(source, frame, &status);
|
||||
CheckStatus(env, status);
|
||||
}
|
||||
|
||||
@@ -1721,72 +1755,47 @@ Java_edu_wpi_first_cscore_CameraServerCvJNI_grabSinkFrameTimeout
|
||||
}
|
||||
}
|
||||
|
||||
static void SetRawFrameData(JNIEnv* env, jobject rawFrameObj,
|
||||
jobject byteBuffer, bool didChangeDataPtr,
|
||||
const WPI_RawFrame& frame) {
|
||||
static jmethodID setMethod =
|
||||
env->GetMethodID(rawFrameCls, "setData", "(Ljava/nio/ByteBuffer;JIIII)V");
|
||||
jlong framePtr = static_cast<jlong>(reinterpret_cast<intptr_t>(frame.data));
|
||||
|
||||
if (didChangeDataPtr) {
|
||||
byteBuffer = env->NewDirectByteBuffer(frame.data, frame.dataLength);
|
||||
}
|
||||
|
||||
env->CallVoidMethod(
|
||||
rawFrameObj, setMethod, byteBuffer, framePtr,
|
||||
static_cast<jint>(frame.totalData), static_cast<jint>(frame.width),
|
||||
static_cast<jint>(frame.height), static_cast<jint>(frame.pixelFormat));
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_cscore_CameraServerJNI
|
||||
* Method: grabRawSinkFrameImpl
|
||||
* Signature: (ILjava/lang/Object;JLjava/lang/Object;III)J
|
||||
* Method: grabRawSinkFrame
|
||||
* Signature: (ILjava/lang/Object;J)J
|
||||
*/
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_edu_wpi_first_cscore_CameraServerJNI_grabRawSinkFrameImpl
|
||||
(JNIEnv* env, jclass, jint sink, jobject rawFrameObj, jlong rawFramePtr,
|
||||
jobject byteBuffer, jint width, jint height, jint pixelFormat)
|
||||
Java_edu_wpi_first_cscore_CameraServerJNI_grabRawSinkFrame
|
||||
(JNIEnv* env, jclass, jint sink, jobject frameObj, jlong framePtr)
|
||||
{
|
||||
WPI_RawFrame* ptr =
|
||||
reinterpret_cast<WPI_RawFrame*>(static_cast<intptr_t>(rawFramePtr));
|
||||
auto origDataPtr = ptr->data;
|
||||
ptr->width = width;
|
||||
ptr->height = height;
|
||||
ptr->pixelFormat = pixelFormat;
|
||||
auto* frame = reinterpret_cast<wpi::RawFrame*>(framePtr);
|
||||
auto origData = frame->data;
|
||||
CS_Status status = 0;
|
||||
auto rv = cs::GrabSinkFrame(static_cast<CS_Sink>(sink), *ptr, &status);
|
||||
auto rv = cs::GrabSinkFrame(static_cast<CS_Sink>(sink), *frame, &status);
|
||||
if (!CheckStatus(env, status)) {
|
||||
return 0;
|
||||
}
|
||||
SetRawFrameData(env, rawFrameObj, byteBuffer, origDataPtr != ptr->data, *ptr);
|
||||
wpi::SetFrameData(env, rawFrameCls, frameObj, *frame,
|
||||
origData != frame->data);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_cscore_CameraServerJNI
|
||||
* Method: grabRawSinkFrameTimeoutImpl
|
||||
* Signature: (ILjava/lang/Object;JLjava/lang/Object;IIID)J
|
||||
* Method: grabRawSinkFrameTimeout
|
||||
* Signature: (ILjava/lang/Object;JD)J
|
||||
*/
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_edu_wpi_first_cscore_CameraServerJNI_grabRawSinkFrameTimeoutImpl
|
||||
(JNIEnv* env, jclass, jint sink, jobject rawFrameObj, jlong rawFramePtr,
|
||||
jobject byteBuffer, jint width, jint height, jint pixelFormat,
|
||||
Java_edu_wpi_first_cscore_CameraServerJNI_grabRawSinkFrameTimeout
|
||||
(JNIEnv* env, jclass, jint sink, jobject frameObj, jlong framePtr,
|
||||
jdouble timeout)
|
||||
{
|
||||
WPI_RawFrame* ptr =
|
||||
reinterpret_cast<WPI_RawFrame*>(static_cast<intptr_t>(rawFramePtr));
|
||||
auto origDataPtr = ptr->data;
|
||||
ptr->width = width;
|
||||
ptr->height = height;
|
||||
ptr->pixelFormat = pixelFormat;
|
||||
auto* frame = reinterpret_cast<wpi::RawFrame*>(framePtr);
|
||||
auto origData = frame->data;
|
||||
CS_Status status = 0;
|
||||
auto rv = cs::GrabSinkFrameTimeout(static_cast<CS_Sink>(sink), *ptr, timeout,
|
||||
&status);
|
||||
auto rv = cs::GrabSinkFrameTimeout(static_cast<CS_Sink>(sink), *frame,
|
||||
timeout, &status);
|
||||
if (!CheckStatus(env, status)) {
|
||||
return 0;
|
||||
}
|
||||
SetRawFrameData(env, rawFrameObj, byteBuffer, origDataPtr != ptr->data, *ptr);
|
||||
wpi::SetFrameData(env, rawFrameCls, frameObj, *frame,
|
||||
origData != frame->data);
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,42 +44,153 @@ class VideoProperty {
|
||||
|
||||
public:
|
||||
enum Kind {
|
||||
/// No specific property.
|
||||
kNone = CS_PROP_NONE,
|
||||
/// Boolean property.
|
||||
kBoolean = CS_PROP_BOOLEAN,
|
||||
/// Integer property.
|
||||
kInteger = CS_PROP_INTEGER,
|
||||
/// String property.
|
||||
kString = CS_PROP_STRING,
|
||||
/// Enum property.
|
||||
kEnum = CS_PROP_ENUM
|
||||
};
|
||||
|
||||
VideoProperty() = default;
|
||||
|
||||
/**
|
||||
* Returns property name.
|
||||
*
|
||||
* @return Property name.
|
||||
*/
|
||||
std::string GetName() const;
|
||||
|
||||
/**
|
||||
* Returns property kind.
|
||||
*
|
||||
* @return Property kind.
|
||||
*/
|
||||
Kind GetKind() const { return m_kind; }
|
||||
|
||||
/**
|
||||
* Returns true if property is valid.
|
||||
*
|
||||
* @return True if property is valid.
|
||||
*/
|
||||
explicit operator bool() const { return m_kind != kNone; }
|
||||
|
||||
// Kind checkers
|
||||
/**
|
||||
* Returns true if property is a boolean.
|
||||
*
|
||||
* @return True if property is a boolean.
|
||||
*/
|
||||
bool IsBoolean() const { return m_kind == kBoolean; }
|
||||
|
||||
/**
|
||||
* Returns true if property is an integer.
|
||||
*
|
||||
* @return True if property is an integer.
|
||||
*/
|
||||
bool IsInteger() const { return m_kind == kInteger; }
|
||||
|
||||
/**
|
||||
* Returns true if property is a string.
|
||||
*
|
||||
* @return True if property is a string.
|
||||
*/
|
||||
bool IsString() const { return m_kind == kString; }
|
||||
|
||||
/**
|
||||
* Returns true if property is an enum.
|
||||
*
|
||||
* @return True if property is an enum.
|
||||
*/
|
||||
bool IsEnum() const { return m_kind == kEnum; }
|
||||
|
||||
/**
|
||||
* Returns property value.
|
||||
*
|
||||
* @return Property value.
|
||||
*/
|
||||
int Get() const;
|
||||
|
||||
/**
|
||||
* Sets property value.
|
||||
*
|
||||
* @param value Property value.
|
||||
*/
|
||||
void Set(int value);
|
||||
|
||||
/**
|
||||
* Returns property minimum value.
|
||||
*
|
||||
* @return Property minimum value.
|
||||
*/
|
||||
int GetMin() const;
|
||||
|
||||
/**
|
||||
* Returns property maximum value.
|
||||
*
|
||||
* @return Property maximum value.
|
||||
*/
|
||||
int GetMax() const;
|
||||
|
||||
/**
|
||||
* Returns property step size.
|
||||
*
|
||||
* @return Property step size.
|
||||
*/
|
||||
int GetStep() const;
|
||||
|
||||
/**
|
||||
* Returns property default value.
|
||||
*
|
||||
* @return Property default value.
|
||||
*/
|
||||
int GetDefault() const;
|
||||
|
||||
// String-specific functions
|
||||
/**
|
||||
* Returns the string property value.
|
||||
*
|
||||
* <p>This function is string-specific.
|
||||
*
|
||||
* @return The string property value.
|
||||
*/
|
||||
std::string GetString() const;
|
||||
|
||||
/**
|
||||
* Returns the string property value as a reference to the given buffer.
|
||||
*
|
||||
* This function is string-specific.
|
||||
*
|
||||
* @param buf The backing storage to which to write the property value.
|
||||
* @return The string property value as a reference to the given buffer.
|
||||
*/
|
||||
std::string_view GetString(wpi::SmallVectorImpl<char>& buf) const;
|
||||
|
||||
/**
|
||||
* Sets the string property value.
|
||||
*
|
||||
* This function is string-specific.
|
||||
*
|
||||
* @param value String property value.
|
||||
*/
|
||||
void SetString(std::string_view value);
|
||||
|
||||
// Enum-specific functions
|
||||
/**
|
||||
* Returns the possible values for the enum property value.
|
||||
*
|
||||
* This function is enum-specific.
|
||||
*
|
||||
* @return The possible values for the enum property value.
|
||||
*/
|
||||
std::vector<std::string> GetChoices() const;
|
||||
|
||||
/**
|
||||
* Returns the last status.
|
||||
*
|
||||
* @return The last status.
|
||||
*/
|
||||
CS_Status GetLastStatus() const { return m_status; }
|
||||
|
||||
private:
|
||||
@@ -99,10 +210,17 @@ class VideoSource {
|
||||
friend class VideoSink;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Video source kind.
|
||||
*/
|
||||
enum Kind {
|
||||
/// Unknown video source.
|
||||
kUnknown = CS_SOURCE_UNKNOWN,
|
||||
/// USB video source.
|
||||
kUsb = CS_SOURCE_USB,
|
||||
/// HTTP video source.
|
||||
kHttp = CS_SOURCE_HTTP,
|
||||
/// CV video source.
|
||||
kCv = CS_SOURCE_CV
|
||||
};
|
||||
|
||||
@@ -350,6 +468,8 @@ class VideoSource {
|
||||
explicit VideoSource(CS_Source handle) : m_handle(handle) {}
|
||||
|
||||
mutable CS_Status m_status = 0;
|
||||
|
||||
/// Video source handle.
|
||||
CS_Source m_handle{0};
|
||||
};
|
||||
|
||||
@@ -358,11 +478,19 @@ class VideoSource {
|
||||
*/
|
||||
class VideoCamera : public VideoSource {
|
||||
public:
|
||||
/**
|
||||
* White balance.
|
||||
*/
|
||||
enum WhiteBalance {
|
||||
/// Fixed indoor white balance.
|
||||
kFixedIndoor = 3000,
|
||||
/// Fixed outdoor white balance 1.
|
||||
kFixedOutdoor1 = 4000,
|
||||
/// Fixed outdoor white balance 2.
|
||||
kFixedOutdoor2 = 5000,
|
||||
/// Fixed fluorescent white balance 1.
|
||||
kFixedFluorescent1 = 5100,
|
||||
/// Fixed fluorescent white balance 2.
|
||||
kFixedFlourescent2 = 5200
|
||||
};
|
||||
|
||||
@@ -470,10 +598,17 @@ class UsbCamera : public VideoCamera {
|
||||
*/
|
||||
class HttpCamera : public VideoCamera {
|
||||
public:
|
||||
/**
|
||||
* HTTP camera kind.
|
||||
*/
|
||||
enum HttpCameraKind {
|
||||
/// Unknown camera kind.
|
||||
kUnknown = CS_HTTP_UNKNOWN,
|
||||
/// MJPG Streamer camera.
|
||||
kMJPGStreamer = CS_HTTP_MJPGSTREAMER,
|
||||
/// CS Core camera.
|
||||
kCSCore = CS_HTTP_CSCORE,
|
||||
/// Axis camera.
|
||||
kAxis = CS_HTTP_AXIS
|
||||
};
|
||||
|
||||
@@ -716,8 +851,11 @@ class VideoSink {
|
||||
|
||||
public:
|
||||
enum Kind {
|
||||
/// Unknown sink type.
|
||||
kUnknown = CS_SINK_UNKNOWN,
|
||||
/// MJPEG video sink.
|
||||
kMjpeg = CS_SINK_MJPEG,
|
||||
/// CV video sink.
|
||||
kCv = CS_SINK_CV
|
||||
};
|
||||
|
||||
@@ -727,8 +865,18 @@ class VideoSink {
|
||||
VideoSink& operator=(VideoSink other) noexcept;
|
||||
~VideoSink();
|
||||
|
||||
/**
|
||||
* Returns true if the VideoSink is valid.
|
||||
*
|
||||
* @return True if the VideoSink is valid.
|
||||
*/
|
||||
explicit operator bool() const { return m_handle != 0; }
|
||||
|
||||
/**
|
||||
* Returns the VideoSink handle.
|
||||
*
|
||||
* @return The VideoSink handle.
|
||||
*/
|
||||
int GetHandle() const { return m_handle; }
|
||||
|
||||
bool operator==(const VideoSink& other) const {
|
||||
@@ -972,17 +1120,23 @@ class ImageSink : public VideoSink {
|
||||
class VideoEvent : public RawEvent {
|
||||
public:
|
||||
/**
|
||||
* Get the source associated with the event (if any).
|
||||
* Returns the source associated with the event (if any).
|
||||
*
|
||||
* @return The source associated with the event (if any).
|
||||
*/
|
||||
VideoSource GetSource() const;
|
||||
|
||||
/**
|
||||
* Get the sink associated with the event (if any).
|
||||
* Returns the sink associated with the event (if any).
|
||||
*
|
||||
* @return The sink associated with the event (if any).
|
||||
*/
|
||||
VideoSink GetSink() const;
|
||||
|
||||
/**
|
||||
* Get the property associated with the event (if any).
|
||||
* Returns the property associated with the event (if any).
|
||||
*
|
||||
* @return The property associated with the event (if any).
|
||||
*/
|
||||
VideoProperty GetProperty() const;
|
||||
};
|
||||
|
||||
@@ -7,13 +7,14 @@ package edu.wpi.first.cscore;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
|
||||
import edu.wpi.first.util.PixelFormat;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class VideoModeTest {
|
||||
@Test
|
||||
void equalityTest() {
|
||||
VideoMode a = new VideoMode(VideoMode.PixelFormat.kMJPEG, 1920, 1080, 30);
|
||||
VideoMode b = new VideoMode(VideoMode.PixelFormat.kMJPEG, 1920, 1080, 30);
|
||||
VideoMode a = new VideoMode(PixelFormat.kMJPEG, 1920, 1080, 30);
|
||||
VideoMode b = new VideoMode(PixelFormat.kMJPEG, 1920, 1080, 30);
|
||||
|
||||
assertEquals(a, b);
|
||||
assertNotEquals(a, null);
|
||||
|
||||
@@ -29,51 +29,8 @@ model {
|
||||
def applicationPath = binary.executable.file
|
||||
def icon = file("$project.projectDir/src/main/native/mac/datalogtool.icns")
|
||||
|
||||
// Create the macOS bundle.
|
||||
def bundleTask = project.tasks.create("bundleDataLogToolOsxApp" + binary.targetPlatform.architecture.name, Copy) {
|
||||
description("Creates a macOS application bundle for DataLogTool")
|
||||
from(file("$project.projectDir/Info.plist"))
|
||||
into(file("$project.buildDir/outputs/bundles/$binary.targetPlatform.architecture.name/DataLogTool.app/Contents"))
|
||||
into("MacOS") {
|
||||
with copySpec {
|
||||
from binary.executable.file
|
||||
}
|
||||
}
|
||||
into("Resources") {
|
||||
with copySpec {
|
||||
from icon
|
||||
}
|
||||
}
|
||||
|
||||
inputs.property "HasDeveloperId", project.hasProperty("developerID")
|
||||
|
||||
doLast {
|
||||
if (project.hasProperty("developerID")) {
|
||||
// Get path to binary.
|
||||
exec {
|
||||
workingDir rootDir
|
||||
def args = [
|
||||
"sh",
|
||||
"-c",
|
||||
"codesign --force --strict --deep " +
|
||||
"--timestamp --options=runtime " +
|
||||
"--verbose -s ${project.findProperty("developerID")} " +
|
||||
"$project.buildDir/outputs/bundles/$binary.targetPlatform.architecture.name/DataLogTool.app/"
|
||||
]
|
||||
commandLine args
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the application path if we are creating a bundle.
|
||||
if (binary.targetPlatform.operatingSystem.isMacOsX()) {
|
||||
applicationPath = file("$project.buildDir/outputs/bundles/$binary.targetPlatform.architecture.name")
|
||||
project.build.dependsOn bundleTask
|
||||
}
|
||||
|
||||
// Create the ZIP.
|
||||
def task = project.tasks.create("copyDataLogToolExecutable" + binary.targetPlatform.architecture.name, Zip) {
|
||||
def task = project.tasks.create("copyDataLogToolExecutable" + binary.targetPlatform.operatingSystem.name + binary.targetPlatform.architecture.name, Zip) {
|
||||
description("Copies the DataLogTool executable to the outputs directory.")
|
||||
destinationDirectory = outputsFolder
|
||||
|
||||
@@ -85,13 +42,63 @@ model {
|
||||
into '/'
|
||||
}
|
||||
|
||||
from(applicationPath)
|
||||
if (binary.targetPlatform.operatingSystem.isWindows()) {
|
||||
def exePath = binary.executable.file.absolutePath
|
||||
exePath = exePath.substring(0, exePath.length() - 4)
|
||||
def pdbPath = new File(exePath + '.pdb')
|
||||
from(pdbPath)
|
||||
}
|
||||
|
||||
into(nativeUtils.getPlatformPath(binary))
|
||||
}
|
||||
|
||||
if (binary.targetPlatform.operatingSystem.isMacOsX()) {
|
||||
// Create the macOS bundle.
|
||||
def bundleTask = project.tasks.create("bundleDataLogToolOsxApp" + binary.targetPlatform.architecture.name, Copy) {
|
||||
description("Creates a macOS application bundle for DataLogTool")
|
||||
from(file("$project.projectDir/Info.plist"))
|
||||
into(file("$project.buildDir/outputs/bundles/$binary.targetPlatform.architecture.name/DataLogTool.app/Contents"))
|
||||
into("MacOS") {
|
||||
with copySpec {
|
||||
from binary.executable.file
|
||||
}
|
||||
}
|
||||
into("Resources") {
|
||||
with copySpec {
|
||||
from icon
|
||||
}
|
||||
}
|
||||
|
||||
inputs.property "HasDeveloperId", project.hasProperty("developerID")
|
||||
|
||||
doLast {
|
||||
if (project.hasProperty("developerID")) {
|
||||
// Get path to binary.
|
||||
exec {
|
||||
workingDir rootDir
|
||||
def args = [
|
||||
"sh",
|
||||
"-c",
|
||||
"codesign --force --strict --deep " +
|
||||
"--timestamp --options=runtime " +
|
||||
"--verbose -s ${project.findProperty("developerID")} " +
|
||||
"$project.buildDir/outputs/bundles/$binary.targetPlatform.architecture.name/DataLogTool.app/"
|
||||
]
|
||||
commandLine args
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the application path if we are creating a bundle.
|
||||
applicationPath = file("$project.buildDir/outputs/bundles/$binary.targetPlatform.architecture.name")
|
||||
task.from(applicationPath)
|
||||
project.build.dependsOn bundleTask
|
||||
|
||||
bundleTask.dependsOn binary.tasks.link
|
||||
task.dependsOn(bundleTask)
|
||||
} else {
|
||||
task.from(applicationPath)
|
||||
}
|
||||
|
||||
task.dependsOn binary.tasks.link
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/**
|
||||
* Autogenerated file! Do not manually edit this file. This version is regenerated
|
||||
* any time the publish task is run, or when this file is deleted.
|
||||
*/
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "DataLogThread.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
DataLogThread::~DataLogThread() {
|
||||
if (m_thread.joinable()) {
|
||||
m_active = false;
|
||||
m_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void DataLogThread::ReadMain() {
|
||||
for (auto record : m_reader) {
|
||||
if (!m_active) {
|
||||
break;
|
||||
}
|
||||
++m_numRecords;
|
||||
if (record.IsStart()) {
|
||||
wpi::log::StartRecordData data;
|
||||
if (record.GetStartData(&data)) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_entries.find(data.entry) != m_entries.end()) {
|
||||
fmt::print("...DUPLICATE entry ID, overriding\n");
|
||||
}
|
||||
m_entries[data.entry] = data;
|
||||
m_entryNames.emplace(data.name, data);
|
||||
sigEntryAdded(data);
|
||||
} else {
|
||||
fmt::print("Start(INVALID)\n");
|
||||
}
|
||||
} else if (record.IsFinish()) {
|
||||
int entry;
|
||||
if (record.GetFinishEntry(&entry)) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
auto it = m_entries.find(entry);
|
||||
if (it == m_entries.end()) {
|
||||
fmt::print("...ID not found\n");
|
||||
} else {
|
||||
m_entries.erase(it);
|
||||
}
|
||||
} else {
|
||||
fmt::print("Finish(INVALID)\n");
|
||||
}
|
||||
} else if (record.IsSetMetadata()) {
|
||||
wpi::log::MetadataRecordData data;
|
||||
if (record.GetSetMetadataData(&data)) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
auto it = m_entries.find(data.entry);
|
||||
if (it == m_entries.end()) {
|
||||
fmt::print("...ID not found\n");
|
||||
} else {
|
||||
it->second.metadata = data.metadata;
|
||||
auto nameIt = m_entryNames.find(it->second.name);
|
||||
if (nameIt != m_entryNames.end()) {
|
||||
nameIt->second.metadata = data.metadata;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt::print("SetMetadata(INVALID)\n");
|
||||
}
|
||||
} else if (record.IsControl()) {
|
||||
fmt::print("Unrecognized control record\n");
|
||||
}
|
||||
}
|
||||
|
||||
sigDone();
|
||||
m_done = true;
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include <wpi/DataLogReader.h>
|
||||
#include <wpi/DenseMap.h>
|
||||
#include <wpi/Signal.h>
|
||||
#include <wpi/mutex.h>
|
||||
|
||||
class DataLogThread {
|
||||
public:
|
||||
explicit DataLogThread(wpi::log::DataLogReader reader)
|
||||
: m_reader{std::move(reader)}, m_thread{[=, this] { ReadMain(); }} {}
|
||||
~DataLogThread();
|
||||
|
||||
bool IsDone() const { return m_done; }
|
||||
std::string_view GetBufferIdentifier() const {
|
||||
return m_reader.GetBufferIdentifier();
|
||||
}
|
||||
unsigned int GetNumRecords() const { return m_numRecords; }
|
||||
unsigned int GetNumEntries() const {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return m_entryNames.size();
|
||||
}
|
||||
|
||||
// Passes wpi::log::StartRecordData to func
|
||||
template <typename T>
|
||||
void ForEachEntryName(T&& func) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
for (auto&& kv : m_entryNames) {
|
||||
func(kv.second);
|
||||
}
|
||||
}
|
||||
|
||||
wpi::log::StartRecordData GetEntry(std::string_view name) const {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
auto it = m_entryNames.find(name);
|
||||
if (it == m_entryNames.end()) {
|
||||
return {};
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
const wpi::log::DataLogReader& GetReader() const { return m_reader; }
|
||||
|
||||
// note: these are called on separate thread
|
||||
wpi::sig::Signal_mt<const wpi::log::StartRecordData&> sigEntryAdded;
|
||||
wpi::sig::Signal_mt<> sigDone;
|
||||
|
||||
private:
|
||||
void ReadMain();
|
||||
|
||||
wpi::log::DataLogReader m_reader;
|
||||
mutable wpi::mutex m_mutex;
|
||||
std::atomic_bool m_active{true};
|
||||
std::atomic_bool m_done{false};
|
||||
std::atomic<unsigned int> m_numRecords{0};
|
||||
std::map<std::string, wpi::log::StartRecordData, std::less<>> m_entryNames;
|
||||
wpi::DenseMap<int, wpi::log::StartRecordData> m_entries;
|
||||
std::thread m_thread;
|
||||
};
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
Downloader::Downloader(glass::Storage& storage)
|
||||
: m_serverTeam{storage.GetString("serverTeam")},
|
||||
m_remoteDir{storage.GetString("remoteDir", "/home/lvuser")},
|
||||
m_remoteDir{storage.GetString("remoteDir", "/home/lvuser/logs")},
|
||||
m_username{storage.GetString("username", "lvuser")},
|
||||
m_localDir{storage.GetString("localDir")},
|
||||
m_deleteAfter{storage.GetBool("deleteAfter", true)},
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <fmt/chrono.h>
|
||||
#include <fmt/format.h>
|
||||
#include <glass/Storage.h>
|
||||
#include <glass/support/DataLogReaderThread.h>
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <imgui_stdlib.h>
|
||||
@@ -32,11 +33,10 @@
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
#include "App.h"
|
||||
#include "DataLogThread.h"
|
||||
|
||||
namespace {
|
||||
struct InputFile {
|
||||
explicit InputFile(std::unique_ptr<DataLogThread> datalog);
|
||||
explicit InputFile(std::unique_ptr<glass::DataLogReaderThread> datalog);
|
||||
|
||||
InputFile(std::string_view filename, std::string_view status)
|
||||
: filename{filename},
|
||||
@@ -47,7 +47,7 @@ struct InputFile {
|
||||
|
||||
std::string filename;
|
||||
std::string stem;
|
||||
std::unique_ptr<DataLogThread> datalog;
|
||||
std::unique_ptr<glass::DataLogReaderThread> datalog;
|
||||
std::string status;
|
||||
bool highlight = false;
|
||||
};
|
||||
@@ -135,7 +135,7 @@ static void RebuildEntryTree() {
|
||||
}
|
||||
}
|
||||
|
||||
InputFile::InputFile(std::unique_ptr<DataLogThread> datalog_)
|
||||
InputFile::InputFile(std::unique_ptr<glass::DataLogReaderThread> datalog_)
|
||||
: filename{datalog_->GetBufferIdentifier()},
|
||||
stem{fs::path{filename}.stem().string()},
|
||||
datalog{std::move(datalog_)} {
|
||||
@@ -192,7 +192,7 @@ static std::unique_ptr<InputFile> LoadDataLog(std::string_view filename) {
|
||||
}
|
||||
|
||||
return std::make_unique<InputFile>(
|
||||
std::make_unique<DataLogThread>(std::move(reader)));
|
||||
std::make_unique<glass::DataLogReaderThread>(std::move(reader)));
|
||||
}
|
||||
|
||||
void DisplayInputFiles() {
|
||||
@@ -284,9 +284,10 @@ static bool EmitEntry(const std::string& name, Entry& entry) {
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
for (auto inputFile : entry.inputFiles) {
|
||||
ImGui::Text(
|
||||
"%s: %s", inputFile->stem.c_str(),
|
||||
std::string{inputFile->datalog->GetEntry(entry.name).type}.c_str());
|
||||
if (auto info = inputFile->datalog->GetEntry(entry.name)) {
|
||||
ImGui::Text("%s: %s", inputFile->stem.c_str(),
|
||||
std::string{info->type}.c_str());
|
||||
}
|
||||
}
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
@@ -300,10 +301,10 @@ static bool EmitEntry(const std::string& name, Entry& entry) {
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
for (auto inputFile : entry.inputFiles) {
|
||||
ImGui::Text(
|
||||
"%s: %s", inputFile->stem.c_str(),
|
||||
std::string{inputFile->datalog->GetEntry(entry.name).metadata}
|
||||
.c_str());
|
||||
if (auto info = inputFile->datalog->GetEntry(entry.name)) {
|
||||
ImGui::Text("%s: %s", inputFile->stem.c_str(),
|
||||
std::string{info->metadata}.c_str());
|
||||
}
|
||||
}
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <string_view>
|
||||
|
||||
void Application(std::string_view saveDir);
|
||||
|
||||
#ifdef _WIN32
|
||||
int __stdcall WinMain(void* hInstance, void* hPrevInstance, char* pCmdLine,
|
||||
int nCmdShow) {
|
||||
int argc = __argc;
|
||||
char** argv = __argv;
|
||||
#else
|
||||
int main(int argc, char** argv) {
|
||||
#endif
|
||||
std::string_view saveDir;
|
||||
if (argc == 2) {
|
||||
saveDir = argv[1];
|
||||
}
|
||||
|
||||
Application(saveDir);
|
||||
|
||||
return 0;
|
||||
}
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <string_view>
|
||||
|
||||
void Application(std::string_view saveDir);
|
||||
|
||||
#ifdef _WIN32
|
||||
int __stdcall WinMain(void* hInstance, void* hPrevInstance, char* pCmdLine,
|
||||
int nCmdShow) {
|
||||
int argc = __argc;
|
||||
char** argv = __argv;
|
||||
#else
|
||||
int main(int argc, char** argv) {
|
||||
#endif
|
||||
std::string_view saveDir;
|
||||
if (argc == 2) {
|
||||
saveDir = argv[1];
|
||||
}
|
||||
|
||||
Application(saveDir);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -208,21 +208,27 @@ configurations {
|
||||
}
|
||||
}
|
||||
|
||||
ext {
|
||||
sharedCvConfigs = [:]
|
||||
staticCvConfigs = [:]
|
||||
useJava = true
|
||||
useCpp = false
|
||||
skipDev = true
|
||||
useDocumentation = true
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/shared/opencv.gradle"
|
||||
|
||||
task generateJavaDocs(type: Javadoc) {
|
||||
classpath += project(":wpimath").sourceSets.main.compileClasspath
|
||||
classpath += project(":wpilibj").sourceSets.main.compileClasspath
|
||||
options.links("https://docs.oracle.com/en/java/javase/17/docs/api/")
|
||||
options.links("https://docs.opencv.org/4.x/javadoc/")
|
||||
options.addStringOption("tag", "pre:a:Pre-Condition")
|
||||
options.addBooleanOption("Xdoclint/package:" +
|
||||
// TODO: v Document these, then remove them from the list
|
||||
"-edu.wpi.first.hal," +
|
||||
"-edu.wpi.first.hal.can," +
|
||||
"-edu.wpi.first.hal.simulation," +
|
||||
// TODO: ^ Document these, then remove them from the list
|
||||
"-edu.wpi.first.math.proto," +
|
||||
"-edu.wpi.first.math.controller.proto," +
|
||||
"-edu.wpi.first.math.controller.struct," +
|
||||
"-edu.wpi.first.math.geometry.proto," +
|
||||
"-edu.wpi.first.math.geometry.struct," +
|
||||
"-edu.wpi.first.math.kinematics.proto," +
|
||||
"-edu.wpi.first.math.kinematics.struct," +
|
||||
"-edu.wpi.first.math.system.plant.proto," +
|
||||
"-edu.wpi.first.math.system.plant.struct," +
|
||||
"-edu.wpi.first.math.trajectory.proto", true)
|
||||
options.addBooleanOption("Xdoclint:html,missing,reference,syntax", true)
|
||||
options.addBooleanOption('html5', true)
|
||||
options.linkSource(true)
|
||||
|
||||
@@ -40,12 +40,10 @@ public class FieldConfig {
|
||||
|
||||
public FieldConfig() {}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public URL getImageUrl() {
|
||||
return getClass().getResource(Fields.kBaseResourceDir + m_fieldImage);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public InputStream getImageAsStream() {
|
||||
return getClass().getResourceAsStream(Fields.kBaseResourceDir + m_fieldImage);
|
||||
}
|
||||
|
||||
@@ -15,12 +15,13 @@ public enum Fields {
|
||||
k2021GalacticSearchB("2021-galacticsearchb.json"),
|
||||
k2021Slalom("2021-slalompath.json"),
|
||||
k2022RapidReact("2022-rapidreact.json"),
|
||||
k2023ChargedUp("2023-chargedup.json");
|
||||
k2023ChargedUp("2023-chargedup.json"),
|
||||
k2024Crescendo("2024-crescendo.json");
|
||||
|
||||
public static final String kBaseResourceDir = "/edu/wpi/first/fields/";
|
||||
|
||||
/** Alias to the current game. */
|
||||
public static final Fields kDefaultField = k2023ChargedUp;
|
||||
public static final Fields kDefaultField = k2024Crescendo;
|
||||
|
||||
public final String m_resourceFile;
|
||||
|
||||
|
||||
@@ -15,10 +15,13 @@
|
||||
#include "fields/2021-slalom.h"
|
||||
#include "fields/2022-rapidreact.h"
|
||||
#include "fields/2023-chargedup.h"
|
||||
#include "fields/2024-crescendo.h"
|
||||
|
||||
using namespace fields;
|
||||
|
||||
static const Field kFields[] = {
|
||||
{"2024 Crescendo", GetResource_2024_crescendo_json,
|
||||
GetResource_2024_field_png},
|
||||
{"2023 Charged Up", GetResource_2023_chargedup_json,
|
||||
GetResource_2023_field_png},
|
||||
{"2022 Rapid React", GetResource_2022_rapidreact_json,
|
||||
|
||||
12
fieldImages/src/main/native/include/fields/2024-crescendo.h
Normal file
12
fieldImages/src/main/native/include/fields/2024-crescendo.h
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace fields {
|
||||
std::string_view GetResource_2024_crescendo_json();
|
||||
std::string_view GetResource_2024_field_png();
|
||||
} // namespace fields
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"game": "Crescendo",
|
||||
"field-image": "2024-field.png",
|
||||
"field-corners": {
|
||||
"top-left": [
|
||||
150,
|
||||
79
|
||||
],
|
||||
"bottom-right": [
|
||||
2961,
|
||||
1476
|
||||
]
|
||||
},
|
||||
"field-size": [
|
||||
54.27083,
|
||||
26.9375
|
||||
],
|
||||
"field-unit": "foot"
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.2 MiB |
@@ -97,8 +97,6 @@ model {
|
||||
into '/'
|
||||
}
|
||||
|
||||
from(applicationPath)
|
||||
|
||||
if (binary.targetPlatform.operatingSystem.isWindows()) {
|
||||
def exePath = binary.executable.file.absolutePath
|
||||
exePath = exePath.substring(0, exePath.length() - 4)
|
||||
@@ -149,10 +147,13 @@ model {
|
||||
|
||||
// Reset the application path if we are creating a bundle.
|
||||
applicationPath = file("$project.buildDir/outputs/bundles/$binary.targetPlatform.architecture.name")
|
||||
task.from(applicationPath)
|
||||
project.build.dependsOn bundleTask
|
||||
|
||||
bundleTask.dependsOn binary.tasks.link
|
||||
task.dependsOn(bundleTask)
|
||||
} else {
|
||||
task.from(applicationPath)
|
||||
}
|
||||
|
||||
task.dependsOn binary.tasks.link
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/**
|
||||
* Autogenerated file! Do not manually edit this file. This version is regenerated
|
||||
* any time the publish task is run, or when this file is deleted.
|
||||
*/
|
||||
|
||||
@@ -96,7 +96,7 @@ static void NtInitialize() {
|
||||
auto inst = nt::GetDefaultInstance();
|
||||
auto poller = nt::CreateListenerPoller(inst);
|
||||
nt::AddPolledListener(poller, inst, NT_EVENT_CONNECTION | NT_EVENT_IMMEDIATE);
|
||||
nt::AddPolledLogger(poller, 0, 100);
|
||||
nt::AddPolledLogger(poller, NT_LOG_INFO, 100);
|
||||
gui::AddEarlyExecute([inst, poller] {
|
||||
auto win = gui::GetSystemWindow();
|
||||
if (!win) {
|
||||
|
||||
@@ -178,8 +178,6 @@ static bool LoadStorageRootImpl(Context* ctx, const std::string& filename,
|
||||
|
||||
static bool LoadStorageImpl(Context* ctx, std::string_view dir,
|
||||
std::string_view name) {
|
||||
WorkspaceResetImpl();
|
||||
|
||||
bool rv = true;
|
||||
for (auto&& root : ctx->storageRoots) {
|
||||
std::string filename;
|
||||
@@ -421,6 +419,7 @@ std::string glass::GetStorageDir() {
|
||||
bool glass::LoadStorage(std::string_view dir) {
|
||||
SaveStorage();
|
||||
SetStorageDir(dir);
|
||||
WorkspaceResetImpl();
|
||||
LoadWindowStorageImpl((fs::path{gContext->storageLoadDir} /
|
||||
fmt::format("{}-window.json", gContext->storageName))
|
||||
.string());
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "glass/other/FMS.h"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_stdlib.h>
|
||||
#include <wpi/SmallString.h>
|
||||
|
||||
#include "glass/DataSource.h"
|
||||
@@ -14,7 +15,7 @@ using namespace glass;
|
||||
static const char* stations[] = {"Invalid", "Red 1", "Red 2", "Red 3",
|
||||
"Blue 1", "Blue 2", "Blue 3"};
|
||||
|
||||
void glass::DisplayFMS(FMSModel* model) {
|
||||
void glass::DisplayFMS(FMSModel* model, bool editableDsAttached) {
|
||||
if (!model->Exists() || model->IsReadOnly()) {
|
||||
return DisplayFMSReadOnly(model);
|
||||
}
|
||||
@@ -31,10 +32,17 @@ void glass::DisplayFMS(FMSModel* model) {
|
||||
// DS Attached
|
||||
if (auto data = model->GetDsAttachedData()) {
|
||||
bool val = data->GetValue();
|
||||
if (ImGui::Checkbox("DS Attached", &val)) {
|
||||
model->SetDsAttached(val);
|
||||
if (editableDsAttached) {
|
||||
if (ImGui::Checkbox("DS Attached", &val)) {
|
||||
model->SetDsAttached(val);
|
||||
}
|
||||
data->EmitDrag();
|
||||
} else {
|
||||
ImGui::Selectable("DS Attached: ");
|
||||
data->EmitDrag();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted(val ? "Yes" : "No");
|
||||
}
|
||||
data->EmitDrag();
|
||||
}
|
||||
|
||||
// Alliance Station
|
||||
@@ -51,8 +59,7 @@ void glass::DisplayFMS(FMSModel* model) {
|
||||
if (auto data = model->GetMatchTimeData()) {
|
||||
double val = data->GetValue();
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
|
||||
if (ImGui::InputDouble("Match Time", &val, 0, 0, "%.1f",
|
||||
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||
if (ImGui::InputDouble("Match Time", &val, 0, 0, "%.1f")) {
|
||||
model->SetMatchTime(val);
|
||||
}
|
||||
data->EmitDrag();
|
||||
@@ -71,16 +78,12 @@ void glass::DisplayFMS(FMSModel* model) {
|
||||
}
|
||||
|
||||
// Game Specific Message
|
||||
// make buffer full 64 width, null terminated, for editability
|
||||
wpi::SmallString<64> gameSpecificMessage;
|
||||
model->GetGameSpecificMessage(gameSpecificMessage);
|
||||
gameSpecificMessage.resize(63);
|
||||
gameSpecificMessage.push_back('\0');
|
||||
wpi::SmallString<64> gameSpecificMessageBuf;
|
||||
std::string gameSpecificMessage{
|
||||
model->GetGameSpecificMessage(gameSpecificMessageBuf)};
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
|
||||
if (ImGui::InputText("Game Specific", gameSpecificMessage.data(),
|
||||
gameSpecificMessage.size(),
|
||||
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||
model->SetGameSpecificMessage(gameSpecificMessage.data());
|
||||
if (ImGui::InputText("Game Specific", &gameSpecificMessage)) {
|
||||
model->SetGameSpecificMessage(gameSpecificMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,9 +147,10 @@ void glass::DisplayFMSReadOnly(FMSModel* model) {
|
||||
}
|
||||
}
|
||||
|
||||
wpi::SmallString<64> gameSpecificMessage;
|
||||
model->GetGameSpecificMessage(gameSpecificMessage);
|
||||
ImGui::Text("Game Specific: %s", exists ? gameSpecificMessage.c_str() : "?");
|
||||
wpi::SmallString<64> gameSpecificMessageBuf;
|
||||
std::string_view gameSpecificMessage =
|
||||
model->GetGameSpecificMessage(gameSpecificMessageBuf);
|
||||
ImGui::Text("Game Specific: %s", exists ? gameSpecificMessage.data() : "?");
|
||||
|
||||
if (!exists) {
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
@@ -222,8 +222,8 @@ class ObjectInfo {
|
||||
|
||||
class FieldInfo {
|
||||
public:
|
||||
static constexpr auto kDefaultWidth = 15.98_m;
|
||||
static constexpr auto kDefaultHeight = 8.21_m;
|
||||
static constexpr auto kDefaultWidth = 16.541052_m;
|
||||
static constexpr auto kDefaultHeight = 8.211_m;
|
||||
|
||||
explicit FieldInfo(Storage& storage);
|
||||
|
||||
@@ -343,7 +343,7 @@ static bool InputPose(frc::Pose2d* pose) {
|
||||
}
|
||||
|
||||
FieldInfo::FieldInfo(Storage& storage)
|
||||
: m_builtin{storage.GetString("builtin")},
|
||||
: m_builtin{storage.GetString("builtin", "2024 Crescendo")},
|
||||
m_filename{storage.GetString("image")},
|
||||
m_width{storage.GetFloat("width", kDefaultWidth.to<float>())},
|
||||
m_height{storage.GetFloat("height", kDefaultHeight.to<float>())},
|
||||
@@ -508,6 +508,16 @@ bool FieldInfo::LoadJson(std::span<const char> is, std::string_view filename) {
|
||||
height = units::convert<units::feet, units::meters>(height);
|
||||
}
|
||||
|
||||
// check scaling
|
||||
int fieldWidth = m_right - m_left;
|
||||
int fieldHeight = m_bottom - m_top;
|
||||
if (std::abs((fieldWidth / width) - (fieldHeight / height)) > 0.3) {
|
||||
fmt::print(stderr,
|
||||
"GUI: Field X and Y scaling substantially different: "
|
||||
"xscale={} yscale={}\n",
|
||||
(fieldWidth / width), (fieldHeight / height));
|
||||
}
|
||||
|
||||
if (!filename.empty()) {
|
||||
// the image filename is relative to the json file
|
||||
auto pathname = fs::path{filename}.replace_filename(image).string();
|
||||
@@ -560,23 +570,29 @@ FieldFrameData FieldInfo::GetFrameData(ImVec2 min, ImVec2 max) const {
|
||||
// fit the image into the window
|
||||
if (m_texture && m_imageHeight != 0 && m_imageWidth != 0) {
|
||||
gui::MaxFit(&min, &max, m_imageWidth, m_imageHeight);
|
||||
} else {
|
||||
gui::MaxFit(&min, &max, m_width, m_height);
|
||||
}
|
||||
|
||||
FieldFrameData ffd;
|
||||
ffd.imageMin = min;
|
||||
ffd.imageMax = max;
|
||||
|
||||
// size down the box by the image corners (if any)
|
||||
if (m_bottom > 0 && m_right > 0) {
|
||||
min.x += m_left * (max.x - min.x) / m_imageWidth;
|
||||
min.y += m_top * (max.y - min.y) / m_imageHeight;
|
||||
max.x -= (m_imageWidth - m_right) * (max.x - min.x) / m_imageWidth;
|
||||
max.y -= (m_imageHeight - m_bottom) * (max.y - min.y) / m_imageHeight;
|
||||
if (m_bottom > 0 && m_right > 0 && m_imageWidth != 0) {
|
||||
// size down the box by the image corners
|
||||
float scale = (max.x - min.x) / m_imageWidth;
|
||||
min.x += m_left * scale;
|
||||
min.y += m_top * scale;
|
||||
max.x -= (m_imageWidth - m_right) * scale;
|
||||
max.y -= (m_imageHeight - m_bottom) * scale;
|
||||
} else if ((max.x - min.x) > 40 && (max.y - min.y > 40)) {
|
||||
// ensure there's some padding
|
||||
min.x += 20;
|
||||
max.x -= 20;
|
||||
min.y += 20;
|
||||
max.y -= 20;
|
||||
}
|
||||
|
||||
// draw the field "active area" as a yellow boundary box
|
||||
gui::MaxFit(&min, &max, m_width, m_height);
|
||||
|
||||
ffd.min = min;
|
||||
ffd.max = max;
|
||||
ffd.scale = (max.x - min.x) / m_width;
|
||||
|
||||
@@ -29,6 +29,16 @@ void glass::DisplayPIDController(PIDControllerModel* m) {
|
||||
callback(*v);
|
||||
}
|
||||
};
|
||||
// Workaround to allow for the input of inf, -inf, and nan
|
||||
auto createTuningParameterNoFilter =
|
||||
[flag](const char* name, double* v,
|
||||
std::function<void(double)> callback) {
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
|
||||
if (ImGui::InputScalar(name, ImGuiDataType_Double, v, NULL, NULL,
|
||||
"%.3f", flag)) {
|
||||
callback(*v);
|
||||
}
|
||||
};
|
||||
|
||||
if (auto p = m->GetPData()) {
|
||||
double value = p->GetValue();
|
||||
@@ -47,6 +57,11 @@ void glass::DisplayPIDController(PIDControllerModel* m) {
|
||||
createTuningParameter("Setpoint", &value,
|
||||
[=](auto v) { m->SetSetpoint(v); });
|
||||
}
|
||||
if (auto s = m->GetIZoneData()) {
|
||||
double value = s->GetValue();
|
||||
createTuningParameterNoFilter("IZone", &value,
|
||||
[=](auto v) { m->SetIZone(v); });
|
||||
}
|
||||
} else {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
|
||||
ImGui::Text("Unknown PID Controller");
|
||||
|
||||
69
glass/src/lib/native/cpp/other/ProfiledPIDController.cpp
Normal file
69
glass/src/lib/native/cpp/other/ProfiledPIDController.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "glass/other/ProfiledPIDController.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
void glass::DisplayProfiledPIDController(ProfiledPIDControllerModel* m) {
|
||||
if (auto name = m->GetName()) {
|
||||
ImGui::Text("%s", name);
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
if (m->Exists()) {
|
||||
auto flag = m->IsReadOnly() ? ImGuiInputTextFlags_ReadOnly
|
||||
: ImGuiInputTextFlags_None;
|
||||
auto createTuningParameter = [flag](const char* name, double* v,
|
||||
std::function<void(double)> callback) {
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
|
||||
if (ImGui::InputDouble(name, v, 0.0, 0.0, "%.3f", flag)) {
|
||||
callback(*v);
|
||||
}
|
||||
};
|
||||
// Workaround to allow for the input of inf, -inf, and nan
|
||||
auto createTuningParameterNoFilter =
|
||||
[flag](const char* name, double* v,
|
||||
std::function<void(double)> callback) {
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
|
||||
if (ImGui::InputScalar(name, ImGuiDataType_Double, v, NULL, NULL,
|
||||
"%.3f", flag)) {
|
||||
callback(*v);
|
||||
}
|
||||
};
|
||||
|
||||
if (auto p = m->GetPData()) {
|
||||
double value = p->GetValue();
|
||||
createTuningParameter("P", &value, [=](auto v) { m->SetP(v); });
|
||||
}
|
||||
if (auto i = m->GetIData()) {
|
||||
double value = i->GetValue();
|
||||
createTuningParameter("I", &value, [=](auto v) { m->SetI(v); });
|
||||
}
|
||||
if (auto d = m->GetDData()) {
|
||||
double value = d->GetValue();
|
||||
createTuningParameter("D", &value, [=](auto v) { m->SetD(v); });
|
||||
}
|
||||
if (auto s = m->GetGoalData()) {
|
||||
double value = s->GetValue();
|
||||
createTuningParameter("Goal", &value, [=](auto v) { m->SetGoal(v); });
|
||||
}
|
||||
if (auto s = m->GetIZoneData()) {
|
||||
double value = s->GetValue();
|
||||
createTuningParameterNoFilter("IZone", &value,
|
||||
[=](auto v) { m->SetIZone(v); });
|
||||
}
|
||||
} else {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
|
||||
ImGui::Text("Unknown PID Controller");
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
}
|
||||
124
glass/src/lib/native/cpp/support/DataLogReaderThread.cpp
Normal file
124
glass/src/lib/native/cpp/support/DataLogReaderThread.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "glass/support/DataLogReaderThread.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
|
||||
using namespace glass;
|
||||
|
||||
DataLogReaderThread::~DataLogReaderThread() {
|
||||
if (m_thread.joinable()) {
|
||||
m_active = false;
|
||||
m_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void DataLogReaderThread::ReadMain() {
|
||||
wpi::SmallDenseMap<
|
||||
int, std::pair<DataLogReaderEntry*, std::span<const uint8_t>>, 8>
|
||||
schemaEntries;
|
||||
|
||||
for (auto recordIt = m_reader.begin(), recordEnd = m_reader.end();
|
||||
recordIt != recordEnd; ++recordIt) {
|
||||
auto& record = *recordIt;
|
||||
if (!m_active) {
|
||||
break;
|
||||
}
|
||||
++m_numRecords;
|
||||
if (record.IsStart()) {
|
||||
DataLogReaderEntry data;
|
||||
if (record.GetStartData(&data)) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
auto& entryPtr = m_entriesById[data.entry];
|
||||
if (entryPtr) {
|
||||
fmt::print("...DUPLICATE entry ID, overriding\n");
|
||||
}
|
||||
auto [it, isNew] = m_entriesByName.emplace(data.name, data);
|
||||
if (isNew) {
|
||||
it->second.ranges.emplace_back(recordIt, recordEnd);
|
||||
}
|
||||
entryPtr = &it->second;
|
||||
if (data.type == "structschema" ||
|
||||
data.type == "proto:FileDescriptorProto") {
|
||||
schemaEntries.try_emplace(data.entry, entryPtr,
|
||||
std::span<const uint8_t>{});
|
||||
}
|
||||
sigEntryAdded(data);
|
||||
} else {
|
||||
fmt::print("Start(INVALID)\n");
|
||||
}
|
||||
} else if (record.IsFinish()) {
|
||||
int entry;
|
||||
if (record.GetFinishEntry(&entry)) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
auto it = m_entriesById.find(entry);
|
||||
if (it == m_entriesById.end()) {
|
||||
fmt::print("...ID not found\n");
|
||||
} else {
|
||||
it->second->ranges.back().m_end = recordIt;
|
||||
m_entriesById.erase(it);
|
||||
}
|
||||
} else {
|
||||
fmt::print("Finish(INVALID)\n");
|
||||
}
|
||||
} else if (record.IsSetMetadata()) {
|
||||
wpi::log::MetadataRecordData data;
|
||||
if (record.GetSetMetadataData(&data)) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
auto it = m_entriesById.find(data.entry);
|
||||
if (it == m_entriesById.end()) {
|
||||
fmt::print("...ID not found\n");
|
||||
} else {
|
||||
it->second->metadata = data.metadata;
|
||||
}
|
||||
} else {
|
||||
fmt::print("SetMetadata(INVALID)\n");
|
||||
}
|
||||
} else if (record.IsControl()) {
|
||||
fmt::print("Unrecognized control record\n");
|
||||
} else {
|
||||
auto it = schemaEntries.find(record.GetEntry());
|
||||
if (it != schemaEntries.end()) {
|
||||
it->second.second = record.GetRaw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// build schema databases
|
||||
for (auto&& schemaPair : schemaEntries) {
|
||||
auto name = schemaPair.second.first->name;
|
||||
auto data = schemaPair.second.second;
|
||||
if (data.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (wpi::starts_with(name, "NT:")) {
|
||||
name = wpi::drop_front(name, 3);
|
||||
}
|
||||
if (wpi::starts_with(name, "/.schema/struct:")) {
|
||||
auto typeStr = wpi::drop_front(name, 16);
|
||||
std::string_view schema{reinterpret_cast<const char*>(data.data()),
|
||||
data.size()};
|
||||
std::string err;
|
||||
auto desc = m_structDb.Add(typeStr, schema, &err);
|
||||
if (!desc) {
|
||||
fmt::print("could not decode struct '{}' schema '{}': {}\n", name,
|
||||
schema, err);
|
||||
}
|
||||
} else if (wpi::starts_with(name, "/.schema/proto:")) {
|
||||
// protobuf descriptor handling
|
||||
auto filename = wpi::drop_front(name, 15);
|
||||
if (!m_protoDb.Add(filename, data)) {
|
||||
fmt::print("could not decode protobuf '{}' filename '{}'\n", name,
|
||||
filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sigDone();
|
||||
m_done = true;
|
||||
}
|
||||
@@ -46,8 +46,9 @@ class FMSModel : public Model {
|
||||
*
|
||||
* @param matchTimeEnabled If not null, a checkbox is displayed for
|
||||
* "enable match time" linked to this value
|
||||
* @param editableDsAttached If true, DS attached should be editable
|
||||
*/
|
||||
void DisplayFMS(FMSModel* model);
|
||||
void DisplayFMS(FMSModel* model, bool editableDsAttached);
|
||||
void DisplayFMSReadOnly(FMSModel* model);
|
||||
|
||||
} // namespace glass
|
||||
|
||||
@@ -16,11 +16,13 @@ class PIDControllerModel : public Model {
|
||||
virtual DataSource* GetIData() = 0;
|
||||
virtual DataSource* GetDData() = 0;
|
||||
virtual DataSource* GetSetpointData() = 0;
|
||||
virtual DataSource* GetIZoneData() = 0;
|
||||
|
||||
virtual void SetP(double value) = 0;
|
||||
virtual void SetI(double value) = 0;
|
||||
virtual void SetD(double value) = 0;
|
||||
virtual void SetSetpoint(double value) = 0;
|
||||
virtual void SetIZone(double value) = 0;
|
||||
};
|
||||
void DisplayPIDController(PIDControllerModel* m);
|
||||
} // namespace glass
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "glass/Model.h"
|
||||
|
||||
namespace glass {
|
||||
class DataSource;
|
||||
class ProfiledPIDControllerModel : public Model {
|
||||
public:
|
||||
virtual const char* GetName() const = 0;
|
||||
|
||||
virtual DataSource* GetPData() = 0;
|
||||
virtual DataSource* GetIData() = 0;
|
||||
virtual DataSource* GetDData() = 0;
|
||||
virtual DataSource* GetGoalData() = 0;
|
||||
virtual DataSource* GetIZoneData() = 0;
|
||||
|
||||
virtual void SetP(double value) = 0;
|
||||
virtual void SetI(double value) = 0;
|
||||
virtual void SetD(double value) = 0;
|
||||
virtual void SetGoal(double value) = 0;
|
||||
virtual void SetIZone(double value) = 0;
|
||||
};
|
||||
void DisplayProfiledPIDController(ProfiledPIDControllerModel* m);
|
||||
} // namespace glass
|
||||
101
glass/src/lib/native/include/glass/support/DataLogReaderThread.h
Normal file
101
glass/src/lib/native/include/glass/support/DataLogReaderThread.h
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/DataLogReader.h>
|
||||
#include <wpi/DenseMap.h>
|
||||
#include <wpi/Signal.h>
|
||||
#include <wpi/mutex.h>
|
||||
#include <wpi/protobuf/ProtobufMessageDatabase.h>
|
||||
#include <wpi/struct/DynamicStruct.h>
|
||||
|
||||
namespace glass {
|
||||
|
||||
class DataLogReaderRange {
|
||||
public:
|
||||
DataLogReaderRange(wpi::log::DataLogReader::iterator begin,
|
||||
wpi::log::DataLogReader::iterator end)
|
||||
: m_begin{begin}, m_end{end} {}
|
||||
|
||||
wpi::log::DataLogReader::iterator begin() const { return m_begin; }
|
||||
wpi::log::DataLogReader::iterator end() const { return m_end; }
|
||||
|
||||
wpi::log::DataLogReader::iterator m_begin;
|
||||
wpi::log::DataLogReader::iterator m_end;
|
||||
};
|
||||
|
||||
class DataLogReaderEntry : public wpi::log::StartRecordData {
|
||||
public:
|
||||
std::vector<DataLogReaderRange> ranges; // ranges where this entry is valid
|
||||
};
|
||||
|
||||
class DataLogReaderThread {
|
||||
public:
|
||||
explicit DataLogReaderThread(wpi::log::DataLogReader reader)
|
||||
: m_reader{std::move(reader)}, m_thread{[this] { ReadMain(); }} {}
|
||||
~DataLogReaderThread();
|
||||
|
||||
bool IsDone() const { return m_done; }
|
||||
std::string_view GetBufferIdentifier() const {
|
||||
return m_reader.GetBufferIdentifier();
|
||||
}
|
||||
unsigned int GetNumRecords() const { return m_numRecords; }
|
||||
unsigned int GetNumEntries() const {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return m_entriesByName.size();
|
||||
}
|
||||
|
||||
// Passes Entry& to func
|
||||
template <typename T>
|
||||
void ForEachEntryName(T&& func) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
for (auto&& kv : m_entriesByName) {
|
||||
func(kv.second);
|
||||
}
|
||||
}
|
||||
|
||||
const DataLogReaderEntry* GetEntry(std::string_view name) const {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
auto it = m_entriesByName.find(name);
|
||||
if (it == m_entriesByName.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
wpi::StructDescriptorDatabase& GetStructDatabase() { return m_structDb; }
|
||||
wpi::ProtobufMessageDatabase& GetProtobufDatabase() { return m_protoDb; }
|
||||
|
||||
const wpi::log::DataLogReader& GetReader() const { return m_reader; }
|
||||
|
||||
// note: these are called on separate thread
|
||||
wpi::sig::Signal_mt<const DataLogReaderEntry&> sigEntryAdded;
|
||||
wpi::sig::Signal_mt<> sigDone;
|
||||
|
||||
private:
|
||||
void ReadMain();
|
||||
|
||||
wpi::log::DataLogReader m_reader;
|
||||
mutable wpi::mutex m_mutex;
|
||||
std::atomic_bool m_active{true};
|
||||
std::atomic_bool m_done{false};
|
||||
std::atomic<unsigned int> m_numRecords{0};
|
||||
std::map<std::string, DataLogReaderEntry, std::less<>> m_entriesByName;
|
||||
wpi::DenseMap<int, DataLogReaderEntry*> m_entriesById;
|
||||
wpi::StructDescriptorDatabase m_structDb;
|
||||
wpi::ProtobufMessageDatabase m_protoDb;
|
||||
std::thread m_thread;
|
||||
};
|
||||
|
||||
} // namespace glass
|
||||
@@ -23,10 +23,12 @@ NTPIDControllerModel::NTPIDControllerModel(nt::NetworkTableInstance inst,
|
||||
m_d{inst.GetDoubleTopic(fmt::format("{}/d", path)).GetEntry(0)},
|
||||
m_setpoint{
|
||||
inst.GetDoubleTopic(fmt::format("{}/setpoint", path)).GetEntry(0)},
|
||||
m_iZone{inst.GetDoubleTopic(fmt::format("{}/izone", path)).GetEntry(0)},
|
||||
m_pData{fmt::format("NTPIDCtrlP:{}", path)},
|
||||
m_iData{fmt::format("NTPIDCtrlI:{}", path)},
|
||||
m_dData{fmt::format("NTPIDCtrlD:{}", path)},
|
||||
m_setpointData{fmt::format("NTPIDCtrlStpt:{}", path)},
|
||||
m_iZoneData{fmt::format("NTPIDCtrlIZone:{}", path)},
|
||||
m_nameValue{wpi::rsplit(path, '/').second} {}
|
||||
|
||||
void NTPIDControllerModel::SetP(double value) {
|
||||
@@ -44,6 +46,9 @@ void NTPIDControllerModel::SetD(double value) {
|
||||
void NTPIDControllerModel::SetSetpoint(double value) {
|
||||
m_setpoint.Set(value);
|
||||
}
|
||||
void NTPIDControllerModel::SetIZone(double value) {
|
||||
m_iZone.Set(value);
|
||||
}
|
||||
|
||||
void NTPIDControllerModel::Update() {
|
||||
for (auto&& v : m_name.ReadQueue()) {
|
||||
@@ -61,6 +66,9 @@ void NTPIDControllerModel::Update() {
|
||||
for (auto&& v : m_setpoint.ReadQueue()) {
|
||||
m_setpointData.SetValue(v.value, v.time);
|
||||
}
|
||||
for (auto&& v : m_iZone.ReadQueue()) {
|
||||
m_iZoneData.SetValue(v.value, v.time);
|
||||
}
|
||||
for (auto&& v : m_controllable.ReadQueue()) {
|
||||
m_controllableValue = v.value;
|
||||
}
|
||||
|
||||
80
glass/src/libnt/native/cpp/NTProfiledPIDController.cpp
Normal file
80
glass/src/libnt/native/cpp/NTProfiledPIDController.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "glass/networktables/NTProfiledPIDController.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
|
||||
using namespace glass;
|
||||
|
||||
NTProfiledPIDControllerModel::NTProfiledPIDControllerModel(
|
||||
std::string_view path)
|
||||
: NTProfiledPIDControllerModel(nt::NetworkTableInstance::GetDefault(),
|
||||
path) {}
|
||||
|
||||
NTProfiledPIDControllerModel::NTProfiledPIDControllerModel(
|
||||
nt::NetworkTableInstance inst, std::string_view path)
|
||||
: m_inst{inst},
|
||||
m_name{inst.GetStringTopic(fmt::format("{}/.name", path)).Subscribe("")},
|
||||
m_controllable{inst.GetBooleanTopic(fmt::format("{}/.controllable", path))
|
||||
.Subscribe(false)},
|
||||
m_p{inst.GetDoubleTopic(fmt::format("{}/p", path)).GetEntry(0)},
|
||||
m_i{inst.GetDoubleTopic(fmt::format("{}/i", path)).GetEntry(0)},
|
||||
m_d{inst.GetDoubleTopic(fmt::format("{}/d", path)).GetEntry(0)},
|
||||
m_goal{inst.GetDoubleTopic(fmt::format("{}/goal", path)).GetEntry(0)},
|
||||
m_iZone{inst.GetDoubleTopic(fmt::format("{}/izone", path)).GetEntry(0)},
|
||||
m_pData{fmt::format("NTPIDCtrlP:{}", path)},
|
||||
m_iData{fmt::format("NTPIDCtrlI:{}", path)},
|
||||
m_dData{fmt::format("NTPIDCtrlD:{}", path)},
|
||||
m_goalData{fmt::format("NTPIDCtrlGoal:{}", path)},
|
||||
m_iZoneData{fmt::format("NTPIDCtrlIZone:{}", path)},
|
||||
m_nameValue{wpi::rsplit(path, '/').second} {}
|
||||
|
||||
void NTProfiledPIDControllerModel::SetP(double value) {
|
||||
m_p.Set(value);
|
||||
}
|
||||
|
||||
void NTProfiledPIDControllerModel::SetI(double value) {
|
||||
m_i.Set(value);
|
||||
}
|
||||
|
||||
void NTProfiledPIDControllerModel::SetD(double value) {
|
||||
m_d.Set(value);
|
||||
}
|
||||
|
||||
void NTProfiledPIDControllerModel::SetGoal(double value) {
|
||||
m_goal.Set(value);
|
||||
}
|
||||
void NTProfiledPIDControllerModel::SetIZone(double value) {
|
||||
m_iZone.Set(value);
|
||||
}
|
||||
|
||||
void NTProfiledPIDControllerModel::Update() {
|
||||
for (auto&& v : m_name.ReadQueue()) {
|
||||
m_nameValue = std::move(v.value);
|
||||
}
|
||||
for (auto&& v : m_p.ReadQueue()) {
|
||||
m_pData.SetValue(v.value, v.time);
|
||||
}
|
||||
for (auto&& v : m_i.ReadQueue()) {
|
||||
m_iData.SetValue(v.value, v.time);
|
||||
}
|
||||
for (auto&& v : m_d.ReadQueue()) {
|
||||
m_dData.SetValue(v.value, v.time);
|
||||
}
|
||||
for (auto&& v : m_goal.ReadQueue()) {
|
||||
m_goalData.SetValue(v.value, v.time);
|
||||
}
|
||||
for (auto&& v : m_iZone.ReadQueue()) {
|
||||
m_iZoneData.SetValue(v.value, v.time);
|
||||
}
|
||||
for (auto&& v : m_controllable.ReadQueue()) {
|
||||
m_controllableValue = v.value;
|
||||
}
|
||||
}
|
||||
|
||||
bool NTProfiledPIDControllerModel::Exists() {
|
||||
return m_goal.Exists();
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "glass/networktables/NTStringChooser.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <wpi/json.h>
|
||||
|
||||
using namespace glass;
|
||||
|
||||
@@ -16,17 +17,17 @@ NTStringChooserModel::NTStringChooserModel(nt::NetworkTableInstance inst,
|
||||
: m_inst{inst},
|
||||
m_default{
|
||||
m_inst.GetStringTopic(fmt::format("{}/default", path)).Subscribe("")},
|
||||
m_selected{
|
||||
m_inst.GetStringTopic(fmt::format("{}/selected", path)).GetEntry("")},
|
||||
m_selected{m_inst.GetStringTopic(fmt::format("{}/selected", path))
|
||||
.Subscribe("")},
|
||||
m_selectedPub{m_inst.GetStringTopic(fmt::format("{}/selected", path))
|
||||
.PublishEx("string", {{"retained", true}})},
|
||||
m_active{
|
||||
m_inst.GetStringTopic(fmt::format("{}/active", path)).Subscribe("")},
|
||||
m_options{m_inst.GetStringArrayTopic(fmt::format("{}/options", path))
|
||||
.Subscribe({})} {
|
||||
m_selected.GetTopic().SetRetained(true);
|
||||
}
|
||||
.Subscribe({})} {}
|
||||
|
||||
void NTStringChooserModel::SetSelected(std::string_view val) {
|
||||
m_selected.Set(val);
|
||||
m_selectedPub.Set(val);
|
||||
}
|
||||
|
||||
void NTStringChooserModel::Update() {
|
||||
|
||||
@@ -879,7 +879,10 @@ void NetworkTablesModel::Update() {
|
||||
} else if (desc->IsValid()) {
|
||||
// loop over all entries with this type and update
|
||||
for (auto&& entryPair : m_entries) {
|
||||
auto ts = entryPair.second->info.type_str;
|
||||
if (!entryPair.second) {
|
||||
continue;
|
||||
}
|
||||
std::string_view ts = entryPair.second->info.type_str;
|
||||
if (!wpi::starts_with(ts, "struct:")) {
|
||||
continue;
|
||||
}
|
||||
@@ -901,7 +904,10 @@ void NetworkTablesModel::Update() {
|
||||
} else {
|
||||
// loop over all protobuf entries and update (conservatively)
|
||||
for (auto&& entryPair : m_entries) {
|
||||
auto& ts = entryPair.second->info.type_str;
|
||||
if (!entryPair.second) {
|
||||
continue;
|
||||
}
|
||||
std::string_view ts = entryPair.second->info.type_str;
|
||||
if (wpi::starts_with(ts, "proto:")) {
|
||||
entryPair.second->UpdateFromValue(*this);
|
||||
}
|
||||
|
||||
@@ -83,6 +83,18 @@ void NetworkTablesProvider::DisplayMenu() {
|
||||
// FIXME: enabled?
|
||||
// data is the last item, so is guaranteed to be null-terminated
|
||||
ImGui::MenuItem(path.back().data(), nullptr, &visible, true);
|
||||
// Add type label to smartdashboard sendables
|
||||
if (wpi::starts_with(entry->name, "/SmartDashboard/")) {
|
||||
auto typeEntry = m_typeCache.FindValue(entry->name);
|
||||
if (typeEntry) {
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
|
||||
ImGui::Text("%s", typeEntry->stringVal.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::SameLine();
|
||||
ImGui::Dummy(ImVec2(10.0f, 0.0f));
|
||||
}
|
||||
}
|
||||
if (!wasVisible && visible) {
|
||||
Show(entry.get(), entry->window);
|
||||
} else if (wasVisible && !visible && entry->window) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user