mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-30 02:31:44 +00:00
Compare commits
140 Commits
v2023.0.0-
...
v2023.1.1-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf8faa9e67 | ||
|
|
5ec067c1f8 | ||
|
|
e962fd2916 | ||
|
|
88bd67e7de | ||
|
|
902e8686d3 | ||
|
|
e2d49181da | ||
|
|
149bac55b1 | ||
|
|
88f7a3ccb9 | ||
|
|
8acce443f0 | ||
|
|
295a1f8f3b | ||
|
|
388e7a4265 | ||
|
|
13aceea8dc | ||
|
|
c203f3f0a9 | ||
|
|
f54d495c90 | ||
|
|
e6392a1570 | ||
|
|
53904e7cf4 | ||
|
|
2e88a496c2 | ||
|
|
ce4c45df13 | ||
|
|
0401597d3b | ||
|
|
2e5f9e45bb | ||
|
|
e4b5795fc7 | ||
|
|
03d0ea188c | ||
|
|
3082bd236b | ||
|
|
b7ca860417 | ||
|
|
64838e6367 | ||
|
|
1269d2b901 | ||
|
|
14d8506b72 | ||
|
|
d1d458db2b | ||
|
|
f656e99245 | ||
|
|
6dd937cef7 | ||
|
|
49047c85b9 | ||
|
|
d07267fed1 | ||
|
|
b53ce1d3f0 | ||
|
|
5a320c326b | ||
|
|
c4e526d315 | ||
|
|
d122e4254f | ||
|
|
5a1e7ea036 | ||
|
|
179f569113 | ||
|
|
b0f6dc199d | ||
|
|
7836f661cd | ||
|
|
dbcc1de37f | ||
|
|
93890c528b | ||
|
|
3d8d5936f9 | ||
|
|
2b04159dec | ||
|
|
2764004fad | ||
|
|
85f1bb8f2b | ||
|
|
231ae2c353 | ||
|
|
e92b6dd5f9 | ||
|
|
2a8e0e1cc8 | ||
|
|
7d06e517e9 | ||
|
|
323524fed6 | ||
|
|
d426873ed1 | ||
|
|
5be5869b2f | ||
|
|
b1b4c1e9e7 | ||
|
|
a4054d702f | ||
|
|
0190301e09 | ||
|
|
9d1ce6a6d9 | ||
|
|
5005e2ca04 | ||
|
|
fa44a07938 | ||
|
|
4ba16db645 | ||
|
|
837415abfd | ||
|
|
2c20fd0d09 | ||
|
|
64a7136e08 | ||
|
|
b2b473b24a | ||
|
|
7aab8fa93a | ||
|
|
12c2851856 | ||
|
|
0da169dd84 | ||
|
|
2416827c25 | ||
|
|
1177a3522e | ||
|
|
102344e27a | ||
|
|
1831ef3e19 | ||
|
|
a9606ce870 | ||
|
|
6c80d5eab3 | ||
|
|
b114006543 | ||
|
|
32fbfb7da6 | ||
|
|
02465920fb | ||
|
|
3a5a376465 | ||
|
|
1c3c86e9f1 | ||
|
|
dcda09f90a | ||
|
|
66157397c1 | ||
|
|
9e22ffbebf | ||
|
|
648ab6115c | ||
|
|
8bc3b04f5b | ||
|
|
cfb84a6083 | ||
|
|
02c47726e1 | ||
|
|
b2a0093294 | ||
|
|
2a98d6b5d7 | ||
|
|
9f36301dc8 | ||
|
|
901fc555f4 | ||
|
|
4170ec6107 | ||
|
|
fe400f68c5 | ||
|
|
794669b346 | ||
|
|
dcfa85a5d5 | ||
|
|
15ad855f1d | ||
|
|
11244a49d9 | ||
|
|
1d2e8eb153 | ||
|
|
ad53fb19b4 | ||
|
|
ba850bac3b | ||
|
|
023a5989f8 | ||
|
|
c970011ccc | ||
|
|
07a43c3d9a | ||
|
|
a05b212b04 | ||
|
|
09faf31b67 | ||
|
|
9e1f9c1133 | ||
|
|
f19d2b9b84 | ||
|
|
a28f93863c | ||
|
|
c9f61669b8 | ||
|
|
dcce5ad3b3 | ||
|
|
6836e5923d | ||
|
|
335188c652 | ||
|
|
60a29dcb99 | ||
|
|
b55d5b3034 | ||
|
|
10ed4b3969 | ||
|
|
4a401b89d7 | ||
|
|
c195b4fc46 | ||
|
|
8f2e34c6a3 | ||
|
|
150d692df7 | ||
|
|
3e5bfff1b5 | ||
|
|
9c7e66a27d | ||
|
|
0ca274866b | ||
|
|
dc037f8d41 | ||
|
|
16cdc741cf | ||
|
|
9d5055176d | ||
|
|
d1e66e1296 | ||
|
|
1fc098e696 | ||
|
|
878cc8defb | ||
|
|
8153911160 | ||
|
|
fbdc810887 | ||
|
|
396143004c | ||
|
|
1f45732700 | ||
|
|
574cb41c18 | ||
|
|
d9d6c425e7 | ||
|
|
58b6484dbe | ||
|
|
ca43fe2798 | ||
|
|
87a64ccedc | ||
|
|
89a3d00297 | ||
|
|
1497665f96 | ||
|
|
27b173374e | ||
|
|
2a13dba8ac | ||
|
|
77301b126c |
13
.github/workflows/cmake.yml
vendored
13
.github/workflows/cmake.yml
vendored
@@ -12,9 +12,9 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
- os: ubuntu-22.04
|
||||
name: Linux
|
||||
container: wpilib/roborio-cross-ubuntu:2022-20.04
|
||||
container: wpilib/roborio-cross-ubuntu:2023-22.04
|
||||
flags: ""
|
||||
- os: macOS-11
|
||||
name: macOS
|
||||
@@ -27,12 +27,17 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install dependencies (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv4.5-java python-is-python3
|
||||
|
||||
- name: Install opencv (macOS)
|
||||
run: brew install opencv
|
||||
if: runner.os == 'macOS'
|
||||
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v2
|
||||
- name: Set up Python 3.8 (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.8
|
||||
|
||||
|
||||
11
.github/workflows/comment-command.yml
vendored
11
.github/workflows/comment-command.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
jobs:
|
||||
wpiformat:
|
||||
if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/wpiformat')
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: React Rocket
|
||||
uses: actions/github-script@v4
|
||||
@@ -37,16 +37,23 @@ jobs:
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 11
|
||||
- name: Install clang-format
|
||||
run: |
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
||||
sudo sh -c "echo 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-14 main' >> /etc/apt/sources.list.d/proposed-repositories.list"
|
||||
sudo sh -c "echo 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-14 main' >> /etc/apt/sources.list.d/proposed-repositories.list"
|
||||
sudo apt-get update -q
|
||||
sudo apt-get install -y clang-format-14
|
||||
- name: Install wpiformat
|
||||
run: pip3 install wpiformat
|
||||
- name: Run wpiformat
|
||||
run: wpiformat -clang 14
|
||||
- name: Run spotlessApply
|
||||
run: ./gradlew spotlessApply
|
||||
- name: Commit
|
||||
run: |
|
||||
# Set credentials
|
||||
|
||||
9
.github/workflows/documentation.yml
vendored
9
.github/workflows/documentation.yml
vendored
@@ -12,7 +12,7 @@ env:
|
||||
jobs:
|
||||
publish:
|
||||
name: "Documentation - Publish"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.repository_owner == 'wpilibsuite' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||||
concurrency: ci-docs-publish
|
||||
steps:
|
||||
@@ -20,11 +20,10 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-java@v1
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 13
|
||||
- name: Install libclang-9
|
||||
run: sudo apt update && sudo apt install -y libclang-cpp9 libclang1-9
|
||||
- name: Set environment variables (Development)
|
||||
run: |
|
||||
echo "TARGET_FOLDER=$BASE_PATH/development" >> $GITHUB_ENV
|
||||
@@ -42,7 +41,7 @@ jobs:
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew docs:generateJavaDocs docs:doxygen -PbuildServer ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
- name: Install SSH Client 🔑
|
||||
uses: webfactory/ssh-agent@v0.4.1
|
||||
uses: webfactory/ssh-agent@v0.7.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.GH_DEPLOY_KEY }}
|
||||
- name: Deploy Java 🚀
|
||||
|
||||
4
.github/workflows/gazebo.yml
vendored
4
.github/workflows/gazebo.yml
vendored
@@ -9,8 +9,8 @@ concurrency:
|
||||
jobs:
|
||||
build:
|
||||
name: "Build"
|
||||
runs-on: ubuntu-latest
|
||||
container: wpilib/gazebo-ubuntu:20.04
|
||||
runs-on: ubuntu-22.04
|
||||
container: wpilib/gazebo-ubuntu:22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
|
||||
55
.github/workflows/gradle.yml
vendored
55
.github/workflows/gradle.yml
vendored
@@ -12,20 +12,20 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- container: wpilib/roborio-cross-ubuntu:2022-20.04
|
||||
- container: wpilib/roborio-cross-ubuntu:2023-22.04
|
||||
artifact-name: Athena
|
||||
build-options: "-Ponlylinuxathena"
|
||||
- container: wpilib/raspbian-cross-ubuntu:10-20.04
|
||||
- container: wpilib/raspbian-cross-ubuntu:bullseye-22.04
|
||||
artifact-name: Arm32
|
||||
build-options: "-Ponlylinuxarm32"
|
||||
- container: wpilib/aarch64-cross-ubuntu:bionic-20.04
|
||||
- container: wpilib/aarch64-cross-ubuntu:bullseye-22.04
|
||||
artifact-name: Arm64
|
||||
build-options: "-Ponlylinuxarm64"
|
||||
- container: wpilib/ubuntu-base:20.04
|
||||
- container: wpilib/ubuntu-base:22.04
|
||||
artifact-name: Linux
|
||||
build-options: "-Ponlylinuxx86-64"
|
||||
name: "Build - ${{ matrix.artifact-name }}"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
container: ${{ matrix.container }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
env:
|
||||
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.artifact-name }}
|
||||
path: build/allOutputs
|
||||
@@ -51,29 +51,37 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-2019
|
||||
- os: windows-2022
|
||||
artifact-name: Win64Debug
|
||||
architecture: x64
|
||||
task: "build"
|
||||
build-options: "-PciDebugOnly --max-workers 1"
|
||||
- os: windows-2019
|
||||
outputs: "build/allOutputs"
|
||||
- os: windows-2022
|
||||
artifact-name: Win64Release
|
||||
architecture: x64
|
||||
build-options: "-PciReleaseOnly --max-workers 1"
|
||||
task: "copyAllOutputs"
|
||||
outputs: "build/allOutputs"
|
||||
- os: macOS-11
|
||||
artifact-name: macOS
|
||||
architecture: x64
|
||||
build-options: "-Pbuildalldesktop"
|
||||
task: "build"
|
||||
outputs: "build/allOutputs"
|
||||
- os: windows-2022
|
||||
artifact-name: Win32
|
||||
architecture: x86
|
||||
task: ":ntcoreffi:build"
|
||||
outputs: "ntcoreffi/build/outputs"
|
||||
name: "Build - ${{ matrix.artifact-name }}"
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v1
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 11
|
||||
architecture: ${{ matrix.architecture }}
|
||||
- name: Import Developer ID Certificate
|
||||
@@ -94,33 +102,35 @@ jobs:
|
||||
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
- name: Set Java Heap Size
|
||||
run: sed -i 's/-Xmx2g/-Xmx1g/g' gradle.properties
|
||||
if: matrix.artifact-name == 'Win32'
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew ${{ matrix.task }} --build-cache -PbuildServer -PskipJavaFormat ${{ matrix.build-options }} ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
env:
|
||||
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
- name: Sign Libraries with Developer ID
|
||||
run: ./gradlew build -PbuildServer -PskipJavaFormat -PdeveloperID=${{ secrets.APPLE_DEVELOPER_ID }} ${{ matrix.build-options }} ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
run: ./gradlew copyAllOutputs --build-cache -PbuildServer -PskipJavaFormat -PdeveloperID=${{ secrets.APPLE_DEVELOPER_ID }} ${{ matrix.build-options }} ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
if: |
|
||||
matrix.artifact-name == 'macOS' && (github.repository_owner == 'wpilibsuite' &&
|
||||
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')))
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.artifact-name }}
|
||||
path: build/allOutputs
|
||||
path: ${{ matrix.outputs }}
|
||||
|
||||
build-documentation:
|
||||
name: "Build - Documentation"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v1
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 13
|
||||
- name: Install libclang-9
|
||||
run: sudo apt update && sudo apt install -y libclang-cpp9 libclang1-9
|
||||
- name: Set release environment variable
|
||||
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
@@ -129,7 +139,7 @@ jobs:
|
||||
env:
|
||||
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Documentation
|
||||
path: docs/build/outputs
|
||||
@@ -137,12 +147,12 @@ jobs:
|
||||
combine:
|
||||
name: Combine
|
||||
needs: [build-docker, build-host, build-documentation]
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: wpilibsuite/build-tools
|
||||
- uses: actions/download-artifact@v2
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: combiner/products/build/allOutputs
|
||||
- name: Flatten Artifacts
|
||||
@@ -151,8 +161,9 @@ jobs:
|
||||
run: |
|
||||
cat combiner/products/build/allOutputs/version.txt
|
||||
test -s combiner/products/build/allOutputs/version.txt
|
||||
- uses: actions/setup-java@v1
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 11
|
||||
- name: Combine
|
||||
if: |
|
||||
@@ -177,7 +188,7 @@ jobs:
|
||||
RUN_AZURE_ARTIFACTORY_RELEASE: "TRUE"
|
||||
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Maven
|
||||
path: ~/releases
|
||||
|
||||
35
.github/workflows/lint-format.yml
vendored
35
.github/workflows/lint-format.yml
vendored
@@ -13,7 +13,7 @@ concurrency:
|
||||
jobs:
|
||||
wpiformat:
|
||||
name: "wpiformat"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Fetch all history and metadata
|
||||
@@ -23,13 +23,13 @@ jobs:
|
||||
git checkout -b pr
|
||||
git branch -f main origin/main
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Install clang-format
|
||||
run: |
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
||||
sudo sh -c "echo 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-14 main' >> /etc/apt/sources.list.d/proposed-repositories.list"
|
||||
sudo sh -c "echo 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-14 main' >> /etc/apt/sources.list.d/proposed-repositories.list"
|
||||
sudo apt-get update -q
|
||||
sudo apt-get install -y clang-format-14
|
||||
- name: Install wpiformat
|
||||
@@ -41,15 +41,23 @@ jobs:
|
||||
- name: Generate diff
|
||||
run: git diff HEAD > wpiformat-fixes.patch
|
||||
if: ${{ failure() }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wpiformat fixes
|
||||
path: wpiformat-fixes.patch
|
||||
if: ${{ failure() }}
|
||||
- name: Write to job summary
|
||||
run: |
|
||||
echo '```diff' >> $GITHUB_STEP_SUMMARY
|
||||
cat wpiformat-fixes.patch >> $GITHUB_STEP_SUMMARY
|
||||
echo '' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
if: ${{ failure() }}
|
||||
|
||||
tidy:
|
||||
name: "clang-tidy"
|
||||
runs-on: ubuntu-latest
|
||||
container: wpilib/roborio-cross-ubuntu:2022-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
container: wpilib/roborio-cross-ubuntu:2023-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Fetch all history and metadata
|
||||
@@ -59,13 +67,13 @@ jobs:
|
||||
git checkout -b pr
|
||||
git branch -f main origin/main
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Install clang-tidy
|
||||
run: |
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
||||
sudo sh -c "echo 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-14 main' >> /etc/apt/sources.list.d/proposed-repositories.list"
|
||||
sudo sh -c "echo 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-14 main' >> /etc/apt/sources.list.d/proposed-repositories.list"
|
||||
sudo apt-get update -q
|
||||
sudo apt-get install -y clang-tidy-14 clang-format-14
|
||||
- name: Install wpiformat
|
||||
@@ -78,8 +86,8 @@ jobs:
|
||||
run: wpiformat -clang 14 -no-format -tidy-changed -compile-commands=build/compile_commands/linuxx86-64 -vv
|
||||
javaformat:
|
||||
name: "Java format"
|
||||
runs-on: ubuntu-latest
|
||||
container: wpilib/ubuntu-base:20.04
|
||||
runs-on: ubuntu-22.04
|
||||
container: wpilib/ubuntu-base:22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Fetch all history and metadata
|
||||
@@ -97,15 +105,14 @@ jobs:
|
||||
if: ${{ failure() }}
|
||||
documentation:
|
||||
name: "Documentation"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v1
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 13
|
||||
- name: Install libclang-9
|
||||
run: sudo apt update && sudo apt install -y libclang-cpp9 libclang1-9
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew docs:zipDocs -PbuildServer -PdocWarningsAsErrors ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
|
||||
23
.github/workflows/sanitizers.yml
vendored
23
.github/workflows/sanitizers.yml
vendored
@@ -15,40 +15,29 @@ jobs:
|
||||
- name: asan
|
||||
cmake-flags: "-DCMAKE_BUILD_TYPE=Asan"
|
||||
ctest-env: ""
|
||||
ctest-flags: "-E 'ntcore|wpilibc'"
|
||||
ctest-flags: "-E 'wpilibc'"
|
||||
- name: tsan
|
||||
cmake-flags: "-DCMAKE_BUILD_TYPE=Tsan"
|
||||
ctest-env: "TSAN_OPTIONS=second_deadlock_stack=1"
|
||||
ctest-flags: "-E 'ntcore|cscore|cameraserver|wpilibc|wpilibNewCommands'"
|
||||
ctest-flags: "-E 'cscore|cameraserver|wpilibc|wpilibNewCommands'"
|
||||
- name: ubsan
|
||||
cmake-flags: "-DCMAKE_BUILD_TYPE=Ubsan"
|
||||
ctest-env: ""
|
||||
ctest-flags: ""
|
||||
name: "${{ matrix.name }}"
|
||||
runs-on: ubuntu-latest
|
||||
container: wpilib/roborio-cross-ubuntu:2022-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
container: wpilib/roborio-cross-ubuntu:2023-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
|
||||
sudo apt install -y gcc-11 g++-11
|
||||
sudo update-alternatives \
|
||||
--install /usr/bin/gcc gcc /usr/bin/gcc-11 11 \
|
||||
--slave /usr/bin/g++ g++ /usr/bin/g++-11
|
||||
sudo update-alternatives --set gcc /usr/bin/gcc-11
|
||||
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv4.5-java python-is-python3 clang-14
|
||||
|
||||
- name: Install jinja
|
||||
run: python -m pip install jinja2
|
||||
|
||||
- name: configure
|
||||
run: mkdir build && cd build && cmake ${{ matrix.cmake-flags }} ..
|
||||
run: mkdir build && cd build && cmake -DCMAKE_C_COMPILER:FILEPATH=/usr/bin/clang-14 -DCMAKE_CXX_COMPILER:FILEPATH=/usr/bin/clang++-14 ${{ matrix.cmake-flags }} ..
|
||||
|
||||
- name: build
|
||||
working-directory: build
|
||||
|
||||
8
.github/workflows/upstream-utils.yml
vendored
8
.github/workflows/upstream-utils.yml
vendored
@@ -13,7 +13,7 @@ concurrency:
|
||||
jobs:
|
||||
update:
|
||||
name: "Update"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Fetch all history and metadata
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
git checkout -b pr
|
||||
git branch -f main origin/main
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Configure committer identity
|
||||
@@ -49,6 +49,10 @@ jobs:
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./update_llvm.py
|
||||
- name: Run update_mpack.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./update_mpack.py
|
||||
- name: Run update_stack_walker.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
|
||||
@@ -36,22 +36,23 @@ SET(CMAKE_SKIP_BUILD_RPATH FALSE)
|
||||
# (but later on when installing)
|
||||
SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
|
||||
|
||||
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/wpilib/lib")
|
||||
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
|
||||
|
||||
# add the automatically determined parts of the RPATH
|
||||
# which point to directories outside the build tree to the install RPATH
|
||||
SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
|
||||
|
||||
# the RPATH to be used when installing, but only if it's not a system directory
|
||||
LIST(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/wpilib/lib" isSystemDir)
|
||||
LIST(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" isSystemDir)
|
||||
IF("${isSystemDir}" STREQUAL "-1")
|
||||
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/wpilib/lib")
|
||||
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
|
||||
ENDIF("${isSystemDir}" STREQUAL "-1")
|
||||
|
||||
# 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)
|
||||
option(WITH_JAVA "Include java and JNI in the build" ON)
|
||||
option(WITH_CSCORE "Build cscore (needs OpenCV)" ON)
|
||||
option(WITH_NTCORE "Build ntcore" ON)
|
||||
option(WITH_WPIMATH "Build wpimath" ON)
|
||||
option(WITH_WPILIB "Build hal, wpilibc/j, and myRobot (needs OpenCV)" ON)
|
||||
option(WITH_EXAMPLES "Build examples" OFF)
|
||||
@@ -63,10 +64,10 @@ option(WITH_SIMULATION_MODULES "Build simulation modules" ON)
|
||||
option(WITH_EXTERNAL_HAL "Use a separately built HAL" OFF)
|
||||
set(EXTERNAL_HAL_FILE "" CACHE FILEPATH "Location to look for an external HAL CMake File")
|
||||
|
||||
# Options for using a package manager (vcpkg) for certain dependencies.
|
||||
option(USE_VCPKG_FMTLIB "Use vcpkg fmtlib" OFF)
|
||||
option(USE_VCPKG_LIBUV "Use vcpkg libuv" OFF)
|
||||
option(USE_VCPKG_EIGEN "Use vcpkg eigen" OFF)
|
||||
# Options for using a package manager (e.g., vcpkg) for certain dependencies.
|
||||
option(USE_SYSTEM_FMTLIB "Use system fmtlib" OFF)
|
||||
option(USE_SYSTEM_LIBUV "Use system libuv" OFF)
|
||||
option(USE_SYSTEM_EIGEN "Use system eigen" OFF)
|
||||
|
||||
# Options for installation.
|
||||
option(WITH_FLAT_INSTALL "Use a flat install directory" OFF)
|
||||
@@ -117,6 +118,34 @@ FATAL: Cannot build simulation modules with wpilib disabled.
|
||||
")
|
||||
endif()
|
||||
|
||||
if (NOT WITH_NTCORE AND WITH_CSCORE)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build cameraserver without ntcore.
|
||||
Enable ntcore by setting WITH_NTCORE=ON
|
||||
")
|
||||
endif()
|
||||
|
||||
if (NOT WITH_NTCORE AND WITH_GUI)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build GUI modules without ntcore.
|
||||
Enable ntcore by setting WITH_NTCORE=ON
|
||||
")
|
||||
endif()
|
||||
|
||||
if (NOT WITH_NTCORE AND WITH_SIMULATION_MODULES)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build simulation modules without ntcore.
|
||||
Enable ntcore by setting WITH_NTCORE=ON
|
||||
")
|
||||
endif()
|
||||
|
||||
if (NOT WITH_NTCORE AND WITH_WPILIB)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build wpilib without ntcore.
|
||||
Enable ntcore by setting WITH_NTCORE=ON
|
||||
")
|
||||
endif()
|
||||
|
||||
if (NOT WITH_WPIMATH AND WITH_WPILIB)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build wpilib without wpimath.
|
||||
@@ -124,11 +153,11 @@ FATAL: Cannot build wpilib without wpimath.
|
||||
")
|
||||
endif()
|
||||
|
||||
set( wpilib_dest wpilib)
|
||||
set( include_dest wpilib/include )
|
||||
set( main_lib_dest wpilib/lib )
|
||||
set( java_lib_dest wpilib/java )
|
||||
set( jni_lib_dest wpilib/jni )
|
||||
set( wpilib_dest "")
|
||||
set( include_dest include )
|
||||
set( main_lib_dest lib )
|
||||
set( java_lib_dest java )
|
||||
set( jni_lib_dest jni )
|
||||
|
||||
if (WITH_FLAT_INSTALL)
|
||||
set (wpilib_config_dir ${wpilib_dest})
|
||||
@@ -136,12 +165,15 @@ else()
|
||||
set (wpilib_config_dir share/wpilib)
|
||||
endif()
|
||||
|
||||
if (USE_VCPKG_LIBUV)
|
||||
set (LIBUV_VCPKG_REPLACE "find_package(unofficial-libuv CONFIG)")
|
||||
if (USE_SYSTEM_LIBUV)
|
||||
set (LIBUV_SYSTEM_REPLACE "
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(libuv REQUIRED IMPORTED_TARGET libuv)
|
||||
")
|
||||
endif()
|
||||
|
||||
if (USE_VCPKG_EIGEN)
|
||||
set (EIGEN_VCPKG_REPLACE "find_package(Eigen3 CONFIG)")
|
||||
if (USE_SYSTEM_EIGEN)
|
||||
set (EIGEN_SYSTEM_REPLACE "find_package(Eigen3 CONFIG)")
|
||||
endif()
|
||||
|
||||
find_package(LIBSSH 0.7.1)
|
||||
@@ -247,8 +279,11 @@ if (WITH_TESTS)
|
||||
endif()
|
||||
|
||||
add_subdirectory(wpiutil)
|
||||
add_subdirectory(wpinet)
|
||||
add_subdirectory(ntcore)
|
||||
|
||||
if (WITH_NTCORE)
|
||||
add_subdirectory(wpinet)
|
||||
add_subdirectory(ntcore)
|
||||
endif()
|
||||
|
||||
if (WITH_WPIMATH)
|
||||
add_subdirectory(wpimath)
|
||||
@@ -280,6 +315,7 @@ endif()
|
||||
|
||||
if (WITH_WPILIB)
|
||||
set(WPILIBC_DEP_REPLACE ${WPILIBC_DEP_REPLACE_IMPL})
|
||||
add_subdirectory(apriltag)
|
||||
add_subdirectory(wpilibj)
|
||||
add_subdirectory(wpilibc)
|
||||
add_subdirectory(wpilibNewCommands)
|
||||
|
||||
@@ -37,8 +37,7 @@ So you want to contribute your changes back to WPILib. Great! We have a few cont
|
||||
|
||||
## Coding Guidelines
|
||||
|
||||
WPILib uses modified Google style guides for both C++ and Java, which can be found in the [styleguide repository](https://github.com/wpilibsuite/styleguide). Autoformatters are available for many popular editors at https://github.com/google/styleguide. Running wpiformat is required for all contributions and is enforced by our continuous integration system. We currently use clang-format 14.0 with wpiformat.
|
||||
|
||||
WPILib uses modified Google style guides for both C++ and Java, which can be found in the [styleguide repository](https://github.com/wpilibsuite/styleguide). Autoformatters are available for many popular editors at https://github.com/google/styleguide. Running wpiformat is required for all contributions and is enforced by our continuous integration system.
|
||||
While the library should be fully formatted according to the styles, additional elements of the style guide were not followed when the library was initially created. All new code should follow the guidelines. If you are looking for some easy ramp-up tasks, finding areas that don't follow the style guide and fixing them is very welcome.
|
||||
|
||||
When writing math expressions in documentation, use https://www.unicodeit.net/ to convert LaTeX to a Unicode equivalent that's easier to read. Not all expressions will translate (e.g., superscripts of superscripts) so focus on making it readable by someone who isn't familiar with LaTeX. If content on multiple lines needs to be aligned in Doxygen/Javadoc comments (e.g., integration/summation limits, matrices packed with square brackets and superscripts for them), put them in @verbatim/@endverbatim blocks in Doxygen or `<pre>` tags in Javadoc so they render with monospace font.
|
||||
|
||||
@@ -8,7 +8,7 @@ This article contains instructions on building projects using a development buil
|
||||
|
||||
Development builds are the per-commit build hosted every time a commit is pushed to the [allwpilib](https://github.com/wpilibsuite/allwpilib/) repository. These builds are then hosted on [artifactory](https://frcmaven.wpi.edu/artifactory/webapp/#/home).
|
||||
|
||||
In order to build a project using a development build, find the build.gradle file and open it. Then, add the following code below the plugin section and replace YEAR with the year of the development version. It is also necessary to use a 2023 GradleRIO version, ie `2023.0.0-alpha-1`
|
||||
To build a project using a development build, find the build.gradle file and open it. Then, add the following code below the plugin section and replace YEAR with the year of the development version. It is also necessary to use a 2023 GradleRIO version, ie `2023.0.0-alpha-1`
|
||||
|
||||
```groovy
|
||||
wpi.maven.useLocal = false
|
||||
@@ -46,6 +46,11 @@ wpi.versions.wpilibVersion = '2023.+'
|
||||
wpi.versions.wpimathVersion = '2023.+'
|
||||
```
|
||||
|
||||
### Development Build Documentation
|
||||
|
||||
* C++: https://github.wpilib.org/allwpilib/docs/development/cpp/
|
||||
* Java: https://github.wpilib.org/allwpilib/docs/development/java/
|
||||
|
||||
## Local Build
|
||||
|
||||
Building with a local build is very similar to building with a development build. Ensure you have built and published WPILib by following the instructions attached [here](https://github.com/wpilibsuite/allwpilib#building-wpilib). Next, find the ``build.gradle`` file in your robot project and open it. Then, add the following code below the plugin section and replace ``YEAR`` with the year of the local version.
|
||||
@@ -90,4 +95,4 @@ The following 3 tasks can be used for deployment:
|
||||
|
||||
Deploying any of these to the roboRIO will disable the current startup project until it is redeployed.
|
||||
|
||||
From here, ssh into the roboRIO using the `admin` account (`lvuser` will fail to run in many cases). In the admin home directory, a file for each deploy type will exist (`myRobotCpp`, `myRobotCppStatic` and `myRobotJavaRun`). These can be run to start up the corresponding project.
|
||||
From here, ssh into the roboRIO using the `lvuser` account and run `frcRunRobot.sh` (It's in path).
|
||||
@@ -72,12 +72,16 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
|
||||
* wpigui
|
||||
* imgui
|
||||
|
||||
* ntcore
|
||||
* wpiutil
|
||||
|
||||
* wpimath
|
||||
* wpiutil
|
||||
|
||||
* wpinet
|
||||
* wpiutil
|
||||
|
||||
* ntcore
|
||||
* wpiutil
|
||||
* wpinet
|
||||
|
||||
* glass/libglass
|
||||
* wpiutil
|
||||
* wpimath
|
||||
@@ -85,6 +89,7 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
|
||||
|
||||
* glass/libglassnt
|
||||
* wpiutil
|
||||
* wpinet
|
||||
* ntcore
|
||||
* wpimath
|
||||
* wpigui
|
||||
@@ -94,6 +99,7 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
|
||||
|
||||
* halsim
|
||||
* wpiutil
|
||||
* wpinet
|
||||
* ntcore
|
||||
* wpimath
|
||||
* wpigui
|
||||
@@ -102,12 +108,14 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
|
||||
|
||||
* cscore
|
||||
* opencv
|
||||
* wpinet
|
||||
* wpiutil
|
||||
|
||||
* cameraserver
|
||||
* ntcore
|
||||
* cscore
|
||||
* opencv
|
||||
* wpinet
|
||||
* wpiutil
|
||||
|
||||
* wpilibj
|
||||
@@ -115,6 +123,7 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
|
||||
* cameraserver
|
||||
* ntcore
|
||||
* cscore
|
||||
* wpinet
|
||||
* wpiutil
|
||||
|
||||
* wpilibc
|
||||
@@ -123,6 +132,7 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
|
||||
* ntcore
|
||||
* cscore
|
||||
* wpimath
|
||||
* wpinet
|
||||
* wpiutil
|
||||
|
||||
* wpilibNewCommands
|
||||
@@ -132,6 +142,7 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
|
||||
* ntcore
|
||||
* cscore
|
||||
* wpimath
|
||||
* wpinet
|
||||
* wpiutil
|
||||
|
||||
|
||||
|
||||
@@ -31,14 +31,20 @@ The following build options are available:
|
||||
* This option will enable Java and JNI builds. If this is on, `WITH_SHARED_LIBS` must be on. Otherwise CMake will error.
|
||||
* `WITH_SHARED_LIBS` (ON Default)
|
||||
* This option will cause cmake to build static libraries instead of shared libraries. If this is off, `WITH_JAVA` must be off. Otherwise CMake will error.
|
||||
* `WITH_TESTS` (ON Default)
|
||||
* This option will build C++ unit tests. These can be run via `make test`.
|
||||
* `WITH_CSCORE` (ON Default)
|
||||
* This option will cause cscore to be built. Turning this off will implicitly disable cameraserver, the hal and wpilib as well, irrespective of their specific options. If this is off, the OpenCV build requirement is removed.
|
||||
* `WITH_NTCORE` (ON Default)
|
||||
* This option will cause ntcore to be built. Turning this off will implicitly disable wpinet and wpilib as well, irrespective of their specific options.
|
||||
* `WITH_WPIMATH` (ON Default)
|
||||
* This option will build the wpimath library. This option must be on to build wpilib.
|
||||
* `WITH_WPILIB` (ON Default)
|
||||
* This option will build the hal and wpilibc/j during the build. The HAL is the simulator hal, unless the external hal options are used. The cmake build has no capability to build for the RoboRIO.
|
||||
* `WITH_EXAMPLES` (ON Default)
|
||||
* This option will build C++ examples.
|
||||
* `WITH_TESTS` (ON Default)
|
||||
* This option will build C++ unit tests. These can be run via `make test`.
|
||||
* `WITH_GUI` (ON Default)
|
||||
* This option will build GUI items.
|
||||
* `WITH_SIMULATION_MODULES` (ON Default)
|
||||
* This option will build simulation modules, including wpigui and the HALSim plugins.
|
||||
* `WITH_EXTERNAL_HAL` (OFF Default)
|
||||
|
||||
18
README.md
18
README.md
@@ -1,8 +1,8 @@
|
||||
# WPILib Project
|
||||
|
||||

|
||||
[](https://first.wpi.edu/wpilib/allwpilib/docs/development/cpp/)
|
||||
[](https://first.wpi.edu/wpilib/allwpilib/docs/development/java/)
|
||||
[](https://github.wpilib.org/allwpilib/docs/development/cpp/)
|
||||
[](https://github.wpilib.org/allwpilib/docs/development/java/)
|
||||
|
||||
Welcome to the WPILib project. This repository contains the HAL, WPILibJ, and WPILibC projects. These are the core libraries for creating robot programs for the roboRIO.
|
||||
|
||||
@@ -33,7 +33,7 @@ Below is a list of instructions that guide you through cloning, building, publis
|
||||
1. Clone the repository with `git clone https://github.com/wpilibsuite/allwpilib.git`
|
||||
2. Build the repository with `./gradlew build` or `./gradlew build --build-cache` if you have an internet connection
|
||||
3. Publish the artifacts locally by running `./gradlew publish`
|
||||
4. [Update your](OtherVersions.md) `build.gradle` [to use the artifacts](OtherVersions.md)
|
||||
4. [Update your](DevelopmentBuilds.md) `build.gradle` [to use the artifacts](DevelopmentBuilds.md)
|
||||
|
||||
# Building WPILib
|
||||
|
||||
@@ -47,8 +47,8 @@ Using Gradle makes building WPILib very straightforward. It only has a few depen
|
||||
- On Windows, install the JDK 11 .msi from the link above
|
||||
- On macOS, install the JDK 11 .pkg from the link above
|
||||
- C++ compiler
|
||||
- On Linux, install GCC 8 or greater
|
||||
- On Windows, install [Visual Studio Community 2022 or 2019](https://visualstudio.microsoft.com/vs/community/) and select the C++ programming language during installation (Gradle can't use the build tools for Visual Studio)
|
||||
- On Linux, install GCC 11 or greater
|
||||
- On Windows, install [Visual Studio Community 2022](https://visualstudio.microsoft.com/vs/community/) and select the C++ programming language during installation (Gradle can't use the build tools for Visual Studio)
|
||||
- On macOS, install the Xcode command-line build tools via `xcode-select --install`
|
||||
- ARM compiler toolchain
|
||||
- Run `./gradlew installRoboRioToolchain` after cloning this repository
|
||||
@@ -62,7 +62,7 @@ On macOS ARM, run `softwareupdate --install-rosetta`. This is necessary to be ab
|
||||
|
||||
Clone the WPILib repository and follow the instructions above for installing any required tooling.
|
||||
|
||||
See the [styleguide README](https://github.com/wpilibsuite/styleguide/blob/main/README.md) for wpiformat setup instructions.
|
||||
See the [styleguide README](https://github.com/wpilibsuite/styleguide/blob/main/README.md) for wpiformat setup instructions. We use clang-format 14.
|
||||
|
||||
## Building
|
||||
|
||||
@@ -104,7 +104,7 @@ Run with `--build-cache` on the command-line to use the shared [build cache](htt
|
||||
|
||||
### Using Development Builds
|
||||
|
||||
Please read the documentation available [here](OtherVersions.md)
|
||||
Please read the documentation available [here](DevelopmentBuilds.md)
|
||||
|
||||
### Custom toolchain location
|
||||
|
||||
@@ -137,7 +137,9 @@ make
|
||||
|
||||
#### wpiformat
|
||||
|
||||
wpiformat can be executed anywhere in the repository via `py -3 -m wpiformat` on Windows or `python3 -m wpiformat` on other platforms.
|
||||
wpiformat can be executed anywhere in the repository via `py -3 -m wpiformat -clang 14` on Windows or `python3 -m wpiformat -clang 14` on other platforms.
|
||||
|
||||
Once a PR has been submitted, formatting can be run in CI by commenting `/wpiformat` on the PR. A new commit will be pushed with the formatting changes.
|
||||
|
||||
#### Java Code Quality Tools
|
||||
|
||||
|
||||
@@ -34,8 +34,6 @@ popper.js wpinet/src/main/native/resources/popper-*
|
||||
units wpimath/src/main/native/include/units/
|
||||
Eigen wpimath/src/main/native/thirdparty/eigen/include/
|
||||
StackWalker wpiutil/src/main/native/windows/StackWalker.*
|
||||
TCB span wpiutil/src/main/native/thirdparty/include/wpi/span.h
|
||||
wpiutil/src/test/native/cpp/span/
|
||||
GHC filesystem wpiutil/src/main/native/thirdparty/include/wpi/ghc/
|
||||
Team 254 Library wpilibj/src/main/java/edu/wpi/first/wpilibj/spline/SplineParameterizer.java
|
||||
wpilibj/src/main/java/edu/wpi/first/wpilibj/trajectory/TrajectoryParameterizer.java
|
||||
@@ -47,6 +45,7 @@ Drake wpimath/src/main/native/thirdparty/drake/
|
||||
wpimath/src/test/native/cpp/drake/
|
||||
wpimath/src/test/native/include/drake/
|
||||
V8 export-template wpiutil/src/main/native/include/wpi/SymbolExports.h
|
||||
GCEM wpimath/src/main/native/thirdparty/gcem/include/
|
||||
|
||||
==============================================================================
|
||||
Google Test License
|
||||
@@ -1226,3 +1225,20 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
============
|
||||
GCEM License
|
||||
============
|
||||
Copyright 2022 - ktholer (https://github.com/kthohr/gcem)
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
41
apriltag/CMakeLists.txt
Normal file
41
apriltag/CMakeLists.txt
Normal file
@@ -0,0 +1,41 @@
|
||||
project(fieldImages)
|
||||
|
||||
include(CompileWarnings)
|
||||
include(GenResources)
|
||||
|
||||
if (WITH_JAVA)
|
||||
find_package(Java REQUIRED)
|
||||
include(UseJava)
|
||||
|
||||
file(GLOB JACKSON_JARS "${WPILIB_BINARY_DIR}/wpiutil/thirdparty/jackson/*.jar")
|
||||
|
||||
set(CMAKE_JAVA_INCLUDE_PATH apriltags.jar ${JACKSON_JARS})
|
||||
|
||||
file(GLOB_RECURSE JAVA_SOURCES src/main/java/*.java)
|
||||
file(GLOB_RECURSE JAVA_RESOURCES src/main/native/resources/*.json)
|
||||
add_jar(apriltags_jar SOURCES ${JAVA_SOURCES} RESOURCES NAMESPACE "edu/wpi/first/apriltag" ${JAVA_RESOURCES} INCLUDE_JARS wpimath_jar OUTPUT_NAME apriltag)
|
||||
endif()
|
||||
|
||||
|
||||
GENERATE_RESOURCES(src/main/native/resources/edu/wpi/first/apriltag generated/main/cpp APRILTAG frc apriltags_resources_src)
|
||||
|
||||
file(GLOB_RECURSE apriltags_native_src src/main/native/cpp/*.cpp)
|
||||
|
||||
add_library(apriltags ${apriltags_native_src} ${apriltags_resources_src})
|
||||
set_target_properties(apriltags PROPERTIES DEBUG_POSTFIX "d")
|
||||
|
||||
set_property(TARGET apriltags PROPERTY FOLDER "libraries")
|
||||
target_compile_features(apriltags PUBLIC cxx_std_20)
|
||||
wpilib_target_warnings(apriltags)
|
||||
target_link_libraries(apriltags wpimath)
|
||||
|
||||
target_include_directories(apriltags PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
|
||||
$<INSTALL_INTERFACE:${include_dest}/apriltags>)
|
||||
|
||||
|
||||
if (WITH_TESTS)
|
||||
wpilib_add_test(apriltags src/test/native/cpp)
|
||||
target_include_directories(apriltags_test PRIVATE src/test/native/include)
|
||||
target_link_libraries(apriltags_test apriltags gmock_main)
|
||||
endif()
|
||||
77
apriltag/build.gradle
Normal file
77
apriltag/build.gradle
Normal file
@@ -0,0 +1,77 @@
|
||||
apply from: "${rootDir}/shared/resources.gradle"
|
||||
|
||||
ext {
|
||||
nativeName = 'apriltags'
|
||||
devMain = 'edu.wpi.first.apriltag.DevMain'
|
||||
|
||||
def generateTask = createGenerateResourcesTask('main', 'APRILTAGS', 'frc', project)
|
||||
|
||||
tasks.withType(CppCompile) {
|
||||
dependsOn generateTask
|
||||
}
|
||||
extraSetup = {
|
||||
it.sources {
|
||||
resourcesCpp(CppSourceSet) {
|
||||
source {
|
||||
srcDirs "$buildDir/generated/main/cpp", "$rootDir/shared/singlelib"
|
||||
include '*.cpp'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
evaluationDependsOn(':wpimath')
|
||||
|
||||
apply from: "${rootDir}/shared/javacpp/setupBuild.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation project(':wpimath')
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
resources {
|
||||
srcDirs 'src/main/native/resources'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
model {
|
||||
components {}
|
||||
binaries {
|
||||
all {
|
||||
if (!it.buildable || !(it instanceof NativeBinarySpec)) {
|
||||
return
|
||||
}
|
||||
it.cppCompiler.define 'WPILIB_EXPORTS'
|
||||
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
lib project: ':wpimath', library: 'wpimath', linkage: 'shared'
|
||||
|
||||
if ((it instanceof NativeExecutableBinarySpec || it instanceof GoogleTestTestSuiteBinarySpec) && it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
|
||||
nativeUtils.useRequiredLibrary(it, 'ni_link_libraries', 'ni_runtime_libraries')
|
||||
}
|
||||
}
|
||||
}
|
||||
tasks {
|
||||
def c = $.components
|
||||
def found = false
|
||||
def systemArch = getCurrentArch()
|
||||
c.each {
|
||||
if (it in NativeExecutableSpec && it.name == "${nativeName}Dev") {
|
||||
it.binaries.each {
|
||||
if (!found) {
|
||||
def arch = it.targetPlatform.name
|
||||
if (arch == systemArch) {
|
||||
def filePath = it.tasks.install.installDirectory.get().toString() + File.separatorChar + 'lib'
|
||||
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
apriltag/src/dev/java/edu/wpi/first/apriltag/DevMain.java
Normal file
14
apriltag/src/dev/java/edu/wpi/first/apriltag/DevMain.java
Normal file
@@ -0,0 +1,14 @@
|
||||
// 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;
|
||||
|
||||
public final class DevMain {
|
||||
/** Main entry point. */
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello World!");
|
||||
}
|
||||
|
||||
private DevMain() {}
|
||||
}
|
||||
5
apriltag/src/dev/native/cpp/main.cpp
Normal file
5
apriltag/src/dev/native/cpp/main.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
// 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.
|
||||
|
||||
int main() {}
|
||||
47
apriltag/src/main/java/edu/wpi/first/apriltag/AprilTag.java
Normal file
47
apriltag/src/main/java/edu/wpi/first/apriltag/AprilTag.java
Normal file
@@ -0,0 +1,47 @@
|
||||
// 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 com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import edu.wpi.first.math.geometry.Pose3d;
|
||||
import java.util.Objects;
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
public class AprilTag {
|
||||
@JsonProperty(value = "ID")
|
||||
public int ID;
|
||||
|
||||
@JsonProperty(value = "pose")
|
||||
public Pose3d pose;
|
||||
|
||||
@SuppressWarnings("ParameterName")
|
||||
@JsonCreator
|
||||
public AprilTag(
|
||||
@JsonProperty(required = true, value = "ID") int ID,
|
||||
@JsonProperty(required = true, value = "pose") Pose3d pose) {
|
||||
this.ID = ID;
|
||||
this.pose = pose;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof AprilTag) {
|
||||
var other = (AprilTag) obj;
|
||||
return ID == other.ID && pose.equals(other.pose);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(ID, pose);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AprilTag(ID: " + ID + ", pose: " + pose + ")";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
// 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 com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import edu.wpi.first.math.geometry.Pose3d;
|
||||
import edu.wpi.first.math.geometry.Rotation3d;
|
||||
import edu.wpi.first.math.geometry.Translation3d;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Class for representing a layout of AprilTags on a field and reading them from a JSON format.
|
||||
*
|
||||
* <p>The JSON format contains two top-level objects, "tags" and "field". The "tags" object is a
|
||||
* list of all AprilTags contained within a layout. Each AprilTag serializes to a JSON object
|
||||
* containing an ID and a Pose3d. The "field" object is a descriptor of the size of the field in
|
||||
* meters with "width" and "length" values. This is to account for arbitrary field sizes when
|
||||
* transforming the poses.
|
||||
*
|
||||
* <p>Pose3ds are assumed to be measured from the bottom-left corner of the field, when the blue
|
||||
* alliance is at the left. By default, Pose3ds will be returned as declared when calling {@link
|
||||
* AprilTagFieldLayout#getTagPose(int)}. {@link #setOrigin(OriginPosition)} can be used to transform
|
||||
* the poses returned from {@link AprilTagFieldLayout#getTagPose(int)} to be correct relative to a
|
||||
* different coordinate frame.
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
public class AprilTagFieldLayout {
|
||||
public enum OriginPosition {
|
||||
kBlueAllianceWallRightSide,
|
||||
kRedAllianceWallRightSide,
|
||||
}
|
||||
|
||||
private final Map<Integer, AprilTag> m_apriltags = new HashMap<>();
|
||||
|
||||
@JsonProperty(value = "field")
|
||||
private FieldDimensions m_fieldDimensions;
|
||||
|
||||
private Pose3d m_origin;
|
||||
|
||||
/**
|
||||
* Construct a new AprilTagFieldLayout with values imported from a JSON file.
|
||||
*
|
||||
* @param path Path of the JSON file to import from.
|
||||
* @throws IOException If reading from the file fails.
|
||||
*/
|
||||
public AprilTagFieldLayout(String path) throws IOException {
|
||||
this(Path.of(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new AprilTagFieldLayout with values imported from a JSON file.
|
||||
*
|
||||
* @param path Path of the JSON file to import from.
|
||||
* @throws IOException If reading from the file fails.
|
||||
*/
|
||||
public AprilTagFieldLayout(Path path) throws IOException {
|
||||
AprilTagFieldLayout layout =
|
||||
new ObjectMapper().readValue(path.toFile(), AprilTagFieldLayout.class);
|
||||
m_apriltags.putAll(layout.m_apriltags);
|
||||
m_fieldDimensions = layout.m_fieldDimensions;
|
||||
setOrigin(OriginPosition.kBlueAllianceWallRightSide);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new AprilTagFieldLayout from a list of {@link AprilTag} objects.
|
||||
*
|
||||
* @param apriltags List of {@link AprilTag}.
|
||||
* @param fieldLength Length of the field the layout is representing in meters.
|
||||
* @param fieldWidth Width of the field the layout is representing in meters.
|
||||
*/
|
||||
public AprilTagFieldLayout(List<AprilTag> apriltags, double fieldLength, double fieldWidth) {
|
||||
this(apriltags, new FieldDimensions(fieldLength, fieldWidth));
|
||||
}
|
||||
|
||||
@JsonCreator
|
||||
private AprilTagFieldLayout(
|
||||
@JsonProperty(required = true, value = "tags") List<AprilTag> apriltags,
|
||||
@JsonProperty(required = true, value = "field") FieldDimensions fieldDimensions) {
|
||||
// To ensure the underlying semantics don't change with what kind of list is passed in
|
||||
for (AprilTag tag : apriltags) {
|
||||
m_apriltags.put(tag.ID, tag);
|
||||
}
|
||||
m_fieldDimensions = fieldDimensions;
|
||||
setOrigin(OriginPosition.kBlueAllianceWallRightSide);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a List of the {@link AprilTag AprilTags} used in this layout.
|
||||
*
|
||||
* @return The {@link AprilTag AprilTags} used in this layout.
|
||||
*/
|
||||
@JsonProperty("tags")
|
||||
public List<AprilTag> getTags() {
|
||||
return new ArrayList<>(m_apriltags.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the origin based on a predefined enumeration of coordinate frame origins. The origins are
|
||||
* calculated from the field dimensions.
|
||||
*
|
||||
* <p>This transforms the Pose3ds returned by {@link #getTagPose(int)} to return the correct pose
|
||||
* relative to a predefined coordinate frame.
|
||||
*
|
||||
* @param origin The predefined origin
|
||||
*/
|
||||
@JsonIgnore
|
||||
public void setOrigin(OriginPosition origin) {
|
||||
switch (origin) {
|
||||
case kBlueAllianceWallRightSide:
|
||||
setOrigin(new Pose3d());
|
||||
break;
|
||||
case kRedAllianceWallRightSide:
|
||||
setOrigin(
|
||||
new Pose3d(
|
||||
new Translation3d(m_fieldDimensions.fieldLength, m_fieldDimensions.fieldWidth, 0),
|
||||
new Rotation3d(0, 0, Math.PI)));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported enum value");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the origin for tag pose transformation.
|
||||
*
|
||||
* <p>This transforms the Pose3ds returned by {@link #getTagPose(int)} to return the correct pose
|
||||
* relative to the provided origin.
|
||||
*
|
||||
* @param origin The new origin for tag transformations
|
||||
*/
|
||||
@JsonIgnore
|
||||
public void setOrigin(Pose3d origin) {
|
||||
m_origin = origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an AprilTag pose by its ID.
|
||||
*
|
||||
* @param ID The ID of the tag.
|
||||
* @return The pose corresponding to the ID passed in or an empty optional if a tag with that ID
|
||||
* was not found.
|
||||
*/
|
||||
@SuppressWarnings("ParameterName")
|
||||
public Optional<Pose3d> getTagPose(int ID) {
|
||||
AprilTag tag = m_apriltags.get(ID);
|
||||
if (tag == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(tag.pose.relativeTo(m_origin));
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a AprilTagFieldLayout to a JSON file.
|
||||
*
|
||||
* @param path The path to write to.
|
||||
* @throws IOException If writing to the file fails.
|
||||
*/
|
||||
public void serialize(String path) throws IOException {
|
||||
serialize(Path.of(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a AprilTagFieldLayout to a JSON file.
|
||||
*
|
||||
* @param path The path to write to.
|
||||
* @throws IOException If writing to the file fails.
|
||||
*/
|
||||
public void serialize(Path path) throws IOException {
|
||||
new ObjectMapper().writeValue(path.toFile(), this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a field layout from a resource within a jar file.
|
||||
*
|
||||
* @param resourcePath The absolute path of the resource
|
||||
* @return The deserialized layout
|
||||
* @throws IOException If the resource could not be loaded
|
||||
*/
|
||||
public static AprilTagFieldLayout loadFromResource(String resourcePath) throws IOException {
|
||||
try (InputStream stream = AprilTagFieldLayout.class.getResourceAsStream(resourcePath);
|
||||
InputStreamReader reader = new InputStreamReader(stream)) {
|
||||
return new ObjectMapper().readerFor(AprilTagFieldLayout.class).readValue(reader);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof AprilTagFieldLayout) {
|
||||
var other = (AprilTagFieldLayout) obj;
|
||||
return m_apriltags.equals(other.m_apriltags) && m_origin.equals(other.m_origin);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(m_apriltags, m_origin);
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
private static class FieldDimensions {
|
||||
@SuppressWarnings("MemberName")
|
||||
@JsonProperty(value = "length")
|
||||
public double fieldLength;
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
@JsonProperty(value = "width")
|
||||
public double fieldWidth;
|
||||
|
||||
@JsonCreator()
|
||||
FieldDimensions(
|
||||
@JsonProperty(required = true, value = "length") double fieldLength,
|
||||
@JsonProperty(required = true, value = "width") double fieldWidth) {
|
||||
this.fieldLength = fieldLength;
|
||||
this.fieldWidth = fieldWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// 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;
|
||||
|
||||
public enum AprilTagFields {
|
||||
k2022RapidReact("2022-rapidreact.json");
|
||||
|
||||
public static final String kBaseResourceDir = "/edu/wpi/first/apriltag/";
|
||||
|
||||
/** Alias to the current game. */
|
||||
public static final AprilTagFields kDefaultField = k2022RapidReact;
|
||||
|
||||
public final String m_resourceFile;
|
||||
|
||||
AprilTagFields(String resourceFile) {
|
||||
m_resourceFile = kBaseResourceDir + resourceFile;
|
||||
}
|
||||
}
|
||||
26
apriltag/src/main/native/cpp/AprilTag.cpp
Normal file
26
apriltag/src/main/native/cpp/AprilTag.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "frc/apriltag/AprilTag.h"
|
||||
|
||||
#include <wpi/json.h>
|
||||
|
||||
using namespace frc;
|
||||
|
||||
bool AprilTag::operator==(const AprilTag& other) const {
|
||||
return ID == other.ID && pose == other.pose;
|
||||
}
|
||||
|
||||
bool AprilTag::operator!=(const AprilTag& other) const {
|
||||
return !operator==(other);
|
||||
}
|
||||
|
||||
void frc::to_json(wpi::json& json, const AprilTag& apriltag) {
|
||||
json = wpi::json{{"ID", apriltag.ID}, {"pose", apriltag.pose}};
|
||||
}
|
||||
|
||||
void frc::from_json(const wpi::json& json, AprilTag& apriltag) {
|
||||
apriltag.ID = json.at("ID").get<int>();
|
||||
apriltag.pose = json.at("pose").get<Pose3d>();
|
||||
}
|
||||
117
apriltag/src/main/native/cpp/AprilTagFieldLayout.cpp
Normal file
117
apriltag/src/main/native/cpp/AprilTagFieldLayout.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
// 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/AprilTagFieldLayout.h"
|
||||
|
||||
#include <system_error>
|
||||
|
||||
#include <units/angle.h>
|
||||
#include <units/length.h>
|
||||
#include <wpi/json.h>
|
||||
#include <wpi/raw_istream.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
using namespace frc;
|
||||
|
||||
AprilTagFieldLayout::AprilTagFieldLayout(std::string_view path) {
|
||||
std::error_code error_code;
|
||||
|
||||
wpi::raw_fd_istream input{path, error_code};
|
||||
if (error_code) {
|
||||
throw std::runtime_error(fmt::format("Cannot open file: {}", path));
|
||||
}
|
||||
|
||||
wpi::json json;
|
||||
input >> json;
|
||||
|
||||
for (const auto& tag : json.at("tags").get<std::vector<AprilTag>>()) {
|
||||
m_apriltags[tag.ID] = tag;
|
||||
}
|
||||
m_fieldWidth = units::meter_t{json.at("field").at("width").get<double>()};
|
||||
m_fieldLength = units::meter_t{json.at("field").at("height").get<double>()};
|
||||
}
|
||||
|
||||
AprilTagFieldLayout::AprilTagFieldLayout(std::vector<AprilTag> apriltags,
|
||||
units::meter_t fieldLength,
|
||||
units::meter_t fieldWidth)
|
||||
: m_fieldLength(std::move(fieldLength)),
|
||||
m_fieldWidth(std::move(fieldWidth)) {
|
||||
for (const auto& tag : apriltags) {
|
||||
m_apriltags[tag.ID] = tag;
|
||||
}
|
||||
}
|
||||
|
||||
void AprilTagFieldLayout::SetOrigin(OriginPosition origin) {
|
||||
switch (origin) {
|
||||
case OriginPosition::kBlueAllianceWallRightSide:
|
||||
SetOrigin(Pose3d{});
|
||||
break;
|
||||
case OriginPosition::kRedAllianceWallRightSide:
|
||||
SetOrigin(Pose3d{Translation3d{m_fieldLength, m_fieldWidth, 0_m},
|
||||
Rotation3d{0_deg, 0_deg, 180_deg}});
|
||||
break;
|
||||
default:
|
||||
throw std::invalid_argument("Invalid origin");
|
||||
}
|
||||
}
|
||||
|
||||
void AprilTagFieldLayout::SetOrigin(const Pose3d& origin) {
|
||||
m_origin = origin;
|
||||
}
|
||||
|
||||
std::optional<frc::Pose3d> AprilTagFieldLayout::GetTagPose(int ID) const {
|
||||
const auto& it = m_apriltags.find(ID);
|
||||
if (it == m_apriltags.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return it->second.pose.RelativeTo(m_origin);
|
||||
}
|
||||
|
||||
void AprilTagFieldLayout::Serialize(std::string_view path) {
|
||||
std::error_code error_code;
|
||||
|
||||
wpi::raw_fd_ostream output{path, error_code};
|
||||
if (error_code) {
|
||||
throw std::runtime_error(fmt::format("Cannot open file: {}", path));
|
||||
}
|
||||
|
||||
wpi::json json = *this;
|
||||
output << json;
|
||||
output.flush();
|
||||
}
|
||||
|
||||
bool AprilTagFieldLayout::operator==(const AprilTagFieldLayout& other) const {
|
||||
return m_apriltags == other.m_apriltags && m_origin == other.m_origin &&
|
||||
m_fieldLength == other.m_fieldLength &&
|
||||
m_fieldWidth == other.m_fieldWidth;
|
||||
}
|
||||
|
||||
bool AprilTagFieldLayout::operator!=(const AprilTagFieldLayout& other) const {
|
||||
return !operator==(other);
|
||||
}
|
||||
|
||||
void frc::to_json(wpi::json& json, const AprilTagFieldLayout& layout) {
|
||||
std::vector<AprilTag> tagVector;
|
||||
tagVector.reserve(layout.m_apriltags.size());
|
||||
for (const auto& pair : layout.m_apriltags) {
|
||||
tagVector.push_back(pair.second);
|
||||
}
|
||||
|
||||
json = wpi::json{{"field",
|
||||
{{"length", layout.m_fieldLength.value()},
|
||||
{"width", layout.m_fieldWidth.value()}}},
|
||||
{"tags", tagVector}};
|
||||
}
|
||||
|
||||
void frc::from_json(const wpi::json& json, AprilTagFieldLayout& layout) {
|
||||
layout.m_apriltags.clear();
|
||||
for (const auto& tag : json.at("tags").get<std::vector<AprilTag>>()) {
|
||||
layout.m_apriltags[tag.ID] = tag;
|
||||
}
|
||||
|
||||
layout.m_fieldLength =
|
||||
units::meter_t{json.at("field").at("length").get<double>()};
|
||||
layout.m_fieldWidth =
|
||||
units::meter_t{json.at("field").at("width").get<double>()};
|
||||
}
|
||||
28
apriltag/src/main/native/cpp/AprilTagFields.cpp
Normal file
28
apriltag/src/main/native/cpp/AprilTagFields.cpp
Normal file
@@ -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.
|
||||
|
||||
#include "frc/apriltag/AprilTagFields.h"
|
||||
|
||||
#include <wpi/json.h>
|
||||
|
||||
namespace frc {
|
||||
|
||||
// C++ generated from resource files
|
||||
std::string_view GetResource_2022_rapidreact_json();
|
||||
|
||||
AprilTagFieldLayout LoadAprilTagLayoutField(AprilTagField field) {
|
||||
std::string_view fieldString;
|
||||
switch (field) {
|
||||
case AprilTagField::k2022RapidReact:
|
||||
fieldString = GetResource_2022_rapidreact_json();
|
||||
break;
|
||||
case AprilTagField::kNumFields:
|
||||
throw std::invalid_argument("Invalid Field");
|
||||
}
|
||||
|
||||
wpi::json json = wpi::json::parse(fieldString);
|
||||
return json.get<AprilTagFieldLayout>();
|
||||
}
|
||||
|
||||
} // namespace frc
|
||||
45
apriltag/src/main/native/include/frc/apriltag/AprilTag.h
Normal file
45
apriltag/src/main/native/include/frc/apriltag/AprilTag.h
Normal file
@@ -0,0 +1,45 @@
|
||||
// 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 <wpi/SymbolExports.h>
|
||||
|
||||
#include "frc/geometry/Pose3d.h"
|
||||
|
||||
namespace wpi {
|
||||
class json;
|
||||
} // namespace wpi
|
||||
|
||||
namespace frc {
|
||||
|
||||
struct WPILIB_DLLEXPORT AprilTag {
|
||||
int ID;
|
||||
|
||||
Pose3d pose;
|
||||
|
||||
/**
|
||||
* Checks equality between this AprilTag and another object.
|
||||
*
|
||||
* @param other The other object.
|
||||
* @return Whether the two objects are equal.
|
||||
*/
|
||||
bool operator==(const AprilTag& other) const;
|
||||
|
||||
/**
|
||||
* Checks inequality between this AprilTag and another object.
|
||||
*
|
||||
* @param other The other object.
|
||||
* @return Whether the two objects are not equal.
|
||||
*/
|
||||
bool operator!=(const AprilTag& other) const;
|
||||
};
|
||||
|
||||
WPILIB_DLLEXPORT
|
||||
void to_json(wpi::json& json, const AprilTag& apriltag);
|
||||
|
||||
WPILIB_DLLEXPORT
|
||||
void from_json(const wpi::json& json, AprilTag& apriltag);
|
||||
|
||||
} // namespace frc
|
||||
@@ -0,0 +1,139 @@
|
||||
// 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 <optional>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <units/length.h>
|
||||
#include <wpi/SymbolExports.h>
|
||||
|
||||
#include "frc/apriltag/AprilTag.h"
|
||||
#include "frc/geometry/Pose3d.h"
|
||||
|
||||
namespace wpi {
|
||||
class json;
|
||||
} // namespace wpi
|
||||
|
||||
namespace frc {
|
||||
/**
|
||||
* Class for representing a layout of AprilTags on a field and reading them from
|
||||
* a JSON format.
|
||||
*
|
||||
* The JSON format contains two top-level objects, "tags" and "field".
|
||||
* The "tags" object is a list of all AprilTags contained within a layout. Each
|
||||
* AprilTag serializes to a JSON object containing an ID and a Pose3d. The
|
||||
* "field" object is a descriptor of the size of the field in meters with
|
||||
* "width" and "length" values. This is to account for arbitrary field sizes
|
||||
* when transforming the poses.
|
||||
*
|
||||
* Pose3ds are assumed to be measured from the bottom-left corner of the field,
|
||||
* when the blue alliance is at the left. By default, Pose3ds will be returned
|
||||
* as declared when calling GetTagPose(int).
|
||||
* SetOrigin(AprilTagFieldLayout::OriginPosition) can be used to transform the
|
||||
* poses returned by GetTagPose(int) to be correct relative to a different
|
||||
* coordinate frame.
|
||||
*/
|
||||
class WPILIB_DLLEXPORT AprilTagFieldLayout {
|
||||
public:
|
||||
enum class OriginPosition {
|
||||
kBlueAllianceWallRightSide,
|
||||
kRedAllianceWallRightSide,
|
||||
};
|
||||
|
||||
AprilTagFieldLayout() = default;
|
||||
|
||||
/**
|
||||
* Construct a new AprilTagFieldLayout with values imported from a JSON file.
|
||||
*
|
||||
* @param path Path of the JSON file to import from.
|
||||
*/
|
||||
explicit AprilTagFieldLayout(std::string_view path);
|
||||
|
||||
/**
|
||||
* Construct a new AprilTagFieldLayout from a vector of AprilTag objects.
|
||||
*
|
||||
* @param apriltags Vector of AprilTags.
|
||||
* @param fieldLength Length of field the layout is representing.
|
||||
* @param fieldWidth Width of field the layout is representing.
|
||||
*/
|
||||
AprilTagFieldLayout(std::vector<AprilTag> apriltags,
|
||||
units::meter_t fieldLength, units::meter_t fieldWidth);
|
||||
|
||||
/**
|
||||
* Sets the origin based on a predefined enumeration of coordinate frame
|
||||
* origins. The origins are calculated from the field dimensions.
|
||||
*
|
||||
* This transforms the Pose3ds returned by GetTagPose(int) to return the
|
||||
* correct pose relative to a predefined coordinate frame.
|
||||
*
|
||||
* @param origin The predefined origin
|
||||
*/
|
||||
void SetOrigin(OriginPosition origin);
|
||||
|
||||
/**
|
||||
* Sets the origin for tag pose transformation.
|
||||
*
|
||||
* This tranforms the Pose3ds returned by GetTagPose(int) to return the
|
||||
* correct pose relative to the provided origin.
|
||||
*
|
||||
* @param origin The new origin for tag transformations
|
||||
*/
|
||||
void SetOrigin(const Pose3d& origin);
|
||||
|
||||
/**
|
||||
* Gets an AprilTag pose by its ID.
|
||||
*
|
||||
* @param ID The ID of the tag.
|
||||
* @return The pose corresponding to the ID that was passed in or an empty
|
||||
* optional if a tag with that ID is not found.
|
||||
*/
|
||||
std::optional<Pose3d> GetTagPose(int ID) const;
|
||||
|
||||
/**
|
||||
* Serializes an AprilTagFieldLayout to a JSON file.
|
||||
*
|
||||
* @param path The path to write the JSON file to.
|
||||
*/
|
||||
void Serialize(std::string_view path);
|
||||
|
||||
/*
|
||||
* Checks equality between this AprilTagFieldLayout and another object.
|
||||
*
|
||||
* @param other The other object.
|
||||
* @return Whether the two objects are equal.
|
||||
*/
|
||||
bool operator==(const AprilTagFieldLayout& other) const;
|
||||
|
||||
/**
|
||||
* Checks inequality between this AprilTagFieldLayout and another object.
|
||||
*
|
||||
* @param other The other object.
|
||||
* @return Whether the two objects are not equal.
|
||||
*/
|
||||
bool operator!=(const AprilTagFieldLayout& other) const;
|
||||
|
||||
private:
|
||||
std::unordered_map<int, AprilTag> m_apriltags;
|
||||
units::meter_t m_fieldLength;
|
||||
units::meter_t m_fieldWidth;
|
||||
Pose3d m_origin;
|
||||
|
||||
friend WPILIB_DLLEXPORT void to_json(wpi::json& json,
|
||||
const AprilTagFieldLayout& layout);
|
||||
|
||||
friend WPILIB_DLLEXPORT void from_json(const wpi::json& json,
|
||||
AprilTagFieldLayout& layout);
|
||||
};
|
||||
|
||||
WPILIB_DLLEXPORT
|
||||
void to_json(wpi::json& json, const AprilTagFieldLayout& layout);
|
||||
|
||||
WPILIB_DLLEXPORT
|
||||
void from_json(const wpi::json& json, AprilTagFieldLayout& layout);
|
||||
|
||||
} // namespace frc
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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>
|
||||
|
||||
#include <wpi/SymbolExports.h>
|
||||
|
||||
#include "frc/apriltag/AprilTagFieldLayout.h"
|
||||
|
||||
namespace frc {
|
||||
|
||||
enum class AprilTagField {
|
||||
k2022RapidReact,
|
||||
|
||||
// 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,415 @@
|
||||
{
|
||||
"tags" : [ {
|
||||
"ID" : 0,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : -0.0035306,
|
||||
"y" : 7.578928199999999,
|
||||
"z" : 0.8858503999999999
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 1.0,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 1,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 3.2327088,
|
||||
"y" : 5.486654,
|
||||
"z" : 1.7254728
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 1.0,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 2,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 3.067812,
|
||||
"y" : 5.3305202,
|
||||
"z" : 1.3762228
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 0.7071067811865476,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : -0.7071067811865475
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 3,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 0.0039878,
|
||||
"y" : 5.058536999999999,
|
||||
"z" : 0.80645
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 1.0,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 4,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 0.0039878,
|
||||
"y" : 3.5124898,
|
||||
"z" : 0.80645
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 1.0,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 5,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 0.12110719999999998,
|
||||
"y" : 1.7178274,
|
||||
"z" : 0.8906002000000001
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 0.9196502204050923,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.39273842708457407
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 6,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 0.8733027999999999,
|
||||
"y" : 0.9412985999999999,
|
||||
"z" : 0.8906002000000001
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 0.9196502204050923,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.39273842708457407
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 7,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 1.6150844,
|
||||
"y" : 0.15725139999999999,
|
||||
"z" : 0.8906002000000001
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 0.9196502204050923,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.39273842708457407
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 10,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 16.4627306,
|
||||
"y" : 0.6506718,
|
||||
"z" : 0.8858503999999999
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 6.123233995736766E-17,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 11,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 13.2350002,
|
||||
"y" : 2.743454,
|
||||
"z" : 1.7254728
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 6.123233995736766E-17,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 12,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 13.391388000000001,
|
||||
"y" : 2.8998418,
|
||||
"z" : 1.3762228
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 0.7071067811865476,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.7071067811865475
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 13,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 16.4552122,
|
||||
"y" : 3.1755079999999998,
|
||||
"z" : 0.80645
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 6.123233995736766E-17,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 14,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 16.4552122,
|
||||
"y" : 4.7171356,
|
||||
"z" : 0.80645
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 6.123233995736766E-17,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 15,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 16.3350194,
|
||||
"y" : 6.5149729999999995,
|
||||
"z" : 0.8937752
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : -0.37298778257580906,
|
||||
"X" : -0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.9278362538989199
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 16,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 15.5904946,
|
||||
"y" : 7.292695599999999,
|
||||
"z" : 0.8906002000000001
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : -0.37298778257580906,
|
||||
"X" : -0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.9278362538989199
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 17,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 14.847188999999998,
|
||||
"y" : 8.0691228,
|
||||
"z" : 0.8906002000000001
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : -0.37298778257580906,
|
||||
"X" : -0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.9278362538989199
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 40,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 7.874127,
|
||||
"y" : 4.9131728,
|
||||
"z" : 0.7032752
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 0.5446390350150271,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.838670567945424
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 41,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 7.4312271999999995,
|
||||
"y" : 3.759327,
|
||||
"z" : 0.7032752
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : -0.20791169081775934,
|
||||
"X" : -0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.9781476007338057
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 42,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 8.585073,
|
||||
"y" : 3.3164272,
|
||||
"z" : 0.7032752
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 0.838670567945424,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : -0.5446390350150271
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 43,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 9.0279728,
|
||||
"y" : 4.470273,
|
||||
"z" : 0.7032752
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 0.9781476007338057,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.20791169081775934
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 50,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 7.6790296,
|
||||
"y" : 4.3261534,
|
||||
"z" : 2.4177244
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 0.17729273396782605,
|
||||
"X" : -0.22744989571511945,
|
||||
"Y" : 0.04215534644161733,
|
||||
"Z" : 0.9565859910053995
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 51,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 8.0182466,
|
||||
"y" : 3.5642296,
|
||||
"z" : 2.4177244
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : -0.5510435465842192,
|
||||
"X" : -0.19063969497246985,
|
||||
"Y" : -0.13102303230819815,
|
||||
"Z" : 0.8017733354717242
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 52,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 8.7801704,
|
||||
"y" : 3.9034466,
|
||||
"z" : 2.4177244
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : -0.9565859910053994,
|
||||
"X" : -0.04215534644161739,
|
||||
"Y" : -0.22744989571511942,
|
||||
"Z" : 0.17729273396782633
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 53,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 8.4409534,
|
||||
"y" : 4.6653704,
|
||||
"z" : 2.4177244
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 0.8017733354717241,
|
||||
"X" : -0.1310230323081982,
|
||||
"Y" : 0.19063969497246983,
|
||||
"Z" : 0.5510435465842194
|
||||
}
|
||||
}
|
||||
}
|
||||
} ],
|
||||
"field" : {
|
||||
"length" : 8.2296,
|
||||
"width" : 16.4592
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.apriltag;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import edu.wpi.first.math.geometry.Pose3d;
|
||||
import edu.wpi.first.math.geometry.Rotation3d;
|
||||
import edu.wpi.first.math.geometry.Translation3d;
|
||||
import edu.wpi.first.math.util.Units;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class AprilTagPoseSetOriginTest {
|
||||
@Test
|
||||
void transformationMatches() {
|
||||
var layout =
|
||||
new AprilTagFieldLayout(
|
||||
List.of(
|
||||
new AprilTag(1, new Pose3d(new Translation3d(0, 0, 0), new Rotation3d(0, 0, 0))),
|
||||
new AprilTag(
|
||||
2,
|
||||
new Pose3d(
|
||||
new Translation3d(
|
||||
Units.feetToMeters(4.0), Units.feetToMeters(4), Units.feetToMeters(4)),
|
||||
new Rotation3d(0, 0, Units.degreesToRadians(180))))),
|
||||
Units.feetToMeters(54.0),
|
||||
Units.feetToMeters(27.0));
|
||||
layout.setOrigin(AprilTagFieldLayout.OriginPosition.kRedAllianceWallRightSide);
|
||||
|
||||
assertEquals(
|
||||
new Pose3d(
|
||||
new Translation3d(Units.feetToMeters(54.0), Units.feetToMeters(27.0), 0.0),
|
||||
new Rotation3d(0.0, 0.0, Units.degreesToRadians(180.0))),
|
||||
layout.getTagPose(1).orElse(null));
|
||||
|
||||
assertEquals(
|
||||
new Pose3d(
|
||||
new Translation3d(
|
||||
Units.feetToMeters(50.0), Units.feetToMeters(23.0), Units.feetToMeters(4)),
|
||||
new Rotation3d(0.0, 0.0, 0)),
|
||||
layout.getTagPose(2).orElse(null));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// 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.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import edu.wpi.first.math.geometry.Pose3d;
|
||||
import edu.wpi.first.math.geometry.Rotation3d;
|
||||
import edu.wpi.first.math.util.Units;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class AprilTagSerializationTest {
|
||||
@Test
|
||||
void deserializeMatches() {
|
||||
var layout =
|
||||
new AprilTagFieldLayout(
|
||||
List.of(
|
||||
new AprilTag(1, new Pose3d(0, 0, 0, new Rotation3d(0, 0, 0))),
|
||||
new AprilTag(3, new Pose3d(0, 1, 0, new Rotation3d(0, 0, 0)))),
|
||||
Units.feetToMeters(54.0),
|
||||
Units.feetToMeters(27.0));
|
||||
|
||||
var objectMapper = new ObjectMapper();
|
||||
|
||||
var deserialized =
|
||||
assertDoesNotThrow(
|
||||
() ->
|
||||
objectMapper.readValue(
|
||||
objectMapper.writeValueAsString(layout), AprilTagFieldLayout.class));
|
||||
|
||||
assertEquals(layout, deserialized);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// 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 static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import edu.wpi.first.math.geometry.Pose3d;
|
||||
import edu.wpi.first.math.geometry.Rotation3d;
|
||||
import edu.wpi.first.math.util.Units;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
|
||||
class LoadConfigTest {
|
||||
@ParameterizedTest
|
||||
@EnumSource(AprilTagFields.class)
|
||||
void testLoad(AprilTagFields field) {
|
||||
AprilTagFieldLayout layout =
|
||||
Assertions.assertDoesNotThrow(
|
||||
() -> AprilTagFieldLayout.loadFromResource(field.m_resourceFile));
|
||||
assertNotNull(layout);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test2022RapidReact() throws IOException {
|
||||
AprilTagFieldLayout layout =
|
||||
AprilTagFieldLayout.loadFromResource(AprilTagFields.k2022RapidReact.m_resourceFile);
|
||||
|
||||
// Blue Hangar Truss - Hub
|
||||
Pose3d expectedPose =
|
||||
new Pose3d(
|
||||
Units.inchesToMeters(127.272),
|
||||
Units.inchesToMeters(216.01),
|
||||
Units.inchesToMeters(67.932),
|
||||
new Rotation3d(0, 0, 0));
|
||||
Optional<Pose3d> maybePose = layout.getTagPose(1);
|
||||
assertTrue(maybePose.isPresent());
|
||||
assertEquals(expectedPose, maybePose.get());
|
||||
|
||||
// Blue Terminal Near Station
|
||||
expectedPose =
|
||||
new Pose3d(
|
||||
Units.inchesToMeters(4.768),
|
||||
Units.inchesToMeters(67.631),
|
||||
Units.inchesToMeters(35.063),
|
||||
new Rotation3d(0, 0, Math.toRadians(46.25)));
|
||||
maybePose = layout.getTagPose(5);
|
||||
assertTrue(maybePose.isPresent());
|
||||
assertEquals(expectedPose, maybePose.get());
|
||||
|
||||
// Upper Hub Blue-Near
|
||||
expectedPose =
|
||||
new Pose3d(
|
||||
Units.inchesToMeters(332.321),
|
||||
Units.inchesToMeters(183.676),
|
||||
Units.inchesToMeters(95.186),
|
||||
new Rotation3d(0, Math.toRadians(26.75), Math.toRadians(69)));
|
||||
maybePose = layout.getTagPose(53);
|
||||
assertTrue(maybePose.isPresent());
|
||||
assertEquals(expectedPose, maybePose.get());
|
||||
|
||||
// Doesn't exist
|
||||
maybePose = layout.getTagPose(54);
|
||||
assertFalse(maybePose.isPresent());
|
||||
}
|
||||
}
|
||||
27
apriltag/src/test/native/cpp/AprilTagJsonTest.cpp
Normal file
27
apriltag/src/test/native/cpp/AprilTagJsonTest.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
// 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 <vector>
|
||||
|
||||
#include <wpi/json.h>
|
||||
|
||||
#include "frc/apriltag/AprilTag.h"
|
||||
#include "frc/apriltag/AprilTagFieldLayout.h"
|
||||
#include "frc/geometry/Pose3d.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
using namespace frc;
|
||||
|
||||
TEST(AprilTagJsonTest, DeserializeMatches) {
|
||||
auto layout = AprilTagFieldLayout{
|
||||
std::vector{
|
||||
AprilTag{1, Pose3d{}},
|
||||
AprilTag{3, Pose3d{0_m, 1_m, 0_m, Rotation3d{0_deg, 0_deg, 0_deg}}}},
|
||||
54_ft, 27_ft};
|
||||
|
||||
AprilTagFieldLayout deserialized;
|
||||
wpi::json json = layout;
|
||||
EXPECT_NO_THROW(deserialized = json.get<AprilTagFieldLayout>());
|
||||
EXPECT_EQ(layout, deserialized);
|
||||
}
|
||||
33
apriltag/src/test/native/cpp/AprilTagPoseSetOriginTest.cpp
Normal file
33
apriltag/src/test/native/cpp/AprilTagPoseSetOriginTest.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
// 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 <vector>
|
||||
|
||||
#include <wpi/json.h>
|
||||
|
||||
#include "frc/apriltag/AprilTag.h"
|
||||
#include "frc/apriltag/AprilTagFieldLayout.h"
|
||||
#include "frc/geometry/Pose3d.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
using namespace frc;
|
||||
|
||||
TEST(AprilTagPoseSetOriginTest, TransformationMatches) {
|
||||
auto layout = AprilTagFieldLayout{
|
||||
std::vector<AprilTag>{
|
||||
AprilTag{1,
|
||||
Pose3d{0_ft, 0_ft, 0_ft, Rotation3d{0_deg, 0_deg, 0_deg}}},
|
||||
AprilTag{
|
||||
2, Pose3d{4_ft, 4_ft, 4_ft, Rotation3d{0_deg, 0_deg, 180_deg}}}},
|
||||
54_ft, 27_ft};
|
||||
|
||||
layout.SetOrigin(
|
||||
AprilTagFieldLayout::OriginPosition::kRedAllianceWallRightSide);
|
||||
|
||||
auto mirrorPose =
|
||||
Pose3d{54_ft, 27_ft, 0_ft, Rotation3d{0_deg, 0_deg, 180_deg}};
|
||||
EXPECT_EQ(mirrorPose, *layout.GetTagPose(1));
|
||||
mirrorPose = Pose3d{50_ft, 23_ft, 4_ft, Rotation3d{0_deg, 0_deg, 0_deg}};
|
||||
EXPECT_EQ(mirrorPose, *layout.GetTagPose(2));
|
||||
}
|
||||
61
apriltag/src/test/native/cpp/LoadConfigTest.cpp
Normal file
61
apriltag/src/test/native/cpp/LoadConfigTest.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
// 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 "gtest/gtest.h"
|
||||
|
||||
namespace frc {
|
||||
|
||||
std::vector<AprilTagField> GetAllFields() {
|
||||
std::vector<AprilTagField> output;
|
||||
|
||||
for (int i = 0; i < static_cast<int>(AprilTagField::kNumFields); ++i) {
|
||||
output.push_back(static_cast<AprilTagField>(i));
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
TEST(AprilTagFieldsTest, TestLoad2022RapidReact) {
|
||||
AprilTagFieldLayout layout =
|
||||
LoadAprilTagLayoutField(AprilTagField::k2022RapidReact);
|
||||
|
||||
// Blue Hangar Truss - Hub
|
||||
auto expectedPose =
|
||||
Pose3d{127.272_in, 216.01_in, 67.932_in, Rotation3d{0_deg, 0_deg, 0_deg}};
|
||||
auto maybePose = layout.GetTagPose(1);
|
||||
EXPECT_TRUE(maybePose);
|
||||
EXPECT_EQ(expectedPose, *maybePose);
|
||||
|
||||
// Blue Terminal Near Station
|
||||
expectedPose = Pose3d{4.768_in, 67.631_in, 35.063_in,
|
||||
Rotation3d{0_deg, 0_deg, 46.25_deg}};
|
||||
maybePose = layout.GetTagPose(5);
|
||||
EXPECT_TRUE(maybePose);
|
||||
EXPECT_EQ(expectedPose, *maybePose);
|
||||
|
||||
// Upper Hub Blue-Near
|
||||
expectedPose = Pose3d{332.321_in, 183.676_in, 95.186_in,
|
||||
Rotation3d{0_deg, 26.75_deg, 69_deg}};
|
||||
maybePose = layout.GetTagPose(53);
|
||||
EXPECT_TRUE(maybePose);
|
||||
EXPECT_EQ(expectedPose, *maybePose);
|
||||
|
||||
// Doesn't exist
|
||||
maybePose = layout.GetTagPose(54);
|
||||
EXPECT_FALSE(maybePose);
|
||||
}
|
||||
|
||||
// Test all of the fields in the enum
|
||||
class AllFieldsFixtureTest : public ::testing::TestWithParam<AprilTagField> {};
|
||||
|
||||
TEST_P(AllFieldsFixtureTest, CheckEntireEnum) {
|
||||
AprilTagField field = GetParam();
|
||||
EXPECT_NO_THROW(LoadAprilTagLayoutField(field));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(ValuesEnumTestInstTests, AllFieldsFixtureTest,
|
||||
::testing::ValuesIn(GetAllFields()));
|
||||
|
||||
} // namespace frc
|
||||
@@ -2,12 +2,10 @@
|
||||
// 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 "networktables/RpcCall.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "networktables/NetworkTableEntry.h"
|
||||
|
||||
using namespace nt;
|
||||
|
||||
NetworkTableEntry RpcCall::GetEntry() const {
|
||||
return NetworkTableEntry{m_entry};
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
int ret = RUN_ALL_TESTS();
|
||||
return ret;
|
||||
}
|
||||
@@ -15,7 +15,7 @@ stages:
|
||||
vmImage: 'ubuntu-latest'
|
||||
|
||||
container:
|
||||
image: wpilib/roborio-cross-ubuntu:2022-18.04
|
||||
image: wpilib/roborio-cross-ubuntu:2023-22.04
|
||||
|
||||
timeoutInMinutes: 0
|
||||
|
||||
|
||||
19
build.gradle
19
build.gradle
@@ -13,10 +13,10 @@ buildscript {
|
||||
|
||||
plugins {
|
||||
id 'base'
|
||||
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '4.1.0'
|
||||
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '2023.0.0'
|
||||
id 'edu.wpi.first.wpilib.repositories.WPILibRepositoriesPlugin' version '2020.2'
|
||||
id 'edu.wpi.first.NativeUtils' apply false
|
||||
id 'edu.wpi.first.GradleJni' version '1.0.0'
|
||||
id 'edu.wpi.first.GradleJni' version '1.1.0'
|
||||
id 'edu.wpi.first.GradleVsCode'
|
||||
id 'idea'
|
||||
id 'visual-studio'
|
||||
@@ -108,6 +108,11 @@ subprojects {
|
||||
subproj.apply plugin: MultiBuilds
|
||||
}
|
||||
|
||||
plugins.withType(JavaPlugin) {
|
||||
sourceCompatibility = 11
|
||||
targetCompatibility = 11
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/shared/java/javastyle.gradle"
|
||||
|
||||
// Disables doclint in java 8.
|
||||
@@ -119,6 +124,10 @@ subprojects {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs.add '-XDstringConcat=inline'
|
||||
}
|
||||
|
||||
// Enables UTF-8 support in Javadoc
|
||||
tasks.withType(Javadoc) {
|
||||
options.addStringOption("charset", "utf-8")
|
||||
@@ -127,8 +136,10 @@ subprojects {
|
||||
}
|
||||
|
||||
// Sign outputs with Developer ID
|
||||
if (project.hasProperty("developerID")) {
|
||||
tasks.withType(AbstractLinkTask) { task ->
|
||||
tasks.withType(AbstractLinkTask) { task ->
|
||||
task.inputs.property "HasDeveloperId", project.hasProperty("developerID")
|
||||
|
||||
if (project.hasProperty("developerID")) {
|
||||
// Don't sign any executables because codesign complains
|
||||
// about relative rpath.
|
||||
if (!(task instanceof LinkExecutable)) {
|
||||
|
||||
@@ -5,5 +5,5 @@ repositories {
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
implementation "edu.wpi.first:native-utils:2023.1.0"
|
||||
implementation "edu.wpi.first:native-utils:2023.8.0"
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import groovy.transform.CompileStatic
|
||||
import javax.inject.Inject
|
||||
import edu.wpi.first.deployutils.deploy.artifact.MavenArtifact
|
||||
import edu.wpi.first.deployutils.deploy.context.DeployContext
|
||||
import org.gradle.api.Project
|
||||
import edu.wpi.first.deployutils.ActionWrapper
|
||||
import edu.wpi.first.deployutils.deploy.target.RemoteTarget
|
||||
import edu.wpi.first.deployutils.PredicateWrapper
|
||||
import groovy.transform.CompileStatic;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import java.util.function.Function
|
||||
import org.gradle.api.Project;
|
||||
|
||||
import edu.wpi.first.deployutils.deploy.CommandDeployResult;
|
||||
import edu.wpi.first.deployutils.deploy.artifact.MavenArtifact;
|
||||
import edu.wpi.first.deployutils.deploy.context.DeployContext;
|
||||
import edu.wpi.first.deployutils.deploy.target.RemoteTarget;
|
||||
import edu.wpi.first.deployutils.PredicateWrapper;
|
||||
import edu.wpi.first.deployutils.ActionWrapper;
|
||||
|
||||
@CompileStatic
|
||||
public class WPIJREArtifact extends MavenArtifact {
|
||||
@@ -17,6 +18,18 @@ public class WPIJREArtifact extends MavenArtifact {
|
||||
return configName;
|
||||
}
|
||||
|
||||
public boolean isCheckJreVersion() {
|
||||
return checkJreVersion;
|
||||
}
|
||||
|
||||
public void setCheckJreVersion(boolean checkJreVersion) {
|
||||
this.checkJreVersion = checkJreVersion;
|
||||
}
|
||||
|
||||
private boolean checkJreVersion = true;
|
||||
|
||||
private final String artifactLocation = "edu.wpi.first.jdk:roborio-2023:17.0.5u7-1"
|
||||
|
||||
@Inject
|
||||
public WPIJREArtifact(String name, RemoteTarget target) {
|
||||
super(name, target);
|
||||
@@ -24,10 +37,10 @@ public class WPIJREArtifact extends MavenArtifact {
|
||||
this.configName = configName;
|
||||
Project project = target.getProject();
|
||||
getConfiguration().set(project.getConfigurations().create(configName));
|
||||
getDependency().set(project.getDependencies().add(configName, "edu.wpi.first.jdk:roborio-2022:11.0.12u5-1"));
|
||||
getDependency().set(project.getDependencies().add(configName, artifactLocation));
|
||||
|
||||
setOnlyIf(new PredicateWrapper({ DeployContext ctx ->
|
||||
return jreMissing(ctx) || project.hasProperty("force-redeploy-jre");
|
||||
return jreMissing(ctx) || jreOutOfDate(ctx) || project.hasProperty("force-redeploy-jre");
|
||||
}));
|
||||
|
||||
getDirectory().set("/tmp");
|
||||
@@ -35,7 +48,7 @@ public class WPIJREArtifact extends MavenArtifact {
|
||||
|
||||
getPostdeploy().add(new ActionWrapper({ DeployContext ctx ->
|
||||
ctx.getLogger().log("Installing JRE...");
|
||||
ctx.execute("opkg remove frc2022-openjdk*; opkg install /tmp/frcjre.ipk; rm /tmp/frcjre.ipk");
|
||||
ctx.execute("opkg remove frc*-openjdk*; opkg install /tmp/frcjre.ipk; rm /tmp/frcjre.ipk");
|
||||
ctx.getLogger().log("JRE Deployed!");
|
||||
}));
|
||||
}
|
||||
@@ -44,5 +57,21 @@ public class WPIJREArtifact extends MavenArtifact {
|
||||
return ctx.execute("if [[ -f \"/usr/local/frc/JRE/bin/java\" ]]; then echo OK; else echo MISSING; fi").getResult().contains("MISSING");
|
||||
}
|
||||
|
||||
|
||||
private boolean jreOutOfDate(DeployContext ctx) {
|
||||
if (!checkJreVersion) {
|
||||
return false;
|
||||
}
|
||||
String version = getDependency().get().getVersion();
|
||||
CommandDeployResult cmdResult = ctx.execute("opkg list-installed | grep openjdk");
|
||||
if (cmdResult.getExitCode() != 0) {
|
||||
ctx.getLogger().log("JRE not found");
|
||||
return false;
|
||||
}
|
||||
String result = cmdResult.getResult().trim();
|
||||
ctx.getLogger().log("Searching for JRE " + version);
|
||||
ctx.getLogger().log("Found JRE " + result);
|
||||
boolean matches = result.contains(version);
|
||||
ctx.getLogger().log(matches ? "JRE Is Correct Version" : "JRE is mismatched. Reinstalling");
|
||||
return !matches;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ model {
|
||||
if (!it.buildable || !(it instanceof NativeBinarySpec)) {
|
||||
return
|
||||
}
|
||||
lib project: ':ntcore', library: 'ntcore', linkage: 'shared'
|
||||
project(':ntcore').addNtcoreDependency(it, 'shared')
|
||||
lib project: ':cscore', library: 'cscore', linkage: 'shared'
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
|
||||
@@ -55,7 +55,7 @@ model {
|
||||
}
|
||||
binaries.all { binary ->
|
||||
lib project: ':cameraserver', library: 'cameraserver', linkage: 'static'
|
||||
lib project: ':ntcore', library: 'ntcore', linkage: 'static'
|
||||
project(':ntcore').addNtcoreDependency(binary, 'static')
|
||||
lib project: ':cscore', library: 'cscore', linkage: 'static'
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'static'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
|
||||
|
||||
@@ -175,7 +175,8 @@ public final class Main {
|
||||
ntinst.startServer();
|
||||
} else {
|
||||
System.out.println("Setting up NetworkTables client for team " + team);
|
||||
ntinst.startClientTeam(team);
|
||||
ntinst.setServerTeam(team);
|
||||
ntinst.startClient4("multicameraserver");
|
||||
}
|
||||
|
||||
// start cameras
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
@@ -182,7 +183,8 @@ int main(int argc, char* argv[]) {
|
||||
ntinst.StartServer();
|
||||
} else {
|
||||
fmt::print("Setting up NetworkTables client for team {}\n", team);
|
||||
ntinst.StartClientTeam(team);
|
||||
ntinst.StartClient4("multicameraserver");
|
||||
ntinst.SetServerTeam(team);
|
||||
}
|
||||
|
||||
// start cameras
|
||||
|
||||
@@ -15,13 +15,18 @@ 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.VideoProperty;
|
||||
import edu.wpi.first.cscore.VideoSink;
|
||||
import edu.wpi.first.cscore.VideoSource;
|
||||
import edu.wpi.first.networktables.EntryListenerFlags;
|
||||
import edu.wpi.first.networktables.BooleanEntry;
|
||||
import edu.wpi.first.networktables.BooleanPublisher;
|
||||
import edu.wpi.first.networktables.IntegerEntry;
|
||||
import edu.wpi.first.networktables.IntegerPublisher;
|
||||
import edu.wpi.first.networktables.NetworkTable;
|
||||
import edu.wpi.first.networktables.NetworkTableEntry;
|
||||
import edu.wpi.first.networktables.NetworkTableInstance;
|
||||
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 java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
@@ -38,11 +43,167 @@ public final class CameraServer {
|
||||
|
||||
private static final String kPublishName = "/CameraPublisher";
|
||||
|
||||
private static final class PropertyPublisher implements AutoCloseable {
|
||||
@SuppressWarnings({"PMD.MissingBreakInSwitch", "PMD.ImplicitSwitchFallThrough", "fallthrough"})
|
||||
PropertyPublisher(NetworkTable table, VideoEvent event) {
|
||||
String name;
|
||||
String infoName;
|
||||
if (event.name.startsWith("raw_")) {
|
||||
name = "RawProperty/" + event.name;
|
||||
infoName = "RawPropertyInfo/" + event.name;
|
||||
} else {
|
||||
name = "Property/" + event.name;
|
||||
infoName = "PropertyInfo/" + event.name;
|
||||
}
|
||||
|
||||
try {
|
||||
switch (event.propertyKind) {
|
||||
case kBoolean:
|
||||
m_booleanValueEntry = table.getBooleanTopic(name).getEntry(false);
|
||||
m_booleanValueEntry.setDefault(event.value != 0);
|
||||
break;
|
||||
case kEnum:
|
||||
m_choicesTopic = table.getStringArrayTopic(infoName + "/choices");
|
||||
// fall through
|
||||
case kInteger:
|
||||
m_integerValueEntry = table.getIntegerTopic(name).getEntry(0);
|
||||
m_minPublisher = table.getIntegerTopic(infoName + "/min").publish();
|
||||
m_maxPublisher = table.getIntegerTopic(infoName + "/max").publish();
|
||||
m_stepPublisher = table.getIntegerTopic(infoName + "/step").publish();
|
||||
m_defaultPublisher = table.getIntegerTopic(infoName + "/default").publish();
|
||||
|
||||
m_integerValueEntry.setDefault(event.value);
|
||||
m_minPublisher.set(CameraServerJNI.getPropertyMin(event.propertyHandle));
|
||||
m_maxPublisher.set(CameraServerJNI.getPropertyMax(event.propertyHandle));
|
||||
m_stepPublisher.set(CameraServerJNI.getPropertyStep(event.propertyHandle));
|
||||
m_defaultPublisher.set(CameraServerJNI.getPropertyDefault(event.propertyHandle));
|
||||
break;
|
||||
case kString:
|
||||
m_stringValueEntry = table.getStringTopic(name).getEntry("");
|
||||
m_stringValueEntry.setDefault(event.valueStr);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (VideoException ignored) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
void update(VideoEvent event) {
|
||||
switch (event.propertyKind) {
|
||||
case kBoolean:
|
||||
if (m_booleanValueEntry != null) {
|
||||
m_booleanValueEntry.set(event.value != 0);
|
||||
}
|
||||
break;
|
||||
case kInteger:
|
||||
case kEnum:
|
||||
if (m_integerValueEntry != null) {
|
||||
m_integerValueEntry.set(event.value);
|
||||
}
|
||||
break;
|
||||
case kString:
|
||||
if (m_stringValueEntry != null) {
|
||||
m_stringValueEntry.set(event.valueStr);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
if (m_booleanValueEntry != null) {
|
||||
m_booleanValueEntry.close();
|
||||
}
|
||||
if (m_integerValueEntry != null) {
|
||||
m_integerValueEntry.close();
|
||||
}
|
||||
if (m_stringValueEntry != null) {
|
||||
m_stringValueEntry.close();
|
||||
}
|
||||
if (m_minPublisher != null) {
|
||||
m_minPublisher.close();
|
||||
}
|
||||
if (m_maxPublisher != null) {
|
||||
m_maxPublisher.close();
|
||||
}
|
||||
if (m_stepPublisher != null) {
|
||||
m_stepPublisher.close();
|
||||
}
|
||||
if (m_defaultPublisher != null) {
|
||||
m_defaultPublisher.close();
|
||||
}
|
||||
if (m_choicesPublisher != null) {
|
||||
m_choicesPublisher.close();
|
||||
}
|
||||
}
|
||||
|
||||
BooleanEntry m_booleanValueEntry;
|
||||
IntegerEntry m_integerValueEntry;
|
||||
StringEntry m_stringValueEntry;
|
||||
IntegerPublisher m_minPublisher;
|
||||
IntegerPublisher m_maxPublisher;
|
||||
IntegerPublisher m_stepPublisher;
|
||||
IntegerPublisher m_defaultPublisher;
|
||||
StringArrayTopic m_choicesTopic;
|
||||
StringArrayPublisher m_choicesPublisher;
|
||||
}
|
||||
|
||||
private static final class SourcePublisher implements AutoCloseable {
|
||||
SourcePublisher(NetworkTable table, int sourceHandle) {
|
||||
this.m_table = table;
|
||||
m_sourcePublisher = table.getStringTopic("source").publish();
|
||||
m_descriptionPublisher = table.getStringTopic("description").publish();
|
||||
m_connectedPublisher = table.getBooleanTopic("connected").publish();
|
||||
m_streamsPublisher = table.getStringArrayTopic("streams").publish();
|
||||
m_modeEntry = table.getStringTopic("mode").getEntry("");
|
||||
m_modesPublisher = table.getStringArrayTopic("modes").publish();
|
||||
|
||||
m_sourcePublisher.set(makeSourceValue(sourceHandle));
|
||||
m_descriptionPublisher.set(CameraServerJNI.getSourceDescription(sourceHandle));
|
||||
m_connectedPublisher.set(CameraServerJNI.isSourceConnected(sourceHandle));
|
||||
m_streamsPublisher.set(getSourceStreamValues(sourceHandle));
|
||||
|
||||
try {
|
||||
VideoMode mode = CameraServerJNI.getSourceVideoMode(sourceHandle);
|
||||
m_modeEntry.setDefault(videoModeToString(mode));
|
||||
m_modesPublisher.set(getSourceModeValues(sourceHandle));
|
||||
} catch (VideoException ignored) {
|
||||
// Do nothing. Let the other event handlers update this if there is an error.
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
m_sourcePublisher.close();
|
||||
m_descriptionPublisher.close();
|
||||
m_connectedPublisher.close();
|
||||
m_streamsPublisher.close();
|
||||
m_modeEntry.close();
|
||||
m_modesPublisher.close();
|
||||
for (PropertyPublisher pp : m_properties.values()) {
|
||||
pp.close();
|
||||
}
|
||||
}
|
||||
|
||||
final NetworkTable m_table;
|
||||
final StringPublisher m_sourcePublisher;
|
||||
final StringPublisher m_descriptionPublisher;
|
||||
final BooleanPublisher m_connectedPublisher;
|
||||
final StringArrayPublisher m_streamsPublisher;
|
||||
final StringEntry m_modeEntry;
|
||||
final StringArrayPublisher m_modesPublisher;
|
||||
final Map<Integer, PropertyPublisher> m_properties = new HashMap<>();
|
||||
}
|
||||
|
||||
private static final AtomicInteger m_defaultUsbDevice = new AtomicInteger();
|
||||
private static String m_primarySourceName;
|
||||
private static final Map<String, VideoSource> m_sources = new HashMap<>();
|
||||
private static final Map<String, VideoSink> m_sinks = new HashMap<>();
|
||||
private static final Map<Integer, NetworkTable> m_tables =
|
||||
private static final Map<Integer, SourcePublisher> m_publishers =
|
||||
new HashMap<>(); // indexed by source handle
|
||||
// source handle indexed by sink handle
|
||||
private static final Map<Integer, Integer> m_fixedSources = new HashMap<>();
|
||||
@@ -61,189 +222,124 @@ public final class CameraServer {
|
||||
// - "PropertyInfo/{Property}" - Property supporting information
|
||||
|
||||
// Listener for video events
|
||||
@SuppressWarnings("PMD.UnusedPrivateField")
|
||||
@SuppressWarnings({"PMD.UnusedPrivateField", "PMD.AvoidCatchingGenericException"})
|
||||
private static final VideoListener m_videoListener =
|
||||
new VideoListener(
|
||||
event -> {
|
||||
switch (event.kind) {
|
||||
case kSourceCreated:
|
||||
{
|
||||
// Create subtable for the camera
|
||||
NetworkTable table = m_publishTable.getSubTable(event.name);
|
||||
m_tables.put(event.sourceHandle, table);
|
||||
table.getEntry("source").setString(makeSourceValue(event.sourceHandle));
|
||||
table
|
||||
.getEntry("description")
|
||||
.setString(CameraServerJNI.getSourceDescription(event.sourceHandle));
|
||||
table
|
||||
.getEntry("connected")
|
||||
.setBoolean(CameraServerJNI.isSourceConnected(event.sourceHandle));
|
||||
table
|
||||
.getEntry("streams")
|
||||
.setStringArray(getSourceStreamValues(event.sourceHandle));
|
||||
try {
|
||||
VideoMode mode = CameraServerJNI.getSourceVideoMode(event.sourceHandle);
|
||||
table.getEntry("mode").setDefaultString(videoModeToString(mode));
|
||||
table.getEntry("modes").setStringArray(getSourceModeValues(event.sourceHandle));
|
||||
} catch (VideoException ignored) {
|
||||
// Do nothing. Let the other event handlers update this if there is an error.
|
||||
synchronized (CameraServer.class) {
|
||||
switch (event.kind) {
|
||||
case kSourceCreated:
|
||||
{
|
||||
// Create subtable for the camera
|
||||
NetworkTable table = m_publishTable.getSubTable(event.name);
|
||||
m_publishers.put(
|
||||
event.sourceHandle, new SourcePublisher(table, event.sourceHandle));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceDestroyed:
|
||||
{
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
table.getEntry("source").setString("");
|
||||
table.getEntry("streams").setStringArray(new String[0]);
|
||||
table.getEntry("modes").setStringArray(new String[0]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceConnected:
|
||||
{
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
// update the description too (as it may have changed)
|
||||
table
|
||||
.getEntry("description")
|
||||
.setString(CameraServerJNI.getSourceDescription(event.sourceHandle));
|
||||
table.getEntry("connected").setBoolean(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceDisconnected:
|
||||
{
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
table.getEntry("connected").setBoolean(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceVideoModesUpdated:
|
||||
{
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
table.getEntry("modes").setStringArray(getSourceModeValues(event.sourceHandle));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceVideoModeChanged:
|
||||
{
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
table.getEntry("mode").setString(videoModeToString(event.mode));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourcePropertyCreated:
|
||||
{
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
putSourcePropertyValue(table, event, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourcePropertyValueUpdated:
|
||||
{
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
putSourcePropertyValue(table, event, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourcePropertyChoicesUpdated:
|
||||
{
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
try {
|
||||
String[] choices =
|
||||
CameraServerJNI.getEnumPropertyChoices(event.propertyHandle);
|
||||
table
|
||||
.getEntry("PropertyInfo/" + event.name + "/choices")
|
||||
.setStringArray(choices);
|
||||
} catch (VideoException ignored) {
|
||||
// ignore
|
||||
case kSourceDestroyed:
|
||||
{
|
||||
SourcePublisher publisher = m_publishers.remove(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
try {
|
||||
publisher.close();
|
||||
} catch (Exception e) {
|
||||
// ignore (nothing we can do about it)
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceConnected:
|
||||
{
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
// update the description too (as it may have changed)
|
||||
publisher.m_descriptionPublisher.set(
|
||||
CameraServerJNI.getSourceDescription(event.sourceHandle));
|
||||
publisher.m_connectedPublisher.set(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceDisconnected:
|
||||
{
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
publisher.m_connectedPublisher.set(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceVideoModesUpdated:
|
||||
{
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
publisher.m_modesPublisher.set(getSourceModeValues(event.sourceHandle));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceVideoModeChanged:
|
||||
{
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
publisher.m_modeEntry.set(videoModeToString(event.mode));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourcePropertyCreated:
|
||||
{
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
publisher.m_properties.put(
|
||||
event.propertyHandle, new PropertyPublisher(publisher.m_table, event));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourcePropertyValueUpdated:
|
||||
{
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
PropertyPublisher pp = publisher.m_properties.get(event.propertyHandle);
|
||||
if (pp != null) {
|
||||
pp.update(event);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourcePropertyChoicesUpdated:
|
||||
{
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
PropertyPublisher pp = publisher.m_properties.get(event.propertyHandle);
|
||||
if (pp != null && pp.m_choicesTopic != null) {
|
||||
try {
|
||||
String[] choices =
|
||||
CameraServerJNI.getEnumPropertyChoices(event.propertyHandle);
|
||||
if (pp.m_choicesPublisher == null) {
|
||||
pp.m_choicesPublisher = pp.m_choicesTopic.publish();
|
||||
}
|
||||
pp.m_choicesPublisher.set(choices);
|
||||
} catch (VideoException ignored) {
|
||||
// ignore (just don't publish choices if we can't get them)
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSinkSourceChanged:
|
||||
case kSinkCreated:
|
||||
case kSinkDestroyed:
|
||||
case kNetworkInterfacesChanged:
|
||||
{
|
||||
m_addresses = CameraServerJNI.getNetworkInterfaces();
|
||||
updateStreamValues();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
case kSinkSourceChanged:
|
||||
case kSinkCreated:
|
||||
case kSinkDestroyed:
|
||||
case kNetworkInterfacesChanged:
|
||||
{
|
||||
m_addresses = CameraServerJNI.getNetworkInterfaces();
|
||||
updateStreamValues();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
0x4fff,
|
||||
true);
|
||||
|
||||
@SuppressWarnings("PMD.UnusedPrivateField")
|
||||
private static final int m_tableListener =
|
||||
NetworkTableInstance.getDefault()
|
||||
.addEntryListener(
|
||||
kPublishName + "/",
|
||||
event -> {
|
||||
String relativeKey = event.name.substring(kPublishName.length() + 1);
|
||||
|
||||
// get source (sourceName/...)
|
||||
int subKeyIndex = relativeKey.indexOf('/');
|
||||
if (subKeyIndex == -1) {
|
||||
return;
|
||||
}
|
||||
String sourceName = relativeKey.substring(0, subKeyIndex);
|
||||
VideoSource source = m_sources.get(sourceName);
|
||||
if (source == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get subkey
|
||||
relativeKey = relativeKey.substring(subKeyIndex + 1);
|
||||
|
||||
// handle standard names
|
||||
String propName;
|
||||
if ("mode".equals(relativeKey)) {
|
||||
// reset to current mode
|
||||
event.getEntry().setString(videoModeToString(source.getVideoMode()));
|
||||
return;
|
||||
} else if (relativeKey.startsWith("Property/")) {
|
||||
propName = relativeKey.substring(9);
|
||||
} else if (relativeKey.startsWith("RawProperty/")) {
|
||||
propName = relativeKey.substring(12);
|
||||
} else {
|
||||
return; // ignore
|
||||
}
|
||||
|
||||
// everything else is a property
|
||||
VideoProperty property = source.getProperty(propName);
|
||||
switch (property.getKind()) {
|
||||
case kNone:
|
||||
return;
|
||||
case kBoolean:
|
||||
// reset to current setting
|
||||
event.getEntry().setBoolean(property.get() != 0);
|
||||
return;
|
||||
case kInteger:
|
||||
case kEnum:
|
||||
// reset to current setting
|
||||
event.getEntry().setDouble(property.get());
|
||||
return;
|
||||
case kString:
|
||||
// reset to current setting
|
||||
event.getEntry().setString(property.getString());
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
},
|
||||
EntryListenerFlags.kImmediate | EntryListenerFlags.kUpdate);
|
||||
|
||||
private static int m_nextPort = kBasePort;
|
||||
private static String[] m_addresses = new String[0];
|
||||
|
||||
@@ -369,8 +465,8 @@ public final class CameraServer {
|
||||
if (source == 0) {
|
||||
continue;
|
||||
}
|
||||
NetworkTable table = m_tables.get(source);
|
||||
if (table != null) {
|
||||
SourcePublisher publisher = m_publishers.get(source);
|
||||
if (publisher != null) {
|
||||
// Don't set stream values if this is a HttpCamera passthrough
|
||||
if (VideoSource.getKindFromInt(CameraServerJNI.getSourceKind(source))
|
||||
== VideoSource.Kind.kHttp) {
|
||||
@@ -380,7 +476,7 @@ public final class CameraServer {
|
||||
// Set table value
|
||||
String[] values = getSinkStreamValues(sink);
|
||||
if (values.length > 0) {
|
||||
table.getEntry("streams").setStringArray(values);
|
||||
publisher.m_streamsPublisher.set(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -390,12 +486,12 @@ public final class CameraServer {
|
||||
int source = i.getHandle();
|
||||
|
||||
// Get the source's subtable (if none exists, we're done)
|
||||
NetworkTable table = m_tables.get(source);
|
||||
if (table != null) {
|
||||
SourcePublisher publisher = m_publishers.get(source);
|
||||
if (publisher != null) {
|
||||
// Set table value
|
||||
String[] values = getSourceStreamValues(source);
|
||||
if (values.length > 0) {
|
||||
table.getEntry("streams").setStringArray(values);
|
||||
publisher.m_streamsPublisher.set(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -449,69 +545,6 @@ public final class CameraServer {
|
||||
return modeStrings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish a source property value to NetworkTables.
|
||||
*
|
||||
* @param table NetworkTable to which to push value.
|
||||
* @param event Video event.
|
||||
* @param isNew Whether the property value hasn't been pushed to NetworkTables before.
|
||||
*/
|
||||
private static void putSourcePropertyValue(NetworkTable table, VideoEvent event, boolean isNew) {
|
||||
String name;
|
||||
String infoName;
|
||||
if (event.name.startsWith("raw_")) {
|
||||
name = "RawProperty/" + event.name;
|
||||
infoName = "RawPropertyInfo/" + event.name;
|
||||
} else {
|
||||
name = "Property/" + event.name;
|
||||
infoName = "PropertyInfo/" + event.name;
|
||||
}
|
||||
|
||||
NetworkTableEntry entry = table.getEntry(name);
|
||||
try {
|
||||
switch (event.propertyKind) {
|
||||
case kBoolean:
|
||||
if (isNew) {
|
||||
entry.setDefaultBoolean(event.value != 0);
|
||||
} else {
|
||||
entry.setBoolean(event.value != 0);
|
||||
}
|
||||
break;
|
||||
case kInteger:
|
||||
case kEnum:
|
||||
if (isNew) {
|
||||
entry.setDefaultDouble(event.value);
|
||||
table
|
||||
.getEntry(infoName + "/min")
|
||||
.setDouble(CameraServerJNI.getPropertyMin(event.propertyHandle));
|
||||
table
|
||||
.getEntry(infoName + "/max")
|
||||
.setDouble(CameraServerJNI.getPropertyMax(event.propertyHandle));
|
||||
table
|
||||
.getEntry(infoName + "/step")
|
||||
.setDouble(CameraServerJNI.getPropertyStep(event.propertyHandle));
|
||||
table
|
||||
.getEntry(infoName + "/default")
|
||||
.setDouble(CameraServerJNI.getPropertyDefault(event.propertyHandle));
|
||||
} else {
|
||||
entry.setDouble(event.value);
|
||||
}
|
||||
break;
|
||||
case kString:
|
||||
if (isNew) {
|
||||
entry.setDefaultString(event.valueStr);
|
||||
} else {
|
||||
entry.setString(event.valueStr);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (VideoException ignored) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
private CameraServer() {}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,8 +8,12 @@
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <networktables/BooleanTopic.h>
|
||||
#include <networktables/IntegerTopic.h>
|
||||
#include <networktables/NetworkTable.h>
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <networktables/StringArrayTopic.h>
|
||||
#include <networktables/StringTopic.h>
|
||||
#include <wpi/DenseMap.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
@@ -24,9 +28,42 @@ using namespace frc;
|
||||
static constexpr char const* kPublishName = "/CameraPublisher";
|
||||
|
||||
namespace {
|
||||
|
||||
struct Instance;
|
||||
|
||||
struct PropertyPublisher {
|
||||
PropertyPublisher(nt::NetworkTable& table, const cs::VideoEvent& event);
|
||||
|
||||
void Update(const cs::VideoEvent& event);
|
||||
|
||||
nt::BooleanEntry booleanValueEntry;
|
||||
nt::IntegerEntry integerValueEntry;
|
||||
nt::StringEntry stringValueEntry;
|
||||
nt::IntegerPublisher minPublisher;
|
||||
nt::IntegerPublisher maxPublisher;
|
||||
nt::IntegerPublisher stepPublisher;
|
||||
nt::IntegerPublisher defaultPublisher;
|
||||
nt::StringArrayTopic choicesTopic;
|
||||
nt::StringArrayPublisher choicesPublisher;
|
||||
};
|
||||
|
||||
struct SourcePublisher {
|
||||
SourcePublisher(Instance& inst, std::shared_ptr<nt::NetworkTable> table,
|
||||
CS_Source source);
|
||||
|
||||
std::shared_ptr<nt::NetworkTable> table;
|
||||
nt::StringPublisher sourcePublisher;
|
||||
nt::StringPublisher descriptionPublisher;
|
||||
nt::BooleanPublisher connectedPublisher;
|
||||
nt::StringArrayPublisher streamsPublisher;
|
||||
nt::StringEntry modeEntry;
|
||||
nt::StringArrayPublisher modesPublisher;
|
||||
wpi::DenseMap<CS_Property, PropertyPublisher> properties;
|
||||
};
|
||||
|
||||
struct Instance {
|
||||
Instance();
|
||||
std::shared_ptr<nt::NetworkTable> GetSourceTable(CS_Source source);
|
||||
SourcePublisher* GetPublisher(CS_Source source);
|
||||
std::vector<std::string> GetSinkStreamValues(CS_Sink sink);
|
||||
std::vector<std::string> GetSourceStreamValues(CS_Source source);
|
||||
void UpdateStreamValues();
|
||||
@@ -37,7 +74,7 @@ struct Instance {
|
||||
wpi::StringMap<cs::VideoSource> m_sources;
|
||||
wpi::StringMap<cs::VideoSink> m_sinks;
|
||||
wpi::DenseMap<CS_Sink, CS_Source> m_fixedSources;
|
||||
wpi::DenseMap<CS_Source, std::shared_ptr<nt::NetworkTable>> m_tables;
|
||||
wpi::DenseMap<CS_Source, SourcePublisher> m_publishers;
|
||||
std::shared_ptr<nt::NetworkTable> m_publishTable{
|
||||
nt::NetworkTableInstance::GetDefault().GetTable(kPublishName)};
|
||||
cs::VideoListener m_videoListener;
|
||||
@@ -45,6 +82,7 @@ struct Instance {
|
||||
int m_nextPort{CameraServer::kBasePort};
|
||||
std::vector<std::string> m_addresses;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
static Instance& GetInstance() {
|
||||
@@ -86,9 +124,13 @@ static std::string MakeStreamValue(std::string_view address, int port) {
|
||||
return fmt::format("mjpg:http://{}:{}/?action=stream", address, port);
|
||||
}
|
||||
|
||||
std::shared_ptr<nt::NetworkTable> Instance::GetSourceTable(CS_Source source) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
return m_tables.lookup(source);
|
||||
SourcePublisher* Instance::GetPublisher(CS_Source source) {
|
||||
auto it = m_publishers.find(source);
|
||||
if (it != m_publishers.end()) {
|
||||
return &it->second;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> Instance::GetSinkStreamValues(CS_Sink sink) {
|
||||
@@ -158,7 +200,6 @@ std::vector<std::string> Instance::GetSourceStreamValues(CS_Source source) {
|
||||
}
|
||||
|
||||
void Instance::UpdateStreamValues() {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
// Over all the sinks...
|
||||
for (const auto& i : m_sinks) {
|
||||
CS_Status status = 0;
|
||||
@@ -172,8 +213,7 @@ void Instance::UpdateStreamValues() {
|
||||
if (source == 0) {
|
||||
continue;
|
||||
}
|
||||
auto table = m_tables.lookup(source);
|
||||
if (table) {
|
||||
if (auto publisher = GetPublisher(source)) {
|
||||
// Don't set stream values if this is a HttpCamera passthrough
|
||||
if (cs::GetSourceKind(source, &status) == CS_SOURCE_HTTP) {
|
||||
continue;
|
||||
@@ -182,7 +222,7 @@ void Instance::UpdateStreamValues() {
|
||||
// Set table value
|
||||
auto values = GetSinkStreamValues(sink);
|
||||
if (!values.empty()) {
|
||||
table->GetEntry("streams").SetStringArray(values);
|
||||
publisher->streamsPublisher.Set(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,12 +232,11 @@ void Instance::UpdateStreamValues() {
|
||||
CS_Source source = i.second.GetHandle();
|
||||
|
||||
// Get the source's subtable (if none exists, we're done)
|
||||
auto table = m_tables.lookup(source);
|
||||
if (table) {
|
||||
if (auto publisher = GetPublisher(source)) {
|
||||
// Set table value
|
||||
auto values = GetSourceStreamValues(source);
|
||||
if (!values.empty()) {
|
||||
table->GetEntry("streams").SetStringArray(values);
|
||||
publisher->streamsPublisher.Set(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,51 +273,71 @@ static std::vector<std::string> GetSourceModeValues(int source) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
static void PutSourcePropertyValue(nt::NetworkTable* table,
|
||||
const cs::VideoEvent& event, bool isNew) {
|
||||
std::string_view namePrefix;
|
||||
std::string_view infoPrefix;
|
||||
PropertyPublisher::PropertyPublisher(nt::NetworkTable& table,
|
||||
const cs::VideoEvent& event) {
|
||||
std::string name;
|
||||
std::string infoName;
|
||||
if (wpi::starts_with(event.name, "raw_")) {
|
||||
namePrefix = "RawProperty";
|
||||
infoPrefix = "RawPropertyInfo";
|
||||
name = fmt::format("RawProperty/{}", event.name);
|
||||
infoName = fmt::format("RawPropertyInfo/{}", event.name);
|
||||
} else {
|
||||
namePrefix = "Property";
|
||||
infoPrefix = "PropertyInfo";
|
||||
name = fmt::format("Property/{}", event.name);
|
||||
infoName = fmt::format("PropertyInfo/{}", event.name);
|
||||
}
|
||||
|
||||
wpi::SmallString<64> buf;
|
||||
CS_Status status = 0;
|
||||
nt::NetworkTableEntry entry =
|
||||
table->GetEntry(fmt::format("{}/{}", namePrefix, event.name));
|
||||
switch (event.propertyKind) {
|
||||
case CS_PROP_BOOLEAN:
|
||||
if (isNew) {
|
||||
entry.SetDefaultBoolean(event.value != 0);
|
||||
} else {
|
||||
entry.SetBoolean(event.value != 0);
|
||||
booleanValueEntry = table.GetBooleanTopic(name).GetEntry(false);
|
||||
booleanValueEntry.SetDefault(event.value != 0);
|
||||
break;
|
||||
case CS_PROP_ENUM:
|
||||
choicesTopic =
|
||||
table.GetStringArrayTopic(fmt::format("{}/choices", infoName));
|
||||
[[fallthrough]];
|
||||
case CS_PROP_INTEGER:
|
||||
integerValueEntry = table.GetIntegerTopic(name).GetEntry(0);
|
||||
minPublisher =
|
||||
table.GetIntegerTopic(fmt::format("{}/min", infoName)).Publish();
|
||||
maxPublisher =
|
||||
table.GetIntegerTopic(fmt::format("{}/max", infoName)).Publish();
|
||||
stepPublisher =
|
||||
table.GetIntegerTopic(fmt::format("{}/step", infoName)).Publish();
|
||||
defaultPublisher =
|
||||
table.GetIntegerTopic(fmt::format("{}/default", infoName)).Publish();
|
||||
|
||||
integerValueEntry.SetDefault(event.value);
|
||||
minPublisher.Set(cs::GetPropertyMin(event.propertyHandle, &status));
|
||||
maxPublisher.Set(cs::GetPropertyMax(event.propertyHandle, &status));
|
||||
stepPublisher.Set(cs::GetPropertyStep(event.propertyHandle, &status));
|
||||
defaultPublisher.Set(
|
||||
cs::GetPropertyDefault(event.propertyHandle, &status));
|
||||
break;
|
||||
case CS_PROP_STRING:
|
||||
stringValueEntry = table.GetStringTopic(name).GetEntry("");
|
||||
stringValueEntry.SetDefault(event.valueStr);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyPublisher::Update(const cs::VideoEvent& event) {
|
||||
switch (event.propertyKind) {
|
||||
case CS_PROP_BOOLEAN:
|
||||
if (booleanValueEntry) {
|
||||
booleanValueEntry.Set(event.value != 0);
|
||||
}
|
||||
break;
|
||||
case CS_PROP_INTEGER:
|
||||
case CS_PROP_ENUM:
|
||||
if (isNew) {
|
||||
entry.SetDefaultDouble(event.value);
|
||||
table->GetEntry(fmt::format("{}/{}/min", infoPrefix, event.name))
|
||||
.SetDouble(cs::GetPropertyMin(event.propertyHandle, &status));
|
||||
table->GetEntry(fmt::format("{}/{}/max", infoPrefix, event.name))
|
||||
.SetDouble(cs::GetPropertyMax(event.propertyHandle, &status));
|
||||
table->GetEntry(fmt::format("{}/{}/step", infoPrefix, event.name))
|
||||
.SetDouble(cs::GetPropertyStep(event.propertyHandle, &status));
|
||||
table->GetEntry(fmt::format("{}/{}/default", infoPrefix, event.name))
|
||||
.SetDouble(cs::GetPropertyDefault(event.propertyHandle, &status));
|
||||
} else {
|
||||
entry.SetDouble(event.value);
|
||||
if (integerValueEntry) {
|
||||
integerValueEntry.Set(event.value);
|
||||
}
|
||||
break;
|
||||
case CS_PROP_STRING:
|
||||
if (isNew) {
|
||||
entry.SetDefaultString(event.valueStr);
|
||||
} else {
|
||||
entry.SetString(event.valueStr);
|
||||
if (stringValueEntry) {
|
||||
stringValueEntry.Set(event.valueStr);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -286,6 +345,28 @@ static void PutSourcePropertyValue(nt::NetworkTable* table,
|
||||
}
|
||||
}
|
||||
|
||||
SourcePublisher::SourcePublisher(Instance& inst,
|
||||
std::shared_ptr<nt::NetworkTable> table,
|
||||
CS_Source source)
|
||||
: table{table},
|
||||
sourcePublisher{table->GetStringTopic("source").Publish()},
|
||||
descriptionPublisher{table->GetStringTopic("description").Publish()},
|
||||
connectedPublisher{table->GetBooleanTopic("connected").Publish()},
|
||||
streamsPublisher{table->GetStringArrayTopic("streams").Publish()},
|
||||
modeEntry{table->GetStringTopic("mode").GetEntry("")},
|
||||
modesPublisher{table->GetStringArrayTopic("modes").Publish()} {
|
||||
CS_Status status = 0;
|
||||
wpi::SmallString<64> buf;
|
||||
sourcePublisher.Set(MakeSourceValue(source, buf));
|
||||
wpi::SmallString<64> descBuf;
|
||||
descriptionPublisher.Set(cs::GetSourceDescription(source, descBuf, &status));
|
||||
connectedPublisher.Set(cs::IsSourceConnected(source, &status));
|
||||
streamsPublisher.Set(inst.GetSourceStreamValues(source));
|
||||
auto mode = cs::GetSourceVideoMode(source, &status);
|
||||
modeEntry.SetDefault(VideoModeToString(mode));
|
||||
modesPublisher.Set(GetSourceModeValues(source));
|
||||
}
|
||||
|
||||
Instance::Instance() {
|
||||
// We publish sources to NetworkTables using the following structure:
|
||||
// "/CameraPublisher/{Source.Name}/" - root
|
||||
@@ -300,177 +381,88 @@ Instance::Instance() {
|
||||
|
||||
// Listener for video events
|
||||
m_videoListener = cs::VideoListener{
|
||||
[=](const cs::VideoEvent& event) {
|
||||
[=, this](const cs::VideoEvent& event) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
CS_Status status = 0;
|
||||
switch (event.kind) {
|
||||
case cs::VideoEvent::kSourceCreated: {
|
||||
// Create subtable for the camera
|
||||
auto table = m_publishTable->GetSubTable(event.name);
|
||||
{
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_tables.insert(std::make_pair(event.sourceHandle, table));
|
||||
}
|
||||
wpi::SmallString<64> buf;
|
||||
table->GetEntry("source").SetString(
|
||||
MakeSourceValue(event.sourceHandle, buf));
|
||||
wpi::SmallString<64> descBuf;
|
||||
table->GetEntry("description")
|
||||
.SetString(cs::GetSourceDescription(event.sourceHandle, descBuf,
|
||||
&status));
|
||||
table->GetEntry("connected")
|
||||
.SetBoolean(cs::IsSourceConnected(event.sourceHandle, &status));
|
||||
table->GetEntry("streams").SetStringArray(
|
||||
GetSourceStreamValues(event.sourceHandle));
|
||||
auto mode = cs::GetSourceVideoMode(event.sourceHandle, &status);
|
||||
table->GetEntry("mode").SetDefaultString(VideoModeToString(mode));
|
||||
table->GetEntry("modes").SetStringArray(
|
||||
GetSourceModeValues(event.sourceHandle));
|
||||
m_publishers.insert(
|
||||
{event.sourceHandle,
|
||||
SourcePublisher{*this, table, event.sourceHandle}});
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSourceDestroyed: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
table->GetEntry("source").SetString("");
|
||||
table->GetEntry("streams").SetStringArray(
|
||||
std::vector<std::string>{});
|
||||
table->GetEntry("modes").SetStringArray(
|
||||
std::vector<std::string>{});
|
||||
}
|
||||
case cs::VideoEvent::kSourceDestroyed:
|
||||
m_publishers.erase(event.sourceHandle);
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSourceConnected: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
case cs::VideoEvent::kSourceConnected:
|
||||
if (auto publisher = GetPublisher(event.sourceHandle)) {
|
||||
// update the description too (as it may have changed)
|
||||
wpi::SmallString<64> descBuf;
|
||||
table->GetEntry("description")
|
||||
.SetString(cs::GetSourceDescription(event.sourceHandle,
|
||||
descBuf, &status));
|
||||
table->GetEntry("connected").SetBoolean(true);
|
||||
publisher->descriptionPublisher.Set(cs::GetSourceDescription(
|
||||
event.sourceHandle, descBuf, &status));
|
||||
publisher->connectedPublisher.Set(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSourceDisconnected: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
table->GetEntry("connected").SetBoolean(false);
|
||||
case cs::VideoEvent::kSourceDisconnected:
|
||||
if (auto publisher = GetPublisher(event.sourceHandle)) {
|
||||
publisher->connectedPublisher.Set(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSourceVideoModesUpdated: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
table->GetEntry("modes").SetStringArray(
|
||||
case cs::VideoEvent::kSourceVideoModesUpdated:
|
||||
if (auto publisher = GetPublisher(event.sourceHandle)) {
|
||||
publisher->modesPublisher.Set(
|
||||
GetSourceModeValues(event.sourceHandle));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSourceVideoModeChanged: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
table->GetEntry("mode").SetString(VideoModeToString(event.mode));
|
||||
case cs::VideoEvent::kSourceVideoModeChanged:
|
||||
if (auto publisher = GetPublisher(event.sourceHandle)) {
|
||||
publisher->modeEntry.Set(VideoModeToString(event.mode));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSourcePropertyCreated: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
PutSourcePropertyValue(table.get(), event, true);
|
||||
case cs::VideoEvent::kSourcePropertyCreated:
|
||||
if (auto publisher = GetPublisher(event.sourceHandle)) {
|
||||
publisher->properties.insert(
|
||||
{event.propertyHandle,
|
||||
PropertyPublisher{*publisher->table, event}});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSourcePropertyValueUpdated: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
PutSourcePropertyValue(table.get(), event, false);
|
||||
case cs::VideoEvent::kSourcePropertyValueUpdated:
|
||||
if (auto publisher = GetPublisher(event.sourceHandle)) {
|
||||
auto ppIt = publisher->properties.find(event.propertyHandle);
|
||||
if (ppIt != publisher->properties.end()) {
|
||||
ppIt->second.Update(event);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSourcePropertyChoicesUpdated: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
auto choices =
|
||||
cs::GetEnumPropertyChoices(event.propertyHandle, &status);
|
||||
table
|
||||
->GetEntry(fmt::format("PropertyInfo/{}/choices", event.name))
|
||||
.SetStringArray(choices);
|
||||
case cs::VideoEvent::kSourcePropertyChoicesUpdated:
|
||||
if (auto publisher = GetPublisher(event.sourceHandle)) {
|
||||
auto ppIt = publisher->properties.find(event.propertyHandle);
|
||||
if (ppIt != publisher->properties.end() &&
|
||||
ppIt->second.choicesTopic) {
|
||||
auto choices =
|
||||
cs::GetEnumPropertyChoices(event.propertyHandle, &status);
|
||||
if (!ppIt->second.choicesPublisher) {
|
||||
ppIt->second.choicesPublisher =
|
||||
ppIt->second.choicesTopic.Publish();
|
||||
}
|
||||
ppIt->second.choicesPublisher.Set(choices);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSinkSourceChanged:
|
||||
case cs::VideoEvent::kSinkCreated:
|
||||
case cs::VideoEvent::kSinkDestroyed:
|
||||
case cs::VideoEvent::kNetworkInterfacesChanged: {
|
||||
case cs::VideoEvent::kNetworkInterfacesChanged:
|
||||
m_addresses = cs::GetNetworkInterfaces();
|
||||
UpdateStreamValues();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
0x4fff, true};
|
||||
|
||||
// Listener for NetworkTable events
|
||||
// We don't currently support changing settings via NT due to
|
||||
// synchronization issues, so just update to current setting if someone
|
||||
// else tries to change it.
|
||||
wpi::SmallString<64> buf;
|
||||
m_tableListener = nt::NetworkTableInstance::GetDefault().AddEntryListener(
|
||||
fmt::format("{}/", kPublishName),
|
||||
[=](const nt::EntryNotification& event) {
|
||||
auto relativeKey = wpi::drop_front(
|
||||
event.name, std::string_view{kPublishName}.size() + 1);
|
||||
|
||||
// get source (sourceName/...)
|
||||
auto subKeyIndex = relativeKey.find('/');
|
||||
if (subKeyIndex == std::string_view::npos) {
|
||||
return;
|
||||
}
|
||||
auto sourceName = wpi::slice(relativeKey, 0, subKeyIndex);
|
||||
auto sourceIt = m_sources.find(sourceName);
|
||||
if (sourceIt == m_sources.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get subkey
|
||||
relativeKey.remove_prefix(subKeyIndex + 1);
|
||||
|
||||
// handle standard names
|
||||
std::string_view propName;
|
||||
nt::NetworkTableEntry entry{event.entry};
|
||||
if (relativeKey == "mode") {
|
||||
// reset to current mode
|
||||
entry.SetString(VideoModeToString(sourceIt->second.GetVideoMode()));
|
||||
return;
|
||||
} else if (wpi::starts_with(relativeKey, "Property/")) {
|
||||
propName = wpi::substr(relativeKey, 9);
|
||||
} else if (wpi::starts_with(relativeKey, "RawProperty/")) {
|
||||
propName = wpi::substr(relativeKey, 12);
|
||||
} else {
|
||||
return; // ignore
|
||||
}
|
||||
|
||||
// everything else is a property
|
||||
auto property = sourceIt->second.GetProperty(propName);
|
||||
switch (property.GetKind()) {
|
||||
case cs::VideoProperty::kNone:
|
||||
return;
|
||||
case cs::VideoProperty::kBoolean:
|
||||
entry.SetBoolean(property.Get() != 0);
|
||||
return;
|
||||
case cs::VideoProperty::kInteger:
|
||||
case cs::VideoProperty::kEnum:
|
||||
entry.SetDouble(property.Get());
|
||||
return;
|
||||
case cs::VideoProperty::kString:
|
||||
entry.SetString(property.GetString());
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
},
|
||||
NT_NOTIFY_IMMEDIATE | NT_NOTIFY_UPDATE);
|
||||
}
|
||||
|
||||
cs::UsbCamera CameraServer::StartAutomaticCapture() {
|
||||
@@ -519,7 +511,7 @@ cs::AxisCamera CameraServer::AddAxisCamera(const std::string& host) {
|
||||
return AddAxisCamera("Axis Camera", host);
|
||||
}
|
||||
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(wpi::span<const std::string> hosts) {
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(std::span<const std::string> hosts) {
|
||||
return AddAxisCamera("Axis Camera", hosts);
|
||||
}
|
||||
|
||||
@@ -551,7 +543,7 @@ cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
|
||||
}
|
||||
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
|
||||
wpi::span<const std::string> hosts) {
|
||||
std::span<const std::string> hosts) {
|
||||
cs::AxisCamera camera{name, hosts};
|
||||
StartAutomaticCapture(camera);
|
||||
auto csShared = GetCameraServerShared();
|
||||
|
||||
@@ -6,11 +6,10 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <wpi/span.h>
|
||||
|
||||
#include "cscore.h"
|
||||
#include "cscore_cv.h"
|
||||
|
||||
@@ -110,7 +109,7 @@ class CameraServer {
|
||||
*
|
||||
* @param hosts Array of Camera host IPs/DNS names
|
||||
*/
|
||||
static cs::AxisCamera AddAxisCamera(wpi::span<const std::string> hosts);
|
||||
static cs::AxisCamera AddAxisCamera(std::span<const std::string> hosts);
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
@@ -155,7 +154,7 @@ class CameraServer {
|
||||
* @param hosts Array of Camera host IPs/DNS names
|
||||
*/
|
||||
static cs::AxisCamera AddAxisCamera(std::string_view name,
|
||||
wpi::span<const std::string> hosts);
|
||||
std::span<const std::string> hosts);
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
|
||||
@@ -49,6 +49,7 @@ class TestEnvironment : public testing::Environment {
|
||||
HAL_GetControlWord(&controlWord);
|
||||
return controlWord.enabled && controlWord.dsAttached;
|
||||
};
|
||||
HAL_RefreshDSData();
|
||||
while (!checkEnabled()) {
|
||||
if (enableCounter > 50) {
|
||||
// Robot did not enable properly after 5 seconds.
|
||||
@@ -60,6 +61,7 @@ class TestEnvironment : public testing::Environment {
|
||||
std::this_thread::sleep_for(100ms);
|
||||
|
||||
fmt::print("Waiting for enable: {}\n", enableCounter++);
|
||||
HAL_RefreshDSData();
|
||||
}
|
||||
std::this_thread::sleep_for(500ms);
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ model {
|
||||
components {
|
||||
examplesMap.each { key, value ->
|
||||
if (key == "usbviewer") {
|
||||
if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxarm32') && !project.hasProperty('onlylinuxarm64')) {
|
||||
if (!project.hasProperty('onlylinuxathena')) {
|
||||
"${key}"(NativeExecutableSpec) {
|
||||
targetBuildTypes 'debug'
|
||||
binaries.all {
|
||||
@@ -189,7 +189,7 @@ model {
|
||||
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
|
||||
lib library: 'cscore', linkage: 'shared'
|
||||
nativeUtils.useRequiredLibrary(it, 'imgui_static')
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm32 || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm64) {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
@@ -199,6 +199,9 @@ model {
|
||||
it.linker.args << '-framework' << 'Metal' << '-framework' << 'MetalKit' << '-framework' << 'Cocoa' << '-framework' << 'IOKit' << '-framework' << 'CoreFoundation' << '-framework' << 'CoreVideo' << '-framework' << 'QuartzCore'
|
||||
} else {
|
||||
it.linker.args << '-lX11'
|
||||
if (it.targetPlatform.name.startsWith('linuxarm')) {
|
||||
it.linker.args << '-lGL'
|
||||
}
|
||||
}
|
||||
}
|
||||
sources {
|
||||
|
||||
@@ -88,7 +88,7 @@ int ConfigurableSourceImpl::CreateProperty(
|
||||
}
|
||||
|
||||
void ConfigurableSourceImpl::SetEnumPropertyChoices(
|
||||
int property, wpi::span<const std::string> choices, CS_Status* status) {
|
||||
int property, std::span<const std::string> choices, CS_Status* status) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) {
|
||||
|
||||
@@ -8,12 +8,11 @@
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/span.h>
|
||||
|
||||
#include "SourceImpl.h"
|
||||
|
||||
namespace cs {
|
||||
@@ -42,7 +41,7 @@ class ConfigurableSourceImpl : public SourceImpl {
|
||||
int maximum, int step, int defaultValue, int value,
|
||||
std::function<void(CS_Property property)> onChange);
|
||||
void SetEnumPropertyChoices(int property,
|
||||
wpi::span<const std::string> choices,
|
||||
std::span<const std::string> choices,
|
||||
CS_Status* status);
|
||||
|
||||
private:
|
||||
|
||||
@@ -110,7 +110,7 @@ void CvSinkImpl::ThreadMain() {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
continue;
|
||||
}
|
||||
SDEBUG4("{}", "waiting for frame");
|
||||
SDEBUG4("waiting for frame");
|
||||
Frame frame = source->GetNextFrame(); // blocks
|
||||
if (!m_active) {
|
||||
break;
|
||||
|
||||
@@ -139,7 +139,7 @@ CS_Property CreateSourcePropertyCallback(
|
||||
}
|
||||
|
||||
void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
|
||||
wpi::span<const std::string> choices,
|
||||
std::span<const std::string> choices,
|
||||
CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || (data->kind & SourceMask) == 0) {
|
||||
|
||||
@@ -75,7 +75,7 @@ void HttpCameraImpl::MonitorThreadMain() {
|
||||
std::unique_lock lock(m_mutex);
|
||||
// sleep for 1 second between checks
|
||||
m_monitorCond.wait_for(lock, std::chrono::seconds(1),
|
||||
[=] { return !m_active; });
|
||||
[=, this] { return !m_active; });
|
||||
|
||||
if (!m_active) {
|
||||
break;
|
||||
@@ -85,7 +85,7 @@ void HttpCameraImpl::MonitorThreadMain() {
|
||||
// (this will result in an error at the read point, and ultimately
|
||||
// a reconnect attempt)
|
||||
if (m_streamConn && m_frameCount == 0) {
|
||||
SWARNING("{}", "Monitor detected stream hung, disconnecting");
|
||||
SWARNING("Monitor detected stream hung, disconnecting");
|
||||
m_streamConn->stream->close();
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ void HttpCameraImpl::MonitorThreadMain() {
|
||||
m_frameCount = 0;
|
||||
}
|
||||
|
||||
SDEBUG("{}", "Monitor Thread exiting");
|
||||
SDEBUG("Monitor Thread exiting");
|
||||
}
|
||||
|
||||
void HttpCameraImpl::StreamThreadMain() {
|
||||
@@ -110,7 +110,8 @@ void HttpCameraImpl::StreamThreadMain() {
|
||||
m_streamConn->stream->close();
|
||||
}
|
||||
// Wait for enable
|
||||
m_sinkEnabledCond.wait(lock, [=] { return !m_active || IsEnabled(); });
|
||||
m_sinkEnabledCond.wait(lock,
|
||||
[=, this] { return !m_active || IsEnabled(); });
|
||||
if (!m_active) {
|
||||
return;
|
||||
}
|
||||
@@ -140,7 +141,7 @@ void HttpCameraImpl::StreamThreadMain() {
|
||||
}
|
||||
}
|
||||
|
||||
SDEBUG("{}", "Camera Thread exiting");
|
||||
SDEBUG("Camera Thread exiting");
|
||||
SetConnected(false);
|
||||
}
|
||||
|
||||
@@ -151,7 +152,7 @@ wpi::HttpConnection* HttpCameraImpl::DeviceStreamConnect(
|
||||
{
|
||||
std::scoped_lock lock(m_mutex);
|
||||
if (m_locations.empty()) {
|
||||
SERROR("{}", "locations array is empty!?");
|
||||
SERROR("locations array is empty!?");
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
return nullptr;
|
||||
}
|
||||
@@ -272,7 +273,7 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
|
||||
wpi::SmallString<64> contentTypeBuf;
|
||||
wpi::SmallString<64> contentLengthBuf;
|
||||
if (!ParseHttpHeaders(is, &contentTypeBuf, &contentLengthBuf)) {
|
||||
SWARNING("{}", "disconnected during headers");
|
||||
SWARNING("disconnected during headers");
|
||||
PutError("disconnected during headers", wpi::Now());
|
||||
return false;
|
||||
}
|
||||
@@ -294,7 +295,7 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
|
||||
// Ugh, no Content-Length? Read the blocks of the JPEG file.
|
||||
int width, height;
|
||||
if (!ReadJpeg(is, imageBuf, &width, &height)) {
|
||||
SWARNING("{}", "did not receive a JPEG image");
|
||||
SWARNING("did not receive a JPEG image");
|
||||
PutError("did not receive a JPEG image", wpi::Now());
|
||||
return false;
|
||||
}
|
||||
@@ -313,7 +314,7 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
|
||||
}
|
||||
int width, height;
|
||||
if (!GetJpegSize(image->str(), &width, &height)) {
|
||||
SWARNING("{}", "did not receive a JPEG image");
|
||||
SWARNING("did not receive a JPEG image");
|
||||
PutError("did not receive a JPEG image", wpi::Now());
|
||||
return false;
|
||||
}
|
||||
@@ -329,7 +330,7 @@ void HttpCameraImpl::SettingsThreadMain() {
|
||||
wpi::HttpRequest req;
|
||||
{
|
||||
std::unique_lock lock(m_mutex);
|
||||
m_settingsCond.wait(lock, [=] {
|
||||
m_settingsCond.wait(lock, [=, this] {
|
||||
return !m_active || (m_prefLocation != -1 && !m_settings.empty());
|
||||
});
|
||||
if (!m_active) {
|
||||
@@ -343,7 +344,7 @@ void HttpCameraImpl::SettingsThreadMain() {
|
||||
DeviceSendSettings(req);
|
||||
}
|
||||
|
||||
SDEBUG("{}", "Settings Thread exiting");
|
||||
SDEBUG("Settings Thread exiting");
|
||||
}
|
||||
|
||||
void HttpCameraImpl::DeviceSendSettings(wpi::HttpRequest& req) {
|
||||
@@ -378,7 +379,7 @@ CS_HttpCameraKind HttpCameraImpl::GetKind() const {
|
||||
return m_kind;
|
||||
}
|
||||
|
||||
bool HttpCameraImpl::SetUrls(wpi::span<const std::string> urls,
|
||||
bool HttpCameraImpl::SetUrls(std::span<const std::string> urls,
|
||||
CS_Status* status) {
|
||||
std::vector<wpi::HttpLocation> locations;
|
||||
for (const auto& url : urls) {
|
||||
@@ -572,14 +573,14 @@ CS_Source CreateHttpCamera(std::string_view name, std::string_view url,
|
||||
break;
|
||||
}
|
||||
std::string urlStr{url};
|
||||
if (!source->SetUrls(wpi::span{&urlStr, 1}, status)) {
|
||||
if (!source->SetUrls(std::span{&urlStr, 1}, status)) {
|
||||
return 0;
|
||||
}
|
||||
return inst.CreateSource(CS_SOURCE_HTTP, source);
|
||||
}
|
||||
|
||||
CS_Source CreateHttpCamera(std::string_view name,
|
||||
wpi::span<const std::string> urls,
|
||||
std::span<const std::string> urls,
|
||||
CS_HttpCameraKind kind, CS_Status* status) {
|
||||
auto& inst = Instance::GetInstance();
|
||||
if (urls.empty()) {
|
||||
@@ -603,7 +604,7 @@ CS_HttpCameraKind GetHttpCameraKind(CS_Source source, CS_Status* status) {
|
||||
return static_cast<HttpCameraImpl&>(*data->source).GetKind();
|
||||
}
|
||||
|
||||
void SetHttpCameraUrls(CS_Source source, wpi::span<const std::string> urls,
|
||||
void SetHttpCameraUrls(CS_Source source, std::span<const std::string> urls,
|
||||
CS_Status* status) {
|
||||
if (urls.empty()) {
|
||||
*status = CS_EMPTY_VALUE;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
@@ -18,7 +19,6 @@
|
||||
#include <wpi/StringMap.h>
|
||||
#include <wpi/condition_variable.h>
|
||||
#include <wpi/raw_istream.h>
|
||||
#include <wpi/span.h>
|
||||
#include <wpinet/HttpUtil.h>
|
||||
|
||||
#include "SourceImpl.h"
|
||||
@@ -55,7 +55,7 @@ class HttpCameraImpl : public SourceImpl {
|
||||
void NumSinksEnabledChanged() override;
|
||||
|
||||
CS_HttpCameraKind GetKind() const;
|
||||
bool SetUrls(wpi::span<const std::string> urls, CS_Status* status);
|
||||
bool SetUrls(std::span<const std::string> urls, CS_Status* status);
|
||||
std::vector<std::string> GetUrls() const;
|
||||
|
||||
// Property data
|
||||
|
||||
@@ -86,16 +86,16 @@ class Instance {
|
||||
void DestroySource(CS_Source handle);
|
||||
void DestroySink(CS_Sink handle);
|
||||
|
||||
wpi::span<CS_Source> EnumerateSourceHandles(
|
||||
std::span<CS_Source> EnumerateSourceHandles(
|
||||
wpi::SmallVectorImpl<CS_Source>& vec) {
|
||||
return m_sources.GetAll(vec);
|
||||
}
|
||||
|
||||
wpi::span<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec) {
|
||||
std::span<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec) {
|
||||
return m_sinks.GetAll(vec);
|
||||
}
|
||||
|
||||
wpi::span<CS_Sink> EnumerateSourceSinks(CS_Source source,
|
||||
std::span<CS_Sink> EnumerateSourceSinks(CS_Source source,
|
||||
wpi::SmallVectorImpl<CS_Sink>& vec) {
|
||||
vec.clear();
|
||||
m_sinks.ForEach([&](CS_Sink sinkHandle, const SinkData& data) {
|
||||
|
||||
@@ -27,26 +27,37 @@ inline void NamedLog(wpi::Logger& logger, unsigned int level, const char* file,
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#define LOG(level, format, ...) WPI_LOG(m_logger, level, format, __VA_ARGS__)
|
||||
#define LOG(level, format, ...) \
|
||||
WPI_LOG(m_logger, level, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#undef ERROR
|
||||
#define ERROR(format, ...) WPI_ERROR(m_logger, format, __VA_ARGS__)
|
||||
#define WARNING(format, ...) WPI_WARNING(m_logger, format, __VA_ARGS__)
|
||||
#define INFO(format, ...) WPI_INFO(m_logger, format, __VA_ARGS__)
|
||||
#define ERROR(format, ...) \
|
||||
WPI_ERROR(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define WARNING(format, ...) \
|
||||
WPI_WARNING(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define INFO(format, ...) WPI_INFO(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#define DEBUG0(format, ...) WPI_DEBUG(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG1(format, ...) WPI_DEBUG1(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG2(format, ...) WPI_DEBUG2(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG3(format, ...) WPI_DEBUG3(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG4(format, ...) WPI_DEBUG4(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG0(format, ...) \
|
||||
WPI_DEBUG(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define DEBUG1(format, ...) \
|
||||
WPI_DEBUG1(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define DEBUG2(format, ...) \
|
||||
WPI_DEBUG2(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define DEBUG3(format, ...) \
|
||||
WPI_DEBUG3(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define DEBUG4(format, ...) \
|
||||
WPI_DEBUG4(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#define SLOG(level, format, ...) \
|
||||
NamedLog(m_logger, level, __FILE__, __LINE__, GetName(), FMT_STRING(format), \
|
||||
__VA_ARGS__)
|
||||
#define SLOG(level, format, ...) \
|
||||
NamedLog(m_logger, level, __FILE__, __LINE__, GetName(), \
|
||||
FMT_STRING(format) __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#define SERROR(format, ...) SLOG(::wpi::WPI_LOG_ERROR, format, __VA_ARGS__)
|
||||
#define SWARNING(format, ...) SLOG(::wpi::WPI_LOG_WARNING, format, __VA_ARGS__)
|
||||
#define SINFO(format, ...) SLOG(::wpi::WPI_LOG_INFO, format, __VA_ARGS__)
|
||||
#define SERROR(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_ERROR, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define SWARNING(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_WARNING, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define SINFO(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_INFO, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#ifdef NDEBUG
|
||||
#define SDEBUG(format, ...) \
|
||||
@@ -65,11 +76,16 @@ inline void NamedLog(wpi::Logger& logger, unsigned int level, const char* file,
|
||||
do { \
|
||||
} while (0)
|
||||
#else
|
||||
#define SDEBUG(format, ...) SLOG(::wpi::WPI_LOG_DEBUG, format, __VA_ARGS__)
|
||||
#define SDEBUG1(format, ...) SLOG(::wpi::WPI_LOG_DEBUG1, format, __VA_ARGS__)
|
||||
#define SDEBUG2(format, ...) SLOG(::wpi::WPI_LOG_DEBUG2, format, __VA_ARGS__)
|
||||
#define SDEBUG3(format, ...) SLOG(::wpi::WPI_LOG_DEBUG3, format, __VA_ARGS__)
|
||||
#define SDEBUG4(format, ...) SLOG(::wpi::WPI_LOG_DEBUG4, format, __VA_ARGS__)
|
||||
#define SDEBUG(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_DEBUG, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define SDEBUG1(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_DEBUG1, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define SDEBUG2(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_DEBUG2, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define SDEBUG3(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_DEBUG3, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define SDEBUG4(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_DEBUG4, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#endif // CSCORE_LOG_H_
|
||||
|
||||
@@ -650,7 +650,7 @@ void MjpegServerImpl::Stop() {
|
||||
// Send HTTP response and a stream of JPG-frames
|
||||
void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
|
||||
if (m_noStreaming) {
|
||||
SERROR("{}", "Too many simultaneous client streams");
|
||||
SERROR("Too many simultaneous client streams");
|
||||
SendError(os, 503, "Too many simultaneous streams");
|
||||
return;
|
||||
}
|
||||
@@ -663,7 +663,7 @@ void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
|
||||
SendHeader(oss, 200, "OK", "multipart/x-mixed-replace;boundary=" BOUNDARY);
|
||||
os << oss.str();
|
||||
|
||||
SDEBUG("{}", "Headers send, sending stream now");
|
||||
SDEBUG("Headers send, sending stream now");
|
||||
|
||||
Frame::Time lastFrameTime = 0;
|
||||
Frame::Time timePerFrame = 0;
|
||||
@@ -685,7 +685,7 @@ void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
continue;
|
||||
}
|
||||
SDEBUG4("{}", "waiting for frame");
|
||||
SDEBUG4("waiting for frame");
|
||||
Frame frame = source->GetNextFrame(0.225); // blocks
|
||||
if (!m_active) {
|
||||
break;
|
||||
@@ -783,7 +783,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
wpi::SmallString<128> reqBuf;
|
||||
std::string_view req = is.getline(reqBuf, 4096);
|
||||
if (is.has_error()) {
|
||||
SDEBUG("{}", "error getting request string");
|
||||
SDEBUG("error getting request string");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -824,7 +824,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
} else if (req.find("GET / ") != std::string_view::npos || req == "GET /\n") {
|
||||
kind = kRootPage;
|
||||
} else {
|
||||
SDEBUG("{}", "HTTP request resource not found");
|
||||
SDEBUG("HTTP request resource not found");
|
||||
SendError(os, 404, "Resource not found");
|
||||
return;
|
||||
}
|
||||
@@ -866,11 +866,11 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
SendHeader(os, 200, "OK", "text/plain");
|
||||
os << "Ignored due to no connected source."
|
||||
<< "\r\n";
|
||||
SDEBUG("{}", "Ignored due to no connected source.");
|
||||
SDEBUG("Ignored due to no connected source.");
|
||||
}
|
||||
break;
|
||||
case kGetSettings:
|
||||
SDEBUG("{}", "request for JSON file");
|
||||
SDEBUG("request for JSON file");
|
||||
if (auto source = GetSource()) {
|
||||
SendJSON(os, *source, true);
|
||||
} else {
|
||||
@@ -878,7 +878,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
}
|
||||
break;
|
||||
case kGetSourceConfig:
|
||||
SDEBUG("{}", "request for JSON file");
|
||||
SDEBUG("request for JSON file");
|
||||
if (auto source = GetSource()) {
|
||||
SendHeader(os, 200, "OK", "application/json");
|
||||
CS_Status status = CS_OK;
|
||||
@@ -889,7 +889,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
}
|
||||
break;
|
||||
case kRootPage:
|
||||
SDEBUG("{}", "request for root page");
|
||||
SDEBUG("request for root page");
|
||||
SendHeader(os, 200, "OK", "text/html");
|
||||
if (auto source = GetSource()) {
|
||||
SendHTML(os, *source, false);
|
||||
@@ -900,7 +900,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
break;
|
||||
}
|
||||
|
||||
SDEBUG("{}", "leaving HTTP client thread");
|
||||
SDEBUG("leaving HTTP client thread");
|
||||
}
|
||||
|
||||
// worker thread for clients that connected to this server
|
||||
@@ -927,7 +927,7 @@ void MjpegServerImpl::ServerThreadMain() {
|
||||
return;
|
||||
}
|
||||
|
||||
SDEBUG("{}", "waiting for clients to connect");
|
||||
SDEBUG("waiting for clients to connect");
|
||||
while (m_active) {
|
||||
auto stream = m_acceptor->accept();
|
||||
if (!stream) {
|
||||
@@ -977,7 +977,7 @@ void MjpegServerImpl::ServerThreadMain() {
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
|
||||
SDEBUG("{}", "leaving server thread");
|
||||
SDEBUG("leaving server thread");
|
||||
}
|
||||
|
||||
void MjpegServerImpl::SetSourceImpl(std::shared_ptr<SourceImpl> source) {
|
||||
|
||||
@@ -27,7 +27,7 @@ int PropertyContainer::GetPropertyIndex(std::string_view name) const {
|
||||
return ndx;
|
||||
}
|
||||
|
||||
wpi::span<int> PropertyContainer::EnumerateProperties(
|
||||
std::span<int> PropertyContainer::EnumerateProperties(
|
||||
wpi::SmallVectorImpl<int>& vec, CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status)) {
|
||||
return {};
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/StringMap.h>
|
||||
#include <wpi/mutex.h>
|
||||
#include <wpi/span.h>
|
||||
|
||||
#include "PropertyImpl.h"
|
||||
#include "cscore_cpp.h"
|
||||
@@ -33,7 +33,7 @@ class PropertyContainer {
|
||||
virtual ~PropertyContainer() = default;
|
||||
|
||||
int GetPropertyIndex(std::string_view name) const;
|
||||
wpi::span<int> EnumerateProperties(wpi::SmallVectorImpl<int>& vec,
|
||||
std::span<int> EnumerateProperties(wpi::SmallVectorImpl<int>& vec,
|
||||
CS_Status* status) const;
|
||||
CS_PropertyKind GetPropertyKind(int property) const;
|
||||
std::string_view GetPropertyName(int property,
|
||||
|
||||
@@ -127,7 +127,7 @@ void RawSinkImpl::ThreadMain() {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
continue;
|
||||
}
|
||||
SDEBUG4("{}", "waiting for frame");
|
||||
SDEBUG4("waiting for frame");
|
||||
Frame frame = source->GetNextFrame(); // blocks
|
||||
if (!m_active) {
|
||||
break;
|
||||
|
||||
@@ -76,7 +76,7 @@ Frame SourceImpl::GetCurFrame() {
|
||||
Frame SourceImpl::GetNextFrame() {
|
||||
std::unique_lock lock{m_frameMutex};
|
||||
auto oldTime = m_frame.GetTime();
|
||||
m_frameCv.wait(lock, [=] { return m_frame.GetTime() != oldTime; });
|
||||
m_frameCv.wait(lock, [=, this] { return m_frame.GetTime() != oldTime; });
|
||||
return m_frame;
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ Frame SourceImpl::GetNextFrame(double timeout) {
|
||||
auto oldTime = m_frame.GetTime();
|
||||
if (!m_frameCv.wait_for(
|
||||
lock, std::chrono::milliseconds(static_cast<int>(timeout * 1000)),
|
||||
[=] { return m_frame.GetTime() != oldTime; })) {
|
||||
[=, this] { return m_frame.GetTime() != oldTime; })) {
|
||||
m_frame = Frame{*this, "timed out getting frame", wpi::Now()};
|
||||
}
|
||||
return m_frame;
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
#define CSCORE_UNLIMITEDHANDLERESOURCE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/mutex.h>
|
||||
#include <wpi/span.h>
|
||||
|
||||
namespace cs {
|
||||
|
||||
@@ -50,7 +50,7 @@ class UnlimitedHandleResource {
|
||||
std::shared_ptr<TStruct> Free(THandle handle);
|
||||
|
||||
template <typename T>
|
||||
wpi::span<T> GetAll(wpi::SmallVectorImpl<T>& vec);
|
||||
std::span<T> GetAll(wpi::SmallVectorImpl<T>& vec);
|
||||
|
||||
std::vector<std::shared_ptr<TStruct>> FreeAll();
|
||||
|
||||
@@ -151,7 +151,7 @@ UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::Free(
|
||||
|
||||
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
|
||||
template <typename T>
|
||||
inline wpi::span<T>
|
||||
inline std::span<T>
|
||||
UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::GetAll(
|
||||
wpi::SmallVectorImpl<T>& vec) {
|
||||
ForEach([&](THandle handle, const TStruct& data) { vec.push_back(handle); });
|
||||
|
||||
@@ -286,7 +286,7 @@ CS_Property GetSourceProperty(CS_Source source, std::string_view name,
|
||||
return Handle{source, property, Handle::kProperty};
|
||||
}
|
||||
|
||||
wpi::span<CS_Property> EnumerateSourceProperties(
|
||||
std::span<CS_Property> EnumerateSourceProperties(
|
||||
CS_Source source, wpi::SmallVectorImpl<CS_Property>& vec,
|
||||
CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
@@ -398,7 +398,7 @@ std::vector<VideoMode> EnumerateSourceVideoModes(CS_Source source,
|
||||
return data->source->EnumerateVideoModes(status);
|
||||
}
|
||||
|
||||
wpi::span<CS_Sink> EnumerateSourceSinks(CS_Source source,
|
||||
std::span<CS_Sink> EnumerateSourceSinks(CS_Source source,
|
||||
wpi::SmallVectorImpl<CS_Sink>& vec,
|
||||
CS_Status* status) {
|
||||
auto& inst = Instance::GetInstance();
|
||||
@@ -583,7 +583,7 @@ CS_Property GetSinkProperty(CS_Sink sink, std::string_view name,
|
||||
return Handle{sink, property, Handle::kSinkProperty};
|
||||
}
|
||||
|
||||
wpi::span<CS_Property> EnumerateSinkProperties(
|
||||
std::span<CS_Property> EnumerateSinkProperties(
|
||||
CS_Sink sink, wpi::SmallVectorImpl<CS_Property>& vec, CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data) {
|
||||
@@ -865,12 +865,12 @@ void Shutdown() {
|
||||
// Utility Functions
|
||||
//
|
||||
|
||||
wpi::span<CS_Source> EnumerateSourceHandles(
|
||||
std::span<CS_Source> EnumerateSourceHandles(
|
||||
wpi::SmallVectorImpl<CS_Source>& vec, CS_Status* status) {
|
||||
return Instance::GetInstance().EnumerateSourceHandles(vec);
|
||||
}
|
||||
|
||||
wpi::span<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec,
|
||||
std::span<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec,
|
||||
CS_Status* status) {
|
||||
return Instance::GetInstance().EnumerateSinkHandles(vec);
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <exception>
|
||||
#include <span>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <opencv2/core/core.hpp>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/jni_util.h>
|
||||
#include <wpi/span.h>
|
||||
|
||||
#include "cscore_cpp.h"
|
||||
#include "cscore_cv.h"
|
||||
@@ -296,7 +296,7 @@ static jobject MakeJObject(JNIEnv* env, const cs::RawEvent& event) {
|
||||
}
|
||||
|
||||
static jobjectArray MakeJObject(JNIEnv* env,
|
||||
wpi::span<const cs::RawEvent> arr) {
|
||||
std::span<const cs::RawEvent> arr) {
|
||||
jobjectArray jarr = env->NewObjectArray(arr.size(), videoEventCls, nullptr);
|
||||
if (!jarr) {
|
||||
return nullptr;
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include <functional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/span.h>
|
||||
|
||||
#include "cscore_c.h"
|
||||
|
||||
@@ -203,7 +203,7 @@ CS_Source CreateUsbCameraPath(std::string_view name, std::string_view path,
|
||||
CS_Source CreateHttpCamera(std::string_view name, std::string_view url,
|
||||
CS_HttpCameraKind kind, CS_Status* status);
|
||||
CS_Source CreateHttpCamera(std::string_view name,
|
||||
wpi::span<const std::string> urls,
|
||||
std::span<const std::string> urls,
|
||||
CS_HttpCameraKind kind, CS_Status* status);
|
||||
CS_Source CreateCvSource(std::string_view name, const VideoMode& mode,
|
||||
CS_Status* status);
|
||||
@@ -230,7 +230,7 @@ bool IsSourceConnected(CS_Source source, CS_Status* status);
|
||||
bool IsSourceEnabled(CS_Source source, CS_Status* status);
|
||||
CS_Property GetSourceProperty(CS_Source source, std::string_view name,
|
||||
CS_Status* status);
|
||||
wpi::span<CS_Property> EnumerateSourceProperties(
|
||||
std::span<CS_Property> EnumerateSourceProperties(
|
||||
CS_Source source, wpi::SmallVectorImpl<CS_Property>& vec,
|
||||
CS_Status* status);
|
||||
VideoMode GetSourceVideoMode(CS_Source source, CS_Status* status);
|
||||
@@ -249,7 +249,7 @@ std::string GetSourceConfigJson(CS_Source source, CS_Status* status);
|
||||
wpi::json GetSourceConfigJsonObject(CS_Source source, CS_Status* status);
|
||||
std::vector<VideoMode> EnumerateSourceVideoModes(CS_Source source,
|
||||
CS_Status* status);
|
||||
wpi::span<CS_Sink> EnumerateSourceSinks(CS_Source source,
|
||||
std::span<CS_Sink> EnumerateSourceSinks(CS_Source source,
|
||||
wpi::SmallVectorImpl<CS_Sink>& vec,
|
||||
CS_Status* status);
|
||||
CS_Source CopySource(CS_Source source, CS_Status* status);
|
||||
@@ -285,7 +285,7 @@ UsbCameraInfo GetUsbCameraInfo(CS_Source source, CS_Status* status);
|
||||
* @{
|
||||
*/
|
||||
CS_HttpCameraKind GetHttpCameraKind(CS_Source source, CS_Status* status);
|
||||
void SetHttpCameraUrls(CS_Source source, wpi::span<const std::string> urls,
|
||||
void SetHttpCameraUrls(CS_Source source, std::span<const std::string> urls,
|
||||
CS_Status* status);
|
||||
std::vector<std::string> GetHttpCameraUrls(CS_Source source, CS_Status* status);
|
||||
/** @} */
|
||||
@@ -304,7 +304,7 @@ CS_Property CreateSourceProperty(CS_Source source, std::string_view name,
|
||||
int step, int defaultValue, int value,
|
||||
CS_Status* status);
|
||||
void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
|
||||
wpi::span<const std::string> choices,
|
||||
std::span<const std::string> choices,
|
||||
CS_Status* status);
|
||||
/** @} */
|
||||
|
||||
@@ -335,7 +335,7 @@ std::string_view GetSinkDescription(CS_Sink sink,
|
||||
CS_Status* status);
|
||||
CS_Property GetSinkProperty(CS_Sink sink, std::string_view name,
|
||||
CS_Status* status);
|
||||
wpi::span<CS_Property> EnumerateSinkProperties(
|
||||
std::span<CS_Property> EnumerateSinkProperties(
|
||||
CS_Sink sink, wpi::SmallVectorImpl<CS_Property>& vec, CS_Status* status);
|
||||
void SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status);
|
||||
CS_Property GetSinkSourceProperty(CS_Sink sink, std::string_view name,
|
||||
@@ -430,9 +430,9 @@ void Shutdown();
|
||||
*/
|
||||
std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status);
|
||||
|
||||
wpi::span<CS_Source> EnumerateSourceHandles(
|
||||
std::span<CS_Source> EnumerateSourceHandles(
|
||||
wpi::SmallVectorImpl<CS_Source>& vec, CS_Status* status);
|
||||
wpi::span<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec,
|
||||
std::span<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec,
|
||||
CS_Status* status);
|
||||
|
||||
std::string GetHostname();
|
||||
|
||||
@@ -6,13 +6,12 @@
|
||||
#define CSCORE_CSCORE_OO_H_
|
||||
|
||||
#include <initializer_list>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/span.h>
|
||||
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
namespace cs {
|
||||
@@ -516,7 +515,7 @@ class HttpCamera : public VideoCamera {
|
||||
* @param urls Array of Camera URLs
|
||||
* @param kind Camera kind (e.g. kAxis)
|
||||
*/
|
||||
HttpCamera(std::string_view name, wpi::span<const std::string> urls,
|
||||
HttpCamera(std::string_view name, std::span<const std::string> urls,
|
||||
HttpCameraKind kind = kUnknown);
|
||||
|
||||
/**
|
||||
@@ -541,7 +540,7 @@ class HttpCamera : public VideoCamera {
|
||||
/**
|
||||
* Change the URLs used to connect to the camera.
|
||||
*/
|
||||
void SetUrls(wpi::span<const std::string> urls);
|
||||
void SetUrls(std::span<const std::string> urls);
|
||||
|
||||
/**
|
||||
* Change the URLs used to connect to the camera.
|
||||
@@ -560,7 +559,7 @@ class HttpCamera : public VideoCamera {
|
||||
*/
|
||||
class AxisCamera : public HttpCamera {
|
||||
static std::string HostToUrl(std::string_view host);
|
||||
static std::vector<std::string> HostToUrl(wpi::span<const std::string> hosts);
|
||||
static std::vector<std::string> HostToUrl(std::span<const std::string> hosts);
|
||||
template <typename T>
|
||||
static std::vector<std::string> HostToUrl(std::initializer_list<T> hosts);
|
||||
|
||||
@@ -595,7 +594,7 @@ class AxisCamera : public HttpCamera {
|
||||
* @param name Source name (arbitrary unique identifier)
|
||||
* @param hosts Array of Camera host IPs/DNS names
|
||||
*/
|
||||
AxisCamera(std::string_view name, wpi::span<const std::string> hosts);
|
||||
AxisCamera(std::string_view name, std::span<const std::string> hosts);
|
||||
|
||||
/**
|
||||
* Create a source for an Axis IP camera.
|
||||
@@ -696,7 +695,7 @@ class ImageSource : public VideoSource {
|
||||
* @param choices Choices
|
||||
*/
|
||||
void SetEnumPropertyChoices(const VideoProperty& property,
|
||||
wpi::span<const std::string> choices);
|
||||
std::span<const std::string> choices);
|
||||
|
||||
/**
|
||||
* Configure enum property choices.
|
||||
|
||||
@@ -302,7 +302,7 @@ inline HttpCamera::HttpCamera(std::string_view name, const std::string& url,
|
||||
: HttpCamera(name, std::string_view{url}, kind) {}
|
||||
|
||||
inline HttpCamera::HttpCamera(std::string_view name,
|
||||
wpi::span<const std::string> urls,
|
||||
std::span<const std::string> urls,
|
||||
HttpCameraKind kind) {
|
||||
m_handle = CreateHttpCamera(
|
||||
name, urls, static_cast<CS_HttpCameraKind>(static_cast<int>(kind)),
|
||||
@@ -329,7 +329,7 @@ inline HttpCamera::HttpCameraKind HttpCamera::GetHttpCameraKind() const {
|
||||
static_cast<int>(::cs::GetHttpCameraKind(m_handle, &m_status)));
|
||||
}
|
||||
|
||||
inline void HttpCamera::SetUrls(wpi::span<const std::string> urls) {
|
||||
inline void HttpCamera::SetUrls(std::span<const std::string> urls) {
|
||||
m_status = 0;
|
||||
::cs::SetHttpCameraUrls(m_handle, urls, &m_status);
|
||||
}
|
||||
@@ -351,7 +351,7 @@ inline std::vector<std::string> HttpCamera::GetUrls() const {
|
||||
}
|
||||
|
||||
inline std::vector<std::string> AxisCamera::HostToUrl(
|
||||
wpi::span<const std::string> hosts) {
|
||||
std::span<const std::string> hosts) {
|
||||
std::vector<std::string> rv;
|
||||
rv.reserve(hosts.size());
|
||||
for (const auto& host : hosts) {
|
||||
@@ -381,7 +381,7 @@ inline AxisCamera::AxisCamera(std::string_view name, const std::string& host)
|
||||
: HttpCamera(name, HostToUrl(std::string_view{host}), kAxis) {}
|
||||
|
||||
inline AxisCamera::AxisCamera(std::string_view name,
|
||||
wpi::span<const std::string> hosts)
|
||||
std::span<const std::string> hosts)
|
||||
: HttpCamera(name, HostToUrl(hosts), kAxis) {}
|
||||
|
||||
template <typename T>
|
||||
@@ -452,7 +452,7 @@ inline VideoProperty ImageSource::CreateStringProperty(std::string_view name,
|
||||
}
|
||||
|
||||
inline void ImageSource::SetEnumPropertyChoices(
|
||||
const VideoProperty& property, wpi::span<const std::string> choices) {
|
||||
const VideoProperty& property, std::span<const std::string> choices) {
|
||||
m_status = 0;
|
||||
SetSourceEnumPropertyChoices(m_handle, property.m_handle, choices, &m_status);
|
||||
}
|
||||
|
||||
@@ -140,6 +140,12 @@ int UsbCameraImpl::RawToPercentage(const UsbCameraProperty& rawProp,
|
||||
}
|
||||
return 100;
|
||||
}
|
||||
// Arducam OV9281 exposure setting quirk
|
||||
if (m_ov9281_exposure && rawProp.name == "raw_exposure_absolute" &&
|
||||
rawProp.minimum == 1 && rawProp.maximum == 5000) {
|
||||
// real range is 1-75
|
||||
return 100.0 * (rawValue - 1) / (75 - 1);
|
||||
}
|
||||
return 100.0 * (rawValue - rawProp.minimum) /
|
||||
(rawProp.maximum - rawProp.minimum);
|
||||
}
|
||||
@@ -159,6 +165,12 @@ int UsbCameraImpl::PercentageToRaw(const UsbCameraProperty& rawProp,
|
||||
}
|
||||
return quirkLifeCamHd3000[ndx];
|
||||
}
|
||||
// Arducam OV9281 exposure setting quirk
|
||||
if (m_ov9281_exposure && rawProp.name == "raw_exposure_absolute" &&
|
||||
rawProp.minimum == 1 && rawProp.maximum == 5000) {
|
||||
// real range is 1-75
|
||||
return 1 + (75 - 1) * (percentValue / 100.0);
|
||||
}
|
||||
return rawProp.minimum +
|
||||
(rawProp.maximum - rawProp.minimum) * (percentValue / 100.0);
|
||||
}
|
||||
@@ -454,7 +466,7 @@ void UsbCameraImpl::CameraThreadMain() {
|
||||
|
||||
// Handle notify events
|
||||
if (notify_fd >= 0 && FD_ISSET(notify_fd, &readfds)) {
|
||||
SDEBUG4("{}", "notify event");
|
||||
SDEBUG4("notify event");
|
||||
struct inotify_event event;
|
||||
do {
|
||||
// Read the event structure
|
||||
@@ -483,7 +495,7 @@ void UsbCameraImpl::CameraThreadMain() {
|
||||
|
||||
// Handle commands
|
||||
if (command_fd >= 0 && FD_ISSET(command_fd, &readfds)) {
|
||||
SDEBUG4("{}", "got command");
|
||||
SDEBUG4("got command");
|
||||
// Read it to clear
|
||||
eventfd_t val;
|
||||
eventfd_read(command_fd, &val);
|
||||
@@ -493,7 +505,7 @@ void UsbCameraImpl::CameraThreadMain() {
|
||||
|
||||
// Handle frames
|
||||
if (m_streaming && fd >= 0 && FD_ISSET(fd, &readfds)) {
|
||||
SDEBUG4("{}", "grabbing image");
|
||||
SDEBUG4("grabbing image");
|
||||
|
||||
// Dequeue buffer
|
||||
struct v4l2_buffer buf;
|
||||
@@ -501,7 +513,7 @@ void UsbCameraImpl::CameraThreadMain() {
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
if (DoIoctl(fd, VIDIOC_DQBUF, &buf) != 0) {
|
||||
SWARNING("{}", "could not dequeue buffer");
|
||||
SWARNING("could not dequeue buffer");
|
||||
wasStreaming = m_streaming;
|
||||
DeviceStreamOff();
|
||||
DeviceDisconnect();
|
||||
@@ -525,7 +537,7 @@ void UsbCameraImpl::CameraThreadMain() {
|
||||
bool good = true;
|
||||
if (m_mode.pixelFormat == VideoMode::kMJPEG &&
|
||||
!GetJpegSize(image, &width, &height)) {
|
||||
SWARNING("{}", "invalid JPEG image received from camera");
|
||||
SWARNING("invalid JPEG image received from camera");
|
||||
good = false;
|
||||
}
|
||||
if (good) {
|
||||
@@ -536,7 +548,7 @@ void UsbCameraImpl::CameraThreadMain() {
|
||||
|
||||
// Requeue buffer
|
||||
if (DoIoctl(fd, VIDIOC_QBUF, &buf) != 0) {
|
||||
SWARNING("{}", "could not requeue buffer");
|
||||
SWARNING("could not requeue buffer");
|
||||
wasStreaming = m_streaming;
|
||||
DeviceStreamOff();
|
||||
DeviceDisconnect();
|
||||
@@ -579,7 +591,7 @@ void UsbCameraImpl::DeviceConnect() {
|
||||
}
|
||||
|
||||
// Try to open the device
|
||||
SDEBUG3("{}", "opening device");
|
||||
SDEBUG3("opening device");
|
||||
int fd = open(m_path.c_str(), O_RDWR);
|
||||
if (fd < 0) {
|
||||
return;
|
||||
@@ -587,7 +599,7 @@ void UsbCameraImpl::DeviceConnect() {
|
||||
m_fd = fd;
|
||||
|
||||
// Get capabilities
|
||||
SDEBUG3("{}", "getting capabilities");
|
||||
SDEBUG3("getting capabilities");
|
||||
struct v4l2_capability vcap;
|
||||
std::memset(&vcap, 0, sizeof(vcap));
|
||||
if (DoIoctl(fd, VIDIOC_QUERYCAP, &vcap) >= 0) {
|
||||
@@ -599,18 +611,18 @@ void UsbCameraImpl::DeviceConnect() {
|
||||
|
||||
// Get or restore video mode
|
||||
if (!m_properties_cached) {
|
||||
SDEBUG3("{}", "caching properties");
|
||||
SDEBUG3("caching properties");
|
||||
DeviceCacheProperties();
|
||||
DeviceCacheVideoModes();
|
||||
DeviceCacheMode();
|
||||
m_properties_cached = true;
|
||||
} else {
|
||||
SDEBUG3("{}", "restoring video mode");
|
||||
SDEBUG3("restoring video mode");
|
||||
DeviceSetMode();
|
||||
DeviceSetFPS();
|
||||
|
||||
// Restore settings
|
||||
SDEBUG3("{}", "restoring settings");
|
||||
SDEBUG3("restoring settings");
|
||||
std::unique_lock lock2(m_mutex);
|
||||
for (size_t i = 0; i < m_propertyData.size(); ++i) {
|
||||
const auto prop =
|
||||
@@ -625,21 +637,21 @@ void UsbCameraImpl::DeviceConnect() {
|
||||
}
|
||||
|
||||
// Request buffers
|
||||
SDEBUG3("{}", "allocating buffers");
|
||||
SDEBUG3("allocating buffers");
|
||||
struct v4l2_requestbuffers rb;
|
||||
std::memset(&rb, 0, sizeof(rb));
|
||||
rb.count = kNumBuffers;
|
||||
rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
rb.memory = V4L2_MEMORY_MMAP;
|
||||
if (DoIoctl(fd, VIDIOC_REQBUFS, &rb) != 0) {
|
||||
SWARNING("{}", "could not allocate buffers");
|
||||
SWARNING("could not allocate buffers");
|
||||
close(fd);
|
||||
m_fd = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Map buffers
|
||||
SDEBUG3("{}", "mapping buffers");
|
||||
SDEBUG3("mapping buffers");
|
||||
for (int i = 0; i < kNumBuffers; ++i) {
|
||||
struct v4l2_buffer buf;
|
||||
std::memset(&buf, 0, sizeof(buf));
|
||||
@@ -689,7 +701,7 @@ bool UsbCameraImpl::DeviceStreamOn() {
|
||||
}
|
||||
|
||||
// Queue buffers
|
||||
SDEBUG3("{}", "queuing buffers");
|
||||
SDEBUG3("queuing buffers");
|
||||
for (int i = 0; i < kNumBuffers; ++i) {
|
||||
struct v4l2_buffer buf;
|
||||
std::memset(&buf, 0, sizeof(buf));
|
||||
@@ -708,7 +720,6 @@ bool UsbCameraImpl::DeviceStreamOn() {
|
||||
if (errno == ENOSPC) {
|
||||
// indicates too much USB bandwidth requested
|
||||
SERROR(
|
||||
"{}",
|
||||
"could not start streaming due to USB bandwidth limitations; try a "
|
||||
"lower resolution or a different pixel format (VIDIOC_STREAMON: "
|
||||
"No space left on device)");
|
||||
@@ -718,7 +729,7 @@ bool UsbCameraImpl::DeviceStreamOn() {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
SDEBUG4("{}", "enabled streaming");
|
||||
SDEBUG4("enabled streaming");
|
||||
m_streaming = true;
|
||||
return true;
|
||||
}
|
||||
@@ -735,7 +746,7 @@ bool UsbCameraImpl::DeviceStreamOff() {
|
||||
if (DoIoctl(fd, VIDIOC_STREAMOFF, &type) != 0) {
|
||||
return false;
|
||||
}
|
||||
SDEBUG4("{}", "disabled streaming");
|
||||
SDEBUG4("disabled streaming");
|
||||
m_streaming = false;
|
||||
return true;
|
||||
}
|
||||
@@ -1000,7 +1011,7 @@ void UsbCameraImpl::DeviceCacheMode() {
|
||||
#endif
|
||||
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (DoIoctl(fd, VIDIOC_G_FMT, &vfmt) != 0) {
|
||||
SERROR("{}", "could not read current video mode");
|
||||
SERROR("could not read current video mode");
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_mode = VideoMode{VideoMode::kMJPEG, 320, 240, 30};
|
||||
return;
|
||||
@@ -1385,6 +1396,7 @@ void UsbCameraImpl::SetQuirks() {
|
||||
std::string_view desc = GetDescription(descbuf);
|
||||
m_lifecam_exposure = wpi::ends_with(desc, "LifeCam HD-3000") ||
|
||||
wpi::ends_with(desc, "LifeCam Cinema (TM)");
|
||||
m_ov9281_exposure = wpi::contains(desc, "OV9281");
|
||||
m_picamera = wpi::ends_with(desc, "mmal service");
|
||||
|
||||
int deviceNum = GetDeviceNum(m_path.c_str());
|
||||
@@ -1668,7 +1680,7 @@ std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
|
||||
::closedir(dp);
|
||||
} else {
|
||||
// *status = ;
|
||||
WPI_ERROR(Instance::GetInstance().logger, "{}", "Could not open /dev");
|
||||
WPI_ERROR(Instance::GetInstance().logger, "Could not open /dev");
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
@@ -161,6 +161,7 @@ class UsbCameraImpl : public SourceImpl {
|
||||
// Quirks
|
||||
bool m_lifecam_exposure{false}; // Microsoft LifeCam exposure
|
||||
bool m_ps3eyecam_exposure{false}; // PS3 Eyecam exposure
|
||||
bool m_ov9281_exposure{false}; // Arducam OV9281 exposure
|
||||
bool m_picamera{false}; // Raspberry Pi camera
|
||||
|
||||
//
|
||||
|
||||
@@ -486,7 +486,7 @@ bool UsbCameraImpl::DeviceConnect() {
|
||||
SINFO("Connecting to USB camera on {}", m_path);
|
||||
}
|
||||
|
||||
SDEBUG3("{}", "opening device");
|
||||
SDEBUG3("opening device");
|
||||
|
||||
const wchar_t* path = m_widePath.c_str();
|
||||
m_mediaSource = CreateVideoCaptureDevice(path);
|
||||
@@ -520,13 +520,13 @@ bool UsbCameraImpl::DeviceConnect() {
|
||||
}
|
||||
|
||||
if (!m_properties_cached) {
|
||||
SDEBUG3("{}", "caching properties");
|
||||
SDEBUG3("caching properties");
|
||||
DeviceCacheProperties();
|
||||
DeviceCacheVideoModes();
|
||||
DeviceCacheMode();
|
||||
m_properties_cached = true;
|
||||
} else {
|
||||
SDEBUG3("{}", "restoring video mode");
|
||||
SDEBUG3("restoring video mode");
|
||||
DeviceSetMode();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
|
||||
if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxarm32') && !project.hasProperty('onlylinuxarm64')) {
|
||||
if (!project.hasProperty('onlylinuxathena')) {
|
||||
|
||||
description = "roboRIO Team Number Setter"
|
||||
|
||||
@@ -24,19 +24,7 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
|
||||
def wpilibVersionFileInput = file("src/main/generate/WPILibVersion.cpp.in")
|
||||
def wpilibVersionFileOutput = file("$buildDir/generated/main/cpp/WPILibVersion.cpp")
|
||||
|
||||
nativeUtils {
|
||||
nativeDependencyContainer {
|
||||
libssh(getNativeDependencyTypeClass('WPIStaticMavenDependency')) {
|
||||
groupId = "edu.wpi.first.thirdparty.frc2023"
|
||||
artifactId = "libssh"
|
||||
headerClassifier = "headers"
|
||||
sourceClassifier = "sources"
|
||||
ext = "zip"
|
||||
version = '0.95-3'
|
||||
targetPlatforms.addAll(nativeUtils.wpi.platforms.desktopPlatforms)
|
||||
}
|
||||
}
|
||||
}
|
||||
apply from: "${rootDir}/shared/libssh.gradle"
|
||||
|
||||
task generateCppVersion() {
|
||||
description = 'Generates the wpilib version class'
|
||||
@@ -107,7 +95,7 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
|
||||
}
|
||||
}
|
||||
binaries.all {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm32 || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm64) {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
@@ -124,6 +112,9 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
|
||||
it.linker.args << '-framework' << 'Kerberos'
|
||||
} else {
|
||||
it.linker.args << '-lX11'
|
||||
if (it.targetPlatform.name.startsWith('linuxarm')) {
|
||||
it.linker.args << '-lGL'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ model {
|
||||
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.
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
class DataLogThread {
|
||||
public:
|
||||
explicit DataLogThread(wpi::log::DataLogReader reader)
|
||||
: m_reader{std::move(reader)}, m_thread{[=] { ReadMain(); }} {}
|
||||
: m_reader{std::move(reader)}, m_thread{[=, this] { ReadMain(); }} {}
|
||||
~DataLogThread();
|
||||
|
||||
bool IsDone() const { return m_done; }
|
||||
|
||||
@@ -75,6 +75,20 @@ void Downloader::DisplayRemoteDirSelector() {
|
||||
m_cv.notify_all();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Deselect All")) {
|
||||
for (auto&& download : m_downloadList) {
|
||||
download.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Select All")) {
|
||||
for (auto&& download : m_downloadList) {
|
||||
download.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Remote directory text box
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 20);
|
||||
if (ImGui::InputText("Remote Dir", &m_remoteDir,
|
||||
|
||||
@@ -107,7 +107,7 @@ static void RebuildEntryTree() {
|
||||
|
||||
// get to leaf
|
||||
auto nodes = &gEntryTree;
|
||||
for (auto part : wpi::drop_back(wpi::span{parts.begin(), parts.end()})) {
|
||||
for (auto part : wpi::drop_back(std::span{parts.begin(), parts.end()})) {
|
||||
auto it =
|
||||
std::find_if(nodes->begin(), nodes->end(),
|
||||
[&](const auto& node) { return node.name == part; });
|
||||
|
||||
@@ -90,7 +90,7 @@ size_t File::AsyncRead(void* data, uint32_t len, AsyncId id) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
size_t File::Write(wpi::span<const uint8_t> data) {
|
||||
size_t File::Write(std::span<const uint8_t> data) {
|
||||
auto rv = sftp_write(m_handle, data.data(), data.size());
|
||||
if (rv < 0) {
|
||||
throw Exception{m_handle->sftp};
|
||||
|
||||
@@ -7,13 +7,12 @@
|
||||
#include <libssh/libssh.h>
|
||||
#include <libssh/sftp.h>
|
||||
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/span.h>
|
||||
|
||||
namespace sftp {
|
||||
|
||||
struct Attributes {
|
||||
@@ -53,7 +52,7 @@ class File {
|
||||
size_t Read(void* buf, uint32_t count);
|
||||
AsyncId AsyncReadBegin(uint32_t len) const;
|
||||
size_t AsyncRead(void* data, uint32_t len, AsyncId id);
|
||||
size_t Write(wpi::span<const uint8_t> data);
|
||||
size_t Write(std::span<const uint8_t> data);
|
||||
|
||||
void Seek(uint64_t offset);
|
||||
uint64_t Tell() const;
|
||||
|
||||
@@ -4,6 +4,7 @@ plugins {
|
||||
}
|
||||
|
||||
evaluationDependsOn(':wpiutil')
|
||||
evaluationDependsOn(':wpinet')
|
||||
evaluationDependsOn(':ntcore')
|
||||
evaluationDependsOn(':cscore')
|
||||
evaluationDependsOn(':hal')
|
||||
@@ -28,6 +29,7 @@ def cppIncludeRoots = []
|
||||
|
||||
cppProjectZips.add(project(':hal').cppHeadersZip)
|
||||
cppProjectZips.add(project(':wpiutil').cppHeadersZip)
|
||||
cppProjectZips.add(project(':wpinet').cppHeadersZip)
|
||||
cppProjectZips.add(project(':ntcore').cppHeadersZip)
|
||||
cppProjectZips.add(project(':cscore').cppHeadersZip)
|
||||
cppProjectZips.add(project(':cameraserver').cppHeadersZip)
|
||||
@@ -57,7 +59,6 @@ doxygen {
|
||||
if (project.hasProperty('docWarningsAsErrors')) {
|
||||
// C++20 shims
|
||||
exclude 'wpi/ghc/filesystem.hpp'
|
||||
exclude 'wpi/span.h'
|
||||
|
||||
// Drake
|
||||
exclude 'drake/common/**'
|
||||
@@ -110,7 +111,7 @@ doxygen {
|
||||
// libuv
|
||||
exclude 'uv.h'
|
||||
exclude 'uv/**'
|
||||
exclude 'wpi/uv/**'
|
||||
exclude 'wpinet/uv/**'
|
||||
|
||||
// json
|
||||
exclude 'wpi/json.h'
|
||||
@@ -125,6 +126,15 @@ doxygen {
|
||||
exclude 'units/**'
|
||||
}
|
||||
|
||||
//TODO: building memory docs causes search to break
|
||||
exclude 'wpi/memory/**'
|
||||
|
||||
aliases 'effects=\\par <i>Effects:</i>^^',
|
||||
'notes=\\par <i>Notes:</i>^^',
|
||||
'requires=\\par <i>Requires:</i>^^',
|
||||
'requiredbe=\\par <i>Required Behavior:</i>^^',
|
||||
'concept{2}=<a href=\"md_doc_concepts.html#\1\">\2</a>',
|
||||
'defaultbe=\\par <i>Default Behavior:</i>^^'
|
||||
case_sense_names false
|
||||
extension_mapping 'inc=C++', 'no_extension=C++'
|
||||
extract_all true
|
||||
@@ -198,8 +208,10 @@ task generateJavaDocs(type: Javadoc) {
|
||||
dependsOn project(':wpilibj').generateJavaVersion
|
||||
dependsOn project(':hal').generateUsageReporting
|
||||
dependsOn project(':wpimath').generateNat
|
||||
dependsOn project(':ntcore').ntcoreGenerateJavaTypes
|
||||
source project(':hal').sourceSets.main.java
|
||||
source project(':wpiutil').sourceSets.main.java
|
||||
source project(':wpinet').sourceSets.main.java
|
||||
source project(':cscore').sourceSets.main.java
|
||||
source project(':ntcore').sourceSets.main.java
|
||||
source project(':wpimath').sourceSets.main.java
|
||||
|
||||
@@ -29,7 +29,7 @@ add_library(fieldImages ${field_images_resources_src})
|
||||
set_target_properties(fieldImages PROPERTIES DEBUG_POSTFIX "d")
|
||||
|
||||
set_property(TARGET fieldImages PROPERTY FOLDER "libraries")
|
||||
target_compile_features(fieldImages PUBLIC cxx_std_17)
|
||||
target_compile_features(fieldImages PUBLIC cxx_std_20)
|
||||
if (MSVC)
|
||||
target_compile_options(fieldImages PUBLIC /bigobj)
|
||||
endif()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
|
||||
if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxarm32') && !project.hasProperty('onlylinuxarm64')) {
|
||||
if (!project.hasProperty('onlylinuxathena')) {
|
||||
|
||||
apply plugin: 'cpp'
|
||||
apply plugin: 'c'
|
||||
|
||||
@@ -24,6 +24,7 @@ includeOtherLibs {
|
||||
^fmt/
|
||||
^frc/
|
||||
^imgui
|
||||
^networktables/
|
||||
^ntcore
|
||||
^wpi/
|
||||
^wpigui
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
|
||||
if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxarm32') && !project.hasProperty('onlylinuxarm64')) {
|
||||
if (!project.hasProperty('onlylinuxathena')) {
|
||||
|
||||
description = "A different kind of dashboard"
|
||||
|
||||
@@ -100,7 +100,7 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
|
||||
}
|
||||
}
|
||||
binaries.all {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm32 || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm64) {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
@@ -128,7 +128,7 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
|
||||
}
|
||||
}
|
||||
binaries.all {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm32 || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm64) {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
@@ -137,7 +137,7 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
|
||||
return
|
||||
}
|
||||
lib library: nativeName, linkage: 'static'
|
||||
lib project: ':ntcore', library: 'ntcore', linkage: 'shared'
|
||||
project(':ntcore').addNtcoreDependency(it, 'shared')
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
lib project: ':wpimath', library: 'wpimath', linkage: 'shared'
|
||||
@@ -169,14 +169,14 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
|
||||
}
|
||||
}
|
||||
binaries.all {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm32 || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm64) {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
lib project: ':cscore', library: 'cscore', linkage: 'static'
|
||||
lib library: 'glassnt', linkage: 'static'
|
||||
lib library: nativeName, linkage: 'static'
|
||||
lib project: ':ntcore', library: 'ntcore', linkage: 'static'
|
||||
project(':ntcore').addNtcoreDependency(it, 'static')
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'static'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
|
||||
lib project: ':wpimath', library: 'wpimath', linkage: 'static'
|
||||
@@ -190,6 +190,9 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
|
||||
it.linker.args << '-framework' << 'Metal' << '-framework' << 'MetalKit' << '-framework' << 'Cocoa' << '-framework' << 'IOKit' << '-framework' << 'CoreFoundation' << '-framework' << 'CoreVideo' << '-framework' << 'QuartzCore'
|
||||
} else {
|
||||
it.linker.args << '-lX11'
|
||||
if (it.targetPlatform.name.startsWith('linuxarm')) {
|
||||
it.linker.args << '-lGL'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,8 @@ model {
|
||||
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.
|
||||
|
||||
@@ -42,6 +42,7 @@ static std::unique_ptr<glass::NetworkTablesModel> gNetworkTablesModel;
|
||||
static std::unique_ptr<glass::NetworkTablesSettings> gNetworkTablesSettings;
|
||||
static glass::LogData gNetworkTablesLog;
|
||||
static std::unique_ptr<glass::Window> gNetworkTablesWindow;
|
||||
static std::unique_ptr<glass::Window> gNetworkTablesInfoWindow;
|
||||
static std::unique_ptr<glass::Window> gNetworkTablesSettingsWindow;
|
||||
static std::unique_ptr<glass::Window> gNetworkTablesLogWindow;
|
||||
|
||||
@@ -69,46 +70,41 @@ static void RemapEnterKeyCallback(GLFWwindow* window, int key, int scancode,
|
||||
}
|
||||
|
||||
static void NtInitialize() {
|
||||
// update window title when connection status changes
|
||||
auto inst = nt::GetDefaultInstance();
|
||||
auto poller = nt::CreateConnectionListenerPoller(inst);
|
||||
nt::AddPolledConnectionListener(poller, true);
|
||||
auto poller = nt::CreateListenerPoller(inst);
|
||||
nt::AddPolledListener(
|
||||
poller, inst,
|
||||
NT_EVENT_CONNECTION | NT_EVENT_IMMEDIATE | NT_EVENT_LOGMESSAGE);
|
||||
gui::AddEarlyExecute([poller] {
|
||||
auto win = gui::GetSystemWindow();
|
||||
if (!win) {
|
||||
return;
|
||||
}
|
||||
bool timedOut;
|
||||
for (auto&& event : nt::PollConnectionListener(poller, 0, &timedOut)) {
|
||||
if (event.connected) {
|
||||
glfwSetWindowTitle(
|
||||
win, fmt::format("Glass - Connected ({})", event.conn.remote_ip)
|
||||
.c_str());
|
||||
} else {
|
||||
glfwSetWindowTitle(win, "Glass - DISCONNECTED");
|
||||
for (auto&& event : nt::ReadListenerQueue(poller)) {
|
||||
if (auto connInfo = event.GetConnectionInfo()) {
|
||||
// update window title when connection status changes
|
||||
if ((event.flags & NT_EVENT_CONNECTED) != 0) {
|
||||
glfwSetWindowTitle(
|
||||
win, fmt::format("Glass - Connected ({})", connInfo->remote_ip)
|
||||
.c_str());
|
||||
} else {
|
||||
glfwSetWindowTitle(win, "Glass - DISCONNECTED");
|
||||
}
|
||||
} else if (auto msg = event.GetLogMessage()) {
|
||||
const char* level = "";
|
||||
if (msg->level >= NT_LOG_CRITICAL) {
|
||||
level = "CRITICAL: ";
|
||||
} else if (msg->level >= NT_LOG_ERROR) {
|
||||
level = "ERROR: ";
|
||||
} else if (msg->level >= NT_LOG_WARNING) {
|
||||
level = "WARNING: ";
|
||||
}
|
||||
gNetworkTablesLog.Append(fmt::format(
|
||||
"{}{} ({}:{})\n", level, msg->message, msg->filename, msg->line));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// handle NetworkTables log messages
|
||||
auto logPoller = nt::CreateLoggerPoller(inst);
|
||||
nt::AddPolledLogger(logPoller, NT_LOG_INFO, 100);
|
||||
gui::AddEarlyExecute([logPoller] {
|
||||
bool timedOut;
|
||||
for (auto&& msg : nt::PollLogger(logPoller, 0, &timedOut)) {
|
||||
const char* level = "";
|
||||
if (msg.level >= NT_LOG_CRITICAL) {
|
||||
level = "CRITICAL: ";
|
||||
} else if (msg.level >= NT_LOG_ERROR) {
|
||||
level = "ERROR: ";
|
||||
} else if (msg.level >= NT_LOG_WARNING) {
|
||||
level = "WARNING: ";
|
||||
}
|
||||
gNetworkTablesLog.Append(fmt::format("{}{} ({}:{})\n", level, msg.message,
|
||||
msg.filename, msg.line));
|
||||
}
|
||||
});
|
||||
|
||||
gNetworkTablesLogWindow = std::make_unique<glass::Window>(
|
||||
glass::GetStorageRoot().GetChild("NetworkTables Log"),
|
||||
"NetworkTables Log", glass::Window::kHide);
|
||||
@@ -132,9 +128,21 @@ static void NtInitialize() {
|
||||
gNetworkTablesWindow->DisableRenamePopup();
|
||||
gui::AddLateExecute([] { gNetworkTablesWindow->Display(); });
|
||||
|
||||
// NetworkTables info window
|
||||
gNetworkTablesInfoWindow = std::make_unique<glass::Window>(
|
||||
glass::GetStorageRoot().GetChild("NetworkTables Info"),
|
||||
"NetworkTables Info");
|
||||
gNetworkTablesInfoWindow->SetView(glass::MakeFunctionView(
|
||||
[&] { glass::DisplayNetworkTablesInfo(gNetworkTablesModel.get()); }));
|
||||
gNetworkTablesInfoWindow->SetDefaultPos(250, 130);
|
||||
gNetworkTablesInfoWindow->SetDefaultSize(750, 145);
|
||||
gNetworkTablesInfoWindow->SetDefaultVisibility(glass::Window::kHide);
|
||||
gNetworkTablesInfoWindow->DisableRenamePopup();
|
||||
gui::AddLateExecute([] { gNetworkTablesInfoWindow->Display(); });
|
||||
|
||||
// NetworkTables settings window
|
||||
gNetworkTablesSettings = std::make_unique<glass::NetworkTablesSettings>(
|
||||
glass::GetStorageRoot().GetChild("NetworkTables Settings"));
|
||||
"glass", glass::GetStorageRoot().GetChild("NetworkTables Settings"));
|
||||
gui::AddEarlyExecute([] { gNetworkTablesSettings->Update(); });
|
||||
|
||||
gNetworkTablesSettingsWindow = std::make_unique<glass::Window>(
|
||||
@@ -218,6 +226,9 @@ int main(int argc, char** argv) {
|
||||
if (gNetworkTablesWindow) {
|
||||
gNetworkTablesWindow->DisplayMenuItem("NetworkTables View");
|
||||
}
|
||||
if (gNetworkTablesInfoWindow) {
|
||||
gNetworkTablesInfoWindow->DisplayMenuItem("NetworkTables Info");
|
||||
}
|
||||
if (gNetworkTablesLogWindow) {
|
||||
gNetworkTablesLogWindow->DisplayMenuItem("NetworkTables Log");
|
||||
}
|
||||
|
||||
@@ -254,7 +254,7 @@ Storage::Value& Storage::GetValue(std::string_view key) {
|
||||
} \
|
||||
\
|
||||
std::vector<ArrCType>& Storage::Get##CapsName##Array( \
|
||||
std::string_view key, wpi::span<const ArrCType> defaultVal) { \
|
||||
std::string_view key, std::span<const ArrCType> defaultVal) { \
|
||||
auto& valuePtr = m_values[key]; \
|
||||
bool setValue = false; \
|
||||
if (!valuePtr) { \
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <wpi/numbers>
|
||||
#include <numbers>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
@@ -54,7 +54,7 @@ void glass::DisplayGyro(GyroModel* m) {
|
||||
|
||||
// Draw the spokes at every 5 degrees and a "major" spoke every 45 degrees.
|
||||
for (int i = -175; i <= 180; i += 5) {
|
||||
double radians = i * 2 * wpi::numbers::pi / 360.0;
|
||||
double radians = i * 2 * std::numbers::pi / 360.0;
|
||||
ImVec2 direction(std::sin(radians), -std::cos(radians));
|
||||
|
||||
bool major = i % 45 == 0;
|
||||
@@ -74,7 +74,7 @@ void glass::DisplayGyro(GyroModel* m) {
|
||||
|
||||
draw->AddCircleFilled(center, radius * 0.075, secondaryColor, 50);
|
||||
|
||||
double radians = value * 2 * wpi::numbers::pi / 360.0;
|
||||
double radians = value * 2 * std::numbers::pi / 360.0;
|
||||
draw->AddLine(
|
||||
center - ImVec2(1, 0),
|
||||
center + ImVec2(std::sin(radians), -std::cos(radians)) * radius * 0.95f,
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <wpi/numbers>
|
||||
#include <numbers>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
@@ -55,11 +55,11 @@ void glass::DisplayDrive(DriveModel* m) {
|
||||
draw->AddTriangleFilled(
|
||||
arrowPos,
|
||||
arrowPos + ImRotate(ImVec2(0.0f, 7.5f),
|
||||
std::cos(angle + wpi::numbers::pi / 4),
|
||||
std::sin(angle + wpi::numbers::pi / 4)),
|
||||
std::cos(angle + std::numbers::pi / 4),
|
||||
std::sin(angle + std::numbers::pi / 4)),
|
||||
arrowPos + ImRotate(ImVec2(0.0f, 7.5f),
|
||||
std::cos(angle - wpi::numbers::pi / 4),
|
||||
std::sin(angle - wpi::numbers::pi / 4)),
|
||||
std::cos(angle - std::numbers::pi / 4),
|
||||
std::sin(angle - std::numbers::pi / 4)),
|
||||
color);
|
||||
};
|
||||
|
||||
@@ -88,30 +88,30 @@ void glass::DisplayDrive(DriveModel* m) {
|
||||
if (rotation != 0) {
|
||||
float radius = 60.0f;
|
||||
double a1 = 0.0;
|
||||
double a2 = wpi::numbers::pi / 2 * rotation;
|
||||
double a2 = std::numbers::pi / 2 * rotation;
|
||||
|
||||
// PathArcTo requires a_min <= a_max, and rotation can be negative
|
||||
if (a1 > a2) {
|
||||
draw->PathArcTo(center, radius, a2, a1, 20);
|
||||
draw->PathStroke(color, false);
|
||||
draw->PathArcTo(center, radius, a2 + wpi::numbers::pi,
|
||||
a1 + wpi::numbers::pi, 20);
|
||||
draw->PathArcTo(center, radius, a2 + std::numbers::pi,
|
||||
a1 + std::numbers::pi, 20);
|
||||
draw->PathStroke(color, false);
|
||||
} else {
|
||||
draw->PathArcTo(center, radius, a1, a2, 20);
|
||||
draw->PathStroke(color, false);
|
||||
draw->PathArcTo(center, radius, a1 + wpi::numbers::pi,
|
||||
a2 + wpi::numbers::pi, 20);
|
||||
draw->PathArcTo(center, radius, a1 + std::numbers::pi,
|
||||
a2 + std::numbers::pi, 20);
|
||||
draw->PathStroke(color, false);
|
||||
}
|
||||
|
||||
double adder = rotation < 0 ? wpi::numbers::pi : 0;
|
||||
double adder = rotation < 0 ? std::numbers::pi : 0;
|
||||
|
||||
auto arrowPos =
|
||||
center + ImVec2(radius * -std::cos(a2), radius * -std::sin(a2));
|
||||
drawArrow(arrowPos, a2 + adder);
|
||||
|
||||
a2 += wpi::numbers::pi;
|
||||
a2 += std::numbers::pi;
|
||||
arrowPos = center + ImVec2(radius * -std::cos(a2), radius * -std::sin(a2));
|
||||
drawArrow(arrowPos, a2 + adder);
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ class PopupState {
|
||||
|
||||
SelectedTargetInfo* GetTarget() { return &m_target; }
|
||||
FieldObjectModel* GetInsertModel() { return m_insertModel; }
|
||||
wpi::span<const frc::Pose2d> GetInsertPoses() const { return m_insertPoses; }
|
||||
std::span<const frc::Pose2d> GetInsertPoses() const { return m_insertPoses; }
|
||||
|
||||
void Display(Field2DModel* model, const FieldFrameData& ffd);
|
||||
|
||||
@@ -113,7 +113,7 @@ class PopupState {
|
||||
struct DisplayOptions {
|
||||
explicit DisplayOptions(const gui::Texture& texture) : texture{texture} {}
|
||||
|
||||
enum Style { kBoxImage = 0, kLine, kLineClosed, kTrack };
|
||||
enum Style { kBoxImage = 0, kLine, kLineClosed, kTrack, kHidden };
|
||||
|
||||
static constexpr Style kDefaultStyle = kBoxImage;
|
||||
static constexpr float kDefaultWeight = 4.0f;
|
||||
@@ -189,7 +189,7 @@ class ObjectInfo {
|
||||
|
||||
DisplayOptions GetDisplayOptions() const;
|
||||
void DisplaySettings();
|
||||
void DrawLine(ImDrawList* drawList, wpi::span<const ImVec2> points) const;
|
||||
void DrawLine(ImDrawList* drawList, std::span<const ImVec2> points) const;
|
||||
|
||||
void LoadImage();
|
||||
const gui::Texture& GetTexture() const { return m_texture; }
|
||||
@@ -547,7 +547,7 @@ ObjectInfo::ObjectInfo(Storage& storage)
|
||||
DisplayOptions::kDefaultLength.to<float>())},
|
||||
m_style{storage.GetString("style"),
|
||||
DisplayOptions::kDefaultStyle,
|
||||
{"Box/Image", "Line", "Line (Closed)", "Track"}},
|
||||
{"Box/Image", "Line", "Line (Closed)", "Track", "Hidden"}},
|
||||
m_weight{storage.GetFloat("weight", DisplayOptions::kDefaultWeight)},
|
||||
m_color{
|
||||
storage.GetFloatArray("color", DisplayOptions::kDefaultColorFloat)},
|
||||
@@ -617,7 +617,7 @@ void ObjectInfo::DisplaySettings() {
|
||||
}
|
||||
|
||||
void ObjectInfo::DrawLine(ImDrawList* drawList,
|
||||
wpi::span<const ImVec2> points) const {
|
||||
std::span<const ImVec2> points) const {
|
||||
if (points.empty()) {
|
||||
return;
|
||||
}
|
||||
@@ -840,6 +840,8 @@ void PoseFrameData::Draw(ImDrawList* drawList, std::vector<ImVec2>* center,
|
||||
left->emplace_back(m_corners[4]);
|
||||
right->emplace_back(m_corners[5]);
|
||||
break;
|
||||
case DisplayOptions::kHidden:
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_displayOptions.arrows) {
|
||||
|
||||
@@ -53,11 +53,12 @@ struct PlotSeriesRef {
|
||||
};
|
||||
|
||||
class PlotSeries {
|
||||
explicit PlotSeries(Storage& storage, int yAxis = 0);
|
||||
explicit PlotSeries(Storage& storage);
|
||||
|
||||
public:
|
||||
PlotSeries(Storage& storage, std::string_view id);
|
||||
PlotSeries(Storage& storage, DataSource* source, int yAxis = 0);
|
||||
PlotSeries(Storage& storage, DataSource* source);
|
||||
PlotSeries(Storage& storage, DataSource* source, int yAxis);
|
||||
|
||||
const std::string& GetId() const { return m_id; }
|
||||
|
||||
@@ -83,7 +84,7 @@ class PlotSeries {
|
||||
return m_digital.GetValue() == kDigital ||
|
||||
(m_digital.GetValue() == kAuto && m_source && m_source->IsDigital());
|
||||
}
|
||||
void AppendValue(double value, uint64_t time);
|
||||
void AppendValue(double value, int64_t time);
|
||||
|
||||
// source linkage
|
||||
DataSource* m_source = nullptr;
|
||||
@@ -195,7 +196,7 @@ class PlotView : public View {
|
||||
|
||||
} // namespace
|
||||
|
||||
PlotSeries::PlotSeries(Storage& storage, int yAxis)
|
||||
PlotSeries::PlotSeries(Storage& storage)
|
||||
: m_id{storage.GetString("id")},
|
||||
m_name{storage.GetString("name")},
|
||||
m_yAxis{storage.GetInt("yAxis", 0)},
|
||||
@@ -208,12 +209,10 @@ PlotSeries::PlotSeries(Storage& storage, int yAxis)
|
||||
m_digital{
|
||||
storage.GetString("digital"), kAuto, {"Auto", "Digital", "Analog"}},
|
||||
m_digitalBitHeight{storage.GetInt("digitalBitHeight", 8)},
|
||||
m_digitalBitGap{storage.GetInt("digitalBitGap", 4)} {
|
||||
m_yAxis = yAxis;
|
||||
}
|
||||
m_digitalBitGap{storage.GetInt("digitalBitGap", 4)} {}
|
||||
|
||||
PlotSeries::PlotSeries(Storage& storage, std::string_view id)
|
||||
: PlotSeries{storage, 0} {
|
||||
: PlotSeries{storage} {
|
||||
m_id = id;
|
||||
if (DataSource* source = DataSource::Find(id)) {
|
||||
SetSource(source);
|
||||
@@ -222,12 +221,17 @@ PlotSeries::PlotSeries(Storage& storage, std::string_view id)
|
||||
CheckSource();
|
||||
}
|
||||
|
||||
PlotSeries::PlotSeries(Storage& storage, DataSource* source, int yAxis)
|
||||
: PlotSeries{storage, yAxis} {
|
||||
PlotSeries::PlotSeries(Storage& storage, DataSource* source)
|
||||
: PlotSeries{storage} {
|
||||
SetSource(source);
|
||||
m_id = source->GetId();
|
||||
}
|
||||
|
||||
PlotSeries::PlotSeries(Storage& storage, DataSource* source, int yAxis)
|
||||
: PlotSeries{storage, source} {
|
||||
m_yAxis = yAxis;
|
||||
}
|
||||
|
||||
void PlotSeries::CheckSource() {
|
||||
if (!m_newValueConn.connected() && !m_sourceCreatedConn.connected()) {
|
||||
m_source = nullptr;
|
||||
@@ -248,10 +252,10 @@ void PlotSeries::SetSource(DataSource* source) {
|
||||
AppendValue(source->GetValue(), 0);
|
||||
|
||||
m_newValueConn = source->valueChanged.connect_connection(
|
||||
[this](double value, uint64_t time) { AppendValue(value, time); });
|
||||
[this](double value, int64_t time) { AppendValue(value, time); });
|
||||
}
|
||||
|
||||
void PlotSeries::AppendValue(double value, uint64_t timeUs) {
|
||||
void PlotSeries::AppendValue(double value, int64_t timeUs) {
|
||||
double time = (timeUs != 0 ? timeUs : wpi::Now()) * 1.0e-6;
|
||||
if (IsDigital()) {
|
||||
if (m_size < kMaxSize) {
|
||||
|
||||
@@ -10,16 +10,13 @@ using namespace glass;
|
||||
|
||||
EnumSetting::EnumSetting(std::string& str, int defaultValue,
|
||||
std::initializer_list<const char*> choices)
|
||||
: m_str{str}, m_choices{choices}, m_value{defaultValue} {
|
||||
// override default value if str is one of the choices
|
||||
int i = 0;
|
||||
for (auto choice : choices) {
|
||||
if (str == choice) {
|
||||
m_value = i;
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
: m_str{str}, m_choices{choices}, m_defaultValue{defaultValue} {}
|
||||
|
||||
int EnumSetting::GetValue() const {
|
||||
if (m_value == -1) {
|
||||
UpdateValue();
|
||||
}
|
||||
return m_value;
|
||||
}
|
||||
|
||||
void EnumSetting::SetValue(int value) {
|
||||
@@ -29,6 +26,9 @@ void EnumSetting::SetValue(int value) {
|
||||
|
||||
bool EnumSetting::Combo(const char* label, int numOptions,
|
||||
int popup_max_height_in_items) {
|
||||
if (m_value == -1) {
|
||||
UpdateValue();
|
||||
}
|
||||
if (ImGui::Combo(
|
||||
label, &m_value, m_choices.data(),
|
||||
numOptions < 0 ? m_choices.size() : static_cast<size_t>(numOptions),
|
||||
@@ -38,3 +38,17 @@ bool EnumSetting::Combo(const char* label, int numOptions,
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void EnumSetting::UpdateValue() const {
|
||||
// override default value if str is one of the choices
|
||||
int i = 0;
|
||||
for (auto choice : m_choices) {
|
||||
if (m_str == choice) {
|
||||
m_value = i;
|
||||
return;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
// no match, default it
|
||||
m_value = m_defaultValue;
|
||||
}
|
||||
|
||||
@@ -6,13 +6,11 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <wpi/Signal.h>
|
||||
#include <wpi/spinlock.h>
|
||||
|
||||
namespace glass {
|
||||
|
||||
@@ -38,11 +36,13 @@ class DataSource {
|
||||
void SetDigital(bool digital) { m_digital = digital; }
|
||||
bool IsDigital() const { return m_digital; }
|
||||
|
||||
void SetValue(double value, uint64_t time = 0) {
|
||||
void SetValue(double value, int64_t time = 0) {
|
||||
m_value = value;
|
||||
m_valueTime = time;
|
||||
valueChanged(value, time);
|
||||
}
|
||||
double GetValue() const { return m_value; }
|
||||
int64_t GetValueTime() const { return m_valueTime; }
|
||||
|
||||
// drag source helpers
|
||||
void LabelText(const char* label, const char* fmt, ...) const IM_FMTARGS(3);
|
||||
@@ -59,7 +59,7 @@ class DataSource {
|
||||
ImGuiInputTextFlags flags = 0) const;
|
||||
void EmitDrag(ImGuiDragDropFlags flags = 0) const;
|
||||
|
||||
wpi::sig::SignalBase<wpi::spinlock, double, uint64_t> valueChanged;
|
||||
wpi::sig::Signal<double, int64_t> valueChanged;
|
||||
|
||||
static DataSource* Find(std::string_view id);
|
||||
|
||||
@@ -69,7 +69,8 @@ class DataSource {
|
||||
std::string m_id;
|
||||
std::string& m_name;
|
||||
bool m_digital = false;
|
||||
std::atomic<double> m_value = 0;
|
||||
double m_value = 0;
|
||||
int64_t m_valueTime = 0;
|
||||
};
|
||||
|
||||
} // namespace glass
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
@@ -15,7 +16,6 @@
|
||||
|
||||
#include <wpi/StringMap.h>
|
||||
#include <wpi/iterator_range.h>
|
||||
#include <wpi/span.h>
|
||||
|
||||
namespace wpi {
|
||||
class json;
|
||||
@@ -137,17 +137,17 @@ class Storage {
|
||||
std::string_view defaultVal = {});
|
||||
|
||||
std::vector<int>& GetIntArray(std::string_view key,
|
||||
wpi::span<const int> defaultVal = {});
|
||||
std::span<const int> defaultVal = {});
|
||||
std::vector<int64_t>& GetInt64Array(std::string_view key,
|
||||
wpi::span<const int64_t> defaultVal = {});
|
||||
std::span<const int64_t> defaultVal = {});
|
||||
std::vector<int>& GetBoolArray(std::string_view key,
|
||||
wpi::span<const int> defaultVal = {});
|
||||
std::span<const int> defaultVal = {});
|
||||
std::vector<float>& GetFloatArray(std::string_view key,
|
||||
wpi::span<const float> defaultVal = {});
|
||||
std::span<const float> defaultVal = {});
|
||||
std::vector<double>& GetDoubleArray(std::string_view key,
|
||||
wpi::span<const double> defaultVal = {});
|
||||
std::span<const double> defaultVal = {});
|
||||
std::vector<std::string>& GetStringArray(
|
||||
std::string_view key, wpi::span<const std::string> defaultVal = {});
|
||||
std::string_view key, std::span<const std::string> defaultVal = {});
|
||||
std::vector<std::unique_ptr<Storage>>& GetChildArray(std::string_view key);
|
||||
|
||||
Value* FindValue(std::string_view key);
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include <wpi/function_ref.h>
|
||||
#include <wpi/span.h>
|
||||
|
||||
#include "glass/Model.h"
|
||||
|
||||
@@ -27,7 +28,7 @@ class LEDDisplayModel : public glass::Model {
|
||||
|
||||
virtual bool IsRunning() = 0;
|
||||
|
||||
virtual wpi::span<const Data> GetData(wpi::SmallVectorImpl<Data>& buf) = 0;
|
||||
virtual std::span<const Data> GetData(wpi::SmallVectorImpl<Data>& buf) = 0;
|
||||
};
|
||||
|
||||
class LEDDisplaysModel : public glass::Model {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
|
||||
#include <frc/geometry/Pose2d.h>
|
||||
@@ -11,7 +12,6 @@
|
||||
#include <frc/geometry/Translation2d.h>
|
||||
#include <imgui.h>
|
||||
#include <wpi/function_ref.h>
|
||||
#include <wpi/span.h>
|
||||
|
||||
#include "glass/Model.h"
|
||||
#include "glass/View.h"
|
||||
@@ -22,8 +22,8 @@ class FieldObjectModel : public Model {
|
||||
public:
|
||||
virtual const char* GetName() const = 0;
|
||||
|
||||
virtual wpi::span<const frc::Pose2d> GetPoses() = 0;
|
||||
virtual void SetPoses(wpi::span<const frc::Pose2d> poses) = 0;
|
||||
virtual std::span<const frc::Pose2d> GetPoses() = 0;
|
||||
virtual void SetPoses(std::span<const frc::Pose2d> poses) = 0;
|
||||
virtual void SetPose(size_t i, frc::Pose2d pose) = 0;
|
||||
virtual void SetPosition(size_t i, frc::Translation2d pos) = 0;
|
||||
virtual void SetRotation(size_t i, frc::Rotation2d rot) = 0;
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/span.h>
|
||||
|
||||
#include "glass/Model.h"
|
||||
|
||||
namespace glass {
|
||||
@@ -21,10 +19,7 @@ class StringChooserModel : public Model {
|
||||
virtual const std::string& GetActive() = 0;
|
||||
virtual const std::vector<std::string>& GetOptions() = 0;
|
||||
|
||||
virtual void SetDefault(std::string_view val) = 0;
|
||||
virtual void SetSelected(std::string_view val) = 0;
|
||||
virtual void SetActive(std::string_view val) = 0;
|
||||
virtual void SetOptions(wpi::span<const std::string> val) = 0;
|
||||
};
|
||||
|
||||
void DisplayStringChooser(StringChooserModel* model);
|
||||
|
||||
@@ -15,7 +15,7 @@ class EnumSetting {
|
||||
EnumSetting(std::string& str, int defaultValue,
|
||||
std::initializer_list<const char*> choices);
|
||||
|
||||
int GetValue() const { return m_value; }
|
||||
int GetValue() const;
|
||||
void SetValue(int value);
|
||||
|
||||
// updates internal value, returns true on change
|
||||
@@ -23,9 +23,12 @@ class EnumSetting {
|
||||
int popup_max_height_in_items = -1);
|
||||
|
||||
private:
|
||||
void UpdateValue() const;
|
||||
|
||||
std::string& m_str;
|
||||
wpi::SmallVector<const char*, 8> m_choices;
|
||||
int m_value;
|
||||
int m_defaultValue;
|
||||
mutable int m_value = -1;
|
||||
};
|
||||
|
||||
} // namespace glass
|
||||
|
||||
@@ -10,49 +10,38 @@
|
||||
using namespace glass;
|
||||
|
||||
NTCommandSchedulerModel::NTCommandSchedulerModel(std::string_view path)
|
||||
: NTCommandSchedulerModel(nt::GetDefaultInstance(), path) {}
|
||||
: NTCommandSchedulerModel(nt::NetworkTableInstance::GetDefault(), path) {}
|
||||
|
||||
NTCommandSchedulerModel::NTCommandSchedulerModel(NT_Inst instance,
|
||||
NTCommandSchedulerModel::NTCommandSchedulerModel(nt::NetworkTableInstance inst,
|
||||
std::string_view path)
|
||||
: m_nt(instance),
|
||||
m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
|
||||
m_commands(m_nt.GetEntry(fmt::format("{}/Names", path))),
|
||||
m_ids(m_nt.GetEntry(fmt::format("{}/Ids", path))),
|
||||
m_cancel(m_nt.GetEntry(fmt::format("{}/Cancel", path))),
|
||||
m_nameValue(wpi::rsplit(path, '/').second) {
|
||||
m_nt.AddListener(m_name);
|
||||
m_nt.AddListener(m_commands);
|
||||
m_nt.AddListener(m_ids);
|
||||
m_nt.AddListener(m_cancel);
|
||||
}
|
||||
: m_inst{inst},
|
||||
m_name{inst.GetStringTopic(fmt::format("{}/.name", path)).Subscribe("")},
|
||||
m_commands{inst.GetStringArrayTopic(fmt::format("{}/Names", path))
|
||||
.Subscribe({})},
|
||||
m_ids{
|
||||
inst.GetIntegerArrayTopic(fmt::format("{}/Ids", path)).Subscribe({})},
|
||||
m_cancel{
|
||||
inst.GetIntegerArrayTopic(fmt::format("{}/Cancel", path)).Publish()},
|
||||
m_nameValue{wpi::rsplit(path, '/').second} {}
|
||||
|
||||
void NTCommandSchedulerModel::CancelCommand(size_t index) {
|
||||
if (index < m_idsValue.size()) {
|
||||
nt::SetEntryValue(
|
||||
m_cancel, nt::NetworkTableValue::MakeDoubleArray({m_idsValue[index]}));
|
||||
m_cancel.Set({{m_idsValue[index]}});
|
||||
}
|
||||
}
|
||||
|
||||
void NTCommandSchedulerModel::Update() {
|
||||
for (auto&& event : m_nt.PollListener()) {
|
||||
if (event.entry == m_name) {
|
||||
if (event.value && event.value->IsString()) {
|
||||
m_nameValue = event.value->GetString();
|
||||
}
|
||||
} else if (event.entry == m_commands) {
|
||||
if (event.value && event.value->IsStringArray()) {
|
||||
auto arr = event.value->GetStringArray();
|
||||
m_commandsValue.assign(arr.begin(), arr.end());
|
||||
}
|
||||
} else if (event.entry == m_ids) {
|
||||
if (event.value && event.value->IsDoubleArray()) {
|
||||
auto arr = event.value->GetDoubleArray();
|
||||
m_idsValue.assign(arr.begin(), arr.end());
|
||||
}
|
||||
}
|
||||
for (auto&& v : m_name.ReadQueue()) {
|
||||
m_nameValue = std::move(v.value);
|
||||
}
|
||||
for (auto&& v : m_commands.ReadQueue()) {
|
||||
m_commandsValue = std::move(v.value);
|
||||
}
|
||||
for (auto&& v : m_ids.ReadQueue()) {
|
||||
m_idsValue = std::move(v.value);
|
||||
}
|
||||
}
|
||||
|
||||
bool NTCommandSchedulerModel::Exists() {
|
||||
return m_nt.IsConnected() && nt::GetEntryType(m_commands) != NT_UNASSIGNED;
|
||||
return m_inst.IsConnected() && m_commands.Exists();
|
||||
}
|
||||
|
||||
@@ -10,38 +10,32 @@
|
||||
using namespace glass;
|
||||
|
||||
NTCommandSelectorModel::NTCommandSelectorModel(std::string_view path)
|
||||
: NTCommandSelectorModel(nt::GetDefaultInstance(), path) {}
|
||||
: NTCommandSelectorModel(nt::NetworkTableInstance::GetDefault(), path) {}
|
||||
|
||||
NTCommandSelectorModel::NTCommandSelectorModel(NT_Inst instance,
|
||||
NTCommandSelectorModel::NTCommandSelectorModel(nt::NetworkTableInstance inst,
|
||||
std::string_view path)
|
||||
: m_nt(instance),
|
||||
m_running(m_nt.GetEntry(fmt::format("{}/running", path))),
|
||||
m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
|
||||
m_runningData(fmt::format("NTCmd:{}", path)),
|
||||
m_nameValue(wpi::rsplit(path, '/').second) {
|
||||
: m_inst{inst},
|
||||
m_running{inst.GetBooleanTopic(fmt::format("{}/running", path))
|
||||
.GetEntry(false)},
|
||||
m_name{inst.GetStringTopic(fmt::format("{}/.name", path)).Subscribe("")},
|
||||
m_runningData{fmt::format("NTCmd:{}", path)},
|
||||
m_nameValue{wpi::rsplit(path, '/').second} {
|
||||
m_runningData.SetDigital(true);
|
||||
m_nt.AddListener(m_running);
|
||||
m_nt.AddListener(m_name);
|
||||
}
|
||||
|
||||
void NTCommandSelectorModel::SetRunning(bool run) {
|
||||
nt::SetEntryValue(m_running, nt::NetworkTableValue::MakeBoolean(run));
|
||||
m_running.Set(run);
|
||||
}
|
||||
|
||||
void NTCommandSelectorModel::Update() {
|
||||
for (auto&& event : m_nt.PollListener()) {
|
||||
if (event.entry == m_running) {
|
||||
if (event.value && event.value->IsBoolean()) {
|
||||
m_runningData.SetValue(event.value->GetBoolean());
|
||||
}
|
||||
} else if (event.entry == m_name) {
|
||||
if (event.value && event.value->IsString()) {
|
||||
m_nameValue = event.value->GetString();
|
||||
}
|
||||
}
|
||||
for (auto&& v : m_running.ReadQueue()) {
|
||||
m_runningData.SetValue(v.value, v.time);
|
||||
}
|
||||
for (auto&& v : m_name.ReadQueue()) {
|
||||
m_nameValue = std::move(v.value);
|
||||
}
|
||||
}
|
||||
|
||||
bool NTCommandSelectorModel::Exists() {
|
||||
return m_nt.IsConnected() && nt::GetEntryType(m_running) != NT_UNASSIGNED;
|
||||
return m_inst.IsConnected() && m_running.Exists();
|
||||
}
|
||||
|
||||
@@ -12,46 +12,40 @@
|
||||
using namespace glass;
|
||||
|
||||
NTDifferentialDriveModel::NTDifferentialDriveModel(std::string_view path)
|
||||
: NTDifferentialDriveModel(nt::GetDefaultInstance(), path) {}
|
||||
: NTDifferentialDriveModel(nt::NetworkTableInstance::GetDefault(), path) {}
|
||||
|
||||
NTDifferentialDriveModel::NTDifferentialDriveModel(NT_Inst instance,
|
||||
std::string_view path)
|
||||
: m_nt(instance),
|
||||
m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
|
||||
m_controllable(m_nt.GetEntry(fmt::format("{}/.controllable", path))),
|
||||
m_lPercent(m_nt.GetEntry(fmt::format("{}/Left Motor Speed", path))),
|
||||
m_rPercent(m_nt.GetEntry(fmt::format("{}/Right Motor Speed", path))),
|
||||
m_nameValue(wpi::rsplit(path, '/').second),
|
||||
m_lPercentData(fmt::format("NTDiffDriveL:{}", path)),
|
||||
m_rPercentData(fmt::format("NTDiffDriveR:{}", path)) {
|
||||
m_nt.AddListener(m_name);
|
||||
m_nt.AddListener(m_controllable);
|
||||
m_nt.AddListener(m_lPercent);
|
||||
m_nt.AddListener(m_rPercent);
|
||||
NTDifferentialDriveModel::NTDifferentialDriveModel(
|
||||
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_lPercent{inst.GetDoubleTopic(fmt::format("{}/Left Motor Speed", path))
|
||||
.GetEntry(0)},
|
||||
m_rPercent{inst.GetDoubleTopic(fmt::format("{}/Right Motor Speed", path))
|
||||
.GetEntry(0)},
|
||||
m_nameValue{wpi::rsplit(path, '/').second},
|
||||
m_lPercentData{fmt::format("NTDiffDriveL:{}", path)},
|
||||
m_rPercentData{fmt::format("NTDiffDriveR:{}", path)} {
|
||||
m_wheels.emplace_back("L % Output", &m_lPercentData,
|
||||
[this](auto value) { m_lPercent.Set(value); });
|
||||
|
||||
m_wheels.emplace_back("L % Output", &m_lPercentData, [this](auto value) {
|
||||
nt::SetEntryValue(m_lPercent, nt::NetworkTableValue::MakeDouble(value));
|
||||
});
|
||||
|
||||
m_wheels.emplace_back("R % Output", &m_rPercentData, [this](auto value) {
|
||||
nt::SetEntryValue(m_rPercent, nt::NetworkTableValue::MakeDouble(value));
|
||||
});
|
||||
m_wheels.emplace_back("R % Output", &m_rPercentData,
|
||||
[this](auto value) { m_rPercent.Set(value); });
|
||||
}
|
||||
|
||||
void NTDifferentialDriveModel::Update() {
|
||||
for (auto&& event : m_nt.PollListener()) {
|
||||
if (event.entry == m_name && event.value && event.value->IsString()) {
|
||||
m_nameValue = event.value->GetString();
|
||||
} else if (event.entry == m_lPercent && event.value &&
|
||||
event.value->IsDouble()) {
|
||||
m_lPercentData.SetValue(event.value->GetDouble());
|
||||
} else if (event.entry == m_rPercent && event.value &&
|
||||
event.value->IsDouble()) {
|
||||
m_rPercentData.SetValue(event.value->GetDouble());
|
||||
} else if (event.entry == m_controllable && event.value &&
|
||||
event.value->IsBoolean()) {
|
||||
m_controllableValue = event.value->GetBoolean();
|
||||
}
|
||||
for (auto&& v : m_name.ReadQueue()) {
|
||||
m_nameValue = std::move(v.value);
|
||||
}
|
||||
for (auto&& v : m_lPercent.ReadQueue()) {
|
||||
m_lPercentData.SetValue(v.value, v.time);
|
||||
}
|
||||
for (auto&& v : m_rPercent.ReadQueue()) {
|
||||
m_rPercentData.SetValue(v.value, v.time);
|
||||
}
|
||||
for (auto&& v : m_controllable.ReadQueue()) {
|
||||
m_controllableValue = v.value;
|
||||
}
|
||||
|
||||
double l = m_lPercentData.GetValue();
|
||||
@@ -62,5 +56,5 @@ void NTDifferentialDriveModel::Update() {
|
||||
}
|
||||
|
||||
bool NTDifferentialDriveModel::Exists() {
|
||||
return m_nt.IsConnected() && nt::GetEntryType(m_lPercent) != NT_UNASSIGNED;
|
||||
return m_inst.IsConnected() && m_lPercent.Exists();
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user