mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-26 01:51:41 +00:00
Compare commits
178 Commits
v2025.3.2
...
v2027.0.0-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3355383fe9 | ||
|
|
fbee476fd2 | ||
|
|
fbbc4bc53c | ||
|
|
05c080328b | ||
|
|
c01e318370 | ||
|
|
5dfc664b93 | ||
|
|
89b97a21d8 | ||
|
|
075cc4a20f | ||
|
|
f99692f287 | ||
|
|
2af8c59858 | ||
|
|
4d74ea6278 | ||
|
|
a4cf2ea6ec | ||
|
|
b205f3e1b4 | ||
|
|
be67432a5e | ||
|
|
1955dcddb3 | ||
|
|
e12d78a70a | ||
|
|
d3fbebc0a9 | ||
|
|
1991af34a5 | ||
|
|
7a3df6175e | ||
|
|
a6f601453a | ||
|
|
6c16e846fa | ||
|
|
ca05ffa1b9 | ||
|
|
de718f7ae5 | ||
|
|
5368e8c6ed | ||
|
|
25eacfa226 | ||
|
|
22d12d2345 | ||
|
|
abd312f3d0 | ||
|
|
b4823569a4 | ||
|
|
0cb4df7e05 | ||
|
|
231ec348fe | ||
|
|
d32e60233f | ||
|
|
1596e2fd7a | ||
|
|
79a9d7f987 | ||
|
|
0877d130be | ||
|
|
89555383cc | ||
|
|
de315947e9 | ||
|
|
b7cd03adc4 | ||
|
|
6e3f48daeb | ||
|
|
0e5a6f38d8 | ||
|
|
cc8eaf3ed7 | ||
|
|
dd6c830768 | ||
|
|
3dee19a435 | ||
|
|
55a97f0c11 | ||
|
|
b1f7e6d6f2 | ||
|
|
ca3137b291 | ||
|
|
e1da917270 | ||
|
|
0a38400734 | ||
|
|
b96264f042 | ||
|
|
a15152712f | ||
|
|
17cae787e7 | ||
|
|
02de5f710e | ||
|
|
e63899e63a | ||
|
|
ba37e7eb3c | ||
|
|
7036bd509e | ||
|
|
8fe3cfb325 | ||
|
|
a4572a01b7 | ||
|
|
f14af97dc7 | ||
|
|
631521a980 | ||
|
|
c49fc29046 | ||
|
|
40ce42712f | ||
|
|
659710a79a | ||
|
|
d059cbc157 | ||
|
|
08297430b5 | ||
|
|
85a8fc9943 | ||
|
|
36811211be | ||
|
|
2f0990e9d2 | ||
|
|
0695a4db89 | ||
|
|
3960045663 | ||
|
|
92010c175f | ||
|
|
90ee11a9e0 | ||
|
|
828199befb | ||
|
|
d04e15b957 | ||
|
|
d98ad815b1 | ||
|
|
1c35a3a5ff | ||
|
|
b248663386 | ||
|
|
9c60da1319 | ||
|
|
21d921184a | ||
|
|
26e299115f | ||
|
|
07192285f6 | ||
|
|
ed94e3af3e | ||
|
|
3cd282d9b0 | ||
|
|
49b4b064cf | ||
|
|
bd78215b43 | ||
|
|
c13b2f45c1 | ||
|
|
ea986427aa | ||
|
|
dcd397e007 | ||
|
|
e2cc9e0059 | ||
|
|
2e21a41f87 | ||
|
|
52b353fe57 | ||
|
|
d3cc185382 | ||
|
|
7cb29ce70b | ||
|
|
baa20fa239 | ||
|
|
b39744b562 | ||
|
|
72bba2491a | ||
|
|
98f933eca5 | ||
|
|
da47f06d70 | ||
|
|
ac1705ae2b | ||
|
|
764ada9b66 | ||
|
|
bfff891b5c | ||
|
|
35aee1d78d | ||
|
|
297f0d1b03 | ||
|
|
ad29d45dfb | ||
|
|
6e704370b3 | ||
|
|
7533b323d1 | ||
|
|
48ce2dcc8d | ||
|
|
b799b285b3 | ||
|
|
3b345fe218 | ||
|
|
eee30c49e2 | ||
|
|
adbe95e610 | ||
|
|
01e71e73ce | ||
|
|
5898cdd5c3 | ||
|
|
e2b6beb28a | ||
|
|
5a6c895b87 | ||
|
|
1600e773f4 | ||
|
|
f80874dd4b | ||
|
|
92f0a3c961 | ||
|
|
dc335ddedb | ||
|
|
ff1b2a205e | ||
|
|
5017393b3a | ||
|
|
d9f8fded09 | ||
|
|
1cad4f64a4 | ||
|
|
58cb395d76 | ||
|
|
24d6e87447 | ||
|
|
f81c42e700 | ||
|
|
fa71fb55a2 | ||
|
|
45d7549ca9 | ||
|
|
afbaa43539 | ||
|
|
e41b33960a | ||
|
|
df77580a15 | ||
|
|
09a6bc9a25 | ||
|
|
666d1638ce | ||
|
|
03d9e96877 | ||
|
|
0f6693594c | ||
|
|
148fcdca85 | ||
|
|
93521420c8 | ||
|
|
12a1475ee4 | ||
|
|
1240ee1bf4 | ||
|
|
da90ffd24a | ||
|
|
a931a6554f | ||
|
|
3232630a38 | ||
|
|
df244cd198 | ||
|
|
2a757eaeb5 | ||
|
|
78b14c5204 | ||
|
|
529bab6ca1 | ||
|
|
03f0fc4dea | ||
|
|
945d416d07 | ||
|
|
6ba7189373 | ||
|
|
31d1aa62c1 | ||
|
|
b6ae9e9cc9 | ||
|
|
f1e4eafaa0 | ||
|
|
41d4826694 | ||
|
|
220f4e1ba4 | ||
|
|
ae44295024 | ||
|
|
4910436b10 | ||
|
|
a7349f00ef | ||
|
|
e493da3486 | ||
|
|
32ba751e58 | ||
|
|
62a6a77bbf | ||
|
|
c81bd0c909 | ||
|
|
c497e4ec22 | ||
|
|
6dbff902fa | ||
|
|
be72e0ecd8 | ||
|
|
e69c5710b3 | ||
|
|
52b33edcbd | ||
|
|
c8900cadc3 | ||
|
|
5058b48dea | ||
|
|
144e79a614 | ||
|
|
38b09a6dfd | ||
|
|
d7cd71589a | ||
|
|
a954091ea2 | ||
|
|
bf653d9895 | ||
|
|
5a9e0abe44 | ||
|
|
5cab27fdd5 | ||
|
|
ce63770970 | ||
|
|
c51f65bd4f | ||
|
|
82132c3272 | ||
|
|
847c3120d3 | ||
|
|
7ae4333c81 |
18
.bazelrc
18
.bazelrc
@@ -3,6 +3,8 @@ try-import %workspace%/user.bazelrc
|
||||
|
||||
common --noenable_bzlmod
|
||||
|
||||
build --incompatible_disallow_empty_glob=1 # Bazel 8 prep
|
||||
|
||||
build --java_language_version=17
|
||||
build --java_runtime_version=roboriojdk_17
|
||||
build --tool_java_language_version=17
|
||||
@@ -11,14 +13,14 @@ build --tool_java_runtime_version=remotejdk_17
|
||||
test --test_output=errors
|
||||
test --test_verbose_timeout_warnings
|
||||
|
||||
import shared/bazel/compiler_flags/sanitizers.rc
|
||||
import shared/bazel/compiler_flags/base_linux_flags.rc
|
||||
import shared/bazel/compiler_flags/linux_flags.rc
|
||||
import shared/bazel/compiler_flags/osx_flags.rc
|
||||
import shared/bazel/compiler_flags/roborio_flags.rc
|
||||
import shared/bazel/compiler_flags/systemcore_flags.rc
|
||||
import shared/bazel/compiler_flags/windows_flags.rc
|
||||
import shared/bazel/compiler_flags/coverage_flags.rc
|
||||
import %workspace%/shared/bazel/compiler_flags/sanitizers.rc
|
||||
import %workspace%/shared/bazel/compiler_flags/base_linux_flags.rc
|
||||
import %workspace%/shared/bazel/compiler_flags/linux_flags.rc
|
||||
import %workspace%/shared/bazel/compiler_flags/osx_flags.rc
|
||||
import %workspace%/shared/bazel/compiler_flags/roborio_flags.rc
|
||||
import %workspace%/shared/bazel/compiler_flags/systemcore_flags.rc
|
||||
import %workspace%/shared/bazel/compiler_flags/windows_flags.rc
|
||||
import %workspace%/shared/bazel/compiler_flags/coverage_flags.rc
|
||||
|
||||
# Alias toolchain names to what wpilibsuite uses for CI/Artifact naming
|
||||
build:athena --config=roborio
|
||||
|
||||
3
.github/actions/pregen/action.yml
vendored
3
.github/actions/pregen/action.yml
vendored
@@ -18,9 +18,6 @@ runs:
|
||||
wget https://github.com/HebiRobotics/QuickBuffers/releases/download/1.3.3/protoc-gen-quickbuf-1.3.3-linux-x86_64.exe
|
||||
chmod +x protoc-gen-quickbuf-1.3.3-linux-x86_64.exe
|
||||
shell: bash
|
||||
- name: Regenerate hal
|
||||
run: ./hal/generate_usage_reporting.py
|
||||
shell: bash
|
||||
|
||||
- name: Regenerate ntcore
|
||||
run: ./ntcore/generate_topics.py
|
||||
|
||||
3
.github/labeler.yml
vendored
3
.github/labeler.yml
vendored
@@ -33,9 +33,6 @@
|
||||
'component: sysid':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: sysid/**
|
||||
'component: teamnumbersetter':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: roborioteamnumbersetter/**
|
||||
'component: wpilibc':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: wpilibc/**
|
||||
|
||||
15
.github/workflows/bazel.yml
vendored
15
.github/workflows/bazel.yml
vendored
@@ -31,8 +31,8 @@ jobs:
|
||||
with:
|
||||
token: ${{ secrets.BUILDBUDDY_API_KEY }}
|
||||
|
||||
- name: Build Release
|
||||
run: bazel --output_user_root=C:\\bazelroot ${{ matrix.action }} -k ... --config=ci -c opt ${{ matrix.config }} --verbose_failures
|
||||
- name: bazel ${{ matrix.action }}
|
||||
run: bazel --output_user_root=C:\\bazelroot ${{ matrix.action }} -k ... --config=ci ${{ matrix.config }} --verbose_failures
|
||||
shell: bash
|
||||
|
||||
build-mac:
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
with:
|
||||
token: ${{ secrets.BUILDBUDDY_API_KEY }}
|
||||
|
||||
- name: Build Release
|
||||
- name: bazel test (release)
|
||||
run: bazel test -k ... --config=ci -c opt --config=macos --nojava_header_compilation --verbose_failures
|
||||
shell: bash
|
||||
|
||||
@@ -56,10 +56,11 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { name: "Linux (native)", os: ubuntu-22.04, action: "test", config: "--config=linux", }
|
||||
- { name: "Linux (roborio)", os: ubuntu-22.04, action: "build", config: "--config=roborio", }
|
||||
- { name: "Linux (native)", os: ubuntu-24.04, container: "wpilib/ubuntu-base:22.04", action: "test", config: "--config=linux", }
|
||||
- { name: "Linux (systemcore)", os: ubuntu-24.04, container: "wpilib/ubuntu-base:22.04", action: "build", config: "--config=systemcore", }
|
||||
name: "${{ matrix.name }}"
|
||||
runs-on: ${{ matrix.os }}
|
||||
container: ${{ matrix.container }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with: { fetch-depth: 0 }
|
||||
@@ -70,12 +71,12 @@ jobs:
|
||||
with:
|
||||
token: ${{ secrets.BUILDBUDDY_API_KEY }}
|
||||
|
||||
- name: Build and Test Release
|
||||
- name: bazel ${{ matrix.action }} (release)
|
||||
run: bazel ${{ matrix.action }} ... --config=ci -c opt ${{ matrix.config }} -k --verbose_failures
|
||||
|
||||
buildifier:
|
||||
name: "buildifier"
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Set up Go 1.15.x
|
||||
uses: actions/setup-go@v5
|
||||
|
||||
6
.github/workflows/cmake-android.yml
vendored
6
.github/workflows/cmake-android.yml
vendored
@@ -16,10 +16,10 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-22.04
|
||||
- os: ubuntu-24.04
|
||||
name: Android Arm64
|
||||
abi: arm64-v8a
|
||||
- os: ubuntu-22.04
|
||||
- os: ubuntu-24.04
|
||||
name: Android X64
|
||||
abi: "x86_64"
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
java-version: 17
|
||||
|
||||
- name: Install sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.5
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get update && sudo apt-get install -y ninja-build
|
||||
|
||||
8
.github/workflows/cmake.yml
vendored
8
.github/workflows/cmake.yml
vendored
@@ -16,9 +16,9 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-22.04
|
||||
- os: ubuntu-24.04
|
||||
name: Linux
|
||||
container: wpilib/roborio-cross-ubuntu:2025-22.04
|
||||
container: wpilib/roborio-cross-ubuntu:2025-24.04
|
||||
flags: "--preset with-java-and-sccache -DCMAKE_BUILD_TYPE=Release -DWITH_EXAMPLES=ON"
|
||||
- os: macOS-14
|
||||
name: macOS
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
steps:
|
||||
- name: Install dependencies (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv4.5-java libprotobuf-dev protobuf-compiler ninja-build
|
||||
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv-java libprotobuf-dev protobuf-compiler ninja-build
|
||||
|
||||
- name: Install dependencies (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
uses: lukka/get-cmake@v3.29.3
|
||||
|
||||
- name: Install sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.5
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
9
.github/workflows/documentation.yml
vendored
9
.github/workflows/documentation.yml
vendored
@@ -12,8 +12,8 @@ env:
|
||||
jobs:
|
||||
publish:
|
||||
name: "Documentation - Publish"
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.repository == 'wpilibsuite/allwpilib' && (github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
|
||||
runs-on: ubuntu-24.04
|
||||
if: github.repository == 'wpilibsuite/allwpilib' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||||
concurrency: ci-docs-publish
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -39,6 +39,11 @@ jobs:
|
||||
echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
|
||||
echo "BRANCH=release" >> $GITHUB_ENV
|
||||
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'alpha') && !contains(github.ref, 'beta') && !contains(github.ref, '2027')
|
||||
- name: Set environment variables (2027)
|
||||
run: |
|
||||
echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
|
||||
echo "BRANCH=2027" >> $GITHUB_ENV
|
||||
if: startsWith(github.ref, 'refs/tags/v') && contains(github.ref, '2027')
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew docs:generateJavaDocs docs:doxygen -PbuildServer ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
- name: Install SSH Client 🔑
|
||||
|
||||
83
.github/workflows/gradle.yml
vendored
83
.github/workflows/gradle.yml
vendored
@@ -19,20 +19,20 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- container: wpilib/roborio-cross-ubuntu:2025-22.04
|
||||
artifact-name: Athena
|
||||
build-options: "-Ponlylinuxathena"
|
||||
- container: wpilib/raspbian-cross-ubuntu:bookworm-22.04
|
||||
- container: wpilib/systemcore-cross-ubuntu:2025-24.04
|
||||
artifact-name: SystemCore
|
||||
build-options: "-Ponlylinuxsystemcore"
|
||||
- container: wpilib/raspbian-cross-ubuntu:bookworm-24.04
|
||||
artifact-name: Arm32
|
||||
build-options: "-Ponlylinuxarm32"
|
||||
- container: wpilib/aarch64-cross-ubuntu:bookworm-22.04
|
||||
- container: wpilib/aarch64-cross-ubuntu:bookworm-24.04
|
||||
artifact-name: Arm64
|
||||
build-options: "-Ponlylinuxarm64"
|
||||
- container: wpilib/ubuntu-base:22.04
|
||||
- container: wpilib/ubuntu-base:24.04
|
||||
artifact-name: Linux
|
||||
build-options: "-Ponlylinuxx86-64"
|
||||
name: "Build - ${{ matrix.artifact-name }}"
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
needs: [validation]
|
||||
steps:
|
||||
- name: Free Disk Space
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- name: Set release environment variable
|
||||
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
|
||||
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')
|
||||
if: startsWith(github.ref, 'refs/tags/v2027')
|
||||
- name: Build with Gradle
|
||||
uses: addnab/docker-run-action@v3
|
||||
with:
|
||||
@@ -103,12 +103,6 @@ jobs:
|
||||
architecture: aarch64
|
||||
task: "build"
|
||||
outputs: "build/allOutputs"
|
||||
- os: windows-2022
|
||||
artifact-name: Win32FFI
|
||||
architecture: x86
|
||||
task: ":ntcoreffi:build"
|
||||
build-options: "-Pntcoreffibuild \"-Dorg.gradle.jvmargs=-Xmx1096m\""
|
||||
outputs: "ntcoreffi/build/outputs"
|
||||
- os: windows-2022
|
||||
artifact-name: Win64FFI
|
||||
architecture: x64
|
||||
@@ -135,19 +129,16 @@ jobs:
|
||||
keychain-password: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
|
||||
if: |
|
||||
matrix.artifact-name == 'macOS' && (github.repository == 'wpilibsuite/allwpilib' &&
|
||||
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027'))))
|
||||
(github.ref == 'refs/heads/2027' || startsWith(github.ref, 'refs/tags/v2027')))
|
||||
- name: Set Keychain Lock Timeout
|
||||
run: security set-keychain-settings -lut 21600
|
||||
if: |
|
||||
matrix.artifact-name == 'macOS' && (github.repository == 'wpilibsuite/allwpilib' &&
|
||||
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027'))))
|
||||
(github.ref == 'refs/heads/2027' || startsWith(github.ref, 'refs/tags/v2027')))
|
||||
- name: Set release environment variable
|
||||
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')
|
||||
- name: Set Java Heap Size
|
||||
run: sed -i 's/-Xmx2g/-Xmx1g/g' gradle.properties
|
||||
if: matrix.artifact-name == 'Win32'
|
||||
if: startsWith(github.ref, 'refs/tags/v2027')
|
||||
- name: Check disk free space (Windows)
|
||||
run: wmic logicaldisk get caption, freespace
|
||||
if: matrix.os == 'windows-2022'
|
||||
@@ -175,7 +166,7 @@ jobs:
|
||||
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 == 'wpilibsuite/allwpilib' &&
|
||||
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027'))))
|
||||
(github.ref == 'refs/heads/2027' || startsWith(github.ref, 'refs/tags/v2027')))
|
||||
- name: Check disk free space (Windows)
|
||||
run: wmic logicaldisk get caption, freespace
|
||||
if: matrix.os == 'windows-2022'
|
||||
@@ -189,7 +180,7 @@ jobs:
|
||||
|
||||
build-documentation:
|
||||
name: "Build - Documentation"
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
needs: [validation]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -201,7 +192,7 @@ jobs:
|
||||
java-version: 17
|
||||
- name: Set release environment variable
|
||||
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
|
||||
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')
|
||||
if: startsWith(github.ref, 'refs/tags/v2027')
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew docs:zipDocs --build-cache -PbuildServer ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
env:
|
||||
@@ -215,12 +206,12 @@ jobs:
|
||||
combine:
|
||||
name: Combine
|
||||
needs: [build-docker, build-host, build-documentation]
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Free Disk Space
|
||||
if: |
|
||||
github.repository == 'wpilibsuite/allwpilib' &&
|
||||
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
|
||||
(github.ref == 'refs/heads/2027' || startsWith(github.ref, 'refs/tags/v2027'))
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
tool-cache: false
|
||||
@@ -233,48 +224,48 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
if: |
|
||||
github.repository == 'wpilibsuite/allwpilib' &&
|
||||
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
|
||||
(github.ref == 'refs/heads/2027' || startsWith(github.ref, 'refs/tags/v2027'))
|
||||
with:
|
||||
repository: wpilibsuite/build-tools
|
||||
- uses: actions/download-artifact@v4
|
||||
if: |
|
||||
github.repository == 'wpilibsuite/allwpilib' &&
|
||||
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
|
||||
(github.ref == 'refs/heads/2027' || startsWith(github.ref, 'refs/tags/v2027'))
|
||||
with:
|
||||
path: combiner/products/build/allOutputs
|
||||
- name: Flatten Artifacts
|
||||
if: |
|
||||
github.repository == 'wpilibsuite/allwpilib' &&
|
||||
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
|
||||
(github.ref == 'refs/heads/2027' || startsWith(github.ref, 'refs/tags/v2027'))
|
||||
run: rsync -a --delete combiner/products/build/allOutputs/*/* combiner/products/build/allOutputs/
|
||||
- name: Check version number exists
|
||||
if: |
|
||||
github.repository == 'wpilibsuite/allwpilib' &&
|
||||
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
|
||||
(github.ref == 'refs/heads/2027' || startsWith(github.ref, 'refs/tags/v2027'))
|
||||
run: |
|
||||
cat combiner/products/build/allOutputs/version.txt
|
||||
test -s combiner/products/build/allOutputs/version.txt
|
||||
- uses: actions/setup-java@v4
|
||||
if: |
|
||||
github.repository == 'wpilibsuite/allwpilib' &&
|
||||
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
|
||||
(github.ref == 'refs/heads/2027' || startsWith(github.ref, 'refs/tags/v2027'))
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
- name: Combine (Main)
|
||||
- name: Combine (2027)
|
||||
if: |
|
||||
github.repository == 'wpilibsuite/allwpilib' &&
|
||||
github.ref == 'refs/heads/main'
|
||||
run: cd combiner && ./gradlew publish -Pallwpilib
|
||||
github.ref == 'refs/heads/2027'
|
||||
run: cd combiner && ./gradlew publish -Pallwpilib -Pbuild2027
|
||||
env:
|
||||
RUN_AZURE_ARTIFACTORY_RELEASE: "TRUE"
|
||||
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
- name: Combine (Release)
|
||||
- name: Combine (2027 Release)
|
||||
if: |
|
||||
github.repository == 'wpilibsuite/allwpilib' &&
|
||||
startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')
|
||||
run: cd combiner && ./gradlew publish -Pallwpilib -PreleaseRepoPublish
|
||||
startsWith(github.ref, 'refs/tags/v2027')
|
||||
run: cd combiner && ./gradlew publish -Pallwpilib -PreleaseRepoPublish -Pbuild2027
|
||||
env:
|
||||
RUN_AZURE_ARTIFACTORY_RELEASE: "TRUE"
|
||||
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
@@ -282,25 +273,7 @@ jobs:
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: |
|
||||
github.repository == 'wpilibsuite/allwpilib' &&
|
||||
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
|
||||
(github.ref == 'refs/heads/2027' || startsWith(github.ref, 'refs/tags/v2027'))
|
||||
with:
|
||||
name: Maven
|
||||
path: ~/releases
|
||||
|
||||
dispatch:
|
||||
name: dispatch
|
||||
needs: [combine]
|
||||
strategy:
|
||||
matrix:
|
||||
repo: ['SmartDashboard', 'PathWeaver', 'Shuffleboard', 'RobotBuilder']
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: peter-evans/repository-dispatch@v3
|
||||
if: |
|
||||
github.repository == 'wpilibsuite/allwpilib' &&
|
||||
startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')
|
||||
with:
|
||||
token: ${{ secrets.TOOL_REPO_ACCESS_TOKEN }}
|
||||
repository: wpilibsuite/${{ matrix.repo }}
|
||||
event-type: tag
|
||||
client-payload: '{"package_name": "allwpilib", "package_version": "${{ github.ref_name }}"}'
|
||||
|
||||
16
.github/workflows/lint-format.yml
vendored
16
.github/workflows/lint-format.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
|
||||
wpiformat:
|
||||
name: "wpiformat"
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
- name: Install wpiformat
|
||||
run: |
|
||||
python -m venv ${{ runner.temp }}/wpiformat
|
||||
${{ runner.temp }}/wpiformat/bin/pip3 install wpiformat==2024.51
|
||||
${{ runner.temp }}/wpiformat/bin/pip3 install wpiformat==2025.33
|
||||
- name: Run
|
||||
run: ${{ runner.temp }}/wpiformat/bin/wpiformat
|
||||
- name: Check output
|
||||
@@ -59,9 +59,9 @@ jobs:
|
||||
|
||||
tidy:
|
||||
name: "clang-tidy"
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
needs: [validation]
|
||||
container: wpilib/ubuntu-base:22.04
|
||||
container: wpilib/ubuntu-base:24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
- name: Install wpiformat
|
||||
run: |
|
||||
python -m venv ${{ runner.temp }}/wpiformat
|
||||
${{ runner.temp }}/wpiformat/bin/pip3 install wpiformat==2024.51
|
||||
${{ runner.temp }}/wpiformat/bin/pip3 install wpiformat==2025.33
|
||||
- name: Create compile_commands.json
|
||||
run: |
|
||||
./gradlew generateCompileCommands -Ptoolchain-optional-roboRio
|
||||
@@ -92,9 +92,9 @@ jobs:
|
||||
run: ${{ runner.temp }}/wpiformat/bin/wpiformat -no-format -tidy-changed -compile-commands=build/TargetedCompileCommands/linuxx86-64debug -vv
|
||||
javaformat:
|
||||
name: "Java format"
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
needs: [validation]
|
||||
container: wpilib/ubuntu-base:22.04
|
||||
container: wpilib/ubuntu-base:24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -126,7 +126,7 @@ jobs:
|
||||
|
||||
documentation:
|
||||
name: "Documentation"
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
needs: [validation]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
2
.github/workflows/pregenerate.yml
vendored
2
.github/workflows/pregenerate.yml
vendored
@@ -13,7 +13,7 @@ concurrency:
|
||||
jobs:
|
||||
update:
|
||||
name: "Update"
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
||||
6
.github/workflows/sanitizers.yml
vendored
6
.github/workflows/sanitizers.yml
vendored
@@ -33,15 +33,15 @@ jobs:
|
||||
container: wpilib/roborio-cross-ubuntu:2025-24.04
|
||||
steps:
|
||||
- name: Install Dependencies
|
||||
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv-java clang-17 libprotobuf-dev protobuf-compiler ninja-build
|
||||
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv-java clang-18 libprotobuf-dev protobuf-compiler ninja-build
|
||||
|
||||
- name: Install sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.5
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: configure
|
||||
run: mkdir build && cd build && cmake -G Ninja -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache -DCMAKE_C_COMPILER:FILEPATH=/usr/bin/clang-17 -DCMAKE_CXX_COMPILER:FILEPATH=/usr/bin/clang++-17 -DWITH_JAVA=OFF ${{ matrix.cmake-flags }} ..
|
||||
run: mkdir build && cd build && cmake -G Ninja -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache -DCMAKE_C_COMPILER:FILEPATH=/usr/bin/clang-18 -DCMAKE_CXX_COMPILER:FILEPATH=/usr/bin/clang++-18 -DWITH_JAVA=OFF ${{ matrix.cmake-flags }} ..
|
||||
env:
|
||||
SCCACHE_WEBDAV_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
SCCACHE_WEBDAV_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
|
||||
18
.github/workflows/sentinel-build.yml
vendored
18
.github/workflows/sentinel-build.yml
vendored
@@ -23,20 +23,20 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- container: wpilib/roborio-cross-ubuntu:2025-22.04
|
||||
- container: wpilib/roborio-cross-ubuntu:2025-24.04
|
||||
artifact-name: Athena
|
||||
build-options: "-Ponlylinuxathena"
|
||||
- container: wpilib/raspbian-cross-ubuntu:bookworm-22.04
|
||||
- container: wpilib/raspbian-cross-ubuntu:bookworm-24.04
|
||||
artifact-name: Arm32
|
||||
build-options: "-Ponlylinuxarm32"
|
||||
- container: wpilib/aarch64-cross-ubuntu:bookworm-22.04
|
||||
- container: wpilib/aarch64-cross-ubuntu:bookworm-24.04
|
||||
artifact-name: Arm64
|
||||
build-options: "-Ponlylinuxarm64"
|
||||
- container: wpilib/ubuntu-base:22.04
|
||||
- container: wpilib/ubuntu-base:24.04
|
||||
artifact-name: Linux
|
||||
build-options: "-Ponlylinuxx86-64"
|
||||
name: "Build - ${{ matrix.artifact-name }}"
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
needs: [validation]
|
||||
steps:
|
||||
- name: Free Disk Space
|
||||
@@ -102,11 +102,6 @@ jobs:
|
||||
architecture: aarch64
|
||||
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 }}
|
||||
needs: [validation]
|
||||
@@ -131,9 +126,6 @@ jobs:
|
||||
run: security set-keychain-settings -lut 21600
|
||||
if: |
|
||||
matrix.artifact-name == 'macOS' && (github.repository_owner == 'wpilibsuite' && github.ref == 'refs/heads/main')
|
||||
- name: Set Java Heap Size
|
||||
run: sed -i 's/-Xmx2g/-Xmx1g/g' gradle.properties
|
||||
if: matrix.artifact-name == 'Win32'
|
||||
- name: Check disk free space (Windows)
|
||||
run: wmic logicaldisk get caption, freespace
|
||||
if: matrix.os == 'windows-2022'
|
||||
|
||||
168
.github/workflows/tools.yml
vendored
168
.github/workflows/tools.yml
vendored
@@ -12,7 +12,7 @@ env:
|
||||
jobs:
|
||||
build-artifacts:
|
||||
name: "Build - WPILib"
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
DISPLAY: ':10'
|
||||
steps:
|
||||
@@ -33,174 +33,12 @@ jobs:
|
||||
- name: Build WPILib with Gradle
|
||||
uses: addnab/docker-run-action@v3
|
||||
with:
|
||||
image: wpilib/roborio-cross-ubuntu:2025-22.04
|
||||
image: wpilib/systemcore-cross-ubuntu:2025-24.04
|
||||
options: -v ${{ github.workspace }}:/work -w /work -e GITHUB_REF -e CI -e DISPLAY
|
||||
run: df . && rm -f semicolon_delimited_script && ./gradlew :wpilibc:publish :wpilibj:publish :wpilibNewCommands:publish :hal:publish :cameraserver:publish :ntcore:publish :cscore:publish :wpimath:publish :wpinet:publish :wpiutil:publish :apriltag:publish :wpiunits:publish :simulation:halsim_gui:publish :simulation:halsim_ds_socket:publish :simulation:halsim_ws_server:publish :simulation:halsim_ws_client:publish :simulation:halsim_xrp:publish :fieldImages:publish :romiVendordep:publish :xrpVendordep:publish :epilogue-processor:publish :epilogue-runtime:publish :thirdparty:googletest:publish -x test -x Javadoc -x doxygen --build-cache && cp -r /root/releases/maven/development /work
|
||||
run: df . && rm -f semicolon_delimited_script && ./gradlew -Pskiplinuxarm64 :wpilibc:publish :wpilibj:publish :wpilibNewCommands:publish :hal:publish :cameraserver:publish :ntcore:publish :cscore:publish :wpimath:publish :wpinet:publish :wpiutil:publish :apriltag:publish :wpiunits:publish :simulation:halsim_gui:publish :simulation:halsim_ds_socket:publish :fieldImages:publish :epilogue-processor:publish :epilogue-runtime:publish :thirdparty:googletest:publish -x test -x Javadoc -x doxygen --build-cache && cp -r /root/releases/maven/development /work
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: MavenArtifacts
|
||||
path: |
|
||||
development
|
||||
retention-days: 1
|
||||
|
||||
Robotbuilder:
|
||||
name: "Build - RobotBuilder"
|
||||
needs: [build-artifacts]
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
DISPLAY: ':10'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: wpilibsuite/robotbuilder
|
||||
fetch-depth: 0
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: MavenArtifacts
|
||||
- name: Patch RobotBuilder to use local development
|
||||
run: cd src/main/resources/export && echo "wpi.maven.useLocal = false" >> java/build.gradle && echo "wpi.maven.useFrcMavenLocalDevelopment = true" >> java/build.gradle && echo "wpi.versions.wpilibVersion = '$YEAR.424242.+'" >> java/build.gradle && echo "wpi.versions.wpimathVersion = '$YEAR.424242.+'" >> java/build.gradle && echo "wpi.maven.useLocal = false" >> cpp/build.gradle && echo "wpi.maven.useFrcMavenLocalDevelopment = true" >> cpp/build.gradle && echo "wpi.versions.wpilibVersion = '$YEAR.424242.+'" >> cpp/build.gradle && echo "wpi.versions.wpimathVersion = '$YEAR.424242.+'" >> cpp/build.gradle
|
||||
- name: Install and run xvfb
|
||||
run: sudo apt-get update && sudo apt-get install -y xvfb && Xvfb $DISPLAY &
|
||||
- name: Move artifacts
|
||||
run: mkdir -p ~/releases/maven/development && cp -r edu ~/releases/maven/development
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: 'temurin'
|
||||
- name: Build RobotBuilder with Gradle
|
||||
run: ./gradlew build test --tests 'robotbuilder.exporters.*' -x htmlSanityCheck -PbuildServer -PreleaseMode ; cat build/test-results/test/TEST-robotbuilder.exporters.*.xml ;
|
||||
- name: Summarize RobotBuilder Test Results
|
||||
uses: EnricoMi/publish-unit-test-result-action@v2
|
||||
if: always()
|
||||
with:
|
||||
files: |
|
||||
build/test-results/test/TEST*.xml
|
||||
check_run: false
|
||||
comment_mode: off
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: RobotBuilderTestResults
|
||||
path: |
|
||||
build/reports/
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: RobotBuilder Build
|
||||
path: |
|
||||
build/libs/
|
||||
retention-days: 7
|
||||
|
||||
Shuffleboard:
|
||||
name: "Build - Shuffleboard"
|
||||
needs: [build-artifacts]
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: wpilibsuite/shuffleboard
|
||||
fetch-depth: 0
|
||||
- name: Patch Shuffleboard to use local development
|
||||
run: sed -i "s/wpilibTools.deps.wpilibVersion.*/wpilibTools.deps.wpilibVersion = \'$YEAR\.424242\.+\'/" app/app.gradle && sed -i "s/wpilibTools.deps.wpilibVersion.*/wpilibTools.deps.wpilibVersion = \'$YEAR\.424242\.+\'/" plugins/cameraserver/cameraserver.gradle && sed -i "s/wpilibTools.deps.wpilibVersion.*/wpilibTools.deps.wpilibVersion = \'$YEAR\.424242\.+\'/" plugins/networktables/networktables.gradle
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: MavenArtifacts
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: 'temurin'
|
||||
- name: Move artifacts
|
||||
run: mkdir -p ~/releases/maven/development && cp -r edu ~/releases/maven/development
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get install -y libgtk2.0-0
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build -x Javadoc
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Shuffleboard Build
|
||||
path: |
|
||||
build/allOutputs/
|
||||
retention-days: 7
|
||||
|
||||
PathWeaver:
|
||||
name: "Build - PathWeaver"
|
||||
needs: [build-artifacts]
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: wpilibsuite/PathWeaver
|
||||
fetch-depth: 0
|
||||
- name: Patch PathWeaver to use local development
|
||||
run: sed -i "s/wpilibTools.deps.wpilibVersion.*/wpilibTools.deps.wpilibVersion = \'$YEAR\.424242\.+\'/" dependencies.gradle
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: MavenArtifacts
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: 'temurin'
|
||||
- name: Move artifacts
|
||||
run: mkdir -p ~/releases/maven/development && cp -r edu ~/releases/maven/development
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PathWeaver Build
|
||||
path: |
|
||||
build/allOutputs/
|
||||
retention-days: 7
|
||||
|
||||
Robotpy:
|
||||
name: "Build - Robotpy"
|
||||
needs: [build-artifacts]
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: robotpy/mostrobotpy
|
||||
fetch-depth: 0
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: MavenArtifacts
|
||||
- name: Move artifacts
|
||||
run: mkdir -p ~/releases/maven/development && cp -r edu ~/releases/maven/development
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.13
|
||||
|
||||
#
|
||||
# Setup build caching
|
||||
#
|
||||
|
||||
- name: Set ccache size
|
||||
shell: bash
|
||||
id: ccache
|
||||
run: echo "MAX_SIZE=500M" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Setup ccache
|
||||
# uses: hendrikmuhs/ccache-action@v1.2.10
|
||||
uses: robotpy/ccache-action@fork
|
||||
with:
|
||||
key: ubuntu-22.04-3.13
|
||||
variant: ccache
|
||||
max-size: ${{ steps.ccache.outputs.max_size }}
|
||||
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
python -m pip --disable-pip-version-check install -r rdev_requirements.txt
|
||||
|
||||
- name: Install numpy (needed for stubgen but broken in raspbian CI)
|
||||
shell: bash
|
||||
run: |
|
||||
python -m pip --disable-pip-version-check install numpy
|
||||
- name: Patch RobotPy rdev to use local development
|
||||
run: git config user.name github-actions && git config user.email github-actions@github.com && set -- ~/releases/maven/development/edu/wpi/first/wpiutil/wpiutil-cpp/*/ ; wpilibversion=$(basename $1) && echo $wpilibversion && sed --regexp-extended -i 's@(wpilib_bin_url =).*@\1 \"file:\/\/'"$HOME"'\/releases\/maven\/development"@' rdev.toml && sed --regexp-extended -i 's/(wpilib_bin_version =).*/\1 \"'"$wpilibversion"'\"/' rdev.toml && ./rdev.sh update-pyproject --commit
|
||||
- name: Build + test wheels
|
||||
shell: bash
|
||||
run: |
|
||||
./rdev.sh ci run
|
||||
env:
|
||||
RPYBUILD_STRIP_LIBPYTHON: "1"
|
||||
RPYBUILD_CC_LAUNCHER: ccache
|
||||
|
||||
2
.github/workflows/upstream-utils.yml
vendored
2
.github/workflows/upstream-utils.yml
vendored
@@ -13,7 +13,7 @@ concurrency:
|
||||
jobs:
|
||||
update:
|
||||
name: "Update"
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
||||
@@ -14,6 +14,7 @@ modifiableFileExclude {
|
||||
thirdparty/
|
||||
\.patch$
|
||||
gradlew
|
||||
BUILD.bazel
|
||||
}
|
||||
|
||||
generatedFileExclude {
|
||||
|
||||
50
BUILD.bazel
Normal file
50
BUILD.bazel
Normal file
@@ -0,0 +1,50 @@
|
||||
load("@aspect_bazel_lib//lib:write_source_files.bzl", "write_source_files")
|
||||
load("@rules_python//python:pip.bzl", "compile_pip_requirements")
|
||||
|
||||
filegroup(
|
||||
name = "license",
|
||||
srcs = [
|
||||
"LICENSE.md",
|
||||
"ThirdPartyNotices.txt",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
# bazel build //:requirements.lock
|
||||
compile_pip_requirements(
|
||||
name = "requirements",
|
||||
extra_args = ["--allow-unsafe"],
|
||||
requirements_in = "requirements.txt",
|
||||
requirements_txt = "requirements_lock.txt",
|
||||
# compile_pip_requirements does not respect target_compatible_with for some of the targets it generates under the hood
|
||||
tags = ["no-systemcore"],
|
||||
)
|
||||
|
||||
alias(
|
||||
name = "quickbuf_protoc",
|
||||
actual = select({
|
||||
"@bazel_tools//src/conditions:darwin": "@quickbuffer_protoc_osx//file",
|
||||
"@bazel_tools//src/conditions:windows": "@quickbuffer_protoc_windows//file",
|
||||
"@rules_bzlmodrio_toolchains//constraints/combined:is_linux": "@quickbuffer_protoc_linux//file",
|
||||
}),
|
||||
tags = ["pregeneration"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
# This is a helper to run all of the pregeneration scripts at once.
|
||||
write_source_files(
|
||||
name = "write_all",
|
||||
additional_update_targets = [
|
||||
"//hal:write_hal",
|
||||
"//ntcore:write_ntcore",
|
||||
"//wpilibc:write_wpilibc",
|
||||
"//wpilibcExamples:write_example_project_list",
|
||||
"//wpilibj:write_wpilibj",
|
||||
"//wpilibjExamples:write_example_project_list",
|
||||
"//wpilibNewCommands:write_wpilib_new_commands",
|
||||
"//wpimath:write_wpimath",
|
||||
"//wpiunits:write_wpiunits",
|
||||
"//wpiutil:write_wpiutil",
|
||||
],
|
||||
tags = ["pregeneration"],
|
||||
)
|
||||
@@ -291,6 +291,8 @@ set(WPIUNITS_DEP_REPLACE_IMPL "find_dependency(wpiunits)")
|
||||
set(WPIUTIL_DEP_REPLACE "find_dependency(wpiutil)")
|
||||
add_subdirectory(wpiutil)
|
||||
|
||||
add_subdirectory(datalog)
|
||||
|
||||
if(WITH_NTCORE)
|
||||
set(NTCORE_DEP_REPLACE "find_dependency(ntcore)")
|
||||
set(WPINET_DEP_REPLACE "find_dependency(wpinet)")
|
||||
@@ -324,7 +326,6 @@ if(WITH_GUI)
|
||||
add_subdirectory(wpical)
|
||||
endif()
|
||||
if(LIBSSH_FOUND)
|
||||
add_subdirectory(roborioteamnumbersetter)
|
||||
add_subdirectory(datalogtool)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -14,54 +14,27 @@ The development repository is where development releases of every commit to [mai
|
||||
## Artifact classifiers
|
||||
We provide two base types of artifacts.
|
||||
|
||||
The first types are Java artifacts. These are usually published as `jar` files. Usually, the actual jar file is published with no classifier. The sources are published with the `-sources` classifier, and the javadocs are published with the `-javadoc` classifier.
|
||||
|
||||
The second types are native artifacts. These are usually published as `zip` files (except for the `JNI` artifact types, which are `jar` files. See below for information on this). The `-sources` and `-headers` classifiers contain the sources and headers respectively for the library. Each artifact also contains a classifier for each platform we publish. This platform is in the format `{os}{arch}`. The platform artifact only contains the binaries for a specific platform. In addition, we provide a `-all` classifier. This classifier combines all of the platform artifacts into a single artifact. This is useful for tools that cannot determine what version to use during builds. However, we recommend using the platform specific classifier when possible. Note that the binary artifacts never contain the headers, you always need the `-headers` classifier to get those.
|
||||
|
||||
## Artifact Names
|
||||
|
||||
WPILib builds four different types of artifacts.
|
||||
|
||||
##### C++ Only Libraries
|
||||
When we publish C++ only libraries, they are published with the base artifact name as their artifact name, with a `-cpp` extension. All dependencies for the library are linked as shared libraries to the binary.
|
||||
|
||||
|
||||
Example:
|
||||
```
|
||||
edu.wpi.first.wpilibc:wpilibc-cpp:version:classifier@zip
|
||||
```
|
||||
|
||||
#### Java Only Libraries
|
||||
When we publish Java only libraries, they are published with the base artifact name as their artifact name, with a `-java` extension.
|
||||
The first types are Java artifacts. These are usually published as `jar` files. Usually, the actual jar file is published with no classifier. The sources are published with the `-sources` classifier, and the javadocs are published with the `-javadoc` classifier. These artifacts are published with the base artifact name as their artifact ID, with a `-java` extension.
|
||||
|
||||
Example:
|
||||
```
|
||||
edu.wpi.first.wpilibj:wpilibj-java:version
|
||||
```
|
||||
|
||||
#### C++/Java Libraries without JNI
|
||||
For libraries that are both C++ and Java, but without a JNI component, the C++ component is published with the `basename-cpp` artifact name, and the Java component is published with the `basename-java` artifact name.
|
||||
The second types are native artifacts. These are usually published as `zip` files. The `-sources` and `-headers` classifiers contain the sources and headers respectively for the library. Each artifact also contains a classifier for each platform we publish. This platform is in the format `{os}{arch}`. The full list of supported platforms can be found in [native-utils](https://github.com/wpilibsuite/native-utils/blob/main/src/main/java/edu/wpi/first/nativeutils/WPINativeUtilsExtension.java#L94). If the library is built statically, it will have `static` appended to the classifier. Additionally, if the library was built in debug mode, `debug` will be appended to the classifier. The platform artifact only contains the binaries for a specific platform. Note that the binary artifacts never contain the headers, you always need the `-headers` classifier to get those.
|
||||
|
||||
If the library is Java and C++ and has a JNI component, the native artifact will have a shared library containing JNI entrypoints alongside the C++ shared library. This JNI shared library will have a `jni` suffix in the file name.
|
||||
|
||||
Native artifacts are published with the base artifact name as their artifact ID, with a `-cpp` extension.
|
||||
|
||||
Example:
|
||||
```
|
||||
edu.wpi.first.wpiutil:wpiutil-cpp:version:classifier@zip (C++)
|
||||
edu.wpi.first.wpiutil:wpiutil-java:version (Java)
|
||||
```
|
||||
|
||||
#### C++/Java Libraries with JNI
|
||||
For libraries that are both C++ and Java with a JNI component there are three different artifact names. For Java, the component is published as `basename-java`. For C++, the `basename-cpp` artifact contains the C++ artifacts with all dependencies linked as shared libraries to the binary. These binaries DO contain the JNI entry points. The `basename-jni` artifact contains identical C++ binaries to the `-cpp` artifact, however all of its dependencies are statically linked, and only the JNI and C entry points are exported.
|
||||
|
||||
The `-jni` artifact should only be used in cases where you want to create a self contained Java application where the native artifacts are embedded in the jar. Note in an extraction scenario, extending off of the library is never supported, which is why the C++ entry points are not exposed. The name of the library is randomly generated during extraction. For pretty much all cases, and if you ever want to extend from a native library, you should use the `-cpp` artifacts. GradleRIO uses the `-cpp` artifacts for all platforms, even desktop, for this reason.
|
||||
|
||||
Example:
|
||||
```
|
||||
edu.wpi.first.ntcore:ntcore-cpp:version:classifier@zip (C++)
|
||||
edu.wpi.first.ntcore:ntcore-jni:version:classifier (JNI jar library)
|
||||
edu.wpi.first.ntcore:ntcore-java:version (Java)
|
||||
edu.wpi.first.wpimath:wpimath-cpp:version:classifier@zip
|
||||
edu.wpi.first.wpimath:wpimath-cpp:version:windowsx86-64staticdebug@zip
|
||||
```
|
||||
|
||||
## Provided Artifacts
|
||||
This repository provides the following artifacts. Below each artifact is its dependencies. Note if ever using the `-jni` artifacts, no dependencies are needed for native binaries.
|
||||
This repository provides the following artifacts. Below each artifact is its dependencies.
|
||||
|
||||
For C++, if building with static dependencies, the listed order should be the link order in your linker.
|
||||
|
||||
|
||||
@@ -23,3 +23,9 @@ Examples:
|
||||
build --local_ram_resources=HOST_RAM*.5 # Don't use more than half my RAM when building
|
||||
build --local_cpu_resources=HOST_CPUS-1 # Leave one core alone
|
||||
```
|
||||
|
||||
## Pregenerating Files
|
||||
allwpilib uses extensive use of pre-generating files that are later used to build C++ / Java libraries that are tracked by version control. Quite often,
|
||||
these pre-generation scripts use some configuration file to create multipile files inside of an output directory. While this process could be accomplished
|
||||
with a `genrule` that would require an explicit listing of every output file, which would be tedious to maintain as well as potentially confusing to people
|
||||
adding new features those libraries. Therefor, we use `@aspect_bazel_lib` and their `write_source_files` feature to generate these directories. In the event that the generation process creates more than a small handful of predictable files, a custom rule is written to generate the directory.
|
||||
|
||||
@@ -24,7 +24,6 @@ WPILib is normally built with Gradle, however for some systems, such as Linux ba
|
||||
* datalogtool
|
||||
* glass
|
||||
* outlineviewer
|
||||
* roborioteamnumbersetter
|
||||
* sysid
|
||||
* halsim_gui (if simulation extensions are enabled)
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ Using Gradle makes building WPILib very straightforward. It only has a few depen
|
||||
- C++ compiler
|
||||
- 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`. Xcode 13 or later is required.
|
||||
- On macOS 13.3 or newer, install Xcode 14 or later (the command-line build tools are insufficient).
|
||||
- ARM compiler toolchain
|
||||
- Run `./gradlew installRoboRioToolchain` after cloning this repository
|
||||
- If the WPILib installer was used, this toolchain is already installed
|
||||
|
||||
171
WORKSPACE
171
WORKSPACE
@@ -1,4 +1,34 @@
|
||||
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file")
|
||||
|
||||
# Rules Python
|
||||
http_archive(
|
||||
name = "rules_python",
|
||||
sha256 = "c68bdc4fbec25de5b5493b8819cfc877c4ea299c0dcb15c244c5a00208cde311",
|
||||
strip_prefix = "rules_python-0.31.0",
|
||||
url = "https://github.com/bazelbuild/rules_python/releases/download/0.31.0/rules_python-0.31.0.tar.gz",
|
||||
)
|
||||
|
||||
load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains")
|
||||
|
||||
py_repositories()
|
||||
|
||||
python_register_toolchains(
|
||||
name = "python_3_10",
|
||||
ignore_root_user_error = True,
|
||||
python_version = "3.10",
|
||||
)
|
||||
|
||||
load("@rules_python//python:pip.bzl", "pip_parse")
|
||||
|
||||
pip_parse(
|
||||
name = "allwpilib_pip_deps",
|
||||
python_interpreter_target = "@python_3_10_host//:python",
|
||||
requirements_lock = "//:requirements_lock.txt",
|
||||
)
|
||||
|
||||
load("@allwpilib_pip_deps//:requirements.bzl", "install_deps")
|
||||
|
||||
install_deps()
|
||||
|
||||
# Download Extra java rules
|
||||
http_archive(
|
||||
@@ -13,6 +43,7 @@ load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps")
|
||||
rules_jvm_external_deps()
|
||||
|
||||
load("@rules_jvm_external//:defs.bzl", "maven_install")
|
||||
load("@rules_jvm_external//:specs.bzl", "maven")
|
||||
|
||||
maven_artifacts = [
|
||||
"org.ejml:ejml-simple:0.43.1",
|
||||
@@ -21,17 +52,100 @@ maven_artifacts = [
|
||||
"com.fasterxml.jackson.core:jackson-databind:2.15.2",
|
||||
"us.hebi.quickbuf:quickbuf-runtime:1.3.3",
|
||||
"com.google.code.gson:gson:2.10.1",
|
||||
maven.artifact(
|
||||
"org.junit.jupiter",
|
||||
"junit-jupiter",
|
||||
"5.10.1",
|
||||
testonly = True,
|
||||
),
|
||||
maven.artifact(
|
||||
"org.junit.platform",
|
||||
"junit-platform-console",
|
||||
"1.10.1",
|
||||
testonly = True,
|
||||
),
|
||||
maven.artifact(
|
||||
"org.junit.platform",
|
||||
"junit-platform-launcher",
|
||||
"1.10.1",
|
||||
testonly = True,
|
||||
),
|
||||
maven.artifact(
|
||||
"org.junit.platform",
|
||||
"junit-platform-reporting",
|
||||
"1.10.1",
|
||||
testonly = True,
|
||||
),
|
||||
maven.artifact(
|
||||
"com.google.code.gson",
|
||||
"gson",
|
||||
"2.10.1",
|
||||
testonly = False,
|
||||
),
|
||||
maven.artifact(
|
||||
"org.hamcrest",
|
||||
"hamcrest-all",
|
||||
"1.3",
|
||||
testonly = True,
|
||||
),
|
||||
maven.artifact(
|
||||
"com.googlecode.junit-toolbox",
|
||||
"junit-toolbox",
|
||||
"2.4",
|
||||
testonly = True,
|
||||
),
|
||||
maven.artifact(
|
||||
"org.apache.ant",
|
||||
"ant",
|
||||
"1.10.12",
|
||||
testonly = True,
|
||||
),
|
||||
maven.artifact(
|
||||
"org.apache.ant",
|
||||
"ant-junit",
|
||||
"1.10.12",
|
||||
testonly = True,
|
||||
),
|
||||
maven.artifact(
|
||||
"org.mockito",
|
||||
"mockito-core",
|
||||
"4.1.0",
|
||||
testonly = True,
|
||||
),
|
||||
maven.artifact(
|
||||
"com.google.testing.compile",
|
||||
"compile-testing",
|
||||
"0.21.0",
|
||||
testonly = True,
|
||||
),
|
||||
]
|
||||
|
||||
maven_install(
|
||||
name = "maven",
|
||||
artifacts = maven_artifacts,
|
||||
maven_install_json = "//:maven_install.json",
|
||||
repositories = [
|
||||
"https://repo1.maven.org/maven2",
|
||||
"https://frcmaven.wpi.edu/artifactory/release/",
|
||||
],
|
||||
)
|
||||
|
||||
load("@maven//:defs.bzl", "pinned_maven_install")
|
||||
|
||||
pinned_maven_install()
|
||||
|
||||
# Setup aspect lib
|
||||
http_archive(
|
||||
name = "aspect_bazel_lib",
|
||||
sha256 = "a8a92645e7298bbf538aa880131c6adb4cf6239bbd27230f077a00414d58e4ce",
|
||||
strip_prefix = "bazel-lib-2.7.2",
|
||||
url = "https://github.com/aspect-build/bazel-lib/releases/download/v2.7.2/bazel-lib-v2.7.2.tar.gz",
|
||||
)
|
||||
|
||||
load("@aspect_bazel_lib//lib:repositories.bzl", "aspect_bazel_lib_dependencies")
|
||||
|
||||
aspect_bazel_lib_dependencies()
|
||||
|
||||
# Download toolchains
|
||||
http_archive(
|
||||
name = "rules_bzlmodrio_toolchains",
|
||||
@@ -99,8 +213,8 @@ setup_legacy_bzlmodrio_ni_cpp_dependencies()
|
||||
|
||||
http_archive(
|
||||
name = "bzlmodrio-opencv",
|
||||
sha256 = "ba3f4910ce9cc0e08abff732aeb5835b1bcfd864ca5296edeadcf2935f7e81b9",
|
||||
url = "https://github.com/wpilibsuite/bzlmodRio-opencv/releases/download/2025.4.10.0-3.bcr1/bzlmodRio-opencv-2025.4.10.0-3.bcr1.tar.gz",
|
||||
sha256 = "6e8544fae07ed5b4fedc146f6ad083d0d8947e3efb5332a20abc46601a52a1b5",
|
||||
url = "https://github.com/wpilibsuite/bzlmodRio-opencv/releases/download/2025.4.10.0-3.bcr2/bzlmodRio-opencv-2025.4.10.0-3.bcr2.tar.gz",
|
||||
)
|
||||
|
||||
load("@bzlmodrio-opencv//:maven_cpp_deps.bzl", "setup_legacy_bzlmodrio_opencv_cpp_dependencies")
|
||||
@@ -111,6 +225,17 @@ load("@bzlmodrio-opencv//:maven_java_deps.bzl", "setup_legacy_bzlmodrio_opencv_j
|
||||
|
||||
setup_legacy_bzlmodrio_opencv_java_dependencies()
|
||||
|
||||
http_archive(
|
||||
name = "bzlmodrio-libssh",
|
||||
sha256 = "65caef82554617403a16c79e8bcac6553d40eca3e23197e63275bba22db7d5b5",
|
||||
strip_prefix = "bzlmodRio-libssh-8405fbd5eb4e42b495f08f6ccf6fbbe5ced28bb7",
|
||||
urls = ["https://github.com/wpilibsuite/bzlmodRio-libssh/archive/8405fbd5eb4e42b495f08f6ccf6fbbe5ced28bb7.tar.gz"],
|
||||
)
|
||||
|
||||
load("@bzlmodrio-libssh//:maven_cpp_deps.bzl", "setup_legacy_bzlmodrio_libssh_cpp_dependencies")
|
||||
|
||||
setup_legacy_bzlmodrio_libssh_cpp_dependencies()
|
||||
|
||||
http_archive(
|
||||
name = "build_bazel_apple_support",
|
||||
sha256 = "c4bb2b7367c484382300aee75be598b92f847896fb31bbd22f3a2346adf66a80",
|
||||
@@ -123,3 +248,43 @@ load(
|
||||
)
|
||||
|
||||
apple_support_dependencies()
|
||||
|
||||
# Setup quickbuf compiler
|
||||
QUICKBUF_VERSION = "1.3.2"
|
||||
|
||||
http_file(
|
||||
name = "quickbuffer_protoc_linux",
|
||||
executable = True,
|
||||
sha256 = "f9a041bccaa7040db523666ef1b5fe9f6f94e70a82c88951f18f58aadd9c50b5",
|
||||
url = "https://repo1.maven.org/maven2/us/hebi/quickbuf/protoc-gen-quickbuf/" + QUICKBUF_VERSION + "/protoc-gen-quickbuf-" + QUICKBUF_VERSION + "-linux-x86_64.exe",
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "quickbuffer_protoc_osx",
|
||||
executable = True,
|
||||
sha256 = "ea307c2b69664ae7e7c69db4cddf5803187e5a34bceffd09a21652f0f16044f7",
|
||||
url = "https://repo1.maven.org/maven2/us/hebi/quickbuf/protoc-gen-quickbuf/" + QUICKBUF_VERSION + "/protoc-gen-quickbuf-" + QUICKBUF_VERSION + "-osx-x86_64.exe ",
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "quickbuffer_protoc_windows",
|
||||
executable = True,
|
||||
sha256 = "27dc1f29764a62b5e6a813a4bcd63e81bbdc3394da760a44acae1025b4a89f1d",
|
||||
url = "https://repo1.maven.org/maven2/us/hebi/quickbuf/protoc-gen-quickbuf/" + QUICKBUF_VERSION + "/protoc-gen-quickbuf-" + QUICKBUF_VERSION + "-windows-x86_64.exe ",
|
||||
)
|
||||
|
||||
# Setup rules_proto
|
||||
http_archive(
|
||||
name = "rules_proto",
|
||||
sha256 = "0e5c64a2599a6e26c6a03d6162242d231ecc0de219534c38cb4402171def21e8",
|
||||
strip_prefix = "rules_proto-7.0.2",
|
||||
url = "https://github.com/bazelbuild/rules_proto/releases/download/7.0.2/rules_proto-7.0.2.tar.gz",
|
||||
)
|
||||
|
||||
load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies")
|
||||
|
||||
rules_proto_dependencies()
|
||||
|
||||
load("@rules_proto//proto:setup.bzl", "rules_proto_setup")
|
||||
|
||||
rules_proto_setup()
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
|
||||
load("@rules_java//java:defs.bzl", "java_binary", "java_library")
|
||||
load("@rules_java//java:defs.bzl", "java_binary")
|
||||
load("@rules_python//python:defs.bzl", "py_binary")
|
||||
load("//shared/bazel/rules:cc_rules.bzl", "wpilib_cc_library")
|
||||
load("//shared/bazel/rules:java_rules.bzl", "wpilib_java_junit5_test")
|
||||
load("//shared/bazel/rules:jni_rules.bzl", "wpilib_jni_cc_library", "wpilib_jni_java_library")
|
||||
load("//shared/bazel/rules/gen:gen-resources.bzl", "generate_resources")
|
||||
|
||||
cc_library(
|
||||
@@ -31,7 +34,6 @@ cc_library(
|
||||
}),
|
||||
includes = ["src/main/native/thirdparty/apriltag/include/common"],
|
||||
strip_include_prefix = "src/main/native/thirdparty/apriltag/include",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
generate_resources(
|
||||
@@ -39,10 +41,9 @@ generate_resources(
|
||||
namespace = "frc",
|
||||
prefix = "APRILTAG",
|
||||
resource_files = glob(["src/main/native/resources/**"]),
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
wpilib_cc_library(
|
||||
name = "apriltag.static",
|
||||
srcs = [":generate-resources"] + glob(
|
||||
["src/main/native/cpp/**"],
|
||||
@@ -59,9 +60,20 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
java_library(
|
||||
wpilib_jni_cc_library(
|
||||
name = "apriltagjni",
|
||||
srcs = glob(["src/main/native/cpp/jni/**"]),
|
||||
java_dep = ":apriltag-java",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":apriltag.static",
|
||||
],
|
||||
)
|
||||
|
||||
wpilib_jni_java_library(
|
||||
name = "apriltag-java",
|
||||
srcs = glob(["src/main/java/**/*.java"]),
|
||||
native_libs = [":apriltagjni"],
|
||||
resource_strip_prefix = "apriltag/src/main/native/resources",
|
||||
resources = glob(["src/main/native/resources/**"]),
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -88,6 +100,20 @@ cc_test(
|
||||
],
|
||||
)
|
||||
|
||||
wpilib_java_junit5_test(
|
||||
name = "apriltag-java-test",
|
||||
srcs = glob(["src/test/java/**/*.java"]),
|
||||
resource_strip_prefix = "apriltag/src/test/resources",
|
||||
resources = glob(["src/test/resources/**"]),
|
||||
deps = [
|
||||
":apriltag-java",
|
||||
"//wpimath:wpimath-java",
|
||||
"//wpiutil:wpiutil-java",
|
||||
"@bzlmodrio-opencv//libraries/java/opencv",
|
||||
"@maven//:com_fasterxml_jackson_core_jackson_databind",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "DevMain-Cpp",
|
||||
srcs = ["src/dev/native/cpp/main.cpp"],
|
||||
@@ -108,5 +134,8 @@ java_binary(
|
||||
py_binary(
|
||||
name = "convert_apriltag_layouts",
|
||||
srcs = ["convert_apriltag_layouts.py"],
|
||||
tags = ["manual"],
|
||||
target_compatible_with = select({
|
||||
"@rules_bzlmodrio_toolchains//constraints/is_systemcore:systemcore": ["@platforms//:incompatible"],
|
||||
"//conditions:default": [],
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -103,14 +103,8 @@ model {
|
||||
return
|
||||
}
|
||||
it.cppCompiler.define 'WPILIB_EXPORTS'
|
||||
|
||||
if (it.component.name == "${nativeName}JNI") {
|
||||
lib project: ':wpimath', library: 'wpimath', linkage: 'static'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
|
||||
} else {
|
||||
lib project: ':wpimath', library: 'wpimath', linkage: 'shared'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
}
|
||||
lib project: ':wpimath', library: 'wpimath', linkage: 'shared'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
}
|
||||
}
|
||||
tasks {
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
# Testing steps for real hardware
|
||||
|
||||
trigger:
|
||||
batch: true
|
||||
branches:
|
||||
include:
|
||||
- master
|
||||
|
||||
stages:
|
||||
- stage: Build
|
||||
jobs:
|
||||
- job: IntegrationTests
|
||||
displayName: Integration Tests
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
|
||||
container:
|
||||
image: wpilib/roborio-cross-ubuntu:2023-22.04
|
||||
|
||||
timeoutInMinutes: 0
|
||||
|
||||
steps:
|
||||
- task: Gradle@2
|
||||
condition: and(succeeded(), not(startsWith(variables['Build.SourceBranch'], 'refs/tags/v')))
|
||||
inputs:
|
||||
workingDirectory: ""
|
||||
gradleWrapperFile: "gradlew"
|
||||
gradleOptions: "-Xmx3072m"
|
||||
publishJUnitResults: false
|
||||
testResultsFiles: "**/TEST-*.xml"
|
||||
tasks: "copyWpilibJIntegrationTestJarToOutput copyWpilibCTestLibrariesToOutput"
|
||||
options: "-Ponlylinuxathena -PbuildServer -PskipJavaFormat"
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
inputs:
|
||||
artifactName: "Integration Tests"
|
||||
targetPath: "build/integrationTestFiles"
|
||||
|
||||
- stage: TestBench
|
||||
displayName: Test Bench
|
||||
condition: false
|
||||
jobs:
|
||||
- job: Cpp
|
||||
displayName: C++
|
||||
pool: RoboRioConnections
|
||||
timeoutInMinutes: 30
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- task: DownloadPipelineArtifact@0
|
||||
inputs:
|
||||
artifactName: "Integration Tests"
|
||||
targetPath: "build/integrationTestFiles"
|
||||
|
||||
- task: ShellScript@2
|
||||
displayName: Run C++ Tests
|
||||
inputs:
|
||||
scriptPath: test-scripts/deploy-and-run-test-on-robot.sh
|
||||
args: 'cpp -A "--gtest_output=xml:/home/admin/testResults/cppreport.xml"'
|
||||
|
||||
- task: PublishTestResults@2
|
||||
displayName: Publish C++ Test Results
|
||||
inputs:
|
||||
testResultsFormat: "JUnit"
|
||||
testResultsFiles: "*.xml"
|
||||
testRunTitle: "C++ Test Report"
|
||||
searchFolder: "$(System.DefaultWorkingDirectory)/test-reports"
|
||||
|
||||
- job: Java
|
||||
pool: RoboRioConnections
|
||||
timeoutInMinutes: 30
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- task: DownloadPipelineArtifact@0
|
||||
inputs:
|
||||
artifactName: "Integration Tests"
|
||||
targetPath: "build/integrationTestFiles"
|
||||
|
||||
- task: ShellScript@2
|
||||
displayName: Run Java Tests
|
||||
inputs:
|
||||
scriptPath: test-scripts/deploy-and-run-test-on-robot.sh
|
||||
args: "java"
|
||||
|
||||
- task: PublishTestResults@2
|
||||
displayName: Publish Java Test Results
|
||||
inputs:
|
||||
testResultsFormat: "JUnit"
|
||||
testResultsFiles: "*.xml"
|
||||
testRunTitle: "Java Test Report"
|
||||
searchFolder: "$(System.DefaultWorkingDirectory)/test-reports"
|
||||
@@ -11,7 +11,7 @@ buildscript {
|
||||
plugins {
|
||||
id 'base'
|
||||
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '2023.0.1'
|
||||
id 'edu.wpi.first.wpilib.repositories.WPILibRepositoriesPlugin' version '2020.2'
|
||||
id 'edu.wpi.first.wpilib.repositories.WPILibRepositoriesPlugin' version '2025.0'
|
||||
id 'edu.wpi.first.NativeUtils' apply false
|
||||
id 'edu.wpi.first.GradleJni' version '1.1.0'
|
||||
id 'edu.wpi.first.GradleVsCode'
|
||||
@@ -32,6 +32,7 @@ allprojects {
|
||||
url = 'https://frcmaven.wpi.edu/artifactory/ex-mvn'
|
||||
}
|
||||
}
|
||||
wpilibRepositories.use2027Repos()
|
||||
if (project.hasProperty('releaseMode')) {
|
||||
wpilibRepositories.addAllReleaseRepositories(it)
|
||||
} else {
|
||||
|
||||
@@ -9,5 +9,5 @@ repositories {
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
implementation "edu.wpi.first:native-utils:2025.9.1"
|
||||
implementation "edu.wpi.first:native-utils:2025.12.1"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
|
||||
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_test")
|
||||
load("@rules_java//java:defs.bzl", "java_binary", "java_library")
|
||||
load("//shared/bazel/rules:cc_rules.bzl", "wpilib_cc_library")
|
||||
|
||||
cc_library(
|
||||
wpilib_cc_library(
|
||||
name = "cameraserver.static",
|
||||
srcs = glob(["src/main/native/cpp/**"]),
|
||||
hdrs = glob(["src/main/native/include/**/*"]),
|
||||
|
||||
@@ -61,9 +61,6 @@ model {
|
||||
lib project: ':cscore', library: 'cscore', linkage: 'static'
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'static'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
|
||||
if (binary.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
|
||||
nativeUtils.useRequiredLibrary(binary, 'ni_link_libraries', 'ni_runtime_libraries')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ public final class Main {
|
||||
} else {
|
||||
System.out.println("Setting up NetworkTables client for team " + team);
|
||||
ntinst.setServerTeam(team);
|
||||
ntinst.startClient4("multicameraserver");
|
||||
ntinst.startClient("multicameraserver");
|
||||
}
|
||||
|
||||
// start cameras
|
||||
|
||||
@@ -190,7 +190,7 @@ int main(int argc, char* argv[]) {
|
||||
ntinst.StartServer();
|
||||
} else {
|
||||
wpi::print("Setting up NetworkTables client for team {}\n", team);
|
||||
ntinst.StartClient4("multicameraserver");
|
||||
ntinst.StartClient("multicameraserver");
|
||||
ntinst.SetServerTeam(team);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
package edu.wpi.first.cameraserver;
|
||||
|
||||
import edu.wpi.first.cscore.AxisCamera;
|
||||
import edu.wpi.first.cscore.CameraServerJNI;
|
||||
import edu.wpi.first.cscore.CvSink;
|
||||
import edu.wpi.first.cscore.CvSource;
|
||||
@@ -541,9 +540,7 @@ public final class CameraServer {
|
||||
* @return The USB camera capturing images.
|
||||
*/
|
||||
public static UsbCamera startAutomaticCapture() {
|
||||
UsbCamera camera = startAutomaticCapture(m_defaultUsbDevice.getAndIncrement());
|
||||
CameraServerSharedStore.getCameraServerShared().reportUsbCamera(camera.getHandle());
|
||||
return camera;
|
||||
return startAutomaticCapture(m_defaultUsbDevice.getAndIncrement());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -558,7 +555,7 @@ public final class CameraServer {
|
||||
public static UsbCamera startAutomaticCapture(int dev) {
|
||||
UsbCamera camera = new UsbCamera("USB Camera " + dev, dev);
|
||||
startAutomaticCapture(camera);
|
||||
CameraServerSharedStore.getCameraServerShared().reportUsbCamera(camera.getHandle());
|
||||
CameraServerSharedStore.reportUsage("UsbCamera[" + dev + "]", "auto");
|
||||
return camera;
|
||||
}
|
||||
|
||||
@@ -572,7 +569,7 @@ public final class CameraServer {
|
||||
public static UsbCamera startAutomaticCapture(String name, int dev) {
|
||||
UsbCamera camera = new UsbCamera(name, dev);
|
||||
startAutomaticCapture(camera);
|
||||
CameraServerSharedStore.getCameraServerShared().reportUsbCamera(camera.getHandle());
|
||||
CameraServerSharedStore.reportUsage("UsbCamera[" + dev + "]", "name");
|
||||
return camera;
|
||||
}
|
||||
|
||||
@@ -586,7 +583,7 @@ public final class CameraServer {
|
||||
public static UsbCamera startAutomaticCapture(String name, String path) {
|
||||
UsbCamera camera = new UsbCamera(name, path);
|
||||
startAutomaticCapture(camera);
|
||||
CameraServerSharedStore.getCameraServerShared().reportUsbCamera(camera.getHandle());
|
||||
CameraServerSharedStore.reportUsage("UsbCamera[" + path + "]", "path");
|
||||
return camera;
|
||||
}
|
||||
|
||||
@@ -603,72 +600,6 @@ public final class CameraServer {
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
*
|
||||
* <p>This overload calls {@link #addAxisCamera(String, String)} with name "Axis Camera".
|
||||
*
|
||||
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
|
||||
* @return The Axis camera capturing images.
|
||||
* @deprecated Call startAutomaticCapture with a HttpCamera instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "2025")
|
||||
@SuppressWarnings("removal")
|
||||
public static AxisCamera addAxisCamera(String host) {
|
||||
return addAxisCamera("Axis Camera", host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
*
|
||||
* <p>This overload calls {@link #addAxisCamera(String, String[])} with name "Axis Camera".
|
||||
*
|
||||
* @param hosts Array of Camera host IPs/DNS names
|
||||
* @return The Axis camera capturing images.
|
||||
* @deprecated Call startAutomaticCapture with a HttpCamera instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "2025")
|
||||
@SuppressWarnings("removal")
|
||||
public static AxisCamera addAxisCamera(String[] hosts) {
|
||||
return addAxisCamera("Axis Camera", hosts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
*
|
||||
* @param name The name to give the camera
|
||||
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
|
||||
* @return The Axis camera capturing images.
|
||||
* @deprecated Call startAutomaticCapture with a HttpCamera instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "2025")
|
||||
@SuppressWarnings("removal")
|
||||
public static AxisCamera addAxisCamera(String name, String host) {
|
||||
AxisCamera camera = new AxisCamera(name, host);
|
||||
// Create a passthrough MJPEG server for USB access
|
||||
startAutomaticCapture(camera);
|
||||
CameraServerSharedStore.getCameraServerShared().reportAxisCamera(camera.getHandle());
|
||||
return camera;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
*
|
||||
* @param name The name to give the camera
|
||||
* @param hosts Array of Camera host IPs/DNS names
|
||||
* @return The Axis camera capturing images.
|
||||
* @deprecated Call startAutomaticCapture with a HttpCamera instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "2025")
|
||||
@SuppressWarnings("removal")
|
||||
public static AxisCamera addAxisCamera(String name, String[] hosts) {
|
||||
AxisCamera camera = new AxisCamera(name, hosts);
|
||||
// Create a passthrough MJPEG server for USB access
|
||||
startAutomaticCapture(camera);
|
||||
CameraServerSharedStore.getCameraServerShared().reportAxisCamera(camera.getHandle());
|
||||
return camera;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a virtual camera for switching between two streams. Unlike the other addCamera methods,
|
||||
* this returns a VideoSink rather than a VideoSource. Calling setSource() on the returned object
|
||||
|
||||
@@ -21,25 +21,12 @@ public interface CameraServerShared {
|
||||
void reportDriverStationError(String error);
|
||||
|
||||
/**
|
||||
* Report an video server usage.
|
||||
* Report usage.
|
||||
*
|
||||
* @param id the usage id
|
||||
* @param resource the resource name
|
||||
* @param data arbitrary string data
|
||||
*/
|
||||
void reportVideoServer(int id);
|
||||
|
||||
/**
|
||||
* Report a usb camera usage.
|
||||
*
|
||||
* @param id the usage id
|
||||
*/
|
||||
void reportUsbCamera(int id);
|
||||
|
||||
/**
|
||||
* Report an axis camera usage.
|
||||
*
|
||||
* @param id the usage id
|
||||
*/
|
||||
void reportAxisCamera(int id);
|
||||
void reportUsage(String resource, String data);
|
||||
|
||||
/**
|
||||
* Get if running on a roboRIO.
|
||||
|
||||
@@ -20,17 +20,11 @@ public final class CameraServerSharedStore {
|
||||
cameraServerShared =
|
||||
new CameraServerShared() {
|
||||
@Override
|
||||
public void reportVideoServer(int id) {}
|
||||
|
||||
@Override
|
||||
public void reportUsbCamera(int id) {}
|
||||
public void reportUsage(String resource, String data) {}
|
||||
|
||||
@Override
|
||||
public void reportDriverStationError(String error) {}
|
||||
|
||||
@Override
|
||||
public void reportAxisCamera(int id) {}
|
||||
|
||||
@Override
|
||||
public Long getRobotMainThreadId() {
|
||||
return null;
|
||||
@@ -40,6 +34,16 @@ public final class CameraServerSharedStore {
|
||||
return cameraServerShared;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report usage.
|
||||
*
|
||||
* @param resource the resource name
|
||||
* @param data arbitrary string data
|
||||
*/
|
||||
public static void reportUsage(String resource, String data) {
|
||||
getCameraServerShared().reportUsage(resource, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the CameraServerShared object.
|
||||
*
|
||||
|
||||
@@ -184,9 +184,9 @@ std::vector<std::string> Instance::GetSourceStreamValues(CS_Source source) {
|
||||
value = "mjpg:" + value;
|
||||
}
|
||||
|
||||
#ifdef __FRC_ROBORIO__
|
||||
#ifdef __FRC_SYSTEMCORE__
|
||||
// Look to see if we have a passthrough server for this source
|
||||
// Only do this on the roboRIO
|
||||
// Only do this on the systemcore
|
||||
for (const auto& i : m_sinks) {
|
||||
CS_Sink sink = i.second.GetHandle();
|
||||
CS_Source sinkSource = cs::GetSinkSource(sink, &status);
|
||||
@@ -471,11 +471,7 @@ Instance::Instance() {
|
||||
}
|
||||
|
||||
cs::UsbCamera CameraServer::StartAutomaticCapture() {
|
||||
cs::UsbCamera camera =
|
||||
StartAutomaticCapture(::GetInstance().m_defaultUsbDevice++);
|
||||
auto csShared = GetCameraServerShared();
|
||||
csShared->ReportUsbCamera(camera.GetHandle());
|
||||
return camera;
|
||||
return StartAutomaticCapture(::GetInstance().m_defaultUsbDevice++);
|
||||
}
|
||||
|
||||
cs::UsbCamera CameraServer::StartAutomaticCapture(int dev) {
|
||||
@@ -483,7 +479,7 @@ cs::UsbCamera CameraServer::StartAutomaticCapture(int dev) {
|
||||
cs::UsbCamera camera{fmt::format("USB Camera {}", dev), dev};
|
||||
StartAutomaticCapture(camera);
|
||||
auto csShared = GetCameraServerShared();
|
||||
csShared->ReportUsbCamera(camera.GetHandle());
|
||||
csShared->ReportUsage(fmt::format("UsbCamera[{}]", dev), "auto");
|
||||
return camera;
|
||||
}
|
||||
|
||||
@@ -493,7 +489,7 @@ cs::UsbCamera CameraServer::StartAutomaticCapture(std::string_view name,
|
||||
cs::UsbCamera camera{name, dev};
|
||||
StartAutomaticCapture(camera);
|
||||
auto csShared = GetCameraServerShared();
|
||||
csShared->ReportUsbCamera(camera.GetHandle());
|
||||
csShared->ReportUsage(fmt::format("UsbCamera[{}]", dev), "name");
|
||||
return camera;
|
||||
}
|
||||
|
||||
@@ -503,67 +499,10 @@ cs::UsbCamera CameraServer::StartAutomaticCapture(std::string_view name,
|
||||
cs::UsbCamera camera{name, path};
|
||||
StartAutomaticCapture(camera);
|
||||
auto csShared = GetCameraServerShared();
|
||||
csShared->ReportUsbCamera(camera.GetHandle());
|
||||
csShared->ReportUsage(fmt::format("UsbCamera[{}]", path), "path");
|
||||
return camera;
|
||||
}
|
||||
|
||||
WPI_IGNORE_DEPRECATED
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(std::string_view host) {
|
||||
return AddAxisCamera("Axis Camera", host);
|
||||
}
|
||||
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(const char* host) {
|
||||
return AddAxisCamera("Axis Camera", host);
|
||||
}
|
||||
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(const std::string& host) {
|
||||
return AddAxisCamera("Axis Camera", host);
|
||||
}
|
||||
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(std::span<const std::string> hosts) {
|
||||
return AddAxisCamera("Axis Camera", hosts);
|
||||
}
|
||||
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
|
||||
std::string_view host) {
|
||||
::GetInstance();
|
||||
cs::AxisCamera camera{name, host};
|
||||
StartAutomaticCapture(camera);
|
||||
auto csShared = GetCameraServerShared();
|
||||
csShared->ReportAxisCamera(camera.GetHandle());
|
||||
return camera;
|
||||
}
|
||||
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
|
||||
const char* host) {
|
||||
::GetInstance();
|
||||
cs::AxisCamera camera{name, host};
|
||||
StartAutomaticCapture(camera);
|
||||
auto csShared = GetCameraServerShared();
|
||||
csShared->ReportAxisCamera(camera.GetHandle());
|
||||
return camera;
|
||||
}
|
||||
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
|
||||
const std::string& host) {
|
||||
::GetInstance();
|
||||
cs::AxisCamera camera{name, host};
|
||||
StartAutomaticCapture(camera);
|
||||
auto csShared = GetCameraServerShared();
|
||||
csShared->ReportAxisCamera(camera.GetHandle());
|
||||
return camera;
|
||||
}
|
||||
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
|
||||
std::span<const std::string> hosts) {
|
||||
::GetInstance();
|
||||
cs::AxisCamera camera{name, hosts};
|
||||
StartAutomaticCapture(camera);
|
||||
auto csShared = GetCameraServerShared();
|
||||
csShared->ReportAxisCamera(camera.GetHandle());
|
||||
return camera;
|
||||
}
|
||||
WPI_UNIGNORE_DEPRECATED
|
||||
cs::MjpegServer CameraServer::AddSwitchedCamera(std::string_view name) {
|
||||
auto& inst = ::GetInstance();
|
||||
// create a dummy CvSource
|
||||
|
||||
@@ -12,9 +12,7 @@
|
||||
namespace {
|
||||
class DefaultCameraServerShared : public frc::CameraServerShared {
|
||||
public:
|
||||
void ReportUsbCamera(int id) override {}
|
||||
void ReportAxisCamera(int id) override {}
|
||||
void ReportVideoServer(int id) override {}
|
||||
void ReportUsage(std::string_view resource, std::string_view data) override {}
|
||||
void SetCameraServerErrorV(fmt::string_view format,
|
||||
fmt::format_args args) override {}
|
||||
void SetVisionRunnerErrorV(fmt::string_view format,
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/deprecated.h>
|
||||
|
||||
#include "cscore_cv.h"
|
||||
|
||||
namespace frc {
|
||||
@@ -75,128 +73,6 @@ class CameraServer {
|
||||
*/
|
||||
static cs::MjpegServer StartAutomaticCapture(const cs::VideoSource& camera);
|
||||
|
||||
WPI_IGNORE_DEPRECATED
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
*
|
||||
* This overload calls AddAxisCamera() with name "Axis Camera".
|
||||
*
|
||||
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
|
||||
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
|
||||
*/
|
||||
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
|
||||
static cs::AxisCamera AddAxisCamera(std::string_view host);
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
*
|
||||
* This overload calls AddAxisCamera() with name "Axis Camera".
|
||||
*
|
||||
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
|
||||
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
|
||||
*/
|
||||
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
|
||||
static cs::AxisCamera AddAxisCamera(const char* host);
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
*
|
||||
* This overload calls AddAxisCamera() with name "Axis Camera".
|
||||
*
|
||||
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
|
||||
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
|
||||
*/
|
||||
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
|
||||
static cs::AxisCamera AddAxisCamera(const std::string& host);
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
*
|
||||
* This overload calls AddAxisCamera() with name "Axis Camera".
|
||||
*
|
||||
* @param hosts Array of Camera host IPs/DNS names
|
||||
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
|
||||
*/
|
||||
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
|
||||
static cs::AxisCamera AddAxisCamera(std::span<const std::string> hosts);
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
*
|
||||
* This overload calls AddAxisCamera() with name "Axis Camera".
|
||||
*
|
||||
* @param hosts Array of Camera host IPs/DNS names
|
||||
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
|
||||
*/
|
||||
template <typename T>
|
||||
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
|
||||
static cs::AxisCamera AddAxisCamera(std::initializer_list<T> hosts) {
|
||||
return AddAxisCamera("Axis Camera", hosts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
*
|
||||
* @param name The name to give the camera
|
||||
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
|
||||
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
|
||||
*/
|
||||
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
|
||||
static cs::AxisCamera AddAxisCamera(std::string_view name,
|
||||
std::string_view host);
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
*
|
||||
* @param name The name to give the camera
|
||||
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
|
||||
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
|
||||
*/
|
||||
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
|
||||
static cs::AxisCamera AddAxisCamera(std::string_view name, const char* host);
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
*
|
||||
* @param name The name to give the camera
|
||||
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
|
||||
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
|
||||
*/
|
||||
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
|
||||
static cs::AxisCamera AddAxisCamera(std::string_view name,
|
||||
const std::string& host);
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
*
|
||||
* @param name The name to give the camera
|
||||
* @param hosts Array of Camera host IPs/DNS names
|
||||
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
|
||||
*/
|
||||
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
|
||||
static cs::AxisCamera AddAxisCamera(std::string_view name,
|
||||
std::span<const std::string> hosts);
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
*
|
||||
* @param name The name to give the camera
|
||||
* @param hosts Array of Camera host IPs/DNS names
|
||||
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
|
||||
*/
|
||||
template <typename T>
|
||||
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
|
||||
static cs::AxisCamera AddAxisCamera(std::string_view name,
|
||||
std::initializer_list<T> hosts) {
|
||||
std::vector<std::string> vec;
|
||||
vec.reserve(hosts.size());
|
||||
for (const auto& host : hosts) {
|
||||
vec.emplace_back(host);
|
||||
}
|
||||
return AddAxisCamera(name, vec);
|
||||
}
|
||||
WPI_UNIGNORE_DEPRECATED
|
||||
|
||||
/**
|
||||
* Adds a virtual camera for switching between two streams. Unlike the
|
||||
* other addCamera methods, this returns a VideoSink rather than a
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
@@ -14,9 +15,8 @@ namespace frc {
|
||||
class CameraServerShared {
|
||||
public:
|
||||
virtual ~CameraServerShared() = default;
|
||||
virtual void ReportUsbCamera(int id) = 0;
|
||||
virtual void ReportAxisCamera(int id) = 0;
|
||||
virtual void ReportVideoServer(int id) = 0;
|
||||
virtual void ReportUsage(std::string_view resource,
|
||||
std::string_view data) = 0;
|
||||
virtual void SetCameraServerErrorV(fmt::string_view format,
|
||||
fmt::format_args args) = 0;
|
||||
virtual void SetVisionRunnerErrorV(fmt::string_view format,
|
||||
|
||||
@@ -46,7 +46,7 @@ macro(wpilib_target_warnings target)
|
||||
|
||||
# Suppress warning "enumeration types with a fixed underlying type are a
|
||||
# Clang extension"
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT EMSCRIPTEN)
|
||||
target_compile_options(${target} PRIVATE $<$<COMPILE_LANGUAGE:C>:-Wno-fixed-enum-extension>)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
import org.gradle.language.base.internal.ProjectLayout
|
||||
import edu.wpi.first.deployutils.deploy.target.RemoteTarget
|
||||
import edu.wpi.first.deployutils.deploy.target.location.SshDeployLocation
|
||||
import edu.wpi.first.deployutils.deploy.artifact.*
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
|
||||
apply plugin: 'cpp'
|
||||
apply plugin: 'visual-studio'
|
||||
apply plugin: 'edu.wpi.first.NativeUtils'
|
||||
apply plugin: ExtraTasks
|
||||
|
||||
apply plugin: 'edu.wpi.first.DeployUtils'
|
||||
|
||||
apply from: '../shared/config.gradle'
|
||||
|
||||
ext {
|
||||
sharedCvConfigs = [crossConnIntegrationTests: []]
|
||||
staticCvConfigs = [:]
|
||||
useJava = false
|
||||
useCpp = true
|
||||
staticGtestConfigs = [crossConnIntegrationTests: []]
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/shared/opencv.gradle"
|
||||
|
||||
apply from: "${rootDir}/shared/googletest.gradle"
|
||||
|
||||
deploy {
|
||||
targets {
|
||||
roborio(RemoteTarget) {
|
||||
directory = '/home/admin'
|
||||
maxChannels = 4
|
||||
locations {
|
||||
ssh(SshDeployLocation) {
|
||||
address = "172.22.11.2"
|
||||
user = 'admin'
|
||||
password = ''
|
||||
ipv6 = false
|
||||
}
|
||||
}
|
||||
|
||||
artifacts {
|
||||
all {
|
||||
predeploy << { ctx ->
|
||||
ctx.execute('/usr/local/frc/bin/frcKillRobot.sh -t')
|
||||
}
|
||||
postdeploy << { ctx ->
|
||||
ctx.execute("sync")
|
||||
ctx.execute("ldconfig")
|
||||
}
|
||||
}
|
||||
|
||||
crossConnIntegrationTests(NativeExecutableArtifact) {
|
||||
libraryDirectory = '/usr/local/frc/third-party/lib'
|
||||
postdeploy << { ctx ->
|
||||
ctx.execute('chmod +x crossConnIntegrationTests')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
model {
|
||||
components {
|
||||
crossConnIntegrationTests(NativeExecutableSpec) {
|
||||
targetBuildTypes 'debug'
|
||||
binaries.all { binary ->
|
||||
if (binary.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
|
||||
if (binary.buildType.name == 'debug') {
|
||||
deploy.targets.roborio.artifacts.crossConnIntegrationTests.binary = binary
|
||||
}
|
||||
|
||||
binary.sources {
|
||||
athenaCpp(CppSourceSet) {
|
||||
source {
|
||||
srcDirs = ['src/main/native/cpp']
|
||||
includes = ['**/*.cpp']
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs = ['src/main/native/include']
|
||||
includes = ['**/*.h']
|
||||
}
|
||||
}
|
||||
}
|
||||
project(':hal').addHalDependency(binary, 'shared')
|
||||
project(':hal').addHalJniDependency(binary)
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
lib project: ':thirdparty:googletest', library: 'googletest', linkage: 'static'
|
||||
if (binary.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
|
||||
nativeUtils.useRequiredLibrary(binary, 'ni_link_libraries', 'ni_runtime_libraries')
|
||||
}
|
||||
} else {
|
||||
binary.sources {
|
||||
simCpp(CppSourceSet) {
|
||||
source {
|
||||
srcDirs 'src/main/native/dt'
|
||||
includes = ['**/*.cpp']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('deployTests') {
|
||||
try {
|
||||
dependsOn tasks.named('deployCrossConnIntegrationTestsLibrariesRoborio')
|
||||
dependsOn tasks.named('deployCrossConnIntegrationTestsRoborio')
|
||||
} catch (ignored) {
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <hal/AnalogInput.h>
|
||||
#include <hal/AnalogOutput.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
|
||||
#include "CrossConnects.h"
|
||||
#include "LifetimeWrappers.h"
|
||||
|
||||
using namespace hlt;
|
||||
|
||||
class AnalogCrossTest : public ::testing::TestWithParam<std::pair<int, int>> {};
|
||||
|
||||
TEST_P(AnalogCrossTest, AnalogCross) {
|
||||
auto param = GetParam();
|
||||
|
||||
int32_t status = 0;
|
||||
AnalogInputHandle input{param.first, &status};
|
||||
ASSERT_EQ(0, status);
|
||||
AnalogOutputHandle output{param.second, &status};
|
||||
ASSERT_EQ(0, status);
|
||||
|
||||
for (double i = 0; i < 5; i += 0.1) {
|
||||
HAL_SetAnalogOutput(output, i, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
usleep(1000);
|
||||
ASSERT_NEAR(i, HAL_GetAnalogVoltage(input, &status), 0.01);
|
||||
ASSERT_EQ(0, status);
|
||||
}
|
||||
|
||||
for (double i = 5; i > 0; i -= 0.1) {
|
||||
HAL_SetAnalogOutput(output, i, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
usleep(1000);
|
||||
ASSERT_NEAR(i, HAL_GetAnalogVoltage(input, &status), 0.01);
|
||||
ASSERT_EQ(0, status);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AnalogInputTest, AllocateAll) {
|
||||
wpi::SmallVector<AnalogInputHandle, 21> analogHandles;
|
||||
for (int i = 0; i < HAL_GetNumAnalogInputs(); i++) {
|
||||
int32_t status = 0;
|
||||
analogHandles.emplace_back(AnalogInputHandle(i, &status));
|
||||
ASSERT_EQ(status, 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AnalogInputTest, MultipleAllocateFails) {
|
||||
int32_t status = 0;
|
||||
AnalogInputHandle handle(0, &status);
|
||||
ASSERT_NE(handle, HAL_kInvalidHandle);
|
||||
ASSERT_EQ(status, 0);
|
||||
|
||||
AnalogInputHandle handle2(0, &status);
|
||||
ASSERT_EQ(handle2, HAL_kInvalidHandle);
|
||||
ASSERT_LAST_ERROR_STATUS(status, RESOURCE_IS_ALLOCATED);
|
||||
}
|
||||
|
||||
TEST(AnalogInputTest, OverAllocateFails) {
|
||||
int32_t status = 0;
|
||||
AnalogInputHandle handle(HAL_GetNumAnalogInputs(), &status);
|
||||
ASSERT_EQ(handle, HAL_kInvalidHandle);
|
||||
ASSERT_LAST_ERROR_STATUS(status, RESOURCE_OUT_OF_RANGE);
|
||||
}
|
||||
|
||||
TEST(AnalogInputTest, UnderAllocateFails) {
|
||||
int32_t status = 0;
|
||||
AnalogInputHandle handle(-1, &status);
|
||||
ASSERT_EQ(handle, HAL_kInvalidHandle);
|
||||
ASSERT_LAST_ERROR_STATUS(status, RESOURCE_OUT_OF_RANGE);
|
||||
}
|
||||
|
||||
TEST(AnalogOutputTest, AllocateAll) {
|
||||
wpi::SmallVector<AnalogOutputHandle, 21> analogHandles;
|
||||
for (int i = 0; i < HAL_GetNumAnalogOutputs(); i++) {
|
||||
int32_t status = 0;
|
||||
analogHandles.emplace_back(AnalogOutputHandle(i, &status));
|
||||
ASSERT_EQ(status, 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AnalogOutputTest, MultipleAllocateFails) {
|
||||
int32_t status = 0;
|
||||
AnalogOutputHandle handle(0, &status);
|
||||
ASSERT_NE(handle, HAL_kInvalidHandle);
|
||||
ASSERT_EQ(status, 0);
|
||||
|
||||
AnalogOutputHandle handle2(0, &status);
|
||||
ASSERT_EQ(handle2, HAL_kInvalidHandle);
|
||||
ASSERT_LAST_ERROR_STATUS(status, RESOURCE_IS_ALLOCATED);
|
||||
}
|
||||
|
||||
TEST(AnalogOutputTest, OverAllocateFails) {
|
||||
int32_t status = 0;
|
||||
AnalogOutputHandle handle(HAL_GetNumAnalogOutputs(), &status);
|
||||
ASSERT_EQ(handle, HAL_kInvalidHandle);
|
||||
ASSERT_LAST_ERROR_STATUS(status, RESOURCE_OUT_OF_RANGE);
|
||||
}
|
||||
|
||||
TEST(AnalogOutputTest, UnderAllocateFails) {
|
||||
int32_t status = 0;
|
||||
AnalogOutputHandle handle(-1, &status);
|
||||
ASSERT_EQ(handle, HAL_kInvalidHandle);
|
||||
ASSERT_LAST_ERROR_STATUS(status, RESOURCE_OUT_OF_RANGE);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(AnalogCrossConnectsTests, AnalogCrossTest,
|
||||
::testing::ValuesIn(AnalogCrossConnects));
|
||||
@@ -1,101 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <hal/DIO.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
|
||||
#include "CrossConnects.h"
|
||||
#include "LifetimeWrappers.h"
|
||||
|
||||
using namespace hlt;
|
||||
|
||||
class DIOTest : public ::testing::TestWithParam<std::pair<int, int>> {};
|
||||
|
||||
TEST_P(DIOTest, DIOCross) {
|
||||
auto param = GetParam();
|
||||
int32_t status = 0;
|
||||
DIOHandle first{param.first, false, &status};
|
||||
ASSERT_EQ(0, status);
|
||||
DIOHandle second{param.second, true, &status};
|
||||
ASSERT_EQ(0, status);
|
||||
|
||||
HAL_SetDIO(first, false, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
usleep(1000);
|
||||
ASSERT_FALSE(HAL_GetDIO(first, &status));
|
||||
ASSERT_EQ(0, status);
|
||||
ASSERT_FALSE(HAL_GetDIO(second, &status));
|
||||
ASSERT_EQ(0, status);
|
||||
|
||||
HAL_SetDIO(first, true, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
usleep(1000);
|
||||
ASSERT_TRUE(HAL_GetDIO(second, &status));
|
||||
ASSERT_EQ(0, status);
|
||||
|
||||
HAL_SetDIODirection(first, true, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
HAL_SetDIODirection(second, false, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
|
||||
HAL_SetDIO(second, false, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
usleep(1000);
|
||||
ASSERT_FALSE(HAL_GetDIO(first, &status));
|
||||
ASSERT_EQ(0, status);
|
||||
|
||||
HAL_SetDIO(second, true, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
usleep(1000);
|
||||
ASSERT_TRUE(HAL_GetDIO(first, &status));
|
||||
ASSERT_EQ(0, status);
|
||||
}
|
||||
|
||||
TEST(DIOTest, AllocateAll) {
|
||||
wpi::SmallVector<DIOHandle, 32> dioHandles;
|
||||
for (int i = 0; i < HAL_GetNumDigitalChannels(); i++) {
|
||||
int32_t status = 0;
|
||||
dioHandles.emplace_back(i, true, &status);
|
||||
ASSERT_EQ(status, 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DIOTest, MultipleAllocateFails) {
|
||||
int32_t status = 0;
|
||||
DIOHandle handle(0, true, &status);
|
||||
ASSERT_NE(handle, HAL_kInvalidHandle);
|
||||
ASSERT_EQ(status, 0);
|
||||
|
||||
DIOHandle handle2(0, true, &status);
|
||||
ASSERT_EQ(handle2, HAL_kInvalidHandle);
|
||||
ASSERT_LAST_ERROR_STATUS(status, RESOURCE_IS_ALLOCATED);
|
||||
}
|
||||
|
||||
TEST(DIOTest, OverAllocateFails) {
|
||||
int32_t status = 0;
|
||||
DIOHandle handle(HAL_GetNumDigitalChannels(), true, &status);
|
||||
ASSERT_EQ(handle, HAL_kInvalidHandle);
|
||||
ASSERT_LAST_ERROR_STATUS(status, RESOURCE_OUT_OF_RANGE);
|
||||
}
|
||||
|
||||
TEST(DIOTest, UnderAllocateFails) {
|
||||
int32_t status = 0;
|
||||
DIOHandle handle(-1, true, &status);
|
||||
ASSERT_EQ(handle, HAL_kInvalidHandle);
|
||||
ASSERT_LAST_ERROR_STATUS(status, RESOURCE_OUT_OF_RANGE);
|
||||
}
|
||||
|
||||
TEST(DIOTest, CrossAllocationFails) {
|
||||
int32_t status = 0;
|
||||
PWMHandle pwmHandle(10, &status);
|
||||
ASSERT_NE(pwmHandle, HAL_kInvalidHandle);
|
||||
ASSERT_EQ(status, 0);
|
||||
DIOHandle handle(10, true, &status);
|
||||
ASSERT_EQ(handle, HAL_kInvalidHandle);
|
||||
ASSERT_LAST_ERROR_STATUS(status, RESOURCE_IS_ALLOCATED);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(DIOCrossConnectsTests, DIOTest,
|
||||
::testing::ValuesIn(DIOCrossConnects));
|
||||
@@ -1,52 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <hal/HAL.h>
|
||||
|
||||
#include "CrossConnects.h"
|
||||
#include "LifetimeWrappers.h"
|
||||
|
||||
using namespace hlt;
|
||||
|
||||
class DutyCycleTest : public ::testing::TestWithParam<std::pair<int, int>> {};
|
||||
|
||||
TEST_P(DutyCycleTest, DutyCycle) {
|
||||
auto param = GetParam();
|
||||
|
||||
int32_t status = 0;
|
||||
PWMHandle pwmHandle(param.first, &status);
|
||||
ASSERT_NE(pwmHandle, HAL_kInvalidHandle);
|
||||
ASSERT_EQ(0, status);
|
||||
|
||||
// Ensure our PWM is disabled, and set up properly
|
||||
HAL_SetPWMPulseTimeMicroseconds(pwmHandle, 0, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
HAL_SetPWMConfigMicroseconds(pwmHandle, 2000, 1000, 1000, 0, 0, &status);
|
||||
HAL_SetPWMConfigMicroseconds(pwmHandle, 5050, 2525, 2525, 2525, 0, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
HAL_SetPWMPeriodScale(pwmHandle, 0, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
|
||||
DIOHandle dioHandle{param.second, true, &status};
|
||||
ASSERT_EQ(0, status);
|
||||
|
||||
DutyCycleHandle dutyCycle{dioHandle, &status};
|
||||
ASSERT_EQ(0, status);
|
||||
|
||||
HAL_SetPWMSpeed(pwmHandle, 0.5, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
|
||||
// Sleep enough time for the frequency to converge
|
||||
usleep(3500000);
|
||||
|
||||
ASSERT_NEAR(
|
||||
1000 / 5.05,
|
||||
static_cast<double>(HAL_GetDutyCycleFrequency(dutyCycle, &status)), 1);
|
||||
|
||||
// TODO measure output
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(DutyCycleCrossConnTests, DutyCycleTest,
|
||||
::testing::ValuesIn(PWMCrossConnects));
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -1,359 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <hal/DMA.h>
|
||||
#include <hal/HAL.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/condition_variable.h>
|
||||
#include <wpi/priority_mutex.h>
|
||||
|
||||
#include "CrossConnects.h"
|
||||
#include "LifetimeWrappers.h"
|
||||
|
||||
using namespace hlt;
|
||||
|
||||
class PWMTest : public ::testing::TestWithParam<std::pair<int, int>> {};
|
||||
|
||||
void TestTimingDMA(int squelch, std::pair<int, int> param) {
|
||||
// Initialize DMA
|
||||
int32_t status = 0;
|
||||
DMAHandle dmaHandle(&status);
|
||||
ASSERT_NE(dmaHandle, HAL_kInvalidHandle);
|
||||
ASSERT_EQ(0, status);
|
||||
|
||||
status = 0;
|
||||
PWMHandle pwmHandle(param.first, &status);
|
||||
ASSERT_NE(pwmHandle, HAL_kInvalidHandle);
|
||||
ASSERT_EQ(0, status);
|
||||
|
||||
// Ensure our PWM is disabled, and set up properly
|
||||
HAL_SetPWMPulseTimeMicroseconds(pwmHandle, 0, &status);
|
||||
HAL_SetPWMConfigMicroseconds(pwmHandle, 2000, 1000, 1000, 0, 0, &status);
|
||||
HAL_SetPWMPeriodScale(pwmHandle, squelch, &status);
|
||||
|
||||
unsigned int checkPeriod = 0;
|
||||
switch (squelch) {
|
||||
case (0):
|
||||
checkPeriod = 5050;
|
||||
break;
|
||||
case (1):
|
||||
checkPeriod = 10100;
|
||||
break;
|
||||
case (3):
|
||||
checkPeriod = 20200;
|
||||
break;
|
||||
}
|
||||
|
||||
status = 0;
|
||||
DIOHandle dioHandle(param.second, true, &status);
|
||||
ASSERT_NE(dioHandle, HAL_kInvalidHandle);
|
||||
|
||||
HAL_AddDMADigitalSource(dmaHandle, dioHandle, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
|
||||
HAL_SetDMAExternalTrigger(dmaHandle, dioHandle,
|
||||
HAL_AnalogTriggerType::HAL_Trigger_kInWindow, true,
|
||||
true, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
|
||||
// Loop to test 5 speeds
|
||||
for (unsigned int testWidth = 1000; testWidth < 2100; testWidth += 250) {
|
||||
HAL_StartDMA(dmaHandle, 1024, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
|
||||
while (true) {
|
||||
int32_t remaining = 0;
|
||||
HAL_DMASample testSample;
|
||||
HAL_ReadDMA(dmaHandle, &testSample, 0.01, &remaining, &status);
|
||||
if (remaining == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
HAL_SetPWMSpeed(pwmHandle, (testWidth - 1000) / 1000.0, &status);
|
||||
|
||||
constexpr const int kSampleCount = 15;
|
||||
HAL_DMASample dmaSamples[kSampleCount];
|
||||
int readCount = 0;
|
||||
while (readCount < kSampleCount) {
|
||||
status = 0;
|
||||
int32_t remaining = 0;
|
||||
HAL_DMAReadStatus readStatus = HAL_ReadDMA(
|
||||
dmaHandle, &dmaSamples[readCount], 1.0, &remaining, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
ASSERT_EQ(HAL_DMAReadStatus::HAL_DMA_OK, readStatus);
|
||||
readCount++;
|
||||
}
|
||||
|
||||
HAL_SetPWMSpeed(pwmHandle, 0, &status);
|
||||
HAL_StopDMA(dmaHandle, &status);
|
||||
|
||||
// Find first rising edge
|
||||
int startIndex = 4;
|
||||
while (startIndex < 6) {
|
||||
status = 0;
|
||||
auto value = HAL_GetDMASampleDigitalSource(&dmaSamples[startIndex],
|
||||
dioHandle, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
if (value) {
|
||||
break;
|
||||
}
|
||||
startIndex++;
|
||||
}
|
||||
ASSERT_LT(startIndex, 6);
|
||||
|
||||
// Check that samples alternate
|
||||
bool previous = false;
|
||||
int iterationCount = 0;
|
||||
for (int i = startIndex; i < startIndex + 8; i++) {
|
||||
auto value =
|
||||
HAL_GetDMASampleDigitalSource(&dmaSamples[i], dioHandle, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
ASSERT_NE(previous, value);
|
||||
previous = !previous;
|
||||
iterationCount++;
|
||||
}
|
||||
ASSERT_EQ(iterationCount, 8);
|
||||
iterationCount = 0;
|
||||
|
||||
// Check width between samples
|
||||
for (int i = startIndex; i < startIndex + 8; i += 2) {
|
||||
auto width = HAL_GetDMASampleTime(&dmaSamples[i + 1], &status) -
|
||||
HAL_GetDMASampleTime(&dmaSamples[i], &status);
|
||||
ASSERT_NEAR(testWidth, width, 10);
|
||||
iterationCount++;
|
||||
}
|
||||
ASSERT_EQ(iterationCount, 4);
|
||||
iterationCount = 0;
|
||||
|
||||
// Check period between samples
|
||||
for (int i = startIndex; i < startIndex + 6; i += 2) {
|
||||
auto period = HAL_GetDMASampleTime(&dmaSamples[i + 2], &status) -
|
||||
HAL_GetDMASampleTime(&dmaSamples[i], &status);
|
||||
ASSERT_NEAR(checkPeriod, period, 10);
|
||||
iterationCount++;
|
||||
}
|
||||
ASSERT_EQ(iterationCount, 3);
|
||||
}
|
||||
}
|
||||
|
||||
struct InterruptCheckData {
|
||||
wpi::SmallVector<uint64_t, 8> risingStamps;
|
||||
wpi::SmallVector<uint64_t, 8> fallingStamps;
|
||||
wpi::priority_mutex mutex;
|
||||
wpi::condition_variable cond;
|
||||
HAL_InterruptHandle handle;
|
||||
};
|
||||
|
||||
// TODO switch this to DMA
|
||||
void TestTiming(int squelch, std::pair<int, int> param) {
|
||||
// Initialize interrupt
|
||||
int32_t status = 0;
|
||||
InterruptHandle interruptHandle(&status);
|
||||
// Ensure we have a valid interrupt handle
|
||||
ASSERT_NE(interruptHandle, HAL_kInvalidHandle);
|
||||
|
||||
status = 0;
|
||||
PWMHandle pwmHandle(param.first, &status);
|
||||
ASSERT_NE(pwmHandle, HAL_kInvalidHandle);
|
||||
|
||||
// Ensure our PWM is disabled, and set up properly
|
||||
HAL_SetPWMPulseTimeMicroseconds(pwmHandle, 0, &status);
|
||||
HAL_SetPWMConfigMicroseconds(pwmHandle, 2000, 1000, 1000, 0, 0, &status);
|
||||
HAL_SetPWMPeriodScale(pwmHandle, squelch, &status);
|
||||
|
||||
unsigned int checkPeriod = 0;
|
||||
switch (squelch) {
|
||||
case (0):
|
||||
checkPeriod = 5050;
|
||||
break;
|
||||
case (1):
|
||||
checkPeriod = 10100;
|
||||
break;
|
||||
case (3):
|
||||
checkPeriod = 20200;
|
||||
break;
|
||||
}
|
||||
|
||||
status = 0;
|
||||
DIOHandle dioHandle(param.second, true, &status);
|
||||
ASSERT_NE(dioHandle, HAL_kInvalidHandle);
|
||||
|
||||
InterruptCheckData interruptData;
|
||||
interruptData.handle = interruptHandle;
|
||||
|
||||
// Can use any type for the interrupt handle
|
||||
HAL_RequestInterrupts(interruptHandle, dioHandle,
|
||||
HAL_AnalogTriggerType::HAL_Trigger_kInWindow, &status);
|
||||
|
||||
HAL_SetInterruptUpSourceEdge(interruptHandle, true, true, &status);
|
||||
|
||||
// Loop to test 5 speeds
|
||||
for (unsigned int i = 1000; i < 2100; i += 250) {
|
||||
interruptData.risingStamps.clear();
|
||||
interruptData.fallingStamps.clear();
|
||||
|
||||
std::atomic_bool runThread{true};
|
||||
|
||||
status = 0;
|
||||
std::thread interruptThread([&]() {
|
||||
while (runThread) {
|
||||
int32_t threadStatus = 0;
|
||||
auto mask =
|
||||
HAL_WaitForInterrupt(interruptHandle, 5, true, &threadStatus);
|
||||
|
||||
if ((mask & 0x100) == 0x100 && interruptData.risingStamps.size() == 0 &&
|
||||
interruptData.fallingStamps.size() == 0) {
|
||||
// Falling edge at start of tracking. Skip
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t status = 0;
|
||||
if ((mask & 0x1) == 0x1) {
|
||||
auto ts = HAL_ReadInterruptRisingTimestamp(interruptHandle, &status);
|
||||
// Rising Edge
|
||||
interruptData.risingStamps.push_back(ts);
|
||||
} else if ((mask & 0x100) == 0x100) {
|
||||
auto ts = HAL_ReadInterruptFallingTimestamp(interruptHandle, &status);
|
||||
// Falling Edge
|
||||
interruptData.fallingStamps.push_back(ts);
|
||||
}
|
||||
|
||||
if (interruptData.risingStamps.size() >= 4 &&
|
||||
interruptData.fallingStamps.size() >= 4) {
|
||||
interruptData.cond.notify_all();
|
||||
runThread = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Ensure our interrupt actually got created correctly.
|
||||
ASSERT_EQ(status, 0);
|
||||
HAL_SetPWMSpeed(pwmHandle, (i - 1000) / 1000.0, &status);
|
||||
ASSERT_EQ(status, 0);
|
||||
{
|
||||
std::unique_lock<wpi::priority_mutex> lock(interruptData.mutex);
|
||||
// Wait for lock
|
||||
// TODO: Add Timeout
|
||||
auto timeout = interruptData.cond.wait_for(lock, std::chrono::seconds(2));
|
||||
if (timeout == std::cv_status::timeout) {
|
||||
runThread = false;
|
||||
if (interruptThread.joinable()) {
|
||||
interruptThread.join();
|
||||
}
|
||||
ASSERT_TRUE(false); // Exit test as failure on timeout
|
||||
}
|
||||
}
|
||||
|
||||
HAL_SetPWMPulseTimeMicroseconds(pwmHandle, 0, &status);
|
||||
|
||||
// Ensure our interrupts have the proper counts
|
||||
ASSERT_EQ(interruptData.risingStamps.size(),
|
||||
interruptData.fallingStamps.size());
|
||||
|
||||
ASSERT_GT(interruptData.risingStamps.size(), 0u);
|
||||
|
||||
ASSERT_EQ(interruptData.risingStamps.size() % 2, 0u);
|
||||
ASSERT_EQ(interruptData.fallingStamps.size() % 2, 0u);
|
||||
|
||||
for (size_t j = 0; j < interruptData.risingStamps.size(); j++) {
|
||||
uint64_t width =
|
||||
interruptData.fallingStamps[j] - interruptData.risingStamps[j];
|
||||
ASSERT_NEAR(width, i, 10);
|
||||
}
|
||||
|
||||
for (unsigned int j = 0; j < interruptData.risingStamps.size() - 1; j++) {
|
||||
uint64_t period =
|
||||
interruptData.risingStamps[j + 1] - interruptData.risingStamps[j];
|
||||
ASSERT_NEAR(period, checkPeriod, 10);
|
||||
}
|
||||
runThread = false;
|
||||
if (interruptThread.joinable()) {
|
||||
interruptThread.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(PWMTest, Timing4x) {
|
||||
auto param = GetParam();
|
||||
TestTiming(3, param);
|
||||
}
|
||||
|
||||
TEST_P(PWMTest, Timing2x) {
|
||||
auto param = GetParam();
|
||||
TestTiming(1, param);
|
||||
}
|
||||
|
||||
TEST_P(PWMTest, Timing1x) {
|
||||
auto param = GetParam();
|
||||
TestTiming(0, param);
|
||||
}
|
||||
|
||||
TEST_P(PWMTest, TimingDMA4x) {
|
||||
auto param = GetParam();
|
||||
TestTimingDMA(3, param);
|
||||
}
|
||||
|
||||
TEST_P(PWMTest, TimingDMA2x) {
|
||||
auto param = GetParam();
|
||||
TestTimingDMA(1, param);
|
||||
}
|
||||
|
||||
TEST_P(PWMTest, TimingDMA1x) {
|
||||
auto param = GetParam();
|
||||
TestTimingDMA(0, param);
|
||||
}
|
||||
|
||||
TEST(PWMTest, AllocateAll) {
|
||||
wpi::SmallVector<PWMHandle, 21> pwmHandles;
|
||||
for (int i = 0; i < HAL_GetNumPWMChannels(); i++) {
|
||||
int32_t status = 0;
|
||||
pwmHandles.emplace_back(PWMHandle(i, &status));
|
||||
ASSERT_EQ(status, 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(PWMTest, MultipleAllocateFails) {
|
||||
int32_t status = 0;
|
||||
PWMHandle handle(0, &status);
|
||||
ASSERT_NE(handle, HAL_kInvalidHandle);
|
||||
ASSERT_EQ(status, 0);
|
||||
|
||||
PWMHandle handle2(0, &status);
|
||||
ASSERT_EQ(handle2, HAL_kInvalidHandle);
|
||||
ASSERT_LAST_ERROR_STATUS(status, RESOURCE_IS_ALLOCATED);
|
||||
}
|
||||
|
||||
TEST(PWMTest, OverAllocateFails) {
|
||||
int32_t status = 0;
|
||||
PWMHandle handle(HAL_GetNumPWMChannels(), &status);
|
||||
ASSERT_EQ(handle, HAL_kInvalidHandle);
|
||||
ASSERT_LAST_ERROR_STATUS(status, RESOURCE_OUT_OF_RANGE);
|
||||
}
|
||||
|
||||
TEST(PWMTest, UnderAllocateFails) {
|
||||
int32_t status = 0;
|
||||
PWMHandle handle(-1, &status);
|
||||
ASSERT_EQ(handle, HAL_kInvalidHandle);
|
||||
ASSERT_LAST_ERROR_STATUS(status, RESOURCE_OUT_OF_RANGE);
|
||||
}
|
||||
|
||||
TEST(PWMTest, CrossAllocationFails) {
|
||||
int32_t status = 0;
|
||||
DIOHandle dioHandle(10, true, &status);
|
||||
ASSERT_NE(dioHandle, HAL_kInvalidHandle);
|
||||
ASSERT_EQ(status, 0);
|
||||
PWMHandle handle(10, &status);
|
||||
ASSERT_EQ(handle, HAL_kInvalidHandle);
|
||||
ASSERT_LAST_ERROR_STATUS(status, RESOURCE_IS_ALLOCATED);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(PWMCrossConnectTests, PWMTest,
|
||||
::testing::ValuesIn(PWMCrossConnects));
|
||||
@@ -1,50 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <hal/AnalogInput.h>
|
||||
#include <hal/Relay.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
|
||||
#include "CrossConnects.h"
|
||||
#include "LifetimeWrappers.h"
|
||||
|
||||
using namespace hlt;
|
||||
|
||||
class RelayAnalogTest : public ::testing::TestWithParam<std::pair<int, int>> {};
|
||||
|
||||
TEST_P(RelayAnalogTest, RelayAnalogCross) {
|
||||
auto param = GetParam();
|
||||
|
||||
int32_t status = 0;
|
||||
RelayHandle relay{param.first, true, &status};
|
||||
ASSERT_EQ(0, status);
|
||||
AnalogInputHandle analog{param.second, &status};
|
||||
ASSERT_EQ(0, status);
|
||||
AnalogTriggerHandle trigger{analog, &status};
|
||||
ASSERT_EQ(0, status);
|
||||
HAL_SetAnalogTriggerLimitsVoltage(trigger, 1.5, 3.0, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
|
||||
HAL_SetRelay(relay, false, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
usleep(1000);
|
||||
ASSERT_FALSE(HAL_GetAnalogTriggerTriggerState(trigger, &status));
|
||||
ASSERT_EQ(0, status);
|
||||
|
||||
HAL_SetRelay(relay, true, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
usleep(1000);
|
||||
ASSERT_TRUE(HAL_GetAnalogTriggerTriggerState(trigger, &status));
|
||||
ASSERT_EQ(0, status);
|
||||
|
||||
HAL_SetRelay(relay, false, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
usleep(1000);
|
||||
ASSERT_FALSE(HAL_GetAnalogTriggerTriggerState(trigger, &status));
|
||||
ASSERT_EQ(0, status);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(RelayAnalogCrossConnectsTests, RelayAnalogTest,
|
||||
::testing::ValuesIn(RelayAnalogCrossConnects));
|
||||
@@ -1,104 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <hal/Relay.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
|
||||
#include "CrossConnects.h"
|
||||
#include "LifetimeWrappers.h"
|
||||
|
||||
using namespace hlt;
|
||||
|
||||
class RelayDigitalTest : public ::testing::TestWithParam<RelayCross> {};
|
||||
|
||||
TEST_P(RelayDigitalTest, RelayCross) {
|
||||
auto param = GetParam();
|
||||
int32_t status = 0;
|
||||
RelayHandle fwd{param.Relay, true, &status};
|
||||
ASSERT_EQ(0, status);
|
||||
RelayHandle rev{param.Relay, false, &status};
|
||||
ASSERT_EQ(0, status);
|
||||
DIOHandle fwdInput{param.FwdDio, true, &status};
|
||||
ASSERT_EQ(0, status);
|
||||
DIOHandle revInput{param.RevDio, true, &status};
|
||||
ASSERT_EQ(0, status);
|
||||
|
||||
HAL_SetRelay(fwd, false, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
HAL_SetRelay(rev, false, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
usleep(1000);
|
||||
ASSERT_FALSE(HAL_GetDIO(fwdInput, &status));
|
||||
ASSERT_EQ(0, status);
|
||||
ASSERT_FALSE(HAL_GetDIO(revInput, &status));
|
||||
ASSERT_EQ(0, status);
|
||||
|
||||
HAL_SetRelay(fwd, false, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
HAL_SetRelay(rev, true, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
usleep(1000);
|
||||
ASSERT_FALSE(HAL_GetDIO(fwdInput, &status));
|
||||
ASSERT_EQ(0, status);
|
||||
ASSERT_TRUE(HAL_GetDIO(revInput, &status));
|
||||
ASSERT_EQ(0, status);
|
||||
|
||||
HAL_SetRelay(fwd, true, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
HAL_SetRelay(rev, false, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
usleep(1000);
|
||||
ASSERT_TRUE(HAL_GetDIO(fwdInput, &status));
|
||||
ASSERT_EQ(0, status);
|
||||
ASSERT_FALSE(HAL_GetDIO(revInput, &status));
|
||||
ASSERT_EQ(0, status);
|
||||
|
||||
HAL_SetRelay(fwd, true, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
HAL_SetRelay(rev, true, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
usleep(1000);
|
||||
ASSERT_TRUE(HAL_GetDIO(fwdInput, &status));
|
||||
ASSERT_EQ(0, status);
|
||||
ASSERT_TRUE(HAL_GetDIO(revInput, &status));
|
||||
ASSERT_EQ(0, status);
|
||||
}
|
||||
|
||||
TEST(RelayDigitalTest, AllocateAll) {
|
||||
wpi::SmallVector<RelayHandle, 32> relayHandles;
|
||||
for (int i = 0; i < HAL_GetNumRelayChannels(); i++) {
|
||||
int32_t status = 0;
|
||||
relayHandles.emplace_back(i / 2, i % 2, &status);
|
||||
ASSERT_EQ(status, 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RelayDigitalTest, MultipleAllocateFails) {
|
||||
int32_t status = 0;
|
||||
RelayHandle handle(0, true, &status);
|
||||
ASSERT_NE(handle, HAL_kInvalidHandle);
|
||||
ASSERT_EQ(status, 0);
|
||||
|
||||
RelayHandle handle2(0, true, &status);
|
||||
ASSERT_EQ(handle2, HAL_kInvalidHandle);
|
||||
ASSERT_LAST_ERROR_STATUS(status, RESOURCE_IS_ALLOCATED);
|
||||
}
|
||||
|
||||
TEST(RelayDigitalTest, OverAllocateFails) {
|
||||
int32_t status = 0;
|
||||
RelayHandle handle(HAL_GetNumRelayChannels(), true, &status);
|
||||
ASSERT_EQ(handle, HAL_kInvalidHandle);
|
||||
ASSERT_LAST_ERROR_STATUS(status, RESOURCE_OUT_OF_RANGE);
|
||||
}
|
||||
|
||||
TEST(RelayDigitalTest, UnderAllocateFails) {
|
||||
int32_t status = 0;
|
||||
RelayHandle handle(-1, true, &status);
|
||||
ASSERT_EQ(handle, HAL_kInvalidHandle);
|
||||
ASSERT_LAST_ERROR_STATUS(status, RESOURCE_OUT_OF_RANGE);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(RelayDigitalCrossConnectsTests, RelayDigitalTest,
|
||||
::testing::ValuesIn(RelayCrossConnects));
|
||||
@@ -1,74 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <hal/HAL.h>
|
||||
#include <wpi/print.h>
|
||||
|
||||
#include "mockds/MockDS.h"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
class TestEnvironment : public testing::Environment {
|
||||
bool m_alreadySetUp = false;
|
||||
MockDS m_mockDS;
|
||||
|
||||
public:
|
||||
TestEnvironment() {
|
||||
// Only set up once. This allows gtest_repeat to be used to automatically
|
||||
// repeat tests.
|
||||
if (m_alreadySetUp) {
|
||||
return;
|
||||
}
|
||||
m_alreadySetUp = true;
|
||||
|
||||
if (!HAL_Initialize(500, 0)) {
|
||||
wpi::print(stderr, "FATAL ERROR: HAL could not be initialized\n");
|
||||
std::exit(-1);
|
||||
}
|
||||
|
||||
m_mockDS.Start();
|
||||
|
||||
// This sets up the network communications library to enable the driver
|
||||
// station. After starting network coms, it will loop until the driver
|
||||
// station returns that the robot is enabled, to ensure that tests will be
|
||||
// able to run on the hardware.
|
||||
HAL_ObserveUserProgramStarting();
|
||||
|
||||
wpi::print("Started coms\n");
|
||||
|
||||
int enableCounter = 0;
|
||||
|
||||
auto checkEnabled = []() {
|
||||
HAL_ControlWord controlWord;
|
||||
std::memset(&controlWord, 0, sizeof(controlWord));
|
||||
HAL_GetControlWord(&controlWord);
|
||||
return controlWord.enabled && controlWord.dsAttached;
|
||||
};
|
||||
HAL_RefreshDSData();
|
||||
while (!checkEnabled()) {
|
||||
if (enableCounter > 50) {
|
||||
// Robot did not enable properly after 5 seconds.
|
||||
// Force exit
|
||||
wpi::print(stderr, " Failed to enable. Aborting\n");
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(100ms);
|
||||
|
||||
wpi::print("Waiting for enable: {}\n", enableCounter++);
|
||||
HAL_RefreshDSData();
|
||||
}
|
||||
std::this_thread::sleep_for(500ms);
|
||||
}
|
||||
|
||||
~TestEnvironment() override { m_mockDS.Stop(); }
|
||||
};
|
||||
|
||||
testing::Environment* const environment =
|
||||
testing::AddGlobalTestEnvironment(new TestEnvironment);
|
||||
@@ -1,86 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "MockDS.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <hal/cpp/fpga_clock.h>
|
||||
#include <wpi/Logger.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/print.h>
|
||||
#include <wpinet/UDPClient.h>
|
||||
|
||||
static void LoggerFunc(unsigned int level, const char* file, unsigned int line,
|
||||
const char* msg) {
|
||||
if (level == 20) {
|
||||
wpi::print(stderr, "DS: {}\n", msg);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string_view levelmsg;
|
||||
if (level >= 50) {
|
||||
levelmsg = "CRITICAL";
|
||||
} else if (level >= 40) {
|
||||
levelmsg = "ERROR";
|
||||
} else if (level >= 30) {
|
||||
levelmsg = "WARNING";
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
wpi::print(stderr, "DS: {}: {} ({}:{})\n", levelmsg, msg, file, line);
|
||||
}
|
||||
|
||||
static void generateEnabledDsPacket(wpi::SmallVectorImpl<uint8_t>& data,
|
||||
uint16_t sendCount) {
|
||||
data.clear();
|
||||
data.push_back(sendCount >> 8);
|
||||
data.push_back(sendCount);
|
||||
data.push_back(0x01); // general data tag
|
||||
data.push_back(0x04); // teleop enabled
|
||||
data.push_back(0x10); // normal data request
|
||||
data.push_back(0x00); // red 1 station
|
||||
}
|
||||
|
||||
void MockDS::Start() {
|
||||
if (m_active) {
|
||||
return;
|
||||
}
|
||||
m_active = true;
|
||||
m_thread = std::thread([&]() {
|
||||
wpi::Logger logger(LoggerFunc);
|
||||
wpi::UDPClient client(logger);
|
||||
client.start();
|
||||
auto timeout_time = hal::fpga_clock::now();
|
||||
int initCount = 0;
|
||||
uint16_t sendCount = 0;
|
||||
wpi::SmallVector<uint8_t, 8> data;
|
||||
while (m_active) {
|
||||
// Keep 20ms intervals, and increase time to next interval
|
||||
auto current = hal::fpga_clock::now();
|
||||
while (timeout_time <= current) {
|
||||
timeout_time += std::chrono::milliseconds(20);
|
||||
}
|
||||
std::this_thread::sleep_until(timeout_time);
|
||||
generateEnabledDsPacket(data, sendCount++);
|
||||
// ~10 disabled packets are required to make the robot actually enable
|
||||
// 1 is definitely not enough.
|
||||
if (initCount < 10) {
|
||||
initCount++;
|
||||
data[3] = 0;
|
||||
}
|
||||
client.send(data, "127.0.0.1", 1110);
|
||||
}
|
||||
client.shutdown();
|
||||
});
|
||||
}
|
||||
|
||||
void MockDS::Stop() {
|
||||
m_active = false;
|
||||
if (m_thread.joinable()) {
|
||||
m_thread.join();
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
|
||||
class MockDS {
|
||||
public:
|
||||
MockDS() = default;
|
||||
~MockDS() { Stop(); }
|
||||
MockDS(const MockDS& other) = delete;
|
||||
MockDS& operator=(const MockDS& other) = delete;
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
std::thread m_thread;
|
||||
std::atomic_bool m_active{false};
|
||||
};
|
||||
@@ -1,62 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <utility>
|
||||
|
||||
namespace hlt {
|
||||
|
||||
constexpr static std::array<std::pair<int, int>, 22> DIOCrossConnects{
|
||||
std::pair{20, 25},
|
||||
std::pair{19, 24},
|
||||
std::pair{17, 13},
|
||||
std::pair{16, 12},
|
||||
std::pair{15, 11},
|
||||
std::pair{14, 10},
|
||||
std::pair{26, 2},
|
||||
std::pair{27, 1},
|
||||
std::pair{28, 0},
|
||||
std::pair{29, 3},
|
||||
std::pair{30, 4},
|
||||
|
||||
// Opposite direction
|
||||
std::pair{25, 20},
|
||||
std::pair{24, 19},
|
||||
std::pair{13, 17},
|
||||
std::pair{12, 16},
|
||||
std::pair{11, 15},
|
||||
std::pair{10, 14},
|
||||
std::pair{2, 26},
|
||||
std::pair{1, 27},
|
||||
std::pair{0, 28},
|
||||
std::pair{3, 29},
|
||||
std::pair{4, 30},
|
||||
};
|
||||
|
||||
// PWM on left, DIO on right
|
||||
constexpr static std::array<std::pair<int, int>, 2> PWMCrossConnects{
|
||||
std::pair{0, 18},
|
||||
std::pair{16, 25},
|
||||
};
|
||||
|
||||
// FWD only, relay on left
|
||||
constexpr static std::array<std::pair<int, int>, 2> RelayAnalogCrossConnects{
|
||||
std::pair{2, 0}, std::pair{3, 1}};
|
||||
|
||||
struct RelayCross {
|
||||
int Relay;
|
||||
int FwdDio;
|
||||
int RevDio;
|
||||
};
|
||||
|
||||
constexpr static std::array<RelayCross, 1> RelayCrossConnects{
|
||||
RelayCross{0, 23, 22}};
|
||||
|
||||
// input on left
|
||||
constexpr static std::array<std::pair<int, int>, 2> AnalogCrossConnects{
|
||||
std::pair{2, 0}, std::pair{4, 1}};
|
||||
|
||||
} // namespace hlt
|
||||
@@ -1,86 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <hal/DMA.h>
|
||||
#include <hal/DutyCycle.h>
|
||||
#include <hal/HAL.h>
|
||||
|
||||
namespace hlt {
|
||||
|
||||
struct InterruptHandle : hal::Handle<HAL_InterruptHandle, HAL_CleanInterrupts> {
|
||||
public:
|
||||
explicit InterruptHandle(int32_t* status)
|
||||
: Handle{HAL_InitializeInterrupts(status)} {}
|
||||
};
|
||||
|
||||
struct DMAHandle : hal::Handle<HAL_DMAHandle, HAL_FreeDMA> {
|
||||
public:
|
||||
explicit DMAHandle(int32_t* status) : Handle{HAL_InitializeDMA(status)} {}
|
||||
};
|
||||
|
||||
struct AnalogInputHandle
|
||||
: hal::Handle<HAL_AnalogInputHandle, HAL_FreeAnalogInputPort> {
|
||||
public:
|
||||
AnalogInputHandle(int32_t port, int32_t* status)
|
||||
: Handle{HAL_InitializeAnalogInputPort(HAL_GetPort(port), nullptr,
|
||||
status)} {}
|
||||
};
|
||||
|
||||
struct AnalogOutputHandle
|
||||
: hal::Handle<HAL_AnalogOutputHandle, HAL_FreeAnalogOutputPort> {
|
||||
public:
|
||||
AnalogOutputHandle(int32_t port, int32_t* status)
|
||||
: Handle{HAL_InitializeAnalogOutputPort(HAL_GetPort(port), nullptr,
|
||||
status)} {}
|
||||
};
|
||||
|
||||
struct AnalogTriggerHandle
|
||||
: hal::Handle<HAL_AnalogTriggerHandle, HAL_CleanAnalogTrigger> {
|
||||
public:
|
||||
explicit AnalogTriggerHandle(HAL_AnalogInputHandle port, int32_t* status)
|
||||
: Handle{HAL_InitializeAnalogTrigger(port, status)} {}
|
||||
};
|
||||
|
||||
struct DutyCycleHandle : hal::Handle<HAL_DutyCycleHandle, HAL_FreeDutyCycle> {
|
||||
public:
|
||||
DutyCycleHandle(HAL_DigitalHandle port, int32_t* status)
|
||||
: Handle{HAL_InitializeDutyCycle(
|
||||
port, HAL_AnalogTriggerType::HAL_Trigger_kInWindow, status)} {}
|
||||
};
|
||||
|
||||
struct DIOHandle : hal::Handle<HAL_DigitalHandle, HAL_FreeDIOPort> {
|
||||
public:
|
||||
DIOHandle() {}
|
||||
|
||||
DIOHandle(int32_t port, HAL_Bool input, int32_t* status)
|
||||
: Handle{
|
||||
HAL_InitializeDIOPort(HAL_GetPort(port), input, nullptr, status)} {}
|
||||
};
|
||||
|
||||
struct PWMHandle : hal::Handle<HAL_DigitalHandle, HAL_FreePWMPort> {
|
||||
public:
|
||||
PWMHandle() {}
|
||||
|
||||
PWMHandle(int32_t port, int32_t* status)
|
||||
: Handle{HAL_InitializePWMPort(HAL_GetPort(port), nullptr, status)} {}
|
||||
};
|
||||
|
||||
struct RelayHandle : hal::Handle<HAL_RelayHandle, HAL_FreeRelayPort> {
|
||||
public:
|
||||
RelayHandle(int32_t port, HAL_Bool fwd, int32_t* status)
|
||||
: Handle{
|
||||
HAL_InitializeRelayPort(HAL_GetPort(port), fwd, nullptr, status)} {}
|
||||
};
|
||||
|
||||
#define ASSERT_LAST_ERROR_STATUS(status, x) \
|
||||
do { \
|
||||
ASSERT_EQ(status, HAL_USE_LAST_ERROR); \
|
||||
[[maybe_unused]] \
|
||||
const char* lastErrorMessageInMacro = HAL_GetLastError(&status); \
|
||||
ASSERT_EQ(status, x); \
|
||||
} while (0)
|
||||
|
||||
} // namespace hlt
|
||||
@@ -38,8 +38,10 @@ includeOtherLibs {
|
||||
^fmt/
|
||||
^gtest/
|
||||
^opencv2/
|
||||
^imgui
|
||||
^support/
|
||||
^tcpsockets/
|
||||
^wpi/
|
||||
^wpigui
|
||||
^wpinet/
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test", "objc_library")
|
||||
load("@rules_java//java:defs.bzl", "java_binary", "java_library")
|
||||
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_test")
|
||||
load("@rules_java//java:defs.bzl", "java_binary")
|
||||
load("//shared/bazel/rules:cc_rules.bzl", "wpilib_cc_library")
|
||||
load("//shared/bazel/rules:java_rules.bzl", "wpilib_java_junit5_test")
|
||||
load("//shared/bazel/rules:jni_rules.bzl", "wpilib_jni_cc_library", "wpilib_jni_java_library")
|
||||
load("//shared/bazel/rules:objectivec_rules.bzl", "wpilib_objc_library")
|
||||
|
||||
WIN_SRCS = glob([
|
||||
"src/main/native/windows/**/*.cpp",
|
||||
@@ -22,7 +26,7 @@ filegroup(
|
||||
}),
|
||||
)
|
||||
|
||||
objc_library(
|
||||
wpilib_objc_library(
|
||||
name = "cscore-mac",
|
||||
srcs = glob([
|
||||
"src/main/native/objcpp/**/*.mm",
|
||||
@@ -32,15 +36,20 @@ objc_library(
|
||||
"src/main/native/include/**/*",
|
||||
"src/main/native/objcpp/**/*.h",
|
||||
]),
|
||||
copts = [
|
||||
"-std=c++20",
|
||||
],
|
||||
include_arc = False,
|
||||
includes = [
|
||||
"src/main/native/cpp",
|
||||
"src/main/native/include",
|
||||
"src/main/native/objcpp",
|
||||
],
|
||||
tags = ["manual"],
|
||||
sdk_frameworks = [
|
||||
"CoreFoundation",
|
||||
"AVFoundation",
|
||||
"Foundation",
|
||||
"CoreMedia",
|
||||
"CoreVideo",
|
||||
"IOKit",
|
||||
],
|
||||
deps = [
|
||||
"//wpinet:wpinet.static",
|
||||
"//wpiutil:wpiutil.static",
|
||||
@@ -48,7 +57,7 @@ objc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
wpilib_cc_library(
|
||||
name = "cscore.static",
|
||||
srcs = [":native-srcs"] + glob(
|
||||
["src/main/native/cpp/**"],
|
||||
@@ -71,9 +80,20 @@ cc_library(
|
||||
}),
|
||||
)
|
||||
|
||||
java_library(
|
||||
wpilib_jni_cc_library(
|
||||
name = "cscorejni",
|
||||
srcs = glob(["src/main/native/cpp/jni/**"]),
|
||||
java_dep = ":cscore-java",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":cscore.static",
|
||||
],
|
||||
)
|
||||
|
||||
wpilib_jni_java_library(
|
||||
name = "cscore-java",
|
||||
srcs = glob(["src/main/java/**/*.java"]),
|
||||
native_libs = [":cscorejni"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//wpiutil:wpiutil-java",
|
||||
@@ -91,6 +111,15 @@ cc_test(
|
||||
],
|
||||
)
|
||||
|
||||
wpilib_java_junit5_test(
|
||||
name = "cscore-java-test",
|
||||
srcs = glob(["src/test/java/**/*.java"]),
|
||||
deps = [
|
||||
":cscore-java",
|
||||
"//wpiutil:wpiutil-java",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "DevMain-Cpp",
|
||||
srcs = ["src/dev/native/cpp/main.cpp"],
|
||||
@@ -108,3 +137,24 @@ java_binary(
|
||||
"//wpiutil:wpiutil-java",
|
||||
],
|
||||
)
|
||||
|
||||
[wpilib_cc_library(
|
||||
name = example + "-examples",
|
||||
srcs = glob([
|
||||
"examples/" + example + "/*.cpp",
|
||||
]),
|
||||
tags = [
|
||||
"wpi-example",
|
||||
],
|
||||
deps = [
|
||||
"//cscore:cscore.static",
|
||||
"//wpigui",
|
||||
],
|
||||
) for example in [
|
||||
"enum_usb",
|
||||
"httpcvstream",
|
||||
"settings",
|
||||
"usbcvstream",
|
||||
"usbstream",
|
||||
"usbviewer",
|
||||
]]
|
||||
|
||||
@@ -21,7 +21,7 @@ if(APPLE)
|
||||
cscore
|
||||
PROPERTIES
|
||||
LINK_FLAGS
|
||||
"-framework CoreFoundation -framework AVFoundation -framework Foundation -framework CoreMedia -framework CoreVideo"
|
||||
"-framework CoreFoundation -framework AVFoundation -framework Foundation -framework CoreMedia -framework CoreVideo -framework IOKit"
|
||||
)
|
||||
elseif(MSVC)
|
||||
target_sources(cscore PRIVATE ${cscore_windows_src})
|
||||
|
||||
@@ -22,7 +22,7 @@ model {
|
||||
|
||||
enableCheckTask true
|
||||
javaCompileTasks << compileJava
|
||||
jniCrossCompileOptions << JniCrossCompileOptions(nativeUtils.wpi.platforms.roborio)
|
||||
jniCrossCompileOptions << JniCrossCompileOptions(nativeUtils.wpi.platforms.systemcore)
|
||||
jniCrossCompileOptions << JniCrossCompileOptions(nativeUtils.wpi.platforms.linuxarm32)
|
||||
jniCrossCompileOptions << JniCrossCompileOptions(nativeUtils.wpi.platforms.linuxarm64)
|
||||
|
||||
@@ -60,13 +60,8 @@ model {
|
||||
if (!it.buildable || !(it instanceof NativeBinarySpec)) {
|
||||
return
|
||||
}
|
||||
if (it.component.name == "${nativeName}JNI") {
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'static'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
|
||||
} else {
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
}
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -177,7 +172,7 @@ model {
|
||||
components {
|
||||
examplesMap.each { key, value ->
|
||||
if (key == "usbviewer") {
|
||||
if (!project.hasProperty('onlylinuxathena')) {
|
||||
if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxsystemcore')) {
|
||||
"${key}"(NativeExecutableSpec) {
|
||||
targetBuildTypes 'debug'
|
||||
binaries.all {
|
||||
@@ -186,7 +181,7 @@ model {
|
||||
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
|
||||
lib library: 'cscore', linkage: 'shared'
|
||||
lib project: ':thirdparty:imgui_suite', library: 'imguiSuite', linkage: 'static'
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.systemcore) {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
@@ -200,9 +195,6 @@ model {
|
||||
it.linker.args << '-lGL'
|
||||
}
|
||||
}
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
|
||||
nativeUtils.useRequiredLibrary(it, 'ni_link_libraries', 'ni_runtime_libraries')
|
||||
}
|
||||
}
|
||||
sources.cpp.source {
|
||||
srcDirs 'examples/' + "${key}"
|
||||
@@ -217,9 +209,6 @@ model {
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
|
||||
lib library: 'cscore', linkage: 'shared'
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
|
||||
nativeUtils.useRequiredLibrary(it, 'ni_link_libraries', 'ni_runtime_libraries')
|
||||
}
|
||||
}
|
||||
sources.cpp.source {
|
||||
srcDirs 'examples/' + "${key}"
|
||||
|
||||
@@ -3,14 +3,17 @@
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <opencv2/core/core.hpp>
|
||||
#include <opencv2/core/mat.hpp>
|
||||
#include <opencv2/imgproc.hpp>
|
||||
#include <wpi/mutex.h>
|
||||
#include <wpi/print.h>
|
||||
#include <wpi/spinlock.h>
|
||||
#include <wpigui.h>
|
||||
@@ -21,10 +24,10 @@
|
||||
namespace gui = wpi::gui;
|
||||
|
||||
int main() {
|
||||
std::atomic<cv::Mat*> latestFrame{nullptr};
|
||||
std::vector<cv::Mat*> sharedFreeList;
|
||||
wpi::spinlock sharedFreeListMutex;
|
||||
std::vector<cv::Mat*> sourceFreeList;
|
||||
wpi::spinlock latestFrameMutex;
|
||||
std::unique_ptr<cv::Mat> latestFrame;
|
||||
wpi::mutex freeListMutex;
|
||||
std::vector<std::unique_ptr<cv::Mat>> freeList;
|
||||
std::atomic<bool> stopCamera{false};
|
||||
|
||||
cs::UsbCamera camera{"usbcam", 0};
|
||||
@@ -42,36 +45,31 @@ int main() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get or create a mat, prefer sourceFreeList over sharedFreeList
|
||||
cv::Mat* out;
|
||||
if (!sourceFreeList.empty()) {
|
||||
out = sourceFreeList.back();
|
||||
sourceFreeList.pop_back();
|
||||
} else {
|
||||
{
|
||||
std::scoped_lock lock(sharedFreeListMutex);
|
||||
for (auto mat : sharedFreeList) {
|
||||
sourceFreeList.emplace_back(mat);
|
||||
}
|
||||
sharedFreeList.clear();
|
||||
}
|
||||
if (!sourceFreeList.empty()) {
|
||||
out = sourceFreeList.back();
|
||||
sourceFreeList.pop_back();
|
||||
// get or create a mat
|
||||
std::unique_ptr<cv::Mat> out;
|
||||
{
|
||||
std::scoped_lock lock{freeListMutex};
|
||||
if (!freeList.empty()) {
|
||||
out = std::move(freeList.back());
|
||||
freeList.pop_back();
|
||||
} else {
|
||||
out = new cv::Mat;
|
||||
out = std::make_unique<cv::Mat>();
|
||||
}
|
||||
}
|
||||
|
||||
// convert to RGBA
|
||||
cv::cvtColor(frame, *out, cv::COLOR_BGR2RGBA);
|
||||
|
||||
// make available
|
||||
auto prev = latestFrame.exchange(out);
|
||||
{
|
||||
// make available
|
||||
std::scoped_lock lock{latestFrameMutex};
|
||||
latestFrame.swap(out);
|
||||
}
|
||||
|
||||
// put prev on free list
|
||||
if (prev) {
|
||||
sourceFreeList.emplace_back(prev);
|
||||
// put the previous frame on free list
|
||||
if (out) {
|
||||
std::scoped_lock lock{freeListMutex};
|
||||
freeList.emplace_back(std::move(out));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -80,7 +78,11 @@ int main() {
|
||||
gui::Initialize("Hello World", 1024, 768);
|
||||
gui::Texture tex;
|
||||
gui::AddEarlyExecute([&] {
|
||||
auto frame = latestFrame.exchange(nullptr);
|
||||
std::unique_ptr<cv::Mat> frame;
|
||||
{
|
||||
std::scoped_lock lock{latestFrameMutex};
|
||||
latestFrame.swap(frame);
|
||||
}
|
||||
if (frame) {
|
||||
// create or update texture
|
||||
if (!tex || frame->cols != tex.GetWidth() ||
|
||||
@@ -90,9 +92,10 @@ int main() {
|
||||
} else {
|
||||
tex.Update(frame->data);
|
||||
}
|
||||
// put back on shared freelist
|
||||
std::scoped_lock lock(sharedFreeListMutex);
|
||||
sharedFreeList.emplace_back(frame);
|
||||
{
|
||||
std::scoped_lock lock{freeListMutex};
|
||||
freeList.emplace_back(std::move(frame));
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(640, 480), ImGuiCond_FirstUseEver);
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.cscore;
|
||||
|
||||
import edu.wpi.cscore.VideoMode.PixelFormat;
|
||||
import edu.wpi.cscore.raw.RawFrame;
|
||||
import java.nio.ByteBuffer;
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
public class RawCVMatSink extends ImageSink {
|
||||
RawFrame frame = new RawFrame();
|
||||
Mat tmpMat;
|
||||
ByteBuffer origByteBuffer;
|
||||
int width;
|
||||
int height;
|
||||
int pixelFormat;
|
||||
int bgrValue = PixelFormat.kBGR.getValue();
|
||||
|
||||
private int getCVFormat(PixelFormat pixelFormat) {
|
||||
return switch (pixelFormat) {
|
||||
case kYUYV, kRGB565, kY16, kUYVY -> CvType.CV_8UC2;
|
||||
case kBGR -> CvType.CV_8UC3;
|
||||
case kBGRA -> CvType.CV_8UC4;
|
||||
case kGray, kMJPEG, kUnknown -> CvType.CV_8UC1;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sink for accepting OpenCV images. WaitForFrame() must be called on the created sink to
|
||||
* get each new image.
|
||||
*
|
||||
* @param name Source name (arbitrary unique identifier)
|
||||
*/
|
||||
public RawCVMatSink(String name) {
|
||||
super(CameraServerJNI.createRawSink(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the next frame and get the image. Times out (returning 0) after 0.225 seconds. The
|
||||
* provided image will have three 3-bit channels stored in BGR order.
|
||||
*
|
||||
* @return Frame time, or 0 on error (call GetError() to obtain the error message)
|
||||
*/
|
||||
public long grabFrame(Mat image) {
|
||||
return grabFrame(image, 0.225);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the next frame and get the image. Times out (returning 0) after timeout seconds. The
|
||||
* provided image will have three 3-bit channels stored in BGR order.
|
||||
*
|
||||
* @return Frame time, or 0 on error (call GetError() to obtain the error message); the frame time
|
||||
* is in 1 us increments.
|
||||
*/
|
||||
public long grabFrame(Mat image, double timeout) {
|
||||
frame.setWidth(0);
|
||||
frame.setHeight(0);
|
||||
frame.setPixelFormat(bgrValue);
|
||||
long rv = CameraServerJNI.grabSinkFrameTimeout(m_handle, frame, timeout);
|
||||
if (rv <= 0) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (frame.getDataByteBuffer() != origByteBuffer
|
||||
|| width != frame.getWidth()
|
||||
|| height != frame.getHeight()
|
||||
|| pixelFormat != frame.getPixelFormat()) {
|
||||
origByteBuffer = frame.getDataByteBuffer();
|
||||
height = frame.getHeight();
|
||||
width = frame.getWidth();
|
||||
pixelFormat = frame.getPixelFormat();
|
||||
tmpMat =
|
||||
new Mat(
|
||||
frame.getHeight(),
|
||||
frame.getWidth(),
|
||||
getCVFormat(VideoMode.getPixelFormatFromInt(pixelFormat)),
|
||||
origByteBuffer);
|
||||
}
|
||||
tmpMat.copyTo(image);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.cscore;
|
||||
|
||||
import edu.wpi.cscore.VideoMode.PixelFormat;
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
public class RawCVMatSource extends ImageSource {
|
||||
/**
|
||||
* Create an OpenCV source.
|
||||
*
|
||||
* @param name Source name (arbitrary unique identifier)
|
||||
* @param mode Video mode being generated
|
||||
*/
|
||||
public RawCVMatSource(String name, VideoMode mode) {
|
||||
super(
|
||||
CameraServerJNI.createRawSource(
|
||||
name, mode.pixelFormat.getValue(), mode.width, mode.height, mode.fps));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an OpenCV source.
|
||||
*
|
||||
* @param name Source name (arbitrary unique identifier)
|
||||
* @param pixelFormat Pixel format
|
||||
* @param width width
|
||||
* @param height height
|
||||
* @param fps fps
|
||||
*/
|
||||
public RawCVMatSource(
|
||||
String name, VideoMode.PixelFormat pixelFormat, int width, int height, int fps) {
|
||||
super(CameraServerJNI.createRawSource(name, pixelFormat.getValue(), width, height, fps));
|
||||
}
|
||||
|
||||
/**
|
||||
* Put an OpenCV image and notify sinks.
|
||||
*
|
||||
* <p>Only 8-bit single-channel or 3-channel (with BGR channel order) images are supported. If the
|
||||
* format, depth or channel order is different, use Mat.convertTo() and/or cvtColor() to convert
|
||||
* it first.
|
||||
*
|
||||
* @param image OpenCV image
|
||||
*/
|
||||
public void putFrame(Mat image) {
|
||||
int channels = image.channels();
|
||||
if (channels != 1 && channels != 3) {
|
||||
throw new VideoException("Unsupported Image Type");
|
||||
}
|
||||
int imgType = channels == 1 ? PixelFormat.kGray.getValue() : PixelFormat.kBGR.getValue();
|
||||
CameraServerJNI.putRawSourceFrame(
|
||||
m_handle,
|
||||
image.dataAddr(),
|
||||
image.width(),
|
||||
image.height(),
|
||||
imgType,
|
||||
(int) image.total() * channels);
|
||||
}
|
||||
}
|
||||
@@ -1025,10 +1025,10 @@ public class CameraServerJNI {
|
||||
/**
|
||||
* Runs main run loop with timeout.
|
||||
*
|
||||
* @param timeoutSeconds Timeout in seconds.
|
||||
* @param timeout Timeout in seconds.
|
||||
* @return 3 on timeout, 2 on signal, 1 on other.
|
||||
*/
|
||||
public static native int runMainRunLoopTimeout(double timeoutSeconds);
|
||||
public static native int runMainRunLoopTimeout(double timeout);
|
||||
|
||||
/** Stops main run loop. */
|
||||
public static native void stopMainRunLoop();
|
||||
|
||||
@@ -2085,9 +2085,9 @@ Java_edu_wpi_first_cscore_CameraServerJNI_runMainRunLoop
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_cscore_CameraServerJNI_runMainRunLoopTimeout
|
||||
(JNIEnv*, jclass, jdouble timeoutSeconds)
|
||||
(JNIEnv*, jclass, jdouble timeout)
|
||||
{
|
||||
return cs::RunMainRunLoopTimeout(timeoutSeconds);
|
||||
return cs::RunMainRunLoopTimeout(timeout);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -5,7 +5,16 @@
|
||||
#pragma once
|
||||
|
||||
namespace cs {
|
||||
|
||||
void RunMainRunLoop();
|
||||
int RunMainRunLoopTimeout(double timeoutSeconds);
|
||||
|
||||
/**
|
||||
* Runs main run loop with timeout.
|
||||
*
|
||||
* @param timeout Timeout in seconds.
|
||||
*/
|
||||
int RunMainRunLoopTimeout(double timeout);
|
||||
|
||||
void StopMainRunLoop();
|
||||
|
||||
} // namespace cs
|
||||
|
||||
@@ -12,16 +12,16 @@ static wpi::Event& GetInstance() {
|
||||
}
|
||||
|
||||
namespace cs {
|
||||
|
||||
void RunMainRunLoop() {
|
||||
wpi::Event& event = GetInstance();
|
||||
wpi::WaitForObject(event.GetHandle());
|
||||
}
|
||||
|
||||
int RunMainRunLoopTimeout(double timeoutSeconds) {
|
||||
int RunMainRunLoopTimeout(double timeout) {
|
||||
wpi::Event& event = GetInstance();
|
||||
bool timedOut = false;
|
||||
bool signaled =
|
||||
wpi::WaitForObject(event.GetHandle(), timeoutSeconds, &timedOut);
|
||||
bool signaled = wpi::WaitForObject(event.GetHandle(), timeout, &timedOut);
|
||||
if (timedOut) {
|
||||
return 3;
|
||||
}
|
||||
@@ -35,4 +35,5 @@ void StopMainRunLoop() {
|
||||
wpi::Event& event = GetInstance();
|
||||
event.Set();
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
namespace cs {
|
||||
|
||||
void RunMainRunLoop() {
|
||||
if (CFRunLoopGetMain() != CFRunLoopGetCurrent()) {
|
||||
NSLog(@"This method can only be called from the main thread");
|
||||
@@ -16,15 +17,16 @@ void RunMainRunLoop() {
|
||||
CFRunLoopRun();
|
||||
}
|
||||
|
||||
int RunMainRunLoopTimeout(double timeoutSeconds) {
|
||||
int RunMainRunLoopTimeout(double timeout) {
|
||||
if (CFRunLoopGetMain() != CFRunLoopGetCurrent()) {
|
||||
NSLog(@"This method can only be called from the main thread");
|
||||
return -1;
|
||||
}
|
||||
return CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeoutSeconds, false);
|
||||
return CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, false);
|
||||
}
|
||||
|
||||
void StopMainRunLoop() {
|
||||
CFRunLoopStop(CFRunLoopGetMain());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
#include <wpi/StringMap.h>
|
||||
|
||||
#include "SourceImpl.h"
|
||||
|
||||
namespace cs {
|
||||
@@ -88,9 +90,34 @@ class UsbCameraImpl : public SourceImpl {
|
||||
|
||||
UsbCameraImplObjc* cppGetObjc() { return m_objc; }
|
||||
|
||||
int CreatePropertyPublic(std::string_view name, std::function<std::unique_ptr<PropertyImpl>()> newFunc) {
|
||||
return CreateProperty(name, newFunc);
|
||||
}
|
||||
|
||||
PropertyImpl* GetPropertyPublic(int property) {
|
||||
return GetProperty(property);
|
||||
}
|
||||
|
||||
void NotifyPropertyCreatedPublic(int propIndex, PropertyImpl& prop) {
|
||||
NotifyPropertyCreated(propIndex, prop);
|
||||
}
|
||||
|
||||
void UpdatePropertyValuePublic(int property, bool setString, int value, std::string_view valueStr) {
|
||||
UpdatePropertyValue(property, setString, value, valueStr);
|
||||
}
|
||||
|
||||
wpi::mutex& GetMutex() { return m_mutex; }
|
||||
|
||||
// Property cache accessors
|
||||
wpi::StringMap<uint32_t>& GetPropertyCache() { return m_propertyCache; }
|
||||
wpi::StringMap<uint32_t>& GetPropertyAutoCache() { return m_propertyAutoCache; }
|
||||
|
||||
private:
|
||||
UsbCameraImplObjc* m_objc;
|
||||
std::vector<CameraModeStore> m_platformModes;
|
||||
VideoMode m_mode;
|
||||
|
||||
// Property caches
|
||||
wpi::StringMap<uint32_t> m_propertyCache;
|
||||
wpi::StringMap<uint32_t> m_propertyAutoCache;
|
||||
};
|
||||
} // namespace cs
|
||||
|
||||
@@ -5,11 +5,39 @@
|
||||
#pragma once
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import "UsbCameraDelegate.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
#import "UsbCameraDelegate.h"
|
||||
#import "UvcControlImpl.h"
|
||||
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
// Quirk: exposure auto is 3 for on, 1 for off
|
||||
#define kPropertyAutoExposureOn 3
|
||||
#define kPropertyAutoExposureOff 1
|
||||
|
||||
// Property names
|
||||
#define kPropertyBrightness "brightness"
|
||||
#define kPropertyWhiteBalance "white_balance_temperature"
|
||||
#define kPropertyExposure "raw_exposure_time_absolute"
|
||||
#define kPropertyContrast "raw_contrast"
|
||||
#define kPropertySaturation "raw_saturation"
|
||||
#define kPropertySharpness "raw_sharpness"
|
||||
#define kPropertyGain "gain"
|
||||
#define kPropertyGamma "gamma"
|
||||
#define kPropertyHue "raw_hue"
|
||||
#define kPropertyFocus "focus_absolute"
|
||||
#define kPropertyZoom "zoom"
|
||||
#define kPropertyBackLightCompensation "backlight_compensation"
|
||||
#define kPropertyPowerLineFrequency "power_line_frequency"
|
||||
|
||||
// Auto property names
|
||||
#define kPropertyAutoExposure "exposure_auto"
|
||||
#define kPropertyAutoWhiteBalance "white_balance_automatic"
|
||||
#define kPropertyAutoFocus "focus_auto"
|
||||
|
||||
namespace cs {
|
||||
class UsbCameraImpl;
|
||||
}
|
||||
@@ -30,6 +58,7 @@ class UsbCameraImpl;
|
||||
@property(nonatomic) AVCaptureDevice* videoDevice;
|
||||
@property(nonatomic) AVCaptureDeviceInput* videoInput;
|
||||
@property(nonatomic) UsbCameraDelegate* callback;
|
||||
@property(nonatomic) UvcControlImpl* uvcControl;
|
||||
@property(nonatomic) AVCaptureVideoDataOutput* videoOutput;
|
||||
@property(nonatomic) AVCaptureSession* session;
|
||||
|
||||
@@ -68,4 +97,8 @@ class UsbCameraImpl;
|
||||
- (void)getCameraName:(std::string*)name;
|
||||
- (void)setNewCameraPath:(std::string_view*)path;
|
||||
|
||||
- (void)deviceCacheProperties;
|
||||
- (void)cacheProperty:(uint32_t)propID withName:(NSString *)name;
|
||||
- (void)cacheAutoProperty:(uint32_t)propID withName:(NSString *)baseName;
|
||||
|
||||
@end
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
// 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.
|
||||
|
||||
#import "UsbCameraImplObjc.h"
|
||||
#include "UsbCameraImpl.h"
|
||||
#include <wpi/SmallString.h>
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
#import "UsbCameraImplObjc.h"
|
||||
|
||||
#include "Notifier.h"
|
||||
#include "Log.h"
|
||||
#include "UsbCameraImpl.h"
|
||||
|
||||
template <typename S, typename... Args>
|
||||
inline void NamedLog(UsbCameraImplObjc* objc, unsigned int level,
|
||||
@@ -104,44 +106,300 @@ using namespace cs;
|
||||
name:AVCaptureDeviceWasDisconnectedNotification
|
||||
object:nil];
|
||||
[self deviceConnect];
|
||||
[self deviceCacheProperties];
|
||||
});
|
||||
}
|
||||
|
||||
- (BOOL)getEnabledWithProperty:(int)property withValue:(int)value {
|
||||
auto sharedThis = self.cppImpl.lock();
|
||||
if (!sharedThis) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// There is room for quirk handling improvement here, but I will leave it
|
||||
// for now.
|
||||
if (property == sharedThis->GetPropertyIndex(kPropertyAutoExposure)) {
|
||||
return value == kPropertyAutoExposureOn;
|
||||
}
|
||||
|
||||
return value != 0;
|
||||
}
|
||||
|
||||
|
||||
- (int)clampToPercent:(int)value {
|
||||
if (value < 0) {
|
||||
return 0;
|
||||
}
|
||||
if (value > 100) {
|
||||
return 100;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
- (int)percentageToRaw:(int)propID percentage:(int)percentage min:(int)min max:(int)max {
|
||||
if (min == max) {
|
||||
return min;
|
||||
}
|
||||
|
||||
return min + (max - min) * percentage / 100;
|
||||
}
|
||||
|
||||
- (BOOL)isPercentageProperty:(int)propID {
|
||||
return propID == CAPPROPID_BRIGHTNESS ||
|
||||
propID == CAPPROPID_CONTRAST ||
|
||||
propID == CAPPROPID_SATURATION ||
|
||||
propID == CAPPROPID_HUE ||
|
||||
propID == CAPPROPID_SHARPNESS ||
|
||||
propID == CAPPROPID_GAIN;
|
||||
}
|
||||
|
||||
// Property functions
|
||||
- (void)setProperty:(int)property
|
||||
withValue:(int)value
|
||||
status:(CS_Status*)status {
|
||||
auto sharedThis = self.cppImpl.lock();
|
||||
if (!sharedThis) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure properties are cached
|
||||
if (!self.propertiesCached) {
|
||||
[self deviceCacheProperties];
|
||||
}
|
||||
|
||||
// Get the property name from the property index
|
||||
wpi::SmallString<128> nameBuf;
|
||||
std::string_view propName = sharedThis->GetPropertyName(property, nameBuf, status);
|
||||
if (*status != 0) {
|
||||
OBJCERROR("Failed to get property name for index {}", property);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string nameStr(propName);
|
||||
|
||||
// Check if it's an auto property
|
||||
auto& propertyAutoCache = sharedThis->GetPropertyAutoCache();
|
||||
auto autoIt = propertyAutoCache.find(nameStr);
|
||||
if (autoIt != propertyAutoCache.end()) {
|
||||
uint32_t propID = autoIt->second;
|
||||
bool enabled = [self getEnabledWithProperty:property withValue:value];
|
||||
dispatch_async_and_wait(self.sessionQueue, ^{
|
||||
if (self.uvcControl == nil) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return;
|
||||
}
|
||||
|
||||
if (![self.uvcControl setAutoProperty:propID enabled:enabled status:status]) {
|
||||
OBJCERROR("Failed to set auto property {} to {}",
|
||||
nameStr, enabled);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update property value
|
||||
sharedThis->UpdatePropertyValuePublic(property, false, value, {});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle regular property
|
||||
auto& propertyCache = sharedThis->GetPropertyCache();
|
||||
auto it = propertyCache.find(nameStr);
|
||||
if (it == propertyCache.end()) {
|
||||
OBJCERROR("Property not found in cache: {}", nameStr);
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t propID = it->second;
|
||||
|
||||
dispatch_async_and_wait(self.sessionQueue, ^{
|
||||
if (self.uvcControl == nil) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the property implementation to access its limits
|
||||
const PropertyImpl* prop = sharedThis->GetPropertyPublic(property);
|
||||
if (!prop) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
int32_t realValue = value;
|
||||
if ([self isPercentageProperty:propID]) {
|
||||
// Clamp to 0-100
|
||||
realValue = [self clampToPercent:realValue];
|
||||
|
||||
// Scale to min/max
|
||||
realValue = [self percentageToRaw:propID percentage:realValue min:prop->minimum max:prop->maximum];
|
||||
}
|
||||
|
||||
if (![self.uvcControl setProperty:propID withValue:realValue status:status]) {
|
||||
OBJCERROR("Failed to set property {} to value {}", nameStr, realValue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update property value in the container
|
||||
sharedThis->UpdatePropertyValuePublic(property, false, value, {});
|
||||
});
|
||||
}
|
||||
|
||||
- (void)setStringProperty:(int)property
|
||||
withValue:(std::string_view*)value
|
||||
status:(CS_Status*)status {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return;
|
||||
}
|
||||
|
||||
// Standard common camera properties
|
||||
- (void)setBrightness:(int)brightness status:(CS_Status*)status {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
auto sharedThis = self.cppImpl.lock();
|
||||
if (!sharedThis) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure properties are cached
|
||||
if (!self.propertiesCached) {
|
||||
[self deviceCacheProperties];
|
||||
}
|
||||
|
||||
// Get the property index and set it
|
||||
int prop = sharedThis->GetPropertyIndex(kPropertyBrightness);
|
||||
sharedThis->SetProperty(prop, brightness, status);
|
||||
}
|
||||
|
||||
- (int)getBrightness:(CS_Status*)status {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return 0;
|
||||
auto sharedThis = self.cppImpl.lock();
|
||||
if (!sharedThis) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Make sure properties are cached
|
||||
if (!self.propertiesCached) {
|
||||
[self deviceCacheProperties];
|
||||
}
|
||||
|
||||
// Get the property index and its value
|
||||
int prop = sharedThis->GetPropertyIndex(kPropertyBrightness);
|
||||
return sharedThis->GetProperty(prop, status);
|
||||
}
|
||||
|
||||
- (void)setWhiteBalanceAuto:(CS_Status*)status {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
auto sharedThis = self.cppImpl.lock();
|
||||
if (!sharedThis) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure properties are cached
|
||||
if (!self.propertiesCached) {
|
||||
[self deviceCacheProperties];
|
||||
}
|
||||
|
||||
int prop = sharedThis->GetPropertyIndex(kPropertyAutoWhiteBalance);
|
||||
sharedThis->SetProperty(prop, 1, status);
|
||||
}
|
||||
|
||||
- (void)setWhiteBalanceHoldCurrent:(CS_Status*)status {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
auto sharedThis = self.cppImpl.lock();
|
||||
if (!sharedThis) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure properties are cached
|
||||
if (!self.propertiesCached) {
|
||||
[self deviceCacheProperties];
|
||||
}
|
||||
|
||||
int prop = sharedThis->GetPropertyIndex(kPropertyAutoWhiteBalance);
|
||||
sharedThis->SetProperty(prop, 0, status);
|
||||
}
|
||||
|
||||
- (void)setWhiteBalanceManual:(int)value status:(CS_Status*)status {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
auto sharedThis = self.cppImpl.lock();
|
||||
if (!sharedThis) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure properties are cached
|
||||
if (!self.propertiesCached) {
|
||||
[self deviceCacheProperties];
|
||||
}
|
||||
|
||||
// First disable auto white balance
|
||||
int autoProp = sharedThis->GetPropertyIndex(kPropertyAutoWhiteBalance);
|
||||
sharedThis->SetProperty(autoProp, 0, status);
|
||||
if (*status != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Then set the white balance value
|
||||
int prop = sharedThis->GetPropertyIndex(kPropertyWhiteBalance);
|
||||
sharedThis->SetProperty(prop, value, status);
|
||||
}
|
||||
|
||||
- (void)setExposureAuto:(CS_Status*)status {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
auto sharedThis = self.cppImpl.lock();
|
||||
if (!sharedThis) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure properties are cached
|
||||
if (!self.propertiesCached) {
|
||||
[self deviceCacheProperties];
|
||||
}
|
||||
|
||||
// Set the auto exposure property to enabled (1)
|
||||
int prop = sharedThis->GetPropertyIndex(kPropertyAutoExposure);
|
||||
sharedThis->SetProperty(prop, kPropertyAutoExposureOn, status);
|
||||
}
|
||||
|
||||
- (void)setExposureHoldCurrent:(CS_Status*)status {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
auto sharedThis = self.cppImpl.lock();
|
||||
if (!sharedThis) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure properties are cached
|
||||
if (!self.propertiesCached) {
|
||||
[self deviceCacheProperties];
|
||||
}
|
||||
|
||||
// Set the auto exposure property to disabled (0)
|
||||
int prop = sharedThis->GetPropertyIndex(kPropertyAutoExposure);
|
||||
sharedThis->SetProperty(prop, kPropertyAutoExposureOff, status);
|
||||
}
|
||||
|
||||
- (void)setExposureManual:(int)value status:(CS_Status*)status {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
auto sharedThis = self.cppImpl.lock();
|
||||
if (!sharedThis) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure properties are cached
|
||||
if (!self.propertiesCached) {
|
||||
[self deviceCacheProperties];
|
||||
}
|
||||
|
||||
// First disable auto exposure
|
||||
int autoProp = sharedThis->GetPropertyIndex(kPropertyAutoExposure);
|
||||
sharedThis->SetProperty(autoProp, kPropertyAutoExposureOff, status);
|
||||
if (*status != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Then set the exposure value
|
||||
int prop = sharedThis->GetPropertyIndex(kPropertyExposure);
|
||||
sharedThis->SetProperty(prop, value, status);
|
||||
}
|
||||
|
||||
- (bool)setVideoMode:(const cs::VideoMode&)mode status:(CS_Status*)status {
|
||||
@@ -295,10 +553,144 @@ using namespace cs;
|
||||
|
||||
// All above are called from C++, must always dispatch to loop
|
||||
|
||||
// Property caching methods
|
||||
- (void)deviceCacheProperties {
|
||||
if (self.session == nil) {
|
||||
return;
|
||||
}
|
||||
if (self.uvcControl == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto sharedThis = self.cppImpl.lock();
|
||||
if (!sharedThis) {
|
||||
OBJCERROR("Cannot cache properties: UsbCameraImpl not available");
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache basic properties
|
||||
[self cacheProperty:CAPPROPID_BRIGHTNESS withName:@kPropertyBrightness];
|
||||
[self cacheProperty:CAPPROPID_WHITEBALANCE withName:@kPropertyWhiteBalance];
|
||||
[self cacheProperty:CAPPROPID_EXPOSURE withName:@kPropertyExposure];
|
||||
[self cacheProperty:CAPPROPID_CONTRAST withName:@kPropertyContrast];
|
||||
[self cacheProperty:CAPPROPID_SATURATION withName:@kPropertySaturation];
|
||||
[self cacheProperty:CAPPROPID_SHARPNESS withName:@kPropertySharpness];
|
||||
[self cacheProperty:CAPPROPID_GAIN withName:@kPropertyGain];
|
||||
[self cacheProperty:CAPPROPID_GAMMA withName:@kPropertyGamma];
|
||||
[self cacheProperty:CAPPROPID_HUE withName:@kPropertyHue];
|
||||
[self cacheProperty:CAPPROPID_FOCUS withName:@kPropertyFocus];
|
||||
[self cacheProperty:CAPPROPID_ZOOM withName:@kPropertyZoom];
|
||||
[self cacheProperty:CAPPROPID_BACKLIGHTCOMP withName:@kPropertyBackLightCompensation];
|
||||
[self cacheProperty:CAPPROPID_POWERLINEFREQ withName:@kPropertyPowerLineFrequency];
|
||||
|
||||
// Cache auto properties
|
||||
[self cacheAutoProperty:CAPPROPID_EXPOSURE withName:@kPropertyAutoExposure];
|
||||
[self cacheAutoProperty:CAPPROPID_WHITEBALANCE withName:@kPropertyAutoWhiteBalance];
|
||||
[self cacheAutoProperty:CAPPROPID_FOCUS withName:@kPropertyAutoFocus];
|
||||
|
||||
self.propertiesCached = true;
|
||||
}
|
||||
|
||||
- (void)cacheProperty:(uint32_t)propID withName:(NSString *)name {
|
||||
auto sharedThis = self.cppImpl.lock();
|
||||
if (!sharedThis) {
|
||||
OBJCERROR("Cannot cache property: UsbCameraImpl not available");
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.uvcControl == nil) {
|
||||
OBJCWARNING("Cannot cache property {}: UVC control not initialized", [name UTF8String]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get property limits
|
||||
int32_t minimum = 0, maximum = 0, defaultValue = 0;
|
||||
int32_t value = defaultValue;
|
||||
CS_Status status;
|
||||
|
||||
std::string nameStr = std::string([name UTF8String]);
|
||||
|
||||
// Get the property limits
|
||||
if (![self.uvcControl getPropertyLimits:propID
|
||||
min:&minimum
|
||||
max:&maximum
|
||||
defValue:&defaultValue
|
||||
status:&status]) {
|
||||
OBJCWARNING("Failed to get property limits for {}", nameStr);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get current value
|
||||
if (![self.uvcControl getProperty:propID withValue:&value status:&status]) {
|
||||
value = defaultValue;
|
||||
OBJCWARNING("Failed to get current value for {}: {}",
|
||||
nameStr, value);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create property
|
||||
auto& propertyCache = sharedThis->GetPropertyCache();
|
||||
propertyCache[nameStr] = propID;
|
||||
|
||||
// Create the property implementation
|
||||
std::unique_ptr<PropertyImpl> prop;
|
||||
prop = std::make_unique<PropertyImpl>(nameStr);
|
||||
prop->propKind = CS_PROP_INTEGER;
|
||||
prop->value = value;
|
||||
prop->minimum = minimum;
|
||||
prop->maximum = maximum;
|
||||
prop->step = 1; // Most camera properties use a step of 1
|
||||
prop->defaultValue = defaultValue;
|
||||
|
||||
// Add the property to the container
|
||||
std::scoped_lock lock(sharedThis->GetMutex());
|
||||
int ndx = sharedThis->CreatePropertyPublic(nameStr, [&] { return std::move(prop); });
|
||||
|
||||
// Notify that property has been created
|
||||
sharedThis->NotifyPropertyCreatedPublic(ndx, *sharedThis->GetPropertyPublic(ndx));
|
||||
}
|
||||
|
||||
- (void)cacheAutoProperty:(uint32_t)propID withName:(NSString *)baseName {
|
||||
auto sharedThis = self.cppImpl.lock();
|
||||
if (!sharedThis) {
|
||||
OBJCERROR("Cannot cache auto property: UsbCameraImpl not available");
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.uvcControl == nil) {
|
||||
OBJCWARNING("Cannot cache auto property {}: UVC control not initialized", [baseName UTF8String]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build auto mode property name
|
||||
std::string nameStr = std::string([baseName UTF8String]);
|
||||
|
||||
// Get current auto mode status
|
||||
bool enabled = false;
|
||||
CS_Status status = 0;
|
||||
|
||||
if(![self.uvcControl getAutoProperty:propID enabled:&enabled status:&status]) {
|
||||
OBJCWARNING("Failed to get auto property {}", nameStr);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create property
|
||||
std::unique_ptr<PropertyImpl> prop;
|
||||
prop = std::make_unique<PropertyImpl>(nameStr);
|
||||
prop->propKind = CS_PROP_BOOLEAN;
|
||||
prop->value = enabled ? 1 : 0;
|
||||
prop->minimum = 0;
|
||||
prop->maximum = 1;
|
||||
prop->step = 1;
|
||||
prop->defaultValue = 0; // Default is manual mode
|
||||
|
||||
// Add property to container
|
||||
std::scoped_lock lock(sharedThis->GetMutex());
|
||||
int ndx = sharedThis->CreatePropertyPublic(nameStr, [&] { return std::move(prop); });
|
||||
|
||||
// Notify property created
|
||||
sharedThis->NotifyPropertyCreatedPublic(ndx, *sharedThis->GetPropertyPublic(ndx));
|
||||
|
||||
// Map property name to ID
|
||||
auto& propertyAutoCache = sharedThis->GetPropertyAutoCache();
|
||||
propertyAutoCache[nameStr] = propID;
|
||||
}
|
||||
|
||||
static cs::VideoMode::PixelFormat FourCCToPixelFormat(FourCharCode fourcc) {
|
||||
@@ -380,22 +772,27 @@ static cs::VideoMode::PixelFormat FourCCToPixelFormat(FourCharCode fourcc) {
|
||||
toCheck->height, toCheck->fps);
|
||||
std::vector<CameraModeStore>& platformModes =
|
||||
sharedThis->objcGetPlatformVideoModes();
|
||||
// Find the matching mode
|
||||
auto match = std::find_if(platformModes.begin(), platformModes.end(),
|
||||
[&](CameraModeStore& input) {
|
||||
return input.mode.CompareWithoutFps(*toCheck);
|
||||
});
|
||||
|
||||
// Find all matching modes
|
||||
std::vector<CameraModeStore*> matchingModes;
|
||||
for (auto& mode : platformModes) {
|
||||
if (mode.mode.CompareWithoutFps(*toCheck)) {
|
||||
matchingModes.push_back(&mode);
|
||||
}
|
||||
}
|
||||
|
||||
if (match == platformModes.end()) {
|
||||
if (matchingModes.empty()) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Check FPS
|
||||
for (CameraFPSRange& range : match->fpsRanges) {
|
||||
OBJCDEBUG3("Checking Range {} {}", range.min, range.max);
|
||||
if (range.IsWithinRange(toCheck->fps)) {
|
||||
*fps = toCheck->fps;
|
||||
return match->format;
|
||||
for (auto mode : matchingModes) {
|
||||
for (CameraFPSRange& range : mode->fpsRanges) {
|
||||
OBJCDEBUG3("Checking Range {} {}", range.min, range.max);
|
||||
if (range.IsWithinRange(toCheck->fps)) {
|
||||
*fps = toCheck->fps;
|
||||
return mode->format;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,6 +851,53 @@ static cs::VideoMode::PixelFormat FourCCToPixelFormat(FourCharCode fourcc) {
|
||||
self.deviceValid = true;
|
||||
}
|
||||
|
||||
- (CMTime)findNearestFrameDuration:(int)fps {
|
||||
if (self.currentFormat == nil) {
|
||||
return CMTimeMake(1, fps);
|
||||
}
|
||||
|
||||
NSArray<AVFrameRateRange*>* frameRates = self.currentFormat.videoSupportedFrameRateRanges;
|
||||
if (frameRates.count == 0) {
|
||||
return CMTimeMake(1, fps);
|
||||
}
|
||||
|
||||
// Find the nearest frame duration
|
||||
CMTime nearestDuration = CMTimeMake(1, fps);
|
||||
double minDiff = DBL_MAX;
|
||||
|
||||
for (AVFrameRateRange* range in frameRates) {
|
||||
CMTime minDuration = range.minFrameDuration;
|
||||
CMTime maxDuration = range.maxFrameDuration;
|
||||
|
||||
// Calculate frame duration for current fps
|
||||
CMTime targetDuration = CMTimeMake(1, fps);
|
||||
|
||||
// Check if within range
|
||||
if (CMTimeCompare(targetDuration, minDuration) >= 0 &&
|
||||
CMTimeCompare(targetDuration, maxDuration) <= 0) {
|
||||
return targetDuration;
|
||||
}
|
||||
|
||||
// Calculate difference with min value
|
||||
double minDiffValue = fabs(CMTimeGetSeconds(targetDuration) - CMTimeGetSeconds(minDuration));
|
||||
if (minDiffValue < minDiff) {
|
||||
minDiff = minDiffValue;
|
||||
nearestDuration = minDuration;
|
||||
}
|
||||
|
||||
// Calculate difference with max value
|
||||
double maxDiffValue = fabs(CMTimeGetSeconds(targetDuration) - CMTimeGetSeconds(maxDuration));
|
||||
if (maxDiffValue < minDiff) {
|
||||
minDiff = maxDiffValue;
|
||||
nearestDuration = maxDuration;
|
||||
}
|
||||
}
|
||||
|
||||
OBJCDEBUG("Nearest fps: {}", nearestDuration.timescale / static_cast<double>(nearestDuration.value));
|
||||
|
||||
return nearestDuration;
|
||||
}
|
||||
|
||||
- (bool)deviceStreamOn {
|
||||
if (self.streaming) {
|
||||
return false;
|
||||
@@ -461,24 +905,32 @@ static cs::VideoMode::PixelFormat FourCCToPixelFormat(FourCharCode fourcc) {
|
||||
if (!self.deviceValid) {
|
||||
return false;
|
||||
}
|
||||
self.streaming = true;
|
||||
|
||||
if (![self.videoDevice lockForConfiguration:nil]) {
|
||||
OBJCERROR("Failed to lock for configuration");
|
||||
return false;
|
||||
}
|
||||
|
||||
[self.session beginConfiguration];
|
||||
|
||||
if (self.currentFormat != nil) {
|
||||
self.videoDevice.activeFormat = self.currentFormat;
|
||||
}
|
||||
|
||||
if (self.currentFPS != 0) {
|
||||
CMTime frameDuration = [self findNearestFrameDuration:self.currentFPS];
|
||||
self.videoDevice.activeVideoMinFrameDuration = frameDuration;
|
||||
self.videoDevice.activeVideoMaxFrameDuration = frameDuration;
|
||||
}
|
||||
|
||||
[self.session commitConfiguration];
|
||||
|
||||
self.streaming = true;
|
||||
// Start the capture session before device unlock to ensure
|
||||
// the session preset settings are preserved
|
||||
[self.session startRunning];
|
||||
|
||||
if ([self.videoDevice lockForConfiguration:nil]) {
|
||||
if (self.currentFormat != nil) {
|
||||
self.videoDevice.activeFormat = self.currentFormat;
|
||||
}
|
||||
if (self.currentFPS != 0) {
|
||||
self.videoDevice.activeVideoMinFrameDuration =
|
||||
CMTimeMake(1, self.currentFPS);
|
||||
self.videoDevice.activeVideoMaxFrameDuration =
|
||||
CMTimeMake(1, self.currentFPS);
|
||||
}
|
||||
[self.videoDevice unlockForConfiguration];
|
||||
} else {
|
||||
OBJCERROR("Failed to lock for configuration");
|
||||
}
|
||||
[self.videoDevice unlockForConfiguration];
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -569,6 +1021,16 @@ static cs::VideoMode::PixelFormat FourCCToPixelFormat(FourCharCode fourcc) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
CS_Status status;
|
||||
self.uvcControl = [UvcControlImpl createFromAVCaptureDevice:self.videoDevice status:&status];
|
||||
if (self.uvcControl == nil) {
|
||||
OBJCWARNING("Failed to initialize UVC control for camera: {}", status);
|
||||
} else {
|
||||
OBJCINFO("UVC control initialized successfully");
|
||||
}
|
||||
|
||||
self.uvcControl.cppImpl = self.cppImpl;
|
||||
|
||||
self.callback = [[UsbCameraDelegate alloc] init];
|
||||
if (self.callback == nil) {
|
||||
OBJCWARNING("Creating Camera Callback failed");
|
||||
|
||||
129
cscore/src/main/native/objcpp/UvcControlImpl.h
Normal file
129
cscore/src/main/native/objcpp/UvcControlImpl.h
Normal file
@@ -0,0 +1,129 @@
|
||||
// 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
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <IOKit/IOCFPlugIn.h>
|
||||
#import <IOKit/usb/IOUSBLib.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
#import "UsbCameraDelegate.h"
|
||||
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
// Status code definition
|
||||
#define CS_UVC_STATUS_ERROR -3001
|
||||
#define CS_UVC_STATUS_DEVICE_DISCONNECTED -3002
|
||||
|
||||
// UVC control selector definitions
|
||||
#define UVC_INPUT_TERMINAL_ID 0x01
|
||||
|
||||
// Camera terminal control selectors
|
||||
#define CT_AE_MODE_CONTROL 0x02
|
||||
#define CT_AE_PRIORITY_CONTROL 0x03
|
||||
#define CT_EXPOSURE_TIME_ABSOLUTE_CONTROL 0x04
|
||||
#define CT_EXPOSURE_TIME_RELATIVE_CONTROL 0x05
|
||||
#define CT_FOCUS_ABSOLUTE_CONTROL 0x06
|
||||
#define CT_FOCUS_RELATIVE_CONTROL 0x07
|
||||
#define CT_FOCUS_AUTO_CONTROL 0x08
|
||||
#define CT_ZOOM_ABSOLUTE_CONTROL 0x0B
|
||||
#define CT_ZOOM_RELATIVE_CONTROL 0x0C
|
||||
|
||||
// Processing unit control selectors
|
||||
#define PU_BACKLIGHT_COMPENSATION_CONTROL 0x01
|
||||
#define PU_BRIGHTNESS_CONTROL 0x02
|
||||
#define PU_CONTRAST_CONTROL 0x03
|
||||
#define PU_GAIN_CONTROL 0x04
|
||||
#define PU_POWER_LINE_FREQUENCY_CONTROL 0x05
|
||||
#define PU_HUE_CONTROL 0x06
|
||||
#define PU_SATURATION_CONTROL 0x07
|
||||
#define PU_SHARPNESS_CONTROL 0x08
|
||||
#define PU_GAMMA_CONTROL 0x09
|
||||
#define PU_WHITE_BALANCE_TEMPERATURE_CONTROL 0x0A
|
||||
#define PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL 0x0B
|
||||
#define PU_WHITE_BALANCE_COMPONENT_CONTROL 0x0C
|
||||
#define PU_WHITE_BALANCE_COMPONENT_AUTO_CONTROL 0x0D
|
||||
#define PU_HUE_AUTO_CONTROL 0x10
|
||||
#define PU_CONTRAST_AUTO_CONTROL 0x13
|
||||
|
||||
// Camera request error code
|
||||
#define VC_REQUEST_ERROR_CODE_CONTROL 0x02
|
||||
|
||||
// UVC control interface definitions
|
||||
#define UVC_CONTROL_INTERFACE_CLASS 14
|
||||
#define UVC_CONTROL_INTERFACE_SUBCLASS 1
|
||||
|
||||
// UVC control request types
|
||||
#define UVC_SET_CUR 0x01
|
||||
#define UVC_GET_CUR 0x81
|
||||
#define UVC_GET_MIN 0x82
|
||||
#define UVC_GET_MAX 0x83
|
||||
#define UVC_GET_RES 0x84
|
||||
#define UVC_GET_INFO 0x86
|
||||
#define UVC_GET_DEF 0x87
|
||||
|
||||
// Camera property ID definitions
|
||||
#define CAPPROPID_EXPOSURE 1
|
||||
#define CAPPROPID_FOCUS 2
|
||||
#define CAPPROPID_ZOOM 3
|
||||
#define CAPPROPID_WHITEBALANCE 4
|
||||
#define CAPPROPID_GAIN 5
|
||||
#define CAPPROPID_BRIGHTNESS 6
|
||||
#define CAPPROPID_CONTRAST 7
|
||||
#define CAPPROPID_SATURATION 8
|
||||
#define CAPPROPID_GAMMA 9
|
||||
#define CAPPROPID_HUE 10
|
||||
#define CAPPROPID_SHARPNESS 11
|
||||
#define CAPPROPID_BACKLIGHTCOMP 12
|
||||
#define CAPPROPID_POWERLINEFREQ 13
|
||||
#define CAPPROPID_LAST 14
|
||||
|
||||
namespace cs {
|
||||
class UsbCameraImpl;
|
||||
}
|
||||
|
||||
@interface UvcControlImpl : NSObject
|
||||
|
||||
@property(nonatomic) IOUSBInterfaceInterface190** controlInterface;
|
||||
@property(nonatomic) uint32_t processingUnitID;
|
||||
@property(nonatomic) std::weak_ptr<cs::UsbCameraImpl> cppImpl;
|
||||
|
||||
// Create from AVCaptureDevice
|
||||
+ (instancetype)createFromAVCaptureDevice:(AVCaptureDevice*)device status:(CS_Status*)status;
|
||||
|
||||
// Initialize with USB vendor/product/location
|
||||
- (instancetype)initWithVendorId:(uint16_t)vid
|
||||
productId:(uint16_t)pid
|
||||
location:(uint32_t)location
|
||||
status:(CS_Status*)status;
|
||||
|
||||
- (void)dealloc;
|
||||
|
||||
// Basic property control
|
||||
- (bool)setProperty:(uint32_t)propID
|
||||
withValue:(int32_t)value
|
||||
status:(CS_Status*)status;
|
||||
- (bool)getProperty:(uint32_t)propID
|
||||
withValue:(int32_t*)value
|
||||
status:(CS_Status*)status;
|
||||
|
||||
// Auto mode control
|
||||
- (bool)setAutoProperty:(uint32_t)propID
|
||||
enabled:(bool)enabled
|
||||
status:(CS_Status*)status;
|
||||
- (bool)getAutoProperty:(uint32_t)propID
|
||||
enabled:(bool*)enabled
|
||||
status:(CS_Status*)status;
|
||||
|
||||
// Property range query
|
||||
- (bool)getPropertyLimits:(uint32_t)propID
|
||||
min:(int32_t*)min
|
||||
max:(int32_t*)max
|
||||
defValue:(int32_t*)defValue
|
||||
status:(CS_Status*)status;
|
||||
|
||||
@end
|
||||
773
cscore/src/main/native/objcpp/UvcControlImpl.mm
Normal file
773
cscore/src/main/native/objcpp/UvcControlImpl.mm
Normal file
@@ -0,0 +1,773 @@
|
||||
// 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.
|
||||
|
||||
// Copyright (c) 2017 Jason von Nieda, Niels Moseley
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
#import "UvcControlImpl.h"
|
||||
|
||||
#include "Log.h"
|
||||
#include "UsbCameraImpl.h"
|
||||
|
||||
template <typename S, typename... Args>
|
||||
inline void NamedLog(UvcControlImpl* objc, unsigned int level,
|
||||
const char* file, unsigned int line, const S& format,
|
||||
Args&&... args) {
|
||||
auto sharedThis = objc.cppImpl.lock();
|
||||
if (!sharedThis) {
|
||||
return;
|
||||
}
|
||||
|
||||
wpi::Logger& logger = sharedThis->objcGetLogger();
|
||||
std::string_view name = sharedThis->GetName();
|
||||
|
||||
if (logger.HasLogger() && level >= logger.min_level()) {
|
||||
cs::NamedLogV(logger, level, file, line, name, format,
|
||||
fmt::make_format_args(args...));
|
||||
}
|
||||
}
|
||||
|
||||
#define UVCLOG(level, format, ...) \
|
||||
NamedLog(self, level, __FILE__, __LINE__, \
|
||||
format __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#define UVCERROR(format, ...) \
|
||||
UVCLOG(::wpi::WPI_LOG_ERROR, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define UVCWARNING(format, ...) \
|
||||
UVCLOG(::wpi::WPI_LOG_WARNING, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define UVCINFO(format, ...) \
|
||||
UVCLOG(::wpi::WPI_LOG_INFO, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#ifdef NDEBUG
|
||||
#define UVCDEBUG(format, ...) \
|
||||
do { \
|
||||
} while (0)
|
||||
#define UVCDEBUG1(format, ...) \
|
||||
do { \
|
||||
} while (0)
|
||||
#define UVCDEBUG2(format, ...) \
|
||||
do { \
|
||||
} while (0)
|
||||
#define UVCDEBUG3(format, ...) \
|
||||
do { \
|
||||
} while (0)
|
||||
#define UVCDEBUG4(format, ...) \
|
||||
do { \
|
||||
} while (0)
|
||||
#else
|
||||
#define UVCDEBUG(format, ...) \
|
||||
UVCLOG(::wpi::WPI_LOG_DEBUG, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define UVCDEBUG1(format, ...) \
|
||||
UVCLOG(::wpi::WPI_LOG_DEBUG1, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define UVCDEBUG2(format, ...) \
|
||||
UVCLOG(::wpi::WPI_LOG_DEBUG2, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define UVCDEBUG3(format, ...) \
|
||||
UVCLOG(::wpi::WPI_LOG_DEBUG3, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define UVCDEBUG4(format, ...) \
|
||||
UVCLOG(::wpi::WPI_LOG_DEBUG4, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
// USB descriptor for UVC processing unit
|
||||
struct ProcessingUnitDescriptor
|
||||
{
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType; // CS_INTERFACE 0x24
|
||||
uint8_t bDescriptorSubtype; // VC_PROCESSING_UNIT 0x05
|
||||
uint8_t bUnitID;
|
||||
};
|
||||
|
||||
struct propertyInfo_t
|
||||
{
|
||||
uint32_t selector; // selector ID
|
||||
uint32_t unit; // unit (==0 for INPUT TERMINA:, ==1 for PROCESSING UNIT)
|
||||
uint32_t length; // length (bytes)
|
||||
};
|
||||
|
||||
/** The order of the propertyInfo structure must
|
||||
be the same as the PROPID numbers in the
|
||||
openpnp-capture.h header */
|
||||
const propertyInfo_t propertyInfo[] =
|
||||
{
|
||||
{0,0,0},
|
||||
{CT_EXPOSURE_TIME_ABSOLUTE_CONTROL , 0, 4},
|
||||
{CT_FOCUS_ABSOLUTE_CONTROL , 0, 2},
|
||||
{CT_ZOOM_ABSOLUTE_CONTROL , 0, 2},
|
||||
{PU_WHITE_BALANCE_TEMPERATURE_CONTROL, 1, 2},
|
||||
{PU_GAIN_CONTROL , 1, 2},
|
||||
{PU_BRIGHTNESS_CONTROL , 1, 2},
|
||||
{PU_CONTRAST_CONTROL , 1, 2},
|
||||
{PU_SATURATION_CONTROL , 1, 2},
|
||||
{PU_GAMMA_CONTROL , 1, 2},
|
||||
{PU_HUE_CONTROL , 1, 2},
|
||||
{PU_SHARPNESS_CONTROL , 1, 2},
|
||||
{PU_BACKLIGHT_COMPENSATION_CONTROL , 1, 2},
|
||||
{PU_POWER_LINE_FREQUENCY_CONTROL , 1, 1}
|
||||
};
|
||||
|
||||
@implementation UvcControlImpl {
|
||||
IOUSBDeviceInterface** _deviceInterface;
|
||||
}
|
||||
|
||||
|
||||
+ (instancetype)createFromAVCaptureDevice:(AVCaptureDevice*)device status:(CS_Status*)status {
|
||||
if (!device) {
|
||||
NSLog(@"UVC: device is nil");
|
||||
*status = CS_UVC_STATUS_ERROR;
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSError* error = nil;
|
||||
NSRegularExpression* regex = [NSRegularExpression regularExpressionWithPattern:@"^UVC\\s+Camera\\s+VendorID\\_([0-9]+)\\s+ProductID\\_([0-9]+)$"
|
||||
options:0
|
||||
error:&error];
|
||||
if (error) {
|
||||
NSLog(@"UVC: failed to create regex: %@", error);
|
||||
*status = CS_UVC_STATUS_ERROR;
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString* modelID = [device valueForKey:@"modelID"];
|
||||
if (!modelID) {
|
||||
NSLog(@"UVC: modelID is nil");
|
||||
*status = CS_UVC_STATUS_ERROR;
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSTextCheckingResult* match = [regex firstMatchInString:modelID
|
||||
options:0
|
||||
range:NSMakeRange(0, modelID.length)];
|
||||
if (!match || match.numberOfRanges != 3) {
|
||||
NSLog(@"UVC: modelID regex match failed");
|
||||
*status = CS_UVC_STATUS_ERROR;
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString* vendorIDStr = [modelID substringWithRange:[match rangeAtIndex:1]];
|
||||
NSString* productIDStr = [modelID substringWithRange:[match rangeAtIndex:2]];
|
||||
uint16_t vendorID = (uint16_t)strtoul([vendorIDStr UTF8String], NULL, 10);
|
||||
uint16_t productID = (uint16_t)strtoul([productIDStr UTF8String], NULL, 10);
|
||||
|
||||
uint32_t locationID = 0;
|
||||
CFMutableDictionaryRef dict = IOServiceMatching(kIOUSBDeviceClassName);
|
||||
CFDictionarySetValue(dict, CFSTR("idVendor"), (__bridge CFNumberRef)@(vendorID));
|
||||
CFDictionarySetValue(dict, CFSTR("idProduct"), (__bridge CFNumberRef)@(productID));
|
||||
|
||||
io_iterator_t iter = 0;
|
||||
kern_return_t ioResult = IOServiceGetMatchingServices(kIOMainPortDefault, dict, &iter);
|
||||
if (ioResult == kIOReturnSuccess) {
|
||||
io_service_t usbDevice = IOIteratorNext(iter);
|
||||
while (usbDevice != 0) {
|
||||
CFTypeRef locationIDRef = IORegistryEntryCreateCFProperty(usbDevice,
|
||||
CFSTR("locationID"),
|
||||
kCFAllocatorDefault,
|
||||
0);
|
||||
if (locationIDRef) {
|
||||
locationID = [(__bridge NSNumber*)locationIDRef unsignedIntValue];
|
||||
CFRelease(locationIDRef);
|
||||
NSString* uniqueID = [device valueForKey:@"uniqueID"];
|
||||
NSString* locationIDHex = [NSString stringWithFormat:@"0x%x", locationID];
|
||||
if ([uniqueID hasPrefix:locationIDHex]) {
|
||||
IOObjectRelease(usbDevice);
|
||||
break;
|
||||
}
|
||||
}
|
||||
IOObjectRelease(usbDevice);
|
||||
usbDevice = IOIteratorNext(iter);
|
||||
}
|
||||
IOObjectRelease(iter);
|
||||
}
|
||||
|
||||
UvcControlImpl *instance = [[UvcControlImpl alloc] initWithVendorId:vendorID
|
||||
productId:productID
|
||||
location:locationID
|
||||
status:status];
|
||||
if (!instance) {
|
||||
NSLog(@"UVC: failed to create UvcControlImpl, status=%d", *status);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
- (instancetype)initWithVendorId:(uint16_t)vid
|
||||
productId:(uint16_t)pid
|
||||
location:(uint32_t)location
|
||||
status:(CS_Status*)status {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
// UVCINFO("Initializing with VID: 0x{:04X}, PID: 0x{:04X}, Location: 0x{:08X}", vid, pid, location);
|
||||
_deviceInterface = [self findDevice:vid productId:pid location:location];
|
||||
if (_deviceInterface == nullptr) {
|
||||
// UVCWARNING("Failed to find device");
|
||||
*status = CS_UVC_STATUS_DEVICE_DISCONNECTED;
|
||||
return nil;
|
||||
}
|
||||
|
||||
_processingUnitID = [self getProcessingUnitID:_deviceInterface];
|
||||
|
||||
_controlInterface = [self createControlInterface:_deviceInterface];
|
||||
|
||||
if (_controlInterface == nullptr) {
|
||||
// UVCWARNING("Failed to create control interface");
|
||||
*status = CS_UVC_STATUS_DEVICE_DISCONNECTED;
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (_controlInterface != nullptr) {
|
||||
(*_controlInterface)->USBInterfaceClose(_controlInterface);
|
||||
(*_controlInterface)->Release(_controlInterface);
|
||||
}
|
||||
if (_deviceInterface != nullptr) {
|
||||
(*_deviceInterface)->Release(_deviceInterface);
|
||||
}
|
||||
}
|
||||
|
||||
- (IOUSBDeviceInterface**)findDevice:(uint16_t)vid
|
||||
productId:(uint16_t)pid
|
||||
location:(uint32_t)location {
|
||||
|
||||
CFMutableDictionaryRef dict = IOServiceMatching(kIOUSBDeviceClassName);
|
||||
|
||||
io_iterator_t serviceIterator;
|
||||
kern_return_t result = IOServiceGetMatchingServices((mach_port_t)NULL, dict, &serviceIterator);
|
||||
if (result != kIOReturnSuccess) {
|
||||
UVCERROR("findDevice: IOServiceGetMatchingServices failed: {}", result);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
io_service_t device;
|
||||
while((device = IOIteratorNext(serviceIterator)) != 0) {
|
||||
IOUSBDeviceInterface **deviceInterface = nullptr;
|
||||
IOCFPlugInInterface **plugInInterface = nullptr;
|
||||
SInt32 score;
|
||||
|
||||
kern_return_t result = IOCreatePlugInInterfaceForService(
|
||||
device, kIOUSBDeviceUserClientTypeID,
|
||||
kIOCFPlugInInterfaceID, &plugInInterface, &score);
|
||||
|
||||
if ((result != kIOReturnSuccess) || (plugInInterface == nullptr)) {
|
||||
UVCERROR("findDevice: Camera control error: {}", result);
|
||||
IOObjectRelease(device);
|
||||
continue;
|
||||
}
|
||||
|
||||
HRESULT hr = (*plugInInterface)->QueryInterface(plugInInterface,
|
||||
CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID),
|
||||
(LPVOID*)&deviceInterface);
|
||||
|
||||
if (hr || (deviceInterface == nullptr)) {
|
||||
(*plugInInterface)->Release(plugInInterface);
|
||||
IOObjectRelease(device);
|
||||
UVCERROR("findDevice: QueryInterface failed");
|
||||
continue;
|
||||
}
|
||||
|
||||
uint16_t vendorID, productID;
|
||||
uint32_t locationID;
|
||||
result = (*deviceInterface)->GetDeviceVendor(deviceInterface, &vendorID);
|
||||
result = (*deviceInterface)->GetDeviceProduct(deviceInterface, &productID);
|
||||
result = (*deviceInterface)->GetLocationID(deviceInterface, &locationID);
|
||||
|
||||
// if 'location' is zero, we won't match on location
|
||||
// to achieve this, we simply set locationID to zero.
|
||||
if (location == 0) {
|
||||
locationID = 0;
|
||||
}
|
||||
|
||||
if ((vendorID == vid) && (productID == pid) && (locationID == location)) {
|
||||
(*plugInInterface)->Release(plugInInterface);
|
||||
IOObjectRelease(device);
|
||||
IOObjectRelease(serviceIterator);
|
||||
return deviceInterface;
|
||||
}
|
||||
|
||||
(*deviceInterface)->Release(deviceInterface);
|
||||
(*plugInInterface)->Release(plugInInterface);
|
||||
IOObjectRelease(device);
|
||||
}
|
||||
|
||||
IOObjectRelease(serviceIterator);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
- (uint32_t)getProcessingUnitID:(IOUSBDeviceInterface**)dev {
|
||||
IOReturn kr;
|
||||
IOUSBConfigurationDescriptorPtr configDesc;
|
||||
|
||||
kr = (*dev)->GetConfigurationDescriptorPtr(dev, 0, &configDesc);
|
||||
if (kr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
UVCDEBUG4("USB descriptor:");
|
||||
UVCDEBUG4(" length = 0x{:08X}", configDesc->bLength);
|
||||
UVCDEBUG4(" type = 0x{:08X}", configDesc->bDescriptorType);
|
||||
UVCDEBUG4(" totalLen = 0x{:08X}", configDesc->wTotalLength);
|
||||
UVCDEBUG4(" interfaces = 0x{:08X}", configDesc->bNumInterfaces);
|
||||
|
||||
uint32_t idx = 0;
|
||||
uint8_t *ptr = (uint8_t*)configDesc;
|
||||
|
||||
// Search for VIDEO/CONTROL interface descriptor
|
||||
// Class=14, Subclass=1, Protocol=0
|
||||
// and find the processing unit, if available..
|
||||
// DescriptorType 0x24, DescriptorSubType 0x5
|
||||
|
||||
IOUSBInterfaceDescriptor *iface = NULL;
|
||||
ProcessingUnitDescriptor *pud = NULL;
|
||||
bool inVideoControlInterfaceDescriptor = false;
|
||||
while(idx < configDesc->wTotalLength) {
|
||||
IOUSBDescriptorHeader *hdr = (IOUSBDescriptorHeader *)&ptr[idx];
|
||||
switch(hdr->bDescriptorType)
|
||||
{
|
||||
case 0x05: // Endpoint descriptor ID
|
||||
break;
|
||||
case 0x02: // Configuration descriptor ID
|
||||
break;
|
||||
case 0x04: // Interface descriptor ID
|
||||
iface = (IOUSBInterfaceDescriptor*)&ptr[idx];
|
||||
if ((iface->bInterfaceClass == 14) &&
|
||||
(iface->bInterfaceSubClass == 1) &&
|
||||
(iface->bInterfaceProtocol == 0))
|
||||
{
|
||||
inVideoControlInterfaceDescriptor = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
inVideoControlInterfaceDescriptor = false;
|
||||
}
|
||||
break;
|
||||
case 0x24: // class-specific ID
|
||||
pud = (ProcessingUnitDescriptor*)&ptr[idx];
|
||||
if (inVideoControlInterfaceDescriptor)
|
||||
{
|
||||
if (pud->bDescriptorSubtype == 0x05)
|
||||
{
|
||||
return pud->bUnitID;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
idx += hdr->bLength;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (IOUSBInterfaceInterface190**)createControlInterface:(IOUSBDeviceInterface**)deviceInterface {
|
||||
IOUSBInterfaceInterface190 **controlInterface;
|
||||
|
||||
io_iterator_t interfaceIterator;
|
||||
IOUSBFindInterfaceRequest interfaceRequest;
|
||||
interfaceRequest.bInterfaceClass = UVC_CONTROL_INTERFACE_CLASS;
|
||||
interfaceRequest.bInterfaceSubClass = UVC_CONTROL_INTERFACE_SUBCLASS;
|
||||
interfaceRequest.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
|
||||
interfaceRequest.bAlternateSetting = kIOUSBFindInterfaceDontCare;
|
||||
|
||||
IOReturn result = (*deviceInterface)->CreateInterfaceIterator(deviceInterface,
|
||||
&interfaceRequest, &interfaceIterator);
|
||||
|
||||
if (result != kIOReturnSuccess) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
io_service_t usbInterface;
|
||||
if ((usbInterface = IOIteratorNext(interfaceIterator)) != 0) {
|
||||
IOCFPlugInInterface **plugInInterface = nullptr;
|
||||
SInt32 score;
|
||||
|
||||
kern_return_t kr = IOCreatePlugInInterfaceForService(usbInterface,
|
||||
kIOUSBInterfaceUserClientTypeID,
|
||||
kIOCFPlugInInterfaceID,
|
||||
&plugInInterface,
|
||||
&score);
|
||||
|
||||
kr = IOObjectRelease(usbInterface);
|
||||
if ((kr != kIOReturnSuccess) || !plugInInterface) {
|
||||
UVCERROR("createControlInterface: cannot create plug-in {:08X}",
|
||||
kr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
HRESULT hr = (*plugInInterface)->QueryInterface(plugInInterface,
|
||||
CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID),
|
||||
(LPVOID*) &controlInterface);
|
||||
|
||||
(*plugInInterface)->Release(plugInInterface);
|
||||
|
||||
if (hr || !controlInterface) {
|
||||
UVCERROR("createControlInterface: cannot create device interface {:08X}",
|
||||
result);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UVCDEBUG3("createControlInterface: created control interface");
|
||||
return controlInterface;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
- (bool)sendControlRequest:(IOUSBDevRequest)req {
|
||||
if (_controlInterface == nullptr) {
|
||||
UVCERROR("control interface is NULL");
|
||||
return false;
|
||||
}
|
||||
|
||||
kern_return_t kr;
|
||||
if (@available(macOS 12.0, *)) {
|
||||
// macOS 12 doesn't like if we're trying to open USB interface here...
|
||||
} else {
|
||||
kr = (*_controlInterface)->USBInterfaceOpen(_controlInterface);
|
||||
if (kr != kIOReturnSuccess) {
|
||||
UVCERROR("USBInterfaceOpen failed with error: 0x{:08X}", kr);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
kr = (*_controlInterface)->ControlRequest(_controlInterface, 0, &req);
|
||||
if (kr != kIOReturnSuccess) {
|
||||
// IOKIT error code
|
||||
#define err_get_system(err) (((err)>>26)&0x3f)
|
||||
#define err_get_sub(err) (((err)>>14)&0xfff)
|
||||
#define err_get_code(err) ((err)&0x3fff)
|
||||
|
||||
uint32_t code = err_get_code(kr);
|
||||
uint32_t sys = err_get_system(kr);
|
||||
uint32_t sub = err_get_sub(kr);
|
||||
|
||||
switch(kr)
|
||||
{
|
||||
case kIOUSBUnknownPipeErr:
|
||||
UVCERROR("Pipe ref not recognised");
|
||||
break;
|
||||
case kIOUSBTooManyPipesErr:
|
||||
UVCERROR("Too many pipes");
|
||||
break;
|
||||
case kIOUSBEndpointNotFound:
|
||||
UVCERROR("Endpoint not found");
|
||||
break;
|
||||
case kIOUSBConfigNotFound:
|
||||
UVCERROR("USB configuration not found");
|
||||
break;
|
||||
case kIOUSBPipeStalled:
|
||||
//Note: we don't report this as an error as this happens when
|
||||
// an unsupported or locked property is set.
|
||||
UVCDEBUG("Pipe has stalled, error needs to be cleared");
|
||||
break;
|
||||
case kIOUSBInterfaceNotFound:
|
||||
UVCERROR("USB control interface not found");
|
||||
break;
|
||||
default:
|
||||
UVCERROR("ControlRequest failed (KR=sys:sub:code) = {:02Xh}:{:03Xh}:{:04Xh}",
|
||||
sys, sub, code);
|
||||
break;
|
||||
}
|
||||
|
||||
if (@available(macOS 12.0, *)) {
|
||||
// macOS 12 doesn't like if we're trying to close USB interface here...
|
||||
} else {
|
||||
kr = (*_controlInterface)->USBInterfaceClose(_controlInterface);
|
||||
if (kr != kIOReturnSuccess) {
|
||||
UVCERROR("USBInterfaceClose failed");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (@available(macOS 12.0, *)) {
|
||||
// macOS 12 doesn't like if we're trying to close USB interface here either...
|
||||
} else {
|
||||
kr = (*_controlInterface)->USBInterfaceClose(_controlInterface);
|
||||
|
||||
if (kr != kIOReturnSuccess) {
|
||||
UVCERROR("USBInterfaceClose failed");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
- (bool)setData:(uint32_t)selector unit:(uint32_t)unit length:(uint32_t)length data:(int32_t)data {
|
||||
IOUSBDevRequest req;
|
||||
req.bmRequestType = USBmakebmRequestType((UInt8)kUSBOut, (UInt8)kUSBClass, (UInt8)kUSBInterface);
|
||||
req.bRequest = UVC_SET_CUR;
|
||||
req.wValue = (selector << 8);
|
||||
req.wIndex = (unit << 8);
|
||||
req.wLength = length;
|
||||
req.wLenDone = 0;
|
||||
req.pData = &data;
|
||||
return [self sendControlRequest:req];
|
||||
}
|
||||
|
||||
- (bool)getData:(uint32_t)selector unit:(uint32_t)unit length:(uint32_t)length data:(int32_t*)data {
|
||||
IOUSBDevRequest req;
|
||||
req.bmRequestType = USBmakebmRequestType((UInt8)kUSBIn, (UInt8)kUSBClass, (UInt8)kUSBInterface);
|
||||
req.bRequest = UVC_GET_CUR;
|
||||
req.wValue = (selector << 8);
|
||||
req.wIndex = (unit << 8);
|
||||
req.wLength = length;
|
||||
req.wLenDone = 0;
|
||||
req.pData = data;
|
||||
return [self sendControlRequest:req];
|
||||
}
|
||||
|
||||
- (bool)getMaxData:(uint32_t)selector unit:(uint32_t)unit length:(uint32_t)length data:(int32_t*)data {
|
||||
IOUSBDevRequest req;
|
||||
*data = 0;
|
||||
req.bmRequestType = USBmakebmRequestType((UInt8)kUSBIn, (UInt8)kUSBClass, (UInt8)kUSBInterface);
|
||||
req.bRequest = UVC_GET_MAX;
|
||||
req.wValue = (selector << 8);
|
||||
req.wIndex = (unit << 8);
|
||||
req.wLength = length;
|
||||
req.wLenDone = 0;
|
||||
req.pData = data;
|
||||
return [self sendControlRequest:req];
|
||||
}
|
||||
|
||||
- (bool)getMinData:(uint32_t)selector unit:(uint32_t)unit length:(uint32_t)length data:(int32_t*)data {
|
||||
IOUSBDevRequest req;
|
||||
*data = 0;
|
||||
req.bmRequestType = USBmakebmRequestType((UInt8)kUSBIn, (UInt8)kUSBClass, (UInt8)kUSBInterface);
|
||||
req.bRequest = UVC_GET_MIN;
|
||||
req.wValue = (selector << 8);
|
||||
req.wIndex = (unit << 8);
|
||||
req.wLength = length;
|
||||
req.wLenDone = 0;
|
||||
req.pData = data;
|
||||
return [self sendControlRequest:req];
|
||||
}
|
||||
|
||||
- (bool)getDefault:(uint32_t)selector unit:(uint32_t)unit length:(uint32_t)length data:(int32_t*)data {
|
||||
IOUSBDevRequest req;
|
||||
*data = 0;
|
||||
req.bmRequestType = USBmakebmRequestType((UInt8)kUSBIn, (UInt8)kUSBClass, (UInt8)kUSBInterface);
|
||||
req.bRequest = UVC_GET_DEF;
|
||||
req.wValue = (selector << 8);
|
||||
req.wIndex = (unit << 8);
|
||||
req.wLength = length;
|
||||
req.wLenDone = 0;
|
||||
req.pData = data;
|
||||
return [self sendControlRequest:req];
|
||||
}
|
||||
|
||||
- (bool)getInfo:(uint32_t)selector unit:(uint32_t)unit data:(uint32_t*)data {
|
||||
IOUSBDevRequest req;
|
||||
*data = 0;
|
||||
req.bmRequestType = USBmakebmRequestType((UInt8)kUSBIn, (UInt8)kUSBClass, (UInt8)kUSBInterface);
|
||||
req.bRequest = UVC_GET_INFO;
|
||||
req.wValue = (selector << 8);
|
||||
req.wIndex = (unit << 8);
|
||||
req.wLength = 1;
|
||||
req.wLenDone = 0;
|
||||
req.pData = data;
|
||||
return [self sendControlRequest:req];
|
||||
}
|
||||
|
||||
- (bool)setProperty:(uint32_t)propID withValue:(int32_t)value status:(CS_Status*)status {
|
||||
if (_controlInterface == nullptr) {
|
||||
UVCERROR("control interface is NULL");
|
||||
*status = CS_UVC_STATUS_DEVICE_DISCONNECTED;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
if (propID < CAPPROPID_LAST) {
|
||||
uint32_t unit = (propertyInfo[propID].unit == 0) ? UVC_INPUT_TERMINAL_ID : _processingUnitID;
|
||||
ok = [self setData:propertyInfo[propID].selector unit:unit length:propertyInfo[propID].length data:value];
|
||||
if (!ok) {
|
||||
UVCWARNING("Failed to set property {}", propID);
|
||||
}
|
||||
} else {
|
||||
UVCWARNING("Invalid property ID: {}", propID);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
- (bool)getProperty:(uint32_t)propID withValue:(int32_t*)value status:(CS_Status*)status {
|
||||
if (_controlInterface == nullptr) {
|
||||
UVCERROR("control interface is NULL");
|
||||
*status = CS_UVC_STATUS_DEVICE_DISCONNECTED;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
if (propID < CAPPROPID_LAST) {
|
||||
uint32_t unit = (propertyInfo[propID].unit == 0) ? UVC_INPUT_TERMINAL_ID : _processingUnitID;
|
||||
ok = [self getData:propertyInfo[propID].selector unit:unit length:propertyInfo[propID].length data:value];
|
||||
|
||||
switch(propertyInfo[propID].length) {
|
||||
case 2:
|
||||
*value = static_cast<int16_t>(*value);
|
||||
break;
|
||||
case 1:
|
||||
*value = static_cast<int8_t>(*value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!ok) {
|
||||
UVCWARNING("Failed to get property {}", propID);
|
||||
}
|
||||
} else {
|
||||
UVCWARNING("Invalid property ID: {}", propID);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
- (bool)setAutoProperty:(uint32_t)propID enabled:(bool)enabled status:(CS_Status*)status {
|
||||
if (_controlInterface == nullptr) {
|
||||
UVCERROR("control interface is NULL");
|
||||
*status = CS_UVC_STATUS_DEVICE_DISCONNECTED;
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t value = enabled ? 1 : 0;
|
||||
switch(propID) {
|
||||
case CAPPROPID_EXPOSURE:
|
||||
return [self setData:CT_AE_MODE_CONTROL unit:UVC_INPUT_TERMINAL_ID length:1 data:enabled ? 0x8 : 0x1];
|
||||
case CAPPROPID_WHITEBALANCE:
|
||||
return [self setData:PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL unit:_processingUnitID length:1 data:value];
|
||||
case CAPPROPID_FOCUS:
|
||||
return [self setData:CT_FOCUS_AUTO_CONTROL unit:UVC_INPUT_TERMINAL_ID length:1 data:value];
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
- (bool)getAutoProperty:(uint32_t)propID enabled:(bool*)enabled status:(CS_Status*)status {
|
||||
if (_controlInterface == nullptr) {
|
||||
UVCERROR("control interface is NULL");
|
||||
*status = CS_UVC_STATUS_DEVICE_DISCONNECTED;
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t value;
|
||||
|
||||
switch(propID) {
|
||||
case CAPPROPID_EXPOSURE:
|
||||
if ([self getData:CT_AE_MODE_CONTROL unit:UVC_INPUT_TERMINAL_ID length:1 data:&value]) {
|
||||
// value = 1 -> manual mode
|
||||
// 2 -> auto mode (I haven't seen this in the wild)
|
||||
// 4 -> shutter priority mode (haven't seen this)
|
||||
// 8 -> aperature prioritry mode (seen this used)
|
||||
value &= 0xFF;
|
||||
*enabled = (value==1) ? false : true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case CAPPROPID_WHITEBALANCE:
|
||||
if ([self getData:PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL unit:_processingUnitID length:1 data:&value]) {
|
||||
value &= 0xFF;
|
||||
*enabled = (value==1) ? true : false;
|
||||
UVCDEBUG3("White balance auto mode: {}", *enabled ? "enabled" : "disabled");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case CAPPROPID_FOCUS:
|
||||
if ([self getData:CT_FOCUS_AUTO_CONTROL unit:UVC_INPUT_TERMINAL_ID length:1 data:&value]) {
|
||||
value &= 0xFF;
|
||||
*enabled = (value==1) ? true : false;
|
||||
UVCDEBUG3("Focus auto mode: {}", *enabled ? "enabled" : "disabled");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
UVCWARNING("Unsupported auto property ID: {}", propID);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
- (bool)getPropertyLimits:(uint32_t)propID min:(int32_t*)min max:(int32_t*)max defValue:(int32_t*)defValue status:(CS_Status*)status {
|
||||
if (_controlInterface == nullptr) {
|
||||
*status = CS_UVC_STATUS_DEVICE_DISCONNECTED;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
if (propID < CAPPROPID_LAST) {
|
||||
uint32_t unit = (propertyInfo[propID].unit == 0) ? UVC_INPUT_TERMINAL_ID : _processingUnitID;
|
||||
|
||||
if (![self getMinData:propertyInfo[propID].selector unit:unit length:propertyInfo[propID].length data:min]) {
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (![self getMaxData:propertyInfo[propID].selector unit:unit length:propertyInfo[propID].length data:max]) {
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (![self getDefault:propertyInfo[propID].selector unit:unit length:propertyInfo[propID].length data:defValue]) {
|
||||
ok = false;
|
||||
}
|
||||
|
||||
switch(propertyInfo[propID].length) {
|
||||
case 2:
|
||||
*min = static_cast<int16_t>(*min);
|
||||
*max = static_cast<int16_t>(*max);
|
||||
*defValue = static_cast<int16_t>(*defValue);
|
||||
break;
|
||||
case 1:
|
||||
*min = static_cast<int8_t>(*min);
|
||||
*max = static_cast<int8_t>(*max);
|
||||
*defValue = static_cast<int8_t>(*defValue);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
UVCWARNING("getPropertyLimits: property ID out of bounds");
|
||||
ok = false;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
- (void)reportCapabilities:(uint32_t)selector unit:(uint32_t)unit {
|
||||
uint32_t info;
|
||||
[self getInfo:selector unit:unit data:&info];
|
||||
if (info & 0x01) {
|
||||
UVCDEBUG4("GET ");
|
||||
}
|
||||
if (info & 0x02) {
|
||||
UVCDEBUG4("SET ");
|
||||
}
|
||||
if (info & 0x04) {
|
||||
UVCDEBUG4("DISABLED ");
|
||||
}
|
||||
if (info & 0x08) {
|
||||
UVCDEBUG4("AUTO-UPD ");
|
||||
}
|
||||
if (info & 0x10) {
|
||||
UVCDEBUG4("ASYNC ");
|
||||
}
|
||||
if (info & 0x20) {
|
||||
UVCDEBUG4("DISCOMMIT");
|
||||
}
|
||||
UVCDEBUG4("");
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -12,16 +12,16 @@ static wpi::Event& GetInstance() {
|
||||
}
|
||||
|
||||
namespace cs {
|
||||
|
||||
void RunMainRunLoop() {
|
||||
wpi::Event& event = GetInstance();
|
||||
wpi::WaitForObject(event.GetHandle());
|
||||
}
|
||||
|
||||
int RunMainRunLoopTimeout(double timeoutSeconds) {
|
||||
int RunMainRunLoopTimeout(double timeout) {
|
||||
wpi::Event& event = GetInstance();
|
||||
bool timedOut = false;
|
||||
bool signaled =
|
||||
wpi::WaitForObject(event.GetHandle(), timeoutSeconds, &timedOut);
|
||||
bool signaled = wpi::WaitForObject(event.GetHandle(), timeout, &timedOut);
|
||||
if (timedOut) {
|
||||
return 3;
|
||||
}
|
||||
@@ -35,4 +35,5 @@ void StopMainRunLoop() {
|
||||
wpi::Event& event = GetInstance();
|
||||
event.Set();
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
|
||||
25
datalog/.styleguide
Normal file
25
datalog/.styleguide
Normal file
@@ -0,0 +1,25 @@
|
||||
cppHeaderFileInclude {
|
||||
\.h$
|
||||
}
|
||||
|
||||
cppSrcFileInclude {
|
||||
\.cpp$
|
||||
}
|
||||
|
||||
licenseUpdateExclude {
|
||||
examples/printlog
|
||||
}
|
||||
|
||||
modifiableFileExclude {
|
||||
examples/printlog/datalog\.py$
|
||||
}
|
||||
|
||||
repoRootNameOverride {
|
||||
datalog
|
||||
}
|
||||
|
||||
includeOtherLibs {
|
||||
^fmt/
|
||||
^gtest/
|
||||
^wpi/(?!datalog)
|
||||
}
|
||||
107
datalog/BUILD.bazel
Normal file
107
datalog/BUILD.bazel
Normal file
@@ -0,0 +1,107 @@
|
||||
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_test")
|
||||
load("@rules_java//java:defs.bzl", "java_binary")
|
||||
load("@rules_python//python:defs.bzl", "py_binary")
|
||||
load("//shared/bazel/rules:cc_rules.bzl", "wpilib_cc_library")
|
||||
load("//shared/bazel/rules:java_rules.bzl", "wpilib_java_junit5_test")
|
||||
load("//shared/bazel/rules:jni_rules.bzl", "wpilib_jni_cc_library", "wpilib_jni_java_library")
|
||||
|
||||
wpilib_cc_library(
|
||||
name = "datalog.static",
|
||||
srcs = glob(
|
||||
["src/main/native/cpp/**"],
|
||||
exclude = ["src/main/native/cpp/jni/**"],
|
||||
),
|
||||
hdrs = glob(["src/main/native/include/**"]),
|
||||
includes = [
|
||||
"src/main/native/cpp",
|
||||
"src/main/native/include",
|
||||
],
|
||||
strip_include_prefix = "src/main/native/include",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//wpiutil:wpiutil.static",
|
||||
],
|
||||
)
|
||||
|
||||
wpilib_jni_cc_library(
|
||||
name = "datalogjni",
|
||||
srcs = glob(["src/main/native/cpp/jni/**"]),
|
||||
java_dep = ":datalog-java",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":datalog.static",
|
||||
],
|
||||
)
|
||||
|
||||
wpilib_jni_java_library(
|
||||
name = "datalog-java",
|
||||
srcs = glob(["src/main/java/**/*.java"]),
|
||||
native_libs = [":datalogjni"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//wpiutil:wpiutil-java",
|
||||
"@maven//:us_hebi_quickbuf_quickbuf_runtime",
|
||||
],
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "datalog",
|
||||
srcs = ["examples/printlog/datalog.py"],
|
||||
target_compatible_with = select({
|
||||
"@rules_bzlmodrio_toolchains//constraints/is_systemcore:systemcore": ["@platforms//:incompatible"],
|
||||
"//conditions:default": [],
|
||||
}),
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "printlog",
|
||||
srcs = ["examples/printlog/printlog.cpp"],
|
||||
deps = [
|
||||
":datalog.static",
|
||||
"//wpiutil:wpiutil.static",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "writelog",
|
||||
srcs = ["examples/writelog/writelog.cpp"],
|
||||
deps = [
|
||||
":datalog.static",
|
||||
"//wpiutil:wpiutil.static",
|
||||
],
|
||||
)
|
||||
|
||||
java_binary(
|
||||
name = "printlog-java",
|
||||
srcs = ["src/printlog/java/printlog/PrintLog.java"],
|
||||
main_class = "printlog.PrintLog",
|
||||
deps = [
|
||||
":datalog-java",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "datalog-cpp-test",
|
||||
size = "small",
|
||||
srcs = glob(["src/test/native/**/*.cpp"]),
|
||||
tags = [
|
||||
"exclusive",
|
||||
"no-asan",
|
||||
"no-tsan",
|
||||
],
|
||||
deps = [
|
||||
":datalog.static",
|
||||
"//thirdparty/googletest:googletest.static",
|
||||
"//wpiutil:wpiutil-testlib",
|
||||
],
|
||||
)
|
||||
|
||||
wpilib_java_junit5_test(
|
||||
name = "datalog-java-test",
|
||||
srcs = glob(["src/test/java/**/*.java"]),
|
||||
tags = ["exclusive"],
|
||||
deps = [
|
||||
":datalog-java",
|
||||
"//wpiutil:wpiutil-java",
|
||||
],
|
||||
)
|
||||
108
datalog/CMakeLists.txt
Normal file
108
datalog/CMakeLists.txt
Normal file
@@ -0,0 +1,108 @@
|
||||
project(datalog)
|
||||
|
||||
include(CompileWarnings)
|
||||
|
||||
file(GLOB datalog_native_src src/main/native/cpp/*.cpp)
|
||||
|
||||
file(GLOB datalog_jni_src src/main/native/cpp/jni/DataLogJNI.cpp)
|
||||
list(REMOVE_ITEM datalog_native_src ${datalog_jni_src})
|
||||
|
||||
add_library(datalog ${datalog_native_src})
|
||||
set_target_properties(datalog PROPERTIES DEBUG_POSTFIX "d")
|
||||
|
||||
target_compile_features(datalog PUBLIC cxx_std_20)
|
||||
if(MSVC)
|
||||
target_compile_options(
|
||||
datalog
|
||||
PUBLIC /permissive- /Zc:preprocessor /Zc:__cplusplus /Zc:throwingNew /MP /bigobj /utf-8
|
||||
)
|
||||
target_compile_definitions(datalog PRIVATE -D_CRT_SECURE_NO_WARNINGS)
|
||||
endif()
|
||||
wpilib_target_warnings(datalog)
|
||||
|
||||
target_include_directories(
|
||||
datalog
|
||||
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
|
||||
)
|
||||
|
||||
target_link_libraries(datalog PRIVATE wpiutil)
|
||||
|
||||
subdir_list(datalog_examples "${CMAKE_CURRENT_SOURCE_DIR}/examples")
|
||||
foreach(example ${datalog_examples})
|
||||
file(GLOB datalog_example_src examples/${example}/*.cpp)
|
||||
if(datalog_example_src)
|
||||
add_executable(datalog_${example} ${datalog_example_src})
|
||||
wpilib_target_warnings(datalog_${example})
|
||||
target_link_libraries(datalog_${example} datalog wpiutil)
|
||||
set_property(TARGET datalog_${example} PROPERTY FOLDER "examples")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Java bindings
|
||||
if(WITH_JAVA)
|
||||
include(UseJava)
|
||||
|
||||
set(CMAKE_JNI_TARGET true)
|
||||
|
||||
file(GLOB_RECURSE JAVA_SOURCES src/main/java/*.java)
|
||||
file(GLOB QUICKBUF_JAR ${WPILIB_BINARY_DIR}/wpiutil/thirdparty/quickbuf/*.jar)
|
||||
|
||||
add_jar(
|
||||
datalog_jar
|
||||
${JAVA_SOURCES}
|
||||
# INCLUDE_JARS ${JACKSON_JARS} ${QUICKBUF_JAR}
|
||||
INCLUDE_JARS wpiutil_jar ${QUICKBUF_JAR}
|
||||
OUTPUT_NAME datalog
|
||||
OUTPUT_DIR ${WPILIB_BINARY_DIR}/${java_lib_dest}
|
||||
GENERATE_NATIVE_HEADERS datalog_jni_headers
|
||||
)
|
||||
set_property(TARGET datalog_jar PROPERTY FOLDER "java")
|
||||
|
||||
install_jar(datalog_jar DESTINATION ${java_lib_dest})
|
||||
install_jar_exports(TARGETS datalog_jar FILE datalog_jar.cmake DESTINATION share/datalog)
|
||||
|
||||
add_library(datalogjni ${datalog_jni_src})
|
||||
wpilib_target_warnings(datalogjni)
|
||||
target_link_libraries(datalogjni PUBLIC datalog wpiutil)
|
||||
|
||||
set_property(TARGET datalogjni PROPERTY FOLDER "libraries")
|
||||
|
||||
target_link_libraries(datalogjni PRIVATE datalog_jni_headers)
|
||||
add_dependencies(datalogjni datalog_jar)
|
||||
|
||||
install(TARGETS datalogjni EXPORT datalogjni)
|
||||
export(TARGETS datalogjni FILE datalogjni.cmake NAMESPACE datalogjni::)
|
||||
endif()
|
||||
|
||||
if(WITH_JAVA_SOURCE)
|
||||
include(UseJava)
|
||||
include(CreateSourceJar)
|
||||
add_source_jar(
|
||||
datalog_src_jar
|
||||
BASE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/src/main/java
|
||||
OUTPUT_NAME datalog-sources
|
||||
OUTPUT_DIR ${WPILIB_BINARY_DIR}/${java_lib_dest}
|
||||
)
|
||||
set_property(TARGET datalog_src_jar PROPERTY FOLDER "java")
|
||||
|
||||
install_jar(datalog_src_jar DESTINATION ${java_lib_dest})
|
||||
endif()
|
||||
|
||||
install(TARGETS datalog EXPORT datalog)
|
||||
export(TARGETS datalog FILE datalog.cmake NAMESPACE datalog::)
|
||||
|
||||
configure_file(datalog-config.cmake.in ${WPILIB_BINARY_DIR}/datalog-config.cmake)
|
||||
install(FILES ${WPILIB_BINARY_DIR}/datalog-config.cmake DESTINATION share/datalog)
|
||||
install(EXPORT datalog DESTINATION share/datalog)
|
||||
|
||||
if(WITH_TESTS)
|
||||
file(GLOB_RECURSE datalog_testlib_src src/test/native/include/*.h)
|
||||
add_library(datalog_testlib INTERFACE ${datalog_test_src})
|
||||
target_include_directories(datalog_testlib INTERFACE src/test/native/include)
|
||||
|
||||
wpilib_add_test(datalog src/test/native/cpp)
|
||||
target_link_libraries(datalog_test datalog googletest datalog_testlib wpiutil)
|
||||
if(MSVC)
|
||||
target_compile_options(datalog_test PRIVATE /utf-8)
|
||||
endif()
|
||||
endif()
|
||||
76
datalog/build.gradle
Normal file
76
datalog/build.gradle
Normal file
@@ -0,0 +1,76 @@
|
||||
ext {
|
||||
useJava = true
|
||||
useCpp = true
|
||||
baseId = 'datalog'
|
||||
groupId = 'edu.wpi.first.datalog'
|
||||
|
||||
nativeName = 'datalog'
|
||||
devMain = 'edu.wpi.first.datalog.DevMain'
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/shared/jni/setupBuild.gradle"
|
||||
|
||||
nativeUtils.exportsConfigs {
|
||||
datalog {
|
||||
}
|
||||
}
|
||||
|
||||
model {
|
||||
components {
|
||||
all {
|
||||
it.sources.each {
|
||||
it.exportedHeaders {
|
||||
srcDirs 'src/main/native/include'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def examplesMap = [:];
|
||||
file("$projectDir/examples").list(new FilenameFilter() {
|
||||
@Override
|
||||
public boolean accept(File current, String name) {
|
||||
return new File(current, name).isDirectory();
|
||||
}
|
||||
}).each {
|
||||
examplesMap.put(it, [])
|
||||
}
|
||||
|
||||
model {
|
||||
components {
|
||||
examplesMap.each { key, value ->
|
||||
"${key}"(NativeExecutableSpec) {
|
||||
targetBuildTypes 'debug'
|
||||
binaries.all {
|
||||
lib library: 'datalog', linkage: 'shared'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
}
|
||||
sources {
|
||||
cpp {
|
||||
source {
|
||||
srcDirs 'examples/' + "${key}"
|
||||
include '**/*.cpp'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
printlog
|
||||
}
|
||||
|
||||
task runPrintLog(type: JavaExec) {
|
||||
classpath = sourceSets.printlog.runtimeClasspath
|
||||
|
||||
mainClass = 'printlog.PrintLog'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(":wpiutil")
|
||||
|
||||
printlogImplementation sourceSets.main.output
|
||||
}
|
||||
5
datalog/datalog-config.cmake.in
Normal file
5
datalog/datalog-config.cmake.in
Normal file
@@ -0,0 +1,5 @@
|
||||
@FILENAME_DEP_REPLACE@
|
||||
include(${SELF_DIR}/wpiutil.cmake)
|
||||
if(@WITH_JAVA@)
|
||||
include(${SELF_DIR}/wpiutil_jar.cmake)
|
||||
endif()
|
||||
@@ -190,3 +190,9 @@ Each entry's data type is an arbitrary string. The following data types are stan
|
||||
=== Metadata
|
||||
|
||||
Each entry has an associated metadata string. If not blank, the metadata should be <<JSON,JSON>>, but may be arbitrary text. Metadata is intended to convey additional information about the entry beyond what the type conveys--for example the source of the data.
|
||||
|
||||
[[additional-resources]]
|
||||
== Additional Resources
|
||||
|
||||
A https://kaitai.io/[Kaitai Struct] definition for the data log format is included in link:./wpilog.ksy[wpilog.ksy].
|
||||
Kaitai Struct is a declarative language used to describe various binary data structures.
|
||||
130
datalog/doc/wpilog.ksy
Normal file
130
datalog/doc/wpilog.ksy
Normal file
@@ -0,0 +1,130 @@
|
||||
meta:
|
||||
id: wpilog
|
||||
title: WPILib Data Log
|
||||
file-extension: wpilog
|
||||
ks-version: "0.10"
|
||||
encoding: UTF-8
|
||||
endian: le
|
||||
bit-endian: le
|
||||
seq:
|
||||
- id: header
|
||||
type: header
|
||||
- id: records
|
||||
type: record
|
||||
repeat: eos
|
||||
types:
|
||||
version_number:
|
||||
seq:
|
||||
- id: minor
|
||||
type: u1
|
||||
- id: major
|
||||
type: u1
|
||||
header:
|
||||
seq:
|
||||
- id: magic
|
||||
contents: WPILOG
|
||||
- id: version
|
||||
type: version_number
|
||||
- id: extra_header_length
|
||||
type: u4
|
||||
- id: extra_header
|
||||
size: extra_header_length
|
||||
type: str
|
||||
record:
|
||||
seq:
|
||||
- id: header_length
|
||||
type: record_header_length
|
||||
- id: entry_id
|
||||
type:
|
||||
switch-on: header_length.len_entry_id
|
||||
cases:
|
||||
0: u1
|
||||
1: u2
|
||||
2: b24
|
||||
3: u4
|
||||
- id: len_payload
|
||||
type:
|
||||
switch-on: header_length.len_payload_size
|
||||
cases:
|
||||
0: u1
|
||||
1: u2
|
||||
2: b24
|
||||
3: u4
|
||||
- id: timestamp
|
||||
type:
|
||||
switch-on: header_length.len_timestamp
|
||||
cases:
|
||||
0: u1
|
||||
1: u2
|
||||
2: b24
|
||||
3: u4
|
||||
4: b40
|
||||
5: b48
|
||||
6: b56
|
||||
7: u8
|
||||
- id: payload
|
||||
size: len_payload
|
||||
type:
|
||||
switch-on: entry_id
|
||||
cases:
|
||||
# ID 0 is reserved for control records
|
||||
0: control_record_payload
|
||||
record_header_length:
|
||||
seq:
|
||||
- id: len_entry_id
|
||||
type: b2
|
||||
- id: len_payload_size
|
||||
type: b2
|
||||
- id: len_timestamp
|
||||
type: b3
|
||||
- id: spare_bit
|
||||
type: b1
|
||||
control_record_payload:
|
||||
seq:
|
||||
- id: type
|
||||
type: u1
|
||||
enum: control_record_type
|
||||
- id: data
|
||||
type:
|
||||
switch-on: type
|
||||
cases:
|
||||
"control_record_type::start": control_record_start_data
|
||||
"control_record_type::finish": control_record_finish_data
|
||||
"control_record_type::set_metadata": control_record_set_metadata_data
|
||||
control_record_start_data:
|
||||
seq:
|
||||
- id: entry_id
|
||||
type: u4
|
||||
- id: len_entry_name
|
||||
type: u4
|
||||
- id: entry_name
|
||||
size: len_entry_name
|
||||
type: str
|
||||
- id: len_entry_type
|
||||
type: u4
|
||||
- id: entry_type
|
||||
size: len_entry_type
|
||||
type: str
|
||||
- id: len_entry_metadata
|
||||
type: u4
|
||||
- id: entry_metadata
|
||||
size: len_entry_metadata
|
||||
type: str
|
||||
control_record_finish_data:
|
||||
seq:
|
||||
- id: entry_id
|
||||
type: u4
|
||||
control_record_set_metadata_data:
|
||||
seq:
|
||||
- id: entry_id
|
||||
type: u4
|
||||
- id: len_entry_metadata
|
||||
type: u4
|
||||
- id: entry_metadata
|
||||
size: len_entry_metadata
|
||||
type: str
|
||||
enums:
|
||||
control_record_type:
|
||||
0: start
|
||||
1: finish
|
||||
2: set_metadata
|
||||
@@ -9,11 +9,11 @@
|
||||
#include <fmt/chrono.h>
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ranges.h>
|
||||
#include <wpi/DenseMap.h>
|
||||
#include <wpi/MemoryBuffer.h>
|
||||
#include <wpi/print.h>
|
||||
|
||||
#include "wpi/DataLogReader.h"
|
||||
#include "wpi/DenseMap.h"
|
||||
#include "wpi/MemoryBuffer.h"
|
||||
#include "wpi/print.h"
|
||||
#include "wpi/datalog/DataLogReader.h"
|
||||
|
||||
int main(int argc, const char** argv) {
|
||||
if (argc != 2) {
|
||||
@@ -8,8 +8,9 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "wpi/DataLogBackgroundWriter.h"
|
||||
#include "wpi/print.h"
|
||||
#include <wpi/print.h>
|
||||
|
||||
#include "wpi/datalog/DataLogBackgroundWriter.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
using std::chrono::duration_cast;
|
||||
@@ -2,10 +2,11 @@
|
||||
// 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/shuffleboard/LayoutType.h"
|
||||
package edu.wpi.first.datalog;
|
||||
|
||||
using namespace frc;
|
||||
public final class DevMain {
|
||||
/** Main entry point. */
|
||||
public static void main(String[] args) {}
|
||||
|
||||
std::string_view LayoutType::GetLayoutName() const {
|
||||
return m_layoutName;
|
||||
private DevMain() {}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
// 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.util.datalog;
|
||||
package edu.wpi.first.datalog;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// 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.util.datalog;
|
||||
package edu.wpi.first.datalog;
|
||||
|
||||
/** Log boolean values. */
|
||||
public class BooleanLogEntry extends DataLogEntry {
|
||||
@@ -2,7 +2,7 @@
|
||||
// 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.util.datalog;
|
||||
package edu.wpi.first.datalog;
|
||||
|
||||
import edu.wpi.first.util.WPIUtilJNI;
|
||||
import edu.wpi.first.util.protobuf.Protobuf;
|
||||
@@ -451,10 +451,10 @@ public class DataLog implements AutoCloseable {
|
||||
if (!seen.add(typeString)) {
|
||||
throw new UnsupportedOperationException(typeString + ": circular reference with " + seen);
|
||||
}
|
||||
addSchema(typeString, "structschema", struct.getSchema(), timestamp);
|
||||
for (Struct<?> inner : struct.getNested()) {
|
||||
addSchemaImpl(inner, timestamp, seen);
|
||||
}
|
||||
addSchema(typeString, "structschema", struct.getSchema(), timestamp);
|
||||
seen.remove(typeString);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// 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.util.datalog;
|
||||
package edu.wpi.first.datalog;
|
||||
|
||||
/**
|
||||
* A data log background writer that periodically flushes the data log on a background thread. The
|
||||
@@ -2,7 +2,7 @@
|
||||
// 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.util.datalog;
|
||||
package edu.wpi.first.datalog;
|
||||
|
||||
/** Log entry base class. */
|
||||
public class DataLogEntry {
|
||||
@@ -2,7 +2,7 @@
|
||||
// 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.util.datalog;
|
||||
package edu.wpi.first.datalog;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
@@ -2,18 +2,72 @@
|
||||
// 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.util.datalog;
|
||||
package edu.wpi.first.datalog;
|
||||
|
||||
import edu.wpi.first.util.WPIUtilJNI;
|
||||
import edu.wpi.first.util.RuntimeLoader;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* DataLog wpiutil JNI Functions.
|
||||
* DataLog JNI Functions.
|
||||
*
|
||||
* @see "wpiutil/DataLog.h"
|
||||
* @see "datalog/DataLog.h"
|
||||
*/
|
||||
public class DataLogJNI extends WPIUtilJNI {
|
||||
public class DataLogJNI {
|
||||
static boolean libraryLoaded = false;
|
||||
|
||||
/** Sets whether JNI should be loaded in the static block. */
|
||||
public static class Helper {
|
||||
private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(true);
|
||||
|
||||
/**
|
||||
* Returns true if the JNI should be loaded in the static block.
|
||||
*
|
||||
* @return True if the JNI should be loaded in the static block.
|
||||
*/
|
||||
public static boolean getExtractOnStaticLoad() {
|
||||
return extractOnStaticLoad.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the JNI should be loaded in the static block.
|
||||
*
|
||||
* @param load Whether the JNI should be loaded in the static block.
|
||||
*/
|
||||
public static void setExtractOnStaticLoad(boolean load) {
|
||||
extractOnStaticLoad.set(load);
|
||||
}
|
||||
|
||||
/** Utility class. */
|
||||
private Helper() {}
|
||||
}
|
||||
|
||||
static {
|
||||
if (Helper.getExtractOnStaticLoad()) {
|
||||
try {
|
||||
RuntimeLoader.loadLibrary("datalogjni");
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
libraryLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force load the library.
|
||||
*
|
||||
* @throws IOException if the library failed to load
|
||||
*/
|
||||
public static synchronized void forceLoad() throws IOException {
|
||||
if (libraryLoaded) {
|
||||
return;
|
||||
}
|
||||
RuntimeLoader.loadLibrary("datalogjni");
|
||||
libraryLoaded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Data Log background writer. The log will be initially created with a temporary
|
||||
* filename.
|
||||
@@ -298,6 +352,24 @@ public class DataLogJNI extends WPIUtilJNI {
|
||||
*/
|
||||
static native void appendStringArray(long impl, int entry, String[] value, long timestamp);
|
||||
|
||||
/**
|
||||
* Create a native FileLogger. When the specified file is modified, appended data will be appended
|
||||
* to the specified data log.
|
||||
*
|
||||
* @param file path to the file
|
||||
* @param log data log implementation handle
|
||||
* @param key log key to append data to
|
||||
* @return The FileLogger handle.
|
||||
*/
|
||||
public static native long createFileLogger(String file, long log, String key);
|
||||
|
||||
/**
|
||||
* Free a native FileLogger. This causes the FileLogger to stop appending data to the log.
|
||||
*
|
||||
* @param fileTail The FileLogger handle.
|
||||
*/
|
||||
public static native void freeFileLogger(long fileTail);
|
||||
|
||||
/** Utility class. */
|
||||
private DataLogJNI() {}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
// 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.util.datalog;
|
||||
package edu.wpi.first.datalog;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
@@ -2,7 +2,7 @@
|
||||
// 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.util.datalog;
|
||||
package edu.wpi.first.datalog;
|
||||
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -2,7 +2,7 @@
|
||||
// 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.util.datalog;
|
||||
package edu.wpi.first.datalog;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
@@ -2,7 +2,7 @@
|
||||
// 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.util.datalog;
|
||||
package edu.wpi.first.datalog;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// 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.util.datalog;
|
||||
package edu.wpi.first.datalog;
|
||||
|
||||
/** Log double values. */
|
||||
public class DoubleLogEntry extends DataLogEntry {
|
||||
@@ -2,9 +2,7 @@
|
||||
// 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.util;
|
||||
|
||||
import edu.wpi.first.util.datalog.DataLog;
|
||||
package edu.wpi.first.datalog;
|
||||
|
||||
/**
|
||||
* A class version of `tail -f`, otherwise known as `tail -f` at home. Watches a file and puts the
|
||||
@@ -22,11 +20,11 @@ public class FileLogger implements AutoCloseable {
|
||||
* @param key The log key to append data to.
|
||||
*/
|
||||
public FileLogger(String file, DataLog log, String key) {
|
||||
m_impl = WPIUtilJNI.createFileLogger(file, log.getImpl(), key);
|
||||
m_impl = DataLogJNI.createFileLogger(file, log.getImpl(), key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
WPIUtilJNI.freeFileLogger(m_impl);
|
||||
DataLogJNI.freeFileLogger(m_impl);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
// 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.util.datalog;
|
||||
package edu.wpi.first.datalog;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// 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.util.datalog;
|
||||
package edu.wpi.first.datalog;
|
||||
|
||||
/** Log float values. */
|
||||
public class FloatLogEntry extends DataLogEntry {
|
||||
@@ -2,7 +2,7 @@
|
||||
// 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.util.datalog;
|
||||
package edu.wpi.first.datalog;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// 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.util.datalog;
|
||||
package edu.wpi.first.datalog;
|
||||
|
||||
/** Log integer values. */
|
||||
public class IntegerLogEntry extends DataLogEntry {
|
||||
@@ -2,7 +2,7 @@
|
||||
// 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.util.datalog;
|
||||
package edu.wpi.first.datalog;
|
||||
|
||||
import edu.wpi.first.util.protobuf.Protobuf;
|
||||
import edu.wpi.first.util.protobuf.ProtobufBuffer;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user