Compare commits
400 Commits
v2022.2.1
...
v2023.1.1-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b114006543 | ||
|
|
32fbfb7da6 | ||
|
|
02465920fb | ||
|
|
3a5a376465 | ||
|
|
1c3c86e9f1 | ||
|
|
dcda09f90a | ||
|
|
66157397c1 | ||
|
|
9e22ffbebf | ||
|
|
648ab6115c | ||
|
|
8bc3b04f5b | ||
|
|
cfb84a6083 | ||
|
|
02c47726e1 | ||
|
|
b2a0093294 | ||
|
|
2a98d6b5d7 | ||
|
|
9f36301dc8 | ||
|
|
901fc555f4 | ||
|
|
4170ec6107 | ||
|
|
fe400f68c5 | ||
|
|
794669b346 | ||
|
|
dcfa85a5d5 | ||
|
|
15ad855f1d | ||
|
|
11244a49d9 | ||
|
|
1d2e8eb153 | ||
|
|
ad53fb19b4 | ||
|
|
ba850bac3b | ||
|
|
023a5989f8 | ||
|
|
c970011ccc | ||
|
|
07a43c3d9a | ||
|
|
a05b212b04 | ||
|
|
09faf31b67 | ||
|
|
9e1f9c1133 | ||
|
|
f19d2b9b84 | ||
|
|
a28f93863c | ||
|
|
c9f61669b8 | ||
|
|
dcce5ad3b3 | ||
|
|
6836e5923d | ||
|
|
335188c652 | ||
|
|
60a29dcb99 | ||
|
|
b55d5b3034 | ||
|
|
10ed4b3969 | ||
|
|
4a401b89d7 | ||
|
|
c195b4fc46 | ||
|
|
8f2e34c6a3 | ||
|
|
150d692df7 | ||
|
|
3e5bfff1b5 | ||
|
|
9c7e66a27d | ||
|
|
0ca274866b | ||
|
|
dc037f8d41 | ||
|
|
16cdc741cf | ||
|
|
9d5055176d | ||
|
|
d1e66e1296 | ||
|
|
1fc098e696 | ||
|
|
878cc8defb | ||
|
|
8153911160 | ||
|
|
fbdc810887 | ||
|
|
396143004c | ||
|
|
1f45732700 | ||
|
|
574cb41c18 | ||
|
|
d9d6c425e7 | ||
|
|
58b6484dbe | ||
|
|
ca43fe2798 | ||
|
|
87a64ccedc | ||
|
|
89a3d00297 | ||
|
|
1497665f96 | ||
|
|
27b173374e | ||
|
|
2a13dba8ac | ||
|
|
77301b126c | ||
|
|
90cfa00115 | ||
|
|
5cf961edb9 | ||
|
|
b2276e47de | ||
|
|
893b46139a | ||
|
|
60e29627c0 | ||
|
|
3b81cf6c35 | ||
|
|
5c067d30a0 | ||
|
|
ceaf493811 | ||
|
|
10e04e2b13 | ||
|
|
726f67c64b | ||
|
|
c7b7624c1c | ||
|
|
d600529ec0 | ||
|
|
b53b3526a2 | ||
|
|
38bb23eb18 | ||
|
|
3937ff8221 | ||
|
|
abbfe244b5 | ||
|
|
4ddb8aa0dd | ||
|
|
a791470de7 | ||
|
|
17f504f548 | ||
|
|
773198537c | ||
|
|
5ac658c8f0 | ||
|
|
8767e4a941 | ||
|
|
8c4af073f4 | ||
|
|
c79f38584a | ||
|
|
36c08dd97c | ||
|
|
69b7b3dd7d | ||
|
|
738c75fed8 | ||
|
|
4eb1d03fb3 | ||
|
|
ba4ec6c967 | ||
|
|
97836f0e55 | ||
|
|
fdfb85f695 | ||
|
|
ab1baf4832 | ||
|
|
9730032866 | ||
|
|
5b656eecf6 | ||
|
|
9ae38eaa7c | ||
|
|
cb33bd71df | ||
|
|
d9b4e7b8bf | ||
|
|
0389bf5214 | ||
|
|
4267fa08d1 | ||
|
|
65c8fbd452 | ||
|
|
f36162fddc | ||
|
|
5149f7d894 | ||
|
|
20b5bed1cb | ||
|
|
f18dd1905d | ||
|
|
aa9d7f1cdc | ||
|
|
2742662254 | ||
|
|
a5df391166 | ||
|
|
59e6706b75 | ||
|
|
8461bb1e03 | ||
|
|
b873e208b4 | ||
|
|
873e72df8c | ||
|
|
c8bd6fc5b4 | ||
|
|
fed68b83b4 | ||
|
|
0ef8a4e1df | ||
|
|
c393b3b367 | ||
|
|
b5a17f762c | ||
|
|
fafc81ed1a | ||
|
|
cc56bdc787 | ||
|
|
4254438d8d | ||
|
|
97c15af238 | ||
|
|
d22ff8a158 | ||
|
|
fdb5a2791f | ||
|
|
c3a93fb995 | ||
|
|
f2a8d38d2a | ||
|
|
9e24c6eac0 | ||
|
|
fe4d12ce22 | ||
|
|
eb08486039 | ||
|
|
ccf83c634a | ||
|
|
3fd69749e7 | ||
|
|
594df5fc08 | ||
|
|
539070820d | ||
|
|
564a56d99b | ||
|
|
5adf50d93c | ||
|
|
d80e8039d7 | ||
|
|
0e6d67b23b | ||
|
|
be5270697a | ||
|
|
8d28851263 | ||
|
|
3d2115c93e | ||
|
|
91002ae3cc | ||
|
|
148c18e658 | ||
|
|
a2a5c926b6 | ||
|
|
ea6b1d8449 | ||
|
|
ac9be78e27 | ||
|
|
151dabb2af | ||
|
|
340465c929 | ||
|
|
d45bcddd15 | ||
|
|
0e0786331a | ||
|
|
c5db23f296 | ||
|
|
44abc8dfa6 | ||
|
|
3fdb2f767d | ||
|
|
0485f05da9 | ||
|
|
0a5eb65231 | ||
|
|
19ffebaf3e | ||
|
|
ce1a90d639 | ||
|
|
d25af48797 | ||
|
|
ebb836dacb | ||
|
|
d83e202f00 | ||
|
|
3ccf806064 | ||
|
|
6f1e01f8bd | ||
|
|
1023c34b1c | ||
|
|
faa29d596c | ||
|
|
add00a96ed | ||
|
|
82fac41244 | ||
|
|
5eb44e22a9 | ||
|
|
2e09fa7325 | ||
|
|
fe3c24b1ee | ||
|
|
aa221597bc | ||
|
|
579a8ee229 | ||
|
|
5105c5eab6 | ||
|
|
787fe6e7a5 | ||
|
|
6671f8d099 | ||
|
|
9ac9b69aa2 | ||
|
|
e61028cb18 | ||
|
|
661d23eaf5 | ||
|
|
666040e3e5 | ||
|
|
aebc272449 | ||
|
|
fd884581e4 | ||
|
|
9b1bf5c7f1 | ||
|
|
c9e620a920 | ||
|
|
41d40dd62f | ||
|
|
30f5b68264 | ||
|
|
f7b3f4b90e | ||
|
|
a99c11c14c | ||
|
|
45b7fc445b | ||
|
|
16a4888c52 | ||
|
|
17752f1337 | ||
|
|
abb45a68db | ||
|
|
1280a54ef3 | ||
|
|
f2d243fa68 | ||
|
|
a4787130f4 | ||
|
|
af7985e46c | ||
|
|
e9d1b5c2d0 | ||
|
|
45b598d236 | ||
|
|
fc37265da5 | ||
|
|
a4ec13eb0e | ||
|
|
2fa52007af | ||
|
|
d9f9cd1140 | ||
|
|
8b6df88783 | ||
|
|
345cff08c0 | ||
|
|
57428112ac | ||
|
|
a18d4ff154 | ||
|
|
d1cd07b9f3 | ||
|
|
e67f8e917a | ||
|
|
be2fedfe50 | ||
|
|
7ad2be172e | ||
|
|
abc605c9c9 | ||
|
|
3e94805220 | ||
|
|
db2e1d170e | ||
|
|
96ebdcaf16 | ||
|
|
553b2a3b12 | ||
|
|
3e13ef42eb | ||
|
|
d651a1fcec | ||
|
|
b193b318c1 | ||
|
|
ef3714223b | ||
|
|
3d8dbbbac3 | ||
|
|
013efdde25 | ||
|
|
816aa4e465 | ||
|
|
046c2c8972 | ||
|
|
d80e9cdf64 | ||
|
|
7576136b4a | ||
|
|
c3b223ce60 | ||
|
|
5aa67f56e6 | ||
|
|
fff4d1f44e | ||
|
|
0d9956273c | ||
|
|
3fada4e0b4 | ||
|
|
65b23ac45e | ||
|
|
4ac34c0141 | ||
|
|
8bd614bb1e | ||
|
|
4253d6d5f0 | ||
|
|
6a4752dcdc | ||
|
|
5876b40f08 | ||
|
|
5983434a70 | ||
|
|
a3d44a1e69 | ||
|
|
d364bbd5a7 | ||
|
|
f341e1b2be | ||
|
|
9af389b200 | ||
|
|
2ae4adf2d7 | ||
|
|
178b2a1e88 | ||
|
|
18db343cdc | ||
|
|
f0c821282a | ||
|
|
d673ead481 | ||
|
|
b33715db15 | ||
|
|
99424ad562 | ||
|
|
dc6f641fd2 | ||
|
|
f20a20f3f1 | ||
|
|
708a4bc3bc | ||
|
|
ef7ed21a9d | ||
|
|
b1abf455c1 | ||
|
|
d5456cf278 | ||
|
|
99343d40ba | ||
|
|
ee03a7ad3b | ||
|
|
ce1a7d698a | ||
|
|
87bf70fa8e | ||
|
|
ebd2a303bf | ||
|
|
e28776d361 | ||
|
|
dac1429aa9 | ||
|
|
e767605e94 | ||
|
|
97c493241f | ||
|
|
8ea90d8bc9 | ||
|
|
ae7b1851ec | ||
|
|
e3d62c22d3 | ||
|
|
7200c4951d | ||
|
|
84056c9347 | ||
|
|
09cf6eeecb | ||
|
|
03230fc842 | ||
|
|
63cf3aaa3f | ||
|
|
18ff694f02 | ||
|
|
4f79ceedd9 | ||
|
|
f7ca72fb41 | ||
|
|
a06b3f0307 | ||
|
|
d926dd1610 | ||
|
|
51bc893bc5 | ||
|
|
fbe761f7f6 | ||
|
|
5ebe911933 | ||
|
|
3919250da2 | ||
|
|
b3aee28388 | ||
|
|
9d20ab3024 | ||
|
|
aaa69f6717 | ||
|
|
355a11a414 | ||
|
|
ffc69d406c | ||
|
|
922d50079a | ||
|
|
dd163b62ae | ||
|
|
bd80e220b9 | ||
|
|
aef4b16d4c | ||
|
|
975171609e | ||
|
|
5bf46a9093 | ||
|
|
f27a1f9bfb | ||
|
|
1b26e2d5da | ||
|
|
88222daa3d | ||
|
|
81c5b41ce1 | ||
|
|
9650e6733e | ||
|
|
c8905ec29a | ||
|
|
b4620f01f9 | ||
|
|
2e462a19d3 | ||
|
|
069f932e59 | ||
|
|
126e3de91a | ||
|
|
ba0dccaae4 | ||
|
|
e1b6e5f212 | ||
|
|
8d79dc8738 | ||
|
|
78108c2aba | ||
|
|
cdafc723fb | ||
|
|
0d70884dce | ||
|
|
765efa325e | ||
|
|
89ffcbbe41 | ||
|
|
95ae23b0e7 | ||
|
|
d5cb6fed67 | ||
|
|
d0fef18378 | ||
|
|
d640c0f41f | ||
|
|
a2fa5e3ff7 | ||
|
|
a3eea9958e | ||
|
|
db27331d7b | ||
|
|
fdfb31f164 | ||
|
|
f93c3331b3 | ||
|
|
ab7ac4fbb9 | ||
|
|
bc39a1a293 | ||
|
|
2668130e70 | ||
|
|
d27ed3722b | ||
|
|
dae18308c9 | ||
|
|
d66555e42f | ||
|
|
9f52d8a3b1 | ||
|
|
757ea91932 | ||
|
|
02a804f1c5 | ||
|
|
9b500df0d9 | ||
|
|
5a89575b3a | ||
|
|
b8c4d7527b | ||
|
|
ac5d46cfa7 | ||
|
|
bc9e96e86f | ||
|
|
f88c435dd0 | ||
|
|
e4b91005cf | ||
|
|
a260bfd83b | ||
|
|
18e262a100 | ||
|
|
4bd1f526ab | ||
|
|
27847d7eb2 | ||
|
|
b2a8d3f0f3 | ||
|
|
49adac9564 | ||
|
|
a19d1133b1 | ||
|
|
dde91717e4 | ||
|
|
e9050afd67 | ||
|
|
165d2837cf | ||
|
|
ac7549edca | ||
|
|
4d96bc72e0 | ||
|
|
3411eee20f | ||
|
|
74de97eeca | ||
|
|
4e3cc25012 | ||
|
|
90c1db393e | ||
|
|
2f43274aa4 | ||
|
|
aeca09db09 | ||
|
|
c107f22c67 | ||
|
|
68fe51e8da | ||
|
|
8d08d67cf1 | ||
|
|
4f1782f66e | ||
|
|
3f77725cd3 | ||
|
|
5635f33a32 | ||
|
|
bca4b7111b | ||
|
|
6a6366b0d6 | ||
|
|
16bf2c70c5 | ||
|
|
4b3edb742c | ||
|
|
fcf23fc9e9 | ||
|
|
af5ef510c5 | ||
|
|
05401e2b81 | ||
|
|
9fde0110b6 | ||
|
|
b03f8ddb2e | ||
|
|
a26df2a022 | ||
|
|
d68d6674e8 | ||
|
|
a8f0f6bb90 | ||
|
|
dd9c92d5bf | ||
|
|
84df14dd70 | ||
|
|
560094ad92 | ||
|
|
7ea1be9c01 | ||
|
|
700f13bffd | ||
|
|
b6aa7c1aa9 | ||
|
|
eb4d183e48 | ||
|
|
77e4e81e1e | ||
|
|
88f5cb6eb0 | ||
|
|
efae552f3e | ||
|
|
46b277421a | ||
|
|
42908126b9 | ||
|
|
a467392cbd | ||
|
|
78d0bcf49d | ||
|
|
02a0ced9b0 | ||
|
|
4ccfe1c9f2 | ||
|
|
830c0c5c2f | ||
|
|
5548a37465 | ||
|
|
2f9a600de2 | ||
|
|
559db11a20 | ||
|
|
76c78e295b | ||
|
|
debbd5ff4b | ||
|
|
841174f302 | ||
|
|
8c55844f91 | ||
|
|
0b990bf0f5 | ||
|
|
104d7e2abc | ||
|
|
5ba69e1af1 | ||
|
|
f3a0b5c7d7 |
@@ -53,7 +53,6 @@ Checks:
|
||||
google-readability-avoid-underscore-in-googletest-name,
|
||||
google-readability-casting,
|
||||
google-runtime-operator,
|
||||
llvm-twine-local,
|
||||
misc-definitions-in-headers,
|
||||
misc-misplaced-const,
|
||||
misc-new-delete-overloads,
|
||||
@@ -70,6 +69,3 @@ Checks:
|
||||
modernize-use-using,
|
||||
readability-braces-around-statements'
|
||||
FormatStyle: file
|
||||
CheckOptions:
|
||||
- key: bugprone-dangling-handle
|
||||
value: 'wpi::StringRef;wpi::Twine'
|
||||
|
||||
57
.github/workflows/cmake.yml
vendored
@@ -2,6 +2,10 @@ name: CMake
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
@@ -10,56 +14,43 @@ jobs:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
name: Linux
|
||||
container: wpilib/roborio-cross-ubuntu:2022-20.04
|
||||
container: wpilib/roborio-cross-ubuntu:2023-22.04
|
||||
flags: ""
|
||||
- os: macOS-11
|
||||
name: macOS
|
||||
container: ""
|
||||
flags: "-DWITH_JAVA=OFF"
|
||||
|
||||
name: "Build - ${{ matrix.name }}"
|
||||
runs-on: ${{ matrix.os }}
|
||||
container: ${{ matrix.container }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "macOS" ]; then
|
||||
brew install opencv
|
||||
fi
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install dependencies (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv4.5-java python-is-python3
|
||||
|
||||
- name: Install opencv (macOS)
|
||||
run: brew install opencv
|
||||
if: runner.os == 'macOS'
|
||||
|
||||
- name: Set up Python 3.8 (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.8
|
||||
|
||||
- name: Install jinja
|
||||
run: python -m pip install jinja2
|
||||
|
||||
- name: configure
|
||||
run: mkdir build && cd build && cmake ${{ matrix.flags }} ..
|
||||
|
||||
- name: build
|
||||
working-directory: build
|
||||
run: cmake --build . -j$(nproc)
|
||||
run: cmake --build . --parallel $(nproc)
|
||||
|
||||
- name: test
|
||||
working-directory: build
|
||||
run: ctest --output-on-failure
|
||||
|
||||
build-vcpkg:
|
||||
name: "Build - Windows"
|
||||
runs-on: windows-2019
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Prepare vcpkg
|
||||
uses: lukka/run-vcpkg@v7
|
||||
with:
|
||||
vcpkgArguments: opencv
|
||||
vcpkgDirectory: ${{ runner.workspace }}/vcpkg
|
||||
vcpkgTriplet: x64-windows
|
||||
vcpkgGitCommitId: d781bd9ca77ac3dc2f13d88169021d48459c665f # HEAD on 2021-07-25
|
||||
- name: Configure & Build
|
||||
uses: lukka/run-cmake@v3
|
||||
with:
|
||||
buildDirectory: ${{ runner.workspace }}/build
|
||||
cmakeAppendedArgs: -DWITH_JAVA=OFF
|
||||
cmakeListsOrSettingsJson: CMakeListsTxtAdvanced
|
||||
useVcpkgToolchainFile: true
|
||||
- name: Run Tests
|
||||
run: ctest -C "Debug" --output-on-failure
|
||||
working-directory: ${{ runner.workspace }}/build
|
||||
|
||||
57
.github/workflows/comment-command.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
name: Comment Commands
|
||||
on:
|
||||
issue_comment:
|
||||
types: [ created ]
|
||||
|
||||
jobs:
|
||||
wpiformat:
|
||||
if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/wpiformat')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: React Rocket
|
||||
uses: actions/github-script@v4
|
||||
with:
|
||||
script: |
|
||||
const {owner, repo} = context.issue
|
||||
github.reactions.createForIssueComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: context.payload.comment.id,
|
||||
content: "rocket",
|
||||
});
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.COMMENT_COMMAND_PAT_TOKEN }}
|
||||
- name: Fetch all history and metadata
|
||||
run: |
|
||||
git fetch --prune --unshallow
|
||||
git checkout -b pr
|
||||
git branch -f main origin/main
|
||||
- name: Checkout PR
|
||||
run: |
|
||||
gh pr checkout $NUMBER
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.COMMENT_COMMAND_PAT_TOKEN }}"
|
||||
NUMBER: ${{ github.event.issue.number }}
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Install clang-format
|
||||
run: |
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
||||
sudo sh -c "echo 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-14 main' >> /etc/apt/sources.list.d/proposed-repositories.list"
|
||||
sudo apt-get update -q
|
||||
sudo apt-get install -y clang-format-14
|
||||
- name: Install wpiformat
|
||||
run: pip3 install wpiformat
|
||||
- name: Run wpiformat
|
||||
run: wpiformat -clang 14
|
||||
- name: Commit
|
||||
run: |
|
||||
# Set credentials
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
# Commit
|
||||
git commit -am "wpiformat"
|
||||
git push
|
||||
12
.github/workflows/documentation.yml
vendored
@@ -2,6 +2,10 @@ name: Documentation
|
||||
|
||||
on: [push, workflow_dispatch]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
BASE_PATH: allwpilib/docs
|
||||
|
||||
@@ -10,13 +14,15 @@ jobs:
|
||||
name: "Documentation - Publish"
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'wpilibsuite' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||||
concurrency: ci-docs-publish
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-java@v1
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 13
|
||||
- name: Install libclang-9
|
||||
run: sudo apt update && sudo apt install -y libclang-cpp9 libclang1-9
|
||||
@@ -37,7 +43,7 @@ jobs:
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew docs:generateJavaDocs docs:doxygen -PbuildServer ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
- name: Install SSH Client 🔑
|
||||
uses: webfactory/ssh-agent@v0.4.1
|
||||
uses: webfactory/ssh-agent@v0.7.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.GH_DEPLOY_KEY }}
|
||||
- name: Deploy Java 🚀
|
||||
|
||||
8
.github/workflows/gazebo.yml
vendored
@@ -2,13 +2,17 @@ name: Gazebo
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: "Build"
|
||||
runs-on: ubuntu-latest
|
||||
container: wpilib/gazebo-ubuntu:18.04
|
||||
container: wpilib/gazebo-ubuntu:22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Build with Gradle
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
name: "Validate Gradle Wrapper"
|
||||
on: [pull_request, push]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
validation:
|
||||
name: "Validation"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
|
||||
95
.github/workflows/gradle.yml
vendored
@@ -2,37 +2,44 @@ name: Gradle
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-docker:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- container: wpilib/roborio-cross-ubuntu:2022-18.04
|
||||
- container: wpilib/roborio-cross-ubuntu:2023-22.04
|
||||
artifact-name: Athena
|
||||
build-options: "-Ponlylinuxathena"
|
||||
- container: wpilib/raspbian-cross-ubuntu:10-18.04
|
||||
artifact-name: Raspbian
|
||||
build-options: "-Ponlylinuxraspbian"
|
||||
- container: wpilib/aarch64-cross-ubuntu:bionic-18.04
|
||||
artifact-name: Aarch64
|
||||
build-options: "-Ponlylinuxaarch64bionic"
|
||||
- container: wpilib/ubuntu-base:18.04
|
||||
- container: wpilib/raspbian-cross-ubuntu:bullseye-22.04
|
||||
artifact-name: Arm32
|
||||
build-options: "-Ponlylinuxarm32"
|
||||
- container: wpilib/aarch64-cross-ubuntu:bullseye-22.04
|
||||
artifact-name: Arm64
|
||||
build-options: "-Ponlylinuxarm64"
|
||||
- container: wpilib/ubuntu-base:22.04
|
||||
artifact-name: Linux
|
||||
build-options: "-Dorg.gradle.jvmargs=-Xmx2g"
|
||||
build-options: "-Ponlylinuxx86-64"
|
||||
name: "Build - ${{ matrix.artifact-name }}"
|
||||
runs-on: ubuntu-latest
|
||||
container: ${{ matrix.container }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set release environment variable
|
||||
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build -PbuildServer -PskipJavaFormat ${{ matrix.build-options }} ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
run: ./gradlew build --build-cache -PbuildServer -PskipJavaFormat ${{ matrix.build-options }} ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
env:
|
||||
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.artifact-name }}
|
||||
path: build/allOutputs
|
||||
@@ -44,23 +51,38 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-2019
|
||||
artifact-name: Win64
|
||||
- os: windows-2022
|
||||
artifact-name: Win64Debug
|
||||
architecture: x64
|
||||
- os: windows-2019
|
||||
artifact-name: Win32
|
||||
architecture: x86
|
||||
task: "build"
|
||||
build-options: "-PciDebugOnly --max-workers 1"
|
||||
outputs: "build/allOutputs"
|
||||
- os: windows-2022
|
||||
artifact-name: Win64Release
|
||||
architecture: x64
|
||||
build-options: "-PciReleaseOnly --max-workers 1"
|
||||
task: "copyAllOutputs"
|
||||
outputs: "build/allOutputs"
|
||||
- os: macOS-11
|
||||
artifact-name: macOS
|
||||
architecture: x64
|
||||
build-options: "-Pbuildalldesktop"
|
||||
task: "build"
|
||||
outputs: "build/allOutputs"
|
||||
- os: windows-2022
|
||||
artifact-name: Win32
|
||||
architecture: x86
|
||||
task: ":ntcoreffi:build"
|
||||
outputs: "ntcoreffi/build/outputs"
|
||||
name: "Build - ${{ matrix.artifact-name }}"
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v1
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 11
|
||||
architecture: ${{ matrix.architecture }}
|
||||
- name: Import Developer ID Certificate
|
||||
@@ -81,27 +103,34 @@ jobs:
|
||||
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
- name: Set Java Heap Size
|
||||
run: sed -i 's/-Xmx2g/-Xmx1g/g' gradle.properties
|
||||
if: matrix.artifact-name == 'Win32'
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build -PbuildServer -PskipJavaFormat ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
run: ./gradlew ${{ matrix.task }} --build-cache -PbuildServer -PskipJavaFormat ${{ matrix.build-options }} ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
env:
|
||||
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
- name: Sign Libraries with Developer ID
|
||||
run: ./gradlew build -PbuildServer -PskipJavaFormat -PdeveloperID=${{ secrets.APPLE_DEVELOPER_ID }} ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
run: ./gradlew build -PbuildServer -PskipJavaFormat -PdeveloperID=${{ secrets.APPLE_DEVELOPER_ID }} ${{ matrix.build-options }} ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
if: |
|
||||
matrix.artifact-name == 'macOS' && (github.repository_owner == 'wpilibsuite' &&
|
||||
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')))
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.artifact-name }}
|
||||
path: build/allOutputs
|
||||
path: ${{ matrix.outputs }}
|
||||
|
||||
build-documentation:
|
||||
name: "Build - Documentation"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v1
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 13
|
||||
- name: Install libclang-9
|
||||
run: sudo apt update && sudo apt install -y libclang-cpp9 libclang1-9
|
||||
@@ -109,8 +138,11 @@ jobs:
|
||||
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew docs:zipDocs -PbuildServer ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
run: ./gradlew docs:zipDocs --build-cache -PbuildServer ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
env:
|
||||
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Documentation
|
||||
path: docs/build/outputs
|
||||
@@ -120,10 +152,10 @@ jobs:
|
||||
needs: [build-docker, build-host, build-documentation]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: wpilibsuite/build-tools
|
||||
- uses: actions/download-artifact@v2
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: combiner/products/build/allOutputs
|
||||
- name: Flatten Artifacts
|
||||
@@ -132,8 +164,9 @@ jobs:
|
||||
run: |
|
||||
cat combiner/products/build/allOutputs/version.txt
|
||||
test -s combiner/products/build/allOutputs/version.txt
|
||||
- uses: actions/setup-java@v1
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 11
|
||||
- name: Combine
|
||||
if: |
|
||||
@@ -158,7 +191,7 @@ jobs:
|
||||
RUN_AZURE_ARTIFACTORY_RELEASE: "TRUE"
|
||||
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Maven
|
||||
path: ~/releases
|
||||
|
||||
58
.github/workflows/lint-format.yml
vendored
@@ -6,61 +6,76 @@ on:
|
||||
branches-ignore:
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
wpiformat:
|
||||
name: "wpiformat"
|
||||
runs-on: ubuntu-latest
|
||||
container: wpilib/roborio-cross-ubuntu:2022-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Fetch all history and metadata
|
||||
run: |
|
||||
git config --global --add safe.directory /__w/allwpilib/allwpilib
|
||||
git fetch --prune --unshallow
|
||||
git checkout -b pr
|
||||
git branch -f main origin/main
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Install clang-format
|
||||
run: |
|
||||
sudo sh -c "echo 'deb http://archive.ubuntu.com/ubuntu/ $(lsb_release -cs)-proposed restricted main multiverse universe' >> /etc/apt/sources.list.d/proposed-repositories.list"
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
||||
sudo sh -c "echo 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-14 main' >> /etc/apt/sources.list.d/proposed-repositories.list"
|
||||
sudo apt-get update -q
|
||||
sudo apt-get install -y clang-format-12
|
||||
sudo apt-get install -y clang-format-14
|
||||
- name: Install wpiformat
|
||||
run: pip3 install wpiformat
|
||||
- name: Run
|
||||
run: wpiformat -clang 12
|
||||
run: wpiformat -clang 14
|
||||
- name: Check output
|
||||
run: git --no-pager diff --exit-code HEAD
|
||||
- name: Generate diff
|
||||
run: git diff HEAD > wpiformat-fixes.patch
|
||||
if: ${{ failure() }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wpiformat fixes
|
||||
path: wpiformat-fixes.patch
|
||||
if: ${{ failure() }}
|
||||
- name: Write to job summary
|
||||
run: |
|
||||
echo '```diff' >> $GITHUB_STEP_SUMMARY
|
||||
cat wpiformat-fixes.patch >> $GITHUB_STEP_SUMMARY
|
||||
echo '' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
if: ${{ failure() }}
|
||||
|
||||
tidy:
|
||||
name: "clang-tidy"
|
||||
runs-on: ubuntu-latest
|
||||
container: wpilib/roborio-cross-ubuntu:2022-20.04
|
||||
container: wpilib/roborio-cross-ubuntu:2023-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Fetch all history and metadata
|
||||
run: |
|
||||
git config --global --add safe.directory /__w/allwpilib/allwpilib
|
||||
git fetch --prune --unshallow
|
||||
git checkout -b pr
|
||||
git branch -f main origin/main
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Install clang-tidy
|
||||
run: |
|
||||
sudo sh -c "echo 'deb http://archive.ubuntu.com/ubuntu/ $(lsb_release -cs)-proposed restricted main multiverse universe' >> /etc/apt/sources.list.d/proposed-repositories.list"
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
||||
sudo sh -c "echo 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-14 main' >> /etc/apt/sources.list.d/proposed-repositories.list"
|
||||
sudo apt-get update -q
|
||||
sudo apt-get install -y clang-tidy-12 clang-format-12
|
||||
sudo apt-get install -y clang-tidy-14 clang-format-14
|
||||
- name: Install wpiformat
|
||||
run: pip3 install wpiformat
|
||||
- name: Create compile_commands.json
|
||||
@@ -68,15 +83,19 @@ jobs:
|
||||
- name: List changed files
|
||||
run: wpiformat -list-changed-files
|
||||
- name: Run clang-tidy
|
||||
run: wpiformat -clang 12 -no-format -tidy-changed -compile-commands=build/compile_commands/linuxx86-64 -vv
|
||||
run: wpiformat -clang 14 -no-format -tidy-changed -compile-commands=build/compile_commands/linuxx86-64 -vv
|
||||
javaformat:
|
||||
name: "Java format"
|
||||
runs-on: ubuntu-latest
|
||||
container: wpilib/ubuntu-base:18.04
|
||||
container: wpilib/ubuntu-base:22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/checkout@v3
|
||||
- name: Fetch all history and metadata
|
||||
run: |
|
||||
git config --global --add safe.directory /__w/allwpilib/allwpilib
|
||||
git fetch --prune --unshallow
|
||||
git checkout -b pr
|
||||
git branch -f main origin/main
|
||||
- name: Run Java format
|
||||
run: ./gradlew javaFormat spotbugsMain spotbugsTest spotbugsDev
|
||||
- name: Check output
|
||||
@@ -88,11 +107,12 @@ jobs:
|
||||
name: "Documentation"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v1
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 13
|
||||
- name: Install libclang-9
|
||||
run: sudo apt update && sudo apt install -y libclang-cpp9 libclang1-9
|
||||
|
||||
33
.github/workflows/sanitizers.yml
vendored
@@ -2,6 +2,10 @@ name: Sanitizers
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
@@ -11,39 +15,34 @@ jobs:
|
||||
- name: asan
|
||||
cmake-flags: "-DCMAKE_BUILD_TYPE=Asan"
|
||||
ctest-env: ""
|
||||
ctest-flags: "-E 'wpiutil|ntcore|wpilibc'"
|
||||
ctest-flags: "-E 'wpilibc'"
|
||||
- name: tsan
|
||||
cmake-flags: "-DCMAKE_BUILD_TYPE=Tsan"
|
||||
ctest-env: "TSAN_OPTIONS=second_deadlock_stack=1"
|
||||
ctest-flags: "-E 'ntcore|cscore|cameraserver|wpilibc|wpilibNewCommands'"
|
||||
ctest-flags: "-E 'cscore|cameraserver|wpilibc|wpilibNewCommands'"
|
||||
- name: ubsan
|
||||
cmake-flags: "-DCMAKE_BUILD_TYPE=Ubsan"
|
||||
ctest-env: ""
|
||||
ctest-flags: ""
|
||||
name: "${{ matrix.name }}"
|
||||
runs-on: ubuntu-latest
|
||||
container: wpilib/roborio-cross-ubuntu:2022-20.04
|
||||
container: wpilib/roborio-cross-ubuntu:2023-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
|
||||
sudo apt install -y gcc-11 g++-11
|
||||
sudo update-alternatives \
|
||||
--install /usr/bin/gcc gcc /usr/bin/gcc-11 11 \
|
||||
--slave /usr/bin/g++ g++ /usr/bin/g++-11
|
||||
sudo update-alternatives --set gcc /usr/bin/gcc-11
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv4.5-java python-is-python3 clang-14
|
||||
|
||||
- name: Install jinja
|
||||
run: python -m pip install jinja2
|
||||
|
||||
- name: configure
|
||||
run: mkdir build && cd build && cmake ${{ matrix.cmake-flags }} ..
|
||||
run: mkdir build && cd build && cmake -DCMAKE_C_COMPILER:FILEPATH=/usr/bin/clang-14 -DCMAKE_CXX_COMPILER:FILEPATH=/usr/bin/clang++-14 ${{ matrix.cmake-flags }} ..
|
||||
|
||||
- name: build
|
||||
working-directory: build
|
||||
run: cmake --build . -j$(nproc)
|
||||
run: cmake --build . --parallel $(nproc)
|
||||
|
||||
- name: test
|
||||
working-directory: build
|
||||
run: ${{ matrix.ctest-env }} ctest --output-on-failure ${{ matrix.ctest-flags }}
|
||||
|
||||
63
.github/workflows/upstream-utils.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
name: Upstream utils
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches-ignore:
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
update:
|
||||
name: "Update"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Fetch all history and metadata
|
||||
run: |
|
||||
git fetch --prune --unshallow
|
||||
git checkout -b pr
|
||||
git branch -f main origin/main
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Configure committer identity
|
||||
run: |
|
||||
git config --global user.email "you@example.com"
|
||||
git config --global user.name "Your Name"
|
||||
- name: Run update_drake.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./update_drake.py
|
||||
- name: Run update_eigen.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./update_eigen.py
|
||||
- name: Run update_fmt.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./update_fmt.py
|
||||
- name: Run update_libuv.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./update_libuv.py
|
||||
- name: Run update_llvm.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./update_llvm.py
|
||||
- name: Run update_stack_walker.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./update_stack_walker.py
|
||||
- name: Run update_memory.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./update_memory.py
|
||||
- name: Add untracked files to index so they count as changes
|
||||
run: git add -A
|
||||
- name: Check output
|
||||
run: git --no-pager diff --exit-code HEAD
|
||||
@@ -44,4 +44,5 @@ includeOtherLibs {
|
||||
^wpi/
|
||||
^wpigui
|
||||
^wpimath/
|
||||
^wpinet/
|
||||
}
|
||||
|
||||
@@ -36,25 +36,25 @@ SET(CMAKE_SKIP_BUILD_RPATH FALSE)
|
||||
# (but later on when installing)
|
||||
SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
|
||||
|
||||
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/wpilib/lib")
|
||||
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
|
||||
|
||||
# add the automatically determined parts of the RPATH
|
||||
# which point to directories outside the build tree to the install RPATH
|
||||
SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
|
||||
|
||||
# the RPATH to be used when installing, but only if it's not a system directory
|
||||
LIST(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/wpilib/lib" isSystemDir)
|
||||
LIST(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" isSystemDir)
|
||||
IF("${isSystemDir}" STREQUAL "-1")
|
||||
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/wpilib/lib")
|
||||
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
|
||||
ENDIF("${isSystemDir}" STREQUAL "-1")
|
||||
|
||||
# Options for building certain parts of the repo. Everything is built by default.
|
||||
option(BUILD_SHARED_LIBS "Build with shared libs (needed for JNI)" ON)
|
||||
option(WITH_JAVA "Include java and JNI in the build" ON)
|
||||
option(WITH_CSCORE "Build cscore (needs OpenCV)" ON)
|
||||
option(WITH_NTCORE "Build ntcore" ON)
|
||||
option(WITH_WPIMATH "Build wpimath" ON)
|
||||
option(WITH_WPILIB "Build hal, wpilibc/j, and myRobot (needs OpenCV)" ON)
|
||||
option(WITH_OLD_COMMANDS "Build old commands" OFF)
|
||||
option(WITH_EXAMPLES "Build examples" OFF)
|
||||
option(WITH_TESTS "Build unit tests (requires internet connection)" ON)
|
||||
option(WITH_GUI "Build GUI items" ON)
|
||||
@@ -64,10 +64,10 @@ option(WITH_SIMULATION_MODULES "Build simulation modules" ON)
|
||||
option(WITH_EXTERNAL_HAL "Use a separately built HAL" OFF)
|
||||
set(EXTERNAL_HAL_FILE "" CACHE FILEPATH "Location to look for an external HAL CMake File")
|
||||
|
||||
# Options for using a package manager (vcpkg) for certain dependencies.
|
||||
option(USE_VCPKG_FMTLIB "Use vcpkg fmtlib" OFF)
|
||||
option(USE_VCPKG_LIBUV "Use vcpkg libuv" OFF)
|
||||
option(USE_VCPKG_EIGEN "Use vcpkg eigen" OFF)
|
||||
# Options for using a package manager (e.g., vcpkg) for certain dependencies.
|
||||
option(USE_SYSTEM_FMTLIB "Use system fmtlib" OFF)
|
||||
option(USE_SYSTEM_LIBUV "Use system libuv" OFF)
|
||||
option(USE_SYSTEM_EIGEN "Use system eigen" OFF)
|
||||
|
||||
# Options for installation.
|
||||
option(WITH_FLAT_INSTALL "Use a flat install directory" OFF)
|
||||
@@ -118,6 +118,34 @@ FATAL: Cannot build simulation modules with wpilib disabled.
|
||||
")
|
||||
endif()
|
||||
|
||||
if (NOT WITH_NTCORE AND WITH_CSCORE)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build cameraserver without ntcore.
|
||||
Enable ntcore by setting WITH_NTCORE=ON
|
||||
")
|
||||
endif()
|
||||
|
||||
if (NOT WITH_NTCORE AND WITH_GUI)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build GUI modules without ntcore.
|
||||
Enable ntcore by setting WITH_NTCORE=ON
|
||||
")
|
||||
endif()
|
||||
|
||||
if (NOT WITH_NTCORE AND WITH_SIMULATION_MODULES)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build simulation modules without ntcore.
|
||||
Enable ntcore by setting WITH_NTCORE=ON
|
||||
")
|
||||
endif()
|
||||
|
||||
if (NOT WITH_NTCORE AND WITH_WPILIB)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build wpilib without ntcore.
|
||||
Enable ntcore by setting WITH_NTCORE=ON
|
||||
")
|
||||
endif()
|
||||
|
||||
if (NOT WITH_WPIMATH AND WITH_WPILIB)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build wpilib without wpimath.
|
||||
@@ -125,11 +153,11 @@ FATAL: Cannot build wpilib without wpimath.
|
||||
")
|
||||
endif()
|
||||
|
||||
set( wpilib_dest wpilib)
|
||||
set( include_dest wpilib/include )
|
||||
set( main_lib_dest wpilib/lib )
|
||||
set( java_lib_dest wpilib/java )
|
||||
set( jni_lib_dest wpilib/jni )
|
||||
set( wpilib_dest "")
|
||||
set( include_dest include )
|
||||
set( main_lib_dest lib )
|
||||
set( java_lib_dest java )
|
||||
set( jni_lib_dest jni )
|
||||
|
||||
if (WITH_FLAT_INSTALL)
|
||||
set (wpilib_config_dir ${wpilib_dest})
|
||||
@@ -137,18 +165,22 @@ else()
|
||||
set (wpilib_config_dir share/wpilib)
|
||||
endif()
|
||||
|
||||
if (USE_VCPKG_LIBUV)
|
||||
set (LIBUV_VCPKG_REPLACE "find_package(unofficial-libuv CONFIG)")
|
||||
if (USE_SYSTEM_LIBUV)
|
||||
set (LIBUV_SYSTEM_REPLACE "
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(libuv REQUIRED IMPORTED_TARGET libuv)
|
||||
")
|
||||
endif()
|
||||
|
||||
if (USE_VCPKG_EIGEN)
|
||||
set (EIGEN_VCPKG_REPLACE "find_package(Eigen3 CONFIG)")
|
||||
if (USE_SYSTEM_EIGEN)
|
||||
set (EIGEN_SYSTEM_REPLACE "find_package(Eigen3 CONFIG)")
|
||||
endif()
|
||||
|
||||
find_package(LIBSSH 0.7.1)
|
||||
|
||||
if (WITH_FLAT_INSTALL)
|
||||
set(WPIUTIL_DEP_REPLACE "include($\{SELF_DIR\}/wpiutil-config.cmake)")
|
||||
set(WPINET_DEP_REPLACE "include($\{SELF_DIR\}/wpinet-config.cmake)")
|
||||
set(NTCORE_DEP_REPLACE "include($\{SELF_DIR\}/ntcore-config.cmake)")
|
||||
set(CSCORE_DEP_REPLACE_IMPL "include(\${SELF_DIR}/cscore-config.cmake)")
|
||||
set(CAMERASERVER_DEP_REPLACE_IMPL "include(\${SELF_DIR}/cameraserver-config.cmake)")
|
||||
@@ -156,9 +188,9 @@ set(HAL_DEP_REPLACE_IMPL "include(\${SELF_DIR}/hal-config.cmake)")
|
||||
set(WPIMATH_DEP_REPLACE "include($\{SELF_DIR\}/wpimath-config.cmake)")
|
||||
set(WPILIBC_DEP_REPLACE_IMPL "include(\${SELF_DIR}/wpilibc-config.cmake)")
|
||||
set(WPILIBNEWCOMMANDS_DEP_REPLACE "include(\${SELF_DIR}/wpilibNewcommands-config.cmake)")
|
||||
set(WPILIBOLDCOMMANDS_DEP_REPLACE "include(\${SELF_DIR}/wpilibOldcommands-config.cmake)")
|
||||
else()
|
||||
set(WPIUTIL_DEP_REPLACE "find_dependency(wpiutil)")
|
||||
set(WPINET_DEP_REPLACE "find_dependency(wpinet)")
|
||||
set(NTCORE_DEP_REPLACE "find_dependency(ntcore)")
|
||||
set(CSCORE_DEP_REPLACE_IMPL "find_dependency(cscore)")
|
||||
set(CAMERASERVER_DEP_REPLACE_IMPL "find_dependency(cameraserver)")
|
||||
@@ -166,7 +198,6 @@ set(HAL_DEP_REPLACE_IMPL "find_dependency(hal)")
|
||||
set(WPIMATH_DEP_REPLACE "find_dependency(wpimath)")
|
||||
set(WPILIBC_DEP_REPLACE_IMPL "find_dependency(wpilibc)")
|
||||
set(WPILIBNEWCOMMANDS_DEP_REPLACE "find_dependency(wpilibNewCommands)")
|
||||
set(WPILIBOLDCOMMANDS_DEP_REPLACE "find_dependency(wpilibOldCommands)")
|
||||
endif()
|
||||
|
||||
set(FILENAME_DEP_REPLACE "get_filename_component(SELF_DIR \"$\{CMAKE_CURRENT_LIST_FILE\}\" PATH)")
|
||||
@@ -248,7 +279,11 @@ if (WITH_TESTS)
|
||||
endif()
|
||||
|
||||
add_subdirectory(wpiutil)
|
||||
add_subdirectory(ntcore)
|
||||
|
||||
if (WITH_NTCORE)
|
||||
add_subdirectory(wpinet)
|
||||
add_subdirectory(ntcore)
|
||||
endif()
|
||||
|
||||
if (WITH_WPIMATH)
|
||||
add_subdirectory(wpimath)
|
||||
@@ -262,6 +297,7 @@ if (WITH_GUI)
|
||||
add_subdirectory(outlineviewer)
|
||||
if (LIBSSH_FOUND)
|
||||
add_subdirectory(roborioteamnumbersetter)
|
||||
add_subdirectory(datalogtool)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -282,9 +318,6 @@ if (WITH_WPILIB)
|
||||
add_subdirectory(wpilibj)
|
||||
add_subdirectory(wpilibc)
|
||||
add_subdirectory(wpilibNewCommands)
|
||||
if (WITH_OLD_COMMANDS)
|
||||
add_subdirectory(wpilibOldCommands)
|
||||
endif()
|
||||
if (WITH_EXAMPLES)
|
||||
add_subdirectory(wpilibcExamples)
|
||||
endif()
|
||||
|
||||
@@ -37,8 +37,7 @@ So you want to contribute your changes back to WPILib. Great! We have a few cont
|
||||
|
||||
## Coding Guidelines
|
||||
|
||||
WPILib uses modified Google style guides for both C++ and Java, which can be found in the [styleguide repository](https://github.com/wpilibsuite/styleguide). Autoformatters are available for many popular editors at https://github.com/google/styleguide. Running wpiformat is required for all contributions and is enforced by our continuous integration system. We currently use clang-format 10.0 with wpiformat.
|
||||
|
||||
WPILib uses modified Google style guides for both C++ and Java, which can be found in the [styleguide repository](https://github.com/wpilibsuite/styleguide). Autoformatters are available for many popular editors at https://github.com/google/styleguide. Running wpiformat is required for all contributions and is enforced by our continuous integration system.
|
||||
While the library should be fully formatted according to the styles, additional elements of the style guide were not followed when the library was initially created. All new code should follow the guidelines. If you are looking for some easy ramp-up tasks, finding areas that don't follow the style guide and fixing them is very welcome.
|
||||
|
||||
When writing math expressions in documentation, use https://www.unicodeit.net/ to convert LaTeX to a Unicode equivalent that's easier to read. Not all expressions will translate (e.g., superscripts of superscripts) so focus on making it readable by someone who isn't familiar with LaTeX. If content on multiple lines needs to be aligned in Doxygen/Javadoc comments (e.g., integration/summation limits, matrices packed with square brackets and superscripts for them), put them in @verbatim/@endverbatim blocks in Doxygen or `<pre>` tags in Javadoc so they render with monospace font.
|
||||
|
||||
@@ -134,14 +134,6 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
|
||||
* wpimath
|
||||
* wpiutil
|
||||
|
||||
* wpilibOldCommands
|
||||
* wpilibc
|
||||
* hal
|
||||
* cameraserver
|
||||
* ntcore
|
||||
* cscore
|
||||
* wpiutil
|
||||
|
||||
|
||||
### Third Party Artifacts
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ This article contains instructions on building projects using a development buil
|
||||
|
||||
## Development Build
|
||||
|
||||
Development builds are the per-commit build hosted everytime a commit is pushed to the [allwpilib](https://github.com/wpilibsuite/allwpilib/) repository. These builds are then hosted on [artifactory](https://frcmaven.wpi.edu/artifactory/webapp/#/home).
|
||||
Development builds are the per-commit build hosted every time a commit is pushed to the [allwpilib](https://github.com/wpilibsuite/allwpilib/) repository. These builds are then hosted on [artifactory](https://frcmaven.wpi.edu/artifactory/webapp/#/home).
|
||||
|
||||
In order to build a project using a development build, find the build.gradle file and open it. Then, add the following code below the plugin section and replace YEAR with the year of the development version.
|
||||
In order to build a project using a development build, find the build.gradle file and open it. Then, add the following code below the plugin section and replace YEAR with the year of the development version. It is also necessary to use a 2023 GradleRIO version, ie `2023.0.0-alpha-1`
|
||||
|
||||
```groovy
|
||||
wpi.maven.useLocal = false
|
||||
@@ -23,13 +23,13 @@ Java
|
||||
```groovy
|
||||
plugins {
|
||||
id "java"
|
||||
id "edu.wpi.first.GradleRIO" version "2022.1.1"
|
||||
id "edu.wpi.first.GradleRIO" version "2023.0.0-alpha-1"
|
||||
}
|
||||
|
||||
wpi.maven.useLocal = false
|
||||
wpi.maven.useDevelopment = true
|
||||
wpi.versions.wpilibVersion = '2022.+'
|
||||
wpi.versions.wpimathVersion = '2022.+'
|
||||
wpi.versions.wpilibVersion = '2023.+'
|
||||
wpi.versions.wpimathVersion = '2023.+'
|
||||
```
|
||||
|
||||
C++
|
||||
@@ -37,13 +37,13 @@ C++
|
||||
plugins {
|
||||
id "cpp"
|
||||
id "google-test-test-suite"
|
||||
id "edu.wpi.first.GradleRIO" version "2022.1.1"
|
||||
id "edu.wpi.first.GradleRIO" version "2023.0.0-alpha-1"
|
||||
}
|
||||
|
||||
wpi.maven.useLocal = false
|
||||
wpi.maven.useDevelopment = true
|
||||
wpi.versions.wpilibVersion = '2022.+'
|
||||
wpi.versions.wpimathVersion = '2022.+'
|
||||
wpi.versions.wpilibVersion = '2023.+'
|
||||
wpi.versions.wpimathVersion = '2023.+'
|
||||
```
|
||||
|
||||
## Local Build
|
||||
@@ -54,7 +54,7 @@ Java
|
||||
```groovy
|
||||
plugins {
|
||||
id "java"
|
||||
id "edu.wpi.first.GradleRIO" version "2022.1.1"
|
||||
id "edu.wpi.first.GradleRIO" version "2023.0.0-alpha-1"
|
||||
}
|
||||
|
||||
wpi.maven.useLocal = false
|
||||
@@ -68,7 +68,7 @@ C++
|
||||
plugins {
|
||||
id "cpp"
|
||||
id "google-test-test-suite"
|
||||
id "edu.wpi.first.GradleRIO" version "2022.1.1"
|
||||
id "edu.wpi.first.GradleRIO" version "2023.0.0-alpha-1"
|
||||
}
|
||||
|
||||
wpi.maven.useLocal = false
|
||||
@@ -90,4 +90,4 @@ The following 3 tasks can be used for deployment:
|
||||
|
||||
Deploying any of these to the roboRIO will disable the current startup project until it is redeployed.
|
||||
|
||||
From here, ssh into the roboRIO using the `admin` account (`lvuser` will fail to run in many cases). In the admin home directory, a file for each deploy type will exist (`myRobotCpp`, `myRobotCppStatic` and `myRobotJavaRun`). These can be run to start up the corresponding project.
|
||||
From here, ssh into the roboRIO using the `lvuser` account and run `frcRunRobot.sh` (It's in path).
|
||||
|
||||
@@ -31,14 +31,20 @@ The following build options are available:
|
||||
* This option will enable Java and JNI builds. If this is on, `WITH_SHARED_LIBS` must be on. Otherwise CMake will error.
|
||||
* `WITH_SHARED_LIBS` (ON Default)
|
||||
* This option will cause cmake to build static libraries instead of shared libraries. If this is off, `WITH_JAVA` must be off. Otherwise CMake will error.
|
||||
* `WITH_TESTS` (ON Default)
|
||||
* This option will build C++ unit tests. These can be run via `make test`.
|
||||
* `WITH_CSCORE` (ON Default)
|
||||
* This option will cause cscore to be built. Turning this off will implicitly disable cameraserver, the hal and wpilib as well, irrespective of their specific options. If this is off, the OpenCV build requirement is removed.
|
||||
* `WITH_NTCORE` (ON Default)
|
||||
* This option will cause ntcore to be built. Turning this off will implicitly disable wpinet and wpilib as well, irrespective of their specific options.
|
||||
* `WITH_WPIMATH` (ON Default)
|
||||
* This option will build the wpimath library. This option must be on to build wpilib.
|
||||
* `WITH_WPILIB` (ON Default)
|
||||
* This option will build the hal and wpilibc/j during the build. The HAL is the simulator hal, unless the external hal options are used. The cmake build has no capability to build for the RoboRIO.
|
||||
* `WITH_EXAMPLES` (ON Default)
|
||||
* This option will build C++ examples.
|
||||
* `WITH_TESTS` (ON Default)
|
||||
* This option will build C++ unit tests. These can be run via `make test`.
|
||||
* `WITH_GUI` (ON Default)
|
||||
* This option will build GUI items.
|
||||
* `WITH_SIMULATION_MODULES` (ON Default)
|
||||
* This option will build simulation modules, including wpigui and the HALSim plugins.
|
||||
* `WITH_EXTERNAL_HAL` (OFF Default)
|
||||
|
||||
37
README.md
@@ -16,7 +16,7 @@ Welcome to the WPILib project. This repository contains the HAL, WPILibJ, and WP
|
||||
- [Using Development Builds](#using-development-builds)
|
||||
- [Custom toolchain location](#custom-toolchain-location)
|
||||
- [Gazebo simulation](#gazebo-simulation)
|
||||
- [Formatting/linting with wpiformat](#formattinglinting-with-wpiformat)
|
||||
- [Formatting/Linting](#formattinglinting)
|
||||
- [CMake](#cmake)
|
||||
- [Publishing](#publishing)
|
||||
- [Structure and Organization](#structure-and-organization)
|
||||
@@ -26,6 +26,15 @@ Welcome to the WPILib project. This repository contains the HAL, WPILibJ, and WP
|
||||
|
||||
The WPILib Mission is to enable FIRST Robotics teams to focus on writing game-specific software rather than focusing on hardware details - "raise the floor, don't lower the ceiling". We work to enable teams with limited programming knowledge and/or mentor experience to be as successful as possible, while not hampering the abilities of teams with more advanced programming capabilities. We support Kit of Parts control system components directly in the library. We also strive to keep parity between major features of each language (Java, C++, and NI's LabVIEW), so that teams aren't at a disadvantage for choosing a specific programming language. WPILib is an open source project, licensed under the BSD 3-clause license. You can find a copy of the license [here](LICENSE.md).
|
||||
|
||||
# Quick Start
|
||||
|
||||
Below is a list of instructions that guide you through cloning, building, publishing and using local allwpilib binaries in a robot project. This quick start is not intended as a replacement for the information further listed in this document.
|
||||
|
||||
1. Clone the repository with `git clone https://github.com/wpilibsuite/allwpilib.git`
|
||||
2. Build the repository with `./gradlew build` or `./gradlew build --build-cache` if you have an internet connection
|
||||
3. Publish the artifacts locally by running `./gradlew publish`
|
||||
4. [Update your](OtherVersions.md) `build.gradle` [to use the artifacts](OtherVersions.md)
|
||||
|
||||
# Building WPILib
|
||||
|
||||
Using Gradle makes building WPILib very straightforward. It only has a few dependencies on outside tools, such as the ARM cross compiler for creating roboRIO binaries.
|
||||
@@ -38,20 +47,22 @@ Using Gradle makes building WPILib very straightforward. It only has a few depen
|
||||
- On Windows, install the JDK 11 .msi from the link above
|
||||
- On macOS, install the JDK 11 .pkg from the link above
|
||||
- C++ compiler
|
||||
- On Linux, install GCC 8 or greater
|
||||
- On Windows, install [Visual Studio Community 2022 or 2019](https://visualstudio.microsoft.com/vs/community/) and select the C++ programming language during installation (Gradle can't use the build tools for Visual Studio)
|
||||
- On Linux, install GCC 11 or greater
|
||||
- On Windows, install [Visual Studio Community 2022](https://visualstudio.microsoft.com/vs/community/) and select the C++ programming language during installation (Gradle can't use the build tools for Visual Studio)
|
||||
- On macOS, install the Xcode command-line build tools via `xcode-select --install`
|
||||
- ARM compiler toolchain
|
||||
- Run `./gradlew installRoboRioToolchain` after cloning this repository
|
||||
- If the WPILib installer was used, this toolchain is already installed
|
||||
- Raspberry Pi toolchain (optional)
|
||||
- Run `./gradlew installRaspbianToolchain` after cloning this repository
|
||||
- Run `./gradlew installArm32Toolchain` after cloning this repository
|
||||
|
||||
On macOS ARM, run `softwareupdate --install-rosetta`. This is necessary to be able to use the macOS x86 roboRIO toolchain on ARM.
|
||||
|
||||
## Setup
|
||||
|
||||
Clone the WPILib repository and follow the instructions above for installing any required tooling.
|
||||
|
||||
See the [styleguide README](https://github.com/wpilibsuite/styleguide/blob/main/README.md) for wpiformat setup instructions.
|
||||
See the [styleguide README](https://github.com/wpilibsuite/styleguide/blob/main/README.md) for wpiformat setup instructions. We use clang-format 14.
|
||||
|
||||
## Building
|
||||
|
||||
@@ -79,10 +90,18 @@ If opening from a fresh clone, generated java dependencies will not exist. Most
|
||||
|
||||
`./gradlew testDesktopCpp` and `./gradlew testDesktopJava` will build and run the tests for `wpilibc` and `wpilibj` respectively. They will only build the minimum components required to run the tests.
|
||||
|
||||
`testDesktopCpp` and `testDesktopJava` tasks also exist for the projects `wpiutil`, `ntcore`, `cscore`, `hal` `wpilibOldCommands`, `wpilibNewCommands` and `cameraserver`. These can be ran with `./gradlew :projectName:task`.
|
||||
`testDesktopCpp` and `testDesktopJava` tasks also exist for the projects `wpiutil`, `ntcore`, `cscore`, `hal` `wpilibNewCommands` and `cameraserver`. These can be ran with `./gradlew :projectName:task`.
|
||||
|
||||
`./gradlew buildDesktopCpp` and `./gradlew buildDesktopJava` will compile `wpilibcExamples` and `wpilibjExamples` respectively. The results can't be ran, but they can compile.
|
||||
|
||||
### Build Cache
|
||||
|
||||
Run with `--build-cache` on the command-line to use the shared [build cache](https://docs.gradle.org/current/userguide/build_cache.html) artifacts generated by the continuous integration server. Example:
|
||||
|
||||
```bash
|
||||
./gradlew build --build-cache
|
||||
```
|
||||
|
||||
### Using Development Builds
|
||||
|
||||
Please read the documentation available [here](OtherVersions.md)
|
||||
@@ -118,7 +137,9 @@ make
|
||||
|
||||
#### wpiformat
|
||||
|
||||
wpiformat can be executed anywhere in the repository via `py -3 -m wpiformat` on Windows or `python3 -m wpiformat` on other platforms.
|
||||
wpiformat can be executed anywhere in the repository via `py -3 -m wpiformat -clang 14` on Windows or `python3 -m wpiformat -clang 14` on other platforms.
|
||||
|
||||
Once a PR has been submitted, formatting can be run in CI by commenting `/wpiformat` on the PR. A new commit will be pushed with the formatting changes.
|
||||
|
||||
#### Java Code Quality Tools
|
||||
|
||||
@@ -149,7 +170,7 @@ The Simulation directory contains extra simulation tools and libraries, such as
|
||||
|
||||
The integration test directories for C++ and Java contain test code that runs on our test-system. When you submit code for review, it is tested by those programs. If you add new functionality you should make sure to write tests for it so we don't break it in the future.
|
||||
|
||||
The hal directory contains more C++ code meant to run on the roboRIO. HAL is an acronym for "Hardware Abstraction Layer", and it interfaces with the NI Libraries. The NI Libraries contain the low-level code for controlling devices on your robot. The NI Libraries are found in the ni-libraries folder.
|
||||
The hal directory contains more C++ code meant to run on the roboRIO. HAL is an acronym for "Hardware Abstraction Layer", and it interfaces with the NI Libraries. The NI Libraries contain the low-level code for controlling devices on your robot. The NI Libraries are found in the [ni-libraries](https://github.com/wpilibsuite/ni-libraries) project.
|
||||
|
||||
The upstream_utils directory contains scripts for updating copies of thirdparty code in the repository.
|
||||
|
||||
|
||||
@@ -17,44 +17,35 @@ Program Locations
|
||||
------- ---------
|
||||
RoboRIO Libraries ni-libraries
|
||||
Google Test gtest
|
||||
LLVM wpiutil/src/main/native/include/wpi/{various files}
|
||||
wpiutil/src/main/native/cpp/llvm/
|
||||
wpiutil/src/main/native/cpp/leb128.cpp
|
||||
wpiutil/src/test/native/cpp/leb128Test.cpp
|
||||
JSON for Modern C++ wpiutil/src/main/native/include/wpi/json.h
|
||||
wpiutil/src/main/native/cpp/json_*.cpp
|
||||
LLVM wpiutil/src/main/native/thirdparty/llvm
|
||||
wpiutil/src/test/native/cpp/llvm/
|
||||
JSON for Modern C++ wpiutil/src/main/native/thirdparty/json
|
||||
wpiutil/src/test/native/cpp/json/
|
||||
libuv wpiutil/src/main/native/include/uv.h
|
||||
wpiutil/src/main/native/include/uv/
|
||||
wpiutil/src/main/native/libuv/
|
||||
fmtlib wpiutil/src/main/native/fmtlib/
|
||||
sigslot wpiutil/src/main/native/include/wpi/Signal.h
|
||||
wpiutil/src/test/native/cpp/sigslot/
|
||||
tcpsockets wpiutil/src/main/native/cpp/TCP{Stream,Connector,Acceptor}.cpp
|
||||
wpiutil/src/main/native/include/wpi/TCP*.h
|
||||
MPack wpiutil/src/main/native/include/mpack.h
|
||||
wpiutil/src/main/native/cpp/mpack.cpp
|
||||
Bootstrap wpiutil/src/main/native/resources/bootstrap-*
|
||||
CoreUI wpiutil/src/main/native/resources/coreui-*
|
||||
Feather Icons wpiutil/src/main/native/resources/feather-*
|
||||
jQuery wpiutil/src/main/native/resources/jquery-*
|
||||
popper.js wpiutil/src/main/native/resources/popper-*
|
||||
libuv wpinet/src/main/native/thirdparty/libuv/
|
||||
fmtlib wpiutil/src/main/native/thirdparty/fmtlib/
|
||||
sigslot wpiutil/src/main/native/thirdparty/sigslot
|
||||
tcpsockets wpinet/src/main/native/thirdparty/tcpsockets
|
||||
MPack wpiutil/src/main/native/thirdparty/mpack
|
||||
Bootstrap wpinet/src/main/native/resources/bootstrap-*
|
||||
CoreUI wpinet/src/main/native/resources/coreui-*
|
||||
Feather Icons wpinet/src/main/native/resources/feather-*
|
||||
jQuery wpinet/src/main/native/resources/jquery-*
|
||||
popper.js wpinet/src/main/native/resources/popper-*
|
||||
units wpimath/src/main/native/include/units/
|
||||
Eigen wpimath/src/main/native/eigeninclude/
|
||||
wpimath/src/main/native/include/unsupported/
|
||||
Eigen wpimath/src/main/native/thirdparty/eigen/include/
|
||||
StackWalker wpiutil/src/main/native/windows/StackWalker.*
|
||||
TCB span wpiutil/src/main/native/include/wpi/span.h
|
||||
wpiutil/src/test/native/cpp/span/
|
||||
GHC filesystem wpiutil/src/main/native/include/wpi/ghc/
|
||||
GHC filesystem wpiutil/src/main/native/thirdparty/include/wpi/ghc/
|
||||
Team 254 Library wpilibj/src/main/java/edu/wpi/first/wpilibj/spline/SplineParameterizer.java
|
||||
wpilibj/src/main/java/edu/wpi/first/wpilibj/trajectory/TrajectoryParameterizer.java
|
||||
wpilibc/src/main/native/include/spline/SplineParameterizer.h
|
||||
wpilibc/src/main/native/include/trajectory/TrajectoryParameterizer.h
|
||||
wpilibc/src/main/native/cpp/trajectory/TrajectoryParameterizer.cpp
|
||||
Portable File Dialogs wpigui/src/main/native/include/portable-file-dialogs.h
|
||||
Drake wpimath/src/main/native/cpp/drake/common/drake_assert_and_throw.cpp
|
||||
wpimath/src/main/native/cpp/drake/math/discrete_algebraic_riccati_equation.cpp
|
||||
|
||||
Drake wpimath/src/main/native/thirdparty/drake/
|
||||
wpimath/src/test/native/cpp/drake/
|
||||
wpimath/src/test/native/include/drake/
|
||||
V8 export-template wpiutil/src/main/native/include/wpi/SymbolExports.h
|
||||
GCEM wpimath/src/main/native/thirdparty/gcem/include/
|
||||
|
||||
==============================================================================
|
||||
Google Test License
|
||||
@@ -90,12 +81,247 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
==============================================================================
|
||||
LLVM Release License
|
||||
The LLVM Project is under the Apache License v2.0 with LLVM Exceptions:
|
||||
==============================================================================
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
---- LLVM Exceptions to the Apache 2.0 License ----
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into an Object form of such source code, you
|
||||
may redistribute such embedded portions in such Object form without complying
|
||||
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
|
||||
|
||||
In addition, if you combine or link compiled forms of this Software with
|
||||
software that is licensed under the GPLv2 ("Combined Software") and if a
|
||||
court of competent jurisdiction determines that the patent provision (Section
|
||||
3), the indemnity provision (Section 9) or other Section of the License
|
||||
conflicts with the conditions of the GPLv2, you may retroactively and
|
||||
prospectively choose to deem waived or otherwise exclude such Section(s) of
|
||||
the License, but only in their entirety and only with respect to the Combined
|
||||
Software.
|
||||
|
||||
==============================================================================
|
||||
Software from third parties included in the LLVM Project:
|
||||
==============================================================================
|
||||
The LLVM Project contains third party software which is under different license
|
||||
terms. All such code will be identified clearly using at least one of two
|
||||
mechanisms:
|
||||
1) It will be in a separate directory tree with its own `LICENSE.txt` or
|
||||
`LICENSE` file at the top containing the specific license and restrictions
|
||||
which apply to that software, or
|
||||
2) It will contain specific license and restriction terms at the top of every
|
||||
file.
|
||||
|
||||
==============================================================================
|
||||
Legacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy):
|
||||
==============================================================================
|
||||
University of Illinois/NCSA
|
||||
Open Source License
|
||||
|
||||
Copyright (c) 2003-2017 University of Illinois at Urbana-Champaign.
|
||||
Copyright (c) 2003-2019 University of Illinois at Urbana-Champaign.
|
||||
All rights reserved.
|
||||
|
||||
Developed by:
|
||||
@@ -969,3 +1195,50 @@ 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.
|
||||
|
||||
==================
|
||||
V8 export-template
|
||||
==================
|
||||
Copyright 2014, the V8 project authors. All rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
============
|
||||
GCEM License
|
||||
============
|
||||
Copyright 2022 - ktholer (https://github.com/kthohr/gcem)
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
@@ -15,7 +15,7 @@ stages:
|
||||
vmImage: 'ubuntu-latest'
|
||||
|
||||
container:
|
||||
image: wpilib/roborio-cross-ubuntu:2022-18.04
|
||||
image: wpilib/roborio-cross-ubuntu:2023-22.04
|
||||
|
||||
timeoutInMinutes: 0
|
||||
|
||||
|
||||
28
build.gradle
@@ -2,7 +2,9 @@ import edu.wpi.first.toolchain.*
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url = 'https://frcmaven.wpi.edu/artifactory/ex-mvn'
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.hubspot.jinjava:jinjava:2.6.0'
|
||||
@@ -20,8 +22,8 @@ plugins {
|
||||
id 'visual-studio'
|
||||
id 'net.ltgt.errorprone' version '2.0.2' apply false
|
||||
id 'com.github.johnrengelman.shadow' version '7.1.2' apply false
|
||||
id 'com.diffplug.spotless' version '6.1.2' apply false
|
||||
id 'com.github.spotbugs' version '5.0.4' apply false
|
||||
id 'com.diffplug.spotless' version '6.4.2' apply false
|
||||
id 'com.github.spotbugs' version '5.0.8' apply false
|
||||
}
|
||||
|
||||
wpilibVersioning.buildServerMode = project.hasProperty('buildServer')
|
||||
@@ -29,7 +31,9 @@ wpilibVersioning.releaseMode = project.hasProperty('releaseMode')
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url = 'https://frcmaven.wpi.edu/artifactory/ex-mvn'
|
||||
}
|
||||
}
|
||||
if (project.hasProperty('releaseMode')) {
|
||||
wpilibRepositories.addAllReleaseRepositories(it)
|
||||
@@ -83,7 +87,12 @@ task copyAllOutputs(type: Copy) {
|
||||
build.dependsOn copyAllOutputs
|
||||
copyAllOutputs.dependsOn outputVersions
|
||||
|
||||
def copyReleaseOnly = project.hasProperty('ciReleaseOnly')
|
||||
|
||||
ext.addTaskToCopyAllOutputs = { task ->
|
||||
if (copyReleaseOnly && task.name.contains('debug')) {
|
||||
return
|
||||
}
|
||||
copyAllOutputs.dependsOn task
|
||||
copyAllOutputs.inputs.file task.archivePath
|
||||
copyAllOutputs.from task.archivePath
|
||||
@@ -99,6 +108,11 @@ subprojects {
|
||||
subproj.apply plugin: MultiBuilds
|
||||
}
|
||||
|
||||
plugins.withType(JavaPlugin) {
|
||||
sourceCompatibility = 11
|
||||
targetCompatibility = 11
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/shared/java/javastyle.gradle"
|
||||
|
||||
// Disables doclint in java 8.
|
||||
@@ -110,6 +124,10 @@ subprojects {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs.add '-XDstringConcat=inline'
|
||||
}
|
||||
|
||||
// Enables UTF-8 support in Javadoc
|
||||
tasks.withType(Javadoc) {
|
||||
options.addStringOption("charset", "utf-8")
|
||||
@@ -147,5 +165,5 @@ ext.getCurrentArch = {
|
||||
}
|
||||
|
||||
wrapper {
|
||||
gradleVersion = '7.3.3'
|
||||
gradleVersion = '7.5.1'
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@ repositories {
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
implementation "edu.wpi.first:native-utils:2022.7.1"
|
||||
implementation "edu.wpi.first:native-utils:2023.4.0"
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import groovy.transform.CompileStatic
|
||||
import javax.inject.Inject
|
||||
import edu.wpi.first.deployutils.deploy.artifact.MavenArtifact
|
||||
import edu.wpi.first.deployutils.deploy.context.DeployContext
|
||||
import org.gradle.api.Project
|
||||
import edu.wpi.first.deployutils.ActionWrapper
|
||||
import edu.wpi.first.deployutils.deploy.target.RemoteTarget
|
||||
import edu.wpi.first.deployutils.PredicateWrapper
|
||||
import groovy.transform.CompileStatic;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import java.util.function.Function
|
||||
import org.gradle.api.Project;
|
||||
|
||||
import edu.wpi.first.deployutils.deploy.CommandDeployResult;
|
||||
import edu.wpi.first.deployutils.deploy.artifact.MavenArtifact;
|
||||
import edu.wpi.first.deployutils.deploy.context.DeployContext;
|
||||
import edu.wpi.first.deployutils.deploy.target.RemoteTarget;
|
||||
import edu.wpi.first.deployutils.PredicateWrapper;
|
||||
import edu.wpi.first.deployutils.ActionWrapper;
|
||||
|
||||
@CompileStatic
|
||||
public class WPIJREArtifact extends MavenArtifact {
|
||||
@@ -17,6 +18,18 @@ public class WPIJREArtifact extends MavenArtifact {
|
||||
return configName;
|
||||
}
|
||||
|
||||
public boolean isCheckJreVersion() {
|
||||
return checkJreVersion;
|
||||
}
|
||||
|
||||
public void setCheckJreVersion(boolean checkJreVersion) {
|
||||
this.checkJreVersion = checkJreVersion;
|
||||
}
|
||||
|
||||
private boolean checkJreVersion = true;
|
||||
|
||||
private final String artifactLocation = "edu.wpi.first.jdk:roborio-2023:17.0.5u7-1"
|
||||
|
||||
@Inject
|
||||
public WPIJREArtifact(String name, RemoteTarget target) {
|
||||
super(name, target);
|
||||
@@ -24,10 +37,10 @@ public class WPIJREArtifact extends MavenArtifact {
|
||||
this.configName = configName;
|
||||
Project project = target.getProject();
|
||||
getConfiguration().set(project.getConfigurations().create(configName));
|
||||
getDependency().set(project.getDependencies().add(configName, "edu.wpi.first.jdk:roborio-2022:11.0.12u5-1"));
|
||||
getDependency().set(project.getDependencies().add(configName, artifactLocation));
|
||||
|
||||
setOnlyIf(new PredicateWrapper({ DeployContext ctx ->
|
||||
return jreMissing(ctx) || project.hasProperty("force-redeploy-jre");
|
||||
return jreMissing(ctx) || jreOutOfDate(ctx) || project.hasProperty("force-redeploy-jre");
|
||||
}));
|
||||
|
||||
getDirectory().set("/tmp");
|
||||
@@ -35,7 +48,7 @@ public class WPIJREArtifact extends MavenArtifact {
|
||||
|
||||
getPostdeploy().add(new ActionWrapper({ DeployContext ctx ->
|
||||
ctx.getLogger().log("Installing JRE...");
|
||||
ctx.execute("opkg remove frc2022-openjdk*; opkg install /tmp/frcjre.ipk; rm /tmp/frcjre.ipk");
|
||||
ctx.execute("opkg remove frc*-openjdk*; opkg install /tmp/frcjre.ipk; rm /tmp/frcjre.ipk");
|
||||
ctx.getLogger().log("JRE Deployed!");
|
||||
}));
|
||||
}
|
||||
@@ -44,5 +57,21 @@ public class WPIJREArtifact extends MavenArtifact {
|
||||
return ctx.execute("if [[ -f \"/usr/local/frc/JRE/bin/java\" ]]; then echo OK; else echo MISSING; fi").getResult().contains("MISSING");
|
||||
}
|
||||
|
||||
|
||||
private boolean jreOutOfDate(DeployContext ctx) {
|
||||
if (!checkJreVersion) {
|
||||
return false;
|
||||
}
|
||||
String version = getDependency().get().getVersion();
|
||||
CommandDeployResult cmdResult = ctx.execute("opkg list-installed | grep openjdk");
|
||||
if (cmdResult.getExitCode() != 0) {
|
||||
ctx.getLogger().log("JRE not found");
|
||||
return false;
|
||||
}
|
||||
String result = cmdResult.getResult().trim();
|
||||
ctx.getLogger().log("Searching for JRE " + version);
|
||||
ctx.getLogger().log("Found JRE " + result);
|
||||
boolean matches = result.contains(version);
|
||||
ctx.getLogger().log(matches ? "JRE Is Correct Version" : "JRE is mismatched. Reinstalling");
|
||||
return !matches;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,11 @@ apply from: "${rootDir}/shared/javacpp/setupBuild.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation project(':wpiutil')
|
||||
implementation project(':wpinet')
|
||||
implementation project(':ntcore')
|
||||
implementation project(':cscore')
|
||||
devImplementation project(':wpiutil')
|
||||
devImplementation project(':wpinet')
|
||||
devImplementation project(':ntcore')
|
||||
devImplementation project(':cscore')
|
||||
}
|
||||
@@ -32,19 +34,6 @@ apply from: "${rootDir}/shared/opencv.gradle"
|
||||
|
||||
nativeUtils.exportsConfigs {
|
||||
cameraserver {
|
||||
x86ExcludeSymbols = [
|
||||
'_CT??_R0?AV_System_error',
|
||||
'_CT??_R0?AVexception',
|
||||
'_CT??_R0?AVfailure',
|
||||
'_CT??_R0?AVruntime_error',
|
||||
'_CT??_R0?AVsystem_error',
|
||||
'_CTA5?AVfailure',
|
||||
'_TI5?AVfailure',
|
||||
'_CT??_R0?AVout_of_range',
|
||||
'_CTA3?AVout_of_range',
|
||||
'_TI3?AVout_of_range',
|
||||
'_CT??_R0?AVbad_cast'
|
||||
]
|
||||
x64ExcludeSymbols = [
|
||||
'_CT??_R0?AV_System_error',
|
||||
'_CT??_R0?AVexception',
|
||||
@@ -68,8 +57,9 @@ model {
|
||||
if (!it.buildable || !(it instanceof NativeBinarySpec)) {
|
||||
return
|
||||
}
|
||||
lib project: ':ntcore', library: 'ntcore', linkage: 'shared'
|
||||
project(':ntcore').addNtcoreDependency(it, 'shared')
|
||||
lib project: ':cscore', library: 'cscore', linkage: 'shared'
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,13 +23,16 @@ mainClassName = 'edu.wpi.Main'
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url = 'https://frcmaven.wpi.edu/artifactory/ex-mvn'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.code.gson:gson:2.8.9'
|
||||
|
||||
implementation project(':wpiutil')
|
||||
implementation project(':wpinet')
|
||||
implementation project(':ntcore')
|
||||
implementation project(':cscore')
|
||||
implementation project(':cameraserver')
|
||||
@@ -38,7 +41,6 @@ dependencies {
|
||||
model {
|
||||
components {
|
||||
multiCameraServerCpp(NativeExecutableSpec) {
|
||||
targetBuildTypes 'release'
|
||||
sources {
|
||||
cpp {
|
||||
source {
|
||||
@@ -53,8 +55,9 @@ model {
|
||||
}
|
||||
binaries.all { binary ->
|
||||
lib project: ':cameraserver', library: 'cameraserver', linkage: 'static'
|
||||
lib project: ':ntcore', library: 'ntcore', linkage: 'static'
|
||||
project(':ntcore').addNtcoreDependency(binary, 'static')
|
||||
lib project: ':cscore', library: 'cscore', linkage: 'static'
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'static'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +175,8 @@ public final class Main {
|
||||
ntinst.startServer();
|
||||
} else {
|
||||
System.out.println("Setting up NetworkTables client for team " + team);
|
||||
ntinst.startClientTeam(team);
|
||||
ntinst.setServerTeam(team);
|
||||
ntinst.startClient4("multicameraserver");
|
||||
}
|
||||
|
||||
// start cameras
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
@@ -182,7 +183,8 @@ int main(int argc, char* argv[]) {
|
||||
ntinst.StartServer();
|
||||
} else {
|
||||
fmt::print("Setting up NetworkTables client for team {}\n", team);
|
||||
ntinst.StartClientTeam(team);
|
||||
ntinst.StartClient4("multicameraserver");
|
||||
ntinst.SetServerTeam(team);
|
||||
}
|
||||
|
||||
// start cameras
|
||||
|
||||
@@ -15,13 +15,18 @@ import edu.wpi.first.cscore.VideoException;
|
||||
import edu.wpi.first.cscore.VideoListener;
|
||||
import edu.wpi.first.cscore.VideoMode;
|
||||
import edu.wpi.first.cscore.VideoMode.PixelFormat;
|
||||
import edu.wpi.first.cscore.VideoProperty;
|
||||
import edu.wpi.first.cscore.VideoSink;
|
||||
import edu.wpi.first.cscore.VideoSource;
|
||||
import edu.wpi.first.networktables.EntryListenerFlags;
|
||||
import edu.wpi.first.networktables.BooleanEntry;
|
||||
import edu.wpi.first.networktables.BooleanPublisher;
|
||||
import edu.wpi.first.networktables.IntegerEntry;
|
||||
import edu.wpi.first.networktables.IntegerPublisher;
|
||||
import edu.wpi.first.networktables.NetworkTable;
|
||||
import edu.wpi.first.networktables.NetworkTableEntry;
|
||||
import edu.wpi.first.networktables.NetworkTableInstance;
|
||||
import edu.wpi.first.networktables.StringArrayPublisher;
|
||||
import edu.wpi.first.networktables.StringArrayTopic;
|
||||
import edu.wpi.first.networktables.StringEntry;
|
||||
import edu.wpi.first.networktables.StringPublisher;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
@@ -33,36 +38,172 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
* Singleton class for creating and keeping camera servers. Also publishes camera information to
|
||||
* NetworkTables.
|
||||
*/
|
||||
@SuppressWarnings("PMD.UnusedPrivateField")
|
||||
public final class CameraServer {
|
||||
public static final int kBasePort = 1181;
|
||||
|
||||
@Deprecated public static final int kSize640x480 = 0;
|
||||
@Deprecated public static final int kSize320x240 = 1;
|
||||
@Deprecated public static final int kSize160x120 = 2;
|
||||
|
||||
private static final String kPublishName = "/CameraPublisher";
|
||||
private static CameraServer server;
|
||||
|
||||
/**
|
||||
* Get the CameraServer instance.
|
||||
*
|
||||
* @return The CameraServer instance.
|
||||
* @deprecated Use the static methods
|
||||
*/
|
||||
@Deprecated
|
||||
public static synchronized CameraServer getInstance() {
|
||||
if (server == null) {
|
||||
server = new CameraServer();
|
||||
private static final class PropertyPublisher implements AutoCloseable {
|
||||
@SuppressWarnings({"PMD.MissingBreakInSwitch", "PMD.ImplicitSwitchFallThrough", "fallthrough"})
|
||||
PropertyPublisher(NetworkTable table, VideoEvent event) {
|
||||
String name;
|
||||
String infoName;
|
||||
if (event.name.startsWith("raw_")) {
|
||||
name = "RawProperty/" + event.name;
|
||||
infoName = "RawPropertyInfo/" + event.name;
|
||||
} else {
|
||||
name = "Property/" + event.name;
|
||||
infoName = "PropertyInfo/" + event.name;
|
||||
}
|
||||
|
||||
try {
|
||||
switch (event.propertyKind) {
|
||||
case kBoolean:
|
||||
m_booleanValueEntry = table.getBooleanTopic(name).getEntry(false);
|
||||
m_booleanValueEntry.setDefault(event.value != 0);
|
||||
break;
|
||||
case kEnum:
|
||||
m_choicesTopic = table.getStringArrayTopic(infoName + "/choices");
|
||||
// fall through
|
||||
case kInteger:
|
||||
m_integerValueEntry = table.getIntegerTopic(name).getEntry(0);
|
||||
m_minPublisher = table.getIntegerTopic(infoName + "/min").publish();
|
||||
m_maxPublisher = table.getIntegerTopic(infoName + "/max").publish();
|
||||
m_stepPublisher = table.getIntegerTopic(infoName + "/step").publish();
|
||||
m_defaultPublisher = table.getIntegerTopic(infoName + "/default").publish();
|
||||
|
||||
m_integerValueEntry.setDefault(event.value);
|
||||
m_minPublisher.set(CameraServerJNI.getPropertyMin(event.propertyHandle));
|
||||
m_maxPublisher.set(CameraServerJNI.getPropertyMax(event.propertyHandle));
|
||||
m_stepPublisher.set(CameraServerJNI.getPropertyStep(event.propertyHandle));
|
||||
m_defaultPublisher.set(CameraServerJNI.getPropertyDefault(event.propertyHandle));
|
||||
break;
|
||||
case kString:
|
||||
m_stringValueEntry = table.getStringTopic(name).getEntry("");
|
||||
m_stringValueEntry.setDefault(event.valueStr);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (VideoException ignored) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return server;
|
||||
|
||||
void update(VideoEvent event) {
|
||||
switch (event.propertyKind) {
|
||||
case kBoolean:
|
||||
if (m_booleanValueEntry != null) {
|
||||
m_booleanValueEntry.set(event.value != 0);
|
||||
}
|
||||
break;
|
||||
case kInteger:
|
||||
case kEnum:
|
||||
if (m_integerValueEntry != null) {
|
||||
m_integerValueEntry.set(event.value);
|
||||
}
|
||||
break;
|
||||
case kString:
|
||||
if (m_stringValueEntry != null) {
|
||||
m_stringValueEntry.set(event.valueStr);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
if (m_booleanValueEntry != null) {
|
||||
m_booleanValueEntry.close();
|
||||
}
|
||||
if (m_integerValueEntry != null) {
|
||||
m_integerValueEntry.close();
|
||||
}
|
||||
if (m_stringValueEntry != null) {
|
||||
m_stringValueEntry.close();
|
||||
}
|
||||
if (m_minPublisher != null) {
|
||||
m_minPublisher.close();
|
||||
}
|
||||
if (m_maxPublisher != null) {
|
||||
m_maxPublisher.close();
|
||||
}
|
||||
if (m_stepPublisher != null) {
|
||||
m_stepPublisher.close();
|
||||
}
|
||||
if (m_defaultPublisher != null) {
|
||||
m_defaultPublisher.close();
|
||||
}
|
||||
if (m_choicesPublisher != null) {
|
||||
m_choicesPublisher.close();
|
||||
}
|
||||
}
|
||||
|
||||
BooleanEntry m_booleanValueEntry;
|
||||
IntegerEntry m_integerValueEntry;
|
||||
StringEntry m_stringValueEntry;
|
||||
IntegerPublisher m_minPublisher;
|
||||
IntegerPublisher m_maxPublisher;
|
||||
IntegerPublisher m_stepPublisher;
|
||||
IntegerPublisher m_defaultPublisher;
|
||||
StringArrayTopic m_choicesTopic;
|
||||
StringArrayPublisher m_choicesPublisher;
|
||||
}
|
||||
|
||||
private static final class SourcePublisher implements AutoCloseable {
|
||||
SourcePublisher(NetworkTable table, int sourceHandle) {
|
||||
this.m_table = table;
|
||||
m_sourcePublisher = table.getStringTopic("source").publish();
|
||||
m_descriptionPublisher = table.getStringTopic("description").publish();
|
||||
m_connectedPublisher = table.getBooleanTopic("connected").publish();
|
||||
m_streamsPublisher = table.getStringArrayTopic("streams").publish();
|
||||
m_modeEntry = table.getStringTopic("mode").getEntry("");
|
||||
m_modesPublisher = table.getStringArrayTopic("modes").publish();
|
||||
|
||||
m_sourcePublisher.set(makeSourceValue(sourceHandle));
|
||||
m_descriptionPublisher.set(CameraServerJNI.getSourceDescription(sourceHandle));
|
||||
m_connectedPublisher.set(CameraServerJNI.isSourceConnected(sourceHandle));
|
||||
m_streamsPublisher.set(getSourceStreamValues(sourceHandle));
|
||||
|
||||
try {
|
||||
VideoMode mode = CameraServerJNI.getSourceVideoMode(sourceHandle);
|
||||
m_modeEntry.setDefault(videoModeToString(mode));
|
||||
m_modesPublisher.set(getSourceModeValues(sourceHandle));
|
||||
} catch (VideoException ignored) {
|
||||
// Do nothing. Let the other event handlers update this if there is an error.
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
m_sourcePublisher.close();
|
||||
m_descriptionPublisher.close();
|
||||
m_connectedPublisher.close();
|
||||
m_streamsPublisher.close();
|
||||
m_modeEntry.close();
|
||||
m_modesPublisher.close();
|
||||
for (PropertyPublisher pp : m_properties.values()) {
|
||||
pp.close();
|
||||
}
|
||||
}
|
||||
|
||||
final NetworkTable m_table;
|
||||
final StringPublisher m_sourcePublisher;
|
||||
final StringPublisher m_descriptionPublisher;
|
||||
final BooleanPublisher m_connectedPublisher;
|
||||
final StringArrayPublisher m_streamsPublisher;
|
||||
final StringEntry m_modeEntry;
|
||||
final StringArrayPublisher m_modesPublisher;
|
||||
final Map<Integer, PropertyPublisher> m_properties = new HashMap<>();
|
||||
}
|
||||
|
||||
private static final AtomicInteger m_defaultUsbDevice = new AtomicInteger();
|
||||
private static String m_primarySourceName;
|
||||
private static final Map<String, VideoSource> m_sources = new HashMap<>();
|
||||
private static final Map<String, VideoSink> m_sinks = new HashMap<>();
|
||||
private static final Map<Integer, NetworkTable> m_tables =
|
||||
private static final Map<Integer, SourcePublisher> m_publishers =
|
||||
new HashMap<>(); // indexed by source handle
|
||||
// source handle indexed by sink handle
|
||||
private static final Map<Integer, Integer> m_fixedSources = new HashMap<>();
|
||||
@@ -81,190 +222,132 @@ public final class CameraServer {
|
||||
// - "PropertyInfo/{Property}" - Property supporting information
|
||||
|
||||
// Listener for video events
|
||||
@SuppressWarnings({"PMD.UnusedPrivateField", "PMD.AvoidCatchingGenericException"})
|
||||
private static final VideoListener m_videoListener =
|
||||
new VideoListener(
|
||||
event -> {
|
||||
switch (event.kind) {
|
||||
case kSourceCreated:
|
||||
{
|
||||
// Create subtable for the camera
|
||||
NetworkTable table = m_publishTable.getSubTable(event.name);
|
||||
m_tables.put(event.sourceHandle, table);
|
||||
table.getEntry("source").setString(makeSourceValue(event.sourceHandle));
|
||||
table
|
||||
.getEntry("description")
|
||||
.setString(CameraServerJNI.getSourceDescription(event.sourceHandle));
|
||||
table
|
||||
.getEntry("connected")
|
||||
.setBoolean(CameraServerJNI.isSourceConnected(event.sourceHandle));
|
||||
table
|
||||
.getEntry("streams")
|
||||
.setStringArray(getSourceStreamValues(event.sourceHandle));
|
||||
try {
|
||||
VideoMode mode = CameraServerJNI.getSourceVideoMode(event.sourceHandle);
|
||||
table.getEntry("mode").setDefaultString(videoModeToString(mode));
|
||||
table.getEntry("modes").setStringArray(getSourceModeValues(event.sourceHandle));
|
||||
} catch (VideoException ignored) {
|
||||
// Do nothing. Let the other event handlers update this if there is an error.
|
||||
synchronized (CameraServer.class) {
|
||||
switch (event.kind) {
|
||||
case kSourceCreated:
|
||||
{
|
||||
// Create subtable for the camera
|
||||
NetworkTable table = m_publishTable.getSubTable(event.name);
|
||||
m_publishers.put(
|
||||
event.sourceHandle, new SourcePublisher(table, event.sourceHandle));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceDestroyed:
|
||||
{
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
table.getEntry("source").setString("");
|
||||
table.getEntry("streams").setStringArray(new String[0]);
|
||||
table.getEntry("modes").setStringArray(new String[0]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceConnected:
|
||||
{
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
// update the description too (as it may have changed)
|
||||
table
|
||||
.getEntry("description")
|
||||
.setString(CameraServerJNI.getSourceDescription(event.sourceHandle));
|
||||
table.getEntry("connected").setBoolean(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceDisconnected:
|
||||
{
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
table.getEntry("connected").setBoolean(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceVideoModesUpdated:
|
||||
{
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
table.getEntry("modes").setStringArray(getSourceModeValues(event.sourceHandle));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceVideoModeChanged:
|
||||
{
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
table.getEntry("mode").setString(videoModeToString(event.mode));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourcePropertyCreated:
|
||||
{
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
putSourcePropertyValue(table, event, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourcePropertyValueUpdated:
|
||||
{
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
putSourcePropertyValue(table, event, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourcePropertyChoicesUpdated:
|
||||
{
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
try {
|
||||
String[] choices =
|
||||
CameraServerJNI.getEnumPropertyChoices(event.propertyHandle);
|
||||
table
|
||||
.getEntry("PropertyInfo/" + event.name + "/choices")
|
||||
.setStringArray(choices);
|
||||
} catch (VideoException ignored) {
|
||||
// ignore
|
||||
case kSourceDestroyed:
|
||||
{
|
||||
SourcePublisher publisher = m_publishers.remove(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
try {
|
||||
publisher.close();
|
||||
} catch (Exception e) {
|
||||
// ignore (nothing we can do about it)
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceConnected:
|
||||
{
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
// update the description too (as it may have changed)
|
||||
publisher.m_descriptionPublisher.set(
|
||||
CameraServerJNI.getSourceDescription(event.sourceHandle));
|
||||
publisher.m_connectedPublisher.set(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceDisconnected:
|
||||
{
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
publisher.m_connectedPublisher.set(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceVideoModesUpdated:
|
||||
{
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
publisher.m_modesPublisher.set(getSourceModeValues(event.sourceHandle));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceVideoModeChanged:
|
||||
{
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
publisher.m_modeEntry.set(videoModeToString(event.mode));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourcePropertyCreated:
|
||||
{
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
publisher.m_properties.put(
|
||||
event.propertyHandle, new PropertyPublisher(publisher.m_table, event));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourcePropertyValueUpdated:
|
||||
{
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
PropertyPublisher pp = publisher.m_properties.get(event.propertyHandle);
|
||||
if (pp != null) {
|
||||
pp.update(event);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourcePropertyChoicesUpdated:
|
||||
{
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
PropertyPublisher pp = publisher.m_properties.get(event.propertyHandle);
|
||||
if (pp != null && pp.m_choicesTopic != null) {
|
||||
try {
|
||||
String[] choices =
|
||||
CameraServerJNI.getEnumPropertyChoices(event.propertyHandle);
|
||||
if (pp.m_choicesPublisher == null) {
|
||||
pp.m_choicesPublisher = pp.m_choicesTopic.publish();
|
||||
}
|
||||
pp.m_choicesPublisher.set(choices);
|
||||
} catch (VideoException ignored) {
|
||||
// ignore (just don't publish choices if we can't get them)
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSinkSourceChanged:
|
||||
case kSinkCreated:
|
||||
case kSinkDestroyed:
|
||||
case kNetworkInterfacesChanged:
|
||||
{
|
||||
m_addresses = CameraServerJNI.getNetworkInterfaces();
|
||||
updateStreamValues();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
case kSinkSourceChanged:
|
||||
case kSinkCreated:
|
||||
case kSinkDestroyed:
|
||||
case kNetworkInterfacesChanged:
|
||||
{
|
||||
m_addresses = CameraServerJNI.getNetworkInterfaces();
|
||||
updateStreamValues();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
0x4fff,
|
||||
true);
|
||||
|
||||
private static final int m_tableListener =
|
||||
NetworkTableInstance.getDefault()
|
||||
.addEntryListener(
|
||||
kPublishName + "/",
|
||||
event -> {
|
||||
String relativeKey = event.name.substring(kPublishName.length() + 1);
|
||||
|
||||
// get source (sourceName/...)
|
||||
int subKeyIndex = relativeKey.indexOf('/');
|
||||
if (subKeyIndex == -1) {
|
||||
return;
|
||||
}
|
||||
String sourceName = relativeKey.substring(0, subKeyIndex);
|
||||
VideoSource source = m_sources.get(sourceName);
|
||||
if (source == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get subkey
|
||||
relativeKey = relativeKey.substring(subKeyIndex + 1);
|
||||
|
||||
// handle standard names
|
||||
String propName;
|
||||
if ("mode".equals(relativeKey)) {
|
||||
// reset to current mode
|
||||
event.getEntry().setString(videoModeToString(source.getVideoMode()));
|
||||
return;
|
||||
} else if (relativeKey.startsWith("Property/")) {
|
||||
propName = relativeKey.substring(9);
|
||||
} else if (relativeKey.startsWith("RawProperty/")) {
|
||||
propName = relativeKey.substring(12);
|
||||
} else {
|
||||
return; // ignore
|
||||
}
|
||||
|
||||
// everything else is a property
|
||||
VideoProperty property = source.getProperty(propName);
|
||||
switch (property.getKind()) {
|
||||
case kNone:
|
||||
return;
|
||||
case kBoolean:
|
||||
// reset to current setting
|
||||
event.getEntry().setBoolean(property.get() != 0);
|
||||
return;
|
||||
case kInteger:
|
||||
case kEnum:
|
||||
// reset to current setting
|
||||
event.getEntry().setDouble(property.get());
|
||||
return;
|
||||
case kString:
|
||||
// reset to current setting
|
||||
event.getEntry().setString(property.getString());
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
},
|
||||
EntryListenerFlags.kImmediate | EntryListenerFlags.kUpdate);
|
||||
private static int m_nextPort = kBasePort;
|
||||
private static String[] m_addresses = new String[0];
|
||||
|
||||
@SuppressWarnings("MissingJavadocMethod")
|
||||
/**
|
||||
* Return URI of source with the given index.
|
||||
*
|
||||
* @param source Source index.
|
||||
*/
|
||||
private static String makeSourceValue(int source) {
|
||||
switch (VideoSource.getKindFromInt(CameraServerJNI.getSourceKind(source))) {
|
||||
case kUsb:
|
||||
@@ -285,12 +368,21 @@ public final class CameraServer {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("MissingJavadocMethod")
|
||||
/**
|
||||
* Return URI of stream with the given address and port.
|
||||
*
|
||||
* @param address Stream IP address.
|
||||
* @param port Stream remote port.
|
||||
*/
|
||||
private static String makeStreamValue(String address, int port) {
|
||||
return "mjpg:http://" + address + ":" + port + "/?action=stream";
|
||||
}
|
||||
|
||||
@SuppressWarnings("MissingJavadocMethod")
|
||||
/**
|
||||
* Return URI of sink stream with the given index.
|
||||
*
|
||||
* @param sink Sink index.
|
||||
*/
|
||||
private static synchronized String[] getSinkStreamValues(int sink) {
|
||||
// Ignore all but MjpegServer
|
||||
if (VideoSink.getKindFromInt(CameraServerJNI.getSinkKind(sink)) != VideoSink.Kind.kMjpeg) {
|
||||
@@ -320,7 +412,11 @@ public final class CameraServer {
|
||||
return values.toArray(new String[0]);
|
||||
}
|
||||
|
||||
@SuppressWarnings("MissingJavadocMethod")
|
||||
/**
|
||||
* Return list of stream source URIs for the given source index.
|
||||
*
|
||||
* @param source Source index.
|
||||
*/
|
||||
private static synchronized String[] getSourceStreamValues(int source) {
|
||||
// Ignore all but HttpCamera
|
||||
if (VideoSource.getKindFromInt(CameraServerJNI.getSourceKind(source))
|
||||
@@ -355,7 +451,7 @@ public final class CameraServer {
|
||||
return values;
|
||||
}
|
||||
|
||||
@SuppressWarnings("MissingJavadocMethod")
|
||||
/** Update list of stream URIs. */
|
||||
private static synchronized void updateStreamValues() {
|
||||
// Over all the sinks...
|
||||
for (VideoSink i : m_sinks.values()) {
|
||||
@@ -369,8 +465,8 @@ public final class CameraServer {
|
||||
if (source == 0) {
|
||||
continue;
|
||||
}
|
||||
NetworkTable table = m_tables.get(source);
|
||||
if (table != null) {
|
||||
SourcePublisher publisher = m_publishers.get(source);
|
||||
if (publisher != null) {
|
||||
// Don't set stream values if this is a HttpCamera passthrough
|
||||
if (VideoSource.getKindFromInt(CameraServerJNI.getSourceKind(source))
|
||||
== VideoSource.Kind.kHttp) {
|
||||
@@ -380,7 +476,7 @@ public final class CameraServer {
|
||||
// Set table value
|
||||
String[] values = getSinkStreamValues(sink);
|
||||
if (values.length > 0) {
|
||||
table.getEntry("streams").setStringArray(values);
|
||||
publisher.m_streamsPublisher.set(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -390,18 +486,18 @@ public final class CameraServer {
|
||||
int source = i.getHandle();
|
||||
|
||||
// Get the source's subtable (if none exists, we're done)
|
||||
NetworkTable table = m_tables.get(source);
|
||||
if (table != null) {
|
||||
SourcePublisher publisher = m_publishers.get(source);
|
||||
if (publisher != null) {
|
||||
// Set table value
|
||||
String[] values = getSourceStreamValues(source);
|
||||
if (values.length > 0) {
|
||||
table.getEntry("streams").setStringArray(values);
|
||||
publisher.m_streamsPublisher.set(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("MissingJavadocMethod")
|
||||
/** Provide string description of pixel format. */
|
||||
private static String pixelFormatToString(PixelFormat pixelFormat) {
|
||||
switch (pixelFormat) {
|
||||
case kMJPEG:
|
||||
@@ -419,9 +515,11 @@ public final class CameraServer {
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide string description of video mode.
|
||||
/// The returned string is "{width}x{height} {format} {fps} fps".
|
||||
@SuppressWarnings("MissingJavadocMethod")
|
||||
/**
|
||||
* Provide string description of video mode.
|
||||
*
|
||||
* <p>The returned string is "{width}x{height} {format} {fps} fps".
|
||||
*/
|
||||
private static String videoModeToString(VideoMode mode) {
|
||||
return mode.width
|
||||
+ "x"
|
||||
@@ -433,7 +531,11 @@ public final class CameraServer {
|
||||
+ " fps";
|
||||
}
|
||||
|
||||
@SuppressWarnings("MissingJavadocMethod")
|
||||
/**
|
||||
* Get list of video modes for the given source handle.
|
||||
*
|
||||
* @param sourceHandle Source handle.
|
||||
*/
|
||||
private static String[] getSourceModeValues(int sourceHandle) {
|
||||
VideoMode[] modes = CameraServerJNI.enumerateSourceVideoModes(sourceHandle);
|
||||
String[] modeStrings = new String[modes.length];
|
||||
@@ -443,63 +545,6 @@ public final class CameraServer {
|
||||
return modeStrings;
|
||||
}
|
||||
|
||||
@SuppressWarnings("MissingJavadocMethod")
|
||||
private static void putSourcePropertyValue(NetworkTable table, VideoEvent event, boolean isNew) {
|
||||
String name;
|
||||
String infoName;
|
||||
if (event.name.startsWith("raw_")) {
|
||||
name = "RawProperty/" + event.name;
|
||||
infoName = "RawPropertyInfo/" + event.name;
|
||||
} else {
|
||||
name = "Property/" + event.name;
|
||||
infoName = "PropertyInfo/" + event.name;
|
||||
}
|
||||
|
||||
NetworkTableEntry entry = table.getEntry(name);
|
||||
try {
|
||||
switch (event.propertyKind) {
|
||||
case kBoolean:
|
||||
if (isNew) {
|
||||
entry.setDefaultBoolean(event.value != 0);
|
||||
} else {
|
||||
entry.setBoolean(event.value != 0);
|
||||
}
|
||||
break;
|
||||
case kInteger:
|
||||
case kEnum:
|
||||
if (isNew) {
|
||||
entry.setDefaultDouble(event.value);
|
||||
table
|
||||
.getEntry(infoName + "/min")
|
||||
.setDouble(CameraServerJNI.getPropertyMin(event.propertyHandle));
|
||||
table
|
||||
.getEntry(infoName + "/max")
|
||||
.setDouble(CameraServerJNI.getPropertyMax(event.propertyHandle));
|
||||
table
|
||||
.getEntry(infoName + "/step")
|
||||
.setDouble(CameraServerJNI.getPropertyStep(event.propertyHandle));
|
||||
table
|
||||
.getEntry(infoName + "/default")
|
||||
.setDouble(CameraServerJNI.getPropertyDefault(event.propertyHandle));
|
||||
} else {
|
||||
entry.setDouble(event.value);
|
||||
}
|
||||
break;
|
||||
case kString:
|
||||
if (isNew) {
|
||||
entry.setDefaultString(event.valueStr);
|
||||
} else {
|
||||
entry.setString(event.valueStr);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (VideoException ignored) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
private CameraServer() {}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* <p>An example use case for grabbing a yellow tote from 2015 in autonomous: <br>
|
||||
*
|
||||
* <pre><code>
|
||||
* public class Robot extends IterativeRobot
|
||||
* public class Robot extends TimedRobot
|
||||
* implements VisionRunner.Listener<MyFindTotePipeline> {
|
||||
*
|
||||
* // A USB camera connected to the roboRIO.
|
||||
|
||||
@@ -8,8 +8,12 @@
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <networktables/BooleanTopic.h>
|
||||
#include <networktables/IntegerTopic.h>
|
||||
#include <networktables/NetworkTable.h>
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <networktables/StringArrayTopic.h>
|
||||
#include <networktables/StringTopic.h>
|
||||
#include <wpi/DenseMap.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
@@ -24,9 +28,42 @@ using namespace frc;
|
||||
static constexpr char const* kPublishName = "/CameraPublisher";
|
||||
|
||||
namespace {
|
||||
|
||||
struct Instance;
|
||||
|
||||
struct PropertyPublisher {
|
||||
PropertyPublisher(nt::NetworkTable& table, const cs::VideoEvent& event);
|
||||
|
||||
void Update(const cs::VideoEvent& event);
|
||||
|
||||
nt::BooleanEntry booleanValueEntry;
|
||||
nt::IntegerEntry integerValueEntry;
|
||||
nt::StringEntry stringValueEntry;
|
||||
nt::IntegerPublisher minPublisher;
|
||||
nt::IntegerPublisher maxPublisher;
|
||||
nt::IntegerPublisher stepPublisher;
|
||||
nt::IntegerPublisher defaultPublisher;
|
||||
nt::StringArrayTopic choicesTopic;
|
||||
nt::StringArrayPublisher choicesPublisher;
|
||||
};
|
||||
|
||||
struct SourcePublisher {
|
||||
SourcePublisher(Instance& inst, std::shared_ptr<nt::NetworkTable> table,
|
||||
CS_Source source);
|
||||
|
||||
std::shared_ptr<nt::NetworkTable> table;
|
||||
nt::StringPublisher sourcePublisher;
|
||||
nt::StringPublisher descriptionPublisher;
|
||||
nt::BooleanPublisher connectedPublisher;
|
||||
nt::StringArrayPublisher streamsPublisher;
|
||||
nt::StringEntry modeEntry;
|
||||
nt::StringArrayPublisher modesPublisher;
|
||||
wpi::DenseMap<CS_Property, PropertyPublisher> properties;
|
||||
};
|
||||
|
||||
struct Instance {
|
||||
Instance();
|
||||
std::shared_ptr<nt::NetworkTable> GetSourceTable(CS_Source source);
|
||||
SourcePublisher* GetPublisher(CS_Source source);
|
||||
std::vector<std::string> GetSinkStreamValues(CS_Sink sink);
|
||||
std::vector<std::string> GetSourceStreamValues(CS_Source source);
|
||||
void UpdateStreamValues();
|
||||
@@ -37,7 +74,7 @@ struct Instance {
|
||||
wpi::StringMap<cs::VideoSource> m_sources;
|
||||
wpi::StringMap<cs::VideoSink> m_sinks;
|
||||
wpi::DenseMap<CS_Sink, CS_Source> m_fixedSources;
|
||||
wpi::DenseMap<CS_Source, std::shared_ptr<nt::NetworkTable>> m_tables;
|
||||
wpi::DenseMap<CS_Source, SourcePublisher> m_publishers;
|
||||
std::shared_ptr<nt::NetworkTable> m_publishTable{
|
||||
nt::NetworkTableInstance::GetDefault().GetTable(kPublishName)};
|
||||
cs::VideoListener m_videoListener;
|
||||
@@ -45,6 +82,7 @@ struct Instance {
|
||||
int m_nextPort{CameraServer::kBasePort};
|
||||
std::vector<std::string> m_addresses;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
static Instance& GetInstance() {
|
||||
@@ -52,12 +90,6 @@ static Instance& GetInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
CameraServer* CameraServer::GetInstance() {
|
||||
::GetInstance();
|
||||
static CameraServer instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
static std::string_view MakeSourceValue(CS_Source source,
|
||||
wpi::SmallVectorImpl<char>& buf) {
|
||||
CS_Status status = 0;
|
||||
@@ -92,9 +124,13 @@ static std::string MakeStreamValue(std::string_view address, int port) {
|
||||
return fmt::format("mjpg:http://{}:{}/?action=stream", address, port);
|
||||
}
|
||||
|
||||
std::shared_ptr<nt::NetworkTable> Instance::GetSourceTable(CS_Source source) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
return m_tables.lookup(source);
|
||||
SourcePublisher* Instance::GetPublisher(CS_Source source) {
|
||||
auto it = m_publishers.find(source);
|
||||
if (it != m_publishers.end()) {
|
||||
return &it->second;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> Instance::GetSinkStreamValues(CS_Sink sink) {
|
||||
@@ -164,7 +200,6 @@ std::vector<std::string> Instance::GetSourceStreamValues(CS_Source source) {
|
||||
}
|
||||
|
||||
void Instance::UpdateStreamValues() {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
// Over all the sinks...
|
||||
for (const auto& i : m_sinks) {
|
||||
CS_Status status = 0;
|
||||
@@ -178,8 +213,7 @@ void Instance::UpdateStreamValues() {
|
||||
if (source == 0) {
|
||||
continue;
|
||||
}
|
||||
auto table = m_tables.lookup(source);
|
||||
if (table) {
|
||||
if (auto publisher = GetPublisher(source)) {
|
||||
// Don't set stream values if this is a HttpCamera passthrough
|
||||
if (cs::GetSourceKind(source, &status) == CS_SOURCE_HTTP) {
|
||||
continue;
|
||||
@@ -188,7 +222,7 @@ void Instance::UpdateStreamValues() {
|
||||
// Set table value
|
||||
auto values = GetSinkStreamValues(sink);
|
||||
if (!values.empty()) {
|
||||
table->GetEntry("streams").SetStringArray(values);
|
||||
publisher->streamsPublisher.Set(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,12 +232,11 @@ void Instance::UpdateStreamValues() {
|
||||
CS_Source source = i.second.GetHandle();
|
||||
|
||||
// Get the source's subtable (if none exists, we're done)
|
||||
auto table = m_tables.lookup(source);
|
||||
if (table) {
|
||||
if (auto publisher = GetPublisher(source)) {
|
||||
// Set table value
|
||||
auto values = GetSourceStreamValues(source);
|
||||
if (!values.empty()) {
|
||||
table->GetEntry("streams").SetStringArray(values);
|
||||
publisher->streamsPublisher.Set(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -240,51 +273,71 @@ static std::vector<std::string> GetSourceModeValues(int source) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
static void PutSourcePropertyValue(nt::NetworkTable* table,
|
||||
const cs::VideoEvent& event, bool isNew) {
|
||||
std::string_view namePrefix;
|
||||
std::string_view infoPrefix;
|
||||
PropertyPublisher::PropertyPublisher(nt::NetworkTable& table,
|
||||
const cs::VideoEvent& event) {
|
||||
std::string name;
|
||||
std::string infoName;
|
||||
if (wpi::starts_with(event.name, "raw_")) {
|
||||
namePrefix = "RawProperty";
|
||||
infoPrefix = "RawPropertyInfo";
|
||||
name = fmt::format("RawProperty/{}", event.name);
|
||||
infoName = fmt::format("RawPropertyInfo/{}", event.name);
|
||||
} else {
|
||||
namePrefix = "Property";
|
||||
infoPrefix = "PropertyInfo";
|
||||
name = fmt::format("Property/{}", event.name);
|
||||
infoName = fmt::format("PropertyInfo/{}", event.name);
|
||||
}
|
||||
|
||||
wpi::SmallString<64> buf;
|
||||
CS_Status status = 0;
|
||||
nt::NetworkTableEntry entry =
|
||||
table->GetEntry(fmt::format("{}/{}", namePrefix, event.name));
|
||||
switch (event.propertyKind) {
|
||||
case CS_PROP_BOOLEAN:
|
||||
if (isNew) {
|
||||
entry.SetDefaultBoolean(event.value != 0);
|
||||
} else {
|
||||
entry.SetBoolean(event.value != 0);
|
||||
booleanValueEntry = table.GetBooleanTopic(name).GetEntry(false);
|
||||
booleanValueEntry.SetDefault(event.value != 0);
|
||||
break;
|
||||
case CS_PROP_ENUM:
|
||||
choicesTopic =
|
||||
table.GetStringArrayTopic(fmt::format("{}/choices", infoName));
|
||||
[[fallthrough]];
|
||||
case CS_PROP_INTEGER:
|
||||
integerValueEntry = table.GetIntegerTopic(name).GetEntry(0);
|
||||
minPublisher =
|
||||
table.GetIntegerTopic(fmt::format("{}/min", infoName)).Publish();
|
||||
maxPublisher =
|
||||
table.GetIntegerTopic(fmt::format("{}/max", infoName)).Publish();
|
||||
stepPublisher =
|
||||
table.GetIntegerTopic(fmt::format("{}/step", infoName)).Publish();
|
||||
defaultPublisher =
|
||||
table.GetIntegerTopic(fmt::format("{}/default", infoName)).Publish();
|
||||
|
||||
integerValueEntry.SetDefault(event.value);
|
||||
minPublisher.Set(cs::GetPropertyMin(event.propertyHandle, &status));
|
||||
maxPublisher.Set(cs::GetPropertyMax(event.propertyHandle, &status));
|
||||
stepPublisher.Set(cs::GetPropertyStep(event.propertyHandle, &status));
|
||||
defaultPublisher.Set(
|
||||
cs::GetPropertyDefault(event.propertyHandle, &status));
|
||||
break;
|
||||
case CS_PROP_STRING:
|
||||
stringValueEntry = table.GetStringTopic(name).GetEntry("");
|
||||
stringValueEntry.SetDefault(event.valueStr);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyPublisher::Update(const cs::VideoEvent& event) {
|
||||
switch (event.propertyKind) {
|
||||
case CS_PROP_BOOLEAN:
|
||||
if (booleanValueEntry) {
|
||||
booleanValueEntry.Set(event.value != 0);
|
||||
}
|
||||
break;
|
||||
case CS_PROP_INTEGER:
|
||||
case CS_PROP_ENUM:
|
||||
if (isNew) {
|
||||
entry.SetDefaultDouble(event.value);
|
||||
table->GetEntry(fmt::format("{}/{}/min", infoPrefix, event.name))
|
||||
.SetDouble(cs::GetPropertyMin(event.propertyHandle, &status));
|
||||
table->GetEntry(fmt::format("{}/{}/max", infoPrefix, event.name))
|
||||
.SetDouble(cs::GetPropertyMax(event.propertyHandle, &status));
|
||||
table->GetEntry(fmt::format("{}/{}/step", infoPrefix, event.name))
|
||||
.SetDouble(cs::GetPropertyStep(event.propertyHandle, &status));
|
||||
table->GetEntry(fmt::format("{}/{}/default", infoPrefix, event.name))
|
||||
.SetDouble(cs::GetPropertyDefault(event.propertyHandle, &status));
|
||||
} else {
|
||||
entry.SetDouble(event.value);
|
||||
if (integerValueEntry) {
|
||||
integerValueEntry.Set(event.value);
|
||||
}
|
||||
break;
|
||||
case CS_PROP_STRING:
|
||||
if (isNew) {
|
||||
entry.SetDefaultString(event.valueStr);
|
||||
} else {
|
||||
entry.SetString(event.valueStr);
|
||||
if (stringValueEntry) {
|
||||
stringValueEntry.Set(event.valueStr);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -292,6 +345,28 @@ static void PutSourcePropertyValue(nt::NetworkTable* table,
|
||||
}
|
||||
}
|
||||
|
||||
SourcePublisher::SourcePublisher(Instance& inst,
|
||||
std::shared_ptr<nt::NetworkTable> table,
|
||||
CS_Source source)
|
||||
: table{table},
|
||||
sourcePublisher{table->GetStringTopic("source").Publish()},
|
||||
descriptionPublisher{table->GetStringTopic("description").Publish()},
|
||||
connectedPublisher{table->GetBooleanTopic("connected").Publish()},
|
||||
streamsPublisher{table->GetStringArrayTopic("streams").Publish()},
|
||||
modeEntry{table->GetStringTopic("mode").GetEntry("")},
|
||||
modesPublisher{table->GetStringArrayTopic("modes").Publish()} {
|
||||
CS_Status status = 0;
|
||||
wpi::SmallString<64> buf;
|
||||
sourcePublisher.Set(MakeSourceValue(source, buf));
|
||||
wpi::SmallString<64> descBuf;
|
||||
descriptionPublisher.Set(cs::GetSourceDescription(source, descBuf, &status));
|
||||
connectedPublisher.Set(cs::IsSourceConnected(source, &status));
|
||||
streamsPublisher.Set(inst.GetSourceStreamValues(source));
|
||||
auto mode = cs::GetSourceVideoMode(source, &status);
|
||||
modeEntry.SetDefault(VideoModeToString(mode));
|
||||
modesPublisher.Set(GetSourceModeValues(source));
|
||||
}
|
||||
|
||||
Instance::Instance() {
|
||||
// We publish sources to NetworkTables using the following structure:
|
||||
// "/CameraPublisher/{Source.Name}/" - root
|
||||
@@ -306,177 +381,88 @@ Instance::Instance() {
|
||||
|
||||
// Listener for video events
|
||||
m_videoListener = cs::VideoListener{
|
||||
[=](const cs::VideoEvent& event) {
|
||||
[=, this](const cs::VideoEvent& event) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
CS_Status status = 0;
|
||||
switch (event.kind) {
|
||||
case cs::VideoEvent::kSourceCreated: {
|
||||
// Create subtable for the camera
|
||||
auto table = m_publishTable->GetSubTable(event.name);
|
||||
{
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_tables.insert(std::make_pair(event.sourceHandle, table));
|
||||
}
|
||||
wpi::SmallString<64> buf;
|
||||
table->GetEntry("source").SetString(
|
||||
MakeSourceValue(event.sourceHandle, buf));
|
||||
wpi::SmallString<64> descBuf;
|
||||
table->GetEntry("description")
|
||||
.SetString(cs::GetSourceDescription(event.sourceHandle, descBuf,
|
||||
&status));
|
||||
table->GetEntry("connected")
|
||||
.SetBoolean(cs::IsSourceConnected(event.sourceHandle, &status));
|
||||
table->GetEntry("streams").SetStringArray(
|
||||
GetSourceStreamValues(event.sourceHandle));
|
||||
auto mode = cs::GetSourceVideoMode(event.sourceHandle, &status);
|
||||
table->GetEntry("mode").SetDefaultString(VideoModeToString(mode));
|
||||
table->GetEntry("modes").SetStringArray(
|
||||
GetSourceModeValues(event.sourceHandle));
|
||||
m_publishers.insert(
|
||||
{event.sourceHandle,
|
||||
SourcePublisher{*this, table, event.sourceHandle}});
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSourceDestroyed: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
table->GetEntry("source").SetString("");
|
||||
table->GetEntry("streams").SetStringArray(
|
||||
std::vector<std::string>{});
|
||||
table->GetEntry("modes").SetStringArray(
|
||||
std::vector<std::string>{});
|
||||
}
|
||||
case cs::VideoEvent::kSourceDestroyed:
|
||||
m_publishers.erase(event.sourceHandle);
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSourceConnected: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
case cs::VideoEvent::kSourceConnected:
|
||||
if (auto publisher = GetPublisher(event.sourceHandle)) {
|
||||
// update the description too (as it may have changed)
|
||||
wpi::SmallString<64> descBuf;
|
||||
table->GetEntry("description")
|
||||
.SetString(cs::GetSourceDescription(event.sourceHandle,
|
||||
descBuf, &status));
|
||||
table->GetEntry("connected").SetBoolean(true);
|
||||
publisher->descriptionPublisher.Set(cs::GetSourceDescription(
|
||||
event.sourceHandle, descBuf, &status));
|
||||
publisher->connectedPublisher.Set(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSourceDisconnected: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
table->GetEntry("connected").SetBoolean(false);
|
||||
case cs::VideoEvent::kSourceDisconnected:
|
||||
if (auto publisher = GetPublisher(event.sourceHandle)) {
|
||||
publisher->connectedPublisher.Set(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSourceVideoModesUpdated: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
table->GetEntry("modes").SetStringArray(
|
||||
case cs::VideoEvent::kSourceVideoModesUpdated:
|
||||
if (auto publisher = GetPublisher(event.sourceHandle)) {
|
||||
publisher->modesPublisher.Set(
|
||||
GetSourceModeValues(event.sourceHandle));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSourceVideoModeChanged: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
table->GetEntry("mode").SetString(VideoModeToString(event.mode));
|
||||
case cs::VideoEvent::kSourceVideoModeChanged:
|
||||
if (auto publisher = GetPublisher(event.sourceHandle)) {
|
||||
publisher->modeEntry.Set(VideoModeToString(event.mode));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSourcePropertyCreated: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
PutSourcePropertyValue(table.get(), event, true);
|
||||
case cs::VideoEvent::kSourcePropertyCreated:
|
||||
if (auto publisher = GetPublisher(event.sourceHandle)) {
|
||||
publisher->properties.insert(
|
||||
{event.propertyHandle,
|
||||
PropertyPublisher{*publisher->table, event}});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSourcePropertyValueUpdated: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
PutSourcePropertyValue(table.get(), event, false);
|
||||
case cs::VideoEvent::kSourcePropertyValueUpdated:
|
||||
if (auto publisher = GetPublisher(event.sourceHandle)) {
|
||||
auto ppIt = publisher->properties.find(event.propertyHandle);
|
||||
if (ppIt != publisher->properties.end()) {
|
||||
ppIt->second.Update(event);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSourcePropertyChoicesUpdated: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
auto choices =
|
||||
cs::GetEnumPropertyChoices(event.propertyHandle, &status);
|
||||
table
|
||||
->GetEntry(fmt::format("PropertyInfo/{}/choices", event.name))
|
||||
.SetStringArray(choices);
|
||||
case cs::VideoEvent::kSourcePropertyChoicesUpdated:
|
||||
if (auto publisher = GetPublisher(event.sourceHandle)) {
|
||||
auto ppIt = publisher->properties.find(event.propertyHandle);
|
||||
if (ppIt != publisher->properties.end() &&
|
||||
ppIt->second.choicesTopic) {
|
||||
auto choices =
|
||||
cs::GetEnumPropertyChoices(event.propertyHandle, &status);
|
||||
if (!ppIt->second.choicesPublisher) {
|
||||
ppIt->second.choicesPublisher =
|
||||
ppIt->second.choicesTopic.Publish();
|
||||
}
|
||||
ppIt->second.choicesPublisher.Set(choices);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSinkSourceChanged:
|
||||
case cs::VideoEvent::kSinkCreated:
|
||||
case cs::VideoEvent::kSinkDestroyed:
|
||||
case cs::VideoEvent::kNetworkInterfacesChanged: {
|
||||
case cs::VideoEvent::kNetworkInterfacesChanged:
|
||||
m_addresses = cs::GetNetworkInterfaces();
|
||||
UpdateStreamValues();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
0x4fff, true};
|
||||
|
||||
// Listener for NetworkTable events
|
||||
// We don't currently support changing settings via NT due to
|
||||
// synchronization issues, so just update to current setting if someone
|
||||
// else tries to change it.
|
||||
wpi::SmallString<64> buf;
|
||||
m_tableListener = nt::NetworkTableInstance::GetDefault().AddEntryListener(
|
||||
fmt::format("{}/", kPublishName),
|
||||
[=](const nt::EntryNotification& event) {
|
||||
auto relativeKey = wpi::drop_front(
|
||||
event.name, std::string_view{kPublishName}.size() + 1);
|
||||
|
||||
// get source (sourceName/...)
|
||||
auto subKeyIndex = relativeKey.find('/');
|
||||
if (subKeyIndex == std::string_view::npos) {
|
||||
return;
|
||||
}
|
||||
auto sourceName = wpi::slice(relativeKey, 0, subKeyIndex);
|
||||
auto sourceIt = m_sources.find(sourceName);
|
||||
if (sourceIt == m_sources.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get subkey
|
||||
relativeKey.remove_prefix(subKeyIndex + 1);
|
||||
|
||||
// handle standard names
|
||||
std::string_view propName;
|
||||
nt::NetworkTableEntry entry{event.entry};
|
||||
if (relativeKey == "mode") {
|
||||
// reset to current mode
|
||||
entry.SetString(VideoModeToString(sourceIt->second.GetVideoMode()));
|
||||
return;
|
||||
} else if (wpi::starts_with(relativeKey, "Property/")) {
|
||||
propName = wpi::substr(relativeKey, 9);
|
||||
} else if (wpi::starts_with(relativeKey, "RawProperty/")) {
|
||||
propName = wpi::substr(relativeKey, 12);
|
||||
} else {
|
||||
return; // ignore
|
||||
}
|
||||
|
||||
// everything else is a property
|
||||
auto property = sourceIt->second.GetProperty(propName);
|
||||
switch (property.GetKind()) {
|
||||
case cs::VideoProperty::kNone:
|
||||
return;
|
||||
case cs::VideoProperty::kBoolean:
|
||||
entry.SetBoolean(property.Get() != 0);
|
||||
return;
|
||||
case cs::VideoProperty::kInteger:
|
||||
case cs::VideoProperty::kEnum:
|
||||
entry.SetDouble(property.Get());
|
||||
return;
|
||||
case cs::VideoProperty::kString:
|
||||
entry.SetString(property.GetString());
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
},
|
||||
NT_NOTIFY_IMMEDIATE | NT_NOTIFY_UPDATE);
|
||||
}
|
||||
|
||||
cs::UsbCamera CameraServer::StartAutomaticCapture() {
|
||||
@@ -525,7 +511,7 @@ cs::AxisCamera CameraServer::AddAxisCamera(const std::string& host) {
|
||||
return AddAxisCamera("Axis Camera", host);
|
||||
}
|
||||
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(wpi::span<const std::string> hosts) {
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(std::span<const std::string> hosts) {
|
||||
return AddAxisCamera("Axis Camera", hosts);
|
||||
}
|
||||
|
||||
@@ -557,7 +543,7 @@ cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
|
||||
}
|
||||
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
|
||||
wpi::span<const std::string> hosts) {
|
||||
std::span<const std::string> hosts) {
|
||||
cs::AxisCamera camera{name, hosts};
|
||||
StartAutomaticCapture(camera);
|
||||
auto csShared = GetCameraServerShared();
|
||||
@@ -615,7 +601,7 @@ cs::CvSink CameraServer::GetVideo(const cs::VideoSource& camera) {
|
||||
if (kind != cs::VideoSink::kCv) {
|
||||
auto csShared = GetCameraServerShared();
|
||||
csShared->SetCameraServerError("expected OpenCV sink, but got {}",
|
||||
kind);
|
||||
static_cast<int>(kind));
|
||||
return cs::CvSink{};
|
||||
}
|
||||
return *static_cast<cs::CvSink*>(&it->second);
|
||||
|
||||
@@ -6,12 +6,10 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <wpi/deprecated.h>
|
||||
#include <wpi/span.h>
|
||||
|
||||
#include "cscore.h"
|
||||
#include "cscore_cv.h"
|
||||
|
||||
@@ -29,13 +27,6 @@ class CameraServer {
|
||||
static constexpr int kSize320x240 = 1;
|
||||
static constexpr int kSize160x120 = 2;
|
||||
|
||||
/**
|
||||
* Get the CameraServer instance.
|
||||
* @deprecated Use the static methods
|
||||
*/
|
||||
WPI_DEPRECATED("Use static methods")
|
||||
static CameraServer* GetInstance();
|
||||
|
||||
/**
|
||||
* Start automatically capturing images to send to the dashboard.
|
||||
*
|
||||
@@ -118,7 +109,7 @@ class CameraServer {
|
||||
*
|
||||
* @param hosts Array of Camera host IPs/DNS names
|
||||
*/
|
||||
static cs::AxisCamera AddAxisCamera(wpi::span<const std::string> hosts);
|
||||
static cs::AxisCamera AddAxisCamera(std::span<const std::string> hosts);
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
@@ -163,7 +154,7 @@ class CameraServer {
|
||||
* @param hosts Array of Camera host IPs/DNS names
|
||||
*/
|
||||
static cs::AxisCamera AddAxisCamera(std::string_view name,
|
||||
wpi::span<const std::string> hosts);
|
||||
std::span<const std::string> hosts);
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
|
||||
@@ -27,20 +27,17 @@ class CameraServerShared {
|
||||
|
||||
template <typename S, typename... Args>
|
||||
inline void SetCameraServerError(const S& format, Args&&... args) {
|
||||
SetCameraServerErrorV(format,
|
||||
fmt::make_args_checked<Args...>(format, args...));
|
||||
SetCameraServerErrorV(format, fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename... Args>
|
||||
inline void SetVisionRunnerError(const S& format, Args&&... args) {
|
||||
SetVisionRunnerErrorV(format,
|
||||
fmt::make_args_checked<Args...>(format, args...));
|
||||
SetVisionRunnerErrorV(format, fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename... Args>
|
||||
inline void ReportDriverStationError(const S& format, Args&&... args) {
|
||||
ReportDriverStationErrorV(format,
|
||||
fmt::make_args_checked<Args...>(format, args...));
|
||||
ReportDriverStationErrorV(format, fmt::make_format_args(args...));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
macro(wpilib_target_warnings target)
|
||||
if(NOT MSVC)
|
||||
target_compile_options(${target} PRIVATE -Wall -pedantic -Wextra -Werror -Wno-unused-parameter -Wno-error=deprecated-declarations)
|
||||
target_compile_options(${target} PRIVATE -Wall -pedantic -Wextra -Werror -Wno-unused-parameter ${WPILIB_TARGET_WARNINGS})
|
||||
else()
|
||||
target_compile_options(${target} PRIVATE /wd4146 /wd4244 /wd4251 /wd4267 /wd4996 /WX)
|
||||
target_compile_options(${target} PRIVATE /wd4146 /wd4244 /wd4251 /wd4267 /WX /D_CRT_SECURE_NO_WARNINGS ${WPILIB_TARGET_WARNINGS})
|
||||
endif()
|
||||
|
||||
# Suppress C++-specific OpenCV warning; C compiler rejects it with an error
|
||||
# https://github.com/opencv/opencv/issues/20269
|
||||
if(UNIX AND NOT APPLE)
|
||||
target_compile_options(${target} PRIVATE $<$<COMPILE_LANGUAGE:CXX>:-Wno-deprecated-enum-enum-conversion>)
|
||||
elseif(UNIX AND APPLE)
|
||||
target_compile_options(${target} PRIVATE $<$<COMPILE_LANGUAGE:CXX>:-Wno-deprecated-anon-enum-enum-conversion>)
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
@@ -110,7 +110,7 @@ else()
|
||||
set(LIBSSH_LIBRARIES ${LIBSSH_LIBRARY} ${LIBSSH_THREADS_LIBRARY})
|
||||
mark_as_advanced(LIBSSH_INCLUDE_DIRS LIBSSH_LIBRARIES)
|
||||
|
||||
find_package_handle_standard_args(LibSSH FOUND_VAR LIBSSH_FOUND
|
||||
find_package_handle_standard_args(LIBSSH FOUND_VAR LIBSSH_FOUND
|
||||
REQUIRED_VARS LIBSSH_INCLUDE_DIRS LIBSSH_LIBRARIES
|
||||
VERSION_VAR LIBSSH_VERSION)
|
||||
endif()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 2.8)
|
||||
cmake_minimum_required(VERSION 3.3.0)
|
||||
|
||||
# load settings in case of "try compile"
|
||||
set(TOOLCHAIN_CONFIG_FILE "${WPILIB_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/toolchain.config.cmake")
|
||||
|
||||
@@ -84,13 +84,9 @@ model {
|
||||
}
|
||||
}
|
||||
}
|
||||
binary.tasks.withType(CppCompile) {
|
||||
cppCompiler.args "-Wno-missing-field-initializers"
|
||||
cppCompiler.args "-Wno-unused-variable"
|
||||
cppCompiler.args "-Wno-error=deprecated-declarations"
|
||||
}
|
||||
project(':hal').addHalDependency(binary, 'shared')
|
||||
project(':hal').addHalJniDependency(binary)
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
if (binary.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
|
||||
nativeUtils.useRequiredLibrary(binary, 'ni_link_libraries', 'ni_runtime_libraries')
|
||||
|
||||
@@ -100,8 +100,9 @@ void TestTimingDMA(int squelch, std::pair<int, int> param) {
|
||||
auto value = HAL_GetDMASampleDigitalSource(&dmaSamples[startIndex],
|
||||
dioHandle, &status);
|
||||
ASSERT_EQ(0, status);
|
||||
if (value)
|
||||
if (value) {
|
||||
break;
|
||||
}
|
||||
startIndex++;
|
||||
}
|
||||
ASSERT_LT(startIndex, 6);
|
||||
|
||||
@@ -49,6 +49,7 @@ class TestEnvironment : public testing::Environment {
|
||||
HAL_GetControlWord(&controlWord);
|
||||
return controlWord.enabled && controlWord.dsAttached;
|
||||
};
|
||||
HAL_RefreshDSData();
|
||||
while (!checkEnabled()) {
|
||||
if (enableCounter > 50) {
|
||||
// Robot did not enable properly after 5 seconds.
|
||||
@@ -60,6 +61,7 @@ class TestEnvironment : public testing::Environment {
|
||||
std::this_thread::sleep_for(100ms);
|
||||
|
||||
fmt::print("Waiting for enable: {}\n", enableCounter++);
|
||||
HAL_RefreshDSData();
|
||||
}
|
||||
std::this_thread::sleep_for(500ms);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <hal/cpp/fpga_clock.h>
|
||||
#include <wpi/Logger.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/UDPClient.h>
|
||||
#include <wpinet/UDPClient.h>
|
||||
|
||||
static void LoggerFunc(unsigned int level, const char* file, unsigned int line,
|
||||
const char* msg) {
|
||||
|
||||
@@ -194,6 +194,7 @@ struct RelayHandle {
|
||||
do { \
|
||||
ASSERT_EQ(status, HAL_USE_LAST_ERROR); \
|
||||
const char* lastErrorMessageInMacro = HAL_GetLastError(&status); \
|
||||
static_cast<void>(lastErrorMessageInMacro); \
|
||||
ASSERT_EQ(status, x); \
|
||||
} while (0)
|
||||
|
||||
|
||||
@@ -36,4 +36,5 @@ includeOtherLibs {
|
||||
^support/
|
||||
^tcpsockets/
|
||||
^wpi/
|
||||
^wpinet/
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ target_include_directories(cscore PUBLIC
|
||||
$<INSTALL_INTERFACE:${include_dest}/cscore>)
|
||||
target_include_directories(cscore PRIVATE src/main/native/cpp)
|
||||
wpilib_target_warnings(cscore)
|
||||
target_link_libraries(cscore PUBLIC wpiutil ${OpenCV_LIBS})
|
||||
target_link_libraries(cscore PUBLIC wpinet wpiutil ${OpenCV_LIBS})
|
||||
|
||||
set_property(TARGET cscore PROPERTY FOLDER "libraries")
|
||||
|
||||
|
||||
@@ -23,8 +23,8 @@ model {
|
||||
enableCheckTask true
|
||||
javaCompileTasks << compileJava
|
||||
jniCrossCompileOptions << JniCrossCompileOptions(nativeUtils.wpi.platforms.roborio)
|
||||
jniCrossCompileOptions << JniCrossCompileOptions(nativeUtils.wpi.platforms.raspbian)
|
||||
jniCrossCompileOptions << JniCrossCompileOptions(nativeUtils.wpi.platforms.aarch64bionic)
|
||||
jniCrossCompileOptions << JniCrossCompileOptions(nativeUtils.wpi.platforms.linuxarm32)
|
||||
jniCrossCompileOptions << JniCrossCompileOptions(nativeUtils.wpi.platforms.linuxarm64)
|
||||
|
||||
sources {
|
||||
cpp {
|
||||
@@ -45,6 +45,7 @@ model {
|
||||
return
|
||||
}
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
|
||||
|
||||
if (it.targetPlatform.operatingSystem.linux) {
|
||||
it.linker.args '-Wl,--version-script=' + file('src/main/native/LinuxSymbolScript.txt')
|
||||
@@ -55,6 +56,15 @@ model {
|
||||
}
|
||||
}
|
||||
}
|
||||
binaries {
|
||||
all {
|
||||
if (!it.buildable || !(it instanceof NativeBinarySpec)) {
|
||||
return
|
||||
}
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -144,19 +154,6 @@ Action<List<String>> symbolFilter = { symbols ->
|
||||
|
||||
nativeUtils.exportsConfigs {
|
||||
cscore {
|
||||
x86ExcludeSymbols = [
|
||||
'_CT??_R0?AV_System_error',
|
||||
'_CT??_R0?AVexception',
|
||||
'_CT??_R0?AVfailure',
|
||||
'_CT??_R0?AVruntime_error',
|
||||
'_CT??_R0?AVsystem_error',
|
||||
'_CTA5?AVfailure',
|
||||
'_TI5?AVfailure',
|
||||
'_CT??_R0?AVout_of_range',
|
||||
'_CTA3?AVout_of_range',
|
||||
'_TI3?AVout_of_range',
|
||||
'_CT??_R0?AVbad_cast'
|
||||
]
|
||||
x64ExcludeSymbols = [
|
||||
'_CT??_R0?AV_System_error',
|
||||
'_CT??_R0?AVexception',
|
||||
@@ -172,11 +169,9 @@ nativeUtils.exportsConfigs {
|
||||
]
|
||||
}
|
||||
cscoreJNI {
|
||||
x86SymbolFilter = symbolFilter
|
||||
x64SymbolFilter = symbolFilter
|
||||
}
|
||||
cscoreJNICvStatic {
|
||||
x86SymbolFilter = symbolFilter
|
||||
x64SymbolFilter = symbolFilter
|
||||
}
|
||||
}
|
||||
@@ -185,15 +180,16 @@ model {
|
||||
components {
|
||||
examplesMap.each { key, value ->
|
||||
if (key == "usbviewer") {
|
||||
if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxraspbian') && !project.hasProperty('onlylinuxaarch64bionic')) {
|
||||
if (!project.hasProperty('onlylinuxathena')) {
|
||||
"${key}"(NativeExecutableSpec) {
|
||||
targetBuildTypes 'debug'
|
||||
binaries.all {
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
|
||||
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
|
||||
lib library: 'cscore', linkage: 'shared'
|
||||
nativeUtils.useRequiredLibrary(it, 'imgui_static')
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.raspbian || it.targetPlatform.name == nativeUtils.wpi.platforms.aarch64bionic) {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
@@ -203,6 +199,9 @@ model {
|
||||
it.linker.args << '-framework' << 'Metal' << '-framework' << 'MetalKit' << '-framework' << 'Cocoa' << '-framework' << 'IOKit' << '-framework' << 'CoreFoundation' << '-framework' << 'CoreVideo' << '-framework' << 'QuartzCore'
|
||||
} else {
|
||||
it.linker.args << '-lX11'
|
||||
if (it.targetPlatform.name.startsWith('linuxarm')) {
|
||||
it.linker.args << '-lGL'
|
||||
}
|
||||
}
|
||||
}
|
||||
sources {
|
||||
@@ -220,6 +219,7 @@ model {
|
||||
targetBuildTypes 'debug'
|
||||
binaries.all {
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
|
||||
lib library: 'cscore', linkage: 'shared'
|
||||
}
|
||||
sources {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package edu.wpi.first.cscore;
|
||||
|
||||
/** USB camera information. */
|
||||
@SuppressWarnings("MemberName")
|
||||
public class UsbCameraInfo {
|
||||
/**
|
||||
* Create a new set of UsbCameraInfo.
|
||||
@@ -28,26 +29,20 @@ public class UsbCameraInfo {
|
||||
}
|
||||
|
||||
/** Device number (e.g. N in '/dev/videoN' on Linux). */
|
||||
@SuppressWarnings("MemberName")
|
||||
public int dev;
|
||||
|
||||
/** Path to device if available (e.g. '/dev/video0' on Linux). */
|
||||
@SuppressWarnings("MemberName")
|
||||
public String path;
|
||||
|
||||
/** Vendor/model name of the camera as provided by the USB driver. */
|
||||
@SuppressWarnings("MemberName")
|
||||
public String name;
|
||||
|
||||
/** Other path aliases to device (e.g. '/dev/v4l/by-id/...' etc on Linux). */
|
||||
@SuppressWarnings("MemberName")
|
||||
public String[] otherPaths;
|
||||
|
||||
/** USB vendor id. */
|
||||
@SuppressWarnings("MemberName")
|
||||
public int vendorId;
|
||||
|
||||
/** USB product id. */
|
||||
@SuppressWarnings("MemberName")
|
||||
public int productId;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package edu.wpi.first.cscore;
|
||||
|
||||
/** Video event. */
|
||||
@SuppressWarnings("MemberName")
|
||||
public class VideoEvent {
|
||||
public enum Kind {
|
||||
kUnknown(0x0000),
|
||||
@@ -117,39 +118,29 @@ public class VideoEvent {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
public Kind kind;
|
||||
|
||||
// Valid for kSource* and kSink* respectively
|
||||
@SuppressWarnings("MemberName")
|
||||
public int sourceHandle;
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
public int sinkHandle;
|
||||
|
||||
// Source/sink/property name
|
||||
@SuppressWarnings("MemberName")
|
||||
public String name;
|
||||
|
||||
// Fields for kSourceVideoModeChanged event
|
||||
@SuppressWarnings("MemberName")
|
||||
public VideoMode mode;
|
||||
|
||||
// Fields for kSourceProperty* events
|
||||
@SuppressWarnings("MemberName")
|
||||
public int propertyHandle;
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
public VideoProperty.Kind propertyKind;
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
public int value;
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
public String valueStr;
|
||||
|
||||
// Listener that was triggered
|
||||
@SuppressWarnings("MemberName")
|
||||
public int listener;
|
||||
|
||||
public VideoSource getSource() {
|
||||
|
||||
@@ -64,7 +64,6 @@ public class VideoListener implements AutoCloseable {
|
||||
private static boolean s_waitQueue;
|
||||
private static final Condition s_waitQueueCond = s_lock.newCondition();
|
||||
|
||||
@SuppressWarnings("PMD.AvoidCatchingThrowable")
|
||||
private static void startThread() {
|
||||
s_thread =
|
||||
new Thread(
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package edu.wpi.first.cscore;
|
||||
|
||||
/** Video mode. */
|
||||
@SuppressWarnings("MemberName")
|
||||
public class VideoMode {
|
||||
public enum PixelFormat {
|
||||
kUnknown(0),
|
||||
@@ -62,18 +63,14 @@ public class VideoMode {
|
||||
}
|
||||
|
||||
/** Pixel format. */
|
||||
@SuppressWarnings("MemberName")
|
||||
public PixelFormat pixelFormat;
|
||||
|
||||
/** Width in pixels. */
|
||||
@SuppressWarnings("MemberName")
|
||||
public int width;
|
||||
|
||||
/** Height in pixels. */
|
||||
@SuppressWarnings("MemberName")
|
||||
public int height;
|
||||
|
||||
/** Frames per second. */
|
||||
@SuppressWarnings("MemberName")
|
||||
public int fps;
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ int ConfigurableSourceImpl::CreateProperty(
|
||||
}
|
||||
|
||||
void ConfigurableSourceImpl::SetEnumPropertyChoices(
|
||||
int property, wpi::span<const std::string> choices, CS_Status* status) {
|
||||
int property, std::span<const std::string> choices, CS_Status* status) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) {
|
||||
|
||||
@@ -8,12 +8,11 @@
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/span.h>
|
||||
|
||||
#include "SourceImpl.h"
|
||||
|
||||
namespace cs {
|
||||
@@ -42,7 +41,7 @@ class ConfigurableSourceImpl : public SourceImpl {
|
||||
int maximum, int step, int defaultValue, int value,
|
||||
std::function<void(CS_Property property)> onChange);
|
||||
void SetEnumPropertyChoices(int property,
|
||||
wpi::span<const std::string> choices,
|
||||
std::span<const std::string> choices,
|
||||
CS_Status* status);
|
||||
|
||||
private:
|
||||
|
||||
@@ -110,7 +110,7 @@ void CvSinkImpl::ThreadMain() {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
continue;
|
||||
}
|
||||
SDEBUG4("{}", "waiting for frame");
|
||||
SDEBUG4("waiting for frame");
|
||||
Frame frame = source->GetNextFrame(); // blocks
|
||||
if (!m_active) {
|
||||
break;
|
||||
|
||||
@@ -139,7 +139,7 @@ CS_Property CreateSourcePropertyCallback(
|
||||
}
|
||||
|
||||
void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
|
||||
wpi::span<const std::string> choices,
|
||||
std::span<const std::string> choices,
|
||||
CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || (data->kind & SourceMask) == 0) {
|
||||
|
||||
@@ -523,7 +523,8 @@ Image* Frame::GetImageImpl(int width, int height,
|
||||
|
||||
WPI_DEBUG4(Instance::GetInstance().logger,
|
||||
"converting image from {}x{} type {} to {}x{} type {}", cur->width,
|
||||
cur->height, cur->pixelFormat, width, height, pixelFormat);
|
||||
cur->height, static_cast<int>(cur->pixelFormat), width, height,
|
||||
static_cast<int>(pixelFormat));
|
||||
|
||||
// If the source image is a JPEG, we need to decode it before we can do
|
||||
// anything else with it. Note that if the destination format is JPEG, we
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
#include <wpi/MemAlloc.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
#include <wpi/TCPConnector.h>
|
||||
#include <wpi/timestamp.h>
|
||||
#include <wpinet/TCPConnector.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "Instance.h"
|
||||
@@ -75,7 +75,7 @@ void HttpCameraImpl::MonitorThreadMain() {
|
||||
std::unique_lock lock(m_mutex);
|
||||
// sleep for 1 second between checks
|
||||
m_monitorCond.wait_for(lock, std::chrono::seconds(1),
|
||||
[=] { return !m_active; });
|
||||
[=, this] { return !m_active; });
|
||||
|
||||
if (!m_active) {
|
||||
break;
|
||||
@@ -85,7 +85,7 @@ void HttpCameraImpl::MonitorThreadMain() {
|
||||
// (this will result in an error at the read point, and ultimately
|
||||
// a reconnect attempt)
|
||||
if (m_streamConn && m_frameCount == 0) {
|
||||
SWARNING("{}", "Monitor detected stream hung, disconnecting");
|
||||
SWARNING("Monitor detected stream hung, disconnecting");
|
||||
m_streamConn->stream->close();
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ void HttpCameraImpl::MonitorThreadMain() {
|
||||
m_frameCount = 0;
|
||||
}
|
||||
|
||||
SDEBUG("{}", "Monitor Thread exiting");
|
||||
SDEBUG("Monitor Thread exiting");
|
||||
}
|
||||
|
||||
void HttpCameraImpl::StreamThreadMain() {
|
||||
@@ -110,7 +110,8 @@ void HttpCameraImpl::StreamThreadMain() {
|
||||
m_streamConn->stream->close();
|
||||
}
|
||||
// Wait for enable
|
||||
m_sinkEnabledCond.wait(lock, [=] { return !m_active || IsEnabled(); });
|
||||
m_sinkEnabledCond.wait(lock,
|
||||
[=, this] { return !m_active || IsEnabled(); });
|
||||
if (!m_active) {
|
||||
return;
|
||||
}
|
||||
@@ -140,7 +141,7 @@ void HttpCameraImpl::StreamThreadMain() {
|
||||
}
|
||||
}
|
||||
|
||||
SDEBUG("{}", "Camera Thread exiting");
|
||||
SDEBUG("Camera Thread exiting");
|
||||
SetConnected(false);
|
||||
}
|
||||
|
||||
@@ -151,7 +152,7 @@ wpi::HttpConnection* HttpCameraImpl::DeviceStreamConnect(
|
||||
{
|
||||
std::scoped_lock lock(m_mutex);
|
||||
if (m_locations.empty()) {
|
||||
SERROR("{}", "locations array is empty!?");
|
||||
SERROR("locations array is empty!?");
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
return nullptr;
|
||||
}
|
||||
@@ -272,7 +273,7 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
|
||||
wpi::SmallString<64> contentTypeBuf;
|
||||
wpi::SmallString<64> contentLengthBuf;
|
||||
if (!ParseHttpHeaders(is, &contentTypeBuf, &contentLengthBuf)) {
|
||||
SWARNING("{}", "disconnected during headers");
|
||||
SWARNING("disconnected during headers");
|
||||
PutError("disconnected during headers", wpi::Now());
|
||||
return false;
|
||||
}
|
||||
@@ -294,7 +295,7 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
|
||||
// Ugh, no Content-Length? Read the blocks of the JPEG file.
|
||||
int width, height;
|
||||
if (!ReadJpeg(is, imageBuf, &width, &height)) {
|
||||
SWARNING("{}", "did not receive a JPEG image");
|
||||
SWARNING("did not receive a JPEG image");
|
||||
PutError("did not receive a JPEG image", wpi::Now());
|
||||
return false;
|
||||
}
|
||||
@@ -313,7 +314,7 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
|
||||
}
|
||||
int width, height;
|
||||
if (!GetJpegSize(image->str(), &width, &height)) {
|
||||
SWARNING("{}", "did not receive a JPEG image");
|
||||
SWARNING("did not receive a JPEG image");
|
||||
PutError("did not receive a JPEG image", wpi::Now());
|
||||
return false;
|
||||
}
|
||||
@@ -329,7 +330,7 @@ void HttpCameraImpl::SettingsThreadMain() {
|
||||
wpi::HttpRequest req;
|
||||
{
|
||||
std::unique_lock lock(m_mutex);
|
||||
m_settingsCond.wait(lock, [=] {
|
||||
m_settingsCond.wait(lock, [=, this] {
|
||||
return !m_active || (m_prefLocation != -1 && !m_settings.empty());
|
||||
});
|
||||
if (!m_active) {
|
||||
@@ -343,7 +344,7 @@ void HttpCameraImpl::SettingsThreadMain() {
|
||||
DeviceSendSettings(req);
|
||||
}
|
||||
|
||||
SDEBUG("{}", "Settings Thread exiting");
|
||||
SDEBUG("Settings Thread exiting");
|
||||
}
|
||||
|
||||
void HttpCameraImpl::DeviceSendSettings(wpi::HttpRequest& req) {
|
||||
@@ -378,7 +379,7 @@ CS_HttpCameraKind HttpCameraImpl::GetKind() const {
|
||||
return m_kind;
|
||||
}
|
||||
|
||||
bool HttpCameraImpl::SetUrls(wpi::span<const std::string> urls,
|
||||
bool HttpCameraImpl::SetUrls(std::span<const std::string> urls,
|
||||
CS_Status* status) {
|
||||
std::vector<wpi::HttpLocation> locations;
|
||||
for (const auto& url : urls) {
|
||||
@@ -572,14 +573,14 @@ CS_Source CreateHttpCamera(std::string_view name, std::string_view url,
|
||||
break;
|
||||
}
|
||||
std::string urlStr{url};
|
||||
if (!source->SetUrls(wpi::span{&urlStr, 1}, status)) {
|
||||
if (!source->SetUrls(std::span{&urlStr, 1}, status)) {
|
||||
return 0;
|
||||
}
|
||||
return inst.CreateSource(CS_SOURCE_HTTP, source);
|
||||
}
|
||||
|
||||
CS_Source CreateHttpCamera(std::string_view name,
|
||||
wpi::span<const std::string> urls,
|
||||
std::span<const std::string> urls,
|
||||
CS_HttpCameraKind kind, CS_Status* status) {
|
||||
auto& inst = Instance::GetInstance();
|
||||
if (urls.empty()) {
|
||||
@@ -603,7 +604,7 @@ CS_HttpCameraKind GetHttpCameraKind(CS_Source source, CS_Status* status) {
|
||||
return static_cast<HttpCameraImpl&>(*data->source).GetKind();
|
||||
}
|
||||
|
||||
void SetHttpCameraUrls(CS_Source source, wpi::span<const std::string> urls,
|
||||
void SetHttpCameraUrls(CS_Source source, std::span<const std::string> urls,
|
||||
CS_Status* status) {
|
||||
if (urls.empty()) {
|
||||
*status = CS_EMPTY_VALUE;
|
||||
|
||||
@@ -9,17 +9,17 @@
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/HttpUtil.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/StringMap.h>
|
||||
#include <wpi/condition_variable.h>
|
||||
#include <wpi/raw_istream.h>
|
||||
#include <wpi/span.h>
|
||||
#include <wpinet/HttpUtil.h>
|
||||
|
||||
#include "SourceImpl.h"
|
||||
#include "cscore_cpp.h"
|
||||
@@ -55,7 +55,7 @@ class HttpCameraImpl : public SourceImpl {
|
||||
void NumSinksEnabledChanged() override;
|
||||
|
||||
CS_HttpCameraKind GetKind() const;
|
||||
bool SetUrls(wpi::span<const std::string> urls, CS_Status* status);
|
||||
bool SetUrls(std::span<const std::string> urls, CS_Status* status);
|
||||
std::vector<std::string> GetUrls() const;
|
||||
|
||||
// Property data
|
||||
|
||||
@@ -22,7 +22,9 @@ class Image {
|
||||
|
||||
public:
|
||||
#ifndef __linux__
|
||||
explicit Image(size_t capacity) { m_data.reserve(capacity); }
|
||||
explicit Image(size_t capacity) {
|
||||
m_data.reserve(capacity);
|
||||
}
|
||||
#else
|
||||
explicit Image(size_t capacity)
|
||||
: m_data{capacity, default_init_allocator<uchar>{}} {
|
||||
@@ -34,20 +36,38 @@ class Image {
|
||||
Image& operator=(const Image&) = delete;
|
||||
|
||||
// Getters
|
||||
operator std::string_view() const { return str(); } // NOLINT
|
||||
std::string_view str() const { return {data(), size()}; }
|
||||
size_t capacity() const { return m_data.capacity(); }
|
||||
operator std::string_view() const { // NOLINT
|
||||
return str();
|
||||
}
|
||||
std::string_view str() const {
|
||||
return {data(), size()};
|
||||
}
|
||||
size_t capacity() const {
|
||||
return m_data.capacity();
|
||||
}
|
||||
const char* data() const {
|
||||
return reinterpret_cast<const char*>(m_data.data());
|
||||
}
|
||||
char* data() { return reinterpret_cast<char*>(m_data.data()); }
|
||||
size_t size() const { return m_data.size(); }
|
||||
char* data() {
|
||||
return reinterpret_cast<char*>(m_data.data());
|
||||
}
|
||||
size_t size() const {
|
||||
return m_data.size();
|
||||
}
|
||||
|
||||
const std::vector<uchar>& vec() const { return m_data; }
|
||||
std::vector<uchar>& vec() { return m_data; }
|
||||
const std::vector<uchar>& vec() const {
|
||||
return m_data;
|
||||
}
|
||||
std::vector<uchar>& vec() {
|
||||
return m_data;
|
||||
}
|
||||
|
||||
void resize(size_t size) { m_data.resize(size); }
|
||||
void SetSize(size_t size) { m_data.resize(size); }
|
||||
void resize(size_t size) {
|
||||
m_data.resize(size);
|
||||
}
|
||||
void SetSize(size_t size) {
|
||||
m_data.resize(size);
|
||||
}
|
||||
|
||||
cv::Mat AsMat() {
|
||||
int type;
|
||||
@@ -68,7 +88,9 @@ class Image {
|
||||
return cv::Mat{height, width, type, m_data.data()};
|
||||
}
|
||||
|
||||
cv::_InputArray AsInputArray() { return cv::_InputArray{m_data}; }
|
||||
cv::_InputArray AsInputArray() {
|
||||
return cv::_InputArray{m_data};
|
||||
}
|
||||
|
||||
bool Is(int width_, int height_) {
|
||||
return width == width_ && height == height_;
|
||||
@@ -90,8 +112,12 @@ class Image {
|
||||
bool IsLarger(const Image& oth) {
|
||||
return width >= oth.width && height >= oth.height;
|
||||
}
|
||||
bool IsSmaller(int width_, int height_) { return !IsLarger(width_, height_); }
|
||||
bool IsSmaller(const Image& oth) { return !IsLarger(oth); }
|
||||
bool IsSmaller(int width_, int height_) {
|
||||
return !IsLarger(width_, height_);
|
||||
}
|
||||
bool IsSmaller(const Image& oth) {
|
||||
return !IsLarger(oth);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<uchar> m_data;
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <wpi/EventLoopRunner.h>
|
||||
#include <wpi/Logger.h>
|
||||
#include <wpinet/EventLoopRunner.h>
|
||||
|
||||
#include "Log.h"
|
||||
#include "NetworkListener.h"
|
||||
@@ -86,16 +86,16 @@ class Instance {
|
||||
void DestroySource(CS_Source handle);
|
||||
void DestroySink(CS_Sink handle);
|
||||
|
||||
wpi::span<CS_Source> EnumerateSourceHandles(
|
||||
std::span<CS_Source> EnumerateSourceHandles(
|
||||
wpi::SmallVectorImpl<CS_Source>& vec) {
|
||||
return m_sources.GetAll(vec);
|
||||
}
|
||||
|
||||
wpi::span<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec) {
|
||||
std::span<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec) {
|
||||
return m_sinks.GetAll(vec);
|
||||
}
|
||||
|
||||
wpi::span<CS_Sink> EnumerateSourceSinks(CS_Source source,
|
||||
std::span<CS_Sink> EnumerateSourceSinks(CS_Source source,
|
||||
wpi::SmallVectorImpl<CS_Sink>& vec) {
|
||||
vec.clear();
|
||||
m_sinks.ForEach([&](CS_Sink sinkHandle, const SinkData& data) {
|
||||
|
||||
@@ -21,32 +21,43 @@ inline void NamedLog(wpi::Logger& logger, unsigned int level, const char* file,
|
||||
Args&&... args) {
|
||||
if (logger.HasLogger() && level >= logger.min_level()) {
|
||||
NamedLogV(logger, level, file, line, name, format,
|
||||
fmt::make_args_checked<Args...>(format, args...));
|
||||
fmt::make_format_args(args...));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#define LOG(level, format, ...) WPI_LOG(m_logger, level, format, __VA_ARGS__)
|
||||
#define LOG(level, format, ...) \
|
||||
WPI_LOG(m_logger, level, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#undef ERROR
|
||||
#define ERROR(format, ...) WPI_ERROR(m_logger, format, __VA_ARGS__)
|
||||
#define WARNING(format, ...) WPI_WARNING(m_logger, format, __VA_ARGS__)
|
||||
#define INFO(format, ...) WPI_INFO(m_logger, format, __VA_ARGS__)
|
||||
#define ERROR(format, ...) \
|
||||
WPI_ERROR(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define WARNING(format, ...) \
|
||||
WPI_WARNING(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define INFO(format, ...) WPI_INFO(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#define DEBUG0(format, ...) WPI_DEBUG(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG1(format, ...) WPI_DEBUG1(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG2(format, ...) WPI_DEBUG2(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG3(format, ...) WPI_DEBUG3(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG4(format, ...) WPI_DEBUG4(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG0(format, ...) \
|
||||
WPI_DEBUG(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define DEBUG1(format, ...) \
|
||||
WPI_DEBUG1(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define DEBUG2(format, ...) \
|
||||
WPI_DEBUG2(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define DEBUG3(format, ...) \
|
||||
WPI_DEBUG3(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define DEBUG4(format, ...) \
|
||||
WPI_DEBUG4(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#define SLOG(level, format, ...) \
|
||||
NamedLog(m_logger, level, __FILE__, __LINE__, GetName(), FMT_STRING(format), \
|
||||
__VA_ARGS__)
|
||||
#define SLOG(level, format, ...) \
|
||||
NamedLog(m_logger, level, __FILE__, __LINE__, GetName(), \
|
||||
FMT_STRING(format) __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#define SERROR(format, ...) SLOG(::wpi::WPI_LOG_ERROR, format, __VA_ARGS__)
|
||||
#define SWARNING(format, ...) SLOG(::wpi::WPI_LOG_WARNING, format, __VA_ARGS__)
|
||||
#define SINFO(format, ...) SLOG(::wpi::WPI_LOG_INFO, format, __VA_ARGS__)
|
||||
#define SERROR(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_ERROR, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define SWARNING(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_WARNING, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define SINFO(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_INFO, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#ifdef NDEBUG
|
||||
#define SDEBUG(format, ...) \
|
||||
@@ -65,11 +76,16 @@ inline void NamedLog(wpi::Logger& logger, unsigned int level, const char* file,
|
||||
do { \
|
||||
} while (0)
|
||||
#else
|
||||
#define SDEBUG(format, ...) SLOG(::wpi::WPI_LOG_DEBUG, format, __VA_ARGS__)
|
||||
#define SDEBUG1(format, ...) SLOG(::wpi::WPI_LOG_DEBUG1, format, __VA_ARGS__)
|
||||
#define SDEBUG2(format, ...) SLOG(::wpi::WPI_LOG_DEBUG2, format, __VA_ARGS__)
|
||||
#define SDEBUG3(format, ...) SLOG(::wpi::WPI_LOG_DEBUG3, format, __VA_ARGS__)
|
||||
#define SDEBUG4(format, ...) SLOG(::wpi::WPI_LOG_DEBUG4, format, __VA_ARGS__)
|
||||
#define SDEBUG(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_DEBUG, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define SDEBUG1(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_DEBUG1, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define SDEBUG2(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_DEBUG2, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define SDEBUG3(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_DEBUG3, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define SDEBUG4(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_DEBUG4, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#endif // CSCORE_LOG_H_
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
#include <chrono>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <wpi/HttpUtil.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
#include <wpi/TCPAcceptor.h>
|
||||
#include <wpi/fmt/raw_ostream.h>
|
||||
#include <wpi/raw_socket_istream.h>
|
||||
#include <wpi/raw_socket_ostream.h>
|
||||
#include <wpinet/HttpUtil.h>
|
||||
#include <wpinet/TCPAcceptor.h>
|
||||
#include <wpinet/raw_socket_istream.h>
|
||||
#include <wpinet/raw_socket_ostream.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "Instance.h"
|
||||
@@ -495,7 +495,7 @@ void MjpegServerImpl::ConnThread::SendJSON(wpi::raw_ostream& os,
|
||||
auto kind = source.GetPropertyKind(prop);
|
||||
fmt::print(os, "\n\"name\": \"{}\"", name);
|
||||
fmt::print(os, ",\n\"id\": \"{}\"", prop);
|
||||
fmt::print(os, ",\n\"type\": \"{}\"", kind);
|
||||
fmt::print(os, ",\n\"type\": \"{}\"", static_cast<int>(kind));
|
||||
fmt::print(os, ",\n\"min\": \"{}\"", source.GetPropertyMin(prop, &status));
|
||||
fmt::print(os, ",\n\"max\": \"{}\"", source.GetPropertyMax(prop, &status));
|
||||
fmt::print(os, ",\n\"step\": \"{}\"",
|
||||
@@ -650,7 +650,7 @@ void MjpegServerImpl::Stop() {
|
||||
// Send HTTP response and a stream of JPG-frames
|
||||
void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
|
||||
if (m_noStreaming) {
|
||||
SERROR("{}", "Too many simultaneous client streams");
|
||||
SERROR("Too many simultaneous client streams");
|
||||
SendError(os, 503, "Too many simultaneous streams");
|
||||
return;
|
||||
}
|
||||
@@ -663,7 +663,7 @@ void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
|
||||
SendHeader(oss, 200, "OK", "multipart/x-mixed-replace;boundary=" BOUNDARY);
|
||||
os << oss.str();
|
||||
|
||||
SDEBUG("{}", "Headers send, sending stream now");
|
||||
SDEBUG("Headers send, sending stream now");
|
||||
|
||||
Frame::Time lastFrameTime = 0;
|
||||
Frame::Time timePerFrame = 0;
|
||||
@@ -685,7 +685,7 @@ void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
continue;
|
||||
}
|
||||
SDEBUG4("{}", "waiting for frame");
|
||||
SDEBUG4("waiting for frame");
|
||||
Frame frame = source->GetNextFrame(0.225); // blocks
|
||||
if (!m_active) {
|
||||
break;
|
||||
@@ -783,7 +783,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
wpi::SmallString<128> reqBuf;
|
||||
std::string_view req = is.getline(reqBuf, 4096);
|
||||
if (is.has_error()) {
|
||||
SDEBUG("{}", "error getting request string");
|
||||
SDEBUG("error getting request string");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -824,7 +824,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
} else if (req.find("GET / ") != std::string_view::npos || req == "GET /\n") {
|
||||
kind = kRootPage;
|
||||
} else {
|
||||
SDEBUG("{}", "HTTP request resource not found");
|
||||
SDEBUG("HTTP request resource not found");
|
||||
SendError(os, 404, "Resource not found");
|
||||
return;
|
||||
}
|
||||
@@ -866,11 +866,11 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
SendHeader(os, 200, "OK", "text/plain");
|
||||
os << "Ignored due to no connected source."
|
||||
<< "\r\n";
|
||||
SDEBUG("{}", "Ignored due to no connected source.");
|
||||
SDEBUG("Ignored due to no connected source.");
|
||||
}
|
||||
break;
|
||||
case kGetSettings:
|
||||
SDEBUG("{}", "request for JSON file");
|
||||
SDEBUG("request for JSON file");
|
||||
if (auto source = GetSource()) {
|
||||
SendJSON(os, *source, true);
|
||||
} else {
|
||||
@@ -878,7 +878,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
}
|
||||
break;
|
||||
case kGetSourceConfig:
|
||||
SDEBUG("{}", "request for JSON file");
|
||||
SDEBUG("request for JSON file");
|
||||
if (auto source = GetSource()) {
|
||||
SendHeader(os, 200, "OK", "application/json");
|
||||
CS_Status status = CS_OK;
|
||||
@@ -889,7 +889,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
}
|
||||
break;
|
||||
case kRootPage:
|
||||
SDEBUG("{}", "request for root page");
|
||||
SDEBUG("request for root page");
|
||||
SendHeader(os, 200, "OK", "text/html");
|
||||
if (auto source = GetSource()) {
|
||||
SendHTML(os, *source, false);
|
||||
@@ -900,7 +900,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
break;
|
||||
}
|
||||
|
||||
SDEBUG("{}", "leaving HTTP client thread");
|
||||
SDEBUG("leaving HTTP client thread");
|
||||
}
|
||||
|
||||
// worker thread for clients that connected to this server
|
||||
@@ -927,7 +927,7 @@ void MjpegServerImpl::ServerThreadMain() {
|
||||
return;
|
||||
}
|
||||
|
||||
SDEBUG("{}", "waiting for clients to connect");
|
||||
SDEBUG("waiting for clients to connect");
|
||||
while (m_active) {
|
||||
auto stream = m_acceptor->accept();
|
||||
if (!stream) {
|
||||
@@ -977,7 +977,7 @@ void MjpegServerImpl::ServerThreadMain() {
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
|
||||
SDEBUG("{}", "leaving server thread");
|
||||
SDEBUG("leaving server thread");
|
||||
}
|
||||
|
||||
void MjpegServerImpl::SetSourceImpl(std::shared_ptr<SourceImpl> source) {
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/NetworkAcceptor.h>
|
||||
#include <wpi/NetworkStream.h>
|
||||
#include <wpi/SafeThread.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/raw_istream.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
#include <wpi/raw_socket_ostream.h>
|
||||
#include <wpinet/NetworkAcceptor.h>
|
||||
#include <wpinet/NetworkStream.h>
|
||||
#include <wpinet/raw_socket_ostream.h>
|
||||
|
||||
#include "SinkImpl.h"
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ int PropertyContainer::GetPropertyIndex(std::string_view name) const {
|
||||
return ndx;
|
||||
}
|
||||
|
||||
wpi::span<int> PropertyContainer::EnumerateProperties(
|
||||
std::span<int> PropertyContainer::EnumerateProperties(
|
||||
wpi::SmallVectorImpl<int>& vec, CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status)) {
|
||||
return {};
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/StringMap.h>
|
||||
#include <wpi/mutex.h>
|
||||
#include <wpi/span.h>
|
||||
|
||||
#include "PropertyImpl.h"
|
||||
#include "cscore_cpp.h"
|
||||
@@ -33,7 +33,7 @@ class PropertyContainer {
|
||||
virtual ~PropertyContainer() = default;
|
||||
|
||||
int GetPropertyIndex(std::string_view name) const;
|
||||
wpi::span<int> EnumerateProperties(wpi::SmallVectorImpl<int>& vec,
|
||||
std::span<int> EnumerateProperties(wpi::SmallVectorImpl<int>& vec,
|
||||
CS_Status* status) const;
|
||||
CS_PropertyKind GetPropertyKind(int property) const;
|
||||
std::string_view GetPropertyName(int property,
|
||||
|
||||
@@ -127,7 +127,7 @@ void RawSinkImpl::ThreadMain() {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
continue;
|
||||
}
|
||||
SDEBUG4("{}", "waiting for frame");
|
||||
SDEBUG4("waiting for frame");
|
||||
Frame frame = source->GetNextFrame(); // blocks
|
||||
if (!m_active) {
|
||||
break;
|
||||
|
||||
@@ -76,7 +76,7 @@ Frame SourceImpl::GetCurFrame() {
|
||||
Frame SourceImpl::GetNextFrame() {
|
||||
std::unique_lock lock{m_frameMutex};
|
||||
auto oldTime = m_frame.GetTime();
|
||||
m_frameCv.wait(lock, [=] { return m_frame.GetTime() != oldTime; });
|
||||
m_frameCv.wait(lock, [=, this] { return m_frame.GetTime() != oldTime; });
|
||||
return m_frame;
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ Frame SourceImpl::GetNextFrame(double timeout) {
|
||||
auto oldTime = m_frame.GetTime();
|
||||
if (!m_frameCv.wait_for(
|
||||
lock, std::chrono::milliseconds(static_cast<int>(timeout * 1000)),
|
||||
[=] { return m_frame.GetTime() != oldTime; })) {
|
||||
[=, this] { return m_frame.GetTime() != oldTime; })) {
|
||||
m_frame = Frame{*this, "timed out getting frame", wpi::Now()};
|
||||
}
|
||||
return m_frame;
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
#define CSCORE_UNLIMITEDHANDLERESOURCE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/mutex.h>
|
||||
#include <wpi/span.h>
|
||||
|
||||
namespace cs {
|
||||
|
||||
@@ -50,7 +50,7 @@ class UnlimitedHandleResource {
|
||||
std::shared_ptr<TStruct> Free(THandle handle);
|
||||
|
||||
template <typename T>
|
||||
wpi::span<T> GetAll(wpi::SmallVectorImpl<T>& vec);
|
||||
std::span<T> GetAll(wpi::SmallVectorImpl<T>& vec);
|
||||
|
||||
std::vector<std::shared_ptr<TStruct>> FreeAll();
|
||||
|
||||
@@ -151,7 +151,7 @@ UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::Free(
|
||||
|
||||
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
|
||||
template <typename T>
|
||||
inline wpi::span<T>
|
||||
inline std::span<T>
|
||||
UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::GetAll(
|
||||
wpi::SmallVectorImpl<T>& vec) {
|
||||
ForEach([&](THandle handle, const TStruct& data) { vec.push_back(handle); });
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/hostname.h>
|
||||
#include <wpi/json.h>
|
||||
#include <wpinet/hostname.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "Instance.h"
|
||||
@@ -286,7 +286,7 @@ CS_Property GetSourceProperty(CS_Source source, std::string_view name,
|
||||
return Handle{source, property, Handle::kProperty};
|
||||
}
|
||||
|
||||
wpi::span<CS_Property> EnumerateSourceProperties(
|
||||
std::span<CS_Property> EnumerateSourceProperties(
|
||||
CS_Source source, wpi::SmallVectorImpl<CS_Property>& vec,
|
||||
CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
@@ -398,7 +398,7 @@ std::vector<VideoMode> EnumerateSourceVideoModes(CS_Source source,
|
||||
return data->source->EnumerateVideoModes(status);
|
||||
}
|
||||
|
||||
wpi::span<CS_Sink> EnumerateSourceSinks(CS_Source source,
|
||||
std::span<CS_Sink> EnumerateSourceSinks(CS_Source source,
|
||||
wpi::SmallVectorImpl<CS_Sink>& vec,
|
||||
CS_Status* status) {
|
||||
auto& inst = Instance::GetInstance();
|
||||
@@ -583,7 +583,7 @@ CS_Property GetSinkProperty(CS_Sink sink, std::string_view name,
|
||||
return Handle{sink, property, Handle::kSinkProperty};
|
||||
}
|
||||
|
||||
wpi::span<CS_Property> EnumerateSinkProperties(
|
||||
std::span<CS_Property> EnumerateSinkProperties(
|
||||
CS_Sink sink, wpi::SmallVectorImpl<CS_Property>& vec, CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data) {
|
||||
@@ -865,12 +865,12 @@ void Shutdown() {
|
||||
// Utility Functions
|
||||
//
|
||||
|
||||
wpi::span<CS_Source> EnumerateSourceHandles(
|
||||
std::span<CS_Source> EnumerateSourceHandles(
|
||||
wpi::SmallVectorImpl<CS_Source>& vec, CS_Status* status) {
|
||||
return Instance::GetInstance().EnumerateSourceHandles(vec);
|
||||
}
|
||||
|
||||
wpi::span<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec,
|
||||
std::span<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec,
|
||||
CS_Status* status) {
|
||||
return Instance::GetInstance().EnumerateSinkHandles(vec);
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <exception>
|
||||
#include <span>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <opencv2/core/core.hpp>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/jni_util.h>
|
||||
#include <wpi/span.h>
|
||||
|
||||
#include "cscore_cpp.h"
|
||||
#include "cscore_cv.h"
|
||||
@@ -296,7 +296,7 @@ static jobject MakeJObject(JNIEnv* env, const cs::RawEvent& event) {
|
||||
}
|
||||
|
||||
static jobjectArray MakeJObject(JNIEnv* env,
|
||||
wpi::span<const cs::RawEvent> arr) {
|
||||
std::span<const cs::RawEvent> arr) {
|
||||
jobjectArray jarr = env->NewObjectArray(arr.size(), videoEventCls, nullptr);
|
||||
if (!jarr) {
|
||||
return nullptr;
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include <functional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/span.h>
|
||||
|
||||
#include "cscore_c.h"
|
||||
|
||||
@@ -203,7 +203,7 @@ CS_Source CreateUsbCameraPath(std::string_view name, std::string_view path,
|
||||
CS_Source CreateHttpCamera(std::string_view name, std::string_view url,
|
||||
CS_HttpCameraKind kind, CS_Status* status);
|
||||
CS_Source CreateHttpCamera(std::string_view name,
|
||||
wpi::span<const std::string> urls,
|
||||
std::span<const std::string> urls,
|
||||
CS_HttpCameraKind kind, CS_Status* status);
|
||||
CS_Source CreateCvSource(std::string_view name, const VideoMode& mode,
|
||||
CS_Status* status);
|
||||
@@ -230,7 +230,7 @@ bool IsSourceConnected(CS_Source source, CS_Status* status);
|
||||
bool IsSourceEnabled(CS_Source source, CS_Status* status);
|
||||
CS_Property GetSourceProperty(CS_Source source, std::string_view name,
|
||||
CS_Status* status);
|
||||
wpi::span<CS_Property> EnumerateSourceProperties(
|
||||
std::span<CS_Property> EnumerateSourceProperties(
|
||||
CS_Source source, wpi::SmallVectorImpl<CS_Property>& vec,
|
||||
CS_Status* status);
|
||||
VideoMode GetSourceVideoMode(CS_Source source, CS_Status* status);
|
||||
@@ -249,7 +249,7 @@ std::string GetSourceConfigJson(CS_Source source, CS_Status* status);
|
||||
wpi::json GetSourceConfigJsonObject(CS_Source source, CS_Status* status);
|
||||
std::vector<VideoMode> EnumerateSourceVideoModes(CS_Source source,
|
||||
CS_Status* status);
|
||||
wpi::span<CS_Sink> EnumerateSourceSinks(CS_Source source,
|
||||
std::span<CS_Sink> EnumerateSourceSinks(CS_Source source,
|
||||
wpi::SmallVectorImpl<CS_Sink>& vec,
|
||||
CS_Status* status);
|
||||
CS_Source CopySource(CS_Source source, CS_Status* status);
|
||||
@@ -285,7 +285,7 @@ UsbCameraInfo GetUsbCameraInfo(CS_Source source, CS_Status* status);
|
||||
* @{
|
||||
*/
|
||||
CS_HttpCameraKind GetHttpCameraKind(CS_Source source, CS_Status* status);
|
||||
void SetHttpCameraUrls(CS_Source source, wpi::span<const std::string> urls,
|
||||
void SetHttpCameraUrls(CS_Source source, std::span<const std::string> urls,
|
||||
CS_Status* status);
|
||||
std::vector<std::string> GetHttpCameraUrls(CS_Source source, CS_Status* status);
|
||||
/** @} */
|
||||
@@ -304,7 +304,7 @@ CS_Property CreateSourceProperty(CS_Source source, std::string_view name,
|
||||
int step, int defaultValue, int value,
|
||||
CS_Status* status);
|
||||
void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
|
||||
wpi::span<const std::string> choices,
|
||||
std::span<const std::string> choices,
|
||||
CS_Status* status);
|
||||
/** @} */
|
||||
|
||||
@@ -335,7 +335,7 @@ std::string_view GetSinkDescription(CS_Sink sink,
|
||||
CS_Status* status);
|
||||
CS_Property GetSinkProperty(CS_Sink sink, std::string_view name,
|
||||
CS_Status* status);
|
||||
wpi::span<CS_Property> EnumerateSinkProperties(
|
||||
std::span<CS_Property> EnumerateSinkProperties(
|
||||
CS_Sink sink, wpi::SmallVectorImpl<CS_Property>& vec, CS_Status* status);
|
||||
void SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status);
|
||||
CS_Property GetSinkSourceProperty(CS_Sink sink, std::string_view name,
|
||||
@@ -430,9 +430,9 @@ void Shutdown();
|
||||
*/
|
||||
std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status);
|
||||
|
||||
wpi::span<CS_Source> EnumerateSourceHandles(
|
||||
std::span<CS_Source> EnumerateSourceHandles(
|
||||
wpi::SmallVectorImpl<CS_Source>& vec, CS_Status* status);
|
||||
wpi::span<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec,
|
||||
std::span<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec,
|
||||
CS_Status* status);
|
||||
|
||||
std::string GetHostname();
|
||||
|
||||
@@ -6,13 +6,12 @@
|
||||
#define CSCORE_CSCORE_OO_H_
|
||||
|
||||
#include <initializer_list>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/span.h>
|
||||
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
namespace cs {
|
||||
@@ -516,7 +515,7 @@ class HttpCamera : public VideoCamera {
|
||||
* @param urls Array of Camera URLs
|
||||
* @param kind Camera kind (e.g. kAxis)
|
||||
*/
|
||||
HttpCamera(std::string_view name, wpi::span<const std::string> urls,
|
||||
HttpCamera(std::string_view name, std::span<const std::string> urls,
|
||||
HttpCameraKind kind = kUnknown);
|
||||
|
||||
/**
|
||||
@@ -541,7 +540,7 @@ class HttpCamera : public VideoCamera {
|
||||
/**
|
||||
* Change the URLs used to connect to the camera.
|
||||
*/
|
||||
void SetUrls(wpi::span<const std::string> urls);
|
||||
void SetUrls(std::span<const std::string> urls);
|
||||
|
||||
/**
|
||||
* Change the URLs used to connect to the camera.
|
||||
@@ -560,7 +559,7 @@ class HttpCamera : public VideoCamera {
|
||||
*/
|
||||
class AxisCamera : public HttpCamera {
|
||||
static std::string HostToUrl(std::string_view host);
|
||||
static std::vector<std::string> HostToUrl(wpi::span<const std::string> hosts);
|
||||
static std::vector<std::string> HostToUrl(std::span<const std::string> hosts);
|
||||
template <typename T>
|
||||
static std::vector<std::string> HostToUrl(std::initializer_list<T> hosts);
|
||||
|
||||
@@ -595,7 +594,7 @@ class AxisCamera : public HttpCamera {
|
||||
* @param name Source name (arbitrary unique identifier)
|
||||
* @param hosts Array of Camera host IPs/DNS names
|
||||
*/
|
||||
AxisCamera(std::string_view name, wpi::span<const std::string> hosts);
|
||||
AxisCamera(std::string_view name, std::span<const std::string> hosts);
|
||||
|
||||
/**
|
||||
* Create a source for an Axis IP camera.
|
||||
@@ -696,7 +695,7 @@ class ImageSource : public VideoSource {
|
||||
* @param choices Choices
|
||||
*/
|
||||
void SetEnumPropertyChoices(const VideoProperty& property,
|
||||
wpi::span<const std::string> choices);
|
||||
std::span<const std::string> choices);
|
||||
|
||||
/**
|
||||
* Configure enum property choices.
|
||||
|
||||
@@ -302,7 +302,7 @@ inline HttpCamera::HttpCamera(std::string_view name, const std::string& url,
|
||||
: HttpCamera(name, std::string_view{url}, kind) {}
|
||||
|
||||
inline HttpCamera::HttpCamera(std::string_view name,
|
||||
wpi::span<const std::string> urls,
|
||||
std::span<const std::string> urls,
|
||||
HttpCameraKind kind) {
|
||||
m_handle = CreateHttpCamera(
|
||||
name, urls, static_cast<CS_HttpCameraKind>(static_cast<int>(kind)),
|
||||
@@ -329,7 +329,7 @@ inline HttpCamera::HttpCameraKind HttpCamera::GetHttpCameraKind() const {
|
||||
static_cast<int>(::cs::GetHttpCameraKind(m_handle, &m_status)));
|
||||
}
|
||||
|
||||
inline void HttpCamera::SetUrls(wpi::span<const std::string> urls) {
|
||||
inline void HttpCamera::SetUrls(std::span<const std::string> urls) {
|
||||
m_status = 0;
|
||||
::cs::SetHttpCameraUrls(m_handle, urls, &m_status);
|
||||
}
|
||||
@@ -351,7 +351,7 @@ inline std::vector<std::string> HttpCamera::GetUrls() const {
|
||||
}
|
||||
|
||||
inline std::vector<std::string> AxisCamera::HostToUrl(
|
||||
wpi::span<const std::string> hosts) {
|
||||
std::span<const std::string> hosts) {
|
||||
std::vector<std::string> rv;
|
||||
rv.reserve(hosts.size());
|
||||
for (const auto& host : hosts) {
|
||||
@@ -381,7 +381,7 @@ inline AxisCamera::AxisCamera(std::string_view name, const std::string& host)
|
||||
: HttpCamera(name, HostToUrl(std::string_view{host}), kAxis) {}
|
||||
|
||||
inline AxisCamera::AxisCamera(std::string_view name,
|
||||
wpi::span<const std::string> hosts)
|
||||
std::span<const std::string> hosts)
|
||||
: HttpCamera(name, HostToUrl(hosts), kAxis) {}
|
||||
|
||||
template <typename T>
|
||||
@@ -452,7 +452,7 @@ inline VideoProperty ImageSource::CreateStringProperty(std::string_view name,
|
||||
}
|
||||
|
||||
inline void ImageSource::SetEnumPropertyChoices(
|
||||
const VideoProperty& property, wpi::span<const std::string> choices) {
|
||||
const VideoProperty& property, std::span<const std::string> choices) {
|
||||
m_status = 0;
|
||||
SetSourceEnumPropertyChoices(m_handle, property.m_handle, choices, &m_status);
|
||||
}
|
||||
|
||||
@@ -454,7 +454,7 @@ void UsbCameraImpl::CameraThreadMain() {
|
||||
|
||||
// Handle notify events
|
||||
if (notify_fd >= 0 && FD_ISSET(notify_fd, &readfds)) {
|
||||
SDEBUG4("{}", "notify event");
|
||||
SDEBUG4("notify event");
|
||||
struct inotify_event event;
|
||||
do {
|
||||
// Read the event structure
|
||||
@@ -483,7 +483,7 @@ void UsbCameraImpl::CameraThreadMain() {
|
||||
|
||||
// Handle commands
|
||||
if (command_fd >= 0 && FD_ISSET(command_fd, &readfds)) {
|
||||
SDEBUG4("{}", "got command");
|
||||
SDEBUG4("got command");
|
||||
// Read it to clear
|
||||
eventfd_t val;
|
||||
eventfd_read(command_fd, &val);
|
||||
@@ -493,7 +493,7 @@ void UsbCameraImpl::CameraThreadMain() {
|
||||
|
||||
// Handle frames
|
||||
if (m_streaming && fd >= 0 && FD_ISSET(fd, &readfds)) {
|
||||
SDEBUG4("{}", "grabbing image");
|
||||
SDEBUG4("grabbing image");
|
||||
|
||||
// Dequeue buffer
|
||||
struct v4l2_buffer buf;
|
||||
@@ -501,7 +501,7 @@ void UsbCameraImpl::CameraThreadMain() {
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
if (DoIoctl(fd, VIDIOC_DQBUF, &buf) != 0) {
|
||||
SWARNING("{}", "could not dequeue buffer");
|
||||
SWARNING("could not dequeue buffer");
|
||||
wasStreaming = m_streaming;
|
||||
DeviceStreamOff();
|
||||
DeviceDisconnect();
|
||||
@@ -525,7 +525,7 @@ void UsbCameraImpl::CameraThreadMain() {
|
||||
bool good = true;
|
||||
if (m_mode.pixelFormat == VideoMode::kMJPEG &&
|
||||
!GetJpegSize(image, &width, &height)) {
|
||||
SWARNING("{}", "invalid JPEG image received from camera");
|
||||
SWARNING("invalid JPEG image received from camera");
|
||||
good = false;
|
||||
}
|
||||
if (good) {
|
||||
@@ -536,7 +536,7 @@ void UsbCameraImpl::CameraThreadMain() {
|
||||
|
||||
// Requeue buffer
|
||||
if (DoIoctl(fd, VIDIOC_QBUF, &buf) != 0) {
|
||||
SWARNING("{}", "could not requeue buffer");
|
||||
SWARNING("could not requeue buffer");
|
||||
wasStreaming = m_streaming;
|
||||
DeviceStreamOff();
|
||||
DeviceDisconnect();
|
||||
@@ -579,7 +579,7 @@ void UsbCameraImpl::DeviceConnect() {
|
||||
}
|
||||
|
||||
// Try to open the device
|
||||
SDEBUG3("{}", "opening device");
|
||||
SDEBUG3("opening device");
|
||||
int fd = open(m_path.c_str(), O_RDWR);
|
||||
if (fd < 0) {
|
||||
return;
|
||||
@@ -587,7 +587,7 @@ void UsbCameraImpl::DeviceConnect() {
|
||||
m_fd = fd;
|
||||
|
||||
// Get capabilities
|
||||
SDEBUG3("{}", "getting capabilities");
|
||||
SDEBUG3("getting capabilities");
|
||||
struct v4l2_capability vcap;
|
||||
std::memset(&vcap, 0, sizeof(vcap));
|
||||
if (DoIoctl(fd, VIDIOC_QUERYCAP, &vcap) >= 0) {
|
||||
@@ -599,18 +599,18 @@ void UsbCameraImpl::DeviceConnect() {
|
||||
|
||||
// Get or restore video mode
|
||||
if (!m_properties_cached) {
|
||||
SDEBUG3("{}", "caching properties");
|
||||
SDEBUG3("caching properties");
|
||||
DeviceCacheProperties();
|
||||
DeviceCacheVideoModes();
|
||||
DeviceCacheMode();
|
||||
m_properties_cached = true;
|
||||
} else {
|
||||
SDEBUG3("{}", "restoring video mode");
|
||||
SDEBUG3("restoring video mode");
|
||||
DeviceSetMode();
|
||||
DeviceSetFPS();
|
||||
|
||||
// Restore settings
|
||||
SDEBUG3("{}", "restoring settings");
|
||||
SDEBUG3("restoring settings");
|
||||
std::unique_lock lock2(m_mutex);
|
||||
for (size_t i = 0; i < m_propertyData.size(); ++i) {
|
||||
const auto prop =
|
||||
@@ -625,21 +625,21 @@ void UsbCameraImpl::DeviceConnect() {
|
||||
}
|
||||
|
||||
// Request buffers
|
||||
SDEBUG3("{}", "allocating buffers");
|
||||
SDEBUG3("allocating buffers");
|
||||
struct v4l2_requestbuffers rb;
|
||||
std::memset(&rb, 0, sizeof(rb));
|
||||
rb.count = kNumBuffers;
|
||||
rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
rb.memory = V4L2_MEMORY_MMAP;
|
||||
if (DoIoctl(fd, VIDIOC_REQBUFS, &rb) != 0) {
|
||||
SWARNING("{}", "could not allocate buffers");
|
||||
SWARNING("could not allocate buffers");
|
||||
close(fd);
|
||||
m_fd = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Map buffers
|
||||
SDEBUG3("{}", "mapping buffers");
|
||||
SDEBUG3("mapping buffers");
|
||||
for (int i = 0; i < kNumBuffers; ++i) {
|
||||
struct v4l2_buffer buf;
|
||||
std::memset(&buf, 0, sizeof(buf));
|
||||
@@ -689,7 +689,7 @@ bool UsbCameraImpl::DeviceStreamOn() {
|
||||
}
|
||||
|
||||
// Queue buffers
|
||||
SDEBUG3("{}", "queuing buffers");
|
||||
SDEBUG3("queuing buffers");
|
||||
for (int i = 0; i < kNumBuffers; ++i) {
|
||||
struct v4l2_buffer buf;
|
||||
std::memset(&buf, 0, sizeof(buf));
|
||||
@@ -708,7 +708,6 @@ bool UsbCameraImpl::DeviceStreamOn() {
|
||||
if (errno == ENOSPC) {
|
||||
// indicates too much USB bandwidth requested
|
||||
SERROR(
|
||||
"{}",
|
||||
"could not start streaming due to USB bandwidth limitations; try a "
|
||||
"lower resolution or a different pixel format (VIDIOC_STREAMON: "
|
||||
"No space left on device)");
|
||||
@@ -718,7 +717,7 @@ bool UsbCameraImpl::DeviceStreamOn() {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
SDEBUG4("{}", "enabled streaming");
|
||||
SDEBUG4("enabled streaming");
|
||||
m_streaming = true;
|
||||
return true;
|
||||
}
|
||||
@@ -735,7 +734,7 @@ bool UsbCameraImpl::DeviceStreamOff() {
|
||||
if (DoIoctl(fd, VIDIOC_STREAMOFF, &type) != 0) {
|
||||
return false;
|
||||
}
|
||||
SDEBUG4("{}", "disabled streaming");
|
||||
SDEBUG4("disabled streaming");
|
||||
m_streaming = false;
|
||||
return true;
|
||||
}
|
||||
@@ -1000,7 +999,7 @@ void UsbCameraImpl::DeviceCacheMode() {
|
||||
#endif
|
||||
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (DoIoctl(fd, VIDIOC_G_FMT, &vfmt) != 0) {
|
||||
SERROR("{}", "could not read current video mode");
|
||||
SERROR("could not read current video mode");
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_mode = VideoMode{VideoMode::kMJPEG, 320, 240, 30};
|
||||
return;
|
||||
@@ -1668,7 +1667,7 @@ std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
|
||||
::closedir(dp);
|
||||
} else {
|
||||
// *status = ;
|
||||
WPI_ERROR(Instance::GetInstance().logger, "{}", "Could not open /dev");
|
||||
WPI_ERROR(Instance::GetInstance().logger, "Could not open /dev");
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
|
||||
#include "UsbCameraListener.h"
|
||||
|
||||
#include <wpi/EventLoopRunner.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
#include <wpi/uv/FsEvent.h>
|
||||
#include <wpi/uv/Timer.h>
|
||||
#include <wpinet/EventLoopRunner.h>
|
||||
#include <wpinet/uv/FsEvent.h>
|
||||
#include <wpinet/uv/Timer.h>
|
||||
|
||||
#include "Notifier.h"
|
||||
|
||||
|
||||
@@ -58,13 +58,13 @@ static std::string GetUsbNameFromFile(int vendor, int product) {
|
||||
// next vendor, but didn't match product?
|
||||
if (line[0] != '\t') {
|
||||
buf += "Unknown";
|
||||
return buf;
|
||||
return std::string{buf};
|
||||
}
|
||||
|
||||
// look for product
|
||||
if (wpi::starts_with(wpi::substr(line, 1), productStr)) {
|
||||
buf += wpi::trim(wpi::substr(line, 6));
|
||||
return buf;
|
||||
return std::string{buf};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,19 +279,23 @@ static bool IsPercentageProperty(std::string_view name) {
|
||||
|
||||
void UsbCameraImpl::ProcessFrame(IMFSample* videoSample,
|
||||
const VideoMode& mode) {
|
||||
if (!videoSample)
|
||||
if (!videoSample) {
|
||||
return;
|
||||
}
|
||||
|
||||
ComPtr<IMFMediaBuffer> buf;
|
||||
|
||||
if (!SUCCEEDED(videoSample->ConvertToContiguousBuffer(buf.GetAddressOf()))) {
|
||||
DWORD bcnt = 0;
|
||||
if (!SUCCEEDED(videoSample->GetBufferCount(&bcnt)))
|
||||
if (!SUCCEEDED(videoSample->GetBufferCount(&bcnt))) {
|
||||
return;
|
||||
if (bcnt == 0)
|
||||
}
|
||||
if (bcnt == 0) {
|
||||
return;
|
||||
if (!SUCCEEDED(videoSample->GetBufferByIndex(0, buf.GetAddressOf())))
|
||||
}
|
||||
if (!SUCCEEDED(videoSample->GetBufferByIndex(0, buf.GetAddressOf()))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
BYTE* ptr = NULL;
|
||||
@@ -474,13 +478,15 @@ static cs::VideoMode::PixelFormat GetFromGUID(const GUID& guid) {
|
||||
}
|
||||
|
||||
bool UsbCameraImpl::DeviceConnect() {
|
||||
if (m_mediaSource && m_sourceReader)
|
||||
if (m_mediaSource && m_sourceReader) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_connectVerbose)
|
||||
if (m_connectVerbose) {
|
||||
SINFO("Connecting to USB camera on {}", m_path);
|
||||
}
|
||||
|
||||
SDEBUG3("{}", "opening device");
|
||||
SDEBUG3("opening device");
|
||||
|
||||
const wchar_t* path = m_widePath.c_str();
|
||||
m_mediaSource = CreateVideoCaptureDevice(path);
|
||||
@@ -514,13 +520,13 @@ bool UsbCameraImpl::DeviceConnect() {
|
||||
}
|
||||
|
||||
if (!m_properties_cached) {
|
||||
SDEBUG3("{}", "caching properties");
|
||||
SDEBUG3("caching properties");
|
||||
DeviceCacheProperties();
|
||||
DeviceCacheVideoModes();
|
||||
DeviceCacheMode();
|
||||
m_properties_cached = true;
|
||||
} else {
|
||||
SDEBUG3("{}", "restoring video mode");
|
||||
SDEBUG3("restoring video mode");
|
||||
DeviceSetMode();
|
||||
}
|
||||
|
||||
@@ -580,8 +586,9 @@ template void UsbCameraImpl::DeviceAddProperty(std::string_view name_,
|
||||
DeviceAddProperty(#val, CameraControl_##val, pCamControl);
|
||||
|
||||
void UsbCameraImpl::DeviceCacheProperties() {
|
||||
if (!m_sourceReader)
|
||||
if (!m_sourceReader) {
|
||||
return;
|
||||
}
|
||||
|
||||
IAMVideoProcAmp* pProcAmp = NULL;
|
||||
|
||||
@@ -778,22 +785,25 @@ CS_StatusValue UsbCameraImpl::DeviceCmdSetProperty(
|
||||
|
||||
// Look up
|
||||
auto prop = static_cast<UsbCameraProperty*>(GetProperty(property));
|
||||
if (!prop)
|
||||
if (!prop) {
|
||||
return CS_INVALID_PROPERTY;
|
||||
}
|
||||
|
||||
// If setting before we get, guess initial type based on set
|
||||
if (prop->propKind == CS_PROP_NONE) {
|
||||
if (setString)
|
||||
if (setString) {
|
||||
prop->propKind = CS_PROP_STRING;
|
||||
else
|
||||
} else {
|
||||
prop->propKind = CS_PROP_INTEGER;
|
||||
}
|
||||
}
|
||||
|
||||
// Check kind match
|
||||
if ((setString && prop->propKind != CS_PROP_STRING) ||
|
||||
(!setString && (prop->propKind &
|
||||
(CS_PROP_BOOLEAN | CS_PROP_INTEGER | CS_PROP_ENUM)) == 0))
|
||||
(!setString && (prop->propKind & (CS_PROP_BOOLEAN | CS_PROP_INTEGER |
|
||||
CS_PROP_ENUM)) == 0)) {
|
||||
return CS_WRONG_PROPERTY_TYPE;
|
||||
}
|
||||
|
||||
// Handle percentage property
|
||||
int percentageProperty = prop->propPair;
|
||||
@@ -810,8 +820,9 @@ CS_StatusValue UsbCameraImpl::DeviceCmdSetProperty(
|
||||
|
||||
// Actually set the new value on the device (if possible)
|
||||
if (!prop->device) {
|
||||
if (prop->id == kPropConnectVerboseId)
|
||||
if (prop->id == kPropConnectVerboseId) {
|
||||
m_connectVerbose = value;
|
||||
}
|
||||
} else {
|
||||
if (!prop->DeviceSet(lock, m_sourceReader.Get())) {
|
||||
return CS_PROPERTY_WRITE_FAILED;
|
||||
@@ -913,11 +924,13 @@ bool UsbCameraImpl::DeviceStreamOff() {
|
||||
}
|
||||
|
||||
void UsbCameraImpl::DeviceCacheMode() {
|
||||
if (!m_sourceReader)
|
||||
if (!m_sourceReader) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_windowsVideoModes.size() == 0)
|
||||
if (m_windowsVideoModes.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_currentMode) {
|
||||
// First, see if our set mode is valid
|
||||
@@ -982,8 +995,9 @@ CS_StatusValue UsbCameraImpl::DeviceSetMode() {
|
||||
}
|
||||
|
||||
void UsbCameraImpl::DeviceCacheVideoModes() {
|
||||
if (!m_sourceReader)
|
||||
if (!m_sourceReader) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<VideoMode> modes;
|
||||
m_windowsVideoModes.clear();
|
||||
@@ -1098,13 +1112,13 @@ std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
|
||||
sizeof(buf) / sizeof(WCHAR), &characters);
|
||||
storage.clear();
|
||||
wpi::sys::windows::UTF16ToUTF8(buf, characters, storage);
|
||||
info.name = storage.string();
|
||||
info.name = std::string{storage};
|
||||
ppDevices[i]->GetString(
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, buf,
|
||||
sizeof(buf) / sizeof(WCHAR), &characters);
|
||||
storage.clear();
|
||||
wpi::sys::windows::UTF16ToUTF8(buf, characters, storage);
|
||||
info.path = storage.string();
|
||||
info.path = std::string{storage};
|
||||
|
||||
// Try to parse path from symbolic link
|
||||
ParseVidAndPid(info.path, &info.productId, &info.vendorId);
|
||||
|
||||
@@ -96,10 +96,10 @@ class UsbCameraImpl : public SourceImpl,
|
||||
};
|
||||
|
||||
explicit Message(Kind kind_)
|
||||
: kind(kind_), data{0}, from(std::this_thread::get_id()) {}
|
||||
: kind(kind_), from(std::this_thread::get_id()) {}
|
||||
|
||||
Kind kind;
|
||||
int data[4];
|
||||
int data[4]{0};
|
||||
std::string dataStr;
|
||||
std::thread::id from;
|
||||
};
|
||||
|
||||
@@ -40,8 +40,9 @@ UsbCameraProperty::UsbCameraProperty(std::string_view name_,
|
||||
|
||||
bool UsbCameraProperty::DeviceGet(std::unique_lock<wpi::mutex>& lock,
|
||||
IAMVideoProcAmp* pProcAmp) {
|
||||
if (!pProcAmp)
|
||||
if (!pProcAmp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
long newValue = 0, paramFlag = 0; // NOLINT(runtime/int)
|
||||
@@ -60,8 +61,9 @@ bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
IAMVideoProcAmp* pProcAmp,
|
||||
int newValue) const {
|
||||
if (!pProcAmp)
|
||||
if (!pProcAmp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
if (SUCCEEDED(
|
||||
@@ -104,8 +106,9 @@ UsbCameraProperty::UsbCameraProperty(std::string_view name_,
|
||||
|
||||
bool UsbCameraProperty::DeviceGet(std::unique_lock<wpi::mutex>& lock,
|
||||
IAMCameraControl* pProcAmp) {
|
||||
if (!pProcAmp)
|
||||
if (!pProcAmp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
long newValue = 0, paramFlag = 0; // NOLINT(runtime/int)
|
||||
@@ -124,8 +127,9 @@ bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
IAMCameraControl* pProcAmp,
|
||||
int newValue) const {
|
||||
if (!pProcAmp)
|
||||
if (!pProcAmp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
if (SUCCEEDED(pProcAmp->Set(tagCameraControl, newValue,
|
||||
@@ -139,8 +143,9 @@ bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
|
||||
bool UsbCameraProperty::DeviceGet(std::unique_lock<wpi::mutex>& lock,
|
||||
IMFSourceReader* sourceReader) {
|
||||
if (!sourceReader)
|
||||
if (!sourceReader) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isControlProperty) {
|
||||
ComPtr<IAMCameraControl> pProcAmp;
|
||||
@@ -169,8 +174,9 @@ bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
IMFSourceReader* sourceReader,
|
||||
int newValue) const {
|
||||
if (!sourceReader)
|
||||
if (!sourceReader) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isControlProperty) {
|
||||
ComPtr<IAMCameraControl> pProcAmp;
|
||||
|
||||
29
datalogtool/.styleguide
Normal file
@@ -0,0 +1,29 @@
|
||||
cppHeaderFileInclude {
|
||||
\.h$
|
||||
\.inc$
|
||||
\.inl$
|
||||
}
|
||||
|
||||
cppSrcFileInclude {
|
||||
\.cpp$
|
||||
}
|
||||
|
||||
generatedFileExclude {
|
||||
src/main/native/resources/
|
||||
src/main/native/win/datalogtool.ico
|
||||
src/main/native/mac/datalogtool.icns
|
||||
}
|
||||
|
||||
repoRootNameOverride {
|
||||
datalogtool
|
||||
}
|
||||
|
||||
includeOtherLibs {
|
||||
^GLFW
|
||||
^fmt/
|
||||
^glass/
|
||||
^imgui
|
||||
^portable-file-dialog
|
||||
^wpi/
|
||||
^wpigui
|
||||
}
|
||||
29
datalogtool/CMakeLists.txt
Normal file
@@ -0,0 +1,29 @@
|
||||
project(datalogtool)
|
||||
|
||||
include(CompileWarnings)
|
||||
include(GenResources)
|
||||
include(LinkMacOSGUI)
|
||||
|
||||
configure_file(src/main/generate/WPILibVersion.cpp.in WPILibVersion.cpp)
|
||||
GENERATE_RESOURCES(src/main/native/resources generated/main/cpp DLT dlt datalogtool_resources_src)
|
||||
|
||||
file(GLOB datalogtool_src src/main/native/cpp/*.cpp ${CMAKE_CURRENT_BINARY_DIR}/WPILibVersion.cpp)
|
||||
|
||||
if (WIN32)
|
||||
set(datalogtool_rc src/main/native/win/datalogtool.rc)
|
||||
elseif(APPLE)
|
||||
set(MACOSX_BUNDLE_ICON_FILE datalogtool.icns)
|
||||
set(APP_ICON_MACOSX src/main/native/mac/datalogtool.icns)
|
||||
set_source_files_properties(${APP_ICON_MACOSX} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
|
||||
endif()
|
||||
|
||||
add_executable(datalogtool ${datalogtool_src} ${datalogtool_resources_src} ${datalogtool_rc} ${APP_ICON_MACOSX})
|
||||
wpilib_link_macos_gui(datalogtool)
|
||||
target_link_libraries(datalogtool libglass ${LIBSSH_LIBRARIES})
|
||||
target_include_directories(datalogtool SYSTEM PRIVATE ${LIBSSH_INCLUDE_DIRS})
|
||||
|
||||
if (WIN32)
|
||||
set_target_properties(datalogtool PROPERTIES WIN32_EXECUTABLE YES)
|
||||
elseif(APPLE)
|
||||
set_target_properties(datalogtool PROPERTIES MACOSX_BUNDLE YES OUTPUT_NAME "datalogTool")
|
||||
endif()
|
||||
32
datalogtool/Info.plist
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key>
|
||||
<string>datalogTool</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>datalogtool</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>datalogTool</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>edu.wpi.first.tools.datalogTool</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>datalogtool.icns</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2021</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2021</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.11</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
125
datalogtool/build.gradle
Normal file
@@ -0,0 +1,125 @@
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
|
||||
if (!project.hasProperty('onlylinuxathena')) {
|
||||
|
||||
description = "roboRIO Team Number Setter"
|
||||
|
||||
apply plugin: 'cpp'
|
||||
apply plugin: 'c'
|
||||
apply plugin: 'google-test-test-suite'
|
||||
apply plugin: 'visual-studio'
|
||||
apply plugin: 'edu.wpi.first.NativeUtils'
|
||||
|
||||
if (OperatingSystem.current().isWindows()) {
|
||||
apply plugin: 'windows-resources'
|
||||
}
|
||||
|
||||
ext {
|
||||
nativeName = 'datalogtool'
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/shared/resources.gradle"
|
||||
apply from: "${rootDir}/shared/config.gradle"
|
||||
|
||||
def wpilibVersionFileInput = file("src/main/generate/WPILibVersion.cpp.in")
|
||||
def wpilibVersionFileOutput = file("$buildDir/generated/main/cpp/WPILibVersion.cpp")
|
||||
|
||||
apply from: "${rootDir}/shared/libssh.gradle"
|
||||
|
||||
task generateCppVersion() {
|
||||
description = 'Generates the wpilib version class'
|
||||
group = 'WPILib'
|
||||
|
||||
outputs.file wpilibVersionFileOutput
|
||||
inputs.file wpilibVersionFileInput
|
||||
|
||||
if (wpilibVersioning.releaseMode) {
|
||||
outputs.upToDateWhen { false }
|
||||
}
|
||||
|
||||
// We follow a simple set of checks to determine whether we should generate a new version file:
|
||||
// 1. If the release type is not development, we generate a new version file
|
||||
// 2. If there is no generated version number, we generate a new version file
|
||||
// 3. If there is a generated build number, and the release type is development, then we will
|
||||
// only generate if the publish task is run.
|
||||
doLast {
|
||||
def version = wpilibVersioning.version.get()
|
||||
println "Writing version ${version} to $wpilibVersionFileOutput"
|
||||
|
||||
if (wpilibVersionFileOutput.exists()) {
|
||||
wpilibVersionFileOutput.delete()
|
||||
}
|
||||
def read = wpilibVersionFileInput.text.replace('${wpilib_version}', version)
|
||||
wpilibVersionFileOutput.write(read)
|
||||
}
|
||||
}
|
||||
|
||||
gradle.taskGraph.addTaskExecutionGraphListener { graph ->
|
||||
def willPublish = graph.hasTask(publish)
|
||||
if (willPublish) {
|
||||
generateCppVersion.outputs.upToDateWhen { false }
|
||||
}
|
||||
}
|
||||
|
||||
def generateTask = createGenerateResourcesTask('main', 'DLT', 'dlt', project)
|
||||
|
||||
project(':').libraryBuild.dependsOn build
|
||||
tasks.withType(CppCompile) {
|
||||
dependsOn generateTask
|
||||
dependsOn generateCppVersion
|
||||
}
|
||||
|
||||
model {
|
||||
components {
|
||||
// By default, a development executable will be generated. This is to help the case of
|
||||
// testing specific functionality of the library.
|
||||
"${nativeName}"(NativeExecutableSpec) {
|
||||
baseName = 'datalogtool'
|
||||
sources {
|
||||
cpp {
|
||||
source {
|
||||
srcDirs 'src/main/native/cpp', "$buildDir/generated/main/cpp"
|
||||
include '**/*.cpp'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/main/native/include'
|
||||
}
|
||||
}
|
||||
if (OperatingSystem.current().isWindows()) {
|
||||
rc {
|
||||
source {
|
||||
srcDirs 'src/main/native/win'
|
||||
include '*.rc'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
binaries.all {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
it.cppCompiler.define("LIBSSH_STATIC")
|
||||
lib project: ':glass', library: 'glass', linkage: 'static'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
|
||||
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
|
||||
nativeUtils.useRequiredLibrary(it, 'imgui_static', 'libssh')
|
||||
if (it.targetPlatform.operatingSystem.isWindows()) {
|
||||
it.linker.args << 'Gdi32.lib' << 'Shell32.lib' << 'd3d11.lib' << 'd3dcompiler.lib'
|
||||
it.linker.args << 'ws2_32.lib' << 'advapi32.lib' << 'crypt32.lib' << 'user32.lib'
|
||||
} else if (it.targetPlatform.operatingSystem.isMacOsX()) {
|
||||
it.linker.args << '-framework' << 'Metal' << '-framework' << 'MetalKit' << '-framework' << 'Cocoa' << '-framework' << 'IOKit' << '-framework' << 'CoreFoundation' << '-framework' << 'CoreVideo' << '-framework' << 'QuartzCore'
|
||||
it.linker.args << '-framework' << 'Kerberos'
|
||||
} else {
|
||||
it.linker.args << '-lX11'
|
||||
if (it.targetPlatform.name.startsWith('linuxarm')) {
|
||||
it.linker.args << '-lGL'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply from: 'publish.gradle'
|
||||
}
|
||||
107
datalogtool/publish.gradle
Normal file
@@ -0,0 +1,107 @@
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
def baseArtifactId = 'DataLogTool'
|
||||
def artifactGroupId = 'edu.wpi.first.tools'
|
||||
def zipBaseName = '_GROUP_edu_wpi_first_tools_ID_DataLogTool_CLS'
|
||||
|
||||
def outputsFolder = file("$project.buildDir/outputs")
|
||||
|
||||
model {
|
||||
tasks {
|
||||
// Create the run task.
|
||||
$.components.datalogtool.binaries.each { bin ->
|
||||
if (bin.buildable && bin.name.toLowerCase().contains("debug") && nativeUtils.isNativeDesktopPlatform(bin.targetPlatform)) {
|
||||
Task run = project.tasks.create("run", Exec) {
|
||||
commandLine bin.tasks.install.runScriptFile.get().asFile.toString()
|
||||
}
|
||||
run.dependsOn bin.tasks.install
|
||||
}
|
||||
}
|
||||
}
|
||||
publishing {
|
||||
def dataLogToolTaskList = []
|
||||
$.components.each { component ->
|
||||
component.binaries.each { binary ->
|
||||
if (binary in NativeExecutableBinarySpec && binary.component.name.contains("datalogtool")) {
|
||||
if (binary.buildable && (binary.name.contains('Release') || binary.name.contains('release'))) {
|
||||
// We are now in the binary that we want.
|
||||
// This is the default application path for the ZIP task.
|
||||
def applicationPath = binary.executable.file
|
||||
def icon = file("$project.projectDir/src/main/native/mac/datalogtool.icns")
|
||||
|
||||
// Create the macOS bundle.
|
||||
def bundleTask = project.tasks.create("bundleDataLogToolOsxApp" + binary.targetPlatform.architecture.name, Copy) {
|
||||
description("Creates a macOS application bundle for DataLogTool")
|
||||
from(file("$project.projectDir/Info.plist"))
|
||||
into(file("$project.buildDir/outputs/bundles/$binary.targetPlatform.architecture.name/DataLogTool.app/Contents"))
|
||||
into("MacOS") { with copySpec { from binary.executable.file } }
|
||||
into("Resources") { with copySpec { from icon } }
|
||||
|
||||
doLast {
|
||||
if (project.hasProperty("developerID")) {
|
||||
// Get path to binary.
|
||||
exec {
|
||||
workingDir rootDir
|
||||
def args = [
|
||||
"sh",
|
||||
"-c",
|
||||
"codesign --force --strict --deep " +
|
||||
"--timestamp --options=runtime " +
|
||||
"--verbose -s ${project.findProperty("developerID")} " +
|
||||
"$project.buildDir/outputs/bundles/$binary.targetPlatform.architecture.name/DataLogTool.app/"
|
||||
]
|
||||
commandLine args
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the application path if we are creating a bundle.
|
||||
if (binary.targetPlatform.operatingSystem.isMacOsX()) {
|
||||
applicationPath = file("$project.buildDir/outputs/bundles/$binary.targetPlatform.architecture.name")
|
||||
project.build.dependsOn bundleTask
|
||||
}
|
||||
|
||||
// Create the ZIP.
|
||||
def task = project.tasks.create("copyDataLogToolExecutable" + binary.targetPlatform.architecture.name, Zip) {
|
||||
description("Copies the DataLogTool executable to the outputs directory.")
|
||||
destinationDirectory = outputsFolder
|
||||
|
||||
archiveBaseName = '_M_' + zipBaseName
|
||||
duplicatesStrategy = 'exclude'
|
||||
classifier = nativeUtils.getPublishClassifier(binary)
|
||||
|
||||
from(licenseFile) {
|
||||
into '/'
|
||||
}
|
||||
|
||||
from(applicationPath)
|
||||
into(nativeUtils.getPlatformPath(binary))
|
||||
}
|
||||
|
||||
if (binary.targetPlatform.operatingSystem.isMacOsX()) {
|
||||
bundleTask.dependsOn binary.tasks.link
|
||||
task.dependsOn(bundleTask)
|
||||
}
|
||||
|
||||
task.dependsOn binary.tasks.link
|
||||
dataLogToolTaskList.add(task)
|
||||
project.build.dependsOn task
|
||||
project.artifacts { task }
|
||||
addTaskToCopyAllOutputs(task)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
publications {
|
||||
datalogtool(MavenPublication) {
|
||||
dataLogToolTaskList.each { artifact it }
|
||||
|
||||
artifactId = baseArtifactId
|
||||
groupId = artifactGroupId
|
||||
version wpilibVersioning.version.get()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
datalogtool/src/main/generate/WPILibVersion.cpp.in
Normal file
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
* Autogenerated file! Do not manually edit this file. This version is regenerated
|
||||
* any time the publish task is run, or when this file is deleted.
|
||||
*/
|
||||
const char* GetWPILibVersion() {
|
||||
return "${wpilib_version}";
|
||||
}
|
||||
156
datalogtool/src/main/native/cpp/App.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
// 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 "App.h"
|
||||
|
||||
#include <libssh/libssh.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
|
||||
#include <glass/Context.h>
|
||||
#include <glass/MainMenuBar.h>
|
||||
#include <glass/Storage.h>
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <wpigui.h>
|
||||
|
||||
#include "Downloader.h"
|
||||
#include "Exporter.h"
|
||||
|
||||
namespace gui = wpi::gui;
|
||||
|
||||
const char* GetWPILibVersion();
|
||||
|
||||
namespace dlt {
|
||||
std::string_view GetResource_dlt_16_png();
|
||||
std::string_view GetResource_dlt_32_png();
|
||||
std::string_view GetResource_dlt_48_png();
|
||||
std::string_view GetResource_dlt_64_png();
|
||||
std::string_view GetResource_dlt_128_png();
|
||||
std::string_view GetResource_dlt_256_png();
|
||||
std::string_view GetResource_dlt_512_png();
|
||||
} // namespace dlt
|
||||
|
||||
bool gShutdown = false;
|
||||
|
||||
static std::unique_ptr<Downloader> gDownloader;
|
||||
static bool* gDownloadVisible;
|
||||
static float gDefaultScale = 1.0;
|
||||
|
||||
void SetNextWindowPos(const ImVec2& pos, ImGuiCond cond, const ImVec2& pivot) {
|
||||
if ((cond & ImGuiCond_FirstUseEver) != 0) {
|
||||
ImGui::SetNextWindowPos(pos * gDefaultScale, cond, pivot);
|
||||
} else {
|
||||
ImGui::SetNextWindowPos(pos, cond, pivot);
|
||||
}
|
||||
}
|
||||
|
||||
void SetNextWindowSize(const ImVec2& size, ImGuiCond cond) {
|
||||
if ((cond & ImGuiCond_FirstUseEver) != 0) {
|
||||
ImGui::SetNextWindowSize(size * gDefaultScale, cond);
|
||||
} else {
|
||||
ImGui::SetNextWindowPos(size, cond);
|
||||
}
|
||||
}
|
||||
|
||||
static void DisplayDownload() {
|
||||
if (!*gDownloadVisible) {
|
||||
return;
|
||||
}
|
||||
SetNextWindowPos(ImVec2{0, 250}, ImGuiCond_FirstUseEver);
|
||||
SetNextWindowSize(ImVec2{375, 260}, ImGuiCond_FirstUseEver);
|
||||
if (ImGui::Begin("Download", gDownloadVisible)) {
|
||||
if (!gDownloader) {
|
||||
gDownloader = std::make_unique<Downloader>(
|
||||
glass::GetStorageRoot().GetChild("download"));
|
||||
}
|
||||
gDownloader->Display();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
static void DisplayMainMenu() {
|
||||
ImGui::BeginMainMenuBar();
|
||||
|
||||
static glass::MainMenuBar mainMenu;
|
||||
mainMenu.WorkspaceMenu();
|
||||
gui::EmitViewMenu();
|
||||
|
||||
if (ImGui::BeginMenu("Window")) {
|
||||
ImGui::MenuItem("Download", nullptr, gDownloadVisible);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
bool about = false;
|
||||
if (ImGui::BeginMenu("Info")) {
|
||||
if (ImGui::MenuItem("About")) {
|
||||
about = true;
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
ImGui::EndMainMenuBar();
|
||||
|
||||
if (about) {
|
||||
ImGui::OpenPopup("About");
|
||||
}
|
||||
if (ImGui::BeginPopupModal("About")) {
|
||||
ImGui::Text("Datalog Tool");
|
||||
ImGui::Separator();
|
||||
ImGui::Text("v%s", GetWPILibVersion());
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Save location: %s", glass::GetStorageDir().c_str());
|
||||
if (ImGui::Button("Close")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
static void DisplayGui() {
|
||||
DisplayMainMenu();
|
||||
DisplayInputFiles();
|
||||
DisplayEntries();
|
||||
DisplayOutput(glass::GetStorageRoot().GetChild("output"));
|
||||
DisplayDownload();
|
||||
}
|
||||
|
||||
void Application(std::string_view saveDir) {
|
||||
ssh_init();
|
||||
|
||||
gui::CreateContext();
|
||||
glass::CreateContext();
|
||||
|
||||
// Add icons
|
||||
gui::AddIcon(dlt::GetResource_dlt_16_png());
|
||||
gui::AddIcon(dlt::GetResource_dlt_32_png());
|
||||
gui::AddIcon(dlt::GetResource_dlt_48_png());
|
||||
gui::AddIcon(dlt::GetResource_dlt_64_png());
|
||||
gui::AddIcon(dlt::GetResource_dlt_128_png());
|
||||
gui::AddIcon(dlt::GetResource_dlt_256_png());
|
||||
gui::AddIcon(dlt::GetResource_dlt_512_png());
|
||||
|
||||
glass::SetStorageName("datalogtool");
|
||||
glass::SetStorageDir(saveDir.empty() ? gui::GetPlatformSaveFileDir()
|
||||
: saveDir);
|
||||
|
||||
gui::AddWindowScaler([](float scale) { gDefaultScale = scale; });
|
||||
gui::AddLateExecute(DisplayGui);
|
||||
gui::Initialize("Datalog Tool", 925, 510);
|
||||
|
||||
gDownloadVisible =
|
||||
&glass::GetStorageRoot().GetChild("download").GetBool("visible", true);
|
||||
|
||||
gui::Main();
|
||||
|
||||
gShutdown = true;
|
||||
glass::DestroyContext();
|
||||
gui::DestroyContext();
|
||||
|
||||
gDownloader.reset();
|
||||
ssh_finalize();
|
||||
}
|
||||
11
datalogtool/src/main/native/cpp/App.h
Normal file
@@ -0,0 +1,11 @@
|
||||
// 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 <imgui.h>
|
||||
|
||||
void SetNextWindowPos(const ImVec2& pos, ImGuiCond cond = 0,
|
||||
const ImVec2& pivot = ImVec2(0, 0));
|
||||
void SetNextWindowSize(const ImVec2& size, ImGuiCond cond = 0);
|
||||
72
datalogtool/src/main/native/cpp/DataLogThread.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "DataLogThread.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
DataLogThread::~DataLogThread() {
|
||||
if (m_thread.joinable()) {
|
||||
m_active = false;
|
||||
m_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void DataLogThread::ReadMain() {
|
||||
for (auto record : m_reader) {
|
||||
if (!m_active) {
|
||||
break;
|
||||
}
|
||||
++m_numRecords;
|
||||
if (record.IsStart()) {
|
||||
wpi::log::StartRecordData data;
|
||||
if (record.GetStartData(&data)) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_entries.find(data.entry) != m_entries.end()) {
|
||||
fmt::print("...DUPLICATE entry ID, overriding\n");
|
||||
}
|
||||
m_entries[data.entry] = data;
|
||||
m_entryNames.emplace(data.name, data);
|
||||
sigEntryAdded(data);
|
||||
} else {
|
||||
fmt::print("Start(INVALID)\n");
|
||||
}
|
||||
} else if (record.IsFinish()) {
|
||||
int entry;
|
||||
if (record.GetFinishEntry(&entry)) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
auto it = m_entries.find(entry);
|
||||
if (it == m_entries.end()) {
|
||||
fmt::print("...ID not found\n");
|
||||
} else {
|
||||
m_entries.erase(it);
|
||||
}
|
||||
} else {
|
||||
fmt::print("Finish(INVALID)\n");
|
||||
}
|
||||
} else if (record.IsSetMetadata()) {
|
||||
wpi::log::MetadataRecordData data;
|
||||
if (record.GetSetMetadataData(&data)) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
auto it = m_entries.find(data.entry);
|
||||
if (it == m_entries.end()) {
|
||||
fmt::print("...ID not found\n");
|
||||
} else {
|
||||
it->second.metadata = data.metadata;
|
||||
auto nameIt = m_entryNames.find(it->second.name);
|
||||
if (nameIt != m_entryNames.end()) {
|
||||
nameIt->second.metadata = data.metadata;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt::print("SetMetadata(INVALID)\n");
|
||||
}
|
||||
} else if (record.IsControl()) {
|
||||
fmt::print("Unrecognized control record\n");
|
||||
}
|
||||
}
|
||||
|
||||
sigDone();
|
||||
m_done = true;
|
||||
}
|
||||
71
datalogtool/src/main/native/cpp/DataLogThread.h
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include <wpi/DataLogReader.h>
|
||||
#include <wpi/DenseMap.h>
|
||||
#include <wpi/Signal.h>
|
||||
#include <wpi/mutex.h>
|
||||
|
||||
class DataLogThread {
|
||||
public:
|
||||
explicit DataLogThread(wpi::log::DataLogReader reader)
|
||||
: m_reader{std::move(reader)}, m_thread{[=, this] { ReadMain(); }} {}
|
||||
~DataLogThread();
|
||||
|
||||
bool IsDone() const { return m_done; }
|
||||
std::string_view GetBufferIdentifier() const {
|
||||
return m_reader.GetBufferIdentifier();
|
||||
}
|
||||
unsigned int GetNumRecords() const { return m_numRecords; }
|
||||
unsigned int GetNumEntries() const {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return m_entryNames.size();
|
||||
}
|
||||
|
||||
// Passes wpi::log::StartRecordData to func
|
||||
template <typename T>
|
||||
void ForEachEntryName(T&& func) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
for (auto&& kv : m_entryNames) {
|
||||
func(kv.second);
|
||||
}
|
||||
}
|
||||
|
||||
wpi::log::StartRecordData GetEntry(std::string_view name) const {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
auto it = m_entryNames.find(name);
|
||||
if (it == m_entryNames.end()) {
|
||||
return {};
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
const wpi::log::DataLogReader& GetReader() const { return m_reader; }
|
||||
|
||||
// note: these are called on separate thread
|
||||
wpi::sig::Signal_mt<const wpi::log::StartRecordData&> sigEntryAdded;
|
||||
wpi::sig::Signal_mt<> sigDone;
|
||||
|
||||
private:
|
||||
void ReadMain();
|
||||
|
||||
wpi::log::DataLogReader m_reader;
|
||||
mutable wpi::mutex m_mutex;
|
||||
std::atomic_bool m_active{true};
|
||||
std::atomic_bool m_done{false};
|
||||
std::atomic<unsigned int> m_numRecords{0};
|
||||
std::map<std::string, wpi::log::StartRecordData, std::less<>> m_entryNames;
|
||||
wpi::DenseMap<int, wpi::log::StartRecordData> m_entries;
|
||||
std::thread m_thread;
|
||||
};
|
||||
407
datalogtool/src/main/native/cpp/Downloader.cpp
Normal file
@@ -0,0 +1,407 @@
|
||||
// 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 "Downloader.h"
|
||||
|
||||
#include <libssh/sftp.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#else
|
||||
#include <sys/fcntl.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <glass/Storage.h>
|
||||
#include <imgui.h>
|
||||
#include <imgui_stdlib.h>
|
||||
#include <portable-file-dialogs.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
#include <wpi/fs.h>
|
||||
|
||||
#include "Sftp.h"
|
||||
|
||||
Downloader::Downloader(glass::Storage& storage)
|
||||
: m_serverTeam{storage.GetString("serverTeam")},
|
||||
m_remoteDir{storage.GetString("remoteDir", "/home/lvuser")},
|
||||
m_username{storage.GetString("username", "lvuser")},
|
||||
m_localDir{storage.GetString("localDir")},
|
||||
m_deleteAfter{storage.GetBool("deleteAfter", true)},
|
||||
m_thread{[this] { ThreadMain(); }} {}
|
||||
|
||||
Downloader::~Downloader() {
|
||||
{
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_state = kExit;
|
||||
}
|
||||
m_cv.notify_all();
|
||||
m_thread.join();
|
||||
}
|
||||
|
||||
void Downloader::DisplayConnect() {
|
||||
// IP or Team Number text box
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 12);
|
||||
ImGui::InputText("Team Number / Address", &m_serverTeam);
|
||||
|
||||
// Username/password
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 12);
|
||||
ImGui::InputText("Username", &m_username);
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 12);
|
||||
ImGui::InputText("Password", &m_password, ImGuiInputTextFlags_Password);
|
||||
|
||||
// Connect button
|
||||
if (ImGui::Button("Connect")) {
|
||||
m_state = kConnecting;
|
||||
m_cv.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
void Downloader::DisplayDisconnectButton() {
|
||||
if (ImGui::Button("Disconnect")) {
|
||||
m_state = kDisconnecting;
|
||||
m_cv.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
void Downloader::DisplayRemoteDirSelector() {
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Refresh")) {
|
||||
m_state = kGetFiles;
|
||||
m_cv.notify_all();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Deselect All")) {
|
||||
for (auto&& download : m_downloadList) {
|
||||
download.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Select All")) {
|
||||
for (auto&& download : m_downloadList) {
|
||||
download.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Remote directory text box
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 20);
|
||||
if (ImGui::InputText("Remote Dir", &m_remoteDir,
|
||||
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||
m_state = kGetFiles;
|
||||
m_cv.notify_all();
|
||||
}
|
||||
|
||||
// List directories
|
||||
for (auto&& dir : m_dirList) {
|
||||
if (ImGui::Selectable(dir.c_str())) {
|
||||
if (dir == "..") {
|
||||
if (wpi::ends_with(m_remoteDir, '/')) {
|
||||
m_remoteDir.resize(m_remoteDir.size() - 1);
|
||||
}
|
||||
m_remoteDir = wpi::rsplit(m_remoteDir, '/').first;
|
||||
if (m_remoteDir.empty()) {
|
||||
m_remoteDir = "/";
|
||||
}
|
||||
} else {
|
||||
if (!wpi::ends_with(m_remoteDir, '/')) {
|
||||
m_remoteDir += '/';
|
||||
}
|
||||
m_remoteDir += dir;
|
||||
}
|
||||
m_state = kGetFiles;
|
||||
m_cv.notify_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Downloader::DisplayLocalDirSelector() {
|
||||
// Local directory text / select button
|
||||
if (ImGui::Button("Select Download Folder...")) {
|
||||
m_localDirSelector =
|
||||
std::make_unique<pfd::select_folder>("Select Download Folder");
|
||||
}
|
||||
ImGui::TextUnformatted(m_localDir.c_str());
|
||||
|
||||
// Delete after download (checkbox)
|
||||
ImGui::Checkbox("Delete after download", &m_deleteAfter);
|
||||
|
||||
// Download button
|
||||
if (!m_localDir.empty()) {
|
||||
if (ImGui::Button("Download")) {
|
||||
m_state = kDownload;
|
||||
m_cv.notify_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t Downloader::DisplayFiles() {
|
||||
// List of files (multi-select) (changes to progress bar for downloading)
|
||||
size_t fileCount = 0;
|
||||
if (ImGui::BeginTable(
|
||||
"files", 3,
|
||||
ImGuiTableFlags_Borders | ImGuiTableFlags_SizingStretchProp)) {
|
||||
ImGui::TableSetupColumn("File");
|
||||
ImGui::TableSetupColumn("Size");
|
||||
ImGui::TableSetupColumn("Download");
|
||||
ImGui::TableHeadersRow();
|
||||
for (auto&& download : m_downloadList) {
|
||||
if ((m_state == kDownload || m_state == kDownloadDone) &&
|
||||
!download.enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
++fileCount;
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(download.name.c_str());
|
||||
ImGui::TableNextColumn();
|
||||
auto sizeText = fmt::format("{}", download.size);
|
||||
ImGui::TextUnformatted(sizeText.c_str());
|
||||
ImGui::TableNextColumn();
|
||||
if (m_state == kDownload || m_state == kDownloadDone) {
|
||||
if (!download.status.empty()) {
|
||||
ImGui::TextUnformatted(download.status.c_str());
|
||||
} else {
|
||||
ImGui::ProgressBar(download.complete);
|
||||
}
|
||||
} else {
|
||||
auto checkboxLabel = fmt::format("##{}", download.name);
|
||||
ImGui::Checkbox(checkboxLabel.c_str(), &download.enabled);
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
return fileCount;
|
||||
}
|
||||
|
||||
void Downloader::Display() {
|
||||
if (m_localDirSelector && m_localDirSelector->ready(0)) {
|
||||
m_localDir = m_localDirSelector->result();
|
||||
m_localDirSelector.reset();
|
||||
}
|
||||
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
if (!m_error.empty()) {
|
||||
ImGui::TextUnformatted(m_error.c_str());
|
||||
}
|
||||
|
||||
switch (m_state) {
|
||||
case kDisconnected:
|
||||
DisplayConnect();
|
||||
break;
|
||||
case kConnecting:
|
||||
DisplayDisconnectButton();
|
||||
ImGui::Text("Connecting to %s...", m_serverTeam.c_str());
|
||||
break;
|
||||
case kDisconnecting:
|
||||
ImGui::TextUnformatted("Disconnecting...");
|
||||
break;
|
||||
case kConnected:
|
||||
case kGetFiles:
|
||||
DisplayDisconnectButton();
|
||||
DisplayRemoteDirSelector();
|
||||
if (DisplayFiles() > 0) {
|
||||
DisplayLocalDirSelector();
|
||||
}
|
||||
break;
|
||||
case kDownload:
|
||||
case kDownloadDone:
|
||||
DisplayDisconnectButton();
|
||||
DisplayFiles();
|
||||
if (m_state == kDownloadDone) {
|
||||
if (ImGui::Button("Download complete!")) {
|
||||
m_state = kGetFiles;
|
||||
m_cv.notify_all();
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Downloader::ThreadMain() {
|
||||
std::unique_ptr<sftp::Session> session;
|
||||
|
||||
static constexpr size_t kBufSize = 32 * 1024;
|
||||
std::unique_ptr<uint8_t[]> copyBuf = std::make_unique<uint8_t[]>(kBufSize);
|
||||
|
||||
std::unique_lock lock{m_mutex};
|
||||
while (m_state != kExit) {
|
||||
State prev = m_state;
|
||||
m_cv.wait(lock, [&] { return m_state != prev; });
|
||||
m_error.clear();
|
||||
try {
|
||||
switch (m_state) {
|
||||
case kConnecting:
|
||||
if (auto team = wpi::parse_integer<unsigned int>(m_serverTeam, 10)) {
|
||||
// team number
|
||||
session = std::make_unique<sftp::Session>(
|
||||
fmt::format("roborio-{}-frc.local", team.value()), 22,
|
||||
m_username, m_password);
|
||||
} else {
|
||||
session = std::make_unique<sftp::Session>(m_serverTeam, 22,
|
||||
m_username, m_password);
|
||||
}
|
||||
lock.unlock();
|
||||
try {
|
||||
session->Connect();
|
||||
} catch (...) {
|
||||
lock.lock();
|
||||
throw;
|
||||
}
|
||||
lock.lock();
|
||||
// FALLTHROUGH
|
||||
case kGetFiles: {
|
||||
std::string dir = m_remoteDir;
|
||||
std::vector<sftp::Attributes> fileList;
|
||||
lock.unlock();
|
||||
try {
|
||||
fileList = session->ReadDir(dir);
|
||||
} catch (sftp::Exception& ex) {
|
||||
lock.lock();
|
||||
if (ex.err == SSH_FX_OK || ex.err == SSH_FX_CONNECTION_LOST) {
|
||||
throw;
|
||||
}
|
||||
m_error = ex.what();
|
||||
m_dirList.clear();
|
||||
m_downloadList.clear();
|
||||
m_state = kConnected;
|
||||
break;
|
||||
}
|
||||
std::sort(
|
||||
fileList.begin(), fileList.end(),
|
||||
[](const auto& l, const auto& r) { return l.name < r.name; });
|
||||
lock.lock();
|
||||
|
||||
m_dirList.clear();
|
||||
m_downloadList.clear();
|
||||
for (auto&& attr : fileList) {
|
||||
if (attr.type == SSH_FILEXFER_TYPE_DIRECTORY) {
|
||||
if (attr.name != ".") {
|
||||
m_dirList.emplace_back(attr.name);
|
||||
}
|
||||
} else if (attr.type == SSH_FILEXFER_TYPE_REGULAR &&
|
||||
(attr.flags & SSH_FILEXFER_ATTR_SIZE) != 0 &&
|
||||
wpi::ends_with(attr.name, ".wpilog")) {
|
||||
m_downloadList.emplace_back(attr.name, attr.size);
|
||||
}
|
||||
}
|
||||
|
||||
m_state = kConnected;
|
||||
break;
|
||||
}
|
||||
case kDisconnecting:
|
||||
session.reset();
|
||||
m_state = kDisconnected;
|
||||
break;
|
||||
case kDownload: {
|
||||
for (auto&& download : m_downloadList) {
|
||||
if (m_state != kDownload) {
|
||||
// user aborted
|
||||
break;
|
||||
}
|
||||
if (!download.enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto remoteFilename = fmt::format(
|
||||
"{}{}{}", m_remoteDir,
|
||||
wpi::ends_with(m_remoteDir, '/') ? "" : "/", download.name);
|
||||
auto localFilename = fs::path{m_localDir} / download.name;
|
||||
uint64_t fileSize = download.size;
|
||||
|
||||
lock.unlock();
|
||||
|
||||
// open local file
|
||||
std::error_code ec;
|
||||
fs::file_t of = fs::OpenFileForWrite(localFilename, ec,
|
||||
fs::CD_CreateNew, fs::OF_None);
|
||||
if (ec) {
|
||||
// failed to open
|
||||
lock.lock();
|
||||
download.status = ec.message();
|
||||
continue;
|
||||
}
|
||||
int ofd = fs::FileToFd(of, ec, fs::OF_None);
|
||||
if (ofd == -1 || ec) {
|
||||
// failed to convert to fd
|
||||
lock.lock();
|
||||
download.status = ec.message();
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// open remote file
|
||||
sftp::File f = session->Open(remoteFilename, O_RDONLY, 0);
|
||||
|
||||
// copy in chunks
|
||||
uint64_t total = 0;
|
||||
while (total < fileSize) {
|
||||
uint64_t toCopy = (std::min)(fileSize - total,
|
||||
static_cast<uint64_t>(kBufSize));
|
||||
auto copied = f.Read(copyBuf.get(), toCopy);
|
||||
if (write(ofd, copyBuf.get(), copied) !=
|
||||
static_cast<int64_t>(copied)) {
|
||||
// error writing
|
||||
close(ofd);
|
||||
fs::remove(localFilename, ec);
|
||||
lock.lock();
|
||||
download.status = "error writing local file";
|
||||
goto err;
|
||||
}
|
||||
total += copied;
|
||||
lock.lock();
|
||||
download.complete = static_cast<float>(total) / fileSize;
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
// close local file
|
||||
close(ofd);
|
||||
ofd = -1;
|
||||
|
||||
// delete remote file (if enabled)
|
||||
if (m_deleteAfter) {
|
||||
f = sftp::File{};
|
||||
session->Unlink(remoteFilename);
|
||||
}
|
||||
} catch (sftp::Exception& ex) {
|
||||
if (ofd != -1) {
|
||||
// close local file and delete it (due to failure)
|
||||
close(ofd);
|
||||
fs::remove(localFilename, ec);
|
||||
}
|
||||
lock.lock();
|
||||
download.status = ex.what();
|
||||
if (ex.err == SSH_FX_OK || ex.err == SSH_FX_CONNECTION_LOST) {
|
||||
throw;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
lock.lock();
|
||||
err : {}
|
||||
}
|
||||
if (m_state == kDownload) {
|
||||
m_state = kDownloadDone;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (sftp::Exception& ex) {
|
||||
m_error = ex.what();
|
||||
session.reset();
|
||||
m_state = kDisconnected;
|
||||
}
|
||||
}
|
||||
}
|
||||
78
datalogtool/src/main/native/cpp/Downloader.h
Normal file
@@ -0,0 +1,78 @@
|
||||
// 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 <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/condition_variable.h>
|
||||
#include <wpi/mutex.h>
|
||||
|
||||
namespace glass {
|
||||
class Storage;
|
||||
} // namespace glass
|
||||
|
||||
namespace pfd {
|
||||
class select_folder;
|
||||
} // namespace pfd
|
||||
|
||||
class Downloader {
|
||||
public:
|
||||
explicit Downloader(glass::Storage& storage);
|
||||
~Downloader();
|
||||
|
||||
void Display();
|
||||
|
||||
private:
|
||||
void DisplayConnect();
|
||||
void DisplayDisconnectButton();
|
||||
void DisplayRemoteDirSelector();
|
||||
void DisplayLocalDirSelector();
|
||||
size_t DisplayFiles();
|
||||
|
||||
void ThreadMain();
|
||||
|
||||
wpi::mutex m_mutex;
|
||||
enum State {
|
||||
kDisconnected,
|
||||
kConnecting,
|
||||
kConnected,
|
||||
kDisconnecting,
|
||||
kGetFiles,
|
||||
kDownload,
|
||||
kDownloadDone,
|
||||
kExit
|
||||
} m_state = kDisconnected;
|
||||
std::condition_variable m_cv;
|
||||
|
||||
std::string& m_serverTeam;
|
||||
std::string& m_remoteDir;
|
||||
std::string& m_username;
|
||||
std::string m_password;
|
||||
|
||||
std::string& m_localDir;
|
||||
std::unique_ptr<pfd::select_folder> m_localDirSelector;
|
||||
|
||||
bool& m_deleteAfter;
|
||||
|
||||
std::vector<std::string> m_dirList;
|
||||
struct DownloadState {
|
||||
DownloadState(std::string_view name, uint64_t size)
|
||||
: name{name}, size{size} {}
|
||||
|
||||
std::string name;
|
||||
uint64_t size;
|
||||
bool enabled = true;
|
||||
float complete = 0.0;
|
||||
std::string status;
|
||||
};
|
||||
std::vector<DownloadState> m_downloadList;
|
||||
|
||||
std::string m_error;
|
||||
|
||||
std::thread m_thread;
|
||||
};
|
||||
661
datalogtool/src/main/native/cpp/Exporter.cpp
Normal file
@@ -0,0 +1,661 @@
|
||||
// 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 "Exporter.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <ctime>
|
||||
#include <future>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
#include <fmt/format.h>
|
||||
#include <glass/Storage.h>
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <imgui_stdlib.h>
|
||||
#include <portable-file-dialogs.h>
|
||||
#include <wpi/DenseMap.h>
|
||||
#include <wpi/MemoryBuffer.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/SpanExtras.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
#include <wpi/fmt/raw_ostream.h>
|
||||
#include <wpi/fs.h>
|
||||
#include <wpi/mutex.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
#include "App.h"
|
||||
#include "DataLogThread.h"
|
||||
|
||||
namespace {
|
||||
struct InputFile {
|
||||
explicit InputFile(std::unique_ptr<DataLogThread> datalog);
|
||||
|
||||
InputFile(std::string_view filename, std::string_view status)
|
||||
: filename{filename},
|
||||
stem{fs::path{filename}.stem().string()},
|
||||
status{status} {}
|
||||
|
||||
~InputFile();
|
||||
|
||||
std::string filename;
|
||||
std::string stem;
|
||||
std::unique_ptr<DataLogThread> datalog;
|
||||
std::string status;
|
||||
bool highlight = false;
|
||||
};
|
||||
|
||||
struct Entry {
|
||||
explicit Entry(const wpi::log::StartRecordData& srd)
|
||||
: name{srd.name}, type{srd.type}, metadata{srd.metadata} {}
|
||||
|
||||
std::string name;
|
||||
std::string type;
|
||||
std::string metadata;
|
||||
std::set<InputFile*> inputFiles;
|
||||
bool typeConflict = false;
|
||||
bool metadataConflict = false;
|
||||
bool selected = true;
|
||||
|
||||
// used only during export
|
||||
int column = -1;
|
||||
};
|
||||
|
||||
struct EntryTreeNode {
|
||||
explicit EntryTreeNode(std::string_view name) : name{name} {}
|
||||
std::string name; // name of just this node
|
||||
std::string path; // full path if entry is nullptr
|
||||
Entry* entry = nullptr;
|
||||
std::vector<EntryTreeNode> children; // children, sorted by name
|
||||
int selected = 1;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
static std::map<std::string, std::unique_ptr<InputFile>, std::less<>>
|
||||
gInputFiles;
|
||||
static wpi::mutex gEntriesMutex;
|
||||
static std::map<std::string, std::unique_ptr<Entry>, std::less<>> gEntries;
|
||||
static std::vector<EntryTreeNode> gEntryTree;
|
||||
std::atomic_int gExportCount{0};
|
||||
|
||||
// must be called with gEntriesMutex held
|
||||
static void RebuildEntryTree() {
|
||||
gEntryTree.clear();
|
||||
wpi::SmallVector<std::string_view, 16> parts;
|
||||
for (auto& kv : gEntries) {
|
||||
parts.clear();
|
||||
// split on first : if one is present
|
||||
auto [prefix, mainpart] = wpi::split(kv.first, ':');
|
||||
if (mainpart.empty() || wpi::contains(prefix, '/')) {
|
||||
mainpart = kv.first;
|
||||
} else {
|
||||
parts.emplace_back(prefix);
|
||||
}
|
||||
wpi::split(mainpart, parts, '/', -1, false);
|
||||
|
||||
// ignore a raw "/" key
|
||||
if (parts.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get to leaf
|
||||
auto nodes = &gEntryTree;
|
||||
for (auto part : wpi::drop_back(std::span{parts.begin(), parts.end()})) {
|
||||
auto it =
|
||||
std::find_if(nodes->begin(), nodes->end(),
|
||||
[&](const auto& node) { return node.name == part; });
|
||||
if (it == nodes->end()) {
|
||||
nodes->emplace_back(part);
|
||||
// path is from the beginning of the string to the end of the current
|
||||
// part; this works because part is a reference to the internals of
|
||||
// kv.first
|
||||
nodes->back().path.assign(kv.first.data(),
|
||||
part.data() + part.size() - kv.first.data());
|
||||
it = nodes->end() - 1;
|
||||
}
|
||||
nodes = &it->children;
|
||||
}
|
||||
|
||||
auto it = std::find_if(nodes->begin(), nodes->end(), [&](const auto& node) {
|
||||
return node.name == parts.back();
|
||||
});
|
||||
if (it == nodes->end()) {
|
||||
nodes->emplace_back(parts.back());
|
||||
// no need to set path, as it's identical to kv.first
|
||||
it = nodes->end() - 1;
|
||||
}
|
||||
it->entry = kv.second.get();
|
||||
}
|
||||
}
|
||||
|
||||
InputFile::InputFile(std::unique_ptr<DataLogThread> datalog_)
|
||||
: filename{datalog_->GetBufferIdentifier()},
|
||||
stem{fs::path{filename}.stem().string()},
|
||||
datalog{std::move(datalog_)} {
|
||||
datalog->sigEntryAdded.connect([this](const wpi::log::StartRecordData& srd) {
|
||||
std::scoped_lock lock{gEntriesMutex};
|
||||
auto it = gEntries.find(srd.name);
|
||||
if (it == gEntries.end()) {
|
||||
it = gEntries.emplace(srd.name, std::make_unique<Entry>(srd)).first;
|
||||
RebuildEntryTree();
|
||||
} else {
|
||||
if (it->second->type != srd.type) {
|
||||
it->second->typeConflict = true;
|
||||
}
|
||||
if (it->second->metadata != srd.metadata) {
|
||||
it->second->metadataConflict = true;
|
||||
}
|
||||
}
|
||||
it->second->inputFiles.emplace(this);
|
||||
});
|
||||
}
|
||||
|
||||
InputFile::~InputFile() {
|
||||
if (gShutdown || !datalog) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{gEntriesMutex};
|
||||
bool changed = false;
|
||||
for (auto it = gEntries.begin(); it != gEntries.end();) {
|
||||
it->second->inputFiles.erase(this);
|
||||
if (it->second->inputFiles.empty()) {
|
||||
it = gEntries.erase(it);
|
||||
changed = true;
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
RebuildEntryTree();
|
||||
}
|
||||
}
|
||||
|
||||
static std::unique_ptr<InputFile> LoadDataLog(std::string_view filename) {
|
||||
std::error_code ec;
|
||||
auto buf = wpi::MemoryBuffer::GetFile(filename, ec);
|
||||
std::string fn{filename};
|
||||
if (ec) {
|
||||
return std::make_unique<InputFile>(
|
||||
fn, fmt::format("Could not open file: {}", ec.message()));
|
||||
}
|
||||
|
||||
wpi::log::DataLogReader reader{std::move(buf)};
|
||||
if (!reader.IsValid()) {
|
||||
return std::make_unique<InputFile>(fn, "Not a valid datalog file");
|
||||
}
|
||||
|
||||
return std::make_unique<InputFile>(
|
||||
std::make_unique<DataLogThread>(std::move(reader)));
|
||||
}
|
||||
|
||||
void DisplayInputFiles() {
|
||||
static std::unique_ptr<pfd::open_file> dataFileSelector;
|
||||
|
||||
SetNextWindowPos(ImVec2{0, 20}, ImGuiCond_FirstUseEver);
|
||||
SetNextWindowSize(ImVec2{375, 230}, ImGuiCond_FirstUseEver);
|
||||
if (ImGui::Begin("Input Files")) {
|
||||
if (ImGui::Button("Open File(s)...")) {
|
||||
dataFileSelector = std::make_unique<pfd::open_file>(
|
||||
"Select Data Log", "",
|
||||
std::vector<std::string>{"DataLog Files", "*.wpilog"},
|
||||
pfd::opt::multiselect);
|
||||
}
|
||||
ImGui::BeginTable(
|
||||
"Input Files", 3,
|
||||
ImGuiTableFlags_Borders | ImGuiTableFlags_SizingStretchProp);
|
||||
ImGui::TableSetupColumn("File");
|
||||
ImGui::TableSetupColumn("Status");
|
||||
ImGui::TableSetupColumn("X", ImGuiTableColumnFlags_WidthFixed |
|
||||
ImGuiTableColumnFlags_NoHeaderLabel |
|
||||
ImGuiTableColumnFlags_NoHeaderWidth);
|
||||
ImGui::TableHeadersRow();
|
||||
for (auto it = gInputFiles.begin(); it != gInputFiles.end();) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
if (it->second->highlight) {
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,
|
||||
IM_COL32(0, 64, 0, 255));
|
||||
it->second->highlight = false;
|
||||
}
|
||||
ImGui::TextUnformatted(it->first.c_str());
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("%s", it->second->filename.c_str());
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (it->second->datalog) {
|
||||
ImGui::Text("%u records, %u entries%s",
|
||||
it->second->datalog->GetNumRecords(),
|
||||
it->second->datalog->GetNumEntries(),
|
||||
it->second->datalog->IsDone() ? "" : " (working)");
|
||||
} else {
|
||||
ImGui::TextUnformatted(it->second->status.c_str());
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::PushID(it->first.c_str());
|
||||
if (ImGui::SmallButton("X")) {
|
||||
it = gInputFiles.erase(it);
|
||||
gExportCount = 0;
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
// Load data file(s)
|
||||
if (dataFileSelector && dataFileSelector->ready(0)) {
|
||||
auto result = dataFileSelector->result();
|
||||
for (auto&& filename : result) {
|
||||
// don't allow duplicates
|
||||
std::string stem = fs::path{filename}.stem().string();
|
||||
auto it = gInputFiles.find(stem);
|
||||
if (it == gInputFiles.end()) {
|
||||
gInputFiles.emplace(std::move(stem), LoadDataLog(filename));
|
||||
gExportCount = 0;
|
||||
}
|
||||
}
|
||||
dataFileSelector.reset();
|
||||
}
|
||||
}
|
||||
|
||||
static bool EmitEntry(const std::string& name, Entry& entry) {
|
||||
ImGui::TableNextColumn();
|
||||
bool rv = ImGui::Checkbox(name.c_str(), &entry.selected);
|
||||
if (ImGui::IsItemHovered() && gInputFiles.size() > 1) {
|
||||
for (auto inputFile : entry.inputFiles) {
|
||||
inputFile->highlight = true;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (entry.typeConflict) {
|
||||
ImGui::TextUnformatted("(Inconsistent)");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
for (auto inputFile : entry.inputFiles) {
|
||||
ImGui::Text(
|
||||
"%s: %s", inputFile->stem.c_str(),
|
||||
std::string{inputFile->datalog->GetEntry(entry.name).type}.c_str());
|
||||
}
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
} else {
|
||||
ImGui::TextUnformatted(entry.type.c_str());
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (entry.metadataConflict) {
|
||||
ImGui::TextUnformatted("(Inconsistent)");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
for (auto inputFile : entry.inputFiles) {
|
||||
ImGui::Text(
|
||||
"%s: %s", inputFile->stem.c_str(),
|
||||
std::string{inputFile->datalog->GetEntry(entry.name).metadata}
|
||||
.c_str());
|
||||
}
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
} else {
|
||||
ImGui::TextUnformatted(entry.metadata.c_str());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
static bool EmitEntryTree(std::vector<EntryTreeNode>& tree) {
|
||||
bool rv = false;
|
||||
for (auto&& node : tree) {
|
||||
if (node.entry) {
|
||||
if (EmitEntry(node.name, *node.entry)) {
|
||||
rv = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!node.children.empty()) {
|
||||
ImGui::TableNextColumn();
|
||||
auto label = fmt::format("##check_{}", node.name);
|
||||
if (node.selected == -1) {
|
||||
ImGui::PushItemFlag(ImGuiItemFlags_MixedValue, true);
|
||||
bool b = false;
|
||||
if (ImGui::Checkbox(label.c_str(), &b)) {
|
||||
node.selected = 3; // 3 = enable group
|
||||
rv = true;
|
||||
}
|
||||
ImGui::PopItemFlag();
|
||||
} else {
|
||||
bool b = node.selected == 1 || node.selected == 3;
|
||||
if (ImGui::Checkbox(label.c_str(), &b)) {
|
||||
node.selected = b ? 3 : 2; // 2 = disable group
|
||||
rv = true;
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
bool open = ImGui::TreeNodeEx(node.name.c_str(),
|
||||
ImGuiTreeNodeFlags_SpanFullWidth);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TableNextColumn();
|
||||
if (open) {
|
||||
if (EmitEntryTree(node.children)) {
|
||||
rv = true;
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
static void RefreshTreeCheckboxes(std::vector<EntryTreeNode>& tree,
|
||||
int* selected) {
|
||||
bool first = true;
|
||||
for (auto&& node : tree) {
|
||||
if (node.entry) {
|
||||
if (first && *selected == -1) {
|
||||
*selected = node.entry->selected ? 1 : 0;
|
||||
}
|
||||
if ((*selected == 0 && node.entry->selected) ||
|
||||
(*selected == 1 && !node.entry->selected)) {
|
||||
*selected = -1; // inconsistent
|
||||
} else if (*selected == 2) { // disable group
|
||||
node.entry->selected = false;
|
||||
} else if (*selected == 3) { // enable group
|
||||
node.entry->selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!node.children.empty()) {
|
||||
if (*selected == 2) { // disable group
|
||||
node.selected = 2;
|
||||
} else if (*selected == 3) { // enable group
|
||||
node.selected = 3;
|
||||
}
|
||||
RefreshTreeCheckboxes(node.children, &node.selected);
|
||||
if (node.selected == 2) {
|
||||
node.selected = 0;
|
||||
} else if (node.selected == 3) {
|
||||
node.selected = 1;
|
||||
}
|
||||
if (first && *selected == -1) {
|
||||
*selected = node.selected;
|
||||
} else if (node.selected == -1 ||
|
||||
(*selected == 0 && node.selected == 1) ||
|
||||
(*selected == 1 && node.selected == 0)) {
|
||||
*selected = -1; // inconsistent
|
||||
}
|
||||
}
|
||||
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayEntries() {
|
||||
SetNextWindowPos(ImVec2{380, 20}, ImGuiCond_FirstUseEver);
|
||||
SetNextWindowSize(ImVec2{540, 365}, ImGuiCond_FirstUseEver);
|
||||
if (ImGui::Begin("Entries")) {
|
||||
static bool treeView = true;
|
||||
if (ImGui::BeginPopupContextItem()) {
|
||||
ImGui::MenuItem("Tree View", "", &treeView);
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
std::scoped_lock lock{gEntriesMutex};
|
||||
ImGui::BeginTable(
|
||||
"Entries", 3,
|
||||
ImGuiTableFlags_Borders | ImGuiTableFlags_SizingStretchProp);
|
||||
ImGui::TableSetupColumn("Name");
|
||||
ImGui::TableSetupColumn("Type");
|
||||
ImGui::TableSetupColumn("Metadata");
|
||||
ImGui::TableHeadersRow();
|
||||
if (treeView) {
|
||||
if (EmitEntryTree(gEntryTree)) {
|
||||
int selected = -1;
|
||||
RefreshTreeCheckboxes(gEntryTree, &selected);
|
||||
}
|
||||
} else {
|
||||
for (auto&& kv : gEntries) {
|
||||
EmitEntry(kv.first, *kv.second);
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
static wpi::mutex gExportMutex;
|
||||
static std::vector<std::string> gExportErrors;
|
||||
|
||||
static void PrintEscapedCsvString(wpi::raw_ostream& os, std::string_view str) {
|
||||
auto s = str;
|
||||
while (!s.empty()) {
|
||||
std::string_view fragment;
|
||||
std::tie(fragment, s) = wpi::split(s, '"');
|
||||
os << fragment;
|
||||
if (!s.empty()) {
|
||||
os << '"' << '"';
|
||||
}
|
||||
}
|
||||
if (wpi::ends_with(str, '"')) {
|
||||
os << '"' << '"';
|
||||
}
|
||||
}
|
||||
|
||||
static void ValueToCsv(wpi::raw_ostream& os, const Entry& entry,
|
||||
const wpi::log::DataLogRecord& record) {
|
||||
// handle systemTime specially
|
||||
if (entry.name == "systemTime" && entry.type == "int64") {
|
||||
int64_t val;
|
||||
if (record.GetInteger(&val)) {
|
||||
std::time_t timeval = val / 1000000;
|
||||
fmt::print(os, "{:%Y-%m-%d %H:%M:%S}.{:06}", *std::localtime(&timeval),
|
||||
val % 1000000);
|
||||
return;
|
||||
}
|
||||
} else if (entry.type == "double") {
|
||||
double val;
|
||||
if (record.GetDouble(&val)) {
|
||||
fmt::print(os, "{}", val);
|
||||
return;
|
||||
}
|
||||
} else if (entry.type == "int64") {
|
||||
int64_t val;
|
||||
if (record.GetInteger(&val)) {
|
||||
fmt::print(os, "{}", val);
|
||||
return;
|
||||
}
|
||||
} else if (entry.type == "string" || entry.type == "json") {
|
||||
std::string_view val;
|
||||
record.GetString(&val);
|
||||
os << '"';
|
||||
PrintEscapedCsvString(os, val);
|
||||
os << '"';
|
||||
return;
|
||||
} else if (entry.type == "boolean") {
|
||||
bool val;
|
||||
if (record.GetBoolean(&val)) {
|
||||
fmt::print(os, "{}", val);
|
||||
return;
|
||||
}
|
||||
} else if (entry.type == "boolean[]") {
|
||||
std::vector<int> val;
|
||||
if (record.GetBooleanArray(&val)) {
|
||||
fmt::print(os, "{}", fmt::join(val, ";"));
|
||||
return;
|
||||
}
|
||||
} else if (entry.type == "double[]") {
|
||||
std::vector<double> val;
|
||||
if (record.GetDoubleArray(&val)) {
|
||||
fmt::print(os, "{}", fmt::join(val, ";"));
|
||||
return;
|
||||
}
|
||||
} else if (entry.type == "float[]") {
|
||||
std::vector<float> val;
|
||||
if (record.GetFloatArray(&val)) {
|
||||
fmt::print(os, "{}", fmt::join(val, ";"));
|
||||
return;
|
||||
}
|
||||
} else if (entry.type == "int64[]") {
|
||||
std::vector<int64_t> val;
|
||||
if (record.GetIntegerArray(&val)) {
|
||||
fmt::print(os, "{}", fmt::join(val, ";"));
|
||||
return;
|
||||
}
|
||||
} else if (entry.type == "string[]") {
|
||||
std::vector<std::string_view> val;
|
||||
if (record.GetStringArray(&val)) {
|
||||
os << '"';
|
||||
bool first = true;
|
||||
for (auto&& v : val) {
|
||||
if (!first) {
|
||||
os << ';';
|
||||
}
|
||||
first = false;
|
||||
PrintEscapedCsvString(os, v);
|
||||
}
|
||||
os << '"';
|
||||
return;
|
||||
}
|
||||
}
|
||||
fmt::print(os, "<invalid>");
|
||||
}
|
||||
|
||||
static void ExportCsvFile(InputFile& f, wpi::raw_ostream& os, int style) {
|
||||
// header
|
||||
if (style == 0) {
|
||||
os << "Timestamp,Name,Value\n";
|
||||
} else if (style == 1) {
|
||||
// scan for exported fields for this file to print header and assign columns
|
||||
os << "Timestamp";
|
||||
int columnNum = 0;
|
||||
for (auto&& entry : gEntries) {
|
||||
if (entry.second->selected &&
|
||||
entry.second->inputFiles.find(&f) != entry.second->inputFiles.end()) {
|
||||
os << ',' << '"';
|
||||
PrintEscapedCsvString(os, entry.first);
|
||||
os << '"';
|
||||
entry.second->column = columnNum++;
|
||||
} else {
|
||||
entry.second->column = -1;
|
||||
}
|
||||
}
|
||||
os << '\n';
|
||||
}
|
||||
|
||||
wpi::DenseMap<int, Entry*> nameMap;
|
||||
for (auto&& record : f.datalog->GetReader()) {
|
||||
if (record.IsStart()) {
|
||||
wpi::log::StartRecordData data;
|
||||
if (record.GetStartData(&data)) {
|
||||
auto it = gEntries.find(data.name);
|
||||
if (it != gEntries.end() && it->second->selected) {
|
||||
nameMap[data.entry] = it->second.get();
|
||||
}
|
||||
}
|
||||
} else if (record.IsFinish()) {
|
||||
int entry;
|
||||
if (record.GetFinishEntry(&entry)) {
|
||||
nameMap.erase(entry);
|
||||
}
|
||||
} else if (!record.IsControl()) {
|
||||
auto entryIt = nameMap.find(record.GetEntry());
|
||||
if (entryIt == nameMap.end()) {
|
||||
continue;
|
||||
}
|
||||
Entry* entry = entryIt->second;
|
||||
|
||||
if (style == 0) {
|
||||
fmt::print(os, "{},\"", record.GetTimestamp() / 1000000.0);
|
||||
PrintEscapedCsvString(os, entry->name);
|
||||
os << '"' << ',';
|
||||
ValueToCsv(os, *entry, record);
|
||||
os << '\n';
|
||||
} else if (style == 1 && entry->column != -1) {
|
||||
fmt::print(os, "{},", record.GetTimestamp() / 1000000.0);
|
||||
for (int i = 0; i < entry->column; ++i) {
|
||||
os << ',';
|
||||
}
|
||||
ValueToCsv(os, *entry, record);
|
||||
os << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ExportCsv(std::string_view outputFolder, int style) {
|
||||
fs::path outPath{outputFolder};
|
||||
for (auto&& f : gInputFiles) {
|
||||
if (f.second->datalog) {
|
||||
std::error_code ec;
|
||||
auto of = fs::OpenFileForWrite(
|
||||
outPath / fs::path{f.first}.replace_extension("csv"), ec,
|
||||
fs::CD_CreateNew, fs::OF_Text);
|
||||
if (ec) {
|
||||
std::scoped_lock lock{gExportMutex};
|
||||
gExportErrors.emplace_back(
|
||||
fmt::format("{}: {}", f.first, ec.message()));
|
||||
++gExportCount;
|
||||
continue;
|
||||
}
|
||||
wpi::raw_fd_ostream os{fs::FileToFd(of, ec, fs::OF_Text), true};
|
||||
ExportCsvFile(*f.second, os, style);
|
||||
}
|
||||
++gExportCount;
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayOutput(glass::Storage& storage) {
|
||||
static std::string& outputFolder = storage.GetString("outputFolder");
|
||||
static std::unique_ptr<pfd::select_folder> outputFolderSelector;
|
||||
|
||||
SetNextWindowPos(ImVec2{380, 390}, ImGuiCond_FirstUseEver);
|
||||
SetNextWindowSize(ImVec2{540, 120}, ImGuiCond_FirstUseEver);
|
||||
if (ImGui::Begin("Output")) {
|
||||
if (ImGui::Button("Select Output Folder...")) {
|
||||
outputFolderSelector =
|
||||
std::make_unique<pfd::select_folder>("Select Output Folder");
|
||||
}
|
||||
ImGui::TextUnformatted(outputFolder.c_str());
|
||||
|
||||
static const char* const options[] = {"List", "Table"};
|
||||
static int style = 0;
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
|
||||
ImGui::Combo("Style", &style, options,
|
||||
sizeof(options) / sizeof(const char*));
|
||||
|
||||
static std::future<void> exporter;
|
||||
if (!gInputFiles.empty() && !outputFolder.empty() &&
|
||||
ImGui::Button("Export CSV") &&
|
||||
(gExportCount == 0 ||
|
||||
gExportCount == static_cast<int>(gInputFiles.size()))) {
|
||||
gExportCount = 0;
|
||||
gExportErrors.clear();
|
||||
exporter = std::async(std::launch::async, ExportCsv, outputFolder, style);
|
||||
}
|
||||
if (exporter.valid()) {
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("Exported %d/%d", gExportCount.load(),
|
||||
static_cast<int>(gInputFiles.size()));
|
||||
}
|
||||
{
|
||||
std::scoped_lock lock{gExportMutex};
|
||||
for (auto&& err : gExportErrors) {
|
||||
ImGui::TextUnformatted(err.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (outputFolderSelector && outputFolderSelector->ready(0)) {
|
||||
outputFolder = outputFolderSelector->result();
|
||||
outputFolderSelector.reset();
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <frc/commands/InstantCommand.h>
|
||||
namespace glass {
|
||||
class Storage;
|
||||
} // namespace glass
|
||||
|
||||
class ReplaceMeInstantCommand : public frc::InstantCommand {
|
||||
public:
|
||||
ReplaceMeInstantCommand();
|
||||
void Initialize() override;
|
||||
};
|
||||
void DisplayInputFiles();
|
||||
void DisplayEntries();
|
||||
void DisplayOutput(glass::Storage& storage);
|
||||
|
||||
extern bool gShutdown;
|
||||
215
datalogtool/src/main/native/cpp/Sftp.cpp
Normal file
@@ -0,0 +1,215 @@
|
||||
// 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 "Sftp.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
using namespace sftp;
|
||||
|
||||
Attributes::Attributes(sftp_attributes&& attr)
|
||||
: name{attr->name}, flags{attr->flags}, type{attr->type}, size{attr->size} {
|
||||
sftp_attributes_free(attr);
|
||||
}
|
||||
|
||||
static std::string GetError(sftp_session sftp) {
|
||||
switch (sftp_get_error(sftp)) {
|
||||
case SSH_FX_EOF:
|
||||
return "end of file";
|
||||
case SSH_FX_NO_SUCH_FILE:
|
||||
return "no such file";
|
||||
case SSH_FX_PERMISSION_DENIED:
|
||||
return "permission denied";
|
||||
case SSH_FX_FAILURE:
|
||||
return "SFTP failure";
|
||||
case SSH_FX_BAD_MESSAGE:
|
||||
return "SFTP bad message";
|
||||
case SSH_FX_NO_CONNECTION:
|
||||
return "SFTP no connection";
|
||||
case SSH_FX_CONNECTION_LOST:
|
||||
return "SFTP connection lost";
|
||||
case SSH_FX_OP_UNSUPPORTED:
|
||||
return "SFTP operation unsupported";
|
||||
case SSH_FX_INVALID_HANDLE:
|
||||
return "SFTP invalid handle";
|
||||
case SSH_FX_NO_SUCH_PATH:
|
||||
return "no such path";
|
||||
case SSH_FX_FILE_ALREADY_EXISTS:
|
||||
return "file already exists";
|
||||
case SSH_FX_WRITE_PROTECT:
|
||||
return "write protected filesystem";
|
||||
case SSH_FX_NO_MEDIA:
|
||||
return "no media inserted";
|
||||
default:
|
||||
return ssh_get_error(sftp->session);
|
||||
}
|
||||
}
|
||||
|
||||
Exception::Exception(sftp_session sftp)
|
||||
: runtime_error{GetError(sftp)}, err{sftp_get_error(sftp)} {}
|
||||
|
||||
File::~File() {
|
||||
if (m_handle) {
|
||||
sftp_close(m_handle);
|
||||
}
|
||||
}
|
||||
|
||||
Attributes File::Stat() const {
|
||||
sftp_attributes attr = sftp_fstat(m_handle);
|
||||
if (!attr) {
|
||||
throw Exception{m_handle->sftp};
|
||||
}
|
||||
return Attributes{std::move(attr)};
|
||||
}
|
||||
|
||||
size_t File::Read(void* buf, uint32_t count) {
|
||||
auto rv = sftp_read(m_handle, buf, count);
|
||||
if (rv < 0) {
|
||||
throw Exception{m_handle->sftp};
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
File::AsyncId File::AsyncReadBegin(uint32_t len) const {
|
||||
int rv = sftp_async_read_begin(m_handle, len);
|
||||
if (rv < 0) {
|
||||
throw Exception{m_handle->sftp};
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
size_t File::AsyncRead(void* data, uint32_t len, AsyncId id) {
|
||||
auto rv = sftp_async_read(m_handle, data, len, id);
|
||||
if (rv == SSH_ERROR) {
|
||||
throw Exception{ssh_get_error(m_handle->sftp->session)};
|
||||
}
|
||||
if (rv == SSH_AGAIN) {
|
||||
return 0;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
size_t File::Write(std::span<const uint8_t> data) {
|
||||
auto rv = sftp_write(m_handle, data.data(), data.size());
|
||||
if (rv < 0) {
|
||||
throw Exception{m_handle->sftp};
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
void File::Seek(uint64_t offset) {
|
||||
if (sftp_seek64(m_handle, offset) < 0) {
|
||||
throw Exception{m_handle->sftp};
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t File::Tell() const {
|
||||
return sftp_tell64(m_handle);
|
||||
}
|
||||
|
||||
void File::Rewind() {
|
||||
sftp_rewind(m_handle);
|
||||
}
|
||||
|
||||
void File::Sync() {
|
||||
if (sftp_fsync(m_handle) < 0) {
|
||||
throw Exception{m_handle->sftp};
|
||||
}
|
||||
}
|
||||
|
||||
Session::Session(std::string_view host, int port, std::string_view user,
|
||||
std::string_view pass)
|
||||
: m_host{host}, m_port{port}, m_username{user}, m_password{pass} {
|
||||
// Create a new SSH session.
|
||||
m_session = ssh_new();
|
||||
if (!m_session) {
|
||||
throw Exception{"The SSH session could not be allocated."};
|
||||
}
|
||||
|
||||
// Set the host, user, and port.
|
||||
ssh_options_set(m_session, SSH_OPTIONS_HOST, m_host.c_str());
|
||||
ssh_options_set(m_session, SSH_OPTIONS_USER, m_username.c_str());
|
||||
ssh_options_set(m_session, SSH_OPTIONS_PORT, &m_port);
|
||||
|
||||
// Set timeout to 3 seconds.
|
||||
int64_t timeout = 3L;
|
||||
ssh_options_set(m_session, SSH_OPTIONS_TIMEOUT, &timeout);
|
||||
|
||||
// Set other miscellaneous options.
|
||||
ssh_options_set(m_session, SSH_OPTIONS_STRICTHOSTKEYCHECK, "no");
|
||||
}
|
||||
|
||||
Session::~Session() {
|
||||
if (m_sftp) {
|
||||
sftp_free(m_sftp);
|
||||
}
|
||||
if (m_session) {
|
||||
ssh_free(m_session);
|
||||
}
|
||||
}
|
||||
|
||||
void Session::Connect() {
|
||||
// Connect to the server.
|
||||
int rc = ssh_connect(m_session);
|
||||
if (rc != SSH_OK) {
|
||||
throw Exception{ssh_get_error(m_session)};
|
||||
}
|
||||
|
||||
// Authenticate with password.
|
||||
rc = ssh_userauth_password(m_session, nullptr, m_password.c_str());
|
||||
if (rc != SSH_AUTH_SUCCESS) {
|
||||
throw Exception{ssh_get_error(m_session)};
|
||||
}
|
||||
|
||||
// Allocate the SFTP session.
|
||||
m_sftp = sftp_new(m_session);
|
||||
if (!m_sftp) {
|
||||
throw Exception{ssh_get_error(m_session)};
|
||||
}
|
||||
|
||||
// Initialize.
|
||||
rc = sftp_init(m_sftp);
|
||||
if (rc != SSH_OK) {
|
||||
sftp_free(m_sftp);
|
||||
m_sftp = nullptr;
|
||||
throw Exception{ssh_get_error(m_session)};
|
||||
}
|
||||
}
|
||||
|
||||
void Session::Disconnect() {
|
||||
if (m_sftp) {
|
||||
sftp_free(m_sftp);
|
||||
m_sftp = nullptr;
|
||||
}
|
||||
ssh_disconnect(m_session);
|
||||
}
|
||||
|
||||
std::vector<Attributes> Session::ReadDir(const std::string& path) {
|
||||
sftp_dir dir = sftp_opendir(m_sftp, path.c_str());
|
||||
if (!dir) {
|
||||
throw Exception{m_sftp};
|
||||
}
|
||||
|
||||
std::vector<Attributes> rv;
|
||||
while (sftp_attributes attr = sftp_readdir(m_sftp, dir)) {
|
||||
rv.emplace_back(std::move(attr));
|
||||
}
|
||||
|
||||
sftp_closedir(dir);
|
||||
return rv;
|
||||
}
|
||||
|
||||
void Session::Unlink(const std::string& filename) {
|
||||
if (sftp_unlink(m_sftp, filename.c_str()) < 0) {
|
||||
throw Exception{m_sftp};
|
||||
}
|
||||
}
|
||||
|
||||
File Session::Open(const std::string& filename, int accesstype, mode_t mode) {
|
||||
sftp_file f = sftp_open(m_sftp, filename.c_str(), accesstype, mode);
|
||||
if (!f) {
|
||||
throw Exception{m_sftp};
|
||||
}
|
||||
return File{std::move(f)};
|
||||
}
|
||||
143
datalogtool/src/main/native/cpp/Sftp.h
Normal file
@@ -0,0 +1,143 @@
|
||||
// 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 <libssh/libssh.h>
|
||||
#include <libssh/sftp.h>
|
||||
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace sftp {
|
||||
|
||||
struct Attributes {
|
||||
Attributes() = default;
|
||||
explicit Attributes(sftp_attributes&& attr);
|
||||
|
||||
std::string name;
|
||||
uint32_t flags = 0;
|
||||
uint8_t type = 0;
|
||||
uint64_t size = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the exception that will be thrown if something goes wrong.
|
||||
*/
|
||||
class Exception : public std::runtime_error {
|
||||
public:
|
||||
explicit Exception(const std::string& msg) : std::runtime_error{msg} {}
|
||||
explicit Exception(sftp_session sftp);
|
||||
|
||||
int err = 0;
|
||||
};
|
||||
|
||||
class File {
|
||||
public:
|
||||
File() = default;
|
||||
explicit File(sftp_file&& handle) : m_handle{handle} {}
|
||||
~File();
|
||||
|
||||
Attributes Stat() const;
|
||||
|
||||
void SetNonblocking() { sftp_file_set_nonblocking(m_handle); }
|
||||
void SetBlocking() { sftp_file_set_blocking(m_handle); }
|
||||
|
||||
using AsyncId = uint32_t;
|
||||
|
||||
size_t Read(void* buf, uint32_t count);
|
||||
AsyncId AsyncReadBegin(uint32_t len) const;
|
||||
size_t AsyncRead(void* data, uint32_t len, AsyncId id);
|
||||
size_t Write(std::span<const uint8_t> data);
|
||||
|
||||
void Seek(uint64_t offset);
|
||||
uint64_t Tell() const;
|
||||
void Rewind();
|
||||
|
||||
void Sync();
|
||||
|
||||
std::string_view GetName() const { return m_handle->name; }
|
||||
uint64_t GetOffset() const { return m_handle->offset; }
|
||||
bool IsEof() const { return m_handle->eof; }
|
||||
bool IsNonblocking() const { return m_handle->nonblocking; }
|
||||
|
||||
private:
|
||||
sftp_file m_handle{nullptr};
|
||||
};
|
||||
|
||||
/**
|
||||
* This class is a C++ implementation of the SshSessionController in
|
||||
* wpilibsuite/deploy-utils. It handles connecting to an SSH server, running
|
||||
* commands, and transferring files.
|
||||
*/
|
||||
class Session {
|
||||
public:
|
||||
/**
|
||||
* Constructs a new session controller.
|
||||
*
|
||||
* @param host The hostname of the server to connect to.
|
||||
* @param port The port that the sshd server is operating on.
|
||||
* @param user The username to login as.
|
||||
* @param pass The password for the given username.
|
||||
*/
|
||||
Session(std::string_view host, int port, std::string_view user,
|
||||
std::string_view pass);
|
||||
|
||||
/**
|
||||
* Destroys the controller object. This also disconnects the session from the
|
||||
* server.
|
||||
*/
|
||||
~Session();
|
||||
|
||||
/**
|
||||
* Opens the SSH connection to the given host.
|
||||
*/
|
||||
void Connect();
|
||||
|
||||
/**
|
||||
* Disconnects the SSH connection.
|
||||
*/
|
||||
void Disconnect();
|
||||
|
||||
/**
|
||||
* Reads directory entries
|
||||
*
|
||||
* @param path remote path
|
||||
* @return vector of file attributes
|
||||
*/
|
||||
std::vector<Attributes> ReadDir(const std::string& path);
|
||||
|
||||
/**
|
||||
* Unlinks (deletes) a file.
|
||||
*
|
||||
* @param filename filename
|
||||
*/
|
||||
void Unlink(const std::string& filename);
|
||||
|
||||
/**
|
||||
* Opens a file.
|
||||
*
|
||||
* @param filename filename
|
||||
* @param accesstype O_RDONLY, O_WRONLY, or O_RDWR, combined with O_CREAT,
|
||||
* O_EXCL, or O_TRUNC
|
||||
* @param mode permissions to use if a new file is created
|
||||
* @return File
|
||||
*/
|
||||
File Open(const std::string& filename, int accesstype, mode_t mode);
|
||||
|
||||
private:
|
||||
ssh_session m_session{nullptr};
|
||||
sftp_session m_sftp{nullptr};
|
||||
std::string m_host;
|
||||
|
||||
int m_port;
|
||||
|
||||
std::string m_username;
|
||||
std::string m_password;
|
||||
};
|
||||
|
||||
} // namespace sftp
|
||||
25
datalogtool/src/main/native/cpp/main.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <string_view>
|
||||
|
||||
void Application(std::string_view saveDir);
|
||||
|
||||
#ifdef _WIN32
|
||||
int __stdcall WinMain(void* hInstance, void* hPrevInstance, char* pCmdLine,
|
||||
int nCmdShow) {
|
||||
int argc = __argc;
|
||||
char** argv = __argv;
|
||||
#else
|
||||
int main(int argc, char** argv) {
|
||||
#endif
|
||||
std::string_view saveDir;
|
||||
if (argc == 2) {
|
||||
saveDir = argv[1];
|
||||
}
|
||||
|
||||
Application(saveDir);
|
||||
|
||||
return 0;
|
||||
}
|
||||
BIN
datalogtool/src/main/native/mac/datalogtool.icns
Normal file
BIN
datalogtool/src/main/native/resources/dlt-128.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
datalogtool/src/main/native/resources/dlt-16.png
Normal file
|
After Width: | Height: | Size: 609 B |
BIN
datalogtool/src/main/native/resources/dlt-256.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
datalogtool/src/main/native/resources/dlt-32.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
datalogtool/src/main/native/resources/dlt-48.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
datalogtool/src/main/native/resources/dlt-512.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
datalogtool/src/main/native/resources/dlt-64.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
datalogtool/src/main/native/win/datalogtool.ico
Normal file
|
After Width: | Height: | Size: 361 KiB |