mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-22 01:11:42 +00:00
Compare commits
119 Commits
v2019.1.1-
...
v2019.3.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60c2f59051 | ||
|
|
d55ca191b8 | ||
|
|
e8b24717c7 | ||
|
|
182758c05b | ||
|
|
74f7ba04b0 | ||
|
|
997d4fdf47 | ||
|
|
76d9e26633 | ||
|
|
a230c814cb | ||
|
|
12cb77cd7c | ||
|
|
8a9822a96b | ||
|
|
a9371a7586 | ||
|
|
6992f5421f | ||
|
|
43696956d2 | ||
|
|
ae3fd5adac | ||
|
|
404666b298 | ||
|
|
1eb4c99d15 | ||
|
|
910b9f3af7 | ||
|
|
09d90b02fb | ||
|
|
0e1f9c2ed2 | ||
|
|
f156a00117 | ||
|
|
4a6087ed56 | ||
|
|
88a09dd13a | ||
|
|
7d19596367 | ||
|
|
bd05dfa1c7 | ||
|
|
05d6660a6b | ||
|
|
1349dd4bd8 | ||
|
|
fdf298b172 | ||
|
|
453a9047e4 | ||
|
|
e97e7a7611 | ||
|
|
308bdbe298 | ||
|
|
f889b45d59 | ||
|
|
444b899a9f | ||
|
|
f121ccff0d | ||
|
|
bc2c932f92 | ||
|
|
6bdd7ce506 | ||
|
|
c12d7729e3 | ||
|
|
3635116049 | ||
|
|
6105873cbe | ||
|
|
80f87ff8ad | ||
|
|
a2368a6199 | ||
|
|
ae3cb6c83b | ||
|
|
f0f196e5b3 | ||
|
|
7c35355d29 | ||
|
|
75cc09a9e4 | ||
|
|
0e2e180635 | ||
|
|
01d1322066 | ||
|
|
ceed1d74dc | ||
|
|
e1bf623997 | ||
|
|
d46ce13ffe | ||
|
|
300eeb330d | ||
|
|
d817001259 | ||
|
|
8ac4b113a5 | ||
|
|
f3864e9abb | ||
|
|
799c3ea8a6 | ||
|
|
8d95c38e39 | ||
|
|
a7f4e29b73 | ||
|
|
b88369f5e8 | ||
|
|
ce6f1d0588 | ||
|
|
f163216a4c | ||
|
|
c449ef1064 | ||
|
|
6593f4346e | ||
|
|
ce1367a115 | ||
|
|
0d7d880261 | ||
|
|
ca2acec88c | ||
|
|
3721463eb3 | ||
|
|
6e8f8be370 | ||
|
|
d84240d8e9 | ||
|
|
1823cb2b68 | ||
|
|
41596608cc | ||
|
|
0c3b488e18 | ||
|
|
7d7af287f6 | ||
|
|
4119622994 | ||
|
|
d528a77963 | ||
|
|
dab7e1b7a2 | ||
|
|
ab49345460 | ||
|
|
608d82423d | ||
|
|
e0e15eafeb | ||
|
|
0fb24538a7 | ||
|
|
d65547ea74 | ||
|
|
bfe15245a6 | ||
|
|
ff58c5156a | ||
|
|
6d4326a560 | ||
|
|
97ba195b88 | ||
|
|
3d546428ab | ||
|
|
b64dfacff3 | ||
|
|
dcbf02a1ec | ||
|
|
7e1ec28839 | ||
|
|
ef16317f8f | ||
|
|
26e8e587f9 | ||
|
|
0d0492bfcc | ||
|
|
3b33abfc7b | ||
|
|
99033481e0 | ||
|
|
b4901985b7 | ||
|
|
97edb6c68f | ||
|
|
73de3364b7 | ||
|
|
5551981b3f | ||
|
|
90572a3cc5 | ||
|
|
c405188052 | ||
|
|
bea0565ac9 | ||
|
|
0b03454366 | ||
|
|
489701cacc | ||
|
|
a769d56ec1 | ||
|
|
6f0c185a05 | ||
|
|
a60f312d19 | ||
|
|
acb786a791 | ||
|
|
df347e3d80 | ||
|
|
e4aa45f34b | ||
|
|
75cc3cda28 | ||
|
|
45f4472d42 | ||
|
|
69cb53b51b | ||
|
|
70a66fc943 | ||
|
|
9207d788ab | ||
|
|
ef3a31aa20 | ||
|
|
63775362fe | ||
|
|
55493b0c18 | ||
|
|
1696557c46 | ||
|
|
ecd376be4c | ||
|
|
f54c0f70f6 | ||
|
|
9bc998f4b0 |
6
.wpilib/wpilib_preferences.json
Normal file
6
.wpilib/wpilib_preferences.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"enableCppIntellisense": true,
|
||||
"currentLanguage": "cpp",
|
||||
"projectYear": "2019",
|
||||
"teamNumber": 0
|
||||
}
|
||||
19
README.md
19
README.md
@@ -25,10 +25,10 @@ Using Gradle makes building WPILib very straightforward. It only has a few depen
|
||||
|
||||
- A C++ compiler
|
||||
- On Linux, GCC works fine
|
||||
- On Windows, you need Visual Studio 2015 (the free community edition works fine).
|
||||
- On Windows, you need Visual Studio 2017 (the free community edition works fine).
|
||||
Make sure to select the C++ Programming Language for installation
|
||||
- [ARM Compiler Toolchain](http://first.wpi.edu/FRC/roborio/toolchains/)
|
||||
* Note that for 2017-2018 and beyond, you will need version 5 or greater of GCC
|
||||
- [ARM Compiler Toolchain](https://github.com/wpilibsuite/toolchain-builder/releases)
|
||||
* Note that for 2019 and beyond, you should use version 6 or greater of GCC
|
||||
- Doxygen (Only required if you want to build the C++ documentation)
|
||||
|
||||
## Setup
|
||||
@@ -79,23 +79,18 @@ There are a few tasks other than `build` available. To see them, run the meta-ta
|
||||
|
||||
wpiformat can be executed anywhere in the repository via `py -3 -m wpiformat` on Windows or `python3 -m wpiformat` on other platforms.
|
||||
|
||||
CMake is also supported for building. See [README-CMAKE.md](README-CMAKE.md).
|
||||
|
||||
## Publishing
|
||||
|
||||
If you are building to test with the Eclipse plugins or just want to export the build as a Maven-style dependency, simply run the `publish` task. This task will publish all available packages to ~/releases/maven/development. If you need to publish the project to a different repo, you can specify it with `-Prepo=repo_name`. Valid options are:
|
||||
If you are building to test with other dependencies or just want to export the build as a Maven-style dependency, simply run the `publish` task. This task will publish all available packages to ~/releases/maven/development. If you need to publish the project to a different repo, you can specify it with `-Prepo=repo_name`. Valid options are:
|
||||
|
||||
- development - The default repo.
|
||||
- beta - Publishes to ~/releases/maven/beta.
|
||||
- stable - Publishes to ~/releases/maven/stable.
|
||||
- release - Publishes to ~/releases/maven/release.
|
||||
|
||||
The following maven targets a published by this task:
|
||||
|
||||
- edu.wpi.first.wpilib.cmake:cpp-root:1.0.0 - roboRIO C++
|
||||
- edu.wpi.first.wpilibc.simulation:WPILibCSim:0.1.0 - Simulation C++
|
||||
- edu.wpi.first.wpilibj:wpilibJavaFinal:0.1.0-SNAPSHOT - roboRIO Java
|
||||
- edu.wpi.first.wpilibj:wpilibJavaSim:0.1.0-SNAPSHOT - Simulation Java
|
||||
- edu.wpi.first.wpilibj.simulation:SimDS:0.1.0-SNAPSHOT - The driverstation for controlling simulation.
|
||||
- org.gazebosim:JavaGazebo:0.1.0-SNAPSHOT - Gazebo protocol for Java.
|
||||
The maven artifacts are described in [MavenArtifacts.md](MavenArtifacts.md)
|
||||
|
||||
## Structure and Organization
|
||||
|
||||
|
||||
@@ -31,6 +31,13 @@ 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
|
||||
Optional wpiutil/src/main/native/include/wpi/optional.h
|
||||
wpiutil/src/test/native/cpp/test_optional.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-*
|
||||
|
||||
|
||||
==============================================================================
|
||||
@@ -212,3 +219,155 @@ 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.
|
||||
|
||||
|
||||
==============================================================================
|
||||
Optional License
|
||||
==============================================================================
|
||||
Copyright (C) 2011 - 2017 Andrzej Krzemienski.
|
||||
|
||||
Boost Software License - Version 1.0 - August 17th, 2003
|
||||
|
||||
Permission is hereby granted, free of charge, to any person or organization
|
||||
obtaining a copy of the software and accompanying documentation covered by
|
||||
this license (the "Software") to use, reproduce, display, distribute,
|
||||
execute, and transmit the Software, and to prepare derivative works of the
|
||||
Software, and to permit third-parties to whom the Software is furnished to
|
||||
do so, all subject to the following:
|
||||
|
||||
The copyright notices in the Software and this entire statement, including
|
||||
the above license grant, this restriction and the following disclaimer,
|
||||
must be included in all copies of the Software, in whole or in part, and
|
||||
all derivative works of the Software, unless such copies or derivative
|
||||
works are solely in the form of machine-executable object code generated by
|
||||
a source language processor.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
==============================================================================
|
||||
Bootstrap License
|
||||
==============================================================================
|
||||
Copyright (c) 2011-2018 Twitter, Inc.
|
||||
Copyright (c) 2011-2018 The Bootstrap Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
==============================================================================
|
||||
CoreUI License
|
||||
==============================================================================
|
||||
Copyright (c) 2018 creativeLabs tukasz Holeczek.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
==============================================================================
|
||||
Feather Icons License
|
||||
==============================================================================
|
||||
Copyright (c) 2013-2017 Cole Bemis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
==============================================================================
|
||||
jQuery License
|
||||
==============================================================================
|
||||
Copyright JS Foundation and other contributors, https://js.foundation/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
==============================================================================
|
||||
popper.js License
|
||||
==============================================================================
|
||||
Copyright (c) 2016 Federico Zivolo and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -166,6 +166,9 @@ jobs:
|
||||
sudo tar xvzf build/jdk.tar.gz -C /Library/Java/JavaVirtualMachines/
|
||||
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-11.jdk/Contents/Home/
|
||||
displayName: 'Setup JDK'
|
||||
- script: |
|
||||
rm /Users/vsts/.gradle/init.d/log-gradle-version-plugin.gradle
|
||||
displayName: 'Delete Version init script'
|
||||
- task: Gradle@2
|
||||
inputs:
|
||||
workingDirectory: ''
|
||||
|
||||
12
build.gradle
12
build.gradle
@@ -1,12 +1,14 @@
|
||||
plugins {
|
||||
id 'base'
|
||||
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '2.2'
|
||||
id 'edu.wpi.first.NativeUtils' version '2.0.1'
|
||||
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '2.3'
|
||||
id 'edu.wpi.first.NativeUtils' version '2.1.2'
|
||||
id 'edu.wpi.first.GradleJni' version '0.3.1'
|
||||
id 'edu.wpi.first.GradleVsCode' version '0.6.1'
|
||||
id 'edu.wpi.first.GradleVsCode' version '0.7.1'
|
||||
id 'idea'
|
||||
id 'com.gradle.build-scan' version '1.15.1'
|
||||
id 'visual-studio'
|
||||
id 'com.gradle.build-scan' version '2.0.2'
|
||||
id 'net.ltgt.errorprone' version '0.6' apply false
|
||||
id 'com.github.johnrengelman.shadow' version '4.0.3' apply false
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -98,5 +100,5 @@ subprojects {
|
||||
}
|
||||
|
||||
wrapper {
|
||||
gradleVersion = '4.9'
|
||||
gradleVersion = '5.0'
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ class SingleNativeBuild implements Plugin<Project> {
|
||||
components.each { component ->
|
||||
if (component.name == "${nativeName}Base") {
|
||||
base = (NativeLibrarySpec) component
|
||||
} else if (component.name == "${nativeName}") {
|
||||
} else if (component.name == "${nativeName}" || component.name == "${nativeName}JNI") {
|
||||
subs << component
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,3 +51,7 @@ endif()
|
||||
|
||||
install(FILES cameraserver-config.cmake DESTINATION ${cameraserver_config_dir})
|
||||
install(EXPORT cameraserver DESTINATION ${cameraserver_config_dir})
|
||||
|
||||
file(GLOB multiCameraServer_src multiCameraServer/src/main/native/cpp/*.cpp)
|
||||
add_executable(multiCameraServer ${multiCameraServer_src})
|
||||
target_link_libraries(multiCameraServer cameraserver)
|
||||
|
||||
62
cameraserver/multiCameraServer/build.gradle
Normal file
62
cameraserver/multiCameraServer/build.gradle
Normal file
@@ -0,0 +1,62 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id 'cpp'
|
||||
id 'visual-studio'
|
||||
}
|
||||
|
||||
apply plugin: 'edu.wpi.first.NativeUtils'
|
||||
|
||||
apply from: "${rootDir}/shared/config.gradle"
|
||||
|
||||
ext {
|
||||
staticCvConfigs = [multiCameraServerCpp: []]
|
||||
useJava = true
|
||||
useCpp = true
|
||||
skipDev = true
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/shared/opencv.gradle"
|
||||
|
||||
mainClassName = 'Main'
|
||||
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.google.code.gson:gson:2.8.5'
|
||||
|
||||
compile project(':wpiutil')
|
||||
compile project(':ntcore')
|
||||
compile project(':cscore')
|
||||
compile project(':cameraserver')
|
||||
}
|
||||
|
||||
model {
|
||||
components {
|
||||
multiCameraServerCpp(NativeExecutableSpec) {
|
||||
targetBuildTypes 'release'
|
||||
sources {
|
||||
cpp {
|
||||
source {
|
||||
srcDirs = ['src/main/native/cpp']
|
||||
includes = ['**/*.cpp']
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs = ['src/main/native/include']
|
||||
includes = ['**/*.h']
|
||||
}
|
||||
}
|
||||
}
|
||||
binaries.all { binary ->
|
||||
lib project: ':cameraserver', library: 'cameraserver', linkage: 'static'
|
||||
lib project: ':ntcore', library: 'ntcore', linkage: 'static'
|
||||
lib project: ':cscore', library: 'cscore', linkage: 'static'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
211
cameraserver/multiCameraServer/src/main/java/Main.java
Normal file
211
cameraserver/multiCameraServer/src/main/java/Main.java
Normal file
@@ -0,0 +1,211 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import edu.wpi.cscore.VideoSource;
|
||||
import edu.wpi.first.cameraserver.CameraServer;
|
||||
import edu.wpi.first.networktables.NetworkTableInstance;
|
||||
|
||||
/*
|
||||
JSON format:
|
||||
{
|
||||
"team": <team number>,
|
||||
"ntmode": <"client" or "server", "client" if unspecified>
|
||||
"cameras": [
|
||||
{
|
||||
"name": <camera name>
|
||||
"path": <path, e.g. "/dev/video0">
|
||||
"pixel format": <"MJPEG", "YUYV", etc> // optional
|
||||
"width": <video mode width> // optional
|
||||
"height": <video mode height> // optional
|
||||
"fps": <video mode fps> // optional
|
||||
"brightness": <percentage brightness> // optional
|
||||
"white balance": <"auto", "hold", value> // optional
|
||||
"exposure": <"auto", "hold", value> // optional
|
||||
"properties": [ // optional
|
||||
{
|
||||
"name": <property name>
|
||||
"value": <property value>
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
|
||||
public final class Main {
|
||||
private static String configFile = "/boot/frc.json";
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
public static class CameraConfig {
|
||||
public String name;
|
||||
public String path;
|
||||
public JsonObject config;
|
||||
}
|
||||
|
||||
public static int team;
|
||||
public static boolean server;
|
||||
public static List<CameraConfig> cameras = new ArrayList<>();
|
||||
|
||||
private Main() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Report parse error.
|
||||
*/
|
||||
public static void parseError(String str) {
|
||||
System.err.println("config error in '" + configFile + "': " + str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read single camera configuration.
|
||||
*/
|
||||
public static boolean readCameraConfig(JsonObject config) {
|
||||
CameraConfig cam = new CameraConfig();
|
||||
|
||||
// name
|
||||
JsonElement nameElement = config.get("name");
|
||||
if (nameElement == null) {
|
||||
parseError("could not read camera name");
|
||||
return false;
|
||||
}
|
||||
cam.name = nameElement.getAsString();
|
||||
|
||||
// path
|
||||
JsonElement pathElement = config.get("path");
|
||||
if (pathElement == null) {
|
||||
parseError("camera '" + cam.name + "': could not read path");
|
||||
return false;
|
||||
}
|
||||
cam.path = pathElement.getAsString();
|
||||
|
||||
cam.config = config;
|
||||
|
||||
cameras.add(cam);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read configuration file.
|
||||
*/
|
||||
@SuppressWarnings("PMD.CyclomaticComplexity")
|
||||
public static boolean readConfig() {
|
||||
// parse file
|
||||
JsonElement top;
|
||||
try {
|
||||
top = new JsonParser().parse(Files.newBufferedReader(Paths.get(configFile)));
|
||||
} catch (IOException ex) {
|
||||
System.err.println("could not open '" + configFile + "': " + ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
// top level must be an object
|
||||
if (!top.isJsonObject()) {
|
||||
parseError("must be JSON object");
|
||||
return false;
|
||||
}
|
||||
JsonObject obj = top.getAsJsonObject();
|
||||
|
||||
// team number
|
||||
JsonElement teamElement = obj.get("team");
|
||||
if (teamElement == null) {
|
||||
parseError("could not read team number");
|
||||
return false;
|
||||
}
|
||||
team = teamElement.getAsInt();
|
||||
|
||||
// ntmode (optional)
|
||||
if (obj.has("ntmode")) {
|
||||
String str = obj.get("ntmode").getAsString();
|
||||
if ("client".equalsIgnoreCase(str)) {
|
||||
server = false;
|
||||
} else if ("server".equalsIgnoreCase(str)) {
|
||||
server = true;
|
||||
} else {
|
||||
parseError("could not understand ntmode value '" + str + "'");
|
||||
}
|
||||
}
|
||||
|
||||
// cameras
|
||||
JsonElement camerasElement = obj.get("cameras");
|
||||
if (camerasElement == null) {
|
||||
parseError("could not read cameras");
|
||||
return false;
|
||||
}
|
||||
JsonArray cameras = camerasElement.getAsJsonArray();
|
||||
for (JsonElement camera : cameras) {
|
||||
if (!readCameraConfig(camera.getAsJsonObject())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start running the camera.
|
||||
*/
|
||||
public static void startCamera(CameraConfig config) {
|
||||
System.out.println("Starting camera '" + config.name + "' on " + config.path);
|
||||
VideoSource camera = CameraServer.getInstance().startAutomaticCapture(
|
||||
config.name, config.path);
|
||||
|
||||
Gson gson = new GsonBuilder().create();
|
||||
|
||||
camera.setConfigJson(gson.toJson(config.config));
|
||||
}
|
||||
|
||||
/**
|
||||
* Main.
|
||||
*/
|
||||
public static void main(String... args) {
|
||||
if (args.length > 0) {
|
||||
configFile = args[0];
|
||||
}
|
||||
|
||||
// read configuration
|
||||
if (!readConfig()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// start NetworkTables
|
||||
NetworkTableInstance ntinst = NetworkTableInstance.getDefault();
|
||||
if (server) {
|
||||
System.out.println("Setting up NetworkTables server");
|
||||
ntinst.startServer();
|
||||
} else {
|
||||
System.out.println("Setting up NetworkTables client for team " + team);
|
||||
ntinst.startClientTeam(team);
|
||||
}
|
||||
|
||||
// start cameras
|
||||
for (CameraConfig camera : cameras) {
|
||||
startCamera(camera);
|
||||
}
|
||||
|
||||
// loop forever
|
||||
for (;;) {
|
||||
try {
|
||||
Thread.sleep(10000);
|
||||
} catch (InterruptedException ex) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
190
cameraserver/multiCameraServer/src/main/native/cpp/main.cpp
Normal file
190
cameraserver/multiCameraServer/src/main/native/cpp/main.cpp
Normal file
@@ -0,0 +1,190 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <wpi/StringRef.h>
|
||||
#include <wpi/json.h>
|
||||
#include <wpi/raw_istream.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
#include "cameraserver/CameraServer.h"
|
||||
|
||||
/*
|
||||
JSON format:
|
||||
{
|
||||
"team": <team number>,
|
||||
"ntmode": <"client" or "server", "client" if unspecified>
|
||||
"cameras": [
|
||||
{
|
||||
"name": <camera name>
|
||||
"path": <path, e.g. "/dev/video0">
|
||||
"pixel format": <"MJPEG", "YUYV", etc> // optional
|
||||
"width": <video mode width> // optional
|
||||
"height": <video mode height> // optional
|
||||
"fps": <video mode fps> // optional
|
||||
"brightness": <percentage brightness> // optional
|
||||
"white balance": <"auto", "hold", value> // optional
|
||||
"exposure": <"auto", "hold", value> // optional
|
||||
"properties": [ // optional
|
||||
{
|
||||
"name": <property name>
|
||||
"value": <property value>
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
|
||||
#ifdef __RASPBIAN__
|
||||
static const char* configFile = "/boot/frc.json";
|
||||
#else
|
||||
static const char* configFile = "frc.json";
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
unsigned int team;
|
||||
bool server = false;
|
||||
|
||||
struct CameraConfig {
|
||||
std::string name;
|
||||
std::string path;
|
||||
wpi::json config;
|
||||
};
|
||||
|
||||
std::vector<CameraConfig> cameras;
|
||||
|
||||
wpi::raw_ostream& ParseError() {
|
||||
return wpi::errs() << "config error in '" << configFile << "': ";
|
||||
}
|
||||
|
||||
bool ReadCameraConfig(const wpi::json& config) {
|
||||
CameraConfig c;
|
||||
|
||||
// name
|
||||
try {
|
||||
c.name = config.at("name").get<std::string>();
|
||||
} catch (const wpi::json::exception& e) {
|
||||
ParseError() << "could not read camera name: " << e.what() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// path
|
||||
try {
|
||||
c.path = config.at("path").get<std::string>();
|
||||
} catch (const wpi::json::exception& e) {
|
||||
ParseError() << "camera '" << c.name
|
||||
<< "': could not read path: " << e.what() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
c.config = config;
|
||||
|
||||
cameras.emplace_back(std::move(c));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadConfig() {
|
||||
// open config file
|
||||
std::error_code ec;
|
||||
wpi::raw_fd_istream is(configFile, ec);
|
||||
if (ec) {
|
||||
wpi::errs() << "could not open '" << configFile << "': " << ec.message()
|
||||
<< '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// parse file
|
||||
wpi::json j;
|
||||
try {
|
||||
j = wpi::json::parse(is);
|
||||
} catch (const wpi::json::parse_error& e) {
|
||||
ParseError() << "byte " << e.byte << ": " << e.what() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// top level must be an object
|
||||
if (!j.is_object()) {
|
||||
ParseError() << "must be JSON object\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// team number
|
||||
try {
|
||||
team = j.at("team").get<unsigned int>();
|
||||
} catch (const wpi::json::exception& e) {
|
||||
ParseError() << "could not read team number: " << e.what() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// ntmode (optional)
|
||||
if (j.count("ntmode") != 0) {
|
||||
try {
|
||||
auto str = j.at("ntmode").get<std::string>();
|
||||
wpi::StringRef s(str);
|
||||
if (s.equals_lower("client")) {
|
||||
server = false;
|
||||
} else if (s.equals_lower("server")) {
|
||||
server = true;
|
||||
} else {
|
||||
ParseError() << "could not understand ntmode value '" << str << "'\n";
|
||||
}
|
||||
} catch (const wpi::json::exception& e) {
|
||||
ParseError() << "could not read ntmode: " << e.what() << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
// cameras
|
||||
try {
|
||||
for (auto&& camera : j.at("cameras")) {
|
||||
if (!ReadCameraConfig(camera)) return false;
|
||||
}
|
||||
} catch (const wpi::json::exception& e) {
|
||||
ParseError() << "could not read cameras: " << e.what() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void StartCamera(const CameraConfig& config) {
|
||||
wpi::outs() << "Starting camera '" << config.name << "' on " << config.path
|
||||
<< '\n';
|
||||
auto camera = frc::CameraServer::GetInstance()->StartAutomaticCapture(
|
||||
config.name, config.path);
|
||||
|
||||
camera.SetConfigJson(config.config);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc >= 2) configFile = argv[1];
|
||||
|
||||
// read configuration
|
||||
if (!ReadConfig()) return EXIT_FAILURE;
|
||||
|
||||
// start NetworkTables
|
||||
auto ntinst = nt::NetworkTableInstance::GetDefault();
|
||||
if (server) {
|
||||
wpi::outs() << "Setting up NetworkTables server\n";
|
||||
ntinst.StartServer();
|
||||
} else {
|
||||
wpi::outs() << "Setting up NetworkTables client for team " << team << '\n';
|
||||
ntinst.StartClientTeam(team);
|
||||
}
|
||||
|
||||
// start cameras
|
||||
for (auto&& camera : cameras) StartCamera(camera);
|
||||
|
||||
// loop forever
|
||||
for (;;) std::this_thread::sleep_for(std::chrono::seconds(10));
|
||||
}
|
||||
@@ -65,6 +65,8 @@ public final class CameraServer {
|
||||
private final Map<String, VideoSource> m_sources;
|
||||
private final Map<String, VideoSink> m_sinks;
|
||||
private final Map<Integer, NetworkTable> m_tables; // indexed by source handle
|
||||
// source handle indexed by sink handle
|
||||
private final Map<Integer, Integer> m_fixedSources;
|
||||
private final NetworkTable m_publishTable;
|
||||
private final VideoListener m_videoListener; //NOPMD
|
||||
private final int m_tableListener; //NOPMD
|
||||
@@ -123,7 +125,7 @@ public final class CameraServer {
|
||||
}
|
||||
}
|
||||
|
||||
return values.toArray(String[]::new);
|
||||
return values.toArray(new String[0]);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"JavadocMethod", "PMD.AvoidUsingHardCodedIP"})
|
||||
@@ -157,14 +159,20 @@ public final class CameraServer {
|
||||
return values;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"JavadocMethod", "PMD.AvoidUsingHardCodedIP"})
|
||||
@SuppressWarnings({"JavadocMethod", "PMD.AvoidUsingHardCodedIP", "PMD.CyclomaticComplexity"})
|
||||
private synchronized void updateStreamValues() {
|
||||
// Over all the sinks...
|
||||
for (VideoSink i : m_sinks.values()) {
|
||||
int sink = i.getHandle();
|
||||
|
||||
// Get the source's subtable (if none exists, we're done)
|
||||
int source = CameraServerJNI.getSinkSource(sink);
|
||||
int source;
|
||||
Integer fixedSource = m_fixedSources.get(sink);
|
||||
if (fixedSource != null) {
|
||||
source = fixedSource;
|
||||
} else {
|
||||
source = CameraServerJNI.getSinkSource(sink);
|
||||
}
|
||||
if (source == 0) {
|
||||
continue;
|
||||
}
|
||||
@@ -295,6 +303,7 @@ public final class CameraServer {
|
||||
m_defaultUsbDevice = new AtomicInteger();
|
||||
m_sources = new Hashtable<>();
|
||||
m_sinks = new Hashtable<>();
|
||||
m_fixedSources = new Hashtable<>();
|
||||
m_tables = new Hashtable<>();
|
||||
m_publishTable = NetworkTableInstance.getDefault().getTable(kPublishName);
|
||||
m_nextPort = kBasePort;
|
||||
@@ -537,10 +546,11 @@ public final class CameraServer {
|
||||
*
|
||||
* @param camera Camera
|
||||
*/
|
||||
public void startAutomaticCapture(VideoSource camera) {
|
||||
public MjpegServer startAutomaticCapture(VideoSource camera) {
|
||||
addCamera(camera);
|
||||
VideoSink server = addServer("serve_" + camera.getName());
|
||||
MjpegServer server = addServer("serve_" + camera.getName());
|
||||
server.setSource(camera);
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -595,6 +605,21 @@ public final class CameraServer {
|
||||
return camera;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a virtual camera for switching between two streams. Unlike the
|
||||
* other addCamera methods, this returns a VideoSink rather than a
|
||||
* VideoSource. Calling setSource() on the returned object can be used
|
||||
* to switch the actual source of the stream.
|
||||
*/
|
||||
public MjpegServer addSwitchedCamera(String name) {
|
||||
// create a dummy CvSource
|
||||
CvSource source = new CvSource(name, VideoMode.PixelFormat.kMJPEG, 160, 120, 30);
|
||||
MjpegServer server = startAutomaticCapture(source);
|
||||
m_fixedSources.put(server.getHandle(), source.getHandle());
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OpenCV access to the primary camera feed. This allows you to
|
||||
* get images from the camera for image processing on the roboRIO.
|
||||
|
||||
@@ -33,10 +33,11 @@ struct CameraServer::Impl {
|
||||
void UpdateStreamValues();
|
||||
|
||||
wpi::mutex m_mutex;
|
||||
std::atomic<int> m_defaultUsbDevice;
|
||||
std::atomic<int> m_defaultUsbDevice{0};
|
||||
std::string m_primarySourceName;
|
||||
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;
|
||||
std::shared_ptr<nt::NetworkTable> m_publishTable;
|
||||
cs::VideoListener m_videoListener;
|
||||
@@ -156,7 +157,8 @@ void CameraServer::Impl::UpdateStreamValues() {
|
||||
CS_Sink sink = i.second.GetHandle();
|
||||
|
||||
// Get the source's subtable (if none exists, we're done)
|
||||
CS_Source source = cs::GetSinkSource(sink, &status);
|
||||
CS_Source source = m_fixedSources.lookup(sink);
|
||||
if (source == 0) source = cs::GetSinkSource(sink, &status);
|
||||
if (source == 0) continue;
|
||||
auto table = m_tables.lookup(source);
|
||||
if (table) {
|
||||
@@ -538,10 +540,21 @@ cs::AxisCamera CameraServer::AddAxisCamera(const wpi::Twine& name,
|
||||
return camera;
|
||||
}
|
||||
|
||||
void CameraServer::StartAutomaticCapture(const cs::VideoSource& camera) {
|
||||
cs::MjpegServer CameraServer::AddSwitchedCamera(const wpi::Twine& name) {
|
||||
// create a dummy CvSource
|
||||
cs::CvSource source{name, cs::VideoMode::PixelFormat::kMJPEG, 160, 120, 30};
|
||||
cs::MjpegServer server = StartAutomaticCapture(source);
|
||||
m_impl->m_fixedSources[server.GetHandle()] = source.GetHandle();
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
cs::MjpegServer CameraServer::StartAutomaticCapture(
|
||||
const cs::VideoSource& camera) {
|
||||
AddCamera(camera);
|
||||
auto server = AddServer(wpi::Twine("serve_") + camera.GetName());
|
||||
server.SetSource(camera);
|
||||
return server;
|
||||
}
|
||||
|
||||
cs::CvSink CameraServer::GetVideo() {
|
||||
|
||||
@@ -82,7 +82,7 @@ class CameraServer {
|
||||
*
|
||||
* @param camera Camera
|
||||
*/
|
||||
void StartAutomaticCapture(const cs::VideoSource& camera);
|
||||
cs::MjpegServer StartAutomaticCapture(const cs::VideoSource& camera);
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
@@ -173,6 +173,14 @@ class CameraServer {
|
||||
cs::AxisCamera AddAxisCamera(const wpi::Twine& name,
|
||||
std::initializer_list<T> hosts);
|
||||
|
||||
/**
|
||||
* Adds a virtual camera for switching between two streams. Unlike the
|
||||
* other addCamera methods, this returns a VideoSink rather than a
|
||||
* VideoSource. Calling SetSource() on the returned object can be used
|
||||
* to switch the actual source of the stream.
|
||||
*/
|
||||
cs::MjpegServer AddSwitchedCamera(const wpi::Twine& name);
|
||||
|
||||
/**
|
||||
* Get OpenCV access to the primary camera feed. This allows you to
|
||||
* get images from the camera for image processing on the roboRIO.
|
||||
|
||||
25
cmake/modules/GenResources.cmake
Normal file
25
cmake/modules/GenResources.cmake
Normal file
@@ -0,0 +1,25 @@
|
||||
MACRO(GENERATE_RESOURCES inputDir outputDir prefix namespace outputFiles)
|
||||
FILE(GLOB inputFiles ${inputDir}/*)
|
||||
SET(${outputFiles})
|
||||
FOREACH(input ${inputFiles})
|
||||
GET_FILENAME_COMPONENT(inputBase ${input} NAME)
|
||||
IF("${inputBase}" MATCHES "^\\.")
|
||||
CONTINUE()
|
||||
ENDIF()
|
||||
SET(output "${outputDir}/${inputBase}.cpp")
|
||||
LIST(APPEND ${outputFiles} "${output}")
|
||||
|
||||
ADD_CUSTOM_COMMAND(
|
||||
OUTPUT ${output}
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
"-Dinput=${input}"
|
||||
"-Doutput=${output}"
|
||||
"-Dprefix=${prefix}"
|
||||
"-Dnamespace=${namespace}"
|
||||
-P "${CMAKE_SOURCE_DIR}/cmake/scripts/GenResource.cmake"
|
||||
MAIN_DEPENDENCY ${input}
|
||||
DEPENDS ${CMAKE_SOURCE_DIR}/cmake/scripts/GenResource.cmake
|
||||
VERBATIM
|
||||
)
|
||||
ENDFOREACH()
|
||||
ENDMACRO()
|
||||
23
cmake/scripts/GenResource.cmake
Normal file
23
cmake/scripts/GenResource.cmake
Normal file
@@ -0,0 +1,23 @@
|
||||
# Parameters: input output prefix namespace
|
||||
FILE(READ ${input} fileHex HEX)
|
||||
STRING(LENGTH "${fileHex}" fileHexSize)
|
||||
MATH(EXPR fileSize "${fileHexSize} / 2")
|
||||
|
||||
GET_FILENAME_COMPONENT(inputBase ${input} NAME)
|
||||
STRING(REGEX REPLACE "[^a-zA-Z0-9]" "_" funcName "${inputBase}")
|
||||
SET(funcName "GetResource_${funcName}")
|
||||
|
||||
FILE(WRITE "${output}" "#include <stddef.h>\n#include <wpi/StringRef.h>\nextern \"C\" {\nstatic const unsigned char contents[] = {")
|
||||
|
||||
STRING(REGEX MATCHALL ".." outputData "${fileHex}")
|
||||
STRING(REGEX REPLACE ";" ", 0x" outputData "${outputData}")
|
||||
FILE(APPEND "${output}" " 0x${outputData} };\n")
|
||||
FILE(APPEND "${output}" "const unsigned char* ${prefix}${funcName}(size_t* len) {\n *len = ${fileSize};\n return contents;\n}\n}\n")
|
||||
|
||||
IF(NOT namespace STREQUAL "")
|
||||
FILE(APPEND "${output}" "namespace ${namespace} {\n")
|
||||
ENDIF()
|
||||
FILE(APPEND "${output}" "wpi::StringRef ${funcName}() {\n return wpi::StringRef(reinterpret_cast<const char*>(contents), ${fileSize});\n}\n")
|
||||
IF(NOT namespace STREQUAL "")
|
||||
FILE(APPEND "${output}" "}\n")
|
||||
ENDIF()
|
||||
@@ -19,7 +19,6 @@ if(NOT MSVC)
|
||||
else()
|
||||
target_sources(cscore PRIVATE ${cscore_linux_src})
|
||||
endif()
|
||||
target_compile_options(cscore PRIVATE -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE)
|
||||
else()
|
||||
target_sources(cscore PRIVATE ${cscore_windows_src})
|
||||
target_compile_options(cscore PUBLIC -DNOMINMAX)
|
||||
|
||||
@@ -29,6 +29,7 @@ ext {
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/main/native/include'
|
||||
include '**/*.h'
|
||||
}
|
||||
}
|
||||
cscoreMacCpp(CppSourceSet) {
|
||||
@@ -38,6 +39,7 @@ ext {
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/main/native/include', 'src/main/native/cpp'
|
||||
include '**/*.h'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,6 +52,7 @@ ext {
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/main/native/include', 'src/main/native/cpp'
|
||||
include '**/*.h'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,6 +65,7 @@ ext {
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/main/native/include', 'src/main/native/cpp'
|
||||
include '**/*.h'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,11 +98,31 @@ model {
|
||||
x86ExcludeSymbols = ['_CT??_R0?AV_System_error', '_CT??_R0?AVexception', '_CT??_R0?AVfailure',
|
||||
'_CT??_R0?AVbad_cast',
|
||||
'_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
|
||||
'_TI5?AVfailure']
|
||||
'_TI5?AVfailure', '==']
|
||||
x64ExcludeSymbols = ['_CT??_R0?AV_System_error', '_CT??_R0?AVexception', '_CT??_R0?AVfailure',
|
||||
'_CT??_R0?AVbad_cast',
|
||||
'_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
|
||||
'_TI5?AVfailure']
|
||||
'_TI5?AVfailure', '==']
|
||||
}
|
||||
cscoreJNI(ExportsConfig) {
|
||||
x86SymbolFilter = { symbols ->
|
||||
def retList = []
|
||||
symbols.each { symbol ->
|
||||
if (symbol.startsWith('CS_')) {
|
||||
retList << symbol
|
||||
}
|
||||
}
|
||||
return retList
|
||||
}
|
||||
x64SymbolFilter = { symbols ->
|
||||
def retList = []
|
||||
symbols.each { symbol ->
|
||||
if (symbol.startsWith('CS_')) {
|
||||
retList << symbol
|
||||
}
|
||||
}
|
||||
return retList
|
||||
}
|
||||
}
|
||||
}
|
||||
components {
|
||||
|
||||
@@ -17,6 +17,12 @@ int main() {
|
||||
for (const auto& caminfo : cs::EnumerateUsbCameras(&status)) {
|
||||
wpi::outs() << caminfo.dev << ": " << caminfo.path << " (" << caminfo.name
|
||||
<< ")\n";
|
||||
if (!caminfo.otherPaths.empty()) {
|
||||
wpi::outs() << "Other device paths:\n";
|
||||
for (auto&& path : caminfo.otherPaths)
|
||||
wpi::outs() << " " << path << '\n';
|
||||
}
|
||||
|
||||
cs::UsbCamera camera{"usbcam", caminfo.dev};
|
||||
|
||||
wpi::outs() << "Properties:\n";
|
||||
|
||||
@@ -89,6 +89,8 @@ public class CameraServerJNI {
|
||||
public static native boolean setSourcePixelFormat(int source, int pixelFormat);
|
||||
public static native boolean setSourceResolution(int source, int width, int height);
|
||||
public static native boolean setSourceFPS(int source, int fps);
|
||||
public static native boolean setSourceConfigJson(int source, String config);
|
||||
public static native String getSourceConfigJson(int source);
|
||||
public static native VideoMode[] enumerateSourceVideoModes(int source);
|
||||
public static native int[] enumerateSourceSinks(int source);
|
||||
public static native int copySource(int source);
|
||||
@@ -110,6 +112,7 @@ public class CameraServerJNI {
|
||||
// UsbCamera Source Functions
|
||||
//
|
||||
public static native String getUsbCameraPath(int source);
|
||||
public static native UsbCameraInfo getUsbCameraInfo(int source);
|
||||
|
||||
//
|
||||
// HttpCamera Source Functions
|
||||
@@ -144,6 +147,8 @@ public class CameraServerJNI {
|
||||
public static native String getSinkDescription(int sink);
|
||||
public static native int getSinkProperty(int sink, String name);
|
||||
public static native int[] enumerateSinkProperties(int sink);
|
||||
public static native boolean setSinkConfigJson(int sink, String config);
|
||||
public static native String getSinkConfigJson(int sink);
|
||||
public static native void setSinkSource(int sink, int source);
|
||||
public static native int getSinkSourceProperty(int sink, String name);
|
||||
public static native int getSinkSource(int sink);
|
||||
|
||||
@@ -60,7 +60,7 @@ public class MjpegServer extends VideoSink {
|
||||
* @param width width, 0 for unspecified
|
||||
* @param height height, 0 for unspecified
|
||||
*/
|
||||
void setResolution(int width, int height) {
|
||||
public void setResolution(int width, int height) {
|
||||
CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "width"), width);
|
||||
CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "height"), height);
|
||||
}
|
||||
@@ -72,7 +72,7 @@ public class MjpegServer extends VideoSink {
|
||||
*
|
||||
* @param fps FPS, 0 for unspecified
|
||||
*/
|
||||
void setFPS(int fps) {
|
||||
public void setFPS(int fps) {
|
||||
CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "fps"), fps);
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ public class MjpegServer extends VideoSink {
|
||||
*
|
||||
* @param quality JPEG compression quality (0-100), -1 for unspecified
|
||||
*/
|
||||
void setCompression(int quality) {
|
||||
public void setCompression(int quality) {
|
||||
CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "compression"),
|
||||
quality);
|
||||
}
|
||||
@@ -97,7 +97,7 @@ public class MjpegServer extends VideoSink {
|
||||
*
|
||||
* @param quality JPEG compression quality (0-100)
|
||||
*/
|
||||
void setDefaultCompression(int quality) {
|
||||
public void setDefaultCompression(int quality) {
|
||||
CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "default_compression"),
|
||||
quality);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,13 @@ public class UsbCamera extends VideoCamera {
|
||||
return CameraServerJNI.getUsbCameraPath(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full camera information for the device.
|
||||
*/
|
||||
public UsbCameraInfo getInfo() {
|
||||
return CameraServerJNI.getUsbCameraInfo(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set how verbose the camera connection messages are.
|
||||
*
|
||||
|
||||
@@ -17,11 +17,14 @@ public class UsbCameraInfo {
|
||||
* @param dev Device number (e.g. N in '/dev/videoN' on Linux)
|
||||
* @param path Path to device if available (e.g. '/dev/video0' on Linux)
|
||||
* @param name Vendor/model name of the camera as provided by the USB driver
|
||||
* @param otherPaths Other path aliases to device
|
||||
*/
|
||||
public UsbCameraInfo(int dev, String path, String name) {
|
||||
@SuppressWarnings("PMD.ArrayIsStoredDirectly")
|
||||
public UsbCameraInfo(int dev, String path, String name, String[] otherPaths) {
|
||||
this.dev = dev;
|
||||
this.path = path;
|
||||
this.name = name;
|
||||
this.otherPaths = otherPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,4 +44,10 @@ public class UsbCameraInfo {
|
||||
*/
|
||||
@SuppressWarnings("MemberName")
|
||||
public String name;
|
||||
|
||||
/**
|
||||
* Other path aliases to device (e.g. '/dev/v4l/by-id/...' etc on Linux).
|
||||
*/
|
||||
@SuppressWarnings("MemberName")
|
||||
public String[] otherPaths;
|
||||
}
|
||||
|
||||
@@ -133,6 +133,38 @@ public class VideoSink implements AutoCloseable {
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set properties from a JSON configuration string.
|
||||
*
|
||||
* <p>The format of the JSON input is:
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* "properties": [
|
||||
* {
|
||||
* "name": property name
|
||||
* "value": property value
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param config configuration
|
||||
* @return True if set successfully
|
||||
*/
|
||||
public boolean setConfigJson(String config) {
|
||||
return CameraServerJNI.setSinkConfigJson(m_handle, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JSON configuration string.
|
||||
*
|
||||
* @return JSON configuration string
|
||||
*/
|
||||
public String getConfigJson() {
|
||||
return CameraServerJNI.getSinkConfigJson(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure which source should provide frames to this sink. Each sink
|
||||
* can accept frames from only a single source, but a single source can
|
||||
|
||||
@@ -270,6 +270,45 @@ public class VideoSource implements AutoCloseable {
|
||||
return CameraServerJNI.setSourceFPS(m_handle, fps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set video mode and properties from a JSON configuration string.
|
||||
*
|
||||
* <p>The format of the JSON input is:
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* "pixel format": "MJPEG", "YUYV", etc
|
||||
* "width": video mode width
|
||||
* "height": video mode height
|
||||
* "fps": video mode fps
|
||||
* "brightness": percentage brightness
|
||||
* "white balance": "auto", "hold", or value
|
||||
* "exposure": "auto", "hold", or value
|
||||
* "properties": [
|
||||
* {
|
||||
* "name": property name
|
||||
* "value": property value
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param config configuration
|
||||
* @return True if set successfully
|
||||
*/
|
||||
public boolean setConfigJson(String config) {
|
||||
return CameraServerJNI.setSourceConfigJson(m_handle, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JSON configuration string.
|
||||
*
|
||||
* @return JSON configuration string
|
||||
*/
|
||||
public String getConfigJson() {
|
||||
return CameraServerJNI.getSourceConfigJson(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actual FPS.
|
||||
*
|
||||
|
||||
@@ -30,6 +30,12 @@ HttpCameraImpl::HttpCameraImpl(const wpi::Twine& name, CS_HttpCameraKind kind,
|
||||
HttpCameraImpl::~HttpCameraImpl() {
|
||||
m_active = false;
|
||||
|
||||
// force wakeup of monitor thread
|
||||
m_monitorCond.notify_one();
|
||||
|
||||
// join monitor thread
|
||||
if (m_monitorThread.joinable()) m_monitorThread.join();
|
||||
|
||||
// Close file if it's open
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
@@ -54,6 +60,31 @@ void HttpCameraImpl::Start() {
|
||||
// Kick off the stream and settings threads
|
||||
m_streamThread = std::thread(&HttpCameraImpl::StreamThreadMain, this);
|
||||
m_settingsThread = std::thread(&HttpCameraImpl::SettingsThreadMain, this);
|
||||
m_monitorThread = std::thread(&HttpCameraImpl::MonitorThreadMain, this);
|
||||
}
|
||||
|
||||
void HttpCameraImpl::MonitorThreadMain() {
|
||||
while (m_active) {
|
||||
std::unique_lock<wpi::mutex> lock(m_mutex);
|
||||
// sleep for 1 second between checks
|
||||
m_monitorCond.wait_for(lock, std::chrono::seconds(1),
|
||||
[=] { return !m_active; });
|
||||
|
||||
if (!m_active) break;
|
||||
|
||||
// check to see if we got any frames, and close the stream if not
|
||||
// (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");
|
||||
m_streamConn->stream->close();
|
||||
}
|
||||
|
||||
// reset the frame counter
|
||||
m_frameCount = 0;
|
||||
}
|
||||
|
||||
SDEBUG("Monitor Thread exiting");
|
||||
}
|
||||
|
||||
void HttpCameraImpl::StreamThreadMain() {
|
||||
@@ -86,6 +117,10 @@ void HttpCameraImpl::StreamThreadMain() {
|
||||
|
||||
// stream
|
||||
DeviceStream(conn->is, boundary);
|
||||
{
|
||||
std::unique_lock<wpi::mutex> lock(m_mutex);
|
||||
m_streamConn = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
SDEBUG("Camera Thread exiting");
|
||||
@@ -120,6 +155,7 @@ wpi::HttpConnection* HttpCameraImpl::DeviceStreamConnect(
|
||||
// update m_streamConn
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_frameCount = 1; // avoid a race with monitor thread
|
||||
m_streamConn = std::move(connPtr);
|
||||
}
|
||||
|
||||
@@ -229,6 +265,7 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
|
||||
}
|
||||
PutFrame(VideoMode::PixelFormat::kMJPEG, width, height, imageBuf,
|
||||
wpi::Now());
|
||||
++m_frameCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -246,6 +283,7 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
|
||||
image->width = width;
|
||||
image->height = height;
|
||||
PutFrame(std::move(image), wpi::Now());
|
||||
++m_frameCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -111,10 +111,14 @@ class HttpCameraImpl : public SourceImpl {
|
||||
void SettingsThreadMain();
|
||||
void DeviceSendSettings(wpi::HttpRequest& req);
|
||||
|
||||
// The monitor thread
|
||||
void MonitorThreadMain();
|
||||
|
||||
std::atomic_bool m_connected{false};
|
||||
std::atomic_bool m_active{true}; // set to false to terminate thread
|
||||
std::thread m_streamThread;
|
||||
std::thread m_settingsThread;
|
||||
std::thread m_monitorThread;
|
||||
|
||||
//
|
||||
// Variables protected by m_mutex
|
||||
@@ -130,6 +134,8 @@ class HttpCameraImpl : public SourceImpl {
|
||||
size_t m_nextLocation{0};
|
||||
int m_prefLocation{-1}; // preferred location
|
||||
|
||||
std::atomic_int m_frameCount{0};
|
||||
|
||||
wpi::condition_variable m_sinkEnabledCond;
|
||||
|
||||
wpi::StringMap<wpi::SmallString<16>> m_settings;
|
||||
@@ -137,6 +143,8 @@ class HttpCameraImpl : public SourceImpl {
|
||||
|
||||
wpi::StringMap<wpi::SmallString<16>> m_streamSettings;
|
||||
std::atomic_bool m_streamSettingsUpdated{false};
|
||||
|
||||
wpi::condition_variable m_monitorCond;
|
||||
};
|
||||
|
||||
class AxisCameraImpl : public HttpCameraImpl {
|
||||
|
||||
@@ -67,7 +67,8 @@ static const char* startRootPage =
|
||||
"</head><body>\n"
|
||||
"<div class=\"stream\">\n"
|
||||
"<img src=\"/stream.mjpg\" /><p />\n"
|
||||
"<a href=\"/settings.json\">Settings JSON</a>\n"
|
||||
"<a href=\"/settings.json\">Settings JSON</a> |\n"
|
||||
"<a href=\"/config.json\">Source Config JSON</a>\n"
|
||||
"</div>\n"
|
||||
"<div class=\"settings\">\n";
|
||||
static const char* endRootPage = "</div></body></html>";
|
||||
@@ -110,13 +111,13 @@ class MjpegServerImpl::ConnThread : public wpi::SafeThread {
|
||||
|
||||
void StartStream() {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_source->EnableSink();
|
||||
if (m_source) m_source->EnableSink();
|
||||
m_streaming = true;
|
||||
}
|
||||
|
||||
void StopStream() {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_source->DisableSink();
|
||||
if (m_source) m_source->DisableSink();
|
||||
m_streaming = false;
|
||||
}
|
||||
};
|
||||
@@ -334,13 +335,14 @@ bool MjpegServerImpl::ConnThread::ProcessCommand(wpi::raw_ostream& os,
|
||||
|
||||
void MjpegServerImpl::ConnThread::SendHTMLHeadTitle(
|
||||
wpi::raw_ostream& os) const {
|
||||
os << "<html><head><title>" << m_name << " CameraServer</title>";
|
||||
os << "<html><head><title>" << m_name << " CameraServer</title>"
|
||||
<< "<meta charset=\"UTF-8\">";
|
||||
}
|
||||
|
||||
// Send the root html file with controls for all the settable properties.
|
||||
void MjpegServerImpl::ConnThread::SendHTML(wpi::raw_ostream& os,
|
||||
SourceImpl& source, bool header) {
|
||||
if (header) SendHeader(os, 200, "OK", "application/x-javascript");
|
||||
if (header) SendHeader(os, 200, "OK", "text/html");
|
||||
|
||||
SendHTMLHeadTitle(os);
|
||||
os << startRootPage;
|
||||
@@ -412,6 +414,15 @@ void MjpegServerImpl::ConnThread::SendHTML(wpi::raw_ostream& os,
|
||||
}
|
||||
}
|
||||
|
||||
status = 0;
|
||||
auto info = GetUsbCameraInfo(Instance::GetInstance().FindSource(source).first,
|
||||
&status);
|
||||
if (status == CS_OK) {
|
||||
os << "<p>USB device path: " << info.path << '\n';
|
||||
for (auto&& path : info.otherPaths)
|
||||
os << "<p>Alternate device path: " << path << '\n';
|
||||
}
|
||||
|
||||
os << "<p>Supported Video Modes:</p>\n";
|
||||
os << "<table cols=\"4\" style=\"border: 1px solid black\">\n";
|
||||
os << "<tr><th>Pixel Format</th>"
|
||||
@@ -453,7 +464,7 @@ void MjpegServerImpl::ConnThread::SendHTML(wpi::raw_ostream& os,
|
||||
// Send a JSON file which is contains information about the source parameters.
|
||||
void MjpegServerImpl::ConnThread::SendJSON(wpi::raw_ostream& os,
|
||||
SourceImpl& source, bool header) {
|
||||
if (header) SendHeader(os, 200, "OK", "application/x-javascript");
|
||||
if (header) SendHeader(os, 200, "OK", "application/json");
|
||||
|
||||
os << "{\n\"controls\": [\n";
|
||||
wpi::SmallVector<int, 32> properties_vec;
|
||||
@@ -632,8 +643,9 @@ void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
|
||||
Frame::Time lastFrameTime = 0;
|
||||
Frame::Time timePerFrame = 0;
|
||||
if (m_fps != 0) timePerFrame = 1000000.0 / m_fps;
|
||||
// Allow fudge factor of 1 ms in frame rate
|
||||
if (timePerFrame >= 1000) timePerFrame -= 1000;
|
||||
Frame::Time averageFrameTime = 0;
|
||||
Frame::Time averagePeriod = 1000000; // 1 second window
|
||||
if (averagePeriod < timePerFrame) averagePeriod = timePerFrame * 10;
|
||||
|
||||
StartStream();
|
||||
while (m_active && !os.has_error()) {
|
||||
@@ -654,10 +666,26 @@ void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (frame.GetTime() < (lastFrameTime + timePerFrame)) {
|
||||
// Limit FPS; sleep for 10 ms so we don't consume all processor time
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
continue;
|
||||
auto thisFrameTime = frame.GetTime();
|
||||
if (thisFrameTime != 0 && timePerFrame != 0 && lastFrameTime != 0) {
|
||||
Frame::Time deltaTime = thisFrameTime - lastFrameTime;
|
||||
|
||||
// drop frame if it is early compared to the desired frame rate AND
|
||||
// the current average is higher than the desired average
|
||||
if (deltaTime < timePerFrame && averageFrameTime < timePerFrame) {
|
||||
// sleep for 1 ms so we don't consume all processor time
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
continue;
|
||||
}
|
||||
|
||||
// update average
|
||||
if (averageFrameTime != 0) {
|
||||
averageFrameTime =
|
||||
averageFrameTime * (averagePeriod - timePerFrame) / averagePeriod +
|
||||
deltaTime * timePerFrame / averagePeriod;
|
||||
} else {
|
||||
averageFrameTime = deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
int width = m_width != 0 ? m_width : frame.GetOriginalWidth();
|
||||
@@ -694,7 +722,7 @@ void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
|
||||
// print the individual mimetype and the length
|
||||
// sending the content-length fixes random stream disruption observed
|
||||
// with firefox
|
||||
lastFrameTime = frame.GetTime();
|
||||
lastFrameTime = thisFrameTime;
|
||||
double timestamp = lastFrameTime / 1000000.0;
|
||||
header.clear();
|
||||
oss << "\r\n--" BOUNDARY "\r\n"
|
||||
@@ -728,7 +756,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
return;
|
||||
}
|
||||
|
||||
enum { kCommand, kStream, kGetSettings, kRootPage } kind;
|
||||
enum { kCommand, kStream, kGetSettings, kGetSourceConfig, kRootPage } kind;
|
||||
wpi::StringRef parameters;
|
||||
size_t pos;
|
||||
|
||||
@@ -748,6 +776,9 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
} else if (req.find("GET /settings") != wpi::StringRef::npos &&
|
||||
req.find(".json") != wpi::StringRef::npos) {
|
||||
kind = kGetSettings;
|
||||
} else if (req.find("GET /config") != wpi::StringRef::npos &&
|
||||
req.find(".json") != wpi::StringRef::npos) {
|
||||
kind = kGetSourceConfig;
|
||||
} else if (req.find("GET /input") != wpi::StringRef::npos &&
|
||||
req.find(".json") != wpi::StringRef::npos) {
|
||||
kind = kGetSettings;
|
||||
@@ -806,6 +837,17 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
else
|
||||
SendError(os, 404, "Resource not found");
|
||||
break;
|
||||
case kGetSourceConfig:
|
||||
SDEBUG("request for JSON file");
|
||||
if (auto source = GetSource()) {
|
||||
SendHeader(os, 200, "OK", "application/json");
|
||||
CS_Status status = CS_OK;
|
||||
os << source->GetConfigJson(&status);
|
||||
os.flush();
|
||||
} else {
|
||||
SendError(os, 404, "Resource not found");
|
||||
}
|
||||
break;
|
||||
case kRootPage:
|
||||
SDEBUG("request for root page");
|
||||
SendHeader(os, 200, "OK", "text/html");
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
|
||||
#include "PropertyContainer.h"
|
||||
|
||||
#include <wpi/Logger.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/json.h>
|
||||
|
||||
using namespace cs;
|
||||
|
||||
int PropertyContainer::GetPropertyIndex(const wpi::Twine& name) const {
|
||||
@@ -204,3 +209,74 @@ bool PropertyContainer::CacheProperties(CS_Status* status) const {
|
||||
m_properties_cached = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PropertyContainer::SetPropertiesJson(const wpi::json& config,
|
||||
wpi::Logger& logger,
|
||||
wpi::StringRef logName,
|
||||
CS_Status* status) {
|
||||
for (auto&& prop : config) {
|
||||
std::string name;
|
||||
try {
|
||||
name = prop.at("name").get<std::string>();
|
||||
} catch (const wpi::json::exception& e) {
|
||||
WPI_WARNING(logger,
|
||||
logName << ": SetConfigJson: could not read property name: "
|
||||
<< e.what());
|
||||
continue;
|
||||
}
|
||||
int n = GetPropertyIndex(name);
|
||||
try {
|
||||
auto& v = prop.at("value");
|
||||
if (v.is_string()) {
|
||||
std::string val = v.get<std::string>();
|
||||
WPI_INFO(logger, logName << ": SetConfigJson: setting property '"
|
||||
<< name << "' to '" << val << '\'');
|
||||
SetStringProperty(n, val, status);
|
||||
} else if (v.is_boolean()) {
|
||||
bool val = v.get<bool>();
|
||||
WPI_INFO(logger, logName << ": SetConfigJson: setting property '"
|
||||
<< name << "' to " << val);
|
||||
SetProperty(n, val, status);
|
||||
} else {
|
||||
int val = v.get<int>();
|
||||
WPI_INFO(logger, logName << ": SetConfigJson: setting property '"
|
||||
<< name << "' to " << val);
|
||||
SetProperty(n, val, status);
|
||||
}
|
||||
} catch (const wpi::json::exception& e) {
|
||||
WPI_WARNING(logger,
|
||||
logName << ": SetConfigJson: could not read property value: "
|
||||
<< e.what());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
wpi::json PropertyContainer::GetPropertiesJsonObject(CS_Status* status) {
|
||||
wpi::json j;
|
||||
wpi::SmallVector<int, 32> propVec;
|
||||
for (int p : EnumerateProperties(propVec, status)) {
|
||||
wpi::json prop;
|
||||
wpi::SmallString<128> strBuf;
|
||||
prop.emplace("name", GetPropertyName(p, strBuf, status));
|
||||
switch (GetPropertyKind(p)) {
|
||||
case CS_PROP_BOOLEAN:
|
||||
prop.emplace("value", static_cast<bool>(GetProperty(p, status)));
|
||||
break;
|
||||
case CS_PROP_INTEGER:
|
||||
case CS_PROP_ENUM:
|
||||
prop.emplace("value", GetProperty(p, status));
|
||||
break;
|
||||
case CS_PROP_STRING:
|
||||
prop.emplace("value", GetStringProperty(p, strBuf, status));
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
j.emplace_back(prop);
|
||||
}
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,11 @@
|
||||
#include "PropertyImpl.h"
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
namespace wpi {
|
||||
class Logger;
|
||||
class json;
|
||||
} // namespace wpi
|
||||
|
||||
namespace cs {
|
||||
|
||||
class PropertyContainer {
|
||||
@@ -50,6 +55,10 @@ class PropertyContainer {
|
||||
std::vector<std::string> GetEnumPropertyChoices(int property,
|
||||
CS_Status* status) const;
|
||||
|
||||
bool SetPropertiesJson(const wpi::json& config, wpi::Logger& logger,
|
||||
wpi::StringRef logName, CS_Status* status);
|
||||
wpi::json GetPropertiesJsonObject(CS_Status* status);
|
||||
|
||||
protected:
|
||||
// Get a property; must be called with m_mutex held.
|
||||
PropertyImpl* GetProperty(int property) {
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
#include "SinkImpl.h"
|
||||
|
||||
#include <wpi/json.h>
|
||||
|
||||
#include "Instance.h"
|
||||
#include "Notifier.h"
|
||||
#include "SourceImpl.h"
|
||||
@@ -102,6 +104,43 @@ wpi::StringRef SinkImpl::GetError(wpi::SmallVectorImpl<char>& buf) const {
|
||||
return wpi::StringRef{buf.data(), buf.size()};
|
||||
}
|
||||
|
||||
bool SinkImpl::SetConfigJson(wpi::StringRef config, CS_Status* status) {
|
||||
wpi::json j;
|
||||
try {
|
||||
j = wpi::json::parse(config);
|
||||
} catch (const wpi::json::parse_error& e) {
|
||||
SWARNING("SetConfigJson: parse error at byte " << e.byte << ": "
|
||||
<< e.what());
|
||||
*status = CS_PROPERTY_WRITE_FAILED;
|
||||
return false;
|
||||
}
|
||||
return SetConfigJson(j, status);
|
||||
}
|
||||
|
||||
bool SinkImpl::SetConfigJson(const wpi::json& config, CS_Status* status) {
|
||||
if (config.count("properties") != 0)
|
||||
SetPropertiesJson(config.at("properties"), m_logger, GetName(), status);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string SinkImpl::GetConfigJson(CS_Status* status) {
|
||||
std::string rv;
|
||||
wpi::raw_string_ostream os(rv);
|
||||
GetConfigJsonObject(status).dump(os, 4);
|
||||
os.flush();
|
||||
return rv;
|
||||
}
|
||||
|
||||
wpi::json SinkImpl::GetConfigJsonObject(CS_Status* status) {
|
||||
wpi::json j;
|
||||
|
||||
wpi::json props = GetPropertiesJsonObject(status);
|
||||
if (props.is_array()) j.emplace("properties", props);
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
void SinkImpl::NotifyPropertyCreated(int propIndex, PropertyImpl& prop) {
|
||||
m_notifier.NotifySinkProperty(*this, CS_SINK_PROPERTY_CREATED, prop.name,
|
||||
propIndex, prop.propKind, prop.value,
|
||||
|
||||
@@ -18,6 +18,10 @@
|
||||
|
||||
#include "SourceImpl.h"
|
||||
|
||||
namespace wpi {
|
||||
class json;
|
||||
} // namespace wpi
|
||||
|
||||
namespace cs {
|
||||
|
||||
class Frame;
|
||||
@@ -51,6 +55,11 @@ class SinkImpl : public PropertyContainer {
|
||||
std::string GetError() const;
|
||||
wpi::StringRef GetError(wpi::SmallVectorImpl<char>& buf) const;
|
||||
|
||||
bool SetConfigJson(wpi::StringRef config, CS_Status* status);
|
||||
virtual bool SetConfigJson(const wpi::json& config, CS_Status* status);
|
||||
std::string GetConfigJson(CS_Status* status);
|
||||
virtual wpi::json GetConfigJsonObject(CS_Status* status);
|
||||
|
||||
protected:
|
||||
// PropertyContainer implementation
|
||||
void NotifyPropertyCreated(int propIndex, PropertyImpl& prop) override;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <cstring>
|
||||
|
||||
#include <wpi/STLExtras.h>
|
||||
#include <wpi/json.h>
|
||||
#include <wpi/timestamp.h>
|
||||
|
||||
#include "Log.h"
|
||||
@@ -161,6 +162,221 @@ bool SourceImpl::SetFPS(int fps, CS_Status* status) {
|
||||
return SetVideoMode(mode, status);
|
||||
}
|
||||
|
||||
bool SourceImpl::SetConfigJson(wpi::StringRef config, CS_Status* status) {
|
||||
wpi::json j;
|
||||
try {
|
||||
j = wpi::json::parse(config);
|
||||
} catch (const wpi::json::parse_error& e) {
|
||||
SWARNING("SetConfigJson: parse error at byte " << e.byte << ": "
|
||||
<< e.what());
|
||||
*status = CS_PROPERTY_WRITE_FAILED;
|
||||
return false;
|
||||
}
|
||||
return SetConfigJson(j, status);
|
||||
}
|
||||
|
||||
bool SourceImpl::SetConfigJson(const wpi::json& config, CS_Status* status) {
|
||||
VideoMode mode;
|
||||
|
||||
// pixel format
|
||||
if (config.count("pixel format") != 0) {
|
||||
try {
|
||||
auto str = config.at("pixel format").get<std::string>();
|
||||
wpi::StringRef s(str);
|
||||
if (s.equals_lower("mjpeg")) {
|
||||
mode.pixelFormat = cs::VideoMode::kMJPEG;
|
||||
} else if (s.equals_lower("yuyv")) {
|
||||
mode.pixelFormat = cs::VideoMode::kYUYV;
|
||||
} else if (s.equals_lower("rgb565")) {
|
||||
mode.pixelFormat = cs::VideoMode::kRGB565;
|
||||
} else if (s.equals_lower("bgr")) {
|
||||
mode.pixelFormat = cs::VideoMode::kBGR;
|
||||
} else if (s.equals_lower("gray")) {
|
||||
mode.pixelFormat = cs::VideoMode::kGray;
|
||||
} else {
|
||||
SWARNING("SetConfigJson: could not understand pixel format value '"
|
||||
<< str << '\'');
|
||||
}
|
||||
} catch (const wpi::json::exception& e) {
|
||||
SWARNING("SetConfigJson: could not read pixel format: " << e.what());
|
||||
}
|
||||
}
|
||||
|
||||
// width
|
||||
if (config.count("width") != 0) {
|
||||
try {
|
||||
mode.width = config.at("width").get<unsigned int>();
|
||||
} catch (const wpi::json::exception& e) {
|
||||
SWARNING("SetConfigJson: could not read width: " << e.what());
|
||||
}
|
||||
}
|
||||
|
||||
// height
|
||||
if (config.count("height") != 0) {
|
||||
try {
|
||||
mode.height = config.at("height").get<unsigned int>();
|
||||
} catch (const wpi::json::exception& e) {
|
||||
SWARNING("SetConfigJson: could not read height: " << e.what());
|
||||
}
|
||||
}
|
||||
|
||||
// fps
|
||||
if (config.count("fps") != 0) {
|
||||
try {
|
||||
mode.fps = config.at("fps").get<unsigned int>();
|
||||
} catch (const wpi::json::exception& e) {
|
||||
SWARNING("SetConfigJson: could not read fps: " << e.what());
|
||||
}
|
||||
}
|
||||
|
||||
// if all of video mode is set, use SetVideoMode, otherwise piecemeal it
|
||||
if (mode.pixelFormat != VideoMode::kUnknown && mode.width != 0 &&
|
||||
mode.height != 0 && mode.fps != 0) {
|
||||
SINFO("SetConfigJson: setting video mode to pixelFormat "
|
||||
<< mode.pixelFormat << ", width " << mode.width << ", height "
|
||||
<< mode.height << ", fps " << mode.fps);
|
||||
SetVideoMode(mode, status);
|
||||
} else {
|
||||
if (mode.pixelFormat != cs::VideoMode::kUnknown) {
|
||||
SINFO("SetConfigJson: setting pixelFormat " << mode.pixelFormat);
|
||||
SetPixelFormat(static_cast<cs::VideoMode::PixelFormat>(mode.pixelFormat),
|
||||
status);
|
||||
}
|
||||
if (mode.width != 0 && mode.height != 0) {
|
||||
SINFO("SetConfigJson: setting width " << mode.width << ", height "
|
||||
<< mode.height);
|
||||
SetResolution(mode.width, mode.height, status);
|
||||
}
|
||||
if (mode.fps != 0) {
|
||||
SINFO("SetConfigJson: setting fps " << mode.fps);
|
||||
SetFPS(mode.fps, status);
|
||||
}
|
||||
}
|
||||
|
||||
// brightness
|
||||
if (config.count("brightness") != 0) {
|
||||
try {
|
||||
int val = config.at("brightness").get<int>();
|
||||
SINFO("SetConfigJson: setting brightness to " << val);
|
||||
SetBrightness(val, status);
|
||||
} catch (const wpi::json::exception& e) {
|
||||
SWARNING("SetConfigJson: could not read brightness: " << e.what());
|
||||
}
|
||||
}
|
||||
|
||||
// white balance
|
||||
if (config.count("white balance") != 0) {
|
||||
try {
|
||||
auto& setting = config.at("white balance");
|
||||
if (setting.is_string()) {
|
||||
auto str = setting.get<std::string>();
|
||||
wpi::StringRef s(str);
|
||||
if (s.equals_lower("auto")) {
|
||||
SINFO("SetConfigJson: setting white balance to auto");
|
||||
SetWhiteBalanceAuto(status);
|
||||
} else if (s.equals_lower("hold")) {
|
||||
SINFO("SetConfigJson: setting white balance to hold current");
|
||||
SetWhiteBalanceHoldCurrent(status);
|
||||
} else {
|
||||
SWARNING("SetConfigJson: could not understand white balance value '"
|
||||
<< str << '\'');
|
||||
}
|
||||
} else {
|
||||
int val = setting.get<int>();
|
||||
SINFO("SetConfigJson: setting white balance to " << val);
|
||||
SetWhiteBalanceManual(val, status);
|
||||
}
|
||||
} catch (const wpi::json::exception& e) {
|
||||
SWARNING("SetConfigJson: could not read white balance: " << e.what());
|
||||
}
|
||||
}
|
||||
|
||||
// exposure
|
||||
if (config.count("exposure") != 0) {
|
||||
try {
|
||||
auto& setting = config.at("exposure");
|
||||
if (setting.is_string()) {
|
||||
auto str = setting.get<std::string>();
|
||||
wpi::StringRef s(str);
|
||||
if (s.equals_lower("auto")) {
|
||||
SINFO("SetConfigJson: setting exposure to auto");
|
||||
SetExposureAuto(status);
|
||||
} else if (s.equals_lower("hold")) {
|
||||
SINFO("SetConfigJson: setting exposure to hold current");
|
||||
SetExposureHoldCurrent(status);
|
||||
} else {
|
||||
SWARNING("SetConfigJson: could not understand exposure value '"
|
||||
<< str << '\'');
|
||||
}
|
||||
} else {
|
||||
int val = setting.get<int>();
|
||||
SINFO("SetConfigJson: setting exposure to " << val);
|
||||
SetExposureManual(val, status);
|
||||
}
|
||||
} catch (const wpi::json::exception& e) {
|
||||
SWARNING("SetConfigJson: could not read exposure: " << e.what());
|
||||
}
|
||||
}
|
||||
|
||||
// properties
|
||||
if (config.count("properties") != 0)
|
||||
SetPropertiesJson(config.at("properties"), m_logger, GetName(), status);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string SourceImpl::GetConfigJson(CS_Status* status) {
|
||||
std::string rv;
|
||||
wpi::raw_string_ostream os(rv);
|
||||
GetConfigJsonObject(status).dump(os, 4);
|
||||
os.flush();
|
||||
return rv;
|
||||
}
|
||||
|
||||
wpi::json SourceImpl::GetConfigJsonObject(CS_Status* status) {
|
||||
wpi::json j;
|
||||
|
||||
// pixel format
|
||||
wpi::StringRef pixelFormat;
|
||||
switch (m_mode.pixelFormat) {
|
||||
case VideoMode::kMJPEG:
|
||||
pixelFormat = "mjpeg";
|
||||
break;
|
||||
case VideoMode::kYUYV:
|
||||
pixelFormat = "yuyv";
|
||||
break;
|
||||
case VideoMode::kRGB565:
|
||||
pixelFormat = "rgb565";
|
||||
break;
|
||||
case VideoMode::kBGR:
|
||||
pixelFormat = "bgr";
|
||||
break;
|
||||
case VideoMode::kGray:
|
||||
pixelFormat = "gray";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!pixelFormat.empty()) j.emplace("pixel format", pixelFormat);
|
||||
|
||||
// width
|
||||
if (m_mode.width != 0) j.emplace("width", m_mode.width);
|
||||
|
||||
// height
|
||||
if (m_mode.height != 0) j.emplace("height", m_mode.height);
|
||||
|
||||
// fps
|
||||
if (m_mode.fps != 0) j.emplace("fps", m_mode.fps);
|
||||
|
||||
// TODO: output brightness, white balance, and exposure?
|
||||
|
||||
// properties
|
||||
wpi::json props = GetPropertiesJsonObject(status);
|
||||
if (props.is_array()) j.emplace("properties", props);
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
std::vector<VideoMode> SourceImpl::EnumerateVideoModes(
|
||||
CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status))
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
#include "PropertyContainer.h"
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
namespace wpi {
|
||||
class json;
|
||||
} // namespace wpi
|
||||
|
||||
namespace cs {
|
||||
|
||||
class Notifier;
|
||||
@@ -127,6 +131,11 @@ class SourceImpl : public PropertyContainer {
|
||||
virtual bool SetResolution(int width, int height, CS_Status* status);
|
||||
virtual bool SetFPS(int fps, CS_Status* status);
|
||||
|
||||
bool SetConfigJson(wpi::StringRef config, CS_Status* status);
|
||||
virtual bool SetConfigJson(const wpi::json& config, CS_Status* status);
|
||||
std::string GetConfigJson(CS_Status* status);
|
||||
virtual wpi::json GetConfigJsonObject(CS_Status* status);
|
||||
|
||||
std::vector<VideoMode> EnumerateVideoModes(CS_Status* status) const;
|
||||
|
||||
std::unique_ptr<Image> AllocImage(VideoMode::PixelFormat pixelFormat,
|
||||
|
||||
@@ -12,6 +12,25 @@
|
||||
|
||||
using namespace cs;
|
||||
|
||||
static void ConvertToC(CS_UsbCameraInfo* out, const UsbCameraInfo& in) {
|
||||
out->dev = in.dev;
|
||||
out->path = ConvertToC(in.path);
|
||||
out->name = ConvertToC(in.name);
|
||||
out->otherPaths = static_cast<char**>(
|
||||
wpi::CheckedMalloc(in.otherPaths.size() * sizeof(char*)));
|
||||
out->otherPathsCount = in.otherPaths.size();
|
||||
for (size_t i = 0; i < in.otherPaths.size(); ++i)
|
||||
out->otherPaths[i] = cs::ConvertToC(in.otherPaths[i]);
|
||||
}
|
||||
|
||||
static void FreeUsbCameraInfo(CS_UsbCameraInfo* info) {
|
||||
std::free(info->path);
|
||||
std::free(info->name);
|
||||
for (int i = 0; i < info->otherPathsCount; ++i)
|
||||
std::free(info->otherPaths[i]);
|
||||
std::free(info->otherPaths);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
CS_Source CS_CreateUsbCameraDev(const char* name, int dev, CS_Status* status) {
|
||||
@@ -27,26 +46,34 @@ char* CS_GetUsbCameraPath(CS_Source source, CS_Status* status) {
|
||||
return ConvertToC(cs::GetUsbCameraPath(source, status));
|
||||
}
|
||||
|
||||
CS_UsbCameraInfo* CS_GetUsbCameraInfo(CS_Source source, CS_Status* status) {
|
||||
auto info = cs::GetUsbCameraInfo(source, status);
|
||||
if (*status != CS_OK) return nullptr;
|
||||
CS_UsbCameraInfo* out = static_cast<CS_UsbCameraInfo*>(
|
||||
wpi::CheckedMalloc(sizeof(CS_UsbCameraInfo)));
|
||||
ConvertToC(out, info);
|
||||
return out;
|
||||
}
|
||||
|
||||
CS_UsbCameraInfo* CS_EnumerateUsbCameras(int* count, CS_Status* status) {
|
||||
auto cameras = cs::EnumerateUsbCameras(status);
|
||||
CS_UsbCameraInfo* out = static_cast<CS_UsbCameraInfo*>(
|
||||
wpi::CheckedMalloc(cameras.size() * sizeof(CS_UsbCameraInfo)));
|
||||
*count = cameras.size();
|
||||
for (size_t i = 0; i < cameras.size(); ++i) {
|
||||
out[i].dev = cameras[i].dev;
|
||||
out[i].path = ConvertToC(cameras[i].path);
|
||||
out[i].name = ConvertToC(cameras[i].name);
|
||||
}
|
||||
for (size_t i = 0; i < cameras.size(); ++i) ConvertToC(&out[i], cameras[i]);
|
||||
return out;
|
||||
}
|
||||
|
||||
void CS_FreeEnumeratedUsbCameras(CS_UsbCameraInfo* cameras, int count) {
|
||||
if (!cameras) return;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
std::free(cameras[i].path);
|
||||
std::free(cameras[i].name);
|
||||
}
|
||||
for (int i = 0; i < count; ++i) FreeUsbCameraInfo(&cameras[i]);
|
||||
std::free(cameras);
|
||||
}
|
||||
|
||||
void CS_FreeUsbCameraInfo(CS_UsbCameraInfo* info) {
|
||||
if (!info) return;
|
||||
FreeUsbCameraInfo(info);
|
||||
std::free(info);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@@ -170,6 +170,15 @@ CS_Bool CS_SetSourceFPS(CS_Source source, int fps, CS_Status* status) {
|
||||
return cs::SetSourceFPS(source, fps, status);
|
||||
}
|
||||
|
||||
CS_Bool CS_SetSourceConfigJson(CS_Source source, const char* config,
|
||||
CS_Status* status) {
|
||||
return cs::SetSourceConfigJson(source, config, status);
|
||||
}
|
||||
|
||||
char* CS_GetSourceConfigJson(CS_Source source, CS_Status* status) {
|
||||
return cs::ConvertToC(cs::GetSourceConfigJson(source, status));
|
||||
}
|
||||
|
||||
CS_VideoMode* CS_EnumerateSourceVideoModes(CS_Source source, int* count,
|
||||
CS_Status* status) {
|
||||
auto vec = cs::EnumerateSourceVideoModes(source, status);
|
||||
@@ -268,6 +277,15 @@ CS_Property* CS_EnumerateSinkProperties(CS_Sink sink, int* count,
|
||||
return out;
|
||||
}
|
||||
|
||||
CS_Bool CS_SetSinkConfigJson(CS_Sink sink, const char* config,
|
||||
CS_Status* status) {
|
||||
return cs::SetSinkConfigJson(sink, config, status);
|
||||
}
|
||||
|
||||
char* CS_GetSinkConfigJson(CS_Sink sink, CS_Status* status) {
|
||||
return cs::ConvertToC(cs::GetSinkConfigJson(sink, status));
|
||||
}
|
||||
|
||||
void CS_SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status) {
|
||||
return cs::SetSinkSource(sink, source, status);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/hostname.h>
|
||||
#include <wpi/json.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "Instance.h"
|
||||
@@ -324,6 +325,44 @@ bool SetSourceFPS(CS_Source source, int fps, CS_Status* status) {
|
||||
return data->source->SetFPS(fps, status);
|
||||
}
|
||||
|
||||
bool SetSourceConfigJson(CS_Source source, wpi::StringRef config,
|
||||
CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return false;
|
||||
}
|
||||
return data->source->SetConfigJson(config, status);
|
||||
}
|
||||
|
||||
bool SetSourceConfigJson(CS_Source source, const wpi::json& config,
|
||||
CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return false;
|
||||
}
|
||||
return data->source->SetConfigJson(config, status);
|
||||
}
|
||||
|
||||
std::string GetSourceConfigJson(CS_Source source, CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::string{};
|
||||
}
|
||||
return data->source->GetConfigJson(status);
|
||||
}
|
||||
|
||||
wpi::json GetSourceConfigJsonObject(CS_Source source, CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return wpi::json{};
|
||||
}
|
||||
return data->source->GetConfigJsonObject(status);
|
||||
}
|
||||
|
||||
std::vector<VideoMode> EnumerateSourceVideoModes(CS_Source source,
|
||||
CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
@@ -525,6 +564,43 @@ wpi::ArrayRef<CS_Property> EnumerateSinkProperties(
|
||||
return vec;
|
||||
}
|
||||
|
||||
bool SetSinkConfigJson(CS_Sink sink, wpi::StringRef config, CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return false;
|
||||
}
|
||||
return data->sink->SetConfigJson(config, status);
|
||||
}
|
||||
|
||||
bool SetSinkConfigJson(CS_Sink sink, const wpi::json& config,
|
||||
CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return false;
|
||||
}
|
||||
return data->sink->SetConfigJson(config, status);
|
||||
}
|
||||
|
||||
std::string GetSinkConfigJson(CS_Sink sink, CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::string{};
|
||||
}
|
||||
return data->sink->GetConfigJson(status);
|
||||
}
|
||||
|
||||
wpi::json GetSinkConfigJsonObject(CS_Sink sink, CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return wpi::json{};
|
||||
}
|
||||
return data->sink->GetConfigJsonObject(status);
|
||||
}
|
||||
|
||||
void SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data) {
|
||||
|
||||
@@ -7,8 +7,20 @@
|
||||
|
||||
#include "cscore_oo.h"
|
||||
|
||||
#include <wpi/json.h>
|
||||
|
||||
using namespace cs;
|
||||
|
||||
wpi::json VideoSource::GetConfigJsonObject() const {
|
||||
m_status = 0;
|
||||
return GetSourceConfigJsonObject(m_handle, &m_status);
|
||||
}
|
||||
|
||||
wpi::json VideoSink::GetConfigJsonObject() const {
|
||||
m_status = 0;
|
||||
return GetSinkConfigJsonObject(m_handle, &m_status);
|
||||
}
|
||||
|
||||
std::vector<VideoProperty> VideoSource::EnumerateProperties() const {
|
||||
wpi::SmallVector<CS_Property, 32> handles_buf;
|
||||
CS_Status status = 0;
|
||||
|
||||
@@ -185,11 +185,14 @@ static inline bool CheckStatus(JNIEnv* env, CS_Status status) {
|
||||
|
||||
static jobject MakeJObject(JNIEnv* env, const cs::UsbCameraInfo& info) {
|
||||
static jmethodID constructor = env->GetMethodID(
|
||||
usbCameraInfoCls, "<init>", "(ILjava/lang/String;Ljava/lang/String;)V");
|
||||
usbCameraInfoCls, "<init>",
|
||||
"(ILjava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V");
|
||||
JLocal<jstring> path(env, MakeJString(env, info.path));
|
||||
JLocal<jstring> name(env, MakeJString(env, info.name));
|
||||
JLocal<jobjectArray> otherPaths(env, MakeJStringArray(env, info.otherPaths));
|
||||
return env->NewObject(usbCameraInfoCls, constructor,
|
||||
static_cast<jint>(info.dev), path.obj(), name.obj());
|
||||
static_cast<jint>(info.dev), path.obj(), name.obj(),
|
||||
otherPaths.obj());
|
||||
}
|
||||
|
||||
static jobject MakeJObject(JNIEnv* env, const cs::VideoMode& videoMode) {
|
||||
@@ -751,6 +754,36 @@ Java_edu_wpi_cscore_CameraServerJNI_setSourceFPS
|
||||
return val;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_cscore_CameraServerJNI
|
||||
* Method: setSourceConfigJson
|
||||
* Signature: (ILjava/lang/String;)Z
|
||||
*/
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_edu_wpi_cscore_CameraServerJNI_setSourceConfigJson
|
||||
(JNIEnv* env, jclass, jint source, jstring config)
|
||||
{
|
||||
CS_Status status = 0;
|
||||
auto val = cs::SetSourceConfigJson(source, JStringRef{env, config}, &status);
|
||||
CheckStatus(env, status);
|
||||
return val;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_cscore_CameraServerJNI
|
||||
* Method: getSourceConfigJson
|
||||
* Signature: (I)Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_edu_wpi_cscore_CameraServerJNI_getSourceConfigJson
|
||||
(JNIEnv* env, jclass, jint source)
|
||||
{
|
||||
CS_Status status = 0;
|
||||
auto val = cs::GetSourceConfigJson(source, &status);
|
||||
CheckStatus(env, status);
|
||||
return MakeJString(env, val);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_cscore_CameraServerJNI
|
||||
* Method: enumerateSourceVideoModes
|
||||
@@ -945,6 +978,21 @@ Java_edu_wpi_cscore_CameraServerJNI_getUsbCameraPath
|
||||
return MakeJString(env, str);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_cscore_CameraServerJNI
|
||||
* Method: getUsbCameraInfo
|
||||
* Signature: (I)Ljava/lang/Object;
|
||||
*/
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_edu_wpi_cscore_CameraServerJNI_getUsbCameraInfo
|
||||
(JNIEnv* env, jclass, jint source)
|
||||
{
|
||||
CS_Status status = 0;
|
||||
auto info = cs::GetUsbCameraInfo(source, &status);
|
||||
if (!CheckStatus(env, status)) return nullptr;
|
||||
return MakeJObject(env, info);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_cscore_CameraServerJNI
|
||||
* Method: getHttpCameraKind
|
||||
@@ -1244,6 +1292,36 @@ Java_edu_wpi_cscore_CameraServerJNI_enumerateSinkProperties
|
||||
return MakeJIntArray(env, arr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_cscore_CameraServerJNI
|
||||
* Method: setSinkConfigJson
|
||||
* Signature: (ILjava/lang/String;)Z
|
||||
*/
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_edu_wpi_cscore_CameraServerJNI_setSinkConfigJson
|
||||
(JNIEnv* env, jclass, jint source, jstring config)
|
||||
{
|
||||
CS_Status status = 0;
|
||||
auto val = cs::SetSinkConfigJson(source, JStringRef{env, config}, &status);
|
||||
CheckStatus(env, status);
|
||||
return val;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_cscore_CameraServerJNI
|
||||
* Method: getSinkConfigJson
|
||||
* Signature: (I)Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_edu_wpi_cscore_CameraServerJNI_getSinkConfigJson
|
||||
(JNIEnv* env, jclass, jint source)
|
||||
{
|
||||
CS_Status status = 0;
|
||||
auto val = cs::GetSinkConfigJson(source, &status);
|
||||
CheckStatus(env, status);
|
||||
return MakeJString(env, val);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_cscore_CameraServerJNI
|
||||
* Method: setSinkSource
|
||||
|
||||
@@ -69,7 +69,8 @@ enum CS_StatusValue {
|
||||
CS_SOURCE_IS_DISCONNECTED = -2005,
|
||||
CS_EMPTY_VALUE = -2006,
|
||||
CS_BAD_URL = -2007,
|
||||
CS_TELEMETRY_NOT_ENABLED = -2008
|
||||
CS_TELEMETRY_NOT_ENABLED = -2008,
|
||||
CS_UNSUPPORTED_MODE = -2009
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -222,6 +223,17 @@ struct CS_Event {
|
||||
const char* valueStr;
|
||||
};
|
||||
|
||||
/**
|
||||
* USB camera infomation
|
||||
*/
|
||||
typedef struct CS_UsbCameraInfo {
|
||||
int dev;
|
||||
char* path;
|
||||
char* name;
|
||||
int otherPathsCount;
|
||||
char** otherPaths;
|
||||
} CS_UsbCameraInfo;
|
||||
|
||||
/**
|
||||
* @defgroup cscore_property_cfunc Property Functions
|
||||
* @{
|
||||
@@ -289,6 +301,9 @@ CS_Bool CS_SetSourcePixelFormat(CS_Source source,
|
||||
CS_Bool CS_SetSourceResolution(CS_Source source, int width, int height,
|
||||
CS_Status* status);
|
||||
CS_Bool CS_SetSourceFPS(CS_Source source, int fps, CS_Status* status);
|
||||
CS_Bool CS_SetSourceConfigJson(CS_Source source, const char* config,
|
||||
CS_Status* status);
|
||||
char* CS_GetSourceConfigJson(CS_Source source, CS_Status* status);
|
||||
CS_VideoMode* CS_EnumerateSourceVideoModes(CS_Source source, int* count,
|
||||
CS_Status* status);
|
||||
CS_Sink* CS_EnumerateSourceSinks(CS_Source source, int* count,
|
||||
@@ -318,6 +333,7 @@ void CS_SetCameraExposureManual(CS_Source source, int value, CS_Status* status);
|
||||
* @{
|
||||
*/
|
||||
char* CS_GetUsbCameraPath(CS_Source source, CS_Status* status);
|
||||
CS_UsbCameraInfo* CS_GetUsbCameraInfo(CS_Source source, CS_Status* status);
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
@@ -377,6 +393,9 @@ CS_Property* CS_EnumerateSinkProperties(CS_Sink sink, int* count,
|
||||
void CS_SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status);
|
||||
CS_Property CS_GetSinkSourceProperty(CS_Sink sink, const char* name,
|
||||
CS_Status* status);
|
||||
CS_Bool CS_SetSinkConfigJson(CS_Sink sink, const char* config,
|
||||
CS_Status* status);
|
||||
char* CS_GetSinkConfigJson(CS_Sink sink, CS_Status* status);
|
||||
CS_Source CS_GetSinkSource(CS_Sink sink, CS_Status* status);
|
||||
CS_Sink CS_CopySink(CS_Sink sink, CS_Status* status);
|
||||
void CS_ReleaseSink(CS_Sink sink, CS_Status* status);
|
||||
@@ -452,15 +471,6 @@ void CS_Shutdown(void);
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* USB camera infomation
|
||||
*/
|
||||
typedef struct CS_UsbCameraInfo {
|
||||
int dev;
|
||||
char* path;
|
||||
char* name;
|
||||
} CS_UsbCameraInfo;
|
||||
|
||||
CS_UsbCameraInfo* CS_EnumerateUsbCameras(int* count, CS_Status* status);
|
||||
void CS_FreeEnumeratedUsbCameras(CS_UsbCameraInfo* cameras, int count);
|
||||
|
||||
@@ -472,6 +482,7 @@ void CS_ReleaseEnumeratedSinks(CS_Sink* sinks, int count);
|
||||
|
||||
void CS_FreeString(char* str);
|
||||
void CS_FreeEnumPropertyChoices(char** choices, int count);
|
||||
void CS_FreeUsbCameraInfo(CS_UsbCameraInfo* info);
|
||||
void CS_FreeHttpCameraUrls(char** urls, int count);
|
||||
|
||||
void CS_FreeEnumeratedProperties(CS_Property* properties, int count);
|
||||
|
||||
@@ -25,6 +25,10 @@ namespace cv {
|
||||
class Mat;
|
||||
} // namespace cv
|
||||
|
||||
namespace wpi {
|
||||
class json;
|
||||
} // namespace wpi
|
||||
|
||||
/** CameraServer (cscore) namespace */
|
||||
namespace cs {
|
||||
|
||||
@@ -43,11 +47,13 @@ namespace cs {
|
||||
*/
|
||||
struct UsbCameraInfo {
|
||||
/** Device number (e.g. N in '/dev/videoN' on Linux) */
|
||||
int dev;
|
||||
int dev = -1;
|
||||
/** Path to device if available (e.g. '/dev/video0' on Linux) */
|
||||
std::string path;
|
||||
/** Vendor/model name of the camera as provided by the USB driver */
|
||||
std::string name;
|
||||
/** Other path aliases to device (e.g. '/dev/v4l/by-id/...' etc on Linux) */
|
||||
std::vector<std::string> otherPaths;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -75,6 +81,13 @@ struct VideoMode : public CS_VideoMode {
|
||||
fps = fps_;
|
||||
}
|
||||
explicit operator bool() const { return pixelFormat == kUnknown; }
|
||||
|
||||
bool operator==(const VideoMode& other) const {
|
||||
return pixelFormat == other.pixelFormat && width == other.width &&
|
||||
height == other.height && fps == other.fps;
|
||||
}
|
||||
|
||||
bool operator!=(const VideoMode& other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -221,6 +234,12 @@ bool SetSourcePixelFormat(CS_Source source, VideoMode::PixelFormat pixelFormat,
|
||||
bool SetSourceResolution(CS_Source source, int width, int height,
|
||||
CS_Status* status);
|
||||
bool SetSourceFPS(CS_Source source, int fps, CS_Status* status);
|
||||
bool SetSourceConfigJson(CS_Source source, wpi::StringRef config,
|
||||
CS_Status* status);
|
||||
bool SetSourceConfigJson(CS_Source source, const wpi::json& config,
|
||||
CS_Status* status);
|
||||
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::ArrayRef<CS_Sink> EnumerateSourceSinks(CS_Source source,
|
||||
@@ -250,6 +269,7 @@ void SetCameraExposureManual(CS_Source source, int value, CS_Status* status);
|
||||
* @{
|
||||
*/
|
||||
std::string GetUsbCameraPath(CS_Source source, CS_Status* status);
|
||||
UsbCameraInfo GetUsbCameraInfo(CS_Source source, CS_Status* status);
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
@@ -312,6 +332,11 @@ wpi::ArrayRef<CS_Property> EnumerateSinkProperties(
|
||||
void SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status);
|
||||
CS_Property GetSinkSourceProperty(CS_Sink sink, const wpi::Twine& name,
|
||||
CS_Status* status);
|
||||
bool SetSinkConfigJson(CS_Sink sink, wpi::StringRef config, CS_Status* status);
|
||||
bool SetSinkConfigJson(CS_Sink sink, const wpi::json& config,
|
||||
CS_Status* status);
|
||||
std::string GetSinkConfigJson(CS_Sink sink, CS_Status* status);
|
||||
wpi::json GetSinkConfigJsonObject(CS_Sink sink, CS_Status* status);
|
||||
CS_Source GetSinkSource(CS_Sink sink, CS_Status* status);
|
||||
CS_Sink CopySink(CS_Sink sink, CS_Status* status);
|
||||
void ReleaseSink(CS_Sink sink, CS_Status* status);
|
||||
|
||||
@@ -253,6 +253,56 @@ class VideoSource {
|
||||
*/
|
||||
bool SetFPS(int fps);
|
||||
|
||||
/**
|
||||
* Set video mode and properties from a JSON configuration string.
|
||||
*
|
||||
* The format of the JSON input is:
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* "pixel format": "MJPEG", "YUYV", etc
|
||||
* "width": video mode width
|
||||
* "height": video mode height
|
||||
* "fps": video mode fps
|
||||
* "brightness": percentage brightness
|
||||
* "white balance": "auto", "hold", or value
|
||||
* "exposure": "auto", "hold", or value
|
||||
* "properties": [
|
||||
* {
|
||||
* "name": property name
|
||||
* "value": property value
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param config configuration
|
||||
* @return True if set successfully
|
||||
*/
|
||||
bool SetConfigJson(wpi::StringRef config);
|
||||
|
||||
/**
|
||||
* Set video mode and properties from a JSON configuration object.
|
||||
*
|
||||
* @param config configuration
|
||||
* @return True if set successfully
|
||||
*/
|
||||
bool SetConfigJson(const wpi::json& config);
|
||||
|
||||
/**
|
||||
* Get a JSON configuration string.
|
||||
*
|
||||
* @return JSON configuration string
|
||||
*/
|
||||
std::string GetConfigJson() const;
|
||||
|
||||
/**
|
||||
* Get a JSON configuration object.
|
||||
*
|
||||
* @return JSON configuration object
|
||||
*/
|
||||
wpi::json GetConfigJsonObject() const;
|
||||
|
||||
/**
|
||||
* Get the actual FPS.
|
||||
*
|
||||
@@ -399,6 +449,11 @@ class UsbCamera : public VideoCamera {
|
||||
*/
|
||||
std::string GetPath() const;
|
||||
|
||||
/**
|
||||
* Get the full camera information for the device.
|
||||
*/
|
||||
UsbCameraInfo GetInfo() const;
|
||||
|
||||
/**
|
||||
* Set how verbose the camera connection messages are.
|
||||
*
|
||||
@@ -532,6 +587,15 @@ class AxisCamera : public HttpCamera {
|
||||
*/
|
||||
AxisCamera(const wpi::Twine& name, const std::string& host);
|
||||
|
||||
/**
|
||||
* Create a source for an Axis IP camera.
|
||||
*
|
||||
* @param name Source name (arbitrary unique identifier)
|
||||
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
|
||||
* @param kind Camera kind (e.g. kAxis)
|
||||
*/
|
||||
AxisCamera(const wpi::Twine& name, wpi::StringRef host);
|
||||
|
||||
/**
|
||||
* Create a source for an Axis IP camera.
|
||||
*
|
||||
@@ -743,6 +807,49 @@ class VideoSink {
|
||||
*/
|
||||
std::vector<VideoProperty> EnumerateProperties() const;
|
||||
|
||||
/**
|
||||
* Set properties from a JSON configuration string.
|
||||
*
|
||||
* The format of the JSON input is:
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* "properties": [
|
||||
* {
|
||||
* "name": property name
|
||||
* "value": property value
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param config configuration
|
||||
* @return True if set successfully
|
||||
*/
|
||||
bool SetConfigJson(wpi::StringRef config);
|
||||
|
||||
/**
|
||||
* Set properties from a JSON configuration object.
|
||||
*
|
||||
* @param config configuration
|
||||
* @return True if set successfully
|
||||
*/
|
||||
bool SetConfigJson(const wpi::json& config);
|
||||
|
||||
/**
|
||||
* Get a JSON configuration string.
|
||||
*
|
||||
* @return JSON configuration string
|
||||
*/
|
||||
std::string GetConfigJson() const;
|
||||
|
||||
/**
|
||||
* Get a JSON configuration object.
|
||||
*
|
||||
* @return JSON configuration object
|
||||
*/
|
||||
wpi::json GetConfigJsonObject() const;
|
||||
|
||||
/**
|
||||
* Configure which source should provide frames to this sink. Each sink
|
||||
* can accept frames from only a single source, but a single source can
|
||||
|
||||
@@ -170,6 +170,21 @@ inline bool VideoSource::SetFPS(int fps) {
|
||||
return SetSourceFPS(m_handle, fps, &m_status);
|
||||
}
|
||||
|
||||
inline bool VideoSource::SetConfigJson(wpi::StringRef config) {
|
||||
m_status = 0;
|
||||
return SetSourceConfigJson(m_handle, config, &m_status);
|
||||
}
|
||||
|
||||
inline bool VideoSource::SetConfigJson(const wpi::json& config) {
|
||||
m_status = 0;
|
||||
return SetSourceConfigJson(m_handle, config, &m_status);
|
||||
}
|
||||
|
||||
inline std::string VideoSource::GetConfigJson() const {
|
||||
m_status = 0;
|
||||
return GetSourceConfigJson(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline double VideoSource::GetActualFPS() const {
|
||||
m_status = 0;
|
||||
return cs::GetTelemetryAverageValue(m_handle, CS_SOURCE_FRAMES_RECEIVED,
|
||||
@@ -245,6 +260,11 @@ inline std::string UsbCamera::GetPath() const {
|
||||
return ::cs::GetUsbCameraPath(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline UsbCameraInfo UsbCamera::GetInfo() const {
|
||||
m_status = 0;
|
||||
return ::cs::GetUsbCameraInfo(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline void UsbCamera::SetConnectVerbose(int level) {
|
||||
m_status = 0;
|
||||
SetProperty(GetSourceProperty(m_handle, "connect_verbose", &m_status), level,
|
||||
@@ -346,6 +366,9 @@ inline AxisCamera::AxisCamera(const wpi::Twine& name, const char* host)
|
||||
inline AxisCamera::AxisCamera(const wpi::Twine& name, const std::string& host)
|
||||
: HttpCamera(name, HostToUrl(wpi::Twine{host}), kAxis) {}
|
||||
|
||||
inline AxisCamera::AxisCamera(const wpi::Twine& name, wpi::StringRef host)
|
||||
: HttpCamera(name, HostToUrl(host), kAxis) {}
|
||||
|
||||
inline AxisCamera::AxisCamera(const wpi::Twine& name,
|
||||
wpi::ArrayRef<std::string> hosts)
|
||||
: HttpCamera(name, HostToUrl(hosts), kAxis) {}
|
||||
@@ -498,6 +521,21 @@ inline VideoProperty VideoSink::GetSourceProperty(const wpi::Twine& name) {
|
||||
return VideoProperty{GetSinkSourceProperty(m_handle, name, &m_status)};
|
||||
}
|
||||
|
||||
inline bool VideoSink::SetConfigJson(wpi::StringRef config) {
|
||||
m_status = 0;
|
||||
return SetSinkConfigJson(m_handle, config, &m_status);
|
||||
}
|
||||
|
||||
inline bool VideoSink::SetConfigJson(const wpi::json& config) {
|
||||
m_status = 0;
|
||||
return SetSinkConfigJson(m_handle, config, &m_status);
|
||||
}
|
||||
|
||||
inline std::string VideoSink::GetConfigJson() const {
|
||||
m_status = 0;
|
||||
return GetSinkConfigJson(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline MjpegServer::MjpegServer(const wpi::Twine& name,
|
||||
const wpi::Twine& listenAddress, int port) {
|
||||
m_handle = CreateMjpegServer(name, listenAddress, port, &m_status);
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <wpi/FileSystem.h>
|
||||
#include <wpi/Path.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/memory.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
@@ -1278,17 +1280,79 @@ std::string GetUsbCameraPath(CS_Source source, CS_Status* status) {
|
||||
return static_cast<UsbCameraImpl&>(*data->source).GetPath();
|
||||
}
|
||||
|
||||
static const char* symlinkDirs[] = {"/dev/v4l/by-id", "/dev/v4l/by-path"};
|
||||
|
||||
UsbCameraInfo GetUsbCameraInfo(CS_Source source, CS_Status* status) {
|
||||
UsbCameraInfo info;
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_USB) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return info;
|
||||
}
|
||||
std::string keypath = static_cast<UsbCameraImpl&>(*data->source).GetPath();
|
||||
info.path = keypath;
|
||||
|
||||
// it might be a symlink; if so, find the symlink target (e.g. /dev/videoN),
|
||||
// add that to the list and make it the keypath
|
||||
if (wpi::sys::fs::is_symlink_file(keypath)) {
|
||||
char* target = ::realpath(keypath.c_str(), nullptr);
|
||||
if (target) {
|
||||
keypath.assign(target);
|
||||
info.otherPaths.emplace_back(keypath);
|
||||
std::free(target);
|
||||
}
|
||||
}
|
||||
|
||||
// device number
|
||||
wpi::StringRef fname = wpi::sys::path::filename(keypath);
|
||||
if (fname.startswith("video")) fname.substr(5).getAsInteger(10, info.dev);
|
||||
|
||||
// description
|
||||
info.name = GetDescriptionImpl(keypath.c_str());
|
||||
|
||||
// look through /dev/v4l/by-id and /dev/v4l/by-path for symlinks to the
|
||||
// keypath
|
||||
wpi::SmallString<128> path;
|
||||
for (auto symlinkDir : symlinkDirs) {
|
||||
if (DIR* dp = ::opendir(symlinkDir)) {
|
||||
while (struct dirent* ep = ::readdir(dp)) {
|
||||
if (ep->d_type == DT_LNK) {
|
||||
path = symlinkDir;
|
||||
path += '/';
|
||||
path += ep->d_name;
|
||||
char* target = ::realpath(path.c_str(), nullptr);
|
||||
if (target) {
|
||||
if (keypath == target) info.otherPaths.emplace_back(path.str());
|
||||
std::free(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
::closedir(dp);
|
||||
}
|
||||
}
|
||||
|
||||
// eliminate any duplicates
|
||||
std::sort(info.otherPaths.begin(), info.otherPaths.end());
|
||||
info.otherPaths.erase(
|
||||
std::unique(info.otherPaths.begin(), info.otherPaths.end()),
|
||||
info.otherPaths.end());
|
||||
return info;
|
||||
}
|
||||
|
||||
std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
|
||||
std::vector<UsbCameraInfo> retval;
|
||||
|
||||
if (DIR* dp = opendir("/dev")) {
|
||||
while (struct dirent* ep = readdir(dp)) {
|
||||
if (DIR* dp = ::opendir("/dev")) {
|
||||
while (struct dirent* ep = ::readdir(dp)) {
|
||||
wpi::StringRef fname{ep->d_name};
|
||||
if (!fname.startswith("video")) continue;
|
||||
|
||||
unsigned int dev = 0;
|
||||
if (fname.substr(5).getAsInteger(10, dev)) continue;
|
||||
|
||||
UsbCameraInfo info;
|
||||
info.dev = -1;
|
||||
fname.substr(5).getAsInteger(10, info.dev);
|
||||
info.dev = dev;
|
||||
|
||||
wpi::SmallString<32> path{"/dev/"};
|
||||
path += fname;
|
||||
info.path = path.str();
|
||||
@@ -1296,20 +1360,47 @@ std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
|
||||
info.name = GetDescriptionImpl(path.c_str());
|
||||
if (info.name.empty()) continue;
|
||||
|
||||
retval.emplace_back(std::move(info));
|
||||
if (dev >= retval.size()) retval.resize(info.dev + 1);
|
||||
retval[info.dev] = std::move(info);
|
||||
}
|
||||
closedir(dp);
|
||||
::closedir(dp);
|
||||
} else {
|
||||
// *status = ;
|
||||
WPI_ERROR(Instance::GetInstance().logger, "Could not open /dev");
|
||||
return retval;
|
||||
}
|
||||
|
||||
// sort by device number
|
||||
std::sort(retval.begin(), retval.end(),
|
||||
[](const UsbCameraInfo& a, const UsbCameraInfo& b) {
|
||||
return a.dev < b.dev;
|
||||
});
|
||||
// look through /dev/v4l/by-id and /dev/v4l/by-path for symlinks to
|
||||
// /dev/videoN
|
||||
wpi::SmallString<128> path;
|
||||
for (auto symlinkDir : symlinkDirs) {
|
||||
if (DIR* dp = ::opendir(symlinkDir)) {
|
||||
while (struct dirent* ep = ::readdir(dp)) {
|
||||
if (ep->d_type == DT_LNK) {
|
||||
path = symlinkDir;
|
||||
path += '/';
|
||||
path += ep->d_name;
|
||||
char* target = ::realpath(path.c_str(), nullptr);
|
||||
if (target) {
|
||||
wpi::StringRef fname = wpi::sys::path::filename(target);
|
||||
unsigned int dev = 0;
|
||||
if (fname.startswith("video") &&
|
||||
!fname.substr(5).getAsInteger(10, dev) && dev < retval.size()) {
|
||||
retval[dev].otherPaths.emplace_back(path.str());
|
||||
}
|
||||
std::free(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
::closedir(dp);
|
||||
}
|
||||
}
|
||||
|
||||
// remove devices with empty names
|
||||
retval.erase(
|
||||
std::remove_if(retval.begin(), retval.end(),
|
||||
[](const UsbCameraInfo& x) { return x.name.empty(); }),
|
||||
retval.end());
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <wpi/STLExtras.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
#include "UsbUtil.h"
|
||||
|
||||
@@ -151,6 +152,9 @@ UsbCameraProperty::UsbCameraProperty(const struct v4l2_query_ext_ctrl& ctrl)
|
||||
propKind = CS_PROP_BOOLEAN;
|
||||
break;
|
||||
case V4L2_CTRL_TYPE_INTEGER_MENU:
|
||||
propKind = CS_PROP_ENUM;
|
||||
intMenu = true;
|
||||
break;
|
||||
case V4L2_CTRL_TYPE_MENU:
|
||||
propKind = CS_PROP_ENUM;
|
||||
break;
|
||||
@@ -243,7 +247,12 @@ std::unique_ptr<UsbCameraProperty> UsbCameraProperty::DeviceQuery(int fd,
|
||||
for (int i = prop->minimum; i <= prop->maximum; ++i) {
|
||||
qmenu.index = static_cast<__u32>(i);
|
||||
if (TryIoctl(fd, VIDIOC_QUERYMENU, &qmenu) != 0) continue;
|
||||
prop->enumChoices[i] = reinterpret_cast<const char*>(qmenu.name);
|
||||
if (prop->intMenu) {
|
||||
wpi::raw_string_ostream os(prop->enumChoices[i]);
|
||||
os << qmenu.value;
|
||||
} else {
|
||||
prop->enumChoices[i] = reinterpret_cast<const char*>(qmenu.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,9 @@ class UsbCameraProperty : public PropertyImpl {
|
||||
|
||||
unsigned id{0}; // implementation-level id
|
||||
int type{0}; // implementation type, not CS_PropertyKind!
|
||||
|
||||
// If the enum property is integer rather than string
|
||||
bool intMenu{false};
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
@@ -26,6 +26,11 @@ std::string GetUsbCameraPath(CS_Source source, CS_Status* status) {
|
||||
return std::string{};
|
||||
}
|
||||
|
||||
UsbCameraInfo GetUsbCameraInfo(CS_Source source, CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return UsbCameraInfo{};
|
||||
}
|
||||
|
||||
std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::vector<UsbCameraInfo>{};
|
||||
|
||||
158
cscore/src/main/native/windows/COMCreators.cpp
Normal file
158
cscore/src/main/native/windows/COMCreators.cpp
Normal file
@@ -0,0 +1,158 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include <mfapi.h>
|
||||
#include <mfidl.h>
|
||||
#include <shlwapi.h>
|
||||
#include <windowsx.h>
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include "UsbCameraImpl.h"
|
||||
|
||||
// https://github.com/opencv/opencv/blob/master/modules/videoio/src/cap_msmf.cpp
|
||||
|
||||
#include <mfidl.h>
|
||||
#include <mfapi.h>
|
||||
#include <Dbt.h>
|
||||
#include <ks.h>
|
||||
#include <ksmedia.h>
|
||||
#include <mfreadwrite.h>
|
||||
|
||||
#include "COMCreators.h"
|
||||
#include "ComPtr.h"
|
||||
|
||||
#pragma comment(lib, "Mfplat.lib")
|
||||
#pragma comment(lib, "Mf.lib")
|
||||
#pragma comment(lib, "mfuuid.lib")
|
||||
#pragma comment(lib, "Ole32.lib")
|
||||
#pragma comment(lib, "User32.lib")
|
||||
#pragma comment(lib, "Mfreadwrite.lib")
|
||||
#pragma comment(lib, "Shlwapi.lib")
|
||||
|
||||
namespace cs {
|
||||
|
||||
SourceReaderCB::SourceReaderCB(std::weak_ptr<cs::UsbCameraImpl> source,
|
||||
const cs::VideoMode& mode)
|
||||
: m_nRefCount(1), m_source(source), m_mode{mode} {}
|
||||
|
||||
// IUnknown methods
|
||||
STDMETHODIMP SourceReaderCB::QueryInterface(REFIID iid, void** ppv) {
|
||||
static const QITAB qit[] = {
|
||||
QITABENT(SourceReaderCB, IMFSourceReaderCallback),
|
||||
{0},
|
||||
};
|
||||
return QISearch(this, qit, iid, ppv);
|
||||
}
|
||||
STDMETHODIMP_(ULONG) SourceReaderCB::AddRef() {
|
||||
return InterlockedIncrement(&m_nRefCount);
|
||||
}
|
||||
STDMETHODIMP_(ULONG) SourceReaderCB::Release() {
|
||||
ULONG uCount = InterlockedDecrement(&m_nRefCount);
|
||||
if (uCount == 0) {
|
||||
delete this;
|
||||
}
|
||||
return uCount;
|
||||
}
|
||||
|
||||
STDMETHODIMP SourceReaderCB::OnEvent(DWORD, IMFMediaEvent*) { return S_OK; }
|
||||
|
||||
STDMETHODIMP SourceReaderCB::OnFlush(DWORD) { return S_OK; }
|
||||
|
||||
void SourceReaderCB::NotifyError(HRESULT hr) {
|
||||
wprintf(L"Source Reader error: 0x%X\n", hr);
|
||||
}
|
||||
|
||||
STDMETHODIMP SourceReaderCB::OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
|
||||
DWORD dwStreamFlags,
|
||||
LONGLONG llTimestamp,
|
||||
IMFSample* pSample // Can be NULL
|
||||
) {
|
||||
auto source = m_source.lock();
|
||||
if (!source) return S_OK;
|
||||
if (SUCCEEDED(hrStatus)) {
|
||||
if (pSample) {
|
||||
// Prcoess sample
|
||||
source->ProcessFrame(pSample, m_mode);
|
||||
// DO NOT release the frame
|
||||
}
|
||||
} else {
|
||||
// Streaming error.
|
||||
NotifyError(hrStatus);
|
||||
}
|
||||
// Trigger asking for a new frame.
|
||||
// This is piped through the message pump for concurrency reasons
|
||||
source->PostRequestNewFrame();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Create a Source Reader COM Smart Object
|
||||
ComPtr<SourceReaderCB> CreateSourceReaderCB(
|
||||
std::weak_ptr<cs::UsbCameraImpl> source, const cs::VideoMode& mode) {
|
||||
SourceReaderCB* ptr = new SourceReaderCB(source, mode);
|
||||
ComPtr<SourceReaderCB> sourceReaderCB;
|
||||
sourceReaderCB.Attach(ptr);
|
||||
return sourceReaderCB;
|
||||
}
|
||||
|
||||
ComPtr<IMFMediaSource> CreateVideoCaptureDevice(LPCWSTR pszSymbolicLink) {
|
||||
ComPtr<IMFAttributes> pAttributes;
|
||||
ComPtr<IMFMediaSource> pSource;
|
||||
|
||||
HRESULT hr = MFCreateAttributes(pAttributes.GetAddressOf(), 2);
|
||||
|
||||
// Set the device type to video.
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = pAttributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
|
||||
}
|
||||
|
||||
// Set the symbolic link.
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = pAttributes->SetString(
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
|
||||
pszSymbolicLink);
|
||||
}
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = MFCreateDeviceSource(pAttributes.Get(), pSource.GetAddressOf());
|
||||
}
|
||||
|
||||
// No need to check last HR, as the source would be null anyway.
|
||||
return pSource;
|
||||
}
|
||||
|
||||
ComPtr<IMFSourceReader> CreateSourceReader(IMFMediaSource* mediaSource,
|
||||
IMFSourceReaderCallback* callback) {
|
||||
HRESULT hr = S_OK;
|
||||
ComPtr<IMFAttributes> pAttributes;
|
||||
ComPtr<IMFSourceReader> sourceReader;
|
||||
|
||||
hr = MFCreateAttributes(pAttributes.GetAddressOf(), 1);
|
||||
if (FAILED(hr)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
hr = pAttributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, callback);
|
||||
if (FAILED(hr)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
hr = pAttributes->SetUINT32(
|
||||
MF_SOURCE_READER_DISCONNECT_MEDIASOURCE_ON_SHUTDOWN, TRUE);
|
||||
if (FAILED(hr)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MFCreateSourceReaderFromMediaSource(mediaSource, pAttributes.Get(),
|
||||
sourceReader.GetAddressOf());
|
||||
|
||||
// No need to check last HR, as the sourceReader would be null anyway.
|
||||
return sourceReader;
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
58
cscore/src/main/native/windows/COMCreators.h
Normal file
58
cscore/src/main/native/windows/COMCreators.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mfidl.h>
|
||||
#include <mfreadwrite.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "ComPtr.h"
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
class UsbCameraImpl;
|
||||
|
||||
// Source callback used by the source reader.
|
||||
// COM object, so it needs a to ref count itself.
|
||||
class SourceReaderCB : public IMFSourceReaderCallback {
|
||||
public:
|
||||
explicit SourceReaderCB(std::weak_ptr<cs::UsbCameraImpl> source,
|
||||
const cs::VideoMode& mode);
|
||||
void SetVideoMode(const VideoMode& mode) { m_mode = mode; }
|
||||
|
||||
STDMETHODIMP QueryInterface(REFIID iid, void** ppv);
|
||||
STDMETHODIMP_(ULONG) AddRef();
|
||||
STDMETHODIMP_(ULONG) Release();
|
||||
|
||||
STDMETHODIMP OnEvent(DWORD, IMFMediaEvent*);
|
||||
STDMETHODIMP OnFlush(DWORD);
|
||||
STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
|
||||
DWORD dwStreamFlags, LONGLONG llTimestamp,
|
||||
IMFSample* pSample // Can be NULL
|
||||
);
|
||||
|
||||
void InvalidateCapture() { m_source = std::weak_ptr<cs::UsbCameraImpl>(); }
|
||||
|
||||
private:
|
||||
// Destructor is private. Caller should call Release.
|
||||
virtual ~SourceReaderCB() {}
|
||||
void NotifyError(HRESULT hr);
|
||||
|
||||
ULONG m_nRefCount;
|
||||
std::weak_ptr<cs::UsbCameraImpl> m_source;
|
||||
cs::VideoMode m_mode;
|
||||
};
|
||||
|
||||
ComPtr<SourceReaderCB> CreateSourceReaderCB(
|
||||
std::weak_ptr<cs::UsbCameraImpl> source, const cs::VideoMode& mode);
|
||||
ComPtr<IMFSourceReader> CreateSourceReader(IMFMediaSource* mediaSource,
|
||||
IMFSourceReaderCallback* callback);
|
||||
ComPtr<IMFMediaSource> CreateVideoCaptureDevice(LPCWSTR pszSymbolicLink);
|
||||
} // namespace cs
|
||||
152
cscore/src/main/native/windows/ComPtr.h
Normal file
152
cscore/src/main/native/windows/ComPtr.h
Normal file
@@ -0,0 +1,152 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <comdef.h>
|
||||
#include <shlwapi.h> // QISearch
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace cs {
|
||||
|
||||
template <typename Interface>
|
||||
class RemoveAddRefRelease : public Interface {
|
||||
ULONG __stdcall AddRef();
|
||||
ULONG __stdcall Release();
|
||||
virtual ~RemoveAddRefRelease();
|
||||
};
|
||||
|
||||
template <typename Interface>
|
||||
class ComPtr {
|
||||
public:
|
||||
template <typename T>
|
||||
friend class ComPtr;
|
||||
|
||||
ComPtr(std::nullptr_t = nullptr) noexcept {} // NOLINT(runtime/explicit)
|
||||
ComPtr(const ComPtr& other) noexcept : m_ptr(other.m_ptr) {
|
||||
InternalAddRef();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ComPtr(const ComPtr<T>& other) noexcept : m_ptr(other.m_ptr) {
|
||||
InternalAddRef();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ComPtr(ComPtr<T>&& other) noexcept : m_ptr(other.m_ptr) {
|
||||
other.m_ptr = nullptr;
|
||||
}
|
||||
|
||||
~ComPtr() noexcept { InternalRelease(); }
|
||||
|
||||
ComPtr& operator=(const ComPtr& other) noexcept {
|
||||
InternalCopy(other.m_ptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ComPtr& operator=(const ComPtr<T>& other) noexcept {
|
||||
InternalCopy(other.m_ptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ComPtr& operator=(ComPtr<T>&& other) noexcept {
|
||||
InternalMove(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Swap(ComPtr& other) noexcept {
|
||||
Interface* temp = m_ptr;
|
||||
m_ptr = other.m_ptr;
|
||||
other.m_ptr = temp;
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept { return nullptr != m_ptr; }
|
||||
|
||||
void Reset() noexcept { InternalRelease(); }
|
||||
|
||||
Interface* Get() const noexcept { return m_ptr; }
|
||||
|
||||
Interface* Detach() noexcept {
|
||||
Interface* temp = m_ptr;
|
||||
m_ptr = nullptr;
|
||||
return temp;
|
||||
}
|
||||
|
||||
void Copy(Interface* other) noexcept { InternalCopy(other); }
|
||||
|
||||
void Attach(Interface* other) noexcept {
|
||||
InternalRelease();
|
||||
m_ptr = other;
|
||||
}
|
||||
|
||||
Interface** GetAddressOf() noexcept {
|
||||
assert(m_ptr == nullptr);
|
||||
return &m_ptr;
|
||||
}
|
||||
|
||||
void CopyTo(Interface** other) const noexcept {
|
||||
InternalAddRef();
|
||||
*other = m_ptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ComPtr<T> As() const noexcept {
|
||||
ComPtr<T> temp;
|
||||
m_ptr->QueryInterface(temp.GetAddressOf());
|
||||
return temp;
|
||||
}
|
||||
|
||||
RemoveAddRefRelease<Interface>* operator->() const noexcept {
|
||||
return static_cast<RemoveAddRefRelease<Interface>*>(m_ptr);
|
||||
}
|
||||
|
||||
private:
|
||||
Interface* m_ptr = nullptr;
|
||||
|
||||
void InternalAddRef() const noexcept {
|
||||
if (m_ptr) {
|
||||
m_ptr->AddRef();
|
||||
}
|
||||
}
|
||||
|
||||
void InternalRelease() noexcept {
|
||||
Interface* temp = m_ptr;
|
||||
if (temp) {
|
||||
m_ptr = nullptr;
|
||||
temp->Release();
|
||||
}
|
||||
}
|
||||
|
||||
void InternalCopy(Interface* other) noexcept {
|
||||
if (m_ptr != other) {
|
||||
InternalRelease();
|
||||
m_ptr = other;
|
||||
InternalAddRef();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void InternalMove(ComPtr<T>& other) noexcept {
|
||||
if (m_ptr != other.m_ptr) {
|
||||
InternalRelease();
|
||||
m_ptr = other.m_ptr;
|
||||
other.m_ptr = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Interface>
|
||||
void swap(
|
||||
ComPtr<Interface>& left,
|
||||
ComPtr<Interface>& right) noexcept { // NOLINT(build/include_what_you_use)
|
||||
left.Swap(right);
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
@@ -7,14 +7,55 @@
|
||||
|
||||
#include "NetworkListener.h"
|
||||
|
||||
#include <winsock2.h> // NOLINT(build/include_order)
|
||||
|
||||
#include <windows.h> // NOLINT(build/include_order)
|
||||
|
||||
#include <ws2def.h> // NOLINT(build/include_order)
|
||||
|
||||
#include <ws2ipdef.h> // NOLINT(build/include_order)
|
||||
|
||||
#include <iphlpapi.h> // NOLINT(build/include_order)
|
||||
|
||||
#include <netioapi.h> // NOLINT(build/include_order)
|
||||
|
||||
#include "Instance.h"
|
||||
#include "Log.h"
|
||||
#include "Notifier.h"
|
||||
|
||||
#pragma comment(lib, "Iphlpapi.lib")
|
||||
|
||||
using namespace cs;
|
||||
|
||||
class NetworkListener::Impl {};
|
||||
class NetworkListener::Impl {
|
||||
public:
|
||||
Impl(wpi::Logger& logger, Notifier& notifier)
|
||||
: m_logger(logger), m_notifier(notifier) {}
|
||||
wpi::Logger& m_logger;
|
||||
Notifier& m_notifier;
|
||||
HANDLE eventHandle = 0;
|
||||
};
|
||||
|
||||
NetworkListener::NetworkListener(wpi::Logger& logger, Notifier& notifier) {}
|
||||
// Static Callback function for NotifyIpInterfaceChange API.
|
||||
static void WINAPI OnInterfaceChange(PVOID callerContext,
|
||||
PMIB_IPINTERFACE_ROW row,
|
||||
MIB_NOTIFICATION_TYPE notificationType) {
|
||||
Notifier* notifier = reinterpret_cast<Notifier*>(callerContext);
|
||||
notifier->NotifyNetworkInterfacesChanged();
|
||||
}
|
||||
|
||||
NetworkListener::~NetworkListener() {}
|
||||
NetworkListener::NetworkListener(wpi::Logger& logger, Notifier& notifier)
|
||||
: m_impl(std::make_unique<Impl>(logger, notifier)) {}
|
||||
|
||||
void NetworkListener::Start() {}
|
||||
NetworkListener::~NetworkListener() { Stop(); }
|
||||
|
||||
void NetworkListener::Stop() {}
|
||||
void NetworkListener::Start() {
|
||||
NotifyIpInterfaceChange(AF_INET, OnInterfaceChange, &m_impl->m_notifier, true,
|
||||
&m_impl->eventHandle);
|
||||
}
|
||||
|
||||
void NetworkListener::Stop() {
|
||||
if (m_impl->eventHandle) {
|
||||
CancelMibChangeNotify2(m_impl->eventHandle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,38 @@
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include <uv.h>
|
||||
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
#pragma comment(lib, "Ws2_32.lib")
|
||||
|
||||
namespace cs {
|
||||
|
||||
std::vector<std::string> GetNetworkInterfaces() {
|
||||
return std::vector<std::string>{}; // TODO
|
||||
uv_interface_address_t* adrs;
|
||||
int counts = 0;
|
||||
|
||||
std::vector<std::string> addresses{};
|
||||
|
||||
uv_interface_addresses(&adrs, &counts);
|
||||
|
||||
char ip[50];
|
||||
|
||||
for (int i = 0; i < counts; i++) {
|
||||
if (adrs[i].is_internal) continue;
|
||||
InetNtop(PF_INET, &(adrs[i].netmask.netmask4.sin_addr.s_addr), ip,
|
||||
sizeof(ip) - 1);
|
||||
ip[49] = '\0';
|
||||
InetNtop(PF_INET, &(adrs[i].address.address4.sin_addr.s_addr), ip,
|
||||
sizeof(ip) - 1);
|
||||
ip[49] = '\0';
|
||||
addresses.emplace_back(std::string{ip});
|
||||
}
|
||||
|
||||
uv_free_interface_addresses(adrs, counts);
|
||||
|
||||
return addresses;
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
185
cscore/src/main/native/windows/UsbCameraImpl.h
Normal file
185
cscore/src/main/native/windows/UsbCameraImpl.h
Normal file
@@ -0,0 +1,185 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_USBCAMERAIMPL_H_
|
||||
#define CSCORE_USBCAMERAIMPL_H_
|
||||
|
||||
#include <mfapi.h>
|
||||
#include <mfidl.h>
|
||||
#include <mfreadwrite.h>
|
||||
|
||||
#include <ks.h> // NOLINT(build/include_order)
|
||||
|
||||
#include <ksmedia.h> // NOLINT(build/include_order)
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <Dbt.h>
|
||||
#include <wpi/STLExtras.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/Twine.h>
|
||||
#include <wpi/condition_variable.h>
|
||||
#include <wpi/mutex.h>
|
||||
#include <wpi/raw_istream.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
#include "ComCreators.h"
|
||||
#include "ComPtr.h"
|
||||
#include "SourceImpl.h"
|
||||
#include "UsbCameraProperty.h"
|
||||
#include "WindowsMessagePump.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
class UsbCameraImpl : public SourceImpl,
|
||||
public std::enable_shared_from_this<UsbCameraImpl> {
|
||||
public:
|
||||
UsbCameraImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier,
|
||||
Telemetry& telemetry, const wpi::Twine& path);
|
||||
UsbCameraImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier,
|
||||
Telemetry& telemetry, int deviceId);
|
||||
~UsbCameraImpl() override;
|
||||
|
||||
void Start();
|
||||
|
||||
// Property functions
|
||||
void SetProperty(int property, int value, CS_Status* status) override;
|
||||
void SetStringProperty(int property, const wpi::Twine& value,
|
||||
CS_Status* status) override;
|
||||
|
||||
// Standard common camera properties
|
||||
void SetBrightness(int brightness, CS_Status* status) override;
|
||||
int GetBrightness(CS_Status* status) const override;
|
||||
void SetWhiteBalanceAuto(CS_Status* status) override;
|
||||
void SetWhiteBalanceHoldCurrent(CS_Status* status) override;
|
||||
void SetWhiteBalanceManual(int value, CS_Status* status) override;
|
||||
void SetExposureAuto(CS_Status* status) override;
|
||||
void SetExposureHoldCurrent(CS_Status* status) override;
|
||||
void SetExposureManual(int value, CS_Status* status) override;
|
||||
|
||||
bool SetVideoMode(const VideoMode& mode, CS_Status* status) override;
|
||||
bool SetPixelFormat(VideoMode::PixelFormat pixelFormat,
|
||||
CS_Status* status) override;
|
||||
bool SetResolution(int width, int height, CS_Status* status) override;
|
||||
bool SetFPS(int fps, CS_Status* status) override;
|
||||
|
||||
void NumSinksChanged() override;
|
||||
void NumSinksEnabledChanged() override;
|
||||
|
||||
void ProcessFrame(IMFSample* sample, const VideoMode& mode);
|
||||
void PostRequestNewFrame();
|
||||
|
||||
std::string GetPath() { return m_path; }
|
||||
|
||||
// Messages passed to/from camera thread
|
||||
struct Message {
|
||||
enum Kind {
|
||||
kNone = 0,
|
||||
kCmdSetMode,
|
||||
kCmdSetPixelFormat,
|
||||
kCmdSetResolution,
|
||||
kCmdSetFPS,
|
||||
kCmdSetProperty,
|
||||
kCmdSetPropertyStr,
|
||||
kNumSinksChanged, // no response
|
||||
kNumSinksEnabledChanged, // no response
|
||||
// Responses
|
||||
kOk,
|
||||
kError
|
||||
};
|
||||
|
||||
explicit Message(Kind kind_)
|
||||
: kind(kind_), from(std::this_thread::get_id()) {}
|
||||
|
||||
Kind kind;
|
||||
int data[4];
|
||||
std::string dataStr;
|
||||
std::thread::id from;
|
||||
};
|
||||
|
||||
protected:
|
||||
std::unique_ptr<PropertyImpl> CreateEmptyProperty(
|
||||
const wpi::Twine& name) const override;
|
||||
|
||||
// Cache properties. Immediately successful if properties are already cached.
|
||||
// If they are not, tries to connect to the camera to do so; returns false and
|
||||
// sets status to CS_SOURCE_IS_DISCONNECTED if that too fails.
|
||||
bool CacheProperties(CS_Status* status) const override;
|
||||
|
||||
private:
|
||||
// The camera processing thread
|
||||
void CameraThreadMain();
|
||||
|
||||
LRESULT PumpMain(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
bool CheckDeviceChange(WPARAM wParam, DEV_BROADCAST_HDR* pHdr,
|
||||
bool* connected);
|
||||
|
||||
// Functions used by CameraThreadMain()
|
||||
void DeviceDisconnect();
|
||||
bool DeviceConnect();
|
||||
bool DeviceStreamOn();
|
||||
bool DeviceStreamOff();
|
||||
CS_StatusValue DeviceSetMode();
|
||||
void DeviceCacheMode();
|
||||
void DeviceCacheProperty(std::unique_ptr<UsbCameraProperty> rawProp,
|
||||
IMFSourceReader* sourceReader);
|
||||
void DeviceCacheProperties();
|
||||
void DeviceCacheVideoModes();
|
||||
template <typename TagProperty, typename IAM>
|
||||
void DeviceAddProperty(const wpi::Twine& name_, TagProperty tag,
|
||||
IAM* pProcAmp);
|
||||
|
||||
ComPtr<IMFMediaType> DeviceCheckModeValid(const VideoMode& toCheck);
|
||||
|
||||
// Command helper functions
|
||||
CS_StatusValue DeviceProcessCommand(std::unique_lock<wpi::mutex>& lock,
|
||||
Message::Kind msgKind,
|
||||
const Message* msg);
|
||||
CS_StatusValue DeviceCmdSetMode(std::unique_lock<wpi::mutex>& lock,
|
||||
const Message& msg);
|
||||
CS_StatusValue DeviceCmdSetProperty(std::unique_lock<wpi::mutex>& lock,
|
||||
const Message& msg);
|
||||
|
||||
// Property helper functions
|
||||
int RawToPercentage(const UsbCameraProperty& rawProp, int rawValue);
|
||||
int PercentageToRaw(const UsbCameraProperty& rawProp, int percentValue);
|
||||
|
||||
//
|
||||
// Variables only used within camera thread
|
||||
//
|
||||
bool m_streaming{false};
|
||||
bool m_wasStreaming{false};
|
||||
bool m_modeSet{false};
|
||||
int m_connectVerbose{1};
|
||||
bool m_deviceValid{false};
|
||||
|
||||
ComPtr<IMFMediaSource> m_mediaSource;
|
||||
ComPtr<IMFSourceReader> m_sourceReader;
|
||||
ComPtr<SourceReaderCB> m_imageCallback;
|
||||
std::unique_ptr<cs::WindowsMessagePump> m_messagePump;
|
||||
ComPtr<IMFMediaType> m_currentMode;
|
||||
|
||||
//
|
||||
// Path never changes, so not protected by mutex.
|
||||
//
|
||||
std::string m_path;
|
||||
|
||||
std::wstring m_widePath;
|
||||
int m_deviceId;
|
||||
|
||||
std::vector<std::pair<VideoMode, ComPtr<IMFMediaType>>> m_windowsVideoModes;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_USBCAMERAIMPL_H_
|
||||
189
cscore/src/main/native/windows/UsbCameraProperty.cpp
Normal file
189
cscore/src/main/native/windows/UsbCameraProperty.cpp
Normal file
@@ -0,0 +1,189 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "UsbCameraProperty.h"
|
||||
|
||||
#include "ComPtr.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
UsbCameraProperty::UsbCameraProperty(const wpi::Twine& name_,
|
||||
tagVideoProcAmpProperty tag, bool autoProp,
|
||||
IAMVideoProcAmp* pProcAmp, bool* isValid)
|
||||
: PropertyImpl{autoProp ? name_ + "_auto" : name_} {
|
||||
this->tagVideoProc = tag;
|
||||
this->isControlProperty = false;
|
||||
this->isAutoProp = autoProp;
|
||||
long paramVal, paramFlag; // NOLINT(runtime/int)
|
||||
HRESULT hr;
|
||||
long minVal, maxVal, stepVal; // NOLINT(runtime/int)
|
||||
hr = pProcAmp->GetRange(tag, &minVal, &maxVal, &stepVal, ¶mVal,
|
||||
¶mFlag); // Unable to get the property, trying to
|
||||
// return default value
|
||||
if (SUCCEEDED(hr)) {
|
||||
minimum = minVal;
|
||||
maximum = maxVal;
|
||||
hasMaximum = true;
|
||||
hasMinimum = true;
|
||||
defaultValue = paramVal;
|
||||
step = stepVal;
|
||||
value = paramVal;
|
||||
propKind = CS_PropertyKind::CS_PROP_INTEGER;
|
||||
*isValid = true;
|
||||
} else {
|
||||
*isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool UsbCameraProperty::DeviceGet(std::unique_lock<wpi::mutex>& lock,
|
||||
IAMVideoProcAmp* pProcAmp) {
|
||||
if (!pProcAmp) return true;
|
||||
|
||||
lock.unlock();
|
||||
long newValue = 0, paramFlag = 0; // NOLINT(runtime/int)
|
||||
if (SUCCEEDED(pProcAmp->Get(tagVideoProc, &newValue, ¶mFlag))) {
|
||||
lock.lock();
|
||||
value = newValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
IAMVideoProcAmp* pProcAmp) const {
|
||||
return DeviceSet(lock, pProcAmp, value);
|
||||
}
|
||||
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
IAMVideoProcAmp* pProcAmp,
|
||||
int newValue) const {
|
||||
if (!pProcAmp) return true;
|
||||
|
||||
lock.unlock();
|
||||
if (SUCCEEDED(
|
||||
pProcAmp->Set(tagVideoProc, newValue, VideoProcAmp_Flags_Manual))) {
|
||||
lock.lock();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
UsbCameraProperty::UsbCameraProperty(const wpi::Twine& name_,
|
||||
tagCameraControlProperty tag,
|
||||
bool autoProp, IAMCameraControl* pProcAmp,
|
||||
bool* isValid)
|
||||
: PropertyImpl{autoProp ? name_ + "_auto" : name_} {
|
||||
this->tagCameraControl = tag;
|
||||
this->isControlProperty = true;
|
||||
this->isAutoProp = autoProp;
|
||||
long paramVal, paramFlag; // NOLINT(runtime/int)
|
||||
HRESULT hr;
|
||||
long minVal, maxVal, stepVal; // NOLINT(runtime/int)
|
||||
hr = pProcAmp->GetRange(tag, &minVal, &maxVal, &stepVal, ¶mVal,
|
||||
¶mFlag); // Unable to get the property, trying to
|
||||
// return default value
|
||||
if (SUCCEEDED(hr)) {
|
||||
minimum = minVal;
|
||||
maximum = maxVal;
|
||||
hasMaximum = true;
|
||||
hasMinimum = true;
|
||||
defaultValue = paramVal;
|
||||
step = stepVal;
|
||||
value = paramVal;
|
||||
propKind = CS_PropertyKind::CS_PROP_INTEGER;
|
||||
*isValid = true;
|
||||
} else {
|
||||
*isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool UsbCameraProperty::DeviceGet(std::unique_lock<wpi::mutex>& lock,
|
||||
IAMCameraControl* pProcAmp) {
|
||||
if (!pProcAmp) return true;
|
||||
|
||||
lock.unlock();
|
||||
long newValue = 0, paramFlag = 0; // NOLINT(runtime/int)
|
||||
if (SUCCEEDED(pProcAmp->Get(tagCameraControl, &newValue, ¶mFlag))) {
|
||||
lock.lock();
|
||||
value = newValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
IAMCameraControl* pProcAmp) const {
|
||||
return DeviceSet(lock, pProcAmp, value);
|
||||
}
|
||||
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
IAMCameraControl* pProcAmp,
|
||||
int newValue) const {
|
||||
if (!pProcAmp) return true;
|
||||
|
||||
lock.unlock();
|
||||
if (SUCCEEDED(pProcAmp->Set(tagCameraControl, newValue,
|
||||
CameraControl_Flags_Manual))) {
|
||||
lock.lock();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UsbCameraProperty::DeviceGet(std::unique_lock<wpi::mutex>& lock,
|
||||
IMFSourceReader* sourceReader) {
|
||||
if (!sourceReader) return true;
|
||||
|
||||
if (isControlProperty) {
|
||||
ComPtr<IAMCameraControl> pProcAmp;
|
||||
if (SUCCEEDED(sourceReader->GetServiceForStream(
|
||||
(DWORD)MF_SOURCE_READER_MEDIASOURCE, GUID_NULL,
|
||||
IID_PPV_ARGS(pProcAmp.GetAddressOf())))) {
|
||||
return DeviceGet(lock, pProcAmp.Get());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
ComPtr<IAMVideoProcAmp> pProcAmp;
|
||||
if (SUCCEEDED(sourceReader->GetServiceForStream(
|
||||
(DWORD)MF_SOURCE_READER_MEDIASOURCE, GUID_NULL,
|
||||
IID_PPV_ARGS(pProcAmp.GetAddressOf())))) {
|
||||
return DeviceGet(lock, pProcAmp.Get());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
IMFSourceReader* sourceReader) const {
|
||||
return DeviceSet(lock, sourceReader, value);
|
||||
}
|
||||
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
IMFSourceReader* sourceReader,
|
||||
int newValue) const {
|
||||
if (!sourceReader) return true;
|
||||
|
||||
if (isControlProperty) {
|
||||
ComPtr<IAMCameraControl> pProcAmp;
|
||||
if (SUCCEEDED(sourceReader->GetServiceForStream(
|
||||
(DWORD)MF_SOURCE_READER_MEDIASOURCE, GUID_NULL,
|
||||
IID_PPV_ARGS(pProcAmp.GetAddressOf())))) {
|
||||
return DeviceSet(lock, pProcAmp.Get(), newValue);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
ComPtr<IAMVideoProcAmp> pProcAmp;
|
||||
if (SUCCEEDED(sourceReader->GetServiceForStream(
|
||||
(DWORD)MF_SOURCE_READER_MEDIASOURCE, GUID_NULL,
|
||||
IID_PPV_ARGS(pProcAmp.GetAddressOf())))) {
|
||||
return DeviceSet(lock, pProcAmp.Get(), newValue);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
97
cscore/src/main/native/windows/UsbCameraProperty.h
Normal file
97
cscore/src/main/native/windows/UsbCameraProperty.h
Normal file
@@ -0,0 +1,97 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mfapi.h>
|
||||
#include <mfidl.h>
|
||||
#include <mfreadwrite.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <Dshow.h>
|
||||
#include <wpi/mutex.h>
|
||||
|
||||
#include "PropertyImpl.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
// Property data
|
||||
class UsbCameraProperty : public PropertyImpl {
|
||||
public:
|
||||
UsbCameraProperty() = default;
|
||||
explicit UsbCameraProperty(const wpi::Twine& name_) : PropertyImpl{name_} {}
|
||||
|
||||
// Software property constructor
|
||||
UsbCameraProperty(const wpi::Twine& name_, unsigned id_,
|
||||
CS_PropertyKind kind_, int minimum_, int maximum_,
|
||||
int step_, int defaultValue_, int value_)
|
||||
: PropertyImpl(name_, kind_, minimum_, maximum_, step_, defaultValue_,
|
||||
value_),
|
||||
device{false},
|
||||
id{id_} {}
|
||||
|
||||
// Normalized property constructor
|
||||
UsbCameraProperty(const wpi::Twine& name_, int rawIndex_,
|
||||
const UsbCameraProperty& rawProp, int defaultValue_,
|
||||
int value_)
|
||||
: PropertyImpl(name_, rawProp.propKind, 1, defaultValue_, value_),
|
||||
percentage{true},
|
||||
propPair{rawIndex_},
|
||||
id{rawProp.id},
|
||||
type{rawProp.type} {
|
||||
hasMinimum = true;
|
||||
minimum = 0;
|
||||
hasMaximum = true;
|
||||
maximum = 100;
|
||||
}
|
||||
|
||||
UsbCameraProperty(const wpi::Twine& name_, tagVideoProcAmpProperty tag,
|
||||
bool autoProp, IAMVideoProcAmp* pProcAmp, bool* isValid);
|
||||
|
||||
UsbCameraProperty(const wpi::Twine& name_, tagCameraControlProperty tag,
|
||||
bool autoProp, IAMCameraControl* pProcAmp, bool* isValid);
|
||||
|
||||
bool DeviceGet(std::unique_lock<wpi::mutex>& lock,
|
||||
IMFSourceReader* sourceReader);
|
||||
bool DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
IMFSourceReader* sourceReader) const;
|
||||
bool DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
IMFSourceReader* sourceReader, int newValue) const;
|
||||
|
||||
// If this is a device (rather than software) property
|
||||
bool device{true};
|
||||
bool isAutoProp{true};
|
||||
|
||||
bool isControlProperty{false};
|
||||
tagVideoProcAmpProperty tagVideoProc;
|
||||
tagCameraControlProperty tagCameraControl;
|
||||
|
||||
// If this is a percentage (rather than raw) property
|
||||
bool percentage{false};
|
||||
|
||||
// If not 0, index of corresponding raw/percentage property
|
||||
int propPair{0};
|
||||
|
||||
unsigned id{0}; // implementation-level id
|
||||
int type{0}; // implementation type, not CS_PropertyKind!
|
||||
|
||||
private:
|
||||
bool DeviceGet(std::unique_lock<wpi::mutex>& lock,
|
||||
IAMCameraControl* pProcAmp);
|
||||
bool DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
IAMCameraControl* pProcAmp) const;
|
||||
bool DeviceSet(std::unique_lock<wpi::mutex>& lock, IAMCameraControl* pProcAmp,
|
||||
int newValue) const;
|
||||
|
||||
bool DeviceGet(std::unique_lock<wpi::mutex>& lock, IAMVideoProcAmp* pProcAmp);
|
||||
bool DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
IAMVideoProcAmp* pProcAmp) const;
|
||||
bool DeviceSet(std::unique_lock<wpi::mutex>& lock, IAMVideoProcAmp* pProcAmp,
|
||||
int newValue) const;
|
||||
};
|
||||
} // namespace cs
|
||||
152
cscore/src/main/native/windows/WindowsMessagePump.cpp
Normal file
152
cscore/src/main/native/windows/WindowsMessagePump.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "WindowsMessagePump.h"
|
||||
|
||||
#include <ks.h>
|
||||
#include <ksmedia.h>
|
||||
#include <mfapi.h>
|
||||
#include <mfidl.h>
|
||||
#include <windows.h>
|
||||
#include <windowsx.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <Dbt.h>
|
||||
|
||||
#pragma comment(lib, "Mfplat.lib")
|
||||
#pragma comment(lib, "Mf.lib")
|
||||
#pragma comment(lib, "mfuuid.lib")
|
||||
#pragma comment(lib, "Ole32.lib")
|
||||
#pragma comment(lib, "User32.lib")
|
||||
|
||||
namespace cs {
|
||||
|
||||
static LRESULT CALLBACK pWndProc(HWND hwnd, UINT uiMsg, WPARAM wParam,
|
||||
LPARAM lParam) {
|
||||
WindowsMessagePump* pumpContainer;
|
||||
// Our "this" parameter is passed only during WM_CREATE
|
||||
// If it is create, store in our user parameter
|
||||
// Otherwise grab from our user parameter
|
||||
if (uiMsg == WM_CREATE) {
|
||||
CREATESTRUCT* pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
|
||||
pumpContainer =
|
||||
reinterpret_cast<WindowsMessagePump*>(pCreate->lpCreateParams);
|
||||
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pumpContainer);
|
||||
SetWindowPos(hwnd, HWND_MESSAGE, 0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
|
||||
} else {
|
||||
pumpContainer = reinterpret_cast<WindowsMessagePump*>(
|
||||
GetWindowLongPtr(hwnd, GWLP_USERDATA));
|
||||
}
|
||||
|
||||
// Run the callback
|
||||
bool hasCalledBack = false;
|
||||
LRESULT result;
|
||||
|
||||
if (pumpContainer) {
|
||||
hasCalledBack = true;
|
||||
result = pumpContainer->m_callback(hwnd, uiMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
// Handle a close message
|
||||
if (uiMsg == WM_CLOSE) {
|
||||
return HANDLE_WM_CLOSE(hwnd, 0, 0, [](HWND) { PostQuitMessage(0); });
|
||||
}
|
||||
|
||||
// Return message, otherwise return the base handler
|
||||
if (hasCalledBack) {
|
||||
return result;
|
||||
}
|
||||
return DefWindowProc(hwnd, uiMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct ClassHolder {
|
||||
HINSTANCE current_instance;
|
||||
WNDCLASSEX wx;
|
||||
const char* class_name = "DUMMY_CLASS";
|
||||
ClassHolder() {
|
||||
current_instance = (HINSTANCE)GetModuleHandle(NULL);
|
||||
wx = {};
|
||||
wx.cbSize = sizeof(WNDCLASSEX);
|
||||
wx.lpfnWndProc = pWndProc; // function which will handle messages
|
||||
wx.hInstance = current_instance;
|
||||
wx.lpszClassName = class_name;
|
||||
RegisterClassEx(&wx);
|
||||
}
|
||||
~ClassHolder() { UnregisterClass(class_name, current_instance); }
|
||||
};
|
||||
} // namespace
|
||||
|
||||
static std::shared_ptr<ClassHolder> GetClassHolder() {
|
||||
static std::shared_ptr<ClassHolder> clsHolder =
|
||||
std::make_shared<ClassHolder>();
|
||||
return clsHolder;
|
||||
}
|
||||
|
||||
WindowsMessagePump::WindowsMessagePump(
|
||||
std::function<LRESULT(HWND, UINT, WPARAM, LPARAM)> callback) {
|
||||
m_callback = callback;
|
||||
auto handle = CreateEvent(NULL, true, false, NULL);
|
||||
m_mainThread = std::thread([=]() { ThreadMain(handle); });
|
||||
auto waitResult = WaitForSingleObject(handle, 1000);
|
||||
if (waitResult == WAIT_OBJECT_0) {
|
||||
CloseHandle(handle);
|
||||
}
|
||||
}
|
||||
|
||||
WindowsMessagePump::~WindowsMessagePump() {
|
||||
auto res = SendMessage(hwnd, WM_CLOSE, NULL, NULL);
|
||||
if (m_mainThread.joinable()) m_mainThread.join();
|
||||
}
|
||||
|
||||
void WindowsMessagePump::ThreadMain(HANDLE eventHandle) {
|
||||
// Initialize COM
|
||||
CoInitializeEx(0, COINIT_MULTITHREADED);
|
||||
// Initialize MF
|
||||
MFStartup(MF_VERSION);
|
||||
|
||||
auto classHolder = GetClassHolder();
|
||||
hwnd = CreateWindowEx(0, classHolder->class_name, "dummy_name", 0, 0, 0, 0, 0,
|
||||
HWND_MESSAGE, NULL, NULL, this);
|
||||
|
||||
// Register for device notifications
|
||||
HDEVNOTIFY g_hdevnotify = NULL;
|
||||
HDEVNOTIFY g_hdevnotify2 = NULL;
|
||||
|
||||
DEV_BROADCAST_DEVICEINTERFACE di = {0};
|
||||
di.dbcc_size = sizeof(di);
|
||||
di.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
|
||||
di.dbcc_classguid = KSCATEGORY_CAPTURE;
|
||||
|
||||
g_hdevnotify =
|
||||
RegisterDeviceNotification(hwnd, &di, DEVICE_NOTIFY_WINDOW_HANDLE);
|
||||
|
||||
DEV_BROADCAST_DEVICEINTERFACE di2 = {0};
|
||||
di2.dbcc_size = sizeof(di2);
|
||||
di2.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
|
||||
di2.dbcc_classguid = KSCATEGORY_VIDEO_CAMERA;
|
||||
|
||||
g_hdevnotify2 =
|
||||
RegisterDeviceNotification(hwnd, &di2, DEVICE_NOTIFY_WINDOW_HANDLE);
|
||||
|
||||
SetEvent(eventHandle);
|
||||
|
||||
MSG Msg;
|
||||
while (GetMessage(&Msg, NULL, 0, 0) > 0) {
|
||||
TranslateMessage(&Msg);
|
||||
DispatchMessage(&Msg);
|
||||
}
|
||||
UnregisterDeviceNotification(g_hdevnotify);
|
||||
UnregisterDeviceNotification(g_hdevnotify2);
|
||||
|
||||
MFShutdown();
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
66
cscore/src/main/native/windows/WindowsMessagePump.h
Normal file
66
cscore/src/main/native/windows/WindowsMessagePump.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
|
||||
namespace cs {
|
||||
class WindowsMessagePump {
|
||||
public:
|
||||
WindowsMessagePump(
|
||||
std::function<LRESULT(HWND, UINT, WPARAM, LPARAM)> callback);
|
||||
~WindowsMessagePump();
|
||||
|
||||
friend LRESULT CALLBACK pWndProc(HWND hwnd, UINT uiMsg, WPARAM wParam,
|
||||
LPARAM lParam);
|
||||
|
||||
template <typename RetVal = LRESULT, typename FirstParam = WPARAM,
|
||||
typename SecondParam = LPARAM>
|
||||
RetVal SendWindowMessage(UINT msg, FirstParam wParam, SecondParam lParam) {
|
||||
static_assert(sizeof(FirstParam) <= sizeof(WPARAM),
|
||||
"First Parameter Does Not Fit");
|
||||
static_assert(sizeof(SecondParam) <= sizeof(LPARAM),
|
||||
"Second Parameter Does Not Fit");
|
||||
static_assert(sizeof(RetVal) <= sizeof(LRESULT),
|
||||
"Return Value Does Not Fit");
|
||||
WPARAM firstToSend = 0;
|
||||
LPARAM secondToSend = 0;
|
||||
std::memcpy(&firstToSend, &wParam, sizeof(FirstParam));
|
||||
std::memcpy(&secondToSend, &lParam, sizeof(SecondParam));
|
||||
LRESULT result = SendMessage(hwnd, msg, firstToSend, secondToSend);
|
||||
RetVal toReturn;
|
||||
std::memset(&toReturn, 0, sizeof(RetVal));
|
||||
std::memcpy(&toReturn, &result, sizeof(RetVal));
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
template <typename FirstParam = WPARAM, typename SecondParam = LPARAM>
|
||||
BOOL PostWindowMessage(UINT msg, FirstParam wParam, SecondParam lParam) {
|
||||
static_assert(sizeof(FirstParam) <= sizeof(WPARAM),
|
||||
"First Parameter Does Not Fit");
|
||||
static_assert(sizeof(SecondParam) <= sizeof(LPARAM),
|
||||
"Second Parameter Does Not Fit");
|
||||
WPARAM firstToSend = 0;
|
||||
LPARAM secondToSend = 0;
|
||||
std::memcpy(&firstToSend, &wParam, sizeof(FirstParam));
|
||||
std::memcpy(&secondToSend, &lParam, sizeof(SecondParam));
|
||||
return PostMessage(hwnd, msg, firstToSend, secondToSend);
|
||||
}
|
||||
|
||||
private:
|
||||
void ThreadMain(HANDLE eventHandle);
|
||||
|
||||
HWND hwnd;
|
||||
std::function<LRESULT(HWND, UINT, WPARAM, LPARAM)> m_callback;
|
||||
|
||||
std::thread m_mainThread;
|
||||
};
|
||||
} // namespace cs
|
||||
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id 'org.ysb33r.doxygen' version '0.4'
|
||||
id 'java'
|
||||
id "org.ysb33r.doxygen" version "0.5"
|
||||
}
|
||||
|
||||
evaluationDependsOn(':wpiutil')
|
||||
@@ -112,6 +112,19 @@ task generateJavaDocs(type: Javadoc) {
|
||||
source configurations.javaSource.collect { zipTree(it) }
|
||||
include '**/*.java'
|
||||
failOnError = true
|
||||
|
||||
title = "WPILib API $pubVersion"
|
||||
ext.entryPoint = "$destinationDir/index.html"
|
||||
|
||||
if (JavaVersion.current().isJava11Compatible()) {
|
||||
options.addBooleanOption('-no-module-directories', true)
|
||||
doLast {
|
||||
// This is a work-around for https://bugs.openjdk.java.net/browse/JDK-8211194. Can be removed once that issue is fixed on JDK's side
|
||||
// Since JDK 11, package-list is missing from javadoc output files and superseded by element-list file, but a lot of external tools still need it
|
||||
// Here we generate this file manually
|
||||
new File(destinationDir, 'package-list').text = new File(destinationDir, 'element-list').text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("zipJavaDocs", Zip) {
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.0-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
2
gradlew
vendored
2
gradlew
vendored
@@ -28,7 +28,7 @@ APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
2
gradlew.bat
vendored
2
gradlew.bat
vendored
@@ -14,7 +14,7 @@ set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
@@ -26,7 +26,7 @@ file(GLOB
|
||||
hal_shared_native_src src/main/native/cpp/cpp/*.cpp
|
||||
hal_shared_native_src src/main/native/cpp/handles/*.cpp
|
||||
hal_sim_native_src src/main/native/sim/*.cpp
|
||||
hal_sim_native_src src/main/native/sim/MockData/*.cpp)
|
||||
hal_sim_native_src src/main/native/sim/mockdata/*.cpp)
|
||||
add_library(hal ${hal_shared_native_src})
|
||||
set_target_properties(hal PROPERTIES DEBUG_POSTFIX "d")
|
||||
|
||||
|
||||
@@ -179,5 +179,25 @@ model {
|
||||
'_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
|
||||
'_TI5?AVfailure']
|
||||
}
|
||||
halJNI(ExportsConfig) {
|
||||
x86SymbolFilter = { symbols ->
|
||||
def retList = []
|
||||
symbols.each { symbol ->
|
||||
if (symbol.startsWith('HAL_') || symbol.startsWith('HALSIM_')) {
|
||||
retList << symbol
|
||||
}
|
||||
}
|
||||
return retList
|
||||
}
|
||||
x64SymbolFilter = { symbols ->
|
||||
def retList = []
|
||||
symbols.each { symbol ->
|
||||
if (symbol.startsWith('HAL_') || symbol.startsWith('HALSIM_')) {
|
||||
retList << symbol
|
||||
}
|
||||
}
|
||||
return retList
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,3 +80,6 @@ kResourceType_Shuffleboard = 78
|
||||
kResourceType_CAN = 79
|
||||
kResourceType_DigilentDMC60 = 80
|
||||
kResourceType_PWMVictorSPX = 81
|
||||
kResourceType_RevSparkMaxPWM = 82
|
||||
kResourceType_RevSparkMaxCAN = 83
|
||||
kResourceType_ADIS16470 = 84
|
||||
|
||||
@@ -25,9 +25,9 @@ public class InterruptJNI extends JNIWrapper {
|
||||
|
||||
public static native void disableInterrupts(int interruptHandle);
|
||||
|
||||
public static native double readInterruptRisingTimestamp(int interruptHandle);
|
||||
public static native long readInterruptRisingTimestamp(int interruptHandle);
|
||||
|
||||
public static native double readInterruptFallingTimestamp(int interruptHandle);
|
||||
public static native long readInterruptFallingTimestamp(int interruptHandle);
|
||||
|
||||
public static native void requestInterrupts(int interruptHandle, int digitalSourceHandle,
|
||||
int analogTriggerType);
|
||||
|
||||
@@ -57,7 +57,7 @@ public class SPIJNI extends JNIWrapper {
|
||||
public static native int spiReadAutoReceivedData(int port, ByteBuffer buffer, int numToRead,
|
||||
double timeout);
|
||||
|
||||
public static native int spiReadAutoReceivedData(int port, byte[] buffer, int numToRead,
|
||||
public static native int spiReadAutoReceivedData(int port, int[] buffer, int numToRead,
|
||||
double timeout);
|
||||
|
||||
public static native int spiGetAutoDroppedCount(int port);
|
||||
|
||||
@@ -75,6 +75,9 @@ public class DriverStationSim {
|
||||
public void setDsAttached(boolean dsAttached) {
|
||||
DriverStationDataJNI.setDsAttached(dsAttached);
|
||||
}
|
||||
public void notifyNewData() {
|
||||
DriverStationDataJNI.notifyNewData();
|
||||
}
|
||||
|
||||
public void resetData() {
|
||||
DriverStationDataJNI.resetData();
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
package edu.wpi.first.hal.sim;
|
||||
|
||||
public interface SpiReadAutoReceiveBufferCallback {
|
||||
int callback(String name, byte[] buffer, int numToRead);
|
||||
int callback(String name, int[] buffer, int numToRead);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ using namespace hal;
|
||||
|
||||
namespace {
|
||||
struct Receives {
|
||||
uint64_t lastTimeStamp;
|
||||
uint32_t lastTimeStamp;
|
||||
uint8_t data[8];
|
||||
uint8_t length;
|
||||
};
|
||||
@@ -40,30 +40,13 @@ struct CANStorage {
|
||||
static UnlimitedHandleResource<HAL_CANHandle, CANStorage, HAL_HandleEnum::CAN>*
|
||||
canHandles;
|
||||
|
||||
static std::atomic_bool HasFixedTime{false};
|
||||
static uint64_t timeSpanDiff;
|
||||
|
||||
static void CheckDeltaTime() {
|
||||
if (HasFixedTime) return;
|
||||
HasFixedTime = true;
|
||||
|
||||
// TODO: Fix locking
|
||||
static uint32_t GetPacketBaseTime() {
|
||||
timespec t;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t);
|
||||
|
||||
int32_t status = 0;
|
||||
uint64_t fpgaTime = HAL_GetFPGATime(&status);
|
||||
|
||||
// Convert t to microseconds
|
||||
uint64_t us = t.tv_sec * 1000000 + t.tv_nsec / 1000;
|
||||
|
||||
timeSpanDiff =
|
||||
us - fpgaTime; // This assumes CLOCK_MONOTONIC is greater then FPGA Time.
|
||||
}
|
||||
|
||||
static inline uint64_t ConvertToFPGATime(uint32_t canMs) {
|
||||
uint64_t canMsToUs = canMs * 1000;
|
||||
return canMsToUs - timeSpanDiff;
|
||||
// Convert t to milliseconds
|
||||
uint64_t ms = t.tv_sec * 1000ull + t.tv_nsec / 1000000ull;
|
||||
return ms & 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
namespace hal {
|
||||
@@ -89,7 +72,6 @@ HAL_CANHandle HAL_InitializeCAN(HAL_CANManufacturer manufacturer,
|
||||
int32_t deviceId, HAL_CANDeviceType deviceType,
|
||||
int32_t* status) {
|
||||
hal::init::CheckInit();
|
||||
CheckDeltaTime();
|
||||
auto can = std::make_shared<CANStorage>();
|
||||
|
||||
auto handle = canHandles->Allocate(can);
|
||||
@@ -189,17 +171,16 @@ void HAL_ReadCANPacketNew(HAL_CANHandle handle, int32_t apiId, uint8_t* data,
|
||||
uint32_t ts = 0;
|
||||
HAL_CAN_ReceiveMessage(&messageId, 0x1FFFFFFF, data, &dataSize, &ts, status);
|
||||
|
||||
uint64_t timestamp = ConvertToFPGATime(ts);
|
||||
|
||||
if (*status == 0) {
|
||||
std::lock_guard<wpi::mutex> lock(can->mapMutex);
|
||||
auto& msg = can->receives[messageId];
|
||||
msg.length = dataSize;
|
||||
msg.lastTimeStamp = timestamp;
|
||||
msg.lastTimeStamp = ts;
|
||||
// The NetComm call placed in data, copy into the msg
|
||||
std::memcpy(msg.data, data, dataSize);
|
||||
}
|
||||
*length = dataSize;
|
||||
*receivedTimestamp = timestamp;
|
||||
*receivedTimestamp = ts;
|
||||
}
|
||||
|
||||
void HAL_ReadCANPacketLatest(HAL_CANHandle handle, int32_t apiId, uint8_t* data,
|
||||
@@ -216,21 +197,21 @@ void HAL_ReadCANPacketLatest(HAL_CANHandle handle, int32_t apiId, uint8_t* data,
|
||||
uint32_t ts = 0;
|
||||
HAL_CAN_ReceiveMessage(&messageId, 0x1FFFFFFF, data, &dataSize, &ts, status);
|
||||
|
||||
uint64_t timestamp = ConvertToFPGATime(ts);
|
||||
|
||||
std::lock_guard<wpi::mutex> lock(can->mapMutex);
|
||||
if (*status == 0) {
|
||||
// fresh update
|
||||
auto& msg = can->receives[messageId];
|
||||
msg.length = dataSize;
|
||||
*length = dataSize;
|
||||
msg.lastTimeStamp = timestamp;
|
||||
*receivedTimestamp = timestamp;
|
||||
msg.lastTimeStamp = ts;
|
||||
*receivedTimestamp = ts;
|
||||
// The NetComm call placed in data, copy into the msg
|
||||
std::memcpy(msg.data, data, dataSize);
|
||||
} else {
|
||||
auto i = can->receives.find(messageId);
|
||||
if (i != can->receives.end()) {
|
||||
std::memcpy(i->second.data, data, i->second.length);
|
||||
// Read the data from the stored message into the output
|
||||
std::memcpy(data, i->second.data, i->second.length);
|
||||
*length = i->second.length;
|
||||
*receivedTimestamp = i->second.lastTimeStamp;
|
||||
*status = 0;
|
||||
@@ -253,29 +234,28 @@ void HAL_ReadCANPacketTimeout(HAL_CANHandle handle, int32_t apiId,
|
||||
uint32_t ts = 0;
|
||||
HAL_CAN_ReceiveMessage(&messageId, 0x1FFFFFFF, data, &dataSize, &ts, status);
|
||||
|
||||
uint64_t timestamp = ConvertToFPGATime(ts);
|
||||
|
||||
std::lock_guard<wpi::mutex> lock(can->mapMutex);
|
||||
if (*status == 0) {
|
||||
// fresh update
|
||||
auto& msg = can->receives[messageId];
|
||||
msg.length = dataSize;
|
||||
*length = dataSize;
|
||||
msg.lastTimeStamp = timestamp;
|
||||
*receivedTimestamp = timestamp;
|
||||
msg.lastTimeStamp = ts;
|
||||
*receivedTimestamp = ts;
|
||||
// The NetComm call placed in data, copy into the msg
|
||||
std::memcpy(msg.data, data, dataSize);
|
||||
} else {
|
||||
auto i = can->receives.find(messageId);
|
||||
if (i != can->receives.end()) {
|
||||
// Found, check if new enough
|
||||
uint64_t now = HAL_GetFPGATime(status);
|
||||
if (now - i->second.lastTimeStamp >
|
||||
static_cast<uint64_t>(timeoutMs) * 1000) {
|
||||
uint32_t now = GetPacketBaseTime();
|
||||
if (now - i->second.lastTimeStamp > static_cast<uint32_t>(timeoutMs)) {
|
||||
// Timeout, return bad status
|
||||
*status = HAL_CAN_TIMEOUT;
|
||||
return;
|
||||
}
|
||||
std::memcpy(i->second.data, data, i->second.length);
|
||||
// Read the data from the stored message into the output
|
||||
std::memcpy(data, i->second.data, i->second.length);
|
||||
*length = i->second.length;
|
||||
*receivedTimestamp = i->second.lastTimeStamp;
|
||||
*status = 0;
|
||||
@@ -299,14 +279,15 @@ void HAL_ReadCANPeriodicPacket(HAL_CANHandle handle, int32_t apiId,
|
||||
std::lock_guard<wpi::mutex> lock(can->mapMutex);
|
||||
auto i = can->receives.find(messageId);
|
||||
if (i != can->receives.end()) {
|
||||
uint64_t now = HAL_GetFPGATime(status);
|
||||
// Found, check if new enough
|
||||
if (now - i->second.lastTimeStamp <
|
||||
static_cast<uint64_t>(periodMs) * 1000) {
|
||||
*status = 0;
|
||||
std::memcpy(i->second.data, data, i->second.length);
|
||||
uint32_t now = GetPacketBaseTime();
|
||||
if (now - i->second.lastTimeStamp < static_cast<uint32_t>(periodMs)) {
|
||||
// Read the data from the stored message into the output
|
||||
std::memcpy(data, i->second.data, i->second.length);
|
||||
*length = i->second.length;
|
||||
*receivedTimestamp = i->second.lastTimeStamp;
|
||||
*status = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -315,29 +296,28 @@ void HAL_ReadCANPeriodicPacket(HAL_CANHandle handle, int32_t apiId,
|
||||
uint32_t ts = 0;
|
||||
HAL_CAN_ReceiveMessage(&messageId, 0x1FFFFFFF, data, &dataSize, &ts, status);
|
||||
|
||||
uint64_t timestamp = ConvertToFPGATime(ts);
|
||||
|
||||
std::lock_guard<wpi::mutex> lock(can->mapMutex);
|
||||
if (*status == 0) {
|
||||
// fresh update
|
||||
auto& msg = can->receives[messageId];
|
||||
msg.length = dataSize;
|
||||
*length = dataSize;
|
||||
msg.lastTimeStamp = timestamp;
|
||||
*receivedTimestamp = timestamp;
|
||||
msg.lastTimeStamp = ts;
|
||||
*receivedTimestamp = ts;
|
||||
// The NetComm call placed in data, copy into the msg
|
||||
std::memcpy(msg.data, data, dataSize);
|
||||
} else {
|
||||
auto i = can->receives.find(messageId);
|
||||
if (i != can->receives.end()) {
|
||||
// Found, check if new enough
|
||||
uint64_t now = HAL_GetFPGATime(status);
|
||||
if (now - i->second.lastTimeStamp >
|
||||
static_cast<uint64_t>(timeoutMs) * 1000) {
|
||||
uint32_t now = GetPacketBaseTime();
|
||||
if (now - i->second.lastTimeStamp > static_cast<uint32_t>(timeoutMs)) {
|
||||
// Timeout, return bad status
|
||||
*status = HAL_CAN_TIMEOUT;
|
||||
return;
|
||||
}
|
||||
std::memcpy(i->second.data, data, i->second.length);
|
||||
// Read the data from the stored message into the output
|
||||
std::memcpy(data, i->second.data, i->second.length);
|
||||
*length = i->second.length;
|
||||
*receivedTimestamp = i->second.lastTimeStamp;
|
||||
*status = 0;
|
||||
|
||||
@@ -207,6 +207,10 @@ const char* HAL_GetErrorMessage(int32_t code) {
|
||||
return HAL_SERIAL_PORT_ERROR_MESSAGE;
|
||||
case HAL_CAN_TIMEOUT:
|
||||
return HAL_CAN_TIMEOUT_MESSAGE;
|
||||
case ERR_FRCSystem_NetCommNotResponding:
|
||||
return ERR_FRCSystem_NetCommNotResponding_MESSAGE;
|
||||
case ERR_FRCSystem_NoDSConnection:
|
||||
return ERR_FRCSystem_NoDSConnection_MESSAGE;
|
||||
default:
|
||||
return "Unknown error status";
|
||||
}
|
||||
@@ -235,7 +239,7 @@ uint64_t HAL_GetFPGATime(int32_t* status) {
|
||||
*status = NiFpga_Status_ResourceNotInitialized;
|
||||
return 0;
|
||||
}
|
||||
|
||||
*status = 0;
|
||||
uint64_t upper1 = global->readLocalTimeUpper(status);
|
||||
uint32_t lower = global->readLocalTime(status);
|
||||
uint64_t upper2 = global->readLocalTimeUpper(status);
|
||||
|
||||
@@ -165,26 +165,26 @@ void HAL_DisableInterrupts(HAL_InterruptHandle interruptHandle,
|
||||
anInterrupt->manager->disable(status);
|
||||
}
|
||||
|
||||
double HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle,
|
||||
int32_t* status) {
|
||||
auto anInterrupt = interruptHandles->Get(interruptHandle);
|
||||
if (anInterrupt == nullptr) {
|
||||
*status = HAL_HANDLE_ERROR;
|
||||
return 0;
|
||||
}
|
||||
uint32_t timestamp = anInterrupt->anInterrupt->readRisingTimeStamp(status);
|
||||
return timestamp * 1e-6;
|
||||
}
|
||||
|
||||
double HAL_ReadInterruptFallingTimestamp(HAL_InterruptHandle interruptHandle,
|
||||
int64_t HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle,
|
||||
int32_t* status) {
|
||||
auto anInterrupt = interruptHandles->Get(interruptHandle);
|
||||
if (anInterrupt == nullptr) {
|
||||
*status = HAL_HANDLE_ERROR;
|
||||
return 0;
|
||||
}
|
||||
uint32_t timestamp = anInterrupt->anInterrupt->readRisingTimeStamp(status);
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
int64_t HAL_ReadInterruptFallingTimestamp(HAL_InterruptHandle interruptHandle,
|
||||
int32_t* status) {
|
||||
auto anInterrupt = interruptHandles->Get(interruptHandle);
|
||||
if (anInterrupt == nullptr) {
|
||||
*status = HAL_HANDLE_ERROR;
|
||||
return 0;
|
||||
}
|
||||
uint32_t timestamp = anInterrupt->anInterrupt->readFallingTimeStamp(status);
|
||||
return timestamp * 1e-6;
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
void HAL_RequestInterrupts(HAL_InterruptHandle interruptHandle,
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <wpi/mutex.h>
|
||||
|
||||
#include "HALInitializer.h"
|
||||
#include "PortsInternal.h"
|
||||
#include "hal/CANAPI.h"
|
||||
@@ -31,7 +33,7 @@ static constexpr int32_t StatusEnergy = 0x5D;
|
||||
|
||||
static constexpr int32_t Control1 = 0x70;
|
||||
|
||||
static constexpr int32_t TimeoutMs = 50;
|
||||
static constexpr int32_t TimeoutMs = 100;
|
||||
static constexpr int32_t StatusPeriodMs = 25;
|
||||
|
||||
/* encoder/decoders */
|
||||
@@ -106,9 +108,16 @@ union PdpStatusEnergy {
|
||||
} bits;
|
||||
};
|
||||
|
||||
static wpi::mutex pdpHandleMutex;
|
||||
static HAL_PDPHandle pdpHandles[kNumPDPModules];
|
||||
|
||||
namespace hal {
|
||||
namespace init {
|
||||
void InitializePDP() {}
|
||||
void InitializePDP() {
|
||||
for (int i = 0; i < kNumPDPModules; i++) {
|
||||
pdpHandles[i] = HAL_kInvalidHandle;
|
||||
}
|
||||
}
|
||||
} // namespace init
|
||||
} // namespace hal
|
||||
|
||||
@@ -121,6 +130,13 @@ HAL_PDPHandle HAL_InitializePDP(int32_t module, int32_t* status) {
|
||||
return HAL_kInvalidHandle;
|
||||
}
|
||||
|
||||
std::lock_guard<wpi::mutex> lock(pdpHandleMutex);
|
||||
|
||||
if (pdpHandles[module] != HAL_kInvalidHandle) {
|
||||
*status = 0;
|
||||
return pdpHandles[module];
|
||||
}
|
||||
|
||||
auto handle = HAL_InitializeCAN(manufacturer, module, deviceType, status);
|
||||
|
||||
if (*status != 0) {
|
||||
@@ -128,10 +144,21 @@ HAL_PDPHandle HAL_InitializePDP(int32_t module, int32_t* status) {
|
||||
return HAL_kInvalidHandle;
|
||||
}
|
||||
|
||||
pdpHandles[module] = handle;
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
void HAL_CleanPDP(HAL_PDPHandle handle) { HAL_CleanCAN(handle); }
|
||||
void HAL_CleanPDP(HAL_PDPHandle handle) {
|
||||
HAL_CleanCAN(handle);
|
||||
|
||||
for (int i = 0; i < kNumPDPModules; i++) {
|
||||
if (pdpHandles[i] == handle) {
|
||||
pdpHandles[i] = HAL_kInvalidHandle;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HAL_Bool HAL_CheckPDPModule(int32_t module) {
|
||||
return module < kNumPDPModules && module >= 0;
|
||||
|
||||
@@ -604,7 +604,7 @@ void HAL_ForceSPIAutoRead(HAL_SPIPort port, int32_t* status) {
|
||||
spiSystem->strobeAutoForceOne(status);
|
||||
}
|
||||
|
||||
int32_t HAL_ReadSPIAutoReceivedData(HAL_SPIPort port, uint8_t* buffer,
|
||||
int32_t HAL_ReadSPIAutoReceivedData(HAL_SPIPort port, uint32_t* buffer,
|
||||
int32_t numToRead, double timeout,
|
||||
int32_t* status) {
|
||||
std::lock_guard<wpi::mutex> lock(spiAutoMutex);
|
||||
|
||||
@@ -215,9 +215,11 @@ void SerialHelper::QueryHubPaths(int32_t* status) {
|
||||
if (matchString.equals(devNameRef)) continue;
|
||||
|
||||
// Search directories to get a list of system accessors
|
||||
// The directories we need are not symbolic, so we can safely
|
||||
// disable symbolic links.
|
||||
std::error_code ec;
|
||||
for (auto p = wpi::sys::fs::recursive_directory_iterator(
|
||||
"/sys/devices/soc0", ec);
|
||||
"/sys/devices/soc0", ec, false);
|
||||
p != wpi::sys::fs::recursive_directory_iterator(); p.increment(ec)) {
|
||||
if (ec) break;
|
||||
wpi::StringRef path{p->path()};
|
||||
|
||||
@@ -127,7 +127,7 @@ void ReportError(JNIEnv* env, int32_t status, bool doThrow) {
|
||||
ThrowUncleanStatusException(env, buf.c_str(), status);
|
||||
} else {
|
||||
std::string func;
|
||||
auto stack = GetJavaStackTrace(env, &func, "edu.wpi.first.wpilibj");
|
||||
auto stack = GetJavaStackTrace(env, &func, "edu.wpi.first");
|
||||
HAL_SendError(1, status, 0, message, func.c_str(), stack.c_str(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,9 +236,9 @@ Java_edu_wpi_first_hal_InterruptJNI_disableInterrupts
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_InterruptJNI
|
||||
* Method: readInterruptRisingTimestamp
|
||||
* Signature: (I)D
|
||||
* Signature: (I)J
|
||||
*/
|
||||
JNIEXPORT jdouble JNICALL
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_edu_wpi_first_hal_InterruptJNI_readInterruptRisingTimestamp
|
||||
(JNIEnv* env, jclass, jint interruptHandle)
|
||||
{
|
||||
@@ -248,7 +248,7 @@ Java_edu_wpi_first_hal_InterruptJNI_readInterruptRisingTimestamp
|
||||
<< "Interrupt Handle = " << (HAL_InterruptHandle)interruptHandle;
|
||||
|
||||
int32_t status = 0;
|
||||
jdouble timeStamp = HAL_ReadInterruptRisingTimestamp(
|
||||
jlong timeStamp = HAL_ReadInterruptRisingTimestamp(
|
||||
(HAL_InterruptHandle)interruptHandle, &status);
|
||||
|
||||
INTERRUPTJNI_LOG(logDEBUG) << "Status = " << status;
|
||||
@@ -259,9 +259,9 @@ Java_edu_wpi_first_hal_InterruptJNI_readInterruptRisingTimestamp
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_InterruptJNI
|
||||
* Method: readInterruptFallingTimestamp
|
||||
* Signature: (I)D
|
||||
* Signature: (I)J
|
||||
*/
|
||||
JNIEXPORT jdouble JNICALL
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_edu_wpi_first_hal_InterruptJNI_readInterruptFallingTimestamp
|
||||
(JNIEnv* env, jclass, jint interruptHandle)
|
||||
{
|
||||
@@ -271,7 +271,7 @@ Java_edu_wpi_first_hal_InterruptJNI_readInterruptFallingTimestamp
|
||||
<< "Interrupt Handle = " << (HAL_InterruptHandle)interruptHandle;
|
||||
|
||||
int32_t status = 0;
|
||||
jdouble timeStamp = HAL_ReadInterruptFallingTimestamp(
|
||||
jlong timeStamp = HAL_ReadInterruptFallingTimestamp(
|
||||
(HAL_InterruptHandle)interruptHandle, &status);
|
||||
|
||||
INTERRUPTJNI_LOG(logDEBUG) << "Status = " << status;
|
||||
|
||||
@@ -436,8 +436,8 @@ Java_edu_wpi_first_hal_SPIJNI_spiReadAutoReceivedData__ILjava_nio_ByteBuffer_2ID
|
||||
SPIJNI_LOG(logDEBUG) << "Port = " << port;
|
||||
SPIJNI_LOG(logDEBUG) << "NumToRead = " << numToRead;
|
||||
SPIJNI_LOG(logDEBUG) << "Timeout = " << timeout;
|
||||
uint8_t* recvBuf =
|
||||
reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(buffer));
|
||||
uint32_t* recvBuf =
|
||||
reinterpret_cast<uint32_t*>(env->GetDirectBufferAddress(buffer));
|
||||
int32_t status = 0;
|
||||
jint retval = HAL_ReadSPIAutoReceivedData(
|
||||
static_cast<HAL_SPIPort>(port), recvBuf, numToRead, timeout, &status);
|
||||
@@ -450,18 +450,18 @@ Java_edu_wpi_first_hal_SPIJNI_spiReadAutoReceivedData__ILjava_nio_ByteBuffer_2ID
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_SPIJNI
|
||||
* Method: spiReadAutoReceivedData
|
||||
* Signature: (I[BID)I
|
||||
* Signature: (I[IID)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_SPIJNI_spiReadAutoReceivedData__I_3BID
|
||||
(JNIEnv* env, jclass, jint port, jbyteArray buffer, jint numToRead,
|
||||
Java_edu_wpi_first_hal_SPIJNI_spiReadAutoReceivedData__I_3IID
|
||||
(JNIEnv* env, jclass, jint port, jintArray buffer, jint numToRead,
|
||||
jdouble timeout)
|
||||
{
|
||||
SPIJNI_LOG(logDEBUG) << "Calling SPIJNI spiReadAutoReceivedData";
|
||||
SPIJNI_LOG(logDEBUG) << "Port = " << port;
|
||||
SPIJNI_LOG(logDEBUG) << "NumToRead = " << numToRead;
|
||||
SPIJNI_LOG(logDEBUG) << "Timeout = " << timeout;
|
||||
wpi::SmallVector<uint8_t, 128> recvBuf;
|
||||
wpi::SmallVector<uint32_t, 128> recvBuf;
|
||||
recvBuf.resize(numToRead);
|
||||
int32_t status = 0;
|
||||
jint retval =
|
||||
@@ -471,8 +471,8 @@ Java_edu_wpi_first_hal_SPIJNI_spiReadAutoReceivedData__I_3BID
|
||||
SPIJNI_LOG(logDEBUG) << "Return = " << retval;
|
||||
if (!CheckStatus(env, status)) return retval;
|
||||
if (numToRead > 0) {
|
||||
env->SetByteArrayRegion(buffer, 0, numToRead,
|
||||
reinterpret_cast<const jbyte*>(recvBuf.data()));
|
||||
env->SetIntArrayRegion(buffer, 0, numToRead,
|
||||
reinterpret_cast<const jint*>(recvBuf.data()));
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,11 @@
|
||||
#define ERR_CANSessionMux_NotAllowed_MESSAGE "CAN: Not allowed"
|
||||
#define ERR_CANSessionMux_NotInitialized_MESSAGE "CAN: Not initialized"
|
||||
|
||||
#define ERR_FRCSystem_NetCommNotResponding_MESSAGE \
|
||||
"FRCSystem: NetComm not responding"
|
||||
#define ERR_FRCSystem_NoDSConnection_MESSAGE \
|
||||
"FRCSystem: No driver station connected"
|
||||
|
||||
#define SAMPLE_RATE_TOO_HIGH 1001
|
||||
#define SAMPLE_RATE_TOO_HIGH_MESSAGE \
|
||||
"HAL: Analog module sample rate is too high"
|
||||
|
||||
@@ -77,24 +77,28 @@ void HAL_DisableInterrupts(HAL_InterruptHandle interruptHandle,
|
||||
/**
|
||||
* Returns the timestamp for the rising interrupt that occurred most recently.
|
||||
*
|
||||
* This is in the same time domain as HAL_GetFPGATime().
|
||||
* This is in the same time domain as HAL_GetFPGATime(). It only contains the
|
||||
* bottom 32 bits of the timestamp. If your robot has been running for over 1
|
||||
* hour, you will need to fill in the upper 32 bits yourself.
|
||||
*
|
||||
* @param interruptHandle the interrupt handle
|
||||
* @return timestamp in seconds since FPGA Initialization
|
||||
* @return timestamp in microseconds since FPGA Initialization
|
||||
*/
|
||||
double HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle,
|
||||
int32_t* status);
|
||||
int64_t HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle,
|
||||
int32_t* status);
|
||||
|
||||
/**
|
||||
* Returns the timestamp for the falling interrupt that occurred most recently.
|
||||
*
|
||||
* This is in the same time domain as HAL_GetFPGATime().
|
||||
* This is in the same time domain as HAL_GetFPGATime(). It only contains the
|
||||
* bottom 32 bits of the timestamp. If your robot has been running for over 1
|
||||
* hour, you will need to fill in the upper 32 bits yourself.
|
||||
*
|
||||
* @param interruptHandle the interrupt handle
|
||||
* @return timestamp in seconds since FPGA Initialization
|
||||
* @return timestamp in microseconds since FPGA Initialization
|
||||
*/
|
||||
double HAL_ReadInterruptFallingTimestamp(HAL_InterruptHandle interruptHandle,
|
||||
int32_t* status);
|
||||
int64_t HAL_ReadInterruptFallingTimestamp(HAL_InterruptHandle interruptHandle,
|
||||
int32_t* status);
|
||||
|
||||
/**
|
||||
* Requests interrupts on a specific digital source.
|
||||
|
||||
@@ -218,16 +218,20 @@ void HAL_SetSPIAutoTransmitData(HAL_SPIPort port, const uint8_t* dataToSend,
|
||||
void HAL_ForceSPIAutoRead(HAL_SPIPort port, int32_t* status);
|
||||
|
||||
/**
|
||||
* Reads data received by the SPI accumulator.
|
||||
* Reads data received by the SPI accumulator. Each received data sequence
|
||||
* consists of a timestamp followed by the received data bytes, one byte per
|
||||
* word (in the least significant byte). The length of each received data
|
||||
* sequence is the same as the combined dataSize + zeroSize set in
|
||||
* HAL_SetSPIAutoTransmitData.
|
||||
*
|
||||
* @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4
|
||||
* for MXP.
|
||||
* @param buffer The buffer to store the data into.
|
||||
* @param numToRead The number of bytes to read.
|
||||
* @param numToRead The number of words to read.
|
||||
* @param timeout The read timeout (in seconds).
|
||||
* @return The number of bytes actually read.
|
||||
* @return The number of words actually read.
|
||||
*/
|
||||
int32_t HAL_ReadSPIAutoReceivedData(HAL_SPIPort port, uint8_t* buffer,
|
||||
int32_t HAL_ReadSPIAutoReceivedData(HAL_SPIPort port, uint32_t* buffer,
|
||||
int32_t numToRead, double timeout,
|
||||
int32_t* status);
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
typedef void (*HAL_SpiReadAutoReceiveBufferCallback)(const char* name,
|
||||
void* param,
|
||||
unsigned char* buffer,
|
||||
uint32_t* buffer,
|
||||
int32_t numToRead,
|
||||
int32_t* outputCount);
|
||||
|
||||
|
||||
@@ -103,6 +103,8 @@ class DriverStationSim {
|
||||
HALSIM_SetDriverStationDsAttached(dsAttached);
|
||||
}
|
||||
|
||||
void NotifyNewData() { HALSIM_NotifyDriverStationNewData(); }
|
||||
|
||||
void ResetData() { HALSIM_ResetDriverStationData(); }
|
||||
};
|
||||
} // namespace sim
|
||||
|
||||
@@ -41,13 +41,11 @@ struct CANStorage {
|
||||
static UnlimitedHandleResource<HAL_CANHandle, CANStorage, HAL_HandleEnum::CAN>*
|
||||
canHandles;
|
||||
|
||||
static void CheckDeltaTime() {
|
||||
// Noop on sim
|
||||
}
|
||||
|
||||
static inline uint64_t ConvertToFPGATime(uint32_t canMs) {
|
||||
uint64_t canMsToUs = canMs * 1000;
|
||||
return canMsToUs;
|
||||
static uint32_t GetPacketBaseTime() {
|
||||
int status = 0;
|
||||
auto basetime = HAL_GetFPGATime(&status);
|
||||
// us to ms
|
||||
return (basetime / 1000ull) & 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
namespace hal {
|
||||
@@ -83,7 +81,6 @@ HAL_CANHandle HAL_InitializeCAN(HAL_CANManufacturer manufacturer,
|
||||
int32_t deviceId, HAL_CANDeviceType deviceType,
|
||||
int32_t* status) {
|
||||
hal::init::CheckInit();
|
||||
CheckDeltaTime();
|
||||
auto can = std::make_shared<CANStorage>();
|
||||
|
||||
auto handle = canHandles->Allocate(can);
|
||||
@@ -183,17 +180,16 @@ void HAL_ReadCANPacketNew(HAL_CANHandle handle, int32_t apiId, uint8_t* data,
|
||||
uint32_t ts = 0;
|
||||
HAL_CAN_ReceiveMessage(&messageId, 0x1FFFFFFF, data, &dataSize, &ts, status);
|
||||
|
||||
uint64_t timestamp = ConvertToFPGATime(ts);
|
||||
|
||||
if (*status == 0) {
|
||||
std::lock_guard<wpi::mutex> lock(can->mapMutex);
|
||||
auto& msg = can->receives[messageId];
|
||||
msg.length = dataSize;
|
||||
msg.lastTimeStamp = timestamp;
|
||||
msg.lastTimeStamp = ts;
|
||||
// The NetComm call placed in data, copy into the msg
|
||||
std::memcpy(msg.data, data, dataSize);
|
||||
}
|
||||
*length = dataSize;
|
||||
*receivedTimestamp = timestamp;
|
||||
*receivedTimestamp = ts;
|
||||
}
|
||||
|
||||
void HAL_ReadCANPacketLatest(HAL_CANHandle handle, int32_t apiId, uint8_t* data,
|
||||
@@ -210,21 +206,21 @@ void HAL_ReadCANPacketLatest(HAL_CANHandle handle, int32_t apiId, uint8_t* data,
|
||||
uint32_t ts = 0;
|
||||
HAL_CAN_ReceiveMessage(&messageId, 0x1FFFFFFF, data, &dataSize, &ts, status);
|
||||
|
||||
uint64_t timestamp = ConvertToFPGATime(ts);
|
||||
|
||||
std::lock_guard<wpi::mutex> lock(can->mapMutex);
|
||||
if (*status == 0) {
|
||||
// fresh update
|
||||
auto& msg = can->receives[messageId];
|
||||
msg.length = dataSize;
|
||||
*length = dataSize;
|
||||
msg.lastTimeStamp = timestamp;
|
||||
*receivedTimestamp = timestamp;
|
||||
msg.lastTimeStamp = ts;
|
||||
*receivedTimestamp = ts;
|
||||
// The NetComm call placed in data, copy into the msg
|
||||
std::memcpy(msg.data, data, dataSize);
|
||||
} else {
|
||||
auto i = can->receives.find(messageId);
|
||||
if (i != can->receives.end()) {
|
||||
std::memcpy(i->second.data, data, i->second.length);
|
||||
// Read the data from the stored message into the output
|
||||
std::memcpy(data, i->second.data, i->second.length);
|
||||
*length = i->second.length;
|
||||
*receivedTimestamp = i->second.lastTimeStamp;
|
||||
*status = 0;
|
||||
@@ -247,29 +243,28 @@ void HAL_ReadCANPacketTimeout(HAL_CANHandle handle, int32_t apiId,
|
||||
uint32_t ts = 0;
|
||||
HAL_CAN_ReceiveMessage(&messageId, 0x1FFFFFFF, data, &dataSize, &ts, status);
|
||||
|
||||
uint64_t timestamp = ConvertToFPGATime(ts);
|
||||
|
||||
std::lock_guard<wpi::mutex> lock(can->mapMutex);
|
||||
if (*status == 0) {
|
||||
// fresh update
|
||||
auto& msg = can->receives[messageId];
|
||||
msg.length = dataSize;
|
||||
*length = dataSize;
|
||||
msg.lastTimeStamp = timestamp;
|
||||
*receivedTimestamp = timestamp;
|
||||
msg.lastTimeStamp = ts;
|
||||
*receivedTimestamp = ts;
|
||||
// The NetComm call placed in data, copy into the msg
|
||||
std::memcpy(msg.data, data, dataSize);
|
||||
} else {
|
||||
auto i = can->receives.find(messageId);
|
||||
if (i != can->receives.end()) {
|
||||
// Found, check if new enough
|
||||
uint64_t now = HAL_GetFPGATime(status);
|
||||
if (now - i->second.lastTimeStamp >
|
||||
static_cast<uint64_t>(timeoutMs) * 1000) {
|
||||
uint32_t now = GetPacketBaseTime();
|
||||
if (now - i->second.lastTimeStamp > static_cast<uint32_t>(timeoutMs)) {
|
||||
// Timeout, return bad status
|
||||
*status = HAL_CAN_TIMEOUT;
|
||||
return;
|
||||
}
|
||||
std::memcpy(i->second.data, data, i->second.length);
|
||||
// Read the data from the stored message into the output
|
||||
std::memcpy(data, i->second.data, i->second.length);
|
||||
*length = i->second.length;
|
||||
*receivedTimestamp = i->second.lastTimeStamp;
|
||||
*status = 0;
|
||||
@@ -286,20 +281,22 @@ void HAL_ReadCANPeriodicPacket(HAL_CANHandle handle, int32_t apiId,
|
||||
*status = HAL_HANDLE_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t messageId = CreateCANId(can.get(), apiId);
|
||||
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(can->mapMutex);
|
||||
auto i = can->receives.find(messageId);
|
||||
if (i != can->receives.end()) {
|
||||
uint64_t now = HAL_GetFPGATime(status);
|
||||
// Found, check if new enough
|
||||
if (now - i->second.lastTimeStamp <
|
||||
static_cast<uint64_t>(periodMs) * 1000) {
|
||||
uint32_t now = GetPacketBaseTime();
|
||||
if (now - i->second.lastTimeStamp < static_cast<uint32_t>(periodMs)) {
|
||||
*status = 0;
|
||||
std::memcpy(i->second.data, data, i->second.length);
|
||||
// Read the data from the stored message into the output
|
||||
std::memcpy(data, i->second.data, i->second.length);
|
||||
*length = i->second.length;
|
||||
*receivedTimestamp = i->second.lastTimeStamp;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,29 +305,28 @@ void HAL_ReadCANPeriodicPacket(HAL_CANHandle handle, int32_t apiId,
|
||||
uint32_t ts = 0;
|
||||
HAL_CAN_ReceiveMessage(&messageId, 0x1FFFFFFF, data, &dataSize, &ts, status);
|
||||
|
||||
uint64_t timestamp = ConvertToFPGATime(ts);
|
||||
|
||||
std::lock_guard<wpi::mutex> lock(can->mapMutex);
|
||||
if (*status == 0) {
|
||||
// fresh update
|
||||
auto& msg = can->receives[messageId];
|
||||
msg.length = dataSize;
|
||||
*length = dataSize;
|
||||
msg.lastTimeStamp = timestamp;
|
||||
*receivedTimestamp = timestamp;
|
||||
msg.lastTimeStamp = ts;
|
||||
*receivedTimestamp = ts;
|
||||
// The NetComm call placed in data, copy into the msg
|
||||
std::memcpy(msg.data, data, dataSize);
|
||||
} else {
|
||||
auto i = can->receives.find(messageId);
|
||||
if (i != can->receives.end()) {
|
||||
// Found, check if new enough
|
||||
uint64_t now = HAL_GetFPGATime(status);
|
||||
if (now - i->second.lastTimeStamp >
|
||||
static_cast<uint64_t>(timeoutMs) * 1000) {
|
||||
uint32_t now = GetPacketBaseTime();
|
||||
if (now - i->second.lastTimeStamp > static_cast<uint32_t>(timeoutMs)) {
|
||||
// Timeout, return bad status
|
||||
*status = HAL_CAN_TIMEOUT;
|
||||
return;
|
||||
}
|
||||
std::memcpy(i->second.data, data, i->second.length);
|
||||
// Read the data from the stored message into the output
|
||||
std::memcpy(data, i->second.data, i->second.length);
|
||||
*length = i->second.length;
|
||||
*receivedTimestamp = i->second.lastTimeStamp;
|
||||
*status = 0;
|
||||
|
||||
@@ -42,8 +42,8 @@ struct Interrupt {
|
||||
uint8_t index;
|
||||
HAL_AnalogTriggerType trigType;
|
||||
bool watcher;
|
||||
double risingTimestamp;
|
||||
double fallingTimestamp;
|
||||
int64_t risingTimestamp;
|
||||
int64_t fallingTimestamp;
|
||||
bool previousState;
|
||||
bool fireOnUp;
|
||||
bool fireOnDown;
|
||||
@@ -238,10 +238,10 @@ static int64_t WaitForInterruptDigital(HAL_InterruptHandle handle,
|
||||
// True => false, Falling
|
||||
if (interrupt->previousState) {
|
||||
// Set our return value and our timestamps
|
||||
interrupt->fallingTimestamp = hal::GetFPGATimestamp();
|
||||
interrupt->fallingTimestamp = hal::GetFPGATime();
|
||||
return 1 << (8 + interrupt->index);
|
||||
} else {
|
||||
interrupt->risingTimestamp = hal::GetFPGATimestamp();
|
||||
interrupt->risingTimestamp = hal::GetFPGATime();
|
||||
return 1 << (interrupt->index);
|
||||
}
|
||||
}
|
||||
@@ -302,10 +302,10 @@ static int64_t WaitForInterruptAnalog(HAL_InterruptHandle handle,
|
||||
// True => false, Falling
|
||||
if (interrupt->previousState) {
|
||||
// Set our return value and our timestamps
|
||||
interrupt->fallingTimestamp = hal::GetFPGATimestamp();
|
||||
interrupt->fallingTimestamp = hal::GetFPGATime();
|
||||
return 1 << (8 + interrupt->index);
|
||||
} else {
|
||||
interrupt->risingTimestamp = hal::GetFPGATimestamp();
|
||||
interrupt->risingTimestamp = hal::GetFPGATime();
|
||||
return 1 << (interrupt->index);
|
||||
}
|
||||
}
|
||||
@@ -350,12 +350,12 @@ static void ProcessInterruptDigitalAsynchronous(const char* name, void* param,
|
||||
int32_t mask = 0;
|
||||
if (interrupt->previousState) {
|
||||
interrupt->previousState = retVal;
|
||||
interrupt->fallingTimestamp = hal::GetFPGATimestamp();
|
||||
interrupt->fallingTimestamp = hal::GetFPGATime();
|
||||
mask = 1 << (8 + interrupt->index);
|
||||
if (!interrupt->fireOnDown) return;
|
||||
} else {
|
||||
interrupt->previousState = retVal;
|
||||
interrupt->risingTimestamp = hal::GetFPGATimestamp();
|
||||
interrupt->risingTimestamp = hal::GetFPGATime();
|
||||
mask = 1 << (interrupt->index);
|
||||
if (!interrupt->fireOnUp) return;
|
||||
}
|
||||
@@ -385,12 +385,12 @@ static void ProcessInterruptAnalogAsynchronous(const char* name, void* param,
|
||||
int mask = 0;
|
||||
if (interrupt->previousState) {
|
||||
interrupt->previousState = retVal;
|
||||
interrupt->fallingTimestamp = hal::GetFPGATimestamp();
|
||||
interrupt->fallingTimestamp = hal::GetFPGATime();
|
||||
if (!interrupt->fireOnDown) return;
|
||||
mask = 1 << (8 + interrupt->index);
|
||||
} else {
|
||||
interrupt->previousState = retVal;
|
||||
interrupt->risingTimestamp = hal::GetFPGATimestamp();
|
||||
interrupt->risingTimestamp = hal::GetFPGATime();
|
||||
if (!interrupt->fireOnUp) return;
|
||||
mask = 1 << (interrupt->index);
|
||||
}
|
||||
@@ -486,8 +486,8 @@ void HAL_DisableInterrupts(HAL_InterruptHandle interruptHandle,
|
||||
}
|
||||
interrupt->callbackId = -1;
|
||||
}
|
||||
double HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle,
|
||||
int32_t* status) {
|
||||
int64_t HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle,
|
||||
int32_t* status) {
|
||||
auto interrupt = interruptHandles->Get(interruptHandle);
|
||||
if (interrupt == nullptr) {
|
||||
*status = HAL_HANDLE_ERROR;
|
||||
@@ -496,8 +496,8 @@ double HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle,
|
||||
|
||||
return interrupt->risingTimestamp;
|
||||
}
|
||||
double HAL_ReadInterruptFallingTimestamp(HAL_InterruptHandle interruptHandle,
|
||||
int32_t* status) {
|
||||
int64_t HAL_ReadInterruptFallingTimestamp(HAL_InterruptHandle interruptHandle,
|
||||
int32_t* status) {
|
||||
auto interrupt = interruptHandles->Get(interruptHandle);
|
||||
if (interrupt == nullptr) {
|
||||
*status = HAL_HANDLE_ERROR;
|
||||
|
||||
@@ -54,7 +54,7 @@ void HAL_SetSPIAutoTransmitData(HAL_SPIPort port, const uint8_t* dataToSend,
|
||||
int32_t dataSize, int32_t zeroSize,
|
||||
int32_t* status) {}
|
||||
void HAL_ForceSPIAutoRead(HAL_SPIPort port, int32_t* status) {}
|
||||
int32_t HAL_ReadSPIAutoReceivedData(HAL_SPIPort port, uint8_t* buffer,
|
||||
int32_t HAL_ReadSPIAutoReceivedData(HAL_SPIPort port, uint32_t* buffer,
|
||||
int32_t numToRead, double timeout,
|
||||
int32_t* status) {
|
||||
return SimSPIData[port].ReadAutoReceivedData(buffer, numToRead, timeout,
|
||||
|
||||
@@ -69,7 +69,7 @@ jint SimOnLoad(JavaVM* vm, void* reserved) {
|
||||
|
||||
spiReadAutoReceiveBufferCallbackCallback =
|
||||
env->GetMethodID(spiReadAutoReceiveBufferCallbackCls, "callback",
|
||||
"(Ljava/lang/String;[BI)I");
|
||||
"(Ljava/lang/String;[II)I");
|
||||
if (!spiReadAutoReceiveBufferCallbackCallback) return JNI_ERR;
|
||||
|
||||
InitializeStore();
|
||||
|
||||
@@ -39,7 +39,7 @@ void SpiReadAutoReceiveBufferCallbackStore::create(JNIEnv* env, jobject obj) {
|
||||
}
|
||||
|
||||
int32_t SpiReadAutoReceiveBufferCallbackStore::performCallback(
|
||||
const char* name, unsigned char* buffer, int32_t numToRead) {
|
||||
const char* name, uint32_t* buffer, int32_t numToRead) {
|
||||
JNIEnv* env;
|
||||
JavaVM* vm = sim::GetJVM();
|
||||
bool didAttachThread = false;
|
||||
@@ -58,15 +58,14 @@ int32_t SpiReadAutoReceiveBufferCallbackStore::performCallback(
|
||||
wpi::outs().flush();
|
||||
}
|
||||
|
||||
auto toCallbackArr =
|
||||
MakeJByteArray(env, wpi::StringRef{reinterpret_cast<const char*>(buffer),
|
||||
static_cast<size_t>(numToRead)});
|
||||
auto toCallbackArr = MakeJIntArray(
|
||||
env, wpi::ArrayRef<uint32_t>{buffer, static_cast<size_t>(numToRead)});
|
||||
|
||||
jint ret = env->CallIntMethod(m_call, sim::GetBufferCallback(),
|
||||
MakeJString(env, name), toCallbackArr,
|
||||
(jint)numToRead);
|
||||
|
||||
jbyte* fromCallbackArr = reinterpret_cast<jbyte*>(
|
||||
jint* fromCallbackArr = reinterpret_cast<jint*>(
|
||||
env->GetPrimitiveArrayCritical(toCallbackArr, nullptr));
|
||||
|
||||
for (int i = 0; i < ret; i++) {
|
||||
@@ -106,7 +105,7 @@ SIM_JniHandle sim::AllocateSpiBufferCallback(
|
||||
|
||||
callbackStore->create(env, callback);
|
||||
|
||||
auto callbackFunc = [](const char* name, void* param, unsigned char* buffer,
|
||||
auto callbackFunc = [](const char* name, void* param, uint32_t* buffer,
|
||||
int32_t numToRead, int32_t* outputCount) {
|
||||
uintptr_t handleTmp = reinterpret_cast<uintptr_t>(param);
|
||||
SIM_JniHandle handle = static_cast<SIM_JniHandle>(handleTmp);
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace sim {
|
||||
class SpiReadAutoReceiveBufferCallbackStore {
|
||||
public:
|
||||
void create(JNIEnv* env, jobject obj);
|
||||
int32_t performCallback(const char* name, unsigned char* buffer,
|
||||
int32_t performCallback(const char* name, uint32_t* buffer,
|
||||
int32_t numToRead);
|
||||
void free(JNIEnv* env);
|
||||
void setCallbackId(int32_t id) { callbackId = id; }
|
||||
|
||||
@@ -41,7 +41,7 @@ void DriverStationData::ResetData() {
|
||||
test.Reset(false);
|
||||
eStop.Reset(false);
|
||||
fmsAttached.Reset(false);
|
||||
dsAttached.Reset(false);
|
||||
dsAttached.Reset(true);
|
||||
allianceStationId.Reset(static_cast<HAL_AllianceStationID>(0));
|
||||
matchTime.Reset(0.0);
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ class DriverStationData {
|
||||
SimDataValue<HAL_Bool, MakeBoolean, GetTestName> test{false};
|
||||
SimDataValue<HAL_Bool, MakeBoolean, GetEStopName> eStop{false};
|
||||
SimDataValue<HAL_Bool, MakeBoolean, GetFmsAttachedName> fmsAttached{false};
|
||||
SimDataValue<HAL_Bool, MakeBoolean, GetDsAttachedName> dsAttached{false};
|
||||
SimDataValue<HAL_Bool, MakeBoolean, GetDsAttachedName> dsAttached{true};
|
||||
SimDataValue<HAL_AllianceStationID, MakeAllianceStationIdValue,
|
||||
GetAllianceStationIdName>
|
||||
allianceStationId{static_cast<HAL_AllianceStationID>(0)};
|
||||
|
||||
@@ -46,7 +46,7 @@ int32_t SPIData::Transaction(const uint8_t* dataToSend, uint8_t* dataReceived,
|
||||
return size;
|
||||
}
|
||||
|
||||
int32_t SPIData::ReadAutoReceivedData(uint8_t* buffer, int32_t numToRead,
|
||||
int32_t SPIData::ReadAutoReceivedData(uint32_t* buffer, int32_t numToRead,
|
||||
double timeout, int32_t* status) {
|
||||
int32_t outputCount = 0;
|
||||
autoReceivedData(buffer, numToRead, &outputCount);
|
||||
|
||||
@@ -24,7 +24,7 @@ class SPIData {
|
||||
int32_t Write(const uint8_t* dataToSend, int32_t sendSize);
|
||||
int32_t Transaction(const uint8_t* dataToSend, uint8_t* dataReceived,
|
||||
int32_t size);
|
||||
int32_t ReadAutoReceivedData(uint8_t* buffer, int32_t numToRead,
|
||||
int32_t ReadAutoReceivedData(uint32_t* buffer, int32_t numToRead,
|
||||
double timeout, int32_t* status);
|
||||
|
||||
SimDataValue<HAL_Bool, MakeBoolean, GetInitializedName> initialized{false};
|
||||
|
||||
@@ -3,7 +3,6 @@ plugins {
|
||||
id 'application'
|
||||
id 'cpp'
|
||||
id 'visual-studio'
|
||||
id 'com.github.johnrengelman.shadow' version '2.0.3' apply false
|
||||
}
|
||||
|
||||
apply plugin: 'edu.wpi.first.NativeUtils'
|
||||
|
||||
@@ -21,6 +21,26 @@ model {
|
||||
'_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
|
||||
'_TI5?AVfailure']
|
||||
}
|
||||
ntcoreJNI(ExportsConfig) {
|
||||
x86SymbolFilter = { symbols ->
|
||||
def retList = []
|
||||
symbols.each { symbol ->
|
||||
if (symbol.startsWith('NT_')) {
|
||||
retList << symbol
|
||||
}
|
||||
}
|
||||
return retList
|
||||
}
|
||||
x64SymbolFilter = { symbols ->
|
||||
def retList = []
|
||||
symbols.each { symbol ->
|
||||
if (symbol.startsWith('NT_')) {
|
||||
retList << symbol
|
||||
}
|
||||
}
|
||||
return retList
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,14 +22,15 @@ using namespace nt;
|
||||
|
||||
void Dispatcher::StartServer(const Twine& persist_filename,
|
||||
const char* listen_address, unsigned int port) {
|
||||
std::string listen_address_copy(StringRef(listen_address).trim());
|
||||
DispatcherBase::StartServer(
|
||||
persist_filename,
|
||||
std::unique_ptr<wpi::NetworkAcceptor>(new wpi::TCPAcceptor(
|
||||
static_cast<int>(port), listen_address, m_logger)));
|
||||
static_cast<int>(port), listen_address_copy.c_str(), m_logger)));
|
||||
}
|
||||
|
||||
void Dispatcher::SetServer(const char* server_name, unsigned int port) {
|
||||
std::string server_name_copy(server_name);
|
||||
std::string server_name_copy(StringRef(server_name).trim());
|
||||
SetConnector([=]() -> std::unique_ptr<wpi::NetworkStream> {
|
||||
return wpi::TCPConnector::connect(server_name_copy.c_str(),
|
||||
static_cast<int>(port), m_logger, 1);
|
||||
@@ -40,7 +41,7 @@ void Dispatcher::SetServer(
|
||||
ArrayRef<std::pair<StringRef, unsigned int>> servers) {
|
||||
wpi::SmallVector<std::pair<std::string, int>, 16> servers_copy;
|
||||
for (const auto& server : servers)
|
||||
servers_copy.emplace_back(std::string{server.first},
|
||||
servers_copy.emplace_back(std::string{server.first.trim()},
|
||||
static_cast<int>(server.second));
|
||||
|
||||
SetConnector([=]() -> std::unique_ptr<wpi::NetworkStream> {
|
||||
@@ -94,7 +95,7 @@ void Dispatcher::SetServerTeam(unsigned int team, unsigned int port) {
|
||||
}
|
||||
|
||||
void Dispatcher::SetServerOverride(const char* server_name, unsigned int port) {
|
||||
std::string server_name_copy(server_name);
|
||||
std::string server_name_copy(StringRef(server_name).trim());
|
||||
SetConnectorOverride([=]() -> std::unique_ptr<wpi::NetworkStream> {
|
||||
return wpi::TCPConnector::connect(server_name_copy.c_str(),
|
||||
static_cast<int>(port), m_logger, 1);
|
||||
|
||||
@@ -11,7 +11,7 @@ using namespace nt;
|
||||
|
||||
std::atomic<int> InstanceImpl::s_default{-1};
|
||||
std::atomic<InstanceImpl*> InstanceImpl::s_fast_instances[10];
|
||||
wpi::UidVector<std::unique_ptr<InstanceImpl>, 10> InstanceImpl::s_instances;
|
||||
wpi::UidVector<InstanceImpl*, 10> InstanceImpl::s_instances;
|
||||
wpi::mutex InstanceImpl::s_mutex;
|
||||
|
||||
using namespace std::placeholders;
|
||||
@@ -54,7 +54,7 @@ InstanceImpl* InstanceImpl::Get(int inst) {
|
||||
|
||||
// vector
|
||||
if (static_cast<unsigned int>(inst) < s_instances.size()) {
|
||||
return s_instances[inst].get();
|
||||
return s_instances[inst];
|
||||
}
|
||||
|
||||
// doesn't exist
|
||||
@@ -84,9 +84,9 @@ int InstanceImpl::Alloc() {
|
||||
}
|
||||
|
||||
int InstanceImpl::AllocImpl() {
|
||||
unsigned int inst = s_instances.emplace_back();
|
||||
unsigned int inst = s_instances.emplace_back(nullptr);
|
||||
InstanceImpl* ptr = new InstanceImpl(inst);
|
||||
s_instances[inst].reset(ptr);
|
||||
s_instances[inst] = ptr;
|
||||
|
||||
if (inst < (sizeof(s_fast_instances) / sizeof(s_fast_instances[0]))) {
|
||||
s_fast_instances[inst] = ptr;
|
||||
@@ -104,5 +104,6 @@ void InstanceImpl::Destroy(int inst) {
|
||||
s_fast_instances[inst] = nullptr;
|
||||
}
|
||||
|
||||
delete s_instances[inst];
|
||||
s_instances.erase(inst);
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ class InstanceImpl {
|
||||
|
||||
static std::atomic<int> s_default;
|
||||
static std::atomic<InstanceImpl*> s_fast_instances[10];
|
||||
static wpi::UidVector<std::unique_ptr<InstanceImpl>, 10> s_instances;
|
||||
static wpi::UidVector<InstanceImpl*, 10> s_instances;
|
||||
static wpi::mutex s_mutex;
|
||||
};
|
||||
|
||||
|
||||
@@ -109,10 +109,6 @@ static wpi::StringRef UnescapeString(wpi::StringRef source,
|
||||
continue;
|
||||
}
|
||||
switch (*++s) {
|
||||
case '\\':
|
||||
case '"':
|
||||
buf.push_back(s[-1]);
|
||||
break;
|
||||
case 't':
|
||||
buf.push_back('\t');
|
||||
break;
|
||||
@@ -133,7 +129,7 @@ static wpi::StringRef UnescapeString(wpi::StringRef source,
|
||||
break;
|
||||
}
|
||||
default:
|
||||
buf.push_back(s[-1]);
|
||||
buf.push_back(*s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,6 +194,7 @@ NetworkTable::NetworkTable(NT_Inst inst, const Twine& path, const private_init&)
|
||||
|
||||
NetworkTable::~NetworkTable() {
|
||||
for (auto& i : m_listeners) RemoveEntryListener(i.second);
|
||||
for (auto i : m_lambdaListeners) RemoveEntryListener(i);
|
||||
}
|
||||
|
||||
NetworkTableInstance NetworkTable::GetInstance() const {
|
||||
@@ -297,6 +298,40 @@ void NetworkTable::AddSubTableListener(ITableListener* listener) {
|
||||
AddSubTableListener(listener, false);
|
||||
}
|
||||
|
||||
NT_EntryListener NetworkTable::AddSubTableListener(TableListener listener,
|
||||
bool localNotify) {
|
||||
size_t prefix_len = m_path.size() + 1;
|
||||
|
||||
// The lambda needs to be copyable, but StringMap is not, so use
|
||||
// a shared_ptr to it.
|
||||
auto notified_tables = std::make_shared<wpi::StringMap<char>>();
|
||||
|
||||
unsigned int flags = NT_NOTIFY_NEW | NT_NOTIFY_IMMEDIATE;
|
||||
if (localNotify) flags |= NT_NOTIFY_LOCAL;
|
||||
NT_EntryListener id = nt::AddEntryListener(
|
||||
m_inst, m_path + Twine(PATH_SEPARATOR_CHAR),
|
||||
[=](const EntryNotification& event) {
|
||||
StringRef relative_key = event.name.substr(prefix_len);
|
||||
auto end_sub_table = relative_key.find(PATH_SEPARATOR_CHAR);
|
||||
if (end_sub_table == StringRef::npos) return;
|
||||
StringRef sub_table_key = relative_key.substr(0, end_sub_table);
|
||||
if (notified_tables->find(sub_table_key) == notified_tables->end())
|
||||
return;
|
||||
notified_tables->insert(std::make_pair(sub_table_key, '\0'));
|
||||
listener(this, sub_table_key, this->GetSubTable(sub_table_key));
|
||||
},
|
||||
flags);
|
||||
m_lambdaListeners.emplace_back(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
void NetworkTable::RemoveTableListener(NT_EntryListener listener) {
|
||||
nt::RemoveEntryListener(listener);
|
||||
auto matches_begin =
|
||||
std::remove(m_lambdaListeners.begin(), m_lambdaListeners.end(), listener);
|
||||
m_lambdaListeners.erase(matches_begin, m_lambdaListeners.end());
|
||||
}
|
||||
|
||||
void NetworkTable::AddSubTableListener(ITableListener* listener,
|
||||
bool localNotify) {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user