mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-28 02:11:43 +00:00
Compare commits
49 Commits
v2019.1.1-
...
v2019.1.1-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -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: ''
|
||||
|
||||
@@ -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.NativeUtils' version '2.1.2'
|
||||
id 'edu.wpi.first.GradleJni' version '0.3.1'
|
||||
id 'edu.wpi.first.GradleVsCode' version '0.6.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));
|
||||
}
|
||||
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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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>";
|
||||
@@ -340,7 +341,7 @@ void MjpegServerImpl::ConnThread::SendHTMLHeadTitle(
|
||||
// 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;
|
||||
@@ -453,7 +454,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;
|
||||
@@ -728,7 +729,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 +749,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 +810,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");
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <cstring>
|
||||
|
||||
#include <wpi/STLExtras.h>
|
||||
#include <wpi/json.h>
|
||||
#include <wpi/timestamp.h>
|
||||
|
||||
#include "Log.h"
|
||||
@@ -161,6 +162,272 @@ 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) {
|
||||
for (auto&& prop : config.at("properties")) {
|
||||
std::string name;
|
||||
try {
|
||||
name = prop.at("name").get<std::string>();
|
||||
} catch (const wpi::json::exception& e) {
|
||||
SWARNING("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>();
|
||||
SINFO("SetConfigJson: setting property '" << name << "' to '" << val
|
||||
<< '\'');
|
||||
SetStringProperty(n, val, status);
|
||||
} else if (v.is_boolean()) {
|
||||
bool val = v.get<bool>();
|
||||
SINFO("SetConfigJson: setting property '" << name << "' to " << val);
|
||||
SetProperty(n, val, status);
|
||||
} else {
|
||||
int val = v.get<int>();
|
||||
SINFO("SetConfigJson: setting property '" << name << "' to " << val);
|
||||
SetProperty(n, val, status);
|
||||
}
|
||||
} catch (const wpi::json::exception& e) {
|
||||
SWARNING("SetConfigJson: could not read property value: " << e.what());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
props.emplace_back(prop);
|
||||
}
|
||||
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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -7,8 +7,15 @@
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
std::vector<VideoProperty> VideoSource::EnumerateProperties() const {
|
||||
wpi::SmallVector<CS_Property, 32> handles_buf;
|
||||
CS_Status status = 0;
|
||||
|
||||
@@ -751,6 +751,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
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -289,6 +290,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,
|
||||
|
||||
@@ -25,6 +25,10 @@ namespace cv {
|
||||
class Mat;
|
||||
} // namespace cv
|
||||
|
||||
namespace wpi {
|
||||
class json;
|
||||
} // namespace wpi
|
||||
|
||||
/** CameraServer (cscore) namespace */
|
||||
namespace cs {
|
||||
|
||||
@@ -75,6 +79,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 +232,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,
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
@@ -532,6 +582,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.
|
||||
*
|
||||
|
||||
@@ -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,
|
||||
@@ -346,6 +361,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) {}
|
||||
|
||||
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,115 @@
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include <winsock2.h> // NOLINT(build/include_order)
|
||||
|
||||
#include <iphlpapi.h> // NOLINT(build/include_order)
|
||||
|
||||
#include <ws2tcpip.h> // NOLINT(build/include_order)
|
||||
|
||||
#include <uv.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
#pragma comment(lib, "IPHLPAPI.lib")
|
||||
#pragma comment(lib, "Ws2_32.lib")
|
||||
|
||||
#define WORKING_BUFFER_SIZE 15000
|
||||
#define MAX_TRIES 3
|
||||
|
||||
#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
|
||||
#define FREE(x) HeapFree(GetProcessHeap(), 0, (x))
|
||||
|
||||
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;
|
||||
std::cout << adrs[i].name << std::endl;
|
||||
InetNtop(PF_INET, &(adrs[i].netmask.netmask4.sin_addr.s_addr), ip,
|
||||
sizeof(ip) - 1);
|
||||
ip[49] = '\0';
|
||||
std::cout << ip << std::endl;
|
||||
InetNtop(PF_INET, &(adrs[i].address.address4.sin_addr.s_addr), ip,
|
||||
sizeof(ip) - 1);
|
||||
ip[49] = '\0';
|
||||
std::cout << ip << std::endl;
|
||||
addresses.emplace_back(std::string{ip});
|
||||
}
|
||||
|
||||
uv_free_interface_addresses(adrs, counts);
|
||||
|
||||
std::cout << "finished\n";
|
||||
|
||||
return addresses;
|
||||
|
||||
PIP_ADAPTER_ADDRESSES pAddresses = NULL;
|
||||
PIP_ADAPTER_ADDRESSES pCurrAddresses = NULL;
|
||||
PIP_ADAPTER_UNICAST_ADDRESS pUnicast = NULL;
|
||||
PIP_ADAPTER_ANYCAST_ADDRESS pAnycast = NULL;
|
||||
PIP_ADAPTER_MULTICAST_ADDRESS pMulticast = NULL;
|
||||
unsigned int i = 0;
|
||||
|
||||
ULONG outBufLen = 0;
|
||||
ULONG Iterations = 0;
|
||||
DWORD dwRetVal = 0;
|
||||
|
||||
// Allocate a 15 KB buffer to start with.
|
||||
outBufLen = WORKING_BUFFER_SIZE;
|
||||
|
||||
do {
|
||||
pAddresses = reinterpret_cast<IP_ADAPTER_ADDRESSES*>(MALLOC(outBufLen));
|
||||
if (pAddresses == NULL) {
|
||||
std::printf("Memory allocation failed for IP_ADAPTER_ADDRESSES struct\n");
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
dwRetVal = GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, NULL,
|
||||
pAddresses, &outBufLen);
|
||||
|
||||
if (dwRetVal == ERROR_BUFFER_OVERFLOW) {
|
||||
FREE(pAddresses);
|
||||
pAddresses = NULL;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
Iterations++;
|
||||
} while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (Iterations < MAX_TRIES));
|
||||
|
||||
if (dwRetVal == NO_ERROR) {
|
||||
pCurrAddresses = pAddresses;
|
||||
while (pCurrAddresses) {
|
||||
pUnicast = pCurrAddresses->FirstUnicastAddress;
|
||||
while (pUnicast != NULL) {
|
||||
sockaddr_in* address =
|
||||
reinterpret_cast<sockaddr_in*>(pUnicast->Address.lpSockaddr);
|
||||
InetNtop(PF_INET, &(address->sin_addr.s_addr), ip, sizeof(ip) - 1);
|
||||
ip[49] = '\0';
|
||||
addresses.emplace_back(std::string{ip});
|
||||
pUnicast = pUnicast->Next;
|
||||
}
|
||||
|
||||
pCurrAddresses = pCurrAddresses->Next;
|
||||
}
|
||||
}
|
||||
|
||||
if (pAddresses) {
|
||||
FREE(pAddresses);
|
||||
}
|
||||
|
||||
return addresses; // TODO
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
184
cscore/src/main/native/windows/UsbCameraImpl.h
Normal file
184
cscore/src/main/native/windows/UsbCameraImpl.h
Normal file
@@ -0,0 +1,184 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* 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. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#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,
|
||||
IAMVideoProcAmp* pProcAmp);
|
||||
void DeviceCacheProperties();
|
||||
void DeviceCacheVideoModes();
|
||||
void DeviceAddProperty(const wpi::Twine& name_, tagVideoProcAmpProperty tag,
|
||||
IAMVideoProcAmp* 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_
|
||||
69
cscore/src/main/native/windows/UsbCameraProperty.cpp
Normal file
69
cscore/src/main/native/windows/UsbCameraProperty.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* 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"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
UsbCameraProperty::UsbCameraProperty(const wpi::Twine& name_,
|
||||
tagVideoProcAmpProperty tag, bool autoProp,
|
||||
IAMVideoProcAmp* pProcAmp, bool* isValid)
|
||||
: PropertyImpl{autoProp ? name_ + "_auto" : name_} {
|
||||
this->tag = tag;
|
||||
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(tag, &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(tag, newValue, VideoProcAmp_Flags_Manual))) {
|
||||
lock.lock();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
72
cscore/src/main/native/windows/UsbCameraProperty.h
Normal file
72
cscore/src/main/native/windows/UsbCameraProperty.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* 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 <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);
|
||||
|
||||
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;
|
||||
|
||||
// If this is a device (rather than software) property
|
||||
bool device{true};
|
||||
bool isAutoProp{true};
|
||||
tagVideoProcAmpProperty tag;
|
||||
|
||||
// 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!
|
||||
};
|
||||
} // 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')
|
||||
|
||||
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
|
||||
|
||||
@@ -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,5 @@ kResourceType_Shuffleboard = 78
|
||||
kResourceType_CAN = 79
|
||||
kResourceType_DigilentDMC60 = 80
|
||||
kResourceType_PWMVictorSPX = 81
|
||||
kResourceType_RevSparkMaxPWM = 82
|
||||
kResourceType_RevSparkMaxCAN = 83
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -56,6 +56,7 @@ class NetworkTable final : public ITable {
|
||||
mutable wpi::StringMap<NT_Entry> m_entries;
|
||||
typedef std::pair<ITableListener*, NT_EntryListener> Listener;
|
||||
std::vector<Listener> m_listeners;
|
||||
std::vector<NT_EntryListener> m_lambdaListeners;
|
||||
|
||||
static std::vector<std::string> s_ip_addresses;
|
||||
static std::string s_persistent_filename;
|
||||
@@ -338,14 +339,14 @@ class NetworkTable final : public ITable {
|
||||
* @return Listener handle
|
||||
*/
|
||||
NT_EntryListener AddSubTableListener(TableListener listener,
|
||||
bool localNotify = false) const;
|
||||
bool localNotify = false);
|
||||
|
||||
/**
|
||||
* Remove a sub-table listener.
|
||||
*
|
||||
* @param listener listener handle
|
||||
*/
|
||||
void RemoveTableListener(NT_EntryListener listener) const;
|
||||
void RemoveTableListener(NT_EntryListener listener);
|
||||
|
||||
WPI_DEPRECATED(
|
||||
"use AddEntryListener() instead with flags value of NT_NOTIFY_NEW | "
|
||||
|
||||
@@ -31,7 +31,7 @@ using wpi::StringRef;
|
||||
* @ingroup ntcore_cpp_api
|
||||
*/
|
||||
typedef std::function<void(NetworkTable* parent, StringRef name,
|
||||
NetworkTable* table)>
|
||||
std::shared_ptr<NetworkTable> table)>
|
||||
TableListener;
|
||||
|
||||
} // namespace nt
|
||||
|
||||
@@ -4,7 +4,6 @@ pluginManagement {
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
enableFeaturePreview('STABLE_PUBLISHING')
|
||||
|
||||
include 'wpiutil'
|
||||
include 'ntcore'
|
||||
@@ -26,5 +25,6 @@ include 'simulation:halsim_gazebo'
|
||||
include 'simulation:lowfi_simulation'
|
||||
include 'simulation:halsim_ds_socket'
|
||||
include 'cameraserver'
|
||||
include 'cameraserver:multiCameraServer'
|
||||
include 'myRobot'
|
||||
include 'docs'
|
||||
|
||||
@@ -60,6 +60,7 @@ model {
|
||||
if (project.hasProperty('generatedHeaders')) {
|
||||
srcDir generatedHeaders
|
||||
}
|
||||
include '**/*.h'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,7 +119,9 @@ model {
|
||||
if (project.hasProperty('generatedHeaders')) {
|
||||
srcDir generatedHeaders
|
||||
}
|
||||
include '**/*.h'
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
binaries.all {
|
||||
@@ -154,6 +157,7 @@ model {
|
||||
if (project.hasProperty('generatedHeaders')) {
|
||||
srcDir generatedHeaders
|
||||
}
|
||||
include '**/*.h'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,7 +166,6 @@ model {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
lib library: "${nativeName}", linkage: 'static'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
|
||||
if (project.hasProperty('jniSplitSetup')) {
|
||||
jniSplitSetup(it)
|
||||
|
||||
@@ -9,7 +9,7 @@ project.netCommComponents.each { String s->
|
||||
netCommLibConfigs[s] = ['linux:athena']
|
||||
}
|
||||
|
||||
def niLibrariesVersion = '2019.7.1'
|
||||
def niLibrariesVersion = '2019.9.3'
|
||||
|
||||
model {
|
||||
dependencyConfigs {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
def opencvVersion = '3.4.3-17'
|
||||
def opencvVersion = '3.4.4-3'
|
||||
|
||||
if (project.hasProperty('useCpp') && project.useCpp) {
|
||||
model {
|
||||
|
||||
50
shared/resources.gradle
Normal file
50
shared/resources.gradle
Normal file
@@ -0,0 +1,50 @@
|
||||
ext.createGenerateResourcesTask = { name, prefix, namespace, project ->
|
||||
def generatedOutputDir = file("$buildDir/generated/$name/cpp")
|
||||
|
||||
def inputDir = file("$projectDir/src/$name/native/resources")
|
||||
|
||||
if (!prefix.isEmpty()) prefix += '_'
|
||||
|
||||
def task = project.tasks.create("generateResources-$name") {
|
||||
outputs.dir generatedOutputDir
|
||||
inputs.dir inputDir
|
||||
|
||||
doLast {
|
||||
generatedOutputDir.mkdirs()
|
||||
inputDir.eachFile { inputFile ->
|
||||
if (inputFile.name.startsWith('.')) return
|
||||
def fileBytes = inputFile.bytes
|
||||
def outputFile = file("$generatedOutputDir/${inputFile.name}.cpp")
|
||||
def funcName = "GetResource_" + inputFile.name.replaceAll('[^a-zA-Z0-9]', '_')
|
||||
outputFile.withWriter { out ->
|
||||
def inputBytes = inputFile.bytes
|
||||
out.print '''#include <stddef.h>
|
||||
#include <wpi/StringRef.h>
|
||||
extern "C" {
|
||||
static const unsigned char contents[] = { '''
|
||||
|
||||
for (int i = 0; i < fileBytes.size(); i++) {
|
||||
out.print String.format('0x%02x', (int) fileBytes[i] & 0xff)
|
||||
out.print ', '
|
||||
}
|
||||
out.println """};
|
||||
const unsigned char* ${prefix}${funcName}(size_t* len) {
|
||||
*len = ${fileBytes.size()};
|
||||
return contents;
|
||||
}
|
||||
}"""
|
||||
if (!namespace.isEmpty()) {
|
||||
out.println "namespace ${namespace} {"
|
||||
}
|
||||
out.println """wpi::StringRef ${funcName}() {
|
||||
return wpi::StringRef(reinterpret_cast<const char*>(contents), ${fileBytes.size()});
|
||||
}"""
|
||||
if (!namespace.isEmpty()) {
|
||||
out.println '}'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return task
|
||||
}
|
||||
@@ -5,6 +5,7 @@ apply plugin: 'cpp'
|
||||
apply plugin: "google-test"
|
||||
|
||||
ext.skipAthena = true
|
||||
ext.skipRaspbian = true
|
||||
|
||||
apply from: "${rootDir}/shared/config.gradle"
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ description = "A C++ and Java library to pass FRC Simulation Messages in and out
|
||||
|
||||
/* The simulation does not run on real hardware; so we always skip Athena */
|
||||
ext.skipAthena = true
|
||||
ext.skipRaspbian = true
|
||||
apply from: "${rootDir}/shared/config.gradle"
|
||||
|
||||
/* Use a sort of poor man's autoconf to find the protobuf development
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
#include <hal/HALBase.h>
|
||||
#include <mockdata/SPIData.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -22,9 +23,11 @@
|
||||
|
||||
using namespace hal;
|
||||
|
||||
const double ADXRS450_SpiGyroWrapper::kAngleLsb = 1 / 0.0125 / 0.0005;
|
||||
static constexpr double kSamplePeriod = 0.0005;
|
||||
|
||||
const double ADXRS450_SpiGyroWrapper::kAngleLsb = 1 / 0.0125 / kSamplePeriod;
|
||||
const double ADXRS450_SpiGyroWrapper::kMaxAngleDeltaPerMessage = 0.1875;
|
||||
const int ADXRS450_SpiGyroWrapper::kPacketSize = 4;
|
||||
const int ADXRS450_SpiGyroWrapper::kPacketSize = 4 + 1; // +1 for timestamp
|
||||
|
||||
template <class T>
|
||||
constexpr const T& clamp(const T& value, const T& low, const T& high) {
|
||||
@@ -38,7 +41,8 @@ static void ADXRS450SPI_ReadBufferCallback(const char* name, void* param,
|
||||
}
|
||||
|
||||
static void ADXRS450SPI_ReadAutoReceivedData(const char* name, void* param,
|
||||
uint8_t* buffer, int32_t numToRead,
|
||||
uint32_t* buffer,
|
||||
int32_t numToRead,
|
||||
int32_t* outputCount) {
|
||||
auto sim = static_cast<ADXRS450_SpiGyroWrapper*>(param);
|
||||
sim->HandleAutoReceiveData(buffer, numToRead, *outputCount);
|
||||
@@ -71,13 +75,14 @@ void ADXRS450_SpiGyroWrapper::HandleRead(uint8_t* buffer, uint32_t count) {
|
||||
std::memcpy(&buffer[0], &returnCode, sizeof(returnCode));
|
||||
}
|
||||
|
||||
void ADXRS450_SpiGyroWrapper::HandleAutoReceiveData(uint8_t* buffer,
|
||||
void ADXRS450_SpiGyroWrapper::HandleAutoReceiveData(uint32_t* buffer,
|
||||
int32_t numToRead,
|
||||
int32_t& outputCount) {
|
||||
std::lock_guard<wpi::recursive_spinlock> lock(m_angle.GetMutex());
|
||||
int32_t messagesToSend = std::abs(
|
||||
m_angleDiff > 0 ? std::ceil(m_angleDiff / kMaxAngleDeltaPerMessage)
|
||||
: std::floor(m_angleDiff / kMaxAngleDeltaPerMessage));
|
||||
int32_t messagesToSend =
|
||||
1 + std::abs(m_angleDiff > 0
|
||||
? std::ceil(m_angleDiff / kMaxAngleDeltaPerMessage)
|
||||
: std::floor(m_angleDiff / kMaxAngleDeltaPerMessage));
|
||||
|
||||
// Zero gets passed in during the "How much data do I need to read" step.
|
||||
// Else it is actually reading the accumulator
|
||||
@@ -87,24 +92,34 @@ void ADXRS450_SpiGyroWrapper::HandleAutoReceiveData(uint8_t* buffer,
|
||||
}
|
||||
|
||||
int valuesToRead = numToRead / kPacketSize;
|
||||
std::memset(&buffer[0], 0, numToRead);
|
||||
std::memset(&buffer[0], 0, numToRead * sizeof(uint32_t));
|
||||
|
||||
int msgCtr = 0;
|
||||
int32_t status = 0;
|
||||
uint32_t timestamp = HAL_GetFPGATime(&status);
|
||||
|
||||
while (msgCtr < valuesToRead) {
|
||||
double cappedDiff =
|
||||
clamp(m_angleDiff, -kMaxAngleDeltaPerMessage, kMaxAngleDeltaPerMessage);
|
||||
for (int msgCtr = 0; msgCtr < valuesToRead; ++msgCtr) {
|
||||
// force the first message to be a rate of 0 to init the timestamp
|
||||
double cappedDiff = (msgCtr == 0)
|
||||
? 0
|
||||
: clamp(m_angleDiff, -kMaxAngleDeltaPerMessage,
|
||||
kMaxAngleDeltaPerMessage);
|
||||
|
||||
// first word is timestamp
|
||||
buffer[msgCtr * kPacketSize] = timestamp;
|
||||
|
||||
int32_t valueToSend =
|
||||
((static_cast<int32_t>(cappedDiff * kAngleLsb) << 10) & (~0x0C00000E)) |
|
||||
0x04000000;
|
||||
valueToSend = ntohl(valueToSend);
|
||||
|
||||
std::memcpy(&buffer[msgCtr * kPacketSize], &valueToSend,
|
||||
sizeof(valueToSend));
|
||||
// following words have byte in LSB, in big endian order
|
||||
for (int i = 4; i >= 1; --i) {
|
||||
buffer[msgCtr * kPacketSize + i] =
|
||||
static_cast<uint32_t>(valueToSend) & 0xffu;
|
||||
valueToSend >>= 8;
|
||||
}
|
||||
|
||||
m_angleDiff -= cappedDiff;
|
||||
msgCtr += 1;
|
||||
timestamp += kSamplePeriod * 1e6; // fpga time is in us
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ class ADXRS450_SpiGyroWrapper {
|
||||
bool GetInitialized() const;
|
||||
|
||||
void HandleRead(uint8_t* buffer, uint32_t count);
|
||||
void HandleAutoReceiveData(uint8_t* buffer, int32_t numToRead,
|
||||
void HandleAutoReceiveData(uint32_t* buffer, int32_t numToRead,
|
||||
int32_t& outputCount);
|
||||
|
||||
int32_t RegisterAngleCallback(HAL_NotifyCallback callback, void* param,
|
||||
|
||||
@@ -4,6 +4,7 @@ apply plugin: 'edu.wpi.first.NativeUtils'
|
||||
apply plugin: 'cpp'
|
||||
|
||||
ext.skipAthena = true
|
||||
ext.skipRaspbian = true
|
||||
ext.pluginName = 'halsim_gazebo'
|
||||
|
||||
/* If gz_msgs or gazebo is not available, do not attempt a build */
|
||||
@@ -33,11 +34,11 @@ if (!gazebo_version?.trim()) {
|
||||
evaluationDependsOn(":simulation:gz_msgs")
|
||||
def gz_msgs_project = project(":simulation:gz_msgs")
|
||||
|
||||
tasks.whenTaskAdded { task ->
|
||||
task.onlyIf { !gz_msgs_project.hasProperty('skip_gz_msgs') && !project.hasProperty('skip_frc_plugins') }
|
||||
}
|
||||
if (!gz_msgs_project.hasProperty('skip_gz_msgs') && !project.hasProperty('skip_frc_plugins')) {
|
||||
|
||||
apply from: "${rootDir}/shared/plugins/setupBuild.gradle"
|
||||
apply from: "${rootDir}/shared/plugins/setupBuild.gradle"
|
||||
|
||||
}
|
||||
|
||||
model {
|
||||
binaries {
|
||||
|
||||
@@ -86,7 +86,7 @@ uint16_t ADXRS450_Gyro::ReadRegister(int reg) {
|
||||
}
|
||||
|
||||
double ADXRS450_Gyro::GetAngle() const {
|
||||
return m_spi.GetAccumulatorValue() * kDegreePerSecondPerLSB * kSamplePeriod;
|
||||
return m_spi.GetAccumulatorIntegratedValue() * kDegreePerSecondPerLSB;
|
||||
}
|
||||
|
||||
double ADXRS450_Gyro::GetRate() const {
|
||||
@@ -99,11 +99,11 @@ void ADXRS450_Gyro::Reset() { m_spi.ResetAccumulator(); }
|
||||
void ADXRS450_Gyro::Calibrate() {
|
||||
Wait(0.1);
|
||||
|
||||
m_spi.SetAccumulatorCenter(0);
|
||||
m_spi.SetAccumulatorIntegratedCenter(0);
|
||||
m_spi.ResetAccumulator();
|
||||
|
||||
Wait(kCalibrationSampleTime);
|
||||
|
||||
m_spi.SetAccumulatorCenter(static_cast<int>(m_spi.GetAccumulatorAverage()));
|
||||
m_spi.SetAccumulatorIntegratedCenter(m_spi.GetAccumulatorIntegratedAverage());
|
||||
m_spi.ResetAccumulator();
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include <wpi/StringRef.h>
|
||||
|
||||
#include "frc/AnalogInput.h"
|
||||
#include "frc/MotorSafetyHelper.h"
|
||||
#include "frc/Timer.h"
|
||||
#include "frc/Utility.h"
|
||||
#include "frc/WPIErrors.h"
|
||||
@@ -161,7 +160,7 @@ bool DriverStation::GetStickButtonReleased(int stick, int button) {
|
||||
wpi_setWPIError(BadJoystickIndex);
|
||||
return false;
|
||||
}
|
||||
if (button == 0) {
|
||||
if (button <= 0) {
|
||||
ReportJoystickUnpluggedError(
|
||||
"ERROR: Button indexes begin at 1 in WPILib for C++ and Java");
|
||||
return false;
|
||||
@@ -190,7 +189,7 @@ double DriverStation::GetStickAxis(int stick, int axis) {
|
||||
wpi_setWPIError(BadJoystickIndex);
|
||||
return 0.0;
|
||||
}
|
||||
if (axis >= HAL_kMaxJoystickAxes) {
|
||||
if (axis < 0 || axis >= HAL_kMaxJoystickAxes) {
|
||||
wpi_setWPIError(BadJoystickAxis);
|
||||
return 0.0;
|
||||
}
|
||||
@@ -212,7 +211,7 @@ int DriverStation::GetStickPOV(int stick, int pov) {
|
||||
wpi_setWPIError(BadJoystickIndex);
|
||||
return -1;
|
||||
}
|
||||
if (pov >= HAL_kMaxJoystickPOVs) {
|
||||
if (pov < 0 || pov >= HAL_kMaxJoystickPOVs) {
|
||||
wpi_setWPIError(BadJoystickAxis);
|
||||
return -1;
|
||||
}
|
||||
@@ -549,17 +548,10 @@ void DriverStation::ReportJoystickUnpluggedWarning(const wpi::Twine& message) {
|
||||
|
||||
void DriverStation::Run() {
|
||||
m_isRunning = true;
|
||||
int safetyCounter = 0;
|
||||
while (m_isRunning) {
|
||||
HAL_WaitForDSData();
|
||||
GetData();
|
||||
|
||||
if (IsDisabled()) safetyCounter = 0;
|
||||
|
||||
if (++safetyCounter >= 4) {
|
||||
MotorSafetyHelper::CheckMotors();
|
||||
safetyCounter = 0;
|
||||
}
|
||||
if (m_userInDisabled) HAL_ObserveUserProgramDisabled();
|
||||
if (m_userInAutonomous) HAL_ObserveUserProgramAutonomous();
|
||||
if (m_userInTeleop) HAL_ObserveUserProgramTeleop();
|
||||
|
||||
@@ -96,18 +96,18 @@ double InterruptableSensorBase::ReadRisingTimestamp() {
|
||||
if (StatusIsFatal()) return 0.0;
|
||||
wpi_assert(m_interrupt != HAL_kInvalidHandle);
|
||||
int32_t status = 0;
|
||||
double timestamp = HAL_ReadInterruptRisingTimestamp(m_interrupt, &status);
|
||||
int64_t timestamp = HAL_ReadInterruptRisingTimestamp(m_interrupt, &status);
|
||||
wpi_setErrorWithContext(status, HAL_GetErrorMessage(status));
|
||||
return timestamp;
|
||||
return timestamp * 1e-6;
|
||||
}
|
||||
|
||||
double InterruptableSensorBase::ReadFallingTimestamp() {
|
||||
if (StatusIsFatal()) return 0.0;
|
||||
wpi_assert(m_interrupt != HAL_kInvalidHandle);
|
||||
int32_t status = 0;
|
||||
double timestamp = HAL_ReadInterruptFallingTimestamp(m_interrupt, &status);
|
||||
int64_t timestamp = HAL_ReadInterruptFallingTimestamp(m_interrupt, &status);
|
||||
wpi_setErrorWithContext(status, HAL_GetErrorMessage(status));
|
||||
return timestamp;
|
||||
return timestamp * 1e-6;
|
||||
}
|
||||
|
||||
void InterruptableSensorBase::SetUpSourceEdge(bool risingEdge,
|
||||
|
||||
@@ -23,7 +23,7 @@ using namespace frc;
|
||||
|
||||
IterativeRobotBase::IterativeRobotBase(double period)
|
||||
: m_period(period),
|
||||
m_watchdog(period, [&] { PrintLoopOverrunMessage(); }) {}
|
||||
m_watchdog(period, [this] { PrintLoopOverrunMessage(); }) {}
|
||||
|
||||
void IterativeRobotBase::RobotInit() {
|
||||
wpi::outs() << "Default " << __FUNCTION__ << "() method... Overload me!\n";
|
||||
|
||||
80
wpilibc/src/main/native/cpp/MotorSafety.cpp
Normal file
80
wpilibc/src/main/native/cpp/MotorSafety.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2008-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 "frc/MotorSafety.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include <wpi/SmallString.h>
|
||||
|
||||
#include "frc/WPIErrors.h"
|
||||
|
||||
using namespace frc;
|
||||
|
||||
MotorSafety::MotorSafety(MotorSafety&& rhs)
|
||||
: ErrorBase(std::move(rhs)), m_enabled(std::move(rhs.m_enabled)) {
|
||||
m_watchdog = Watchdog(rhs.m_watchdog.GetTimeout(), [this] { TimeoutFunc(); });
|
||||
}
|
||||
|
||||
MotorSafety& MotorSafety::operator=(MotorSafety&& rhs) {
|
||||
ErrorBase::operator=(std::move(rhs));
|
||||
|
||||
m_watchdog = Watchdog(rhs.m_watchdog.GetTimeout(), [this] { TimeoutFunc(); });
|
||||
m_enabled = std::move(rhs.m_enabled);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void MotorSafety::Feed() {
|
||||
std::lock_guard<wpi::mutex> lock(m_thisMutex);
|
||||
m_watchdog.Reset();
|
||||
}
|
||||
|
||||
void MotorSafety::SetExpiration(double expirationTime) {
|
||||
std::lock_guard<wpi::mutex> lock(m_thisMutex);
|
||||
m_watchdog.SetTimeout(expirationTime);
|
||||
}
|
||||
|
||||
double MotorSafety::GetExpiration() const {
|
||||
std::lock_guard<wpi::mutex> lock(m_thisMutex);
|
||||
return m_watchdog.GetTimeout();
|
||||
}
|
||||
|
||||
bool MotorSafety::IsAlive() const {
|
||||
std::lock_guard<wpi::mutex> lock(m_thisMutex);
|
||||
return !m_enabled || !m_watchdog.IsExpired();
|
||||
}
|
||||
|
||||
void MotorSafety::SetSafetyEnabled(bool enabled) {
|
||||
std::lock_guard<wpi::mutex> lock(m_thisMutex);
|
||||
if (enabled) {
|
||||
m_watchdog.Enable();
|
||||
} else {
|
||||
m_watchdog.Disable();
|
||||
}
|
||||
m_enabled = enabled;
|
||||
}
|
||||
|
||||
bool MotorSafety::IsSafetyEnabled() const {
|
||||
std::lock_guard<wpi::mutex> lock(m_thisMutex);
|
||||
return m_enabled;
|
||||
}
|
||||
|
||||
void MotorSafety::TimeoutFunc() {
|
||||
auto& ds = DriverStation::GetInstance();
|
||||
if (ds.IsDisabled() || ds.IsTest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
wpi::SmallString<128> buf;
|
||||
wpi::raw_svector_ostream desc(buf);
|
||||
GetDescription(desc);
|
||||
desc << "... Output not updated often enough.";
|
||||
wpi_setWPIErrorWithContext(Timeout, desc.str());
|
||||
StopMotor();
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2008-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 "frc/MotorSafetyHelper.h"
|
||||
|
||||
#include <wpi/SmallPtrSet.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
#include "frc/DriverStation.h"
|
||||
#include "frc/MotorSafety.h"
|
||||
#include "frc/Timer.h"
|
||||
#include "frc/WPIErrors.h"
|
||||
|
||||
using namespace frc;
|
||||
|
||||
static wpi::SmallPtrSet<MotorSafetyHelper*, 32> helperList;
|
||||
static wpi::mutex listMutex;
|
||||
|
||||
MotorSafetyHelper::MotorSafetyHelper(MotorSafety* safeObject)
|
||||
: m_safeObject(safeObject) {
|
||||
m_enabled = false;
|
||||
m_expiration = DEFAULT_SAFETY_EXPIRATION;
|
||||
m_stopTime = Timer::GetFPGATimestamp();
|
||||
|
||||
std::lock_guard<wpi::mutex> lock(listMutex);
|
||||
helperList.insert(this);
|
||||
}
|
||||
|
||||
MotorSafetyHelper::~MotorSafetyHelper() {
|
||||
std::lock_guard<wpi::mutex> lock(listMutex);
|
||||
helperList.erase(this);
|
||||
}
|
||||
|
||||
void MotorSafetyHelper::Feed() {
|
||||
std::lock_guard<wpi::mutex> lock(m_thisMutex);
|
||||
m_stopTime = Timer::GetFPGATimestamp() + m_expiration;
|
||||
}
|
||||
|
||||
void MotorSafetyHelper::SetExpiration(double expirationTime) {
|
||||
std::lock_guard<wpi::mutex> lock(m_thisMutex);
|
||||
m_expiration = expirationTime;
|
||||
}
|
||||
|
||||
double MotorSafetyHelper::GetExpiration() const {
|
||||
std::lock_guard<wpi::mutex> lock(m_thisMutex);
|
||||
return m_expiration;
|
||||
}
|
||||
|
||||
bool MotorSafetyHelper::IsAlive() const {
|
||||
std::lock_guard<wpi::mutex> lock(m_thisMutex);
|
||||
return !m_enabled || m_stopTime > Timer::GetFPGATimestamp();
|
||||
}
|
||||
|
||||
void MotorSafetyHelper::Check() {
|
||||
bool enabled;
|
||||
double stopTime;
|
||||
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(m_thisMutex);
|
||||
enabled = m_enabled;
|
||||
stopTime = m_stopTime;
|
||||
}
|
||||
|
||||
DriverStation& ds = DriverStation::GetInstance();
|
||||
if (!enabled || ds.IsDisabled() || ds.IsTest()) return;
|
||||
|
||||
if (stopTime < Timer::GetFPGATimestamp()) {
|
||||
wpi::SmallString<128> buf;
|
||||
wpi::raw_svector_ostream desc(buf);
|
||||
m_safeObject->GetDescription(desc);
|
||||
desc << "... Output not updated often enough.";
|
||||
wpi_setWPIErrorWithContext(Timeout, desc.str());
|
||||
m_safeObject->StopMotor();
|
||||
}
|
||||
}
|
||||
|
||||
void MotorSafetyHelper::SetSafetyEnabled(bool enabled) {
|
||||
std::lock_guard<wpi::mutex> lock(m_thisMutex);
|
||||
m_enabled = enabled;
|
||||
}
|
||||
|
||||
bool MotorSafetyHelper::IsSafetyEnabled() const {
|
||||
std::lock_guard<wpi::mutex> lock(m_thisMutex);
|
||||
return m_enabled;
|
||||
}
|
||||
|
||||
void MotorSafetyHelper::CheckMotors() {
|
||||
std::lock_guard<wpi::mutex> lock(listMutex);
|
||||
for (auto elem : helperList) {
|
||||
elem->Check();
|
||||
}
|
||||
}
|
||||
@@ -14,11 +14,11 @@
|
||||
using namespace frc;
|
||||
|
||||
NidecBrushless::NidecBrushless(int pwmChannel, int dioChannel)
|
||||
: m_safetyHelper(this), m_dio(dioChannel), m_pwm(pwmChannel) {
|
||||
: m_dio(dioChannel), m_pwm(pwmChannel) {
|
||||
AddChild(&m_dio);
|
||||
AddChild(&m_pwm);
|
||||
m_safetyHelper.SetExpiration(0.0);
|
||||
m_safetyHelper.SetSafetyEnabled(false);
|
||||
SetExpiration(0.0);
|
||||
SetSafetyEnabled(false);
|
||||
|
||||
// the dio controls the output (in PWM mode)
|
||||
m_dio.SetPWMRate(15625);
|
||||
@@ -34,7 +34,7 @@ void NidecBrushless::Set(double speed) {
|
||||
m_dio.UpdateDutyCycle(0.5 + 0.5 * (m_isInverted ? -speed : speed));
|
||||
m_pwm.SetRaw(0xffff);
|
||||
}
|
||||
m_safetyHelper.Feed();
|
||||
Feed();
|
||||
}
|
||||
|
||||
double NidecBrushless::Get() const { return m_speed; }
|
||||
@@ -49,31 +49,13 @@ void NidecBrushless::Disable() {
|
||||
m_pwm.SetDisabled();
|
||||
}
|
||||
|
||||
void NidecBrushless::StopMotor() {
|
||||
m_dio.UpdateDutyCycle(0.5);
|
||||
m_pwm.SetDisabled();
|
||||
}
|
||||
|
||||
void NidecBrushless::Enable() { m_disabled = false; }
|
||||
|
||||
void NidecBrushless::PIDWrite(double output) { Set(output); }
|
||||
|
||||
void NidecBrushless::SetExpiration(double timeout) {
|
||||
m_safetyHelper.SetExpiration(timeout);
|
||||
}
|
||||
|
||||
double NidecBrushless::GetExpiration() const {
|
||||
return m_safetyHelper.GetExpiration();
|
||||
}
|
||||
|
||||
bool NidecBrushless::IsAlive() const { return m_safetyHelper.IsAlive(); }
|
||||
|
||||
void NidecBrushless::SetSafetyEnabled(bool enabled) {
|
||||
m_safetyHelper.SetSafetyEnabled(enabled);
|
||||
}
|
||||
|
||||
bool NidecBrushless::IsSafetyEnabled() const {
|
||||
return m_safetyHelper.IsSafetyEnabled();
|
||||
void NidecBrushless::StopMotor() {
|
||||
m_dio.UpdateDutyCycle(0.5);
|
||||
m_pwm.SetDisabled();
|
||||
}
|
||||
|
||||
void NidecBrushless::GetDescription(wpi::raw_ostream& desc) const {
|
||||
|
||||
@@ -234,7 +234,7 @@ void PIDBase::Reset() {
|
||||
void PIDBase::PIDWrite(double output) { SetSetpoint(output); }
|
||||
|
||||
void PIDBase::InitSendable(SendableBuilder& builder) {
|
||||
builder.SetSmartDashboardType("PIDBase");
|
||||
builder.SetSmartDashboardType("PIDController");
|
||||
builder.SetSafeState([=]() { Reset(); });
|
||||
builder.AddDoubleProperty("p", [=]() { return GetP(); },
|
||||
[=](double value) { SetP(value); });
|
||||
|
||||
@@ -47,6 +47,8 @@ PWM::PWM(int channel) {
|
||||
|
||||
HAL_Report(HALUsageReporting::kResourceType_PWM, channel);
|
||||
SetName("PWM", channel);
|
||||
|
||||
SetSafetyEnabled(false);
|
||||
}
|
||||
|
||||
PWM::~PWM() {
|
||||
@@ -60,7 +62,7 @@ PWM::~PWM() {
|
||||
}
|
||||
|
||||
PWM::PWM(PWM&& rhs)
|
||||
: ErrorBase(std::move(rhs)),
|
||||
: MotorSafety(std::move(rhs)),
|
||||
SendableBase(std::move(rhs)),
|
||||
m_channel(std::move(rhs.m_channel)) {
|
||||
std::swap(m_handle, rhs.m_handle);
|
||||
@@ -76,6 +78,12 @@ PWM& PWM::operator=(PWM&& rhs) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
void PWM::StopMotor() { SetDisabled(); }
|
||||
|
||||
void PWM::GetDescription(wpi::raw_ostream& desc) const {
|
||||
desc << "PWM " << GetChannel();
|
||||
}
|
||||
|
||||
void PWM::SetRaw(uint16_t value) {
|
||||
if (StatusIsFatal()) return;
|
||||
|
||||
@@ -114,6 +122,8 @@ void PWM::SetSpeed(double speed) {
|
||||
int32_t status = 0;
|
||||
HAL_SetPWMSpeed(m_handle, speed, &status);
|
||||
wpi_setErrorWithContext(status, HAL_GetErrorMessage(status));
|
||||
|
||||
Feed();
|
||||
}
|
||||
|
||||
double PWM::GetSpeed() const {
|
||||
|
||||
@@ -25,11 +25,11 @@ bool PWMSpeedController::GetInverted() const { return m_isInverted; }
|
||||
|
||||
void PWMSpeedController::Disable() { SetDisabled(); }
|
||||
|
||||
void PWMSpeedController::StopMotor() { SafePWM::StopMotor(); }
|
||||
void PWMSpeedController::StopMotor() { PWM::StopMotor(); }
|
||||
|
||||
void PWMSpeedController::PIDWrite(double output) { Set(output); }
|
||||
|
||||
PWMSpeedController::PWMSpeedController(int channel) : SafePWM(channel) {}
|
||||
PWMSpeedController::PWMSpeedController(int channel) : PWM(channel) {}
|
||||
|
||||
void PWMSpeedController::InitSendable(SendableBuilder& builder) {
|
||||
builder.SetSmartDashboardType("Speed Controller");
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <hal/Relay.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
#include "frc/MotorSafetyHelper.h"
|
||||
#include "frc/SensorUtil.h"
|
||||
#include "frc/WPIErrors.h"
|
||||
#include "frc/smartdashboard/SendableBuilder.h"
|
||||
@@ -77,9 +76,6 @@ Relay::Relay(int channel, Relay::Direction direction)
|
||||
}
|
||||
}
|
||||
|
||||
m_safetyHelper = std::make_unique<MotorSafetyHelper>(this);
|
||||
m_safetyHelper->SetSafetyEnabled(false);
|
||||
|
||||
SetName("Relay", m_channel);
|
||||
}
|
||||
|
||||
@@ -94,25 +90,21 @@ Relay::~Relay() {
|
||||
|
||||
Relay::Relay(Relay&& rhs)
|
||||
: MotorSafety(std::move(rhs)),
|
||||
ErrorBase(std::move(rhs)),
|
||||
SendableBase(std::move(rhs)),
|
||||
m_channel(std::move(rhs.m_channel)),
|
||||
m_direction(std::move(rhs.m_direction)),
|
||||
m_safetyHelper(std::move(rhs.m_safetyHelper)) {
|
||||
m_direction(std::move(rhs.m_direction)) {
|
||||
std::swap(m_forwardHandle, rhs.m_forwardHandle);
|
||||
std::swap(m_reverseHandle, rhs.m_reverseHandle);
|
||||
}
|
||||
|
||||
Relay& Relay::operator=(Relay&& rhs) {
|
||||
MotorSafety::operator=(std::move(rhs));
|
||||
ErrorBase::operator=(std::move(rhs));
|
||||
SendableBase::operator=(std::move(rhs));
|
||||
|
||||
m_channel = std::move(rhs.m_channel);
|
||||
m_direction = std::move(rhs.m_direction);
|
||||
std::swap(m_forwardHandle, rhs.m_forwardHandle);
|
||||
std::swap(m_reverseHandle, rhs.m_reverseHandle);
|
||||
m_safetyHelper = std::move(rhs.m_safetyHelper);
|
||||
|
||||
return *this;
|
||||
}
|
||||
@@ -204,24 +196,8 @@ Relay::Value Relay::Get() const {
|
||||
|
||||
int Relay::GetChannel() const { return m_channel; }
|
||||
|
||||
void Relay::SetExpiration(double timeout) {
|
||||
m_safetyHelper->SetExpiration(timeout);
|
||||
}
|
||||
|
||||
double Relay::GetExpiration() const { return m_safetyHelper->GetExpiration(); }
|
||||
|
||||
bool Relay::IsAlive() const { return m_safetyHelper->IsAlive(); }
|
||||
|
||||
void Relay::StopMotor() { Set(kOff); }
|
||||
|
||||
void Relay::SetSafetyEnabled(bool enabled) {
|
||||
m_safetyHelper->SetSafetyEnabled(enabled);
|
||||
}
|
||||
|
||||
bool Relay::IsSafetyEnabled() const {
|
||||
return m_safetyHelper->IsSafetyEnabled();
|
||||
}
|
||||
|
||||
void Relay::GetDescription(wpi::raw_ostream& desc) const {
|
||||
desc << "Relay " << GetChannel();
|
||||
}
|
||||
|
||||
@@ -298,7 +298,7 @@ void RobotDrive::MecanumDrive_Cartesian(double x, double y, double rotation,
|
||||
m_rearLeftMotor->Set(wheelSpeeds[kRearLeftMotor] * m_maxOutput);
|
||||
m_rearRightMotor->Set(wheelSpeeds[kRearRightMotor] * m_maxOutput);
|
||||
|
||||
m_safetyHelper->Feed();
|
||||
Feed();
|
||||
}
|
||||
|
||||
void RobotDrive::MecanumDrive_Polar(double magnitude, double direction,
|
||||
@@ -330,7 +330,7 @@ void RobotDrive::MecanumDrive_Polar(double magnitude, double direction,
|
||||
m_rearLeftMotor->Set(wheelSpeeds[kRearLeftMotor] * m_maxOutput);
|
||||
m_rearRightMotor->Set(wheelSpeeds[kRearRightMotor] * m_maxOutput);
|
||||
|
||||
m_safetyHelper->Feed();
|
||||
Feed();
|
||||
}
|
||||
|
||||
void RobotDrive::HolonomicDrive(double magnitude, double direction,
|
||||
@@ -350,7 +350,7 @@ void RobotDrive::SetLeftRightMotorOutputs(double leftOutput,
|
||||
m_frontRightMotor->Set(-Limit(rightOutput) * m_maxOutput);
|
||||
m_rearRightMotor->Set(-Limit(rightOutput) * m_maxOutput);
|
||||
|
||||
m_safetyHelper->Feed();
|
||||
Feed();
|
||||
}
|
||||
|
||||
void RobotDrive::SetInvertedMotor(MotorType motor, bool isInverted) {
|
||||
@@ -380,40 +380,19 @@ void RobotDrive::SetSensitivity(double sensitivity) {
|
||||
|
||||
void RobotDrive::SetMaxOutput(double maxOutput) { m_maxOutput = maxOutput; }
|
||||
|
||||
void RobotDrive::SetExpiration(double timeout) {
|
||||
m_safetyHelper->SetExpiration(timeout);
|
||||
void RobotDrive::GetDescription(wpi::raw_ostream& desc) const {
|
||||
desc << "RobotDrive";
|
||||
}
|
||||
|
||||
double RobotDrive::GetExpiration() const {
|
||||
return m_safetyHelper->GetExpiration();
|
||||
}
|
||||
|
||||
bool RobotDrive::IsAlive() const { return m_safetyHelper->IsAlive(); }
|
||||
|
||||
void RobotDrive::StopMotor() {
|
||||
if (m_frontLeftMotor != nullptr) m_frontLeftMotor->StopMotor();
|
||||
if (m_frontRightMotor != nullptr) m_frontRightMotor->StopMotor();
|
||||
if (m_rearLeftMotor != nullptr) m_rearLeftMotor->StopMotor();
|
||||
if (m_rearRightMotor != nullptr) m_rearRightMotor->StopMotor();
|
||||
m_safetyHelper->Feed();
|
||||
Feed();
|
||||
}
|
||||
|
||||
bool RobotDrive::IsSafetyEnabled() const {
|
||||
return m_safetyHelper->IsSafetyEnabled();
|
||||
}
|
||||
|
||||
void RobotDrive::SetSafetyEnabled(bool enabled) {
|
||||
m_safetyHelper->SetSafetyEnabled(enabled);
|
||||
}
|
||||
|
||||
void RobotDrive::GetDescription(wpi::raw_ostream& desc) const {
|
||||
desc << "RobotDrive";
|
||||
}
|
||||
|
||||
void RobotDrive::InitRobotDrive() {
|
||||
m_safetyHelper = std::make_unique<MotorSafetyHelper>(this);
|
||||
m_safetyHelper->SetSafetyEnabled(true);
|
||||
}
|
||||
void RobotDrive::InitRobotDrive() { SetSafetyEnabled(true); }
|
||||
|
||||
double RobotDrive::Limit(double number) {
|
||||
if (number > 1.0) {
|
||||
|
||||
@@ -31,13 +31,13 @@ class SPI::Accumulator {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
Update();
|
||||
}),
|
||||
m_buf(new uint8_t[xferSize * kAccumulateDepth]),
|
||||
m_buf(new uint32_t[(xferSize + 1) * kAccumulateDepth]),
|
||||
m_validMask(validMask),
|
||||
m_validValue(validValue),
|
||||
m_dataMax(1 << dataSize),
|
||||
m_dataMsbMask(1 << (dataSize - 1)),
|
||||
m_dataShift(dataShift),
|
||||
m_xferSize(xferSize),
|
||||
m_xferSize(xferSize + 1), // +1 for timestamp
|
||||
m_isSigned(isSigned),
|
||||
m_bigEndian(bigEndian),
|
||||
m_port(port) {}
|
||||
@@ -46,15 +46,18 @@ class SPI::Accumulator {
|
||||
void Update();
|
||||
|
||||
Notifier m_notifier;
|
||||
uint8_t* m_buf;
|
||||
uint32_t* m_buf;
|
||||
wpi::mutex m_mutex;
|
||||
|
||||
int64_t m_value = 0;
|
||||
uint32_t m_count = 0;
|
||||
int32_t m_lastValue = 0;
|
||||
uint32_t m_lastTimestamp = 0;
|
||||
double m_integratedValue = 0;
|
||||
|
||||
int32_t m_center = 0;
|
||||
int32_t m_deadband = 0;
|
||||
double m_integratedCenter = 0;
|
||||
|
||||
int32_t m_validMask;
|
||||
int32_t m_validValue;
|
||||
@@ -78,7 +81,7 @@ void SPI::Accumulator::Update() {
|
||||
HAL_ReadSPIAutoReceivedData(m_port, m_buf, 0, 0, &status);
|
||||
if (status != 0) return; // error reading
|
||||
|
||||
// only get whole responses
|
||||
// only get whole responses; +1 is for timestamp
|
||||
numToRead -= numToRead % m_xferSize;
|
||||
if (numToRead > m_xferSize * kAccumulateDepth) {
|
||||
numToRead = m_xferSize * kAccumulateDepth;
|
||||
@@ -92,15 +95,18 @@ void SPI::Accumulator::Update() {
|
||||
|
||||
// loop over all responses
|
||||
for (int32_t off = 0; off < numToRead; off += m_xferSize) {
|
||||
// get timestamp from first word
|
||||
uint32_t timestamp = m_buf[off];
|
||||
|
||||
// convert from bytes
|
||||
uint32_t resp = 0;
|
||||
if (m_bigEndian) {
|
||||
for (int32_t i = 0; i < m_xferSize; ++i) {
|
||||
for (int32_t i = 1; i < m_xferSize; ++i) {
|
||||
resp <<= 8;
|
||||
resp |= m_buf[off + i] & 0xff;
|
||||
}
|
||||
} else {
|
||||
for (int32_t i = m_xferSize - 1; i >= 0; --i) {
|
||||
for (int32_t i = m_xferSize - 1; i >= 1; --i) {
|
||||
resp <<= 8;
|
||||
resp |= m_buf[off + i] & 0xff;
|
||||
}
|
||||
@@ -114,15 +120,34 @@ void SPI::Accumulator::Update() {
|
||||
// 2s complement conversion if signed MSB is set
|
||||
if (m_isSigned && (data & m_dataMsbMask) != 0) data -= m_dataMax;
|
||||
// center offset
|
||||
int32_t dataNoCenter = data;
|
||||
data -= m_center;
|
||||
// only accumulate if outside deadband
|
||||
if (data < -m_deadband || data > m_deadband) m_value += data;
|
||||
if (data < -m_deadband || data > m_deadband) {
|
||||
m_value += data;
|
||||
if (m_count != 0) {
|
||||
// timestamps use the 1us FPGA clock; also handle rollover
|
||||
if (timestamp >= m_lastTimestamp)
|
||||
m_integratedValue +=
|
||||
dataNoCenter *
|
||||
static_cast<int32_t>(timestamp - m_lastTimestamp) * 1e-6 -
|
||||
m_integratedCenter;
|
||||
else
|
||||
m_integratedValue +=
|
||||
dataNoCenter *
|
||||
static_cast<int32_t>((1ULL << 32) - m_lastTimestamp +
|
||||
timestamp) *
|
||||
1e-6 -
|
||||
m_integratedCenter;
|
||||
}
|
||||
}
|
||||
++m_count;
|
||||
m_lastValue = data;
|
||||
} else {
|
||||
// no data from the sensor; just clear the last value
|
||||
m_lastValue = 0;
|
||||
}
|
||||
m_lastTimestamp = timestamp;
|
||||
}
|
||||
} while (!done);
|
||||
}
|
||||
@@ -284,7 +309,7 @@ void SPI::ForceAutoRead() {
|
||||
wpi_setErrorWithContext(status, HAL_GetErrorMessage(status));
|
||||
}
|
||||
|
||||
int SPI::ReadAutoReceivedData(uint8_t* buffer, int numToRead, double timeout) {
|
||||
int SPI::ReadAutoReceivedData(uint32_t* buffer, int numToRead, double timeout) {
|
||||
int32_t status = 0;
|
||||
int32_t val =
|
||||
HAL_ReadSPIAutoReceivedData(m_port, buffer, numToRead, timeout, &status);
|
||||
@@ -337,6 +362,8 @@ void SPI::ResetAccumulator() {
|
||||
m_accum->m_value = 0;
|
||||
m_accum->m_count = 0;
|
||||
m_accum->m_lastValue = 0;
|
||||
m_accum->m_lastTimestamp = 0;
|
||||
m_accum->m_integratedValue = 0;
|
||||
}
|
||||
|
||||
void SPI::SetAccumulatorCenter(int center) {
|
||||
@@ -391,3 +418,25 @@ void SPI::GetAccumulatorOutput(int64_t& value, int64_t& count) const {
|
||||
value = m_accum->m_value;
|
||||
count = m_accum->m_count;
|
||||
}
|
||||
|
||||
void SPI::SetAccumulatorIntegratedCenter(double center) {
|
||||
if (!m_accum) return;
|
||||
std::lock_guard<wpi::mutex> lock(m_accum->m_mutex);
|
||||
m_accum->m_integratedCenter = center;
|
||||
}
|
||||
|
||||
double SPI::GetAccumulatorIntegratedValue() const {
|
||||
if (!m_accum) return 0;
|
||||
std::lock_guard<wpi::mutex> lock(m_accum->m_mutex);
|
||||
m_accum->Update();
|
||||
return m_accum->m_integratedValue;
|
||||
}
|
||||
|
||||
double SPI::GetAccumulatorIntegratedAverage() const {
|
||||
if (!m_accum) return 0;
|
||||
std::lock_guard<wpi::mutex> lock(m_accum->m_mutex);
|
||||
m_accum->Update();
|
||||
if (m_accum->m_count <= 1) return 0.0;
|
||||
// count-1 due to not integrating the first value received
|
||||
return m_accum->m_integratedValue / (m_accum->m_count - 1);
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2008-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 "frc/SafePWM.h"
|
||||
|
||||
using namespace frc;
|
||||
|
||||
SafePWM::SafePWM(int channel) : PWM(channel) {
|
||||
m_safetyHelper = std::make_unique<MotorSafetyHelper>(this);
|
||||
m_safetyHelper->SetSafetyEnabled(false);
|
||||
}
|
||||
|
||||
void SafePWM::SetExpiration(double timeout) {
|
||||
m_safetyHelper->SetExpiration(timeout);
|
||||
}
|
||||
|
||||
double SafePWM::GetExpiration() const {
|
||||
return m_safetyHelper->GetExpiration();
|
||||
}
|
||||
|
||||
bool SafePWM::IsAlive() const { return m_safetyHelper->IsAlive(); }
|
||||
|
||||
void SafePWM::StopMotor() { SetDisabled(); }
|
||||
|
||||
void SafePWM::SetSafetyEnabled(bool enabled) {
|
||||
m_safetyHelper->SetSafetyEnabled(enabled);
|
||||
}
|
||||
|
||||
bool SafePWM::IsSafetyEnabled() const {
|
||||
return m_safetyHelper->IsSafetyEnabled();
|
||||
}
|
||||
|
||||
void SafePWM::GetDescription(wpi::raw_ostream& desc) const {
|
||||
desc << "PWM " << GetChannel();
|
||||
}
|
||||
|
||||
void SafePWM::SetSpeed(double speed) {
|
||||
PWM::SetSpeed(speed);
|
||||
m_safetyHelper->Feed();
|
||||
}
|
||||
@@ -19,7 +19,7 @@ constexpr double Servo::kMinServoAngle;
|
||||
constexpr double Servo::kDefaultMaxServoPWM;
|
||||
constexpr double Servo::kDefaultMinServoPWM;
|
||||
|
||||
Servo::Servo(int channel) : SafePWM(channel) {
|
||||
Servo::Servo(int channel) : PWM(channel) {
|
||||
// Set minimum and maximum PWM values supported by the servo
|
||||
SetBounds(kDefaultMaxServoPWM, 0.0, 0.0, 0.0, kDefaultMinServoPWM);
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* 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 "frc/SynchronousPID.h"
|
||||
|
||||
using namespace frc;
|
||||
|
||||
SynchronousPID::SynchronousPID(double Kp, double Ki, double Kd,
|
||||
PIDSource& source, PIDOutput& output)
|
||||
: SynchronousPID(Kp, Ki, Kd, 0.0, source, output) {}
|
||||
|
||||
SynchronousPID::SynchronousPID(double Kp, double Ki, double Kd, double Kf,
|
||||
PIDSource& source, PIDOutput& output)
|
||||
: PIDBase(Kp, Ki, Kd, Kf, source, output) {
|
||||
m_enabled = true;
|
||||
}
|
||||
|
||||
void SynchronousPID::Calculate() { PIDBase::Calculate(); }
|
||||
@@ -7,53 +7,167 @@
|
||||
|
||||
#include "frc/Watchdog.h"
|
||||
|
||||
#include <wpi/Format.h>
|
||||
#include <wpi/PriorityQueue.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
#include "frc/Timer.h"
|
||||
|
||||
using namespace frc;
|
||||
|
||||
Watchdog::Watchdog(double timeout, std::function<void()> callback)
|
||||
: m_timeout(timeout),
|
||||
m_callback(callback),
|
||||
m_notifier(&Watchdog::TimeoutFunc, this) {
|
||||
Enable();
|
||||
constexpr std::chrono::milliseconds Watchdog::kMinPrintPeriod;
|
||||
|
||||
class Watchdog::Thread : public wpi::SafeThread {
|
||||
public:
|
||||
template <typename T>
|
||||
struct DerefGreater : public std::binary_function<T, T, bool> {
|
||||
constexpr bool operator()(const T& lhs, const T& rhs) const {
|
||||
return *lhs > *rhs;
|
||||
}
|
||||
};
|
||||
|
||||
wpi::PriorityQueue<Watchdog*, std::vector<Watchdog*>, DerefGreater<Watchdog*>>
|
||||
m_watchdogs;
|
||||
|
||||
private:
|
||||
void Main() override;
|
||||
};
|
||||
|
||||
void Watchdog::Thread::Main() {
|
||||
std::unique_lock<wpi::mutex> lock(m_mutex);
|
||||
|
||||
while (m_active) {
|
||||
if (m_watchdogs.size() > 0) {
|
||||
if (m_cond.wait_until(lock, m_watchdogs.top()->m_expirationTime) ==
|
||||
std::cv_status::timeout) {
|
||||
if (m_watchdogs.size() == 0 ||
|
||||
m_watchdogs.top()->m_expirationTime > hal::fpga_clock::now()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the condition variable timed out, that means a Watchdog timeout
|
||||
// has occurred, so call its timeout function.
|
||||
auto watchdog = m_watchdogs.top();
|
||||
m_watchdogs.pop();
|
||||
|
||||
auto now = hal::fpga_clock::now();
|
||||
if (now - watchdog->m_lastTimeoutPrintTime > kMinPrintPeriod) {
|
||||
watchdog->m_lastTimeoutPrintTime = now;
|
||||
wpi::outs() << "Watchdog not fed within "
|
||||
<< wpi::format("%.6f",
|
||||
watchdog->m_timeout.count() / 1.0e6)
|
||||
<< "s\n";
|
||||
}
|
||||
lock.unlock();
|
||||
watchdog->m_callback();
|
||||
lock.lock();
|
||||
watchdog->m_isExpired = true;
|
||||
}
|
||||
// Otherwise, a Watchdog removed itself from the queue (it notifies the
|
||||
// scheduler of this) or a spurious wakeup occurred, so just rewait with
|
||||
// the soonest watchdog timeout.
|
||||
} else {
|
||||
m_cond.wait(lock, [&] { return m_watchdogs.size() > 0 || !m_active; });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Watchdog::Watchdog(double timeout, std::function<void()> callback)
|
||||
: m_timeout(static_cast<int64_t>(timeout * 1.0e6)),
|
||||
m_callback(callback),
|
||||
m_owner(&GetThreadOwner()) {}
|
||||
|
||||
Watchdog::~Watchdog() { Disable(); }
|
||||
|
||||
double Watchdog::GetTime() const {
|
||||
return Timer::GetFPGATimestamp() - m_startTime;
|
||||
return (hal::fpga_clock::now() - m_startTime).count() / 1.0e6;
|
||||
}
|
||||
|
||||
bool Watchdog::IsExpired() const { return m_isExpired; }
|
||||
void Watchdog::SetTimeout(double timeout) {
|
||||
m_startTime = hal::fpga_clock::now();
|
||||
m_epochs.clear();
|
||||
|
||||
// Locks mutex
|
||||
auto thr = m_owner->GetThread();
|
||||
if (!thr) return;
|
||||
|
||||
m_timeout = std::chrono::microseconds(static_cast<int64_t>(timeout * 1.0e6));
|
||||
m_isExpired = false;
|
||||
|
||||
thr->m_watchdogs.remove(this);
|
||||
m_expirationTime = m_startTime + m_timeout;
|
||||
thr->m_watchdogs.emplace(this);
|
||||
thr->m_cond.notify_all();
|
||||
}
|
||||
|
||||
double Watchdog::GetTimeout() const {
|
||||
// Locks mutex
|
||||
auto thr = m_owner->GetThread();
|
||||
|
||||
return m_timeout.count() / 1.0e6;
|
||||
}
|
||||
|
||||
bool Watchdog::IsExpired() const {
|
||||
// Locks mutex
|
||||
auto thr = m_owner->GetThread();
|
||||
|
||||
return m_isExpired;
|
||||
}
|
||||
|
||||
void Watchdog::AddEpoch(wpi::StringRef epochName) {
|
||||
double currentTime = Timer::GetFPGATimestamp();
|
||||
auto currentTime = hal::fpga_clock::now();
|
||||
m_epochs[epochName] = currentTime - m_startTime;
|
||||
m_startTime = currentTime;
|
||||
}
|
||||
|
||||
void Watchdog::PrintEpochs() {
|
||||
for (const auto& epoch : m_epochs) {
|
||||
wpi::outs() << "\t" << epoch.getKey() << ": " << epoch.getValue() << "s\n";
|
||||
auto now = hal::fpga_clock::now();
|
||||
if (now - m_lastEpochsPrintTime > kMinPrintPeriod) {
|
||||
m_lastEpochsPrintTime = now;
|
||||
for (const auto& epoch : m_epochs) {
|
||||
wpi::outs() << '\t' << epoch.getKey() << ": "
|
||||
<< wpi::format("%.6f", epoch.getValue().count() / 1.0e6)
|
||||
<< "s\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Watchdog::Reset() { Enable(); }
|
||||
|
||||
void Watchdog::Enable() {
|
||||
m_startTime = Timer::GetFPGATimestamp();
|
||||
m_isExpired = false;
|
||||
m_startTime = hal::fpga_clock::now();
|
||||
m_epochs.clear();
|
||||
m_notifier.StartPeriodic(m_timeout);
|
||||
|
||||
// Locks mutex
|
||||
auto thr = m_owner->GetThread();
|
||||
if (!thr) return;
|
||||
|
||||
m_isExpired = false;
|
||||
|
||||
thr->m_watchdogs.remove(this);
|
||||
m_expirationTime = m_startTime + m_timeout;
|
||||
thr->m_watchdogs.emplace(this);
|
||||
thr->m_cond.notify_all();
|
||||
}
|
||||
|
||||
void Watchdog::Disable() { m_notifier.Stop(); }
|
||||
void Watchdog::Disable() {
|
||||
// Locks mutex
|
||||
auto thr = m_owner->GetThread();
|
||||
if (!thr) return;
|
||||
|
||||
void Watchdog::TimeoutFunc() {
|
||||
if (!m_isExpired) {
|
||||
wpi::outs() << "Watchdog not fed after " << m_timeout << "s\n";
|
||||
m_callback();
|
||||
m_isExpired = true;
|
||||
Disable();
|
||||
}
|
||||
m_isExpired = false;
|
||||
|
||||
thr->m_watchdogs.remove(this);
|
||||
thr->m_cond.notify_all();
|
||||
}
|
||||
|
||||
bool Watchdog::operator>(const Watchdog& rhs) {
|
||||
return m_expirationTime > rhs.m_expirationTime;
|
||||
}
|
||||
|
||||
wpi::SafeThreadOwner<Watchdog::Thread>& Watchdog::GetThreadOwner() {
|
||||
static wpi::SafeThreadOwner<Thread> inst = [] {
|
||||
wpi::SafeThreadOwner<Watchdog::Thread> inst;
|
||||
inst.Start();
|
||||
return inst;
|
||||
}();
|
||||
return inst;
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ void DifferentialDrive::ArcadeDrive(double xSpeed, double zRotation,
|
||||
m_rightMotor.Set(Limit(rightMotorOutput) * m_maxOutput *
|
||||
m_rightSideInvertMultiplier);
|
||||
|
||||
m_safetyHelper.Feed();
|
||||
Feed();
|
||||
}
|
||||
|
||||
void DifferentialDrive::CurvatureDrive(double xSpeed, double zRotation,
|
||||
@@ -152,7 +152,7 @@ void DifferentialDrive::CurvatureDrive(double xSpeed, double zRotation,
|
||||
m_rightMotor.Set(rightMotorOutput * m_maxOutput *
|
||||
m_rightSideInvertMultiplier);
|
||||
|
||||
m_safetyHelper.Feed();
|
||||
Feed();
|
||||
}
|
||||
|
||||
void DifferentialDrive::TankDrive(double leftSpeed, double rightSpeed,
|
||||
@@ -180,7 +180,7 @@ void DifferentialDrive::TankDrive(double leftSpeed, double rightSpeed,
|
||||
m_leftMotor.Set(leftSpeed * m_maxOutput);
|
||||
m_rightMotor.Set(rightSpeed * m_maxOutput * m_rightSideInvertMultiplier);
|
||||
|
||||
m_safetyHelper.Feed();
|
||||
Feed();
|
||||
}
|
||||
|
||||
void DifferentialDrive::SetQuickStopThreshold(double threshold) {
|
||||
@@ -202,7 +202,7 @@ void DifferentialDrive::SetRightSideInverted(bool rightSideInverted) {
|
||||
void DifferentialDrive::StopMotor() {
|
||||
m_leftMotor.StopMotor();
|
||||
m_rightMotor.StopMotor();
|
||||
m_safetyHelper.Feed();
|
||||
Feed();
|
||||
}
|
||||
|
||||
void DifferentialDrive::GetDescription(wpi::raw_ostream& desc) const {
|
||||
|
||||
@@ -73,7 +73,7 @@ void KilloughDrive::DriveCartesian(double ySpeed, double xSpeed,
|
||||
m_rightMotor.Set(wheelSpeeds[kRight] * m_maxOutput);
|
||||
m_backMotor.Set(wheelSpeeds[kBack] * m_maxOutput);
|
||||
|
||||
m_safetyHelper.Feed();
|
||||
Feed();
|
||||
}
|
||||
|
||||
void KilloughDrive::DrivePolar(double magnitude, double angle,
|
||||
@@ -92,7 +92,7 @@ void KilloughDrive::StopMotor() {
|
||||
m_leftMotor.StopMotor();
|
||||
m_rightMotor.StopMotor();
|
||||
m_backMotor.StopMotor();
|
||||
m_safetyHelper.Feed();
|
||||
Feed();
|
||||
}
|
||||
|
||||
void KilloughDrive::GetDescription(wpi::raw_ostream& desc) const {
|
||||
|
||||
@@ -70,7 +70,7 @@ void MecanumDrive::DriveCartesian(double ySpeed, double xSpeed,
|
||||
m_rearRightMotor.Set(wheelSpeeds[kRearRight] * m_maxOutput *
|
||||
m_rightSideInvertMultiplier);
|
||||
|
||||
m_safetyHelper.Feed();
|
||||
Feed();
|
||||
}
|
||||
|
||||
void MecanumDrive::DrivePolar(double magnitude, double angle,
|
||||
@@ -98,7 +98,7 @@ void MecanumDrive::StopMotor() {
|
||||
m_frontRightMotor.StopMotor();
|
||||
m_rearLeftMotor.StopMotor();
|
||||
m_rearRightMotor.StopMotor();
|
||||
m_safetyHelper.Feed();
|
||||
Feed();
|
||||
}
|
||||
|
||||
void MecanumDrive::GetDescription(wpi::raw_ostream& desc) const {
|
||||
|
||||
@@ -18,31 +18,13 @@
|
||||
|
||||
using namespace frc;
|
||||
|
||||
RobotDriveBase::RobotDriveBase() { m_safetyHelper.SetSafetyEnabled(true); }
|
||||
RobotDriveBase::RobotDriveBase() { SetSafetyEnabled(true); }
|
||||
|
||||
void RobotDriveBase::SetDeadband(double deadband) { m_deadband = deadband; }
|
||||
|
||||
void RobotDriveBase::SetMaxOutput(double maxOutput) { m_maxOutput = maxOutput; }
|
||||
|
||||
void RobotDriveBase::FeedWatchdog() { m_safetyHelper.Feed(); }
|
||||
|
||||
void RobotDriveBase::SetExpiration(double timeout) {
|
||||
m_safetyHelper.SetExpiration(timeout);
|
||||
}
|
||||
|
||||
double RobotDriveBase::GetExpiration() const {
|
||||
return m_safetyHelper.GetExpiration();
|
||||
}
|
||||
|
||||
bool RobotDriveBase::IsAlive() const { return m_safetyHelper.IsAlive(); }
|
||||
|
||||
bool RobotDriveBase::IsSafetyEnabled() const {
|
||||
return m_safetyHelper.IsSafetyEnabled();
|
||||
}
|
||||
|
||||
void RobotDriveBase::SetSafetyEnabled(bool enabled) {
|
||||
m_safetyHelper.SetSafetyEnabled(enabled);
|
||||
}
|
||||
void RobotDriveBase::FeedWatchdog() { Feed(); }
|
||||
|
||||
double RobotDriveBase::Limit(double value) {
|
||||
if (value > 1.0) {
|
||||
|
||||
@@ -175,6 +175,10 @@ void LiveWindow::SetEnabled(bool enabled) {
|
||||
std::lock_guard<wpi::mutex> lock(m_impl->mutex);
|
||||
if (m_impl->liveWindowEnabled == enabled) return;
|
||||
Scheduler* scheduler = Scheduler::GetInstance();
|
||||
m_impl->startLiveWindow = enabled;
|
||||
m_impl->liveWindowEnabled = enabled;
|
||||
// Force table generation now to make sure everything is defined
|
||||
UpdateValuesUnsafe();
|
||||
if (enabled) {
|
||||
scheduler->SetEnabled(false);
|
||||
scheduler->RemoveAll();
|
||||
@@ -184,13 +188,15 @@ void LiveWindow::SetEnabled(bool enabled) {
|
||||
}
|
||||
scheduler->SetEnabled(true);
|
||||
}
|
||||
m_impl->startLiveWindow = enabled;
|
||||
m_impl->liveWindowEnabled = enabled;
|
||||
m_impl->enabledEntry.SetBoolean(enabled);
|
||||
}
|
||||
|
||||
void LiveWindow::UpdateValues() {
|
||||
std::lock_guard<wpi::mutex> lock(m_impl->mutex);
|
||||
UpdateValuesUnsafe();
|
||||
}
|
||||
|
||||
void LiveWindow::UpdateValuesUnsafe() {
|
||||
// Only do this if either LiveWindow mode or telemetry is enabled.
|
||||
if (!m_impl->liveWindowEnabled && !m_impl->telemetryEnabled) return;
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* 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 "frc/shuffleboard/RecordingController.h"
|
||||
|
||||
#include "frc/DriverStation.h"
|
||||
|
||||
using namespace frc;
|
||||
using namespace frc::detail;
|
||||
|
||||
RecordingController::RecordingController(nt::NetworkTableInstance ntInstance)
|
||||
: m_recordingControlEntry(), m_recordingFileNameFormatEntry() {
|
||||
m_recordingControlEntry =
|
||||
ntInstance.GetEntry("/Shuffleboard/.recording/RecordData");
|
||||
m_recordingFileNameFormatEntry =
|
||||
ntInstance.GetEntry("/Shuffleboard/.recording/FileNameFormat");
|
||||
m_eventsTable = ntInstance.GetTable("/Shuffleboard/.recording/events");
|
||||
}
|
||||
|
||||
void RecordingController::StartRecording() {
|
||||
m_recordingControlEntry.SetBoolean(true);
|
||||
}
|
||||
|
||||
void RecordingController::StopRecording() {
|
||||
m_recordingControlEntry.SetBoolean(false);
|
||||
}
|
||||
|
||||
void RecordingController::SetRecordingFileNameFormat(wpi::StringRef format) {
|
||||
m_recordingFileNameFormatEntry.SetString(format);
|
||||
}
|
||||
|
||||
void RecordingController::ClearRecordingFileNameFormat() {
|
||||
m_recordingFileNameFormatEntry.Delete();
|
||||
}
|
||||
|
||||
void RecordingController::AddEventMarker(
|
||||
wpi::StringRef name, wpi::StringRef description,
|
||||
ShuffleboardEventImportance importance) {
|
||||
if (name.empty()) {
|
||||
DriverStation::ReportError("Shuffleboard event name was not specified");
|
||||
return;
|
||||
}
|
||||
auto arr = wpi::ArrayRef<std::string>{
|
||||
description, ShuffleboardEventImportanceName(importance)};
|
||||
m_eventsTable->GetSubTable(name)->GetEntry("Info").SetStringArray(arr);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user