mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-23 01:21:40 +00:00
Compare commits
82 Commits
v2022.1.2
...
v2023.1.1-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dcad7f34a2 | ||
|
|
72d8f49145 | ||
|
|
df852410b0 | ||
|
|
3c7165bb0d | ||
|
|
f193a2331a | ||
|
|
c7aa84ca41 | ||
|
|
209cdbf45f | ||
|
|
e03ec862a8 | ||
|
|
8169da5ad4 | ||
|
|
916431b4ff | ||
|
|
7dd1719fbd | ||
|
|
b408a58e9e | ||
|
|
a64697e714 | ||
|
|
e971db2f52 | ||
|
|
7b6afd545b | ||
|
|
0f99044468 | ||
|
|
1412155c50 | ||
|
|
b1280e49d5 | ||
|
|
aaac6a4fbb | ||
|
|
b68b0ca5f6 | ||
|
|
45d99f1f6b | ||
|
|
a42fef67f2 | ||
|
|
bd4d74c192 | ||
|
|
c4500ce12b | ||
|
|
81d19672d2 | ||
|
|
04bde1b230 | ||
|
|
4f355f2749 | ||
|
|
5e604cf98d | ||
|
|
2d7a88e231 | ||
|
|
27198a3e32 | ||
|
|
fbf6fb304e | ||
|
|
d24a8d4188 | ||
|
|
def40484e3 | ||
|
|
aff163fc6a | ||
|
|
c392d5fa4d | ||
|
|
8dbd428359 | ||
|
|
ccd3a512d6 | ||
|
|
bfc5e45cd0 | ||
|
|
a1b09100e0 | ||
|
|
2bf7a77885 | ||
|
|
d1bfb86ab4 | ||
|
|
07904589df | ||
|
|
5540bbf115 | ||
|
|
c827afb25f | ||
|
|
87e7c3ca74 | ||
|
|
4d5904dd6d | ||
|
|
9bf589ebc6 | ||
|
|
1e4a92c71f | ||
|
|
4ad9d97508 | ||
|
|
2c6b0ddac3 | ||
|
|
dafee954e0 | ||
|
|
5ac541642e | ||
|
|
ad0474d42a | ||
|
|
4b4a0a1cd9 | ||
|
|
a764ace7f2 | ||
|
|
a3bcd3ac4f | ||
|
|
661f8b2c04 | ||
|
|
72717cecf0 | ||
|
|
971ff3ac40 | ||
|
|
b80e436f02 | ||
|
|
be1a053cbe | ||
|
|
f4555dc545 | ||
|
|
54fdd1db51 | ||
|
|
1805785cc6 | ||
|
|
e62f6419b5 | ||
|
|
fa7824c616 | ||
|
|
9090aa6bcc | ||
|
|
5655ca6890 | ||
|
|
50fdfd8bce | ||
|
|
3120a6439b | ||
|
|
ab3e8c8db7 | ||
|
|
5144819ce2 | ||
|
|
d779fe23f0 | ||
|
|
b2a3f34433 | ||
|
|
b09a6d6a2d | ||
|
|
9893cf1f7e | ||
|
|
fc91daf397 | ||
|
|
a3e205cb6f | ||
|
|
553bed32b5 | ||
|
|
6c91feaf3f | ||
|
|
4ddb9aa08f | ||
|
|
4aadebdbb2 |
80
.github/workflows/main.yml
vendored
80
.github/workflows/main.yml
vendored
@@ -24,24 +24,20 @@ jobs:
|
||||
# The type of runner that the job will run on.
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Grab the docker container.
|
||||
container:
|
||||
image: docker://node:10
|
||||
|
||||
steps:
|
||||
# Checkout code.
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# Setup Node.js
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 10
|
||||
node-version: 16
|
||||
|
||||
# Run npm
|
||||
- run: |
|
||||
npm ci
|
||||
npm run build --if-present
|
||||
- run: npm update -g npm
|
||||
- run: npm ci
|
||||
- run: npm run build --if-present
|
||||
|
||||
# Upload client artifact.
|
||||
- uses: actions/upload-artifact@master
|
||||
@@ -56,7 +52,9 @@ jobs:
|
||||
steps:
|
||||
# Checkout code.
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Fetch tags.
|
||||
- name: Fetch tags
|
||||
@@ -64,9 +62,10 @@ jobs:
|
||||
|
||||
# Install Java 11.
|
||||
- name: Install Java 11
|
||||
uses: actions/setup-java@v1
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: temurin
|
||||
|
||||
# Run Gradle build.
|
||||
- name: Gradle Build
|
||||
@@ -84,12 +83,12 @@ jobs:
|
||||
|
||||
# Publish Coverage Report.
|
||||
- name: Publish Server Coverage Report
|
||||
uses: codecov/codecov-action@v1
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
file: ./photon-server/build/reports/jacoco/test/jacocoTestReport.xml
|
||||
|
||||
- name: Publish Core Coverage Report
|
||||
uses: codecov/codecov-action@v1
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
file: ./photon-core/build/reports/jacoco/test/jacocoTestReport.xml
|
||||
|
||||
@@ -98,13 +97,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
# Checkout docs.
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: 'PhotonVision/photonvision-docs.git'
|
||||
ref: master
|
||||
|
||||
# Install Python.
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.6'
|
||||
|
||||
@@ -135,12 +134,15 @@ jobs:
|
||||
|
||||
steps:
|
||||
# Checkout code.
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Install Java 11.
|
||||
- uses: actions/setup-java@v1
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: temurin
|
||||
|
||||
# Check server code with Spotless.
|
||||
- run: |
|
||||
@@ -151,6 +153,8 @@ jobs:
|
||||
|
||||
# Building photonlib
|
||||
photonlib-build-host:
|
||||
env:
|
||||
MACOSX_DEPLOYMENT_TARGET: 10.14
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -165,12 +169,13 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: "Photonlib - Build - ${{ matrix.artifact-name }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v1
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: temurin
|
||||
- run: git fetch --tags --force
|
||||
- run: |
|
||||
chmod +x gradlew
|
||||
@@ -197,12 +202,13 @@ jobs:
|
||||
container: ${{ matrix.container }}
|
||||
name: "Photonlib - Build - ${{ matrix.artifact-name }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v1
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: temurin
|
||||
- run: |
|
||||
chmod +x gradlew
|
||||
./gradlew photon-lib:build --max-workers 1
|
||||
@@ -217,14 +223,14 @@ jobs:
|
||||
name: "wpiformat"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Fetch all history and metadata
|
||||
run: |
|
||||
git fetch --prune --unshallow
|
||||
git checkout -b pr
|
||||
git branch -f master origin/master
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Install clang-format
|
||||
@@ -241,7 +247,7 @@ jobs:
|
||||
- name: Generate diff
|
||||
run: git diff HEAD > wpiformat-fixes.patch
|
||||
if: ${{ failure() }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wpiformat fixes
|
||||
path: wpiformat-fixes.patch
|
||||
@@ -255,12 +261,15 @@ jobs:
|
||||
|
||||
steps:
|
||||
# Checkout code.
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Install Java 11.
|
||||
- uses: actions/setup-java@v1
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: temurin
|
||||
|
||||
# Clear any existing web resources.
|
||||
- run: |
|
||||
@@ -268,13 +277,13 @@ jobs:
|
||||
mkdir -p photon-server/src/main/resources/web/docs
|
||||
|
||||
# Download client artifact to resources folder.
|
||||
- uses: actions/download-artifact@v2
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: built-client
|
||||
path: photon-server/src/main/resources/web/
|
||||
|
||||
# Download docs artifact to resources folder.
|
||||
- uses: actions/download-artifact@v2
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: built-docs
|
||||
path: photon-server/src/main/resources/web/docs
|
||||
@@ -293,14 +302,15 @@ jobs:
|
||||
./scripts/generatePiImage.sh
|
||||
|
||||
# Upload final fat jar as artifact.
|
||||
- uses: actions/upload-artifact@master
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: jar
|
||||
name: jars
|
||||
path: photon-server/build/libs
|
||||
- uses: actions/upload-artifact@master
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
name: image
|
||||
path: photonvision*.zip
|
||||
path: photonvision*.xz
|
||||
|
||||
- uses: pyTooling/Actions/releaser@r0
|
||||
with:
|
||||
@@ -309,7 +319,7 @@ jobs:
|
||||
rm: true
|
||||
files: |
|
||||
photon-server/build/libs/*.jar
|
||||
photonvision*.zip
|
||||
photonvision*.xz
|
||||
if: github.event_name == 'push'
|
||||
|
||||
photon-release:
|
||||
@@ -320,7 +330,7 @@ jobs:
|
||||
# This *should* pull in fat and pi-only jars
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: jar
|
||||
name: jars
|
||||
|
||||
# And the image we made previously
|
||||
- uses: actions/download-artifact@v2
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -30,6 +30,7 @@ backend/settings/
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.xz
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
@@ -143,3 +144,9 @@ build/*
|
||||
build
|
||||
photon-lib/src/main/java/org/photonvision/PhotonVersion.java
|
||||
/photonlib-java-examples/bin/
|
||||
photon-lib/src/generate/native/include/PhotonVersion.h
|
||||
.gitattributes
|
||||
lib/*
|
||||
photon-server/lib/libapriltag.so
|
||||
photon-server/bin/main/nativelibraries/apriltag/*
|
||||
photon-server/src/main/resources/nativelibraries/apriltag/*
|
||||
|
||||
@@ -11,8 +11,11 @@ cppSrcFileInclude {
|
||||
|
||||
modifiableFileExclude {
|
||||
\.jpg$
|
||||
\.jpeg$
|
||||
\.png$
|
||||
\.gif$
|
||||
\.so$
|
||||
\.dll$
|
||||
}
|
||||
|
||||
includeProject {
|
||||
@@ -25,7 +28,3 @@ includeOtherLibs {
|
||||
^units/
|
||||
^wpi/
|
||||
}
|
||||
|
||||
licenseUpdateExclude {
|
||||
\.java$
|
||||
}
|
||||
|
||||
12
build.gradle
12
build.gradle
@@ -11,7 +11,7 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven { url = "https://maven.photonvision.org/repository/internal/" }
|
||||
}
|
||||
wpilibRepositories.addAllReleaseRepositories(it)
|
||||
@@ -28,9 +28,14 @@ ext {
|
||||
pubVersion = versionString
|
||||
isDev = pubVersion.startsWith("dev")
|
||||
|
||||
if(project.hasProperty('pionly')) {
|
||||
jniPlatforms = ['linuxraspbian']
|
||||
} else if(project.hasProperty('winonly')) {
|
||||
jniPlatforms = ['windowsx86-64']
|
||||
} else {
|
||||
jniPlatforms = ['linuxaarch64bionic', 'linuxraspbian', 'linuxx86-64', 'osxx86-64', 'windowsx86-64']
|
||||
|
||||
jniPlatforms = project.hasProperty('pionly') ? ['linuxraspbian']
|
||||
: ['linuxaarch64bionic', 'linuxraspbian', 'linuxx86-64', 'osxx86-64', 'windowsx86-64']
|
||||
}
|
||||
|
||||
println("Building for archs " + jniPlatforms)
|
||||
}
|
||||
@@ -47,7 +52,6 @@ spotless {
|
||||
}
|
||||
java {
|
||||
target "**/*.java"
|
||||
licenseHeaderFile "$rootDir/LicenseHeader.txt"
|
||||
targetExclude("photon-core/src/main/java/org/photonvision/PhotonVersion.java")
|
||||
targetExclude("photon-lib/src/main/java/org/photonvision/PhotonVersion.java")
|
||||
}
|
||||
|
||||
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-7.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
227
gradlew
vendored
227
gradlew
vendored
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
# Copyright <20> 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -16,68 +16,58 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
APP_BASE_NAME=${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='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
@@ -87,9 +77,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@@ -98,7 +88,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
@@ -106,80 +96,95 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
178
gradlew.bat
vendored
178
gradlew.bat
vendored
@@ -1,89 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@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="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@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="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
|
||||
25515
photon-client/package-lock.json
generated
25515
photon-client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -16,9 +16,10 @@
|
||||
"jspdf": "^2.4.0",
|
||||
"material-design-icons-iconfont": "^5.0.1",
|
||||
"msgpack5": "^4.2.1",
|
||||
"three-full": "^28.0.2",
|
||||
"vue": "^2.6.12",
|
||||
"vue-axios": "^2.1.5",
|
||||
"vue-native-websocket": "git+https://github.com/PhotonVision/vue-native-websocket.git#7a32791",
|
||||
"vue-native-websocket": "git+https://git@github.com/PhotonVision/vue-native-websocket.git#5189f29",
|
||||
"vue-router": "^3.4.3",
|
||||
"vuetify": "^2.3.10",
|
||||
"vuex": "^3.5.1"
|
||||
|
||||
BIN
photon-client/public/loading.gif
Normal file
BIN
photon-client/public/loading.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
317
photon-client/public/thinclient.html
Normal file
317
photon-client/public/thinclient.html
Normal file
@@ -0,0 +1,317 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>ThinClient</title>
|
||||
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.imgbox {
|
||||
display: grid;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.center-fit {
|
||||
|
||||
width: 90vw;
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<hr>
|
||||
<div class="imgbox">
|
||||
<img id="streamImg" class="center-fit" src=''>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
<form id="frm1">
|
||||
Host <input type="text" id="host" value="photonvision.local"><br>
|
||||
Port <input type="text" id="port" value="1181"><br>
|
||||
</form>
|
||||
|
||||
<button>Start Stream</button>
|
||||
|
||||
<script type="module">
|
||||
class WebsocketVideoStream{
|
||||
|
||||
constructor(drawDiv, streamPort, host) {
|
||||
|
||||
this.drawDiv = drawDiv;
|
||||
this.image = document.getElementById(this.drawDiv);
|
||||
this.streamPort = streamPort;
|
||||
this.newStreamPortReq = null;
|
||||
this.serverAddr = "ws://" + host + "/websocket_cameras";
|
||||
this.dispNoStream();
|
||||
this.ws_connect();
|
||||
this.imgData = null;
|
||||
this.imgDataTime = -1;
|
||||
this.imgObjURL = null;
|
||||
this.frameRxCount = 0;
|
||||
|
||||
//Display state machine
|
||||
this.DSM_DISCONNECTED = "DISCONNECTED";
|
||||
this.DSM_WAIT_FOR_VALID_PORT = "WAIT_FOR_VALID_PORT";
|
||||
this.DSM_SUBSCRIBE = "SUBSCRIBE";
|
||||
this.DSM_WAIT_FOR_FIRST_FRAME = "WAIT_FOR_FIRST_FRAME";
|
||||
this.DSM_SHOWING = "SHOWING";
|
||||
this.DSM_RESTART_UNSUBSCRIBE = "UNSUBSCRIBE";
|
||||
this.DSM_RESTART_WAIT = "WAIT_BEFORE_SUBSCRIBE";
|
||||
|
||||
this.dsm_cur_state = this.DSM_DISCONNECTED;
|
||||
this.dsm_prev_state = this.DSM_DISCONNECTED;
|
||||
this.dsm_restart_start_time = window.performance.now();
|
||||
|
||||
requestAnimationFrame(()=>this.animationLoop());
|
||||
}
|
||||
|
||||
dispImageData(){
|
||||
//From https://stackoverflow.com/questions/67507616/set-image-src-from-image-blob/67507685#67507685
|
||||
if(this.imgObjURL != null){
|
||||
URL.revokeObjectURL(this.imgObjURL)
|
||||
}
|
||||
this.imgObjURL = URL.createObjectURL(this.imgData);
|
||||
|
||||
//Update the image with the new mimetype and image
|
||||
this.image.src = this.imgObjURL;
|
||||
}
|
||||
|
||||
dispNoStream() {
|
||||
this.image.src = "loading.gif";
|
||||
}
|
||||
|
||||
animationLoop(){
|
||||
// Update time metrics
|
||||
var now = window.performance.now();
|
||||
var timeInState = now - this.dsm_restart_start_time;
|
||||
|
||||
// Save previous state
|
||||
this.dsm_prev_state = this.dsm_cur_state;
|
||||
|
||||
// Evaluate state transitions
|
||||
if(this.serverConnectionActive == false){
|
||||
//Any state - if the server connection goes false, always transition to disconnected
|
||||
this.dsm_cur_state = this.DSM_DISCONNECTED;
|
||||
} else {
|
||||
//Conditional transitions
|
||||
switch(this.dsm_cur_state) {
|
||||
case this.DSM_DISCONNECTED:
|
||||
//Immediately transition to waiting for the first frame
|
||||
this.dsm_cur_state = this.DSM_WAIT_FOR_VALID_PORT;
|
||||
break;
|
||||
case this.DSM_WAIT_FOR_VALID_PORT:
|
||||
// Wait until the user has configured a valid port
|
||||
if(this.streamPort > 0){
|
||||
this.dsm_cur_state = this.DSM_SUBSCRIBE;
|
||||
} else {
|
||||
this.dsm_cur_state = this.DSM_WAIT_FOR_VALID_PORT;
|
||||
}
|
||||
break;
|
||||
case this.DSM_SUBSCRIBE:
|
||||
// Immediately transition after subscriptions is sent
|
||||
this.dsm_cur_state = this.DSM_WAIT_FOR_FIRST_FRAME;
|
||||
break;
|
||||
case this.DSM_WAIT_FOR_FIRST_FRAME:
|
||||
if(this.imgData != null){
|
||||
//we got some image data, start showing it
|
||||
this.dsm_cur_state = this.DSM_SHOWING;
|
||||
} else if (this.newStreamPortReq != null){
|
||||
//Stream port requested changed, unsubscribe and restart
|
||||
this.dsm_cur_state = this.DSM_RESTART_UNSUBSCRIBE;
|
||||
} else {
|
||||
this.dsm_cur_state = this.DSM_WAIT_FOR_FIRST_FRAME;
|
||||
}
|
||||
break;
|
||||
case this.DSM_SHOWING:
|
||||
if((now - this.imgDataTime) > 2500){
|
||||
//timeout, begin the restart sequence
|
||||
this.dsm_cur_state = this.DSM_RESTART_UNSUBSCRIBE;
|
||||
} else if (this.newStreamPortReq != null){
|
||||
//Stream port requested changed, unsubscribe and restart
|
||||
this.dsm_cur_state = this.DSM_RESTART_UNSUBSCRIBE;
|
||||
} else {
|
||||
//stay in this state.
|
||||
this.dsm_cur_state = this.DSM_SHOWING;
|
||||
}
|
||||
break;
|
||||
case this.DSM_RESTART_UNSUBSCRIBE:
|
||||
//Only should spend one loop in Unsubscribe, immediately transition
|
||||
this.dsm_cur_state = this.DSM_RESTART_WAIT;
|
||||
break;
|
||||
case this.DSM_RESTART_WAIT:
|
||||
if (timeInState > 250) {
|
||||
//we've waited long enough, go to try to re-subscribe
|
||||
this.dsm_cur_state = this.DSM_WAIT_FOR_VALID_PORT;
|
||||
} else {
|
||||
//stay in this state.
|
||||
this.dsm_cur_state = this.DSM_RESTART_WAIT;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Shouldn't get here, default back to init
|
||||
this.dsm_cur_state = this.DSM_DISCONNECTED;
|
||||
}
|
||||
}
|
||||
|
||||
//take current-state or state-transition actions
|
||||
|
||||
if(this.dsm_cur_state != this.dsm_prev_state){
|
||||
//Any state transition
|
||||
console.log("State Change: " + this.dsm_prev_state + " -> " + this.dsm_cur_state);
|
||||
}
|
||||
|
||||
if(this.dsm_cur_state == this.DSM_SHOWING){
|
||||
// Currently in SHOWING
|
||||
this.dispImageData();
|
||||
}
|
||||
|
||||
if(this.dsm_cur_state != this.DSM_SHOWING && this.dsm_prev_state == this.DSM_SHOWING ){
|
||||
//Any transition out of showing - no stream
|
||||
this.dispNoStream();
|
||||
}
|
||||
|
||||
if(this.dsm_cur_state == this.DSM_RESTART_UNSUBSCRIBE){
|
||||
// Currently in UNSUBSCRIBE, do the unsubscribe actions
|
||||
this.stopStream();
|
||||
this.dsm_restart_start_time = now;
|
||||
}
|
||||
|
||||
if(this.dsm_cur_state == this.DSM_SUBSCRIBE){
|
||||
// Currently in SUBSCRIBE, do the subscribe actions
|
||||
this.startStream();
|
||||
this.dsm_restart_start_time = now;
|
||||
}
|
||||
|
||||
if(this.dsm_cur_state == this.DSM_WAIT_FOR_VALID_PORT){
|
||||
// Currently waiting for a vaild port to be requested
|
||||
if(this.newStreamPortReq != null){
|
||||
this.streamPort = this.newStreamPortReq;
|
||||
this.newStreamPortReq = null;
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(()=>this.animationLoop());
|
||||
}
|
||||
|
||||
startStream() {
|
||||
console.log("Subscribing to port " + this.streamPort);
|
||||
this.imgData = null;
|
||||
this.ws.send(JSON.stringify({"cmd": "subscribe", "port":this.streamPort}));
|
||||
}
|
||||
|
||||
stopStream() {
|
||||
console.log("Unsubscribing");
|
||||
this.ws.send(JSON.stringify({"cmd": "unsubscribe"}));
|
||||
this.imgData = null;
|
||||
}
|
||||
|
||||
setPort(streamPort){
|
||||
console.log("Port set to " + streamPort);
|
||||
this.newStreamPortReq = streamPort;
|
||||
}
|
||||
|
||||
ws_onOpen() {
|
||||
// Set the flag allowing general server communication
|
||||
this.serverConnectionActive = true;
|
||||
console.log("Connected!");
|
||||
}
|
||||
|
||||
ws_onClose(e) {
|
||||
//Clear flags to stop server communication
|
||||
this.ws = null;
|
||||
this.serverConnectionActive = false;
|
||||
|
||||
console.log('Camera Socket is closed. Reconnect will be attempted in 0.5 second.', e.reason);
|
||||
setTimeout(this.ws_connect.bind(this), 500);
|
||||
|
||||
if(!e.wasClean){
|
||||
console.error('Socket encountered error!');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ws_onError(e){
|
||||
e; //prevent unused failure
|
||||
this.ws.close();
|
||||
}
|
||||
|
||||
ws_onMessage(e){
|
||||
if(typeof e.data === 'string'){
|
||||
//string data from host
|
||||
//TODO - anything to recieve info here? Maybe "avaialble streams?"
|
||||
} else {
|
||||
if(e.data.size > 0){
|
||||
//binary data - a frame
|
||||
this.imgData = e.data;
|
||||
this.imgDataTime = window.performance.now();
|
||||
this.frameRxCount++;
|
||||
} else {
|
||||
//TODO - server is sending empty frames?
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ws_connect() {
|
||||
this.serverConnectionActive = false;
|
||||
this.ws = new WebSocket(this.serverAddr);
|
||||
this.ws.binaryType = "blob";
|
||||
this.ws.onopen = this.ws_onOpen.bind(this);
|
||||
this.ws.onmessage = this.ws_onMessage.bind(this);
|
||||
this.ws.onclose = this.ws_onClose.bind(this);
|
||||
this.ws.onerror = this.ws_onError.bind(this);
|
||||
console.log("Connecting to server " + this.serverAddr);
|
||||
}
|
||||
|
||||
ws_close(){
|
||||
this.ws.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var stream = null;
|
||||
|
||||
function streamStartRequest() {
|
||||
var host = document.getElementById("host").value + ":5800";
|
||||
var port = document.getElementById("port").value;
|
||||
if(stream == null){
|
||||
stream = new WebsocketVideoStream("streamImg",port,host);
|
||||
} else {
|
||||
stream.setPort(port);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Attach listener
|
||||
document.querySelector('button').addEventListener('click', streamStartRequest);
|
||||
|
||||
// Deal with URLParams, validating inputs
|
||||
const queryString = window.location.search;
|
||||
const urlParams = new URLSearchParams(queryString);
|
||||
const port_in = urlParams.get('port')
|
||||
const host_in = urlParams.get('host')
|
||||
if(port_in != ""){
|
||||
document.getElementById("port").value = port_in;
|
||||
}
|
||||
|
||||
if(host_in != ""){
|
||||
document.getElementById("host").value = host_in;
|
||||
}
|
||||
|
||||
if(port_in != "" & host_in != ""){
|
||||
streamStartRequest(); //we got valid inputs, auto-start the stream
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
|
||||
</html>
|
||||
@@ -87,31 +87,56 @@
|
||||
</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>Advanced Mode</v-list-item-title>
|
||||
<v-list-item-title>Compact Mode</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<div style="position: absolute; bottom: 0; left: 0;">
|
||||
<v-list-item>
|
||||
<v-list-item-icon>
|
||||
<v-icon v-if="$store.state.settings.networkSettings.runNTServer">mdi-server</v-icon>
|
||||
<img v-else-if="$store.state.ntConnectionInfo.connected" src="@/assets/robot.svg" alt="">
|
||||
<img v-else class="pulse" style="border-radius: 100%" src="@/assets/robot-off.svg" alt="">
|
||||
<v-icon v-if="$store.state.settings.networkSettings.runNTServer">
|
||||
mdi-server
|
||||
</v-icon>
|
||||
<img
|
||||
v-else-if="$store.state.ntConnectionInfo.connected"
|
||||
src="@/assets/robot.svg"
|
||||
alt=""
|
||||
>
|
||||
<img
|
||||
v-else
|
||||
class="pulse"
|
||||
style="border-radius: 100%"
|
||||
src="@/assets/robot-off.svg"
|
||||
alt=""
|
||||
>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title class="text-wrap" v-if="$store.state.settings.networkSettings.runNTServer">
|
||||
NetworkTables server running for {{$store.state.ntConnectionInfo.clients ? $store.state.ntConnectionInfo.clients : 'zero'}} clients!
|
||||
<v-list-item-title
|
||||
v-if="$store.state.settings.networkSettings.runNTServer"
|
||||
class="text-wrap"
|
||||
>
|
||||
NetworkTables server running for {{ $store.state.ntConnectionInfo.clients ? $store.state.ntConnectionInfo.clients : 'zero' }} clients!
|
||||
</v-list-item-title>
|
||||
<v-list-item-title class="text-wrap" v-else-if="$store.state.ntConnectionInfo.connected && $store.state.backendConnected">
|
||||
Robot connected! {{$store.state.ntConnectionInfo.address}}
|
||||
<v-list-item-title
|
||||
v-else-if="$store.state.ntConnectionInfo.connected && $store.state.backendConnected"
|
||||
class="text-wrap"
|
||||
>
|
||||
Robot connected! {{ $store.state.ntConnectionInfo.address }}
|
||||
</v-list-item-title>
|
||||
<v-list-item-title class="text-wrap" v-else>
|
||||
<v-list-item-title
|
||||
v-else
|
||||
class="text-wrap"
|
||||
>
|
||||
Not connected to robot!
|
||||
</v-list-item-title>
|
||||
<a
|
||||
href="/#/settings"
|
||||
style="color:#FFD843"
|
||||
>{{"Team: " + $store.state.settings.networkSettings.teamNumber}}</a>
|
||||
<router-link
|
||||
v-if="!$store.state.settings.networkSettings.runNTServer"
|
||||
to="settings"
|
||||
class="accent--text"
|
||||
@click="switchToSettingsTab"
|
||||
>
|
||||
Team number is {{ $store.state.settings.networkSettings.teamNumber }}
|
||||
</router-link>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
@@ -121,9 +146,9 @@
|
||||
mdi-wifi
|
||||
</v-icon>
|
||||
<v-icon
|
||||
v-else
|
||||
class="pulse"
|
||||
style="border-radius: 100%;"
|
||||
v-else
|
||||
class="pulse"
|
||||
style="border-radius: 100%;"
|
||||
>
|
||||
mdi-wifi-off
|
||||
</v-icon>
|
||||
@@ -135,7 +160,6 @@
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</div>
|
||||
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
<v-main>
|
||||
@@ -159,23 +183,27 @@
|
||||
<logs />
|
||||
</v-dialog>
|
||||
<v-dialog
|
||||
v-model="needsTeamNumberSet"
|
||||
width="500"
|
||||
dark
|
||||
persistent
|
||||
v-model="needsTeamNumberSet"
|
||||
width="500"
|
||||
dark
|
||||
persistent
|
||||
>
|
||||
<v-card
|
||||
dark
|
||||
color="primary"
|
||||
flat
|
||||
dark
|
||||
color="primary"
|
||||
flat
|
||||
>
|
||||
<v-card-title>No team number set!</v-card-title>
|
||||
<v-card-text>
|
||||
PhotonVision cannot connect to your robot! Please
|
||||
<a
|
||||
href="/#/settings"
|
||||
style="color:#FFD843"
|
||||
>head to the settings page</a> and set your team number.
|
||||
<router-link
|
||||
to="settings"
|
||||
class="accent--text"
|
||||
@click="switchToSettingsTab"
|
||||
>
|
||||
visit the settings tab
|
||||
</router-link>
|
||||
and set your team number.
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
@@ -219,12 +247,6 @@ export default {
|
||||
localStorage.setItem("compactMode", value);
|
||||
},
|
||||
},
|
||||
// ...mapState({
|
||||
// ntServerMode: state => state.settings.networkSettings.runNTServer,
|
||||
// ntClients: state => state.ntConnectionInfo.clients,
|
||||
// ntConnected: state => state.ntConnectionInfo.connected,
|
||||
// backendConnected: state => state.backendConnected
|
||||
// })
|
||||
},
|
||||
created() {
|
||||
document.addEventListener("keydown", e => {
|
||||
@@ -286,11 +308,7 @@ export default {
|
||||
} else if (this.$store.state.settings.hasOwnProperty(key)) {
|
||||
this.$store.commit('mutateSettings', {[key]: value});
|
||||
} else {
|
||||
switch (key) {
|
||||
default: {
|
||||
console.error("Unknown message from backend: " + value);
|
||||
}
|
||||
}
|
||||
console.error("Unknown message from backend: " + value);
|
||||
}
|
||||
},
|
||||
toggleCompactMode() {
|
||||
@@ -319,8 +337,7 @@ export default {
|
||||
}
|
||||
}
|
||||
this.previouslySelectedIndices = null;
|
||||
}
|
||||
,
|
||||
},
|
||||
switchToSettingsTab() {
|
||||
this.axios.post('http://' + this.$address + '/api/sendMetrics', {})
|
||||
}
|
||||
|
||||
BIN
photon-client/src/assets/loading.gif
Normal file
BIN
photon-client/src/assets/loading.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 76 KiB |
@@ -5,7 +5,7 @@
|
||||
:style="styleObject"
|
||||
:src="src"
|
||||
alt=""
|
||||
@click="e => $emit('click', e)"
|
||||
@click="e => {this.openThinclientStream(e)}"
|
||||
>
|
||||
</template>
|
||||
|
||||
@@ -13,10 +13,11 @@
|
||||
export default {
|
||||
name: "CvImage",
|
||||
// eslint-disable-next-line vue/require-prop-types
|
||||
props: ['address', 'scale', 'maxHeight', 'maxHeightMd', 'maxHeightXl', 'colorPicking', 'id', 'disconnected'],
|
||||
props: ['idx', 'scale', 'maxHeight', 'maxHeightMd', 'maxHeightLg', 'maxHeightXl', 'colorPicking', 'id', 'disconnected'],
|
||||
data() {
|
||||
return {
|
||||
seed: 1.0,
|
||||
src: ""
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -37,25 +38,56 @@
|
||||
|
||||
if (this.$vuetify.breakpoint.xl) {
|
||||
ret["max-height"] = this.maxHeightXl;
|
||||
} else if (this.$vuetify.breakpoint.mdAndUp) {
|
||||
} else if (this.$vuetify.breakpoint.lg) {
|
||||
ret["max-height"] = this.maxHeightLg;
|
||||
} else if (this.$vuetify.breakpoint.md) {
|
||||
ret["max-height"] = this.maxHeightMd;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
},
|
||||
src: {
|
||||
port: {
|
||||
get() {
|
||||
return this.disconnected ? require("../../assets/noStream.jpg") : this.address + "?" + this.seed // This prevents caching
|
||||
},
|
||||
},
|
||||
if(this.idx == 0){
|
||||
return this.$store.state.cameraSettings[this.$store.state.currentCameraIndex].inputStreamPort;
|
||||
} else {
|
||||
return this.$store.state.cameraSettings[this.$store.state.currentCameraIndex].outputStreamPort;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch : {
|
||||
port(newPort, oldPort){
|
||||
newPort;
|
||||
oldPort;
|
||||
this.reload();
|
||||
},
|
||||
disconnected(newVal, oldVal){
|
||||
oldVal;
|
||||
if(newVal){
|
||||
this.wsStream.setPort(0);
|
||||
} else {
|
||||
this.wsStream.setPort(this.port);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.reload(); // Force reload image on creation
|
||||
var wsvs = require('../../plugins/WebsocketVideoStream');
|
||||
this.wsStream = new wsvs.WebsocketVideoStream(this.id, this.port, this.$address);
|
||||
},
|
||||
unmounted() {
|
||||
this.wsStream.setPort(0);
|
||||
},
|
||||
methods: {
|
||||
reload() {
|
||||
this.seed = new Date().getTime();
|
||||
console.log("Reloading " + this.id + " with port " + String(this.port));
|
||||
this.wsStream.setPort(this.port);
|
||||
},
|
||||
openThinclientStream(e){
|
||||
e;
|
||||
var URL = "/thinclient.html?port=" + String(this.port) + "&host=" + window.location.hostname;
|
||||
window.open(URL, '_blank');
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,28 +1,46 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-radio-group
|
||||
v-model="localValue"
|
||||
row
|
||||
dark
|
||||
:mandatory="true"
|
||||
<v-row
|
||||
dense
|
||||
align="center"
|
||||
>
|
||||
<v-radio
|
||||
v-for="(name,index) in list"
|
||||
:key="index"
|
||||
color="#ffd843"
|
||||
:label="name"
|
||||
:value="index"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</v-radio-group>
|
||||
<v-col :cols="12 - (inputCols || 8)">
|
||||
<tooltipped-label
|
||||
:tooltip="tooltip"
|
||||
:text="name"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col :cols="inputCols || 8">
|
||||
<v-radio-group
|
||||
v-model="localValue"
|
||||
row
|
||||
dark
|
||||
:mandatory="true"
|
||||
>
|
||||
<v-radio
|
||||
v-for="(radioName,index) in list"
|
||||
:key="index"
|
||||
color="#ffd843"
|
||||
:label="radioName"
|
||||
:value="index"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</v-radio-group>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TooltippedLabel from "./cv-tooltipped-label";
|
||||
|
||||
export default {
|
||||
name: 'Radio',
|
||||
components: {
|
||||
TooltippedLabel
|
||||
},
|
||||
// eslint-disable-next-line vue/require-prop-types
|
||||
props: ['value', 'list', 'disabled'],
|
||||
props: ['name', 'value', 'list', 'disabled', 'inputCols', 'tooltip'],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
hide-details
|
||||
class="align-center"
|
||||
dark
|
||||
color="accent"
|
||||
:color="inverted ? 'rgba(255, 255, 255, 0.2)' : 'accent'"
|
||||
:track-color="inverted ? 'accent' : undefined"
|
||||
thumb-color="accent"
|
||||
:step="step"
|
||||
@input="handleInput"
|
||||
@mousedown="$emit('rollback', localValue)"
|
||||
@@ -76,7 +78,7 @@ export default {
|
||||
TooltippedLabel,
|
||||
},
|
||||
// eslint-disable-next-line vue/require-prop-types
|
||||
props: ["name", "min", "max", "value", "step", "tooltip", "disabled"],
|
||||
props: ["name", "min", "max", "value", "step", "tooltip", "disabled", "inverted"],
|
||||
data() {
|
||||
return {
|
||||
prependFocused: false,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<v-tooltip
|
||||
:disabled="tooltip === undefined"
|
||||
right
|
||||
open-delay="600"
|
||||
open-delay="300"
|
||||
>
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<span
|
||||
|
||||
@@ -1,154 +1,268 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
id="MapContainer"
|
||||
style="flex-grow:1"
|
||||
>
|
||||
<v-row>
|
||||
<v-col
|
||||
align="center"
|
||||
cols="12"
|
||||
>
|
||||
<span class="white--text">Target Location</span>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col
|
||||
align="center"
|
||||
cols="12"
|
||||
align-self="stretch"
|
||||
>
|
||||
<canvas
|
||||
id="canvasId"
|
||||
class="mt-2"
|
||||
width="800"
|
||||
height="800"
|
||||
style="width:100%;height:100%"
|
||||
/>
|
||||
</v-col>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-btn
|
||||
class="ml-10"
|
||||
color="secondary"
|
||||
@click="resetCamFirstPerson"
|
||||
>
|
||||
First Person
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-btn
|
||||
class="ml-10"
|
||||
color="secondary"
|
||||
@click="resetCamThirdPerson"
|
||||
>
|
||||
Third Person
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import theme from "../../../theme";
|
||||
|
||||
export default {
|
||||
name: "MiniMap",
|
||||
props: {
|
||||
// eslint-disable-next-line vue/require-default-prop
|
||||
targets: Array,
|
||||
// eslint-disable-next-line vue/require-default-prop
|
||||
horizontalFOV: Number
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ctx: undefined,
|
||||
canvas: undefined,
|
||||
x: 0,
|
||||
y: 0,
|
||||
targetWidth: 40,
|
||||
targetHeight: 6
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hLen: {
|
||||
get() {
|
||||
return Math.tan(this.horizontalFOV / 2 * Math.PI / 180) * 150;
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
targets: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.draw();
|
||||
}
|
||||
},
|
||||
horizontalFOV() {
|
||||
this.draw();
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
const canvas = document.getElementById("canvasId"); // getting the canvas element
|
||||
const ctx = canvas.getContext("2d"); // getting the canvas context
|
||||
this.canvas = canvas; // setting the canvas as a vue variable
|
||||
this.ctx = ctx; // setting the canvas context as a vue variable
|
||||
this.grad = this.ctx.createLinearGradient(400, 800, 400, 600);
|
||||
this.grad.addColorStop(0, "rgb(119,119,119)");
|
||||
this.grad.addColorStop(0.05, "rgba(14,92,22,0.96)");
|
||||
this.grad.addColorStop(0.8, 'rgba(43,43,43,0.48)');
|
||||
import {
|
||||
ArrowHelper,
|
||||
BoxGeometry,
|
||||
ConeGeometry,
|
||||
Mesh,
|
||||
MeshNormalMaterial,
|
||||
PerspectiveCamera,
|
||||
Quaternion,
|
||||
Scene,
|
||||
TrackballControls,
|
||||
Vector3,
|
||||
Color,
|
||||
WebGLRenderer
|
||||
} from "three-full";
|
||||
|
||||
// setting canvas context values for drawing
|
||||
export default {
|
||||
name: "MiniMap",
|
||||
props: {
|
||||
// eslint-disable-next-line vue/require-default-prop
|
||||
targets: Array,
|
||||
// eslint-disable-next-line vue/require-default-prop
|
||||
horizontalFOV: Number
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
scene: undefined,
|
||||
cubes: [],
|
||||
|
||||
|
||||
this.ctx.font = "26px Arial";
|
||||
this.ctx.strokeStyle = "whitesmoke";
|
||||
this.ctx.lineWidth = 2;
|
||||
|
||||
this.$nextTick(function () {
|
||||
this.drawPlayer();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
draw() {
|
||||
this.clearBoard();
|
||||
this.drawPlayer();
|
||||
for (let index in this.targets) {
|
||||
this.drawTarget(index, this.targets[index].pose);
|
||||
}
|
||||
},
|
||||
drawTarget(index, target) {
|
||||
// first save the untranslated/unrotated context
|
||||
let x = 800 - (160 * target.x); // getting meters as pixels
|
||||
let y = 400 - (160 * target.y);
|
||||
this.ctx.save();
|
||||
this.ctx.beginPath();
|
||||
// move the rotation point to the center of the rect
|
||||
this.ctx.translate(y + this.targetWidth / 2, x + this.targetHeight / 2); // wpi lib makes x forward and back and y left to right
|
||||
// rotate the rect
|
||||
this.ctx.rotate(target.rot * -1 * Math.PI / 180.0);
|
||||
|
||||
// draw the rect on the transformed context
|
||||
// Note: after transforming [0,0] is visually [x,y]
|
||||
// so the rect needs to be offset accordingly when drawn
|
||||
this.ctx.rect(-this.targetWidth / 2, -this.targetHeight / 2, this.targetWidth, this.targetHeight);
|
||||
|
||||
this.ctx.fillStyle = theme.accent;
|
||||
this.ctx.fill();
|
||||
|
||||
// restore the context to its untranslated/unrotated state
|
||||
this.ctx.restore();
|
||||
this.ctx.fillStyle = "whitesmoke";
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(y + this.targetWidth / 2, x + this.targetHeight / 2, 3, 0, 2 * Math.PI, true);
|
||||
this.ctx.fill();
|
||||
this.ctx.fillText(index, y - 30, x - 5);
|
||||
|
||||
},
|
||||
drawPlayer() {
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(400, 820);
|
||||
this.ctx.lineTo(400 + this.hLen, 650);
|
||||
this.ctx.lineTo(400 - this.hLen, 650);
|
||||
this.ctx.closePath();
|
||||
this.ctx.fillStyle = this.grad;
|
||||
this.ctx.fill();
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(400, 820);
|
||||
this.ctx.lineTo(400 + this.hLen, 650);
|
||||
this.ctx.stroke();
|
||||
this.ctx.moveTo(400, 820);
|
||||
this.ctx.lineTo(400 - this.hLen, 650);
|
||||
this.ctx.stroke();
|
||||
|
||||
},
|
||||
clearBoard() {
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); // clearing the canvas
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
targets: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.drawTargets();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const scene = new Scene();
|
||||
this.scene = scene;
|
||||
const camera = new PerspectiveCamera(75, 800 / 800, 0.1, 1000);
|
||||
this.camera = camera;
|
||||
|
||||
const canvas = document.getElementById("canvasId"); // getting the canvas element
|
||||
this.canvas = canvas;
|
||||
const renderer = new WebGLRenderer({"canvas": canvas});
|
||||
this.renderer = renderer;
|
||||
scene.background = new Color(0xa9a9a9)
|
||||
|
||||
//Set up resize handlers
|
||||
this.onWindowResize();
|
||||
window.addEventListener( 'resize', this.onWindowResize, false );
|
||||
|
||||
//Add the reference frame cues
|
||||
this.refFrameCues = []
|
||||
// coordinate system
|
||||
this.refFrameCues.push(new ArrowHelper(new Vector3(1, 0, 0).normalize(), new Vector3(0, 0, 0),
|
||||
1, // length
|
||||
0xff0000,
|
||||
0.1,
|
||||
0.1,
|
||||
))
|
||||
this.refFrameCues.push(new ArrowHelper(new Vector3(0, 1, 0).normalize(), new Vector3(0, 0, 0),
|
||||
1, // length
|
||||
0x00ff00,
|
||||
0.1,
|
||||
0.1,
|
||||
))
|
||||
this.refFrameCues.push(new ArrowHelper(new Vector3(0, 0, 1).normalize(), new Vector3(0, 0, 0),
|
||||
1, // length
|
||||
0x0000ff,
|
||||
0.1,
|
||||
0.1,
|
||||
))
|
||||
|
||||
//something that looks vaguely like a camera
|
||||
const camSize = 0.2;
|
||||
const camBodyGeometry = new BoxGeometry(camSize, camSize, camSize);
|
||||
const camLensGeometry = new ConeGeometry(camSize*0.4, camSize*0.8, 30);
|
||||
const camMaterial = new MeshNormalMaterial();
|
||||
const camBody = new Mesh(camBodyGeometry, camMaterial);
|
||||
const camLens = new Mesh(camLensGeometry, camMaterial);
|
||||
camBody.position.set(0,0,0);
|
||||
camLens.rotateZ(Math.PI / 2);
|
||||
camLens.position.set(camSize*0.8,0,0);
|
||||
this.refFrameCues.push(camBody)
|
||||
this.refFrameCues.push(camLens)
|
||||
|
||||
var controls = new TrackballControls(
|
||||
camera,
|
||||
renderer.domElement
|
||||
);
|
||||
controls.rotateSpeed = 1.0;
|
||||
controls.zoomSpeed = 1.2;
|
||||
controls.panSpeed = 0.8;
|
||||
controls.noZoom = false;
|
||||
controls.noPan = false;
|
||||
controls.staticMoving = true;
|
||||
controls.dynamicDampingFactor = 0.3;
|
||||
controls.keys = [65, 83, 68];
|
||||
this.controls = controls;
|
||||
|
||||
this.scene.add(...this.refFrameCues)
|
||||
this.resetCamFirstPerson();
|
||||
|
||||
controls.update();
|
||||
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
controls.update();
|
||||
renderer.render(scene, camera);
|
||||
|
||||
//camera.updateMatrixWorld();
|
||||
//console.log("================")
|
||||
//console.log(camera.position);
|
||||
//console.log(camera.rotation);
|
||||
//console.log(camera.up);
|
||||
|
||||
}
|
||||
|
||||
this.drawTargets()
|
||||
|
||||
animate();
|
||||
},
|
||||
methods: {
|
||||
drawTargets() {
|
||||
this.scene.remove(...this.cubes)
|
||||
this.cubes = []
|
||||
|
||||
for (const target of this.targets) {
|
||||
const geometry = new BoxGeometry(0.3 / 5, 0.2, 0.2);
|
||||
const material = new MeshNormalMaterial();
|
||||
let quat = (new Quaternion(
|
||||
target.pose.qx,
|
||||
target.pose.qy,
|
||||
target.pose.qz,
|
||||
target.pose.qw,
|
||||
))
|
||||
const cube = new Mesh(geometry, material);
|
||||
cube.position.set(target.pose.x, target.pose.y, target.pose.z)
|
||||
cube.rotation.setFromQuaternion(quat);
|
||||
this.cubes.push(cube)
|
||||
|
||||
let arrow = (new ArrowHelper(new Vector3(1, 0, 0).normalize(), new Vector3(0, 0, 0),
|
||||
1, // length
|
||||
0xff0000,
|
||||
0.1,
|
||||
0.1,
|
||||
));
|
||||
arrow.rotation.setFromQuaternion(quat)
|
||||
arrow.rotateZ(-Math.PI / 2)
|
||||
arrow.position.set(target.pose.x, target.pose.y, target.pose.z)
|
||||
this.cubes.push(arrow);
|
||||
|
||||
arrow = (new ArrowHelper(new Vector3(1, 0, 0).normalize(), new Vector3(0, 0, 0),
|
||||
1, // length
|
||||
0x00ff00,
|
||||
0.1,
|
||||
0.1,
|
||||
));
|
||||
arrow.rotation.setFromQuaternion(quat)
|
||||
// arrow.rotateX(Math.PI / 2)
|
||||
arrow.position.set(target.pose.x, target.pose.y, target.pose.z)
|
||||
this.cubes.push(arrow);
|
||||
arrow = (new ArrowHelper(new Vector3(1, 0, 0).normalize(), new Vector3(0, 0, 0),
|
||||
1, // length
|
||||
0x0000ff,
|
||||
0.1,
|
||||
0.1,
|
||||
));
|
||||
arrow.setRotationFromQuaternion(quat)
|
||||
arrow.rotateX(Math.PI / 2)
|
||||
arrow.position.set(target.pose.x, target.pose.y, target.pose.z)
|
||||
this.cubes.push(arrow);
|
||||
}
|
||||
if(this.cubes.length > 0)
|
||||
this.scene.add(...this.cubes);
|
||||
},
|
||||
|
||||
onWindowResize() {
|
||||
var container = document.getElementById("MapContainer")
|
||||
if(container){
|
||||
this.canvas.width = container.clientWidth * 0.95;
|
||||
this.canvas.height = container.clientWidth * 0.85;
|
||||
this.camera.aspect = this.canvas.width / this.canvas.height;
|
||||
this.camera.updateProjectionMatrix();
|
||||
this.renderer.setSize( this.canvas.width, this.canvas.height );
|
||||
}
|
||||
},
|
||||
resetCamThirdPerson(){
|
||||
//Sets camera to third person position
|
||||
this.controls.reset();
|
||||
this.camera.position.set(-1.39,-1.09,1.17);
|
||||
this.camera.up.set(0,0,1);
|
||||
this.controls.target.set(4.0,0.0,0.0);
|
||||
this.controls.update();
|
||||
this.scene.add(...this.refFrameCues)
|
||||
},
|
||||
resetCamFirstPerson(){
|
||||
//Sets camera to first person position
|
||||
this.controls.reset();
|
||||
this.camera.position.set(-0.1,0,0);
|
||||
this.camera.up.set(0,0,1);
|
||||
this.controls.target.set(0.0,0.0,0.0);
|
||||
this.controls.update();
|
||||
this.scene.remove(...this.refFrameCues)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#canvasId {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
background-color: #232C37;
|
||||
border-radius: 5px;
|
||||
border: 2px solid grey;
|
||||
box-shadow: 0 0 5px 1px;
|
||||
}
|
||||
|
||||
th {
|
||||
width: 80px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-row align="center">
|
||||
<v-row
|
||||
align="center"
|
||||
class="pl-6"
|
||||
>
|
||||
<v-col
|
||||
cols="10"
|
||||
md="5"
|
||||
lg="10"
|
||||
class="pt-0 pb-0 pl-6"
|
||||
no-gutters
|
||||
class="pa-0"
|
||||
>
|
||||
<CVselect
|
||||
v-if="isCameraNameEdit === false"
|
||||
@@ -59,7 +63,8 @@
|
||||
cols="10"
|
||||
md="5"
|
||||
lg="10"
|
||||
class="pt-0 pb-0 pl-6"
|
||||
no-gutters
|
||||
class="pa-0"
|
||||
>
|
||||
<CVselect
|
||||
v-model="currentPipelineIndex"
|
||||
@@ -139,14 +144,16 @@
|
||||
<v-col
|
||||
v-if="currentPipelineType >= 0"
|
||||
cols="10"
|
||||
md="5"
|
||||
md="11"
|
||||
lg="10"
|
||||
class="pt-0 pb-0 pl-6"
|
||||
no-gutters
|
||||
class="pa-0"
|
||||
>
|
||||
<CVselect
|
||||
v-model="currentPipelineType"
|
||||
name="Type"
|
||||
:list="['Reflective', 'Shape']"
|
||||
tooltip="Changes the pipeline type, which changes the type of processing that will happen on input frames"
|
||||
:list="['Reflective Tape', 'Colored Shape', 'AprilTag']"
|
||||
@input="e => showTypeDialog(e)"
|
||||
/>
|
||||
</v-col>
|
||||
@@ -176,12 +183,6 @@
|
||||
name="Name"
|
||||
:error-message="checkPipelineName"
|
||||
/>
|
||||
<!-- <CVselect-->
|
||||
<!-- v-model="currentPipelineType"-->
|
||||
<!-- name="Pipeline Type"-->
|
||||
<!-- :list="['Reflective', 'Shape']"-->
|
||||
<!-- :disabled="true"-->
|
||||
<!-- />-->
|
||||
</v-card-text>
|
||||
<v-divider />
|
||||
<v-card-actions>
|
||||
@@ -276,12 +277,12 @@ export default {
|
||||
for (let cam in this.cameraList) {
|
||||
if (this.cameraList.hasOwnProperty(cam)) {
|
||||
if (this.newCameraName === this.cameraList[cam]) {
|
||||
return "A camera by that name already Exists"
|
||||
return "A camera by that name already exists"
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return "A camera name can only contain letters, numbers and spaces"
|
||||
return "A camera name can only contain letters, numbers, and spaces"
|
||||
}
|
||||
}
|
||||
return "";
|
||||
@@ -384,7 +385,7 @@ export default {
|
||||
if (this.isPipelineNameEdit) {
|
||||
this.handleInputWithIndex("changePipelineName", this.newPipelineName);
|
||||
} else {
|
||||
this.handleInputWithIndex("addNewPipeline", [this.newPipelineName, this.newPipelineType]); // 0 for reflective, 1 for colored shpae
|
||||
this.handleInputWithIndex("addNewPipeline", [this.newPipelineName, this.currentPipelineType]); // 0 for reflective, 1 for colored shpae
|
||||
}
|
||||
this.discardPipelineNameChange();
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@ if (process.env.NODE_ENV === "production") {
|
||||
Vue.prototype.$address = location.hostname + ":5800";
|
||||
}
|
||||
|
||||
const wsURL = '//' + Vue.prototype.$address + '/websocket';
|
||||
const wsDataURL = '//' + Vue.prototype.$address + '/websocket_data';
|
||||
|
||||
import VueNativeSock from 'vue-native-websocket';
|
||||
|
||||
Vue.use(VueNativeSock, wsURL, {
|
||||
Vue.use(VueNativeSock, wsDataURL, {
|
||||
reconnection: true,
|
||||
reconnectionDelay: 100,
|
||||
connectManually: true,
|
||||
|
||||
@@ -5,9 +5,11 @@ function initColorPicker() {
|
||||
if (!canvas)
|
||||
canvas = document.createElement('canvas');
|
||||
|
||||
image = document.querySelector('#normal-stream');
|
||||
canvas.width = image.width;
|
||||
canvas.height = image.height;
|
||||
image = document.querySelector('#raw-stream');
|
||||
if (image !== null) {
|
||||
canvas.width = image.width;
|
||||
canvas.height = image.height;
|
||||
}
|
||||
}
|
||||
|
||||
//Called on click of the image,
|
||||
|
||||
244
photon-client/src/plugins/WebsocketVideoStream.js
Normal file
244
photon-client/src/plugins/WebsocketVideoStream.js
Normal file
@@ -0,0 +1,244 @@
|
||||
|
||||
|
||||
export class WebsocketVideoStream{
|
||||
|
||||
|
||||
constructor(drawDiv, streamPort, host) {
|
||||
console.log("host " + host + " port " + streamPort)
|
||||
|
||||
this.drawDiv = drawDiv;
|
||||
this.image = document.getElementById(this.drawDiv);
|
||||
this.streamPort = streamPort;
|
||||
this.newStreamPortReq = null;
|
||||
this.serverAddr = "ws://" + host + "/websocket_cameras";
|
||||
this.dispNoStream();
|
||||
this.ws_connect();
|
||||
this.imgData = null;
|
||||
this.imgDataTime = -1;
|
||||
this.imgObjURL = null;
|
||||
this.frameRxCount = 0;
|
||||
|
||||
//Display state machine
|
||||
this.DSM_DISCONNECTED = "DISCONNECTED";
|
||||
this.DSM_WAIT_FOR_VALID_PORT = "WAIT_FOR_VALID_PORT";
|
||||
this.DSM_SUBSCRIBE = "SUBSCRIBE";
|
||||
this.DSM_WAIT_FOR_FIRST_FRAME = "WAIT_FOR_FIRST_FRAME";
|
||||
this.DSM_SHOWING = "SHOWING";
|
||||
this.DSM_RESTART_UNSUBSCRIBE = "UNSUBSCRIBE";
|
||||
this.DSM_RESTART_WAIT = "WAIT_BEFORE_SUBSCRIBE";
|
||||
|
||||
this.dsm_cur_state = this.DSM_DISCONNECTED;
|
||||
this.dsm_prev_state = this.DSM_DISCONNECTED;
|
||||
this.dsm_restart_start_time = window.performance.now();
|
||||
|
||||
requestAnimationFrame(()=>this.animationLoop());
|
||||
|
||||
}
|
||||
|
||||
dispImageData(){
|
||||
//From https://stackoverflow.com/questions/67507616/set-image-src-from-image-blob/67507685#67507685
|
||||
if(this.imgObjURL != null){
|
||||
URL.revokeObjectURL(this.imgObjURL)
|
||||
}
|
||||
this.imgObjURL = URL.createObjectURL(this.imgData);
|
||||
|
||||
//Update the image with the new mimetype and image
|
||||
this.image.src = this.imgObjURL;
|
||||
}
|
||||
|
||||
dispNoStream() {
|
||||
this.image.src = require("../assets/loading.gif");
|
||||
}
|
||||
|
||||
animationLoop(){
|
||||
// Update time metrics
|
||||
var now = window.performance.now();
|
||||
var timeInState = now - this.dsm_restart_start_time;
|
||||
|
||||
// Save previous state
|
||||
this.dsm_prev_state = this.dsm_cur_state;
|
||||
|
||||
// Evaluate state transitions
|
||||
if(this.serverConnectionActive == false){
|
||||
//Any state - if the server connection goes false, always transition to disconnected
|
||||
this.dsm_cur_state = this.DSM_DISCONNECTED;
|
||||
} else {
|
||||
//Conditional transitions
|
||||
switch(this.dsm_cur_state) {
|
||||
case this.DSM_DISCONNECTED:
|
||||
//Immediately transition to waiting for the first frame
|
||||
this.dsm_cur_state = this.DSM_WAIT_FOR_VALID_PORT;
|
||||
break;
|
||||
case this.DSM_WAIT_FOR_VALID_PORT:
|
||||
// Wait until the user has configured a valid port
|
||||
if(this.streamPort > 0){
|
||||
this.dsm_cur_state = this.DSM_SUBSCRIBE;
|
||||
} else {
|
||||
this.dsm_cur_state = this.DSM_WAIT_FOR_VALID_PORT;
|
||||
}
|
||||
break;
|
||||
case this.DSM_SUBSCRIBE:
|
||||
// Immediately transition after subscriptions is sent
|
||||
this.dsm_cur_state = this.DSM_WAIT_FOR_FIRST_FRAME;
|
||||
break;
|
||||
case this.DSM_WAIT_FOR_FIRST_FRAME:
|
||||
if(this.imgData != null){
|
||||
//we got some image data, start showing it
|
||||
this.dsm_cur_state = this.DSM_SHOWING;
|
||||
} else if (this.newStreamPortReq != null){
|
||||
//Stream port requested changed, unsubscribe and restart
|
||||
this.dsm_cur_state = this.DSM_RESTART_UNSUBSCRIBE;
|
||||
} else {
|
||||
this.dsm_cur_state = this.DSM_WAIT_FOR_FIRST_FRAME;
|
||||
}
|
||||
break;
|
||||
case this.DSM_SHOWING:
|
||||
if((now - this.imgDataTime) > 2500){
|
||||
//timeout, begin the restart sequence
|
||||
this.dsm_cur_state = this.DSM_RESTART_UNSUBSCRIBE;
|
||||
} else if (this.newStreamPortReq != null){
|
||||
//Stream port requested changed, unsubscribe and restart
|
||||
this.dsm_cur_state = this.DSM_RESTART_UNSUBSCRIBE;
|
||||
} else {
|
||||
//stay in this state.
|
||||
this.dsm_cur_state = this.DSM_SHOWING;
|
||||
}
|
||||
break;
|
||||
case this.DSM_RESTART_UNSUBSCRIBE:
|
||||
//Only should spend one loop in Unsubscribe, immediately transition
|
||||
this.dsm_cur_state = this.DSM_RESTART_WAIT;
|
||||
break;
|
||||
case this.DSM_RESTART_WAIT:
|
||||
if (timeInState > 250) {
|
||||
//we've waited long enough, go to try to re-subscribe
|
||||
this.dsm_cur_state = this.DSM_WAIT_FOR_VALID_PORT;
|
||||
} else {
|
||||
//stay in this state.
|
||||
this.dsm_cur_state = this.DSM_RESTART_WAIT;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Shouldn't get here, default back to init
|
||||
this.dsm_cur_state = this.DSM_DISCONNECTED;
|
||||
}
|
||||
}
|
||||
|
||||
//take current-state or state-transition actions
|
||||
|
||||
if(this.dsm_cur_state != this.dsm_prev_state){
|
||||
//Any state transition
|
||||
console.log("State Change: " + this.dsm_prev_state + " -> " + this.dsm_cur_state);
|
||||
}
|
||||
|
||||
if(this.dsm_cur_state == this.DSM_SHOWING){
|
||||
// Currently in SHOWING
|
||||
this.dispImageData();
|
||||
}
|
||||
|
||||
if(this.dsm_cur_state != this.DSM_SHOWING && this.dsm_prev_state == this.DSM_SHOWING ){
|
||||
//Any transition out of showing - no stream
|
||||
this.dispNoStream();
|
||||
}
|
||||
|
||||
if(this.dsm_cur_state == this.DSM_RESTART_UNSUBSCRIBE){
|
||||
// Currently in UNSUBSCRIBE, do the unsubscribe actions
|
||||
this.stopStream();
|
||||
this.dsm_restart_start_time = now;
|
||||
}
|
||||
|
||||
if(this.dsm_cur_state == this.DSM_SUBSCRIBE){
|
||||
// Currently in SUBSCRIBE, do the subscribe actions
|
||||
this.startStream();
|
||||
this.dsm_restart_start_time = now;
|
||||
}
|
||||
|
||||
if(this.dsm_cur_state == this.DSM_WAIT_FOR_VALID_PORT){
|
||||
// Currently waiting for a vaild port to be requested
|
||||
if(this.newStreamPortReq != null){
|
||||
this.streamPort = this.newStreamPortReq;
|
||||
this.newStreamPortReq = null;
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(()=>this.animationLoop());
|
||||
}
|
||||
|
||||
startStream() {
|
||||
console.log("Subscribing to port " + this.streamPort);
|
||||
this.imgData = null;
|
||||
this.ws.send(JSON.stringify({"cmd": "subscribe", "port":this.streamPort}));
|
||||
}
|
||||
|
||||
stopStream() {
|
||||
console.log("Unsubscribing");
|
||||
this.ws.send(JSON.stringify({"cmd": "unsubscribe"}));
|
||||
this.imgData = null;
|
||||
}
|
||||
|
||||
setPort(streamPort){
|
||||
console.log("Port set to " + streamPort);
|
||||
this.newStreamPortReq = streamPort;
|
||||
}
|
||||
|
||||
ws_onOpen() {
|
||||
// Set the flag allowing general server communication
|
||||
this.serverConnectionActive = true;
|
||||
console.log("Camera Websockets Connected!");
|
||||
}
|
||||
|
||||
ws_onClose(e) {
|
||||
//Clear flags to stop server communication
|
||||
this.ws = null;
|
||||
this.serverConnectionActive = false;
|
||||
|
||||
console.log('Camera Socket is closed. Reconnect will be attempted in 0.5 second.', e.reason);
|
||||
setTimeout(this.ws_connect.bind(this), 500);
|
||||
|
||||
if(!e.wasClean){
|
||||
console.error('Socket encountered error!');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ws_onError(e){
|
||||
e; //prevent unused failure
|
||||
this.ws.close();
|
||||
}
|
||||
|
||||
ws_onMessage(e){
|
||||
console.log("Got message from " + this.serverAddr)
|
||||
if(typeof e.data === 'string'){
|
||||
//string data from host
|
||||
//TODO - anything to receive info here? Maybe "available streams?"
|
||||
} else {
|
||||
if(e.data.size > 0){
|
||||
//binary data - a frame
|
||||
this.imgData = e.data;
|
||||
this.imgDataTime = window.performance.now();
|
||||
this.frameRxCount++;
|
||||
} else {
|
||||
//TODO - server is sending empty frames?
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ws_connect() {
|
||||
this.serverConnectionActive = false;
|
||||
this.ws = new WebSocket(this.serverAddr);
|
||||
this.ws.binaryType = "blob";
|
||||
this.ws.onopen = this.ws_onOpen.bind(this);
|
||||
this.ws.onmessage = this.ws_onMessage.bind(this);
|
||||
this.ws.onclose = this.ws_onClose.bind(this);
|
||||
this.ws.onerror = this.ws_onError.bind(this);
|
||||
console.log("Connecting to server " + this.serverAddr);
|
||||
}
|
||||
|
||||
ws_close(){
|
||||
this.ws.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default {WebsocketVideoStream}
|
||||
@@ -19,6 +19,8 @@ export default new Vuex.Store({
|
||||
connected: false,
|
||||
address: "",
|
||||
clients: 0,
|
||||
},
|
||||
networkInfo: {
|
||||
possibleRios: ["Loading..."],
|
||||
deviceips: ["Loading..."],
|
||||
},
|
||||
@@ -33,8 +35,8 @@ export default new Vuex.Store({
|
||||
tiltDegrees: 0.0,
|
||||
currentPipelineIndex: 0,
|
||||
pipelineNicknames: ["Unknown"],
|
||||
outputStreamPort: 1181,
|
||||
inputStreamPort: 1182,
|
||||
outputStreamPort: 0,
|
||||
inputStreamPort: 0,
|
||||
nickname: "Unknown",
|
||||
videoFormatList: [
|
||||
{
|
||||
@@ -49,13 +51,15 @@ export default new Vuex.Store({
|
||||
isFovConfigurable: true,
|
||||
calibrated: false,
|
||||
currentPipelineSettings: {
|
||||
pipelineType: 2, // One of "calib", "driver", "reflective", "shape"
|
||||
pipelineType: 4, // One of "calib", "driver", "reflective", "shape", "AprilTag"
|
||||
// 2 is reflective
|
||||
|
||||
// Settings that apply to all pipeline types
|
||||
cameraExposure: 1,
|
||||
cameraBrightness: 2,
|
||||
cameraGain: 3,
|
||||
cameraAutoExposure: false,
|
||||
cameraRedGain: 3,
|
||||
cameraBlueGain: 4,
|
||||
inputImageRotationMode: 0,
|
||||
cameraVideoModeIndex: 0,
|
||||
streamingFrameDivisor: 0,
|
||||
@@ -64,10 +68,13 @@ export default new Vuex.Store({
|
||||
hsvHue: [0, 15],
|
||||
hsvSaturation: [0, 15],
|
||||
hsvValue: [0, 25],
|
||||
hueInverted: false,
|
||||
contourArea: [0, 12],
|
||||
contourRatio: [0, 12],
|
||||
contourFullness: [0, 12],
|
||||
contourSpecklePercentage: 5,
|
||||
contourFilterRangeX: 5,
|
||||
contourFilterRangeY: 5,
|
||||
contourGroupingMode: 0,
|
||||
contourIntersection: 0,
|
||||
contourSortMode: 0,
|
||||
@@ -82,7 +89,16 @@ export default new Vuex.Store({
|
||||
|
||||
cornerDetectionAccuracyPercentage: 10,
|
||||
|
||||
// Settings that apply to shape
|
||||
// Settings that apply to AprilTag
|
||||
tagFamily: 0,
|
||||
decimate: 1.0,
|
||||
blur: 0.0,
|
||||
threads: 1,
|
||||
debug: false,
|
||||
refineEdges: true,
|
||||
numIterations: 1,
|
||||
decisionMargin: 0,
|
||||
hammingDist: 0,
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -96,9 +112,18 @@ export default new Vuex.Store({
|
||||
skew: 0,
|
||||
area: 0,
|
||||
// 3D only
|
||||
pose: {x: 0, y: 0, rot: 0},
|
||||
}]
|
||||
},
|
||||
pose: {x: 1, y: 1, z: 0, qw: 1, qx: 0, qy: 0, qz: 0},
|
||||
},
|
||||
{
|
||||
// Available in both 2D and 3D
|
||||
pitch: 0,
|
||||
yaw: 0,
|
||||
skew: 0,
|
||||
area: 0,
|
||||
// 3D only
|
||||
pose: {x: 2, y: 3, z: 0, qw: 1, qx: 0, qy: 0, qz: 0},
|
||||
}]
|
||||
},
|
||||
settings: {
|
||||
general: {
|
||||
version: "Unknown",
|
||||
@@ -151,6 +176,7 @@ export default new Vuex.Store({
|
||||
calibrationData: set('calibrationData'),
|
||||
metrics: set('metrics'),
|
||||
ntConnectionInfo: set('ntConnectionInfo'),
|
||||
networkInfo: set('networkInfo'),
|
||||
backendConnected: set('backendConnected'),
|
||||
logString: (state, newStr) => {
|
||||
const str = state.logMessages;
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
v-model="currentCameraIndex"
|
||||
name="Camera"
|
||||
:list="$store.getters.cameraList"
|
||||
@input="handleInput('currentCamera',currentCameraIndex)"
|
||||
:select-cols="$vuetify.breakpoint.mdAndUp ? 10 : 7"
|
||||
@input="handleInput('currentCamera',currentCameraIndex)"
|
||||
/>
|
||||
<CVnumberinput
|
||||
v-model="cameraSettings.fov"
|
||||
@@ -31,14 +31,6 @@
|
||||
:label-cols="$vuetify.breakpoint.mdAndUp ? undefined : 7"
|
||||
/>
|
||||
<br>
|
||||
<CVnumberinput
|
||||
v-model="cameraSettings.tiltDegrees"
|
||||
name="Camera pitch"
|
||||
tooltip="How many degrees above the horizontal the physical camera is tilted"
|
||||
:step="0.01"
|
||||
:label-cols="$vuetify.breakpoint.mdAndUp ? undefined : 7"
|
||||
/>
|
||||
<br>
|
||||
<v-btn
|
||||
style="margin-top:10px"
|
||||
small
|
||||
@@ -146,6 +138,24 @@
|
||||
text="Standard Deviation"
|
||||
/>
|
||||
</th>
|
||||
<th class="text-center">
|
||||
<tooltipped-label
|
||||
tooltip="Estimated Horizontal FOV, in degrees"
|
||||
text="Horizontal FOV"
|
||||
/>
|
||||
</th>
|
||||
<th class="text-center">
|
||||
<tooltipped-label
|
||||
tooltip="Estimated Vertical FOV, in degrees"
|
||||
text="Vertical FOV"
|
||||
/>
|
||||
</th>
|
||||
<th class="text-center">
|
||||
<tooltipped-label
|
||||
tooltip="Estimated Diagonal FOV, in degrees"
|
||||
text="Diagonal FOV"
|
||||
/>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -158,6 +168,9 @@
|
||||
{{ isCalibrated(value) ? value.mean.toFixed(2) + "px" : "—" }}
|
||||
</td>
|
||||
<td> {{ isCalibrated(value) ? value.standardDeviation.toFixed(2) + "px" : "—" }} </td>
|
||||
<td> {{ isCalibrated(value) ? value.horizontalFOV.toFixed(2) + "°" : "—" }} </td>
|
||||
<td> {{ isCalibrated(value) ? value.verticalFOV.toFixed(2) + "°" : "—" }} </td>
|
||||
<td> {{ isCalibrated(value) ? value.diagonalFOV.toFixed(2) + "°" : "—" }} </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-simple-table>
|
||||
@@ -181,10 +194,13 @@
|
||||
>
|
||||
<CVslider
|
||||
v-model="$store.getters.currentPipelineSettings.cameraExposure"
|
||||
:disabled="$store.getters.currentPipelineSettings.cameraAutoExposure"
|
||||
name="Exposure"
|
||||
:min="0"
|
||||
:max="100"
|
||||
slider-cols="8"
|
||||
step="0.1"
|
||||
tooltip="Directly controls how much light is allowed to fall onto the sensor, which affects apparent brightness"
|
||||
@input="e => handlePipelineUpdate('cameraExposure', e)"
|
||||
/>
|
||||
<CVslider
|
||||
@@ -195,14 +211,32 @@
|
||||
slider-cols="8"
|
||||
@input="e => handlePipelineUpdate('cameraBrightness', e)"
|
||||
/>
|
||||
<CVswitch
|
||||
v-model="$store.getters.currentPipelineSettings.cameraAutoExposure"
|
||||
class="pt-2"
|
||||
name="Auto Exposure"
|
||||
tooltip="Enables or Disables camera automatic adjustment for current lighting conditions"
|
||||
@input="e => handlePipelineUpdate('cameraAutoExposure', e)"
|
||||
/>
|
||||
<CVslider
|
||||
v-if="$store.getters.currentPipelineSettings.cameraGain !== -1"
|
||||
v-model="$store.getters.currentPipelineSettings.cameraGain"
|
||||
name="Gain"
|
||||
:min="0"
|
||||
:max="100"
|
||||
slider-cols="8"
|
||||
@input="e => handlePipelineUpdate('cameraGain', e)"
|
||||
v-if="$store.getters.currentPipelineSettings.cameraRedGain !== -1"
|
||||
v-model="$store.getters.currentPipelineSettings.cameraRedGain"
|
||||
name="Red AWB Gain"
|
||||
min="0"
|
||||
max="100"
|
||||
tooltip="Controls red automatic white balance gain, which affects how the camera captures colors in different conditions"
|
||||
:slider-cols="8"
|
||||
@input="e => handlePipelineData('cameraRedGain', e)"
|
||||
/>
|
||||
<CVslider
|
||||
v-if="$store.getters.currentPipelineSettings.cameraBlueGain !== -1"
|
||||
v-model="$store.getters.currentPipelineSettings.cameraBlueGain"
|
||||
name="Blue AWB Gain"
|
||||
min="0"
|
||||
max="100"
|
||||
tooltip="Controls blue automatic white balance gain, which affects how the camera captures colors in different conditions"
|
||||
:slider-cols="8"
|
||||
@input="e => handlePipelineData('cameraBlueGain', e)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -257,7 +291,8 @@
|
||||
>
|
||||
<template>
|
||||
<CVimage
|
||||
:address="$store.getters.streamAddress[1]"
|
||||
:id="cameras-cal"
|
||||
:idx=1
|
||||
:disconnected="!$store.state.backendConnected"
|
||||
scale="100"
|
||||
style="border-radius: 5px;"
|
||||
@@ -328,6 +363,7 @@
|
||||
import CVselect from '../components/common/cv-select';
|
||||
import CVnumberinput from '../components/common/cv-number-input';
|
||||
import CVslider from '../components/common/cv-slider';
|
||||
import CVswitch from '../components/common/cv-switch';
|
||||
import CVimage from "../components/common/cv-image";
|
||||
import TooltippedLabel from "../components/common/cv-tooltipped-label";
|
||||
import jsPDF from "jspdf";
|
||||
@@ -340,6 +376,7 @@ export default {
|
||||
CVselect,
|
||||
CVnumberinput,
|
||||
CVslider,
|
||||
CVswitch,
|
||||
CVimage
|
||||
},
|
||||
data() {
|
||||
@@ -385,6 +422,9 @@ export default {
|
||||
if (calib != null) {
|
||||
it['standardDeviation'] = calib.standardDeviation;
|
||||
it['mean'] = calib.perViewErrors.reduce((a, b) => a + b) / calib.perViewErrors.length;
|
||||
it['horizontalFOV'] = 2 * Math.atan2(it.width/2,calib.intrinsics[0]) * (180/Math.PI);
|
||||
it['verticalFOV'] = 2 * Math.atan2(it.height/2,calib.intrinsics[4]) * (180/Math.PI);
|
||||
it['diagonalFOV'] = 2 * Math.atan2(Math.sqrt(it.width**2 + (it.height/(calib.intrinsics[4]/calib.intrinsics[0]))**2)/2,calib.intrinsics[0]) * (180/Math.PI);
|
||||
}
|
||||
filtered.push(it);
|
||||
}
|
||||
@@ -393,13 +433,11 @@ export default {
|
||||
return filtered
|
||||
}
|
||||
},
|
||||
|
||||
stringResolutionList: {
|
||||
get() {
|
||||
return this.filteredResolutionList.map(res => `${res['width']} X ${res['height']}`);
|
||||
}
|
||||
},
|
||||
|
||||
cameraSettings: {
|
||||
get() {
|
||||
return this.$store.getters.currentCameraSettings;
|
||||
@@ -408,7 +446,6 @@ export default {
|
||||
this.$store.commit('cameraSettings', value);
|
||||
}
|
||||
},
|
||||
|
||||
boardType: {
|
||||
get() {
|
||||
return this.calibrationData.boardType
|
||||
@@ -590,8 +627,7 @@ export default {
|
||||
this.axios.post("http://" + this.$address + "/api/settings/camera", {
|
||||
"settings": this.cameraSettings,
|
||||
"index": this.$store.state.currentCameraIndex
|
||||
}).then(
|
||||
function (response) {
|
||||
}).then(response => {
|
||||
if (response.status === 200) {
|
||||
this.$store.state.saveBar = true;
|
||||
}
|
||||
@@ -612,13 +648,14 @@ export default {
|
||||
if (this.isCalibrating === true) {
|
||||
data['takeCalibrationSnapshot'] = true
|
||||
} else {
|
||||
// This store prevents an edge case of a user not selecting a different resolution, which causes the set logic to not be called
|
||||
this.$store.commit('mutateCalibrationState', {['videoModeIndex']: this.filteredResolutionList[this.selectedFilteredResIndex].index});
|
||||
const calData = this.calibrationData;
|
||||
calData.isCalibrating = true;
|
||||
data['startPnpCalibration'] = calData;
|
||||
|
||||
console.log("starting calibration with index " + calData.videoModeIndex);
|
||||
}
|
||||
|
||||
this.$store.commit('currentPipelineIndex', -2);
|
||||
this.$socket.send(this.$msgPack.encode(data));
|
||||
},
|
||||
sendCalibrationFinish() {
|
||||
|
||||
@@ -30,20 +30,13 @@
|
||||
:class="fpsTooLow ? 'ml-2 mt-1' : 'mt-2'"
|
||||
x-small
|
||||
label
|
||||
:color="fpsTooLow ? 'red' : 'transparent'"
|
||||
:color="fpsTooLow ? 'error' : 'transparent'"
|
||||
:text-color="fpsTooLow ? 'white' : 'grey'"
|
||||
>
|
||||
<span class="pr-1">{{ Math.round($store.state.pipelineResults.fps) }} FPS –</span>
|
||||
<span v-if="!fpsTooLow">{{ Math.min(Math.round($store.state.pipelineResults.latency), 100) }} ms latency</span>
|
||||
<span v-if="!fpsTooLow">{{ Math.min(Math.round($store.state.pipelineResults.latency), 9999) }} ms latency</span>
|
||||
<span v-else-if="!$store.getters.currentPipelineSettings.inputShouldShow">HSV thresholds are too broad; narrow them for better performance</span>
|
||||
<span v-else>stop viewing the color stream for better performance</span>
|
||||
</v-chip>
|
||||
<v-chip small label color="red" text-color="white" v-if="!$store.state.ntConnectionInfo.connected || $store.state.settings.networkSettings.runNTServer">
|
||||
<span>
|
||||
{{ $store.state.settings.networkSettings.runNTServer ?
|
||||
"NetworkTables Server Enabled! Photonlib may not work" :
|
||||
"NetworkTables not connected!" }}
|
||||
</span>
|
||||
<span v-else>stop viewing the raw stream for better performance</span>
|
||||
</v-chip>
|
||||
<v-switch
|
||||
v-model="driverMode"
|
||||
@@ -65,15 +58,16 @@
|
||||
>
|
||||
<div style="position: relative; width: 100%; height: 100%;">
|
||||
<cv-image
|
||||
:id="idx === 0 ? 'normal-stream' : ''"
|
||||
:id="idx === 0 ? 'raw-stream' : 'processed-stream'"
|
||||
ref="streams"
|
||||
:address="$store.getters.streamAddress[idx]"
|
||||
:idx=idx
|
||||
:disconnected="!$store.state.backendConnected"
|
||||
scale="100"
|
||||
:max-height="$store.getters.isDriverMode ? '40vh' : '300px'"
|
||||
:max-height-md="$store.getters.isDriverMode ? '50vh' : '320px'"
|
||||
:max-height-md="$store.getters.isDriverMode ? '50vh' : '380px'"
|
||||
:max-height-lg="$store.getters.isDriverMode ? '55vh' : '390px'"
|
||||
:max-height-xl="$store.getters.isDriverMode ? '60vh' : '450px'"
|
||||
:alt="'Stream' + idx"
|
||||
:alt="'Stream ' + idx"
|
||||
:color-picking="$store.state.colorPicking && idx === 0"
|
||||
@click="onImageClick"
|
||||
/>
|
||||
@@ -90,23 +84,18 @@
|
||||
>
|
||||
<v-card
|
||||
color="primary"
|
||||
class="mt-3"
|
||||
>
|
||||
<!-- <v-btn @click="onCamNameChange">-->
|
||||
<!-- Reload-->
|
||||
<!-- </v-btn>-->
|
||||
<camera-and-pipeline-select @camera-name-changed="reloadStreams" />
|
||||
<camera-and-pipeline-select />
|
||||
</v-card>
|
||||
<v-card
|
||||
:disabled="$store.getters.isDriverMode || $store.state.colorPicking"
|
||||
class="mt-6 mb-3"
|
||||
class="mt-3"
|
||||
color="primary"
|
||||
>
|
||||
<v-row
|
||||
align="center"
|
||||
class="pl-3 pr-3"
|
||||
>
|
||||
<!-- -->
|
||||
<v-col lg="12">
|
||||
<p style="color: white;">
|
||||
Processing mode:
|
||||
@@ -147,15 +136,15 @@
|
||||
color="secondary"
|
||||
class="fill"
|
||||
>
|
||||
<v-icon>mdi-palette</v-icon>
|
||||
<span>Normal</span>
|
||||
<v-icon>mdi-import</v-icon>
|
||||
<span>Raw</span>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
class="fill"
|
||||
>
|
||||
<v-icon>mdi-compare</v-icon>
|
||||
<span>Threshold</span>
|
||||
<v-icon>mdi-export</v-icon>
|
||||
<span>Processed</span>
|
||||
</v-btn>
|
||||
</v-btn-toggle>
|
||||
</v-col>
|
||||
@@ -186,7 +175,7 @@
|
||||
slider-color="accent"
|
||||
>
|
||||
<v-tab
|
||||
v-for="(tab, i) in tabs.filter(it => it.name !== '3D' || $store.getters.currentPipelineSettings.solvePNPEnabled)"
|
||||
v-for="(tab, i) in tabs"
|
||||
:key="i"
|
||||
>
|
||||
{{ tab.name }}
|
||||
@@ -206,21 +195,24 @@
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
<!-- snack bar and modal -->
|
||||
|
||||
<v-snackbar
|
||||
v-model="snackbar"
|
||||
:timeout="3000"
|
||||
top
|
||||
v-model="showNTWarning"
|
||||
color="error"
|
||||
timeout="-1"
|
||||
top
|
||||
>
|
||||
<span style="color:#000">Can not remove the only pipeline!</span>
|
||||
<v-btn
|
||||
color="black"
|
||||
text
|
||||
@click="snackbar = false"
|
||||
>
|
||||
Close
|
||||
</v-btn>
|
||||
{{ $store.state.settings.networkSettings.runNTServer ?
|
||||
"NetworkTables server enabled! PhotonLib may not work." :
|
||||
"NetworkTables not connected! Are you on a network with a robot?" }}
|
||||
<template v-slot:action>
|
||||
<v-btn
|
||||
text
|
||||
@click="hideNTWarning = true"
|
||||
>
|
||||
Hide
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
|
||||
<v-dialog
|
||||
@@ -269,7 +261,9 @@ import ThresholdTab from './PipelineViews/ThresholdTab';
|
||||
import ContoursTab from './PipelineViews/ContoursTab';
|
||||
import OutputTab from './PipelineViews/OutputTab';
|
||||
import TargetsTab from "./PipelineViews/TargetsTab";
|
||||
import Map3DTab from './PipelineViews/Map3DTab';
|
||||
import PnPTab from './PipelineViews/PnPTab';
|
||||
import AprilTagTab from './PipelineViews/AprilTagTab';
|
||||
|
||||
export default {
|
||||
name: 'Pipeline',
|
||||
@@ -281,15 +275,17 @@ export default {
|
||||
ContoursTab,
|
||||
OutputTab,
|
||||
TargetsTab,
|
||||
Map3DTab,
|
||||
PnPTab,
|
||||
AprilTagTab,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedTabsData: [0, 0, 0, 0],
|
||||
snackbar: false,
|
||||
counterData: 0,
|
||||
dialog: false,
|
||||
processingModeOverride: false
|
||||
processingModeOverride: false,
|
||||
hideNTWarning: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -316,20 +312,33 @@ export default {
|
||||
name: "Contours",
|
||||
component: "ContoursTab",
|
||||
},
|
||||
apriltag: {
|
||||
name: "AprilTag",
|
||||
component: "AprilTagTab",
|
||||
},
|
||||
output: {
|
||||
name: "Output",
|
||||
component: "OutputTab",
|
||||
},
|
||||
targets: {
|
||||
name: "Target Info",
|
||||
name: "Targets",
|
||||
component: "TargetsTab",
|
||||
},
|
||||
pnp: {
|
||||
name: "3D",
|
||||
name: "PnP",
|
||||
component: "PnPTab",
|
||||
},
|
||||
map3d: {
|
||||
name: "3D",
|
||||
component: "Map3DTab",
|
||||
}
|
||||
};
|
||||
|
||||
// If not in 3d, name "3D" is illegal
|
||||
const allow3d = this.$store.getters.currentPipelineSettings.solvePNPEnabled;
|
||||
// If in apriltag, "Threshold" and "Contours" are illegal -- otherwise "AprilTag" is
|
||||
const isAprilTag = (this.$store.getters.currentPipelineSettings.pipelineType - 2) === 2;
|
||||
|
||||
// 2D array of tab names and component names; each sub-array is a separate tab group
|
||||
let ret = [];
|
||||
if (this.$vuetify.breakpoint.smAndDown || this.$store.getters.isDriverMode || (this.$vuetify.breakpoint.mdAndDown && !this.$store.state.compactMode)) {
|
||||
@@ -337,22 +346,37 @@ export default {
|
||||
ret[0] = Object.values(tabs);
|
||||
} else if (this.$vuetify.breakpoint.mdAndDown || !this.$store.state.compactMode) {
|
||||
// Two tab groups, one with "input, threshold, contours, output" and the other with "target info, 3D"
|
||||
ret[0] = [tabs.input, tabs.threshold, tabs.contours, tabs.output];
|
||||
ret[1] = [tabs.targets, tabs.pnp];
|
||||
ret[0] = [tabs.input, tabs.threshold, tabs.contours, tabs.apriltag, tabs.output];
|
||||
ret[1] = [tabs.targets, tabs.pnp, tabs.map3d];
|
||||
} else if (this.$vuetify.breakpoint.lgAndDown) {
|
||||
// Three tab groups, one with "input", one with "threshold, contours, output", and the other with "target info, 3D"
|
||||
ret[0] = [tabs.input];
|
||||
ret[1] = [tabs.threshold, tabs.contours, tabs.output];
|
||||
ret[2] = [tabs.targets, tabs.pnp];
|
||||
ret[1] = [tabs.threshold, tabs.contours, tabs.apriltag, tabs.output];
|
||||
ret[2] = [tabs.targets, tabs.pnp, tabs.map3d];
|
||||
} else if (this.$vuetify.breakpoint.xl) {
|
||||
// Three tab groups, one with "input", one with "threshold, contours", and the other with "output, target info, 3D"
|
||||
ret[0] = [tabs.input];
|
||||
ret[1] = [tabs.threshold];
|
||||
ret[2] = [tabs.contours, tabs.output];
|
||||
ret[3] = [tabs.targets, tabs.pnp];
|
||||
ret[2] = [tabs.contours, tabs.apriltag, tabs.output];
|
||||
ret[3] = [tabs.targets, tabs.pnp, tabs.map3d];
|
||||
}
|
||||
|
||||
return ret;
|
||||
for(let i = 0; i < ret.length; i++) {
|
||||
const group = ret[i];
|
||||
|
||||
// All the tabs we allow
|
||||
const filteredGroup = group.filter(it =>
|
||||
!(!allow3d && it.name === "3D") //Filter out 3D tab any time 3D isn't calibrated
|
||||
&& !((!allow3d || isAprilTag) && it.name === "PnP") //Filter out the PnP config tab if 3D isn't available, or we're doing Apriltags
|
||||
&& !(isAprilTag && (it.name === "Threshold")) //Filter out threshold tab if we're doing apriltags
|
||||
&& !(isAprilTag && (it.name === "Contours")) //Filter out contours if we're doing Apriltag
|
||||
&& !(!isAprilTag && it.name === "AprilTag") //Filter out apriltag unless we actually are doing Apriltags
|
||||
);
|
||||
ret[i] = filteredGroup;
|
||||
}
|
||||
|
||||
// One last filter to remove empty lists
|
||||
return ret.filter(it => it !== undefined && it.length > 0);
|
||||
}
|
||||
},
|
||||
processingMode: {
|
||||
@@ -434,7 +458,12 @@ export default {
|
||||
// return this.$store.state.ntConnectionInfo.connected && this.$store.state.backendConnected;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
},
|
||||
showNTWarning: {
|
||||
get() {
|
||||
return (!this.$store.state.ntConnectionInfo.connected || this.$store.state.settings.networkSettings.runNTServer) && this.$store.state.settings.networkSettings.teamNumber > 0 && this.$store.state.backendConnected && !this.hideNTWarning;
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$store.state.connectedCallbacks.push(this.reloadStreams)
|
||||
|
||||
196
photon-client/src/views/PipelineViews/AprilTagTab.vue
Normal file
196
photon-client/src/views/PipelineViews/AprilTagTab.vue
Normal file
@@ -0,0 +1,196 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-select
|
||||
v-model="selectedFamily"
|
||||
dark
|
||||
color="accent"
|
||||
item-color="secondary"
|
||||
label="Select target family"
|
||||
:items="familyList"
|
||||
@input="handlePipelineUpdate('tagFamily', familyList.indexOf(selectedFamily))"
|
||||
/>
|
||||
<v-select
|
||||
v-model="selectedModel"
|
||||
dark
|
||||
color="accent"
|
||||
item-color="secondary"
|
||||
label="Select a target model"
|
||||
:items="targetList"
|
||||
item-text="name"
|
||||
item-value="data"
|
||||
@input="handlePipelineUpdate('targetModel', targetList.indexOf(selectedModel) + 6)"
|
||||
/>
|
||||
<CVslider
|
||||
v-model="decimate"
|
||||
class="pt-2"
|
||||
slider-cols="8"
|
||||
name="Decimate"
|
||||
min="1"
|
||||
max="8"
|
||||
step="1.0"
|
||||
tooltip="Increases FPS at the expense of range by reducing image resolution initially"
|
||||
@input="handlePipelineData('decimate')"
|
||||
/>
|
||||
<CVslider
|
||||
v-model="blur"
|
||||
class="pt-2"
|
||||
slider-cols="8"
|
||||
name="Blur"
|
||||
min="0"
|
||||
max="5"
|
||||
step=".01"
|
||||
tooltip="Gaussian blur added to the image, high FPS cost for slightly decreased noise"
|
||||
@input="handlePipelineData('blur')"
|
||||
/>
|
||||
<CVslider
|
||||
v-model="threads"
|
||||
class="pt-2"
|
||||
slider-cols="8"
|
||||
name="Threads"
|
||||
min="1"
|
||||
max="8"
|
||||
step="1"
|
||||
tooltip="Number of threads spawned by the AprilTag detector"
|
||||
@input="handlePipelineData('threads')"
|
||||
/>
|
||||
<CVswitch
|
||||
v-model="refineEdges"
|
||||
class="pt-2"
|
||||
slider-cols="8"
|
||||
name="Refine Edges"
|
||||
tooltip="Further refines the apriltag corner position initial estimate, suggested left on"
|
||||
@input="handlePipelineData('refineEdges')"
|
||||
/>
|
||||
<CVslider
|
||||
v-model="hammingDist"
|
||||
class="pt-2 pb-4"
|
||||
slider-cols="8"
|
||||
name="Max error bits"
|
||||
min="0"
|
||||
max="10"
|
||||
step="1"
|
||||
tooltip="Maximum number of error bits to correct; potential tags with more will be thrown out. For smaller tags (like 16h5), set this as low as possible."
|
||||
@input="handlePipelineData('hammingDist')"
|
||||
/>
|
||||
<CVslider
|
||||
v-model="decisionMargin"
|
||||
class="pt-2 pb-4"
|
||||
slider-cols="8"
|
||||
name="Decision Margin Cutoff"
|
||||
min="0"
|
||||
max="250"
|
||||
step="1"
|
||||
tooltip="Tags with a 'margin' (decoding quality score) less than this wil be rejected. Increase this to reduce the number of false positive detections"
|
||||
@input="handlePipelineData('decisionMargin')"
|
||||
/>
|
||||
<CVslider
|
||||
v-model="numIterations"
|
||||
class="pt-2 pb-4"
|
||||
slider-cols="8"
|
||||
name="Pose Estimation Iterations"
|
||||
min="0"
|
||||
max="500"
|
||||
step="1"
|
||||
tooltip="Number of iterations the pose estimation algorithm will run, 50-100 is a good starting point"
|
||||
@input="handlePipelineData('numIterations')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CVslider from '../../components/common/cv-slider'
|
||||
import CVswitch from '../../components/common/cv-switch'
|
||||
|
||||
export default {
|
||||
name: "AprilTag",
|
||||
components: {
|
||||
CVslider,
|
||||
CVswitch,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
familyList: ["tag36h11", "tag25h9", "tag16h5"],
|
||||
// Selected model is offset (ew) by 6 from the photon ordinal, as we only wanna show the 36h11 and 16h5 options
|
||||
targetList: ['6.5in (36h11) AprilTag', '6in (16h5) AprilTag'], //Keep in sync with TargetModel.java
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectedModel: {
|
||||
get() {
|
||||
let ret = this.$store.getters.currentPipelineSettings.targetModel - 6
|
||||
return this.targetList[ret];
|
||||
},
|
||||
set(val) {
|
||||
this.$store.commit("mutatePipeline", {"targetModel": this.targetList.indexOf(val) + 6})
|
||||
}
|
||||
},
|
||||
selectedFamily: {
|
||||
get() {
|
||||
let ret = this.$store.getters.currentPipelineSettings.tagFamily
|
||||
return this.familyList[ret];
|
||||
},
|
||||
set(val) {
|
||||
this.$store.commit("mutatePipeline", {"tagFamily": this.familyList.indexOf(val)})
|
||||
}
|
||||
},
|
||||
decimate: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineSettings.decimate
|
||||
},
|
||||
set(val) {
|
||||
this.$store.commit("mutatePipeline", {"decimate": val});
|
||||
}
|
||||
},
|
||||
hammingDist: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineSettings.hammingDist
|
||||
},
|
||||
set(val) {
|
||||
this.$store.commit("mutatePipeline", {"hammingDist": val});
|
||||
}
|
||||
},
|
||||
decisionMargin: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineSettings.decisionMargin
|
||||
},
|
||||
set(val) {
|
||||
this.$store.commit("mutatePipeline", {"decisionMargin": val});
|
||||
}
|
||||
},
|
||||
numIterations: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineSettings.numIterations
|
||||
},
|
||||
set(val) {
|
||||
this.$store.commit("mutatePipeline", {"numIterations": val});
|
||||
}
|
||||
},
|
||||
blur: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineSettings.blur
|
||||
},
|
||||
set(val) {
|
||||
this.$store.commit("mutatePipeline", {"blur": val});
|
||||
}
|
||||
},
|
||||
threads: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineSettings.threads
|
||||
},
|
||||
set(val) {
|
||||
this.$store.commit("mutatePipeline", {"threads": val});
|
||||
}
|
||||
},
|
||||
refineEdges: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineSettings.refineEdges
|
||||
},
|
||||
set(val) {
|
||||
this.$store.commit("mutatePipeline", {"refineEdges": val});
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,140 +1,169 @@
|
||||
<template>
|
||||
<div>
|
||||
<CVrangeSlider
|
||||
v-model="contourArea"
|
||||
name="Area"
|
||||
min="0"
|
||||
max="100"
|
||||
step="0.1"
|
||||
@input="handlePipelineData('contourArea')"
|
||||
v-model="contourArea"
|
||||
name="Area"
|
||||
min="0"
|
||||
max="100"
|
||||
step="0.01"
|
||||
@input="handlePipelineData('contourArea')"
|
||||
/>
|
||||
<CVrangeSlider
|
||||
v-model="contourRatio"
|
||||
v-if="currentPipelineType() !== 3"
|
||||
name="Ratio (W/H)"
|
||||
tooltip="Min and max ratio between the width and height of a contour's bounding rectangle"
|
||||
min="0"
|
||||
max="100"
|
||||
step="0.1"
|
||||
@input="handlePipelineData('contourRatio')"
|
||||
v-if="currentPipelineType() !== 3"
|
||||
v-model="contourRatio"
|
||||
name="Ratio (W/H)"
|
||||
tooltip="Min and max ratio between the width and height of a contour's bounding rectangle"
|
||||
min="0"
|
||||
max="100"
|
||||
step="0.1"
|
||||
@input="handlePipelineData('contourRatio')"
|
||||
/>
|
||||
<CVselect
|
||||
v-model="contourTargetOrientation"
|
||||
name="Target Orientation"
|
||||
tooltip="Used to determine how to calculate target landmarks, as well as aspect ratio"
|
||||
:list="['Portrait', 'Landscape']"
|
||||
@input="handlePipelineData('contourTargetOrientation')"
|
||||
@rollback="e=> rollback('contourTargetOrientation', e)"
|
||||
/>
|
||||
<CVrangeSlider
|
||||
v-model="contourFullness"
|
||||
v-if="currentPipelineType() !== 3"
|
||||
name="Fullness"
|
||||
tooltip="Min and max ratio between a contour's area and its bounding rectangle"
|
||||
min="0"
|
||||
max="100"
|
||||
@input="handlePipelineData('contourFullness')"
|
||||
v-if="currentPipelineType() !== 3"
|
||||
v-model="contourFullness"
|
||||
name="Fullness"
|
||||
tooltip="Min and max ratio between a contour's area and its bounding rectangle"
|
||||
min="0"
|
||||
max="100"
|
||||
@input="handlePipelineData('contourFullness')"
|
||||
/>
|
||||
<CVrangeSlider
|
||||
v-model="contourPerimeter"
|
||||
v-if="currentPipelineType() === 3"
|
||||
name="Perimeter"
|
||||
tooltip="Min and max perimeter of the shape, in pixels"
|
||||
min="0"
|
||||
max="4000"
|
||||
@input="handlePipelineData('contourPerimeter')"
|
||||
v-if="currentPipelineType() === 3"
|
||||
v-model="contourPerimeter"
|
||||
name="Perimeter"
|
||||
tooltip="Min and max perimeter of the shape, in pixels"
|
||||
min="0"
|
||||
max="4000"
|
||||
@input="handlePipelineData('contourPerimeter')"
|
||||
/>
|
||||
<CVslider
|
||||
v-model="contourSpecklePercentage"
|
||||
name="Speckle Rejection"
|
||||
tooltip="Rejects contours whose average area is less than the given percentage of the average area of all the other contours"
|
||||
min="0"
|
||||
max="100"
|
||||
:slider-cols="largeBox"
|
||||
@input="handlePipelineData('contourSpecklePercentage')"
|
||||
v-model="contourSpecklePercentage"
|
||||
name="Speckle Rejection"
|
||||
tooltip="Rejects contours whose average area is less than the given percentage of the average area of all the other contours"
|
||||
min="0"
|
||||
max="100"
|
||||
:slider-cols="largeBox"
|
||||
@input="handlePipelineData('contourSpecklePercentage')"
|
||||
/>
|
||||
<template v-if="currentPipelineType() !== 3">
|
||||
<CVselect
|
||||
v-model="contourGroupingMode"
|
||||
name="Target Grouping"
|
||||
tooltip="Whether or not every two targets are paired with each other (good for e.g. 2019 targets)"
|
||||
:select-cols="largeBox"
|
||||
:list="['Single','Dual','2orMore']"
|
||||
@input="handlePipelineData('contourGroupingMode')"
|
||||
<CVslider
|
||||
v-model="contourFilterRangeX"
|
||||
name="X filter tightness"
|
||||
tooltip="Rejects contours whose center X is further than X standard deviations above/below the mean X location"
|
||||
min="0.1"
|
||||
max="4"
|
||||
step="0.1"
|
||||
:slider-cols="largeBox"
|
||||
@input="handlePipelineData('contourFilterRangeX')"
|
||||
/>
|
||||
<CVslider
|
||||
v-model="contourFilterRangeY"
|
||||
name="Y filter tightness"
|
||||
tooltip="Rejects contours whose center Y is further than X standard deviations above/below the mean Y location"
|
||||
min="0.1"
|
||||
max="4"
|
||||
step="0.1"
|
||||
:slider-cols="largeBox"
|
||||
@input="handlePipelineData('contourFilterRangeY')"
|
||||
/>
|
||||
<CVselect
|
||||
v-model="contourIntersection"
|
||||
name="Target Intersection"
|
||||
tooltip="If target grouping is in dual mode it will use this dropdown to decide how targets are grouped with adjacent targets"
|
||||
:select-cols="largeBox"
|
||||
:list="['None','Up','Down','Left','Right']"
|
||||
:disabled="contourGroupingMode === 0"
|
||||
@input="handlePipelineData('contourIntersection')"
|
||||
v-model="contourGroupingMode"
|
||||
name="Target Grouping"
|
||||
tooltip="Whether or not every two targets are paired with each other (good for e.g. 2019 targets)"
|
||||
:select-cols="largeBox"
|
||||
:list="['Single','Dual','2orMore']"
|
||||
@input="handlePipelineData('contourGroupingMode')"
|
||||
/>
|
||||
<CVselect
|
||||
v-model="contourIntersection"
|
||||
name="Target Intersection"
|
||||
tooltip="If target grouping is in dual mode it will use this dropdown to decide how targets are grouped with adjacent targets"
|
||||
:select-cols="largeBox"
|
||||
:list="['None','Up','Down','Left','Right']"
|
||||
:disabled="contourGroupingMode === 0"
|
||||
@input="handlePipelineData('contourIntersection')"
|
||||
/>
|
||||
</template>
|
||||
<!-- If we arent not a shape, we are a shape-->
|
||||
<template v-else>
|
||||
<v-divider class="mt-3"/>
|
||||
<v-divider class="mt-3" />
|
||||
<CVselect
|
||||
v-model="contourShape"
|
||||
name="Target Shape"
|
||||
tooltip="The shape of targets to look for"
|
||||
:select-cols="largeBox"
|
||||
:list="['Circle', 'Polygon', 'Triangle', 'Quadrilateral']"
|
||||
@input="handlePipelineData('contourShape')"
|
||||
v-model="contourShape"
|
||||
name="Target Shape"
|
||||
tooltip="The shape of targets to look for"
|
||||
:select-cols="largeBox"
|
||||
:list="['Circle', 'Polygon', 'Triangle', 'Quadrilateral']"
|
||||
@input="handlePipelineData('contourShape')"
|
||||
/>
|
||||
|
||||
<!-- Accuracy % is only for polygons-->
|
||||
<CVslider
|
||||
v-model="accuracyPercentage"
|
||||
:disabled="currentPipelineSettings().contourShape < 1"
|
||||
name="Shape Simplification"
|
||||
tooltip="How much we should simply the input contour before checking how many sides it has"
|
||||
min="0"
|
||||
max="100"
|
||||
:slider-cols="largeBox"
|
||||
@input="handlePipelineData('accuracyPercentage')"
|
||||
v-model="accuracyPercentage"
|
||||
:disabled="currentPipelineSettings().contourShape < 1"
|
||||
name="Shape Simplification"
|
||||
tooltip="How much we should simply the input contour before checking how many sides it has"
|
||||
min="0"
|
||||
max="100"
|
||||
:slider-cols="largeBox"
|
||||
@input="handlePipelineData('accuracyPercentage')"
|
||||
/>
|
||||
<!-- Similarly, the threshold is only for circles -->
|
||||
<CVslider
|
||||
v-model="circleDetectThreshold"
|
||||
:disabled="currentPipelineSettings().contourShape !== 0"
|
||||
name="Circle match distance"
|
||||
tooltip="How close the centroid of a contour must be to the center of a circle in order for them to be matched"
|
||||
min="1"
|
||||
max="100"
|
||||
:slider-cols="largeBox"
|
||||
@input="handlePipelineData('circleDetectThreshold')"
|
||||
v-model="circleDetectThreshold"
|
||||
:disabled="currentPipelineSettings().contourShape !== 0"
|
||||
name="Circle match distance"
|
||||
tooltip="How close the centroid of a contour must be to the center of a circle in order for them to be matched"
|
||||
min="1"
|
||||
max="100"
|
||||
:slider-cols="largeBox"
|
||||
@input="handlePipelineData('circleDetectThreshold')"
|
||||
/>
|
||||
<CVrangeSlider
|
||||
v-model="contourRadius"
|
||||
:disabled="currentPipelineSettings().contourShape !== 0"
|
||||
name="Radius"
|
||||
min="0"
|
||||
max="100"
|
||||
step="1"
|
||||
@input="handlePipelineData('contourRadius')"
|
||||
v-model="contourRadius"
|
||||
:disabled="currentPipelineSettings().contourShape !== 0"
|
||||
name="Radius"
|
||||
min="0"
|
||||
max="100"
|
||||
step="1"
|
||||
label-cols="3"
|
||||
@input="handlePipelineData('contourRadius')"
|
||||
/>
|
||||
<CVslider
|
||||
v-model="maxCannyThresh"
|
||||
:disabled="currentPipelineSettings().contourShape !== 0"
|
||||
name="Max Canny Threshold"
|
||||
min="1"
|
||||
max="100"
|
||||
:slider-cols="largeBox"
|
||||
@input="handlePipelineData('maxCannyThresh')"
|
||||
v-model="maxCannyThresh"
|
||||
:disabled="currentPipelineSettings().contourShape !== 0"
|
||||
name="Max Canny Threshold"
|
||||
min="1"
|
||||
max="100"
|
||||
:slider-cols="largeBox"
|
||||
@input="handlePipelineData('maxCannyThresh')"
|
||||
/>
|
||||
<CVslider
|
||||
v-model="circleAccuracy"
|
||||
:disabled="currentPipelineSettings().contourShape !== 0"
|
||||
name="Circle Accuracy"
|
||||
min="0"
|
||||
max="100"
|
||||
:slider-cols="largeBox"
|
||||
@input="handlePipelineData('circleAccuracy')"
|
||||
v-model="circleAccuracy"
|
||||
:disabled="currentPipelineSettings().contourShape !== 0"
|
||||
name="Circle Accuracy"
|
||||
min="1"
|
||||
max="100"
|
||||
:slider-cols="largeBox"
|
||||
@input="handlePipelineData('circleAccuracy')"
|
||||
/>
|
||||
<v-divider class="mt-3"/>
|
||||
<v-divider class="mt-3" />
|
||||
</template>
|
||||
<CVselect
|
||||
v-model="contourSortMode"
|
||||
name="Target Sort"
|
||||
tooltip="Chooses the sorting mode used to determine the 'best' targets to provide to user code"
|
||||
:select-cols="largeBox"
|
||||
:list="['Largest','Smallest','Highest','Lowest','Rightmost','Leftmost','Centermost']"
|
||||
@input="handlePipelineData('contourSortMode')"
|
||||
@rollback="e => rollback('contourSortMode', e)"
|
||||
v-model="contourSortMode"
|
||||
name="Target Sort"
|
||||
tooltip="Chooses the sorting mode used to determine the 'best' targets to provide to user code"
|
||||
:select-cols="largeBox"
|
||||
:list="['Largest','Smallest','Highest','Lowest','Rightmost','Leftmost','Centermost']"
|
||||
@input="handlePipelineData('contourSortMode')"
|
||||
@rollback="e => rollback('contourSortMode', e)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -182,6 +211,14 @@ export default {
|
||||
this.$store.commit("mutatePipeline", {"contourRatio": val});
|
||||
}
|
||||
},
|
||||
contourTargetOrientation: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineSettings.contourTargetOrientation
|
||||
},
|
||||
set(val) {
|
||||
this.$store.commit("mutatePipeline", {"contourTargetOrientation": val});
|
||||
}
|
||||
},
|
||||
contourFullness: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineSettings.contourFullness
|
||||
@@ -206,6 +243,25 @@ export default {
|
||||
this.$store.commit("mutatePipeline", {"contourSpecklePercentage": val});
|
||||
}
|
||||
},
|
||||
contourFilterRangeX: {
|
||||
get() {
|
||||
console.log(this.$store.getters.currentPipelineSettings.contourFilterRangeX)
|
||||
return this.$store.getters.currentPipelineSettings.contourFilterRangeX
|
||||
},
|
||||
set(val) {
|
||||
console.log("set")
|
||||
console.log(val)
|
||||
this.$store.commit("mutatePipeline", {"contourFilterRangeX": val});
|
||||
}
|
||||
},
|
||||
contourFilterRangeY: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineSettings.contourFilterRangeY
|
||||
},
|
||||
set(val) {
|
||||
this.$store.commit("mutatePipeline", {"contourFilterRangeY": val});
|
||||
}
|
||||
},
|
||||
contourGroupingMode: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineSettings.contourGroupingMode
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
<div>
|
||||
<CVslider
|
||||
v-model="cameraExposure"
|
||||
:disabled="cameraAutoExposure"
|
||||
name="Exposure"
|
||||
min="0"
|
||||
max="100"
|
||||
step="0.1"
|
||||
tooltip="Directly controls how much light is allowed to fall onto the sensor, which affects brightness"
|
||||
tooltip="Directly controls how much light is allowed to fall onto the sensor, which affects apparent brightness"
|
||||
:slider-cols="largeBox"
|
||||
@input="handlePipelineData('cameraExposure')"
|
||||
@rollback="e => rollback('cameraExposure', e)"
|
||||
@@ -21,17 +22,46 @@
|
||||
@input="handlePipelineData('cameraBrightness')"
|
||||
@rollback="e => rollback('cameraBrightness', e)"
|
||||
/>
|
||||
<CVswitch
|
||||
v-model="cameraAutoExposure"
|
||||
class="pt-2"
|
||||
name="Auto Exposure"
|
||||
tooltip="Enables or Disables camera automatic adjustment for current lighting conditions"
|
||||
@input="handlePipelineData('cameraAutoExposure')"
|
||||
/>
|
||||
<CVslider
|
||||
v-if="cameraGain !== -1"
|
||||
v-if="cameraGain >= 0"
|
||||
v-model="cameraGain"
|
||||
name="Gain"
|
||||
name="Camera Gain"
|
||||
min="0"
|
||||
max="100"
|
||||
tooltip="Controls automatic white balance gain, which affects how the camera captures colors in different conditions"
|
||||
tooltip="Controls camera gain, similar to brightness"
|
||||
:slider-cols="largeBox"
|
||||
@input="handlePipelineData('cameraGain')"
|
||||
@rollback="e => rollback('cameraGain', e)"
|
||||
/>
|
||||
<CVslider
|
||||
v-if="cameraRedGain !== -1"
|
||||
v-model="cameraRedGain"
|
||||
name="Red Balance"
|
||||
min="0"
|
||||
max="100"
|
||||
tooltip="Controls red automatic white balance gain, which affects how the camera captures colors in different conditions"
|
||||
:slider-cols="largeBox"
|
||||
@input="handlePipelineData('cameraRedGain')"
|
||||
@rollback="e => rollback('cameraRedGain', e)"
|
||||
/>
|
||||
<CVslider
|
||||
v-if="cameraBlueGain !== -1"
|
||||
v-model="cameraBlueGain"
|
||||
name="Blue Balance"
|
||||
min="0"
|
||||
max="100"
|
||||
tooltip="Controls blue automatic white balance gain, which affects how the camera captures colors in different conditions"
|
||||
:slider-cols="largeBox"
|
||||
@input="handlePipelineData('cameraBlueGain')"
|
||||
@rollback="e => rollback('cameraBlueGain', e)"
|
||||
/>
|
||||
<CVselect
|
||||
v-model="inputImageRotationMode"
|
||||
name="Orientation"
|
||||
@@ -64,6 +94,7 @@
|
||||
<script>
|
||||
import CVslider from '../../components/common/cv-slider'
|
||||
import CVselect from '../../components/common/cv-select'
|
||||
import CVswitch from '../../components/common/cv-switch'
|
||||
|
||||
const unfilteredStreamDivisors = [1, 2, 4, 6];
|
||||
|
||||
@@ -72,14 +103,10 @@
|
||||
components: {
|
||||
CVslider,
|
||||
CVselect,
|
||||
CVswitch,
|
||||
},
|
||||
// eslint-disable-next-line vue/require-prop-types
|
||||
props: ['value'],
|
||||
data() {
|
||||
return {
|
||||
rawStreamDivisorIndex: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
largeBox: {
|
||||
get() {
|
||||
@@ -97,6 +124,14 @@
|
||||
this.$store.commit("mutatePipeline", {"cameraExposure": parseFloat(val)});
|
||||
}
|
||||
},
|
||||
cameraAutoExposure: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineSettings.cameraAutoExposure;
|
||||
},
|
||||
set(val) {
|
||||
this.$store.commit("mutatePipeline", {"cameraAutoExposure": val});
|
||||
}
|
||||
},
|
||||
cameraBrightness: {
|
||||
get() {
|
||||
return parseInt(this.$store.getters.currentPipelineSettings.cameraBrightness)
|
||||
@@ -113,6 +148,22 @@
|
||||
this.$store.commit("mutatePipeline", {"cameraGain": parseInt(val)});
|
||||
}
|
||||
},
|
||||
cameraRedGain: {
|
||||
get() {
|
||||
return parseInt(this.$store.getters.currentPipelineSettings.cameraRedGain)
|
||||
},
|
||||
set(val) {
|
||||
this.$store.commit("mutatePipeline", {"cameraRedGain": parseInt(val)});
|
||||
}
|
||||
},
|
||||
cameraBlueGain: {
|
||||
get() {
|
||||
return parseInt(this.$store.getters.currentPipelineSettings.cameraBlueGain)
|
||||
},
|
||||
set(val) {
|
||||
this.$store.commit("mutatePipeline", {"cameraBlueGain": parseInt(val)});
|
||||
}
|
||||
},
|
||||
inputImageRotationMode: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineSettings.inputImageRotationMode
|
||||
@@ -129,15 +180,22 @@
|
||||
this.$store.commit("mutatePipeline", {"cameraVideoModeIndex": val});
|
||||
|
||||
this.handlePipelineUpdate("streamingFrameDivisor", this.getNumSkippedStreamDivisors());
|
||||
this.rawStreamDivisorIndex = 0;
|
||||
this.$store.commit("mutatePipeline", {"streamingFrameDivisor": 0});
|
||||
|
||||
// If we don't have 3d mode calibrated at the new resolution either, we should disable it here
|
||||
// (TODO) This probably belongs in the backend (Matt)
|
||||
if (!this.$store.getters.isCalibrated) {
|
||||
this.handlePipelineUpdate("solvePNPEnabled", false);
|
||||
this.$store.commit("mutatePipeline", {"solvePNPEnabled": false});
|
||||
}
|
||||
}
|
||||
},
|
||||
streamingFrameDivisor: {
|
||||
get() {
|
||||
return this.rawStreamDivisorIndex;
|
||||
return this.$store.getters.currentPipelineSettings.streamingFrameDivisor;
|
||||
},
|
||||
set(val) {
|
||||
this.rawStreamDivisorIndex = val;
|
||||
this.$store.commit("mutatePipeline", {"streamingFrameDivisor": val});
|
||||
this.handlePipelineUpdate("streamingFrameDivisor", this.getNumSkippedStreamDivisors() + val);
|
||||
}
|
||||
},
|
||||
|
||||
53
photon-client/src/views/PipelineViews/Map3DTab.vue
Normal file
53
photon-client/src/views/PipelineViews/Map3DTab.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div>
|
||||
<mini-map
|
||||
class="miniMapClass"
|
||||
:targets="targets"
|
||||
:horizontal-f-o-v="horizontalFOV"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import miniMap from '../../components/pipeline/3D/MiniMap';
|
||||
|
||||
export default {
|
||||
name: "Map3D",
|
||||
components: {
|
||||
miniMap
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
targets: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineResults.targets;
|
||||
}
|
||||
},
|
||||
horizontalFOV: {
|
||||
get() {
|
||||
let index = this.$store.getters.currentPipelineSettings.cameraVideoModeIndex;
|
||||
let FOV = this.$store.getters.currentCameraSettings.fov;
|
||||
let resolution = this.$store.getters.videoFormatList[index];
|
||||
let diagonalView = FOV * (Math.PI / 180);
|
||||
let diagonalAspect = Math.hypot(resolution.width, resolution.height);
|
||||
return Math.atan(Math.tan(diagonalView / 2) * (resolution.width / diagonalAspect)) * 2 * (180 / Math.PI)
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.miniMapClass {
|
||||
width: 400px !important;
|
||||
height: 100% !important;
|
||||
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -1,8 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<span class="white--text">Target Manipulation</span>
|
||||
<v-divider class="mt-2" />
|
||||
|
||||
<CVselect
|
||||
v-model="contourTargetOffsetPointEdge"
|
||||
name="Target Offset Point"
|
||||
@@ -31,8 +28,7 @@
|
||||
|
||||
@rollback="e=> rollback('outputShowMultipleTargets', e)"
|
||||
/>
|
||||
<span class="white--text">Robot Offset</span>
|
||||
<v-divider class="mt-2" />
|
||||
<v-divider />
|
||||
<CVselect
|
||||
v-model="offsetRobotOffsetMode"
|
||||
name="Robot Offset Mode"
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
type="file"
|
||||
accept=".csv"
|
||||
style="display: none;"
|
||||
|
||||
@change="readFile"
|
||||
>
|
||||
|
||||
@@ -32,11 +31,7 @@
|
||||
@input="handlePipelineData('cornerDetectionAccuracyPercentage')"
|
||||
@rollback="e => rollback('cornerDetectionAccuracyPercentage', e)"
|
||||
/>
|
||||
<mini-map
|
||||
class="miniMapClass"
|
||||
:targets="targets"
|
||||
:horizontal-f-o-v="horizontalFOV"
|
||||
/>
|
||||
|
||||
<v-snackbar
|
||||
v-model="snack"
|
||||
top
|
||||
@@ -49,18 +44,16 @@
|
||||
|
||||
<script>
|
||||
import Papa from 'papaparse';
|
||||
import miniMap from '../../components/pipeline/3D/MiniMap';
|
||||
import CVslider from '../../components/common/cv-slider'
|
||||
|
||||
export default {
|
||||
name: "PnP",
|
||||
components: {
|
||||
CVslider,
|
||||
miniMap
|
||||
CVslider
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
targetList: ['2020 High Goal Outer', '2020 High Goal Inner', '2019 Dual Target', 'Power Cell (7in)', '2016 High Goal'], //Keep in sync with TargetModel.java
|
||||
targetList: ['2020 High Goal Outer', '2020 High Goal Inner', '2019 Dual Target', '2020 Power Cell (7in)','2022 Cargo Ball (9.5in)', '2016 High Goal', '6.5in (36h11) AprilTag', '6in (16h5) AprilTag'], //Keep in sync with TargetModel.java
|
||||
snackbar: {
|
||||
color: "Success",
|
||||
text: ""
|
||||
@@ -72,7 +65,6 @@
|
||||
selectedModel: {
|
||||
get() {
|
||||
let ret = this.$store.getters.currentPipelineSettings.targetModel
|
||||
console.log(ret)
|
||||
return this.targetList[ret];
|
||||
},
|
||||
set(val) {
|
||||
@@ -87,21 +79,6 @@
|
||||
this.$store.commit("mutatePipeline", {"cornerDetectionAccuracyPercentage": val});
|
||||
}
|
||||
},
|
||||
targets: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineResults.targets;
|
||||
}
|
||||
},
|
||||
horizontalFOV: {
|
||||
get() {
|
||||
let index = this.$store.getters.currentPipelineSettings.cameraVideoModeIndex;
|
||||
let FOV = this.$store.getters.currentCameraSettings.fov;
|
||||
let resolution = this.$store.getters.videoFormatList[index];
|
||||
let diagonalView = FOV * (Math.PI / 180);
|
||||
let diagonalAspect = Math.hypot(resolution.width, resolution.height);
|
||||
return Math.atan(Math.tan(diagonalView / 2) * (resolution.width / diagonalAspect)) * 2 * (180 / Math.PI)
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
readFile(event) {
|
||||
|
||||
@@ -18,29 +18,40 @@
|
||||
<th class="text-center">
|
||||
Target
|
||||
</th>
|
||||
<th
|
||||
v-if="$store.getters.pipelineType === 4"
|
||||
class="text-center"
|
||||
>
|
||||
Fiducial ID
|
||||
</th>
|
||||
<template v-if="!$store.getters.currentPipelineSettings.solvePNPEnabled">
|
||||
<th class="text-center">
|
||||
Pitch
|
||||
Pitch, °
|
||||
</th>
|
||||
<th class="text-center">
|
||||
Yaw
|
||||
Yaw, °
|
||||
</th>
|
||||
<th class="text-center">
|
||||
Skew
|
||||
Skew, °
|
||||
</th>
|
||||
<th class="text-center">
|
||||
Area, %
|
||||
</th>
|
||||
</template>
|
||||
<th class="text-center">
|
||||
Area
|
||||
</th>
|
||||
<template v-if="$store.getters.currentPipelineSettings.solvePNPEnabled">
|
||||
<template v-else>
|
||||
<th class="text-center">
|
||||
X
|
||||
X, m
|
||||
</th>
|
||||
<th class="text-center">
|
||||
Y
|
||||
Y, m
|
||||
</th>
|
||||
<th class="text-center">
|
||||
Angle
|
||||
Z Angle, °
|
||||
</th>
|
||||
</template>
|
||||
<template v-if="$store.getters.pipelineType === 4 && $store.getters.currentPipelineSettings.solvePNPEnabled">
|
||||
<th class="text-center">
|
||||
Ambiguity
|
||||
</th>
|
||||
</template>
|
||||
</tr>
|
||||
@@ -51,17 +62,29 @@
|
||||
:key="index"
|
||||
>
|
||||
<td>{{ index }}</td>
|
||||
<td v-if="$store.getters.pipelineType === 4">
|
||||
{{ parseInt(value.fiducialId) }}
|
||||
</td>
|
||||
<template v-if="!$store.getters.currentPipelineSettings.solvePNPEnabled">
|
||||
<td>{{ parseFloat(value.pitch).toFixed(2) }}</td>
|
||||
<td>{{ parseFloat(value.yaw).toFixed(2) }}</td>
|
||||
<td>{{ parseFloat(value.skew).toFixed(2) }}</td>
|
||||
<td>{{ parseFloat(value.area).toFixed(2) }}</td>
|
||||
</template>
|
||||
<td>{{ parseFloat(value.area).toFixed(2) }}</td>
|
||||
<template v-if="$store.getters.currentPipelineSettings.solvePNPEnabled">
|
||||
<!-- TODO: Make sure that units are correct -->
|
||||
<template v-else-if="$store.getters.currentPipelineSettings.solvePNPEnabled && $store.getters.pipelineType === 4">
|
||||
<td>{{ parseFloat(value.pose.x).toFixed(2) }} m</td>
|
||||
<td>{{ parseFloat(value.pose.y).toFixed(2) }} m</td>
|
||||
<td>{{ parseFloat(value.pose.rot).toFixed(2) }}°</td>
|
||||
<td>{{ (parseFloat(value.pose.angle_z) * 180 / Math.PI).toFixed(2) }}°</td>
|
||||
</template>
|
||||
<template v-else-if="$store.getters.currentPipelineSettings.solvePNPEnabled">
|
||||
<td>{{ parseFloat(value.pose.x).toFixed(2) }} m</td>
|
||||
<td>{{ parseFloat(value.pose.y).toFixed(2) }} m</td>
|
||||
<td>{{ (parseFloat(value.pose.angle_z) * 180 / Math.PI).toFixed(2) }}°</td>
|
||||
</template>
|
||||
<template v-if="$store.getters.pipelineType === 4 && $store.getters.currentPipelineSettings.solvePNPEnabled">
|
||||
<td>
|
||||
{{ parseFloat(value.ambiguity).toFixed(2) }}
|
||||
</td>
|
||||
</template>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -1,66 +1,79 @@
|
||||
<template>
|
||||
<div>
|
||||
<div :style="{'--averageHue': averageHue}">
|
||||
<CVrangeSlider
|
||||
v-model="hsvHue"
|
||||
name="Hue"
|
||||
tooltip="Describes color"
|
||||
:min="0"
|
||||
:max="180"
|
||||
@input="handlePipelineData('hsvHue')"
|
||||
@rollback="e => rollback('hue',e)"
|
||||
id="hue-slider"
|
||||
v-model="hsvHue"
|
||||
:class="hueInverted ? 'inverted-slider' : 'normal-slider'"
|
||||
name="Hue"
|
||||
tooltip="Describes color"
|
||||
:min="0"
|
||||
:max="180"
|
||||
:inverted="hueInverted"
|
||||
@input="handlePipelineData('hsvHue')"
|
||||
@rollback="e => rollback('hue',e)"
|
||||
/>
|
||||
<CVrangeSlider
|
||||
v-model="hsvSaturation"
|
||||
name="Saturation"
|
||||
tooltip="Describes colorfulness; the smaller this value the 'whiter' the color becomes"
|
||||
:min="0"
|
||||
:max="255"
|
||||
@input="handlePipelineData('hsvSaturation')"
|
||||
@rollback="e => rollback('saturation',e)"
|
||||
id="sat-slider"
|
||||
v-model="hsvSaturation"
|
||||
class="normal-slider"
|
||||
name="Saturation"
|
||||
tooltip="Describes colorfulness; the smaller this value the 'whiter' the color becomes"
|
||||
:min="0"
|
||||
:max="255"
|
||||
@input="handlePipelineData('hsvSaturation')"
|
||||
@rollback="e => rollback('saturation',e)"
|
||||
/>
|
||||
<CVrangeSlider
|
||||
v-model="hsvValue"
|
||||
name="Value"
|
||||
tooltip="Describes lightness; the smaller this value the 'blacker' the color becomes"
|
||||
:min="0"
|
||||
:max="255"
|
||||
@input="handlePipelineData('hsvValue')"
|
||||
@rollback="e => rollback('value',e)"
|
||||
id="value-slider"
|
||||
v-model="hsvValue"
|
||||
class="normal-slider"
|
||||
name="Value"
|
||||
tooltip="Describes lightness; the smaller this value the 'blacker' the color becomes"
|
||||
:min="0"
|
||||
:max="255"
|
||||
@input="handlePipelineData('hsvValue')"
|
||||
@rollback="e => rollback('value',e)"
|
||||
/>
|
||||
<template v-if="this.currentPipelineType() === 3">
|
||||
<CVSwitch
|
||||
v-model="hueInverted"
|
||||
name="Invert hue"
|
||||
tooltip="Selects the hue range outside of the hue slider bounds instead of inside"
|
||||
@input="handlePipelineData('hueInverted')"
|
||||
@rollback="e => rollback('hueInverted',e)"
|
||||
/>
|
||||
<template v-if="currentPipelineType() === 3">
|
||||
<CVSwitch
|
||||
v-model="erode"
|
||||
name="Erode"
|
||||
tooltip="Removes pixels around the edges of white areas in the thresholded image"
|
||||
@input="handlePipelineData('erode')"
|
||||
@rollback="e => rollback('erode',e)"
|
||||
v-model="erode"
|
||||
name="Erode"
|
||||
tooltip="Removes pixels around the edges of white areas in the thresholded image"
|
||||
@input="handlePipelineData('erode')"
|
||||
@rollback="e => rollback('erode',e)"
|
||||
/>
|
||||
<CVSwitch
|
||||
v-model="dilate"
|
||||
class="mb-0"
|
||||
name="Dilate"
|
||||
tooltip="Adds pixels around the edges of white areas in the thresholded image"
|
||||
@input="handlePipelineData('dilate')"
|
||||
@rollback="e => rollback('dilate',e)"
|
||||
v-model="dilate"
|
||||
class="mb-0"
|
||||
name="Dilate"
|
||||
tooltip="Adds pixels around the edges of white areas in the thresholded image"
|
||||
@input="handlePipelineData('dilate')"
|
||||
@rollback="e => rollback('dilate',e)"
|
||||
/>
|
||||
</template>
|
||||
<v-divider class="mb-3 mt-3"/>
|
||||
<div class="pt-3 white--text">
|
||||
Color Picker
|
||||
</div>
|
||||
<v-divider
|
||||
class="mt-3"
|
||||
class="mt-3"
|
||||
/>
|
||||
<v-row
|
||||
justify="center"
|
||||
class="mt-3 mb-3"
|
||||
justify="center"
|
||||
class="mt-3 mb-3"
|
||||
>
|
||||
<template v-if="!$store.state.colorPicking">
|
||||
<v-btn
|
||||
color="accent"
|
||||
class="ma-2 black--text"
|
||||
small
|
||||
@click="setFunction(3)"
|
||||
color="accent"
|
||||
class="ma-2 black--text"
|
||||
small
|
||||
@click="setFunction(hueInverted ? 2 : 3)"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-minus
|
||||
@@ -68,21 +81,21 @@
|
||||
Shrink Range
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="accent"
|
||||
class="ma-2 black--text"
|
||||
small
|
||||
@click="setFunction(1)"
|
||||
color="accent"
|
||||
class="ma-2 black--text"
|
||||
small
|
||||
@click="setFunction(1)"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-plus-minus
|
||||
</v-icon>
|
||||
Set To Average
|
||||
{{ hueInverted ? "Exclude" : "Set to" }} Average
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="accent"
|
||||
class="ma-2 black--text"
|
||||
small
|
||||
@click="setFunction(2)"
|
||||
color="accent"
|
||||
class="ma-2 black--text"
|
||||
small
|
||||
@click="setFunction(hueInverted ? 3: 2)"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-plus
|
||||
@@ -92,16 +105,17 @@
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-btn
|
||||
color="accent"
|
||||
class="ma-2 black--text"
|
||||
style="width: 30%;"
|
||||
small
|
||||
@click="setFunction(0)"
|
||||
color="accent"
|
||||
class="ma-2 black--text"
|
||||
style="width: 30%;"
|
||||
small
|
||||
@click="setFunction(0)"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-row>
|
||||
<v-divider class="mb-3" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -133,9 +147,41 @@ export default {
|
||||
this.$store.commit("mutatePipeline", {"hsvHue": val})
|
||||
}
|
||||
},
|
||||
averageHue: {
|
||||
get() {
|
||||
var isInverted = this.$store.getters.currentPipelineSettings.hueInverted;
|
||||
const arr = this.$store.getters.currentPipelineSettings.hsvHue;
|
||||
var retVal = 0;
|
||||
|
||||
if (Array.isArray(arr)) {
|
||||
retVal = (arr[0] + arr[1]);
|
||||
} else {
|
||||
retVal = (arr.first + arr.second);
|
||||
}
|
||||
|
||||
if(isInverted){
|
||||
retVal += 180;
|
||||
}
|
||||
|
||||
if(retVal > 360){
|
||||
retVal -= 360;
|
||||
}
|
||||
|
||||
return retVal;
|
||||
|
||||
},
|
||||
},
|
||||
hueInverted: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineSettings.hueInverted;
|
||||
},
|
||||
set(val) {
|
||||
this.$store.commit("mutatePipeline", {"hueInverted": val});
|
||||
}
|
||||
},
|
||||
hsvSaturation: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineSettings.hsvSaturation
|
||||
return this.$store.getters.currentPipelineSettings.hsvSaturation;
|
||||
},
|
||||
set(val) {
|
||||
this.$store.commit("mutatePipeline", {"hsvSaturation": val})
|
||||
@@ -143,15 +189,15 @@ export default {
|
||||
},
|
||||
hsvValue: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineSettings.hsvValue
|
||||
return this.$store.getters.currentPipelineSettings.hsvValue;
|
||||
},
|
||||
set(val) {
|
||||
this.$store.commit("mutatePipeline", {"hsvValue": val})
|
||||
this.$store.commit("mutatePipeline", {"hsvValue": val});
|
||||
}
|
||||
},
|
||||
erode: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineSettings.erode
|
||||
return this.$store.getters.currentPipelineSettings.erode;
|
||||
},
|
||||
set(val) {
|
||||
this.$store.commit("mutatePipeline", {"erode": val});
|
||||
@@ -159,7 +205,7 @@ export default {
|
||||
},
|
||||
dilate: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineSettings.dilate
|
||||
return this.$store.getters.currentPipelineSettings.dilate;
|
||||
},
|
||||
set(val) {
|
||||
this.$store.commit("mutatePipeline", {"dilate": val});
|
||||
@@ -233,3 +279,31 @@ export default {
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
#hue-slider >>> .v-slider {
|
||||
background: linear-gradient( to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100% );
|
||||
border-radius: 10px;
|
||||
box-shadow: 0px 0px 5px #333, inset 0px 0px 3px #333;
|
||||
}
|
||||
#sat-slider >>> .v-slider {
|
||||
background: linear-gradient( to right, #fff 0%, hsl(var(--averageHue), 100%, 50%) 100% );
|
||||
border-radius: 10px;
|
||||
box-shadow: 0px 0px 5px #333, inset 0px 0px 3px #333;
|
||||
}
|
||||
#value-slider >>> .v-slider {
|
||||
background: linear-gradient( to right, #000 0%, hsl(var(--averageHue), 100%, 50%) 100% );
|
||||
border-radius: 10px;
|
||||
box-shadow: 0px 0px 5px #333, inset 0px 0px 3px #333;
|
||||
}
|
||||
>>> .v-slider__thumb {
|
||||
outline: black solid thin;
|
||||
}
|
||||
.normal-slider >>> .v-slider__track-fill {
|
||||
outline: black solid thin;
|
||||
}
|
||||
|
||||
.inverted-slider >>> .v-slider__track-background {
|
||||
outline: black solid thin;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -49,22 +49,46 @@
|
||||
<th class="infoElem">
|
||||
Disk Usage
|
||||
</th>
|
||||
<th class="infoElem">
|
||||
<v-tooltip top>
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<span
|
||||
v-bind="attrs"
|
||||
v-on="on"
|
||||
>
|
||||
ⓘ CPU Throttling
|
||||
</span>
|
||||
</template>
|
||||
<span>
|
||||
Current or Previous Reason for the cpu being held back from maximum performance.
|
||||
</span>
|
||||
</v-tooltip>
|
||||
</th>
|
||||
<th class="infoElem">
|
||||
CPU Uptime
|
||||
</th>
|
||||
</tr>
|
||||
<tr v-if="metrics.cpuUtil !== 'N/A'">
|
||||
<td class="infoElem">
|
||||
{{ metrics.cpuUtil.replace(" ", "") }}%
|
||||
{{ metrics.cpuUtil }}%
|
||||
</td>
|
||||
<td class="infoElem">
|
||||
{{ parseInt(metrics.cpuTemp) }}° C
|
||||
</td>
|
||||
<td class="infoElem">
|
||||
{{ metrics.ramUtil.replace(" ", "") }}MB of {{ metrics.cpuMem }}MB
|
||||
{{ metrics.ramUtil }}MB of {{ metrics.cpuMem }}MB
|
||||
</td>
|
||||
<td class="infoElem">
|
||||
{{ metrics.gpuMemUtil.replace(" ", "") }}MB of {{ metrics.gpuMem }}MB
|
||||
{{ metrics.gpuMemUtil }}MB of {{ metrics.gpuMem }}MB
|
||||
</td>
|
||||
<td class="infoElem">
|
||||
{{ metrics.diskUtilPct.replace(" ", "") }}
|
||||
{{ metrics.diskUtilPct }}
|
||||
</td>
|
||||
<td class="infoElem">
|
||||
{{ metrics.cpuThr }}
|
||||
</td>
|
||||
<td class="infoElem">
|
||||
{{ metrics.cpuUptime }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="metrics.cpuUtil === 'N/A'">
|
||||
@@ -83,6 +107,12 @@
|
||||
<td class="infoElem">
|
||||
---
|
||||
</td>
|
||||
<td class="infoElem">
|
||||
---
|
||||
</td>
|
||||
<td class="infoElem">
|
||||
---
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</v-row>
|
||||
@@ -143,7 +173,7 @@
|
||||
<v-icon left>
|
||||
mdi-restart
|
||||
</v-icon>
|
||||
Restart Photon
|
||||
Restart PhotonVision
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col
|
||||
@@ -155,7 +185,7 @@
|
||||
@click="restartDevice()"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-restart
|
||||
mdi-restart-alert
|
||||
</v-icon>
|
||||
Restart Device
|
||||
</v-btn>
|
||||
|
||||
@@ -10,46 +10,52 @@
|
||||
name="Team Number"
|
||||
:rules="[v => (v > 0) || 'Team number must be greater than zero', v => (v < 10000) || 'Team number must have fewer than five digits']"
|
||||
class="mb-4"
|
||||
:label-cols="$vuetify.breakpoint.mdAndUp ? undefined : 7"
|
||||
:label-cols="$vuetify.breakpoint.mdAndUp ? undefined : 5"
|
||||
/>
|
||||
<v-chip label color="red" v-bind:style="$vuetify.breakpoint.xsOnly ? 'height: auto;' : ''" text-color="white" v-if="parseInt(teamNumber) < 1 && !runNTServer">
|
||||
<span class="text-wrap">
|
||||
Team number not set! NetworkTables cannot connect.
|
||||
</span>
|
||||
</v-chip>
|
||||
<v-banner
|
||||
v-show="(teamNumber < 1 || teamNumber > 10000) && !runNTServer"
|
||||
rounded
|
||||
color="red"
|
||||
text-color="white"
|
||||
>
|
||||
Team number is unset or invalid. NetworkTables will not be able to connect.
|
||||
</v-banner>
|
||||
<CVradio
|
||||
v-model="connectionType"
|
||||
v-show="$store.state.settings.networkSettings.supported"
|
||||
v-model="connectionType"
|
||||
:input-cols="inputCols"
|
||||
name="IP Assignment Mode"
|
||||
tooltip="DHCP will make the radio (router) automatically assign an IP address; this may result in an IP address that changes across reboots. Static IP assignment means that you pick the IP address and it won't change."
|
||||
:list="['DHCP','Static']"
|
||||
:disabled="!$store.state.settings.networkSettings.supported"
|
||||
/>
|
||||
<template v-if="!isDHCP">
|
||||
<CVinput
|
||||
v-model="staticIp"
|
||||
:input-cols="inputCols"
|
||||
:rules="[v => isIPv4(v) || 'Invalid IPv4 address']"
|
||||
name="IP"
|
||||
/>
|
||||
</template>
|
||||
<CVinput
|
||||
v-if="!isDHCP"
|
||||
v-model="staticIp"
|
||||
:input-cols="inputCols"
|
||||
:rules="[v => isIPv4(v) || 'Invalid IPv4 address']"
|
||||
name="IP"
|
||||
/>
|
||||
<CVinput
|
||||
v-model="hostname"
|
||||
:input-cols="inputCols"
|
||||
:rules="[v => isHostname(v) || 'Invalid hostname']"
|
||||
name="Hostname"
|
||||
/>
|
||||
Advanced
|
||||
<v-divider/>
|
||||
<CVSwitch
|
||||
v-model="runNTServer"
|
||||
name="Run NetworkTables Server (Debugging Only!)"
|
||||
tooltip="If enabled, this device will create a NT server. This is useful for home debugging, but should be disabled on-robot."
|
||||
class="mt-3 mb-3"
|
||||
:text-cols="$vuetify.breakpoint.mdAndUp ? undefined : 7"
|
||||
v-model="runNTServer"
|
||||
name="Run NetworkTables Server (Debugging Only)"
|
||||
tooltip="If enabled, this device will create a NT server. This is useful for home debugging, but should be disabled on-robot."
|
||||
class="mt-3 mb-3"
|
||||
:text-cols="$vuetify.breakpoint.mdAndUp ? undefined : 5"
|
||||
/>
|
||||
<v-chip label color="red" text-color="white" v-if="runNTServer">
|
||||
<span>
|
||||
Disable this switch if you're on a robot! Photonlib will NOT work.
|
||||
</span>
|
||||
</v-chip>
|
||||
<v-banner
|
||||
v-show="runNTServer"
|
||||
rounded
|
||||
color="red"
|
||||
text-color="white"
|
||||
>
|
||||
This switch is intended for testing; it should be off on a robot. PhotonLib will NOT work!
|
||||
</v-banner>
|
||||
</v-form>
|
||||
<v-btn
|
||||
color="accent"
|
||||
@@ -60,60 +66,75 @@
|
||||
>
|
||||
Save
|
||||
</v-btn>
|
||||
<v-divider class="mt-4 mb-4"/>
|
||||
<v-snackbar
|
||||
v-model="snack"
|
||||
top
|
||||
:color="snackbar.color"
|
||||
timeout="5000"
|
||||
>
|
||||
<span>{{ snackbar.text }}</span>
|
||||
</v-snackbar>
|
||||
<v-divider class="mt-4 mb-4" />
|
||||
<!-- TEMP - RIO finder is not currently enabled
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6">
|
||||
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<v-simple-table
|
||||
fixed-header
|
||||
height="100%"
|
||||
dense
|
||||
fixed-header
|
||||
height="100%"
|
||||
dense
|
||||
>
|
||||
<template v-slot:default>
|
||||
<thead style="font-size: 1.25rem;">
|
||||
<tr>
|
||||
<th>
|
||||
Device IPs
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Device IPs
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(value, index) in $store.state.ntConnectionInfo.deviceips"
|
||||
<tr
|
||||
v-for="(value, index) in $store.state.networkInfo.deviceips"
|
||||
:key="index"
|
||||
>
|
||||
<td>{{ value }}</td>
|
||||
</tr>
|
||||
>
|
||||
<td>{{ value }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</template>
|
||||
</v-simple-table>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<v-simple-table
|
||||
fixed-header
|
||||
height="100%"
|
||||
dense
|
||||
fixed-header
|
||||
height="100%"
|
||||
dense
|
||||
>
|
||||
<template v-slot:default>
|
||||
<thead style="font-size: 1.25rem;">
|
||||
<tr>
|
||||
<th>
|
||||
Possible RoboRIOs
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Possible RoboRIOs
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(value, index) in $store.state.ntConnectionInfo.possibleRios"
|
||||
<tr
|
||||
v-for="(value, index) in $store.state.networkInfo.possibleRios"
|
||||
:key="index"
|
||||
>
|
||||
<td>{{ value }}</td>
|
||||
</tr>
|
||||
>
|
||||
<td>{{ value }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</template>
|
||||
</v-simple-table>
|
||||
</v-col>
|
||||
</v-row>
|
||||
-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -150,7 +171,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
inputCols() {
|
||||
return this.$vuetify.breakpoint.smAndUp ? 10 : 7;
|
||||
return this.$vuetify.breakpoint.mdAndUp ? 10 : 7;
|
||||
},
|
||||
isDHCP() {
|
||||
return this.settings.connectionType === 0;
|
||||
@@ -226,7 +247,7 @@ export default {
|
||||
},
|
||||
sendGeneralSettings() {
|
||||
this.axios.post("http://" + this.$address + "/api/settings/general", this.settings).then(
|
||||
function (response) {
|
||||
response => {
|
||||
if (response.status === 200) {
|
||||
this.snackbar = {
|
||||
color: "success",
|
||||
@@ -235,7 +256,7 @@ export default {
|
||||
this.snack = true;
|
||||
}
|
||||
},
|
||||
function (error) {
|
||||
error => {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: (error.response || {data: "Couldn't save settings"}).data
|
||||
|
||||
2
photon-core/.gitignore
vendored
2
photon-core/.gitignore
vendored
@@ -9,5 +9,7 @@ build
|
||||
build/*
|
||||
photonvision/*
|
||||
photonvision_config/*
|
||||
photon-server/lib/*
|
||||
photon-server/package-lock.json
|
||||
|
||||
src/main/java/org/photonvision/PhotonVersion.java
|
||||
|
||||
@@ -25,7 +25,8 @@ dependencies {
|
||||
}
|
||||
|
||||
task writeCurrentVersionJava {
|
||||
writePhotonVersionFile(Path.of("$projectDir", "src", "main", "java", "org", "photonvision", "PhotonVersion.java"),
|
||||
def versionFileIn = file("${rootDir}/shared/PhotonVersion.java.in")
|
||||
writePhotonVersionFile(versionFileIn, Path.of("$projectDir", "src", "main", "java", "org", "photonvision", "PhotonVersion.java"),
|
||||
versionString)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common;
|
||||
|
||||
public enum ProgramStatus {
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import edu.wpi.first.math.geometry.Rotation2d;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.photonvision.common.logging.LogGroup;
|
||||
@@ -49,7 +49,6 @@ public class CameraConfiguration {
|
||||
public double FOV = 70;
|
||||
public final List<CameraCalibrationCoefficients> calibrations;
|
||||
public int currentPipelineIndex = 0;
|
||||
public Rotation2d camPitch = new Rotation2d();
|
||||
|
||||
public int streamIndex = 0; // 0 index means ports [1181, 1182], 1 means [1183, 1184], etc...
|
||||
|
||||
@@ -73,6 +72,7 @@ public class CameraConfiguration {
|
||||
logger.debug(
|
||||
"Creating USB camera configuration for "
|
||||
+ cameraType
|
||||
+ " "
|
||||
+ baseName
|
||||
+ " (AKA "
|
||||
+ nickname
|
||||
@@ -89,8 +89,7 @@ public class CameraConfiguration {
|
||||
@JsonProperty("path") String path,
|
||||
@JsonProperty("cameraType") CameraType cameraType,
|
||||
@JsonProperty("calibration") List<CameraCalibrationCoefficients> calibrations,
|
||||
@JsonProperty("currentPipelineIndex") int currentPipelineIndex,
|
||||
@JsonProperty("camPitch") Rotation2d camPitch) {
|
||||
@JsonProperty("currentPipelineIndex") int currentPipelineIndex) {
|
||||
this.baseName = baseName;
|
||||
this.uniqueName = uniqueName;
|
||||
this.nickname = nickname;
|
||||
@@ -99,11 +98,11 @@ public class CameraConfiguration {
|
||||
this.cameraType = cameraType;
|
||||
this.calibrations = calibrations != null ? calibrations : new ArrayList<>();
|
||||
this.currentPipelineIndex = currentPipelineIndex;
|
||||
this.camPitch = camPitch;
|
||||
|
||||
logger.debug(
|
||||
"Creating camera configuration for "
|
||||
+ cameraType
|
||||
+ " "
|
||||
+ baseName
|
||||
+ " (AKA "
|
||||
+ nickname
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.configuration;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
@@ -31,7 +32,6 @@ import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import org.photonvision.common.logging.LogGroup;
|
||||
import org.photonvision.common.logging.Logger;
|
||||
import org.photonvision.common.util.TimedTaskManager;
|
||||
import org.photonvision.common.util.file.FileUtils;
|
||||
import org.photonvision.common.util.file.JacksonUtils;
|
||||
import org.photonvision.vision.pipeline.CVPipelineSettings;
|
||||
@@ -56,6 +56,7 @@ public class ConfigManager {
|
||||
final File configDirectoryFile;
|
||||
|
||||
private long saveRequestTimestamp = -1;
|
||||
private Thread settingsSaveThread;
|
||||
|
||||
public static ConfigManager getInstance() {
|
||||
if (INSTANCE == null) {
|
||||
@@ -96,7 +97,8 @@ public class ConfigManager {
|
||||
new File(Path.of(configDirectoryFile.toString(), NET_SET_FNAME).toUri());
|
||||
this.camerasFolder = new File(Path.of(configDirectoryFile.toString(), "cameras").toUri());
|
||||
|
||||
TimedTaskManager.getInstance().addTask("ConfigManager", this::checkSaveAndWrite, 1000);
|
||||
settingsSaveThread = new Thread(this::saveAndWriteTask);
|
||||
settingsSaveThread.start();
|
||||
}
|
||||
|
||||
public void load() {
|
||||
@@ -424,12 +426,24 @@ public class ConfigManager {
|
||||
saveRequestTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
private void checkSaveAndWrite() {
|
||||
private void saveAndWriteTask() {
|
||||
// Only save if 1 second has past since the request was made
|
||||
if (saveRequestTimestamp > 0 && (System.currentTimeMillis() - saveRequestTimestamp) > 1000L) {
|
||||
saveRequestTimestamp = -1;
|
||||
logger.debug("Saving to disk...");
|
||||
saveToDisk();
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
if (saveRequestTimestamp > 0 && (System.currentTimeMillis() - saveRequestTimestamp) > 1000L) {
|
||||
saveRequestTimestamp = -1;
|
||||
logger.debug("Saving to disk...");
|
||||
saveToDisk();
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
logger.error("Exception waiting for settings semaphore", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void unloadCameraConfigs() {
|
||||
this.config.getCameraConfigurations().clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
@@ -40,6 +41,8 @@ public class HardwareConfig {
|
||||
public final String cpuTempCommand;
|
||||
public final String cpuMemoryCommand;
|
||||
public final String cpuUtilCommand;
|
||||
public final String cpuThrottleReasonCmd;
|
||||
public final String cpuUptimeCommand;
|
||||
public final String gpuMemoryCommand;
|
||||
public final String ramUtilCommand;
|
||||
public final String gpuMemUsageCommand;
|
||||
@@ -64,6 +67,8 @@ public class HardwareConfig {
|
||||
cpuTempCommand = "";
|
||||
cpuMemoryCommand = "";
|
||||
cpuUtilCommand = "";
|
||||
cpuThrottleReasonCmd = "";
|
||||
cpuUptimeCommand = "";
|
||||
gpuMemoryCommand = "";
|
||||
ramUtilCommand = "";
|
||||
ledBlinkCommand = "";
|
||||
@@ -90,6 +95,8 @@ public class HardwareConfig {
|
||||
String cpuTempCommand,
|
||||
String cpuMemoryCommand,
|
||||
String cpuUtilCommand,
|
||||
String cpuThrottleReasonCmd,
|
||||
String cpuUptimeCommand,
|
||||
String gpuMemoryCommand,
|
||||
String ramUtilCommand,
|
||||
String gpuMemUsageCommand,
|
||||
@@ -110,6 +117,8 @@ public class HardwareConfig {
|
||||
this.cpuTempCommand = cpuTempCommand;
|
||||
this.cpuMemoryCommand = cpuMemoryCommand;
|
||||
this.cpuUtilCommand = cpuUtilCommand;
|
||||
this.cpuThrottleReasonCmd = cpuThrottleReasonCmd;
|
||||
this.cpuUptimeCommand = cpuUptimeCommand;
|
||||
this.gpuMemoryCommand = gpuMemoryCommand;
|
||||
this.ramUtilCommand = ramUtilCommand;
|
||||
this.gpuMemUsageCommand = gpuMemUsageCommand;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.configuration;
|
||||
|
||||
public class HardwareSettings {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.configuration;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -127,7 +128,8 @@ public class PhotonConfiguration {
|
||||
|
||||
public static class UICameraConfiguration {
|
||||
@SuppressWarnings("unused")
|
||||
public double fov, tiltDegrees;
|
||||
public double fov;
|
||||
|
||||
public String nickname;
|
||||
public HashMap<String, Object> currentPipelineSettings;
|
||||
public int currentPipelineIndex;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.dataflow;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.dataflow;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.dataflow;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.dataflow;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.dataflow;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.dataflow.events;
|
||||
|
||||
import org.photonvision.common.dataflow.DataChangeDestination;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.dataflow.events;
|
||||
|
||||
import org.photonvision.common.dataflow.DataChangeDestination;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.dataflow.events;
|
||||
|
||||
import io.javalin.websocket.WsContext;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.dataflow.events;
|
||||
|
||||
import io.javalin.websocket.WsContext;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.dataflow.networktables;
|
||||
|
||||
import edu.wpi.first.networktables.EntryListenerFlags;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.dataflow.networktables;
|
||||
|
||||
import edu.wpi.first.networktables.EntryNotification;
|
||||
@@ -188,10 +189,17 @@ public class NTDataPublisher implements CVPipelineResultConsumer {
|
||||
targetAreaEntry.forceSetDouble(bestTarget.getArea());
|
||||
targetSkewEntry.forceSetDouble(bestTarget.getSkew());
|
||||
|
||||
var poseX = bestTarget.getCameraToTarget().getTranslation().getX();
|
||||
var poseY = bestTarget.getCameraToTarget().getTranslation().getY();
|
||||
var poseRot = bestTarget.getCameraToTarget().getRotation().getDegrees();
|
||||
targetPoseEntry.forceSetDoubleArray(new double[] {poseX, poseY, poseRot});
|
||||
var pose = bestTarget.getBestCameraToTarget3d();
|
||||
targetPoseEntry.forceSetDoubleArray(
|
||||
new double[] {
|
||||
pose.getTranslation().getX(),
|
||||
pose.getTranslation().getY(),
|
||||
pose.getTranslation().getZ(),
|
||||
pose.getRotation().getQuaternion().getW(),
|
||||
pose.getRotation().getQuaternion().getX(),
|
||||
pose.getRotation().getQuaternion().getY(),
|
||||
pose.getRotation().getQuaternion().getZ()
|
||||
});
|
||||
|
||||
var targetOffsetPoint = bestTarget.getTargetOffsetPoint();
|
||||
bestTargetPosX.forceSetDouble(targetOffsetPoint.x);
|
||||
@@ -223,7 +231,10 @@ public class NTDataPublisher implements CVPipelineResultConsumer {
|
||||
t.getPitch(),
|
||||
t.getArea(),
|
||||
t.getSkew(),
|
||||
t.getCameraToTarget(),
|
||||
t.getFiducialId(),
|
||||
t.getBestCameraToTarget3d(),
|
||||
t.getAltCameraToTarget3d(),
|
||||
t.getPoseAmbiguity(),
|
||||
cornerList));
|
||||
}
|
||||
return ret;
|
||||
|
||||
@@ -14,19 +14,13 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.dataflow.networktables;
|
||||
|
||||
import edu.wpi.first.cscore.CameraServerJNI;
|
||||
import edu.wpi.first.networktables.LogMessage;
|
||||
import edu.wpi.first.networktables.NetworkTable;
|
||||
import edu.wpi.first.networktables.NetworkTableInstance;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import org.photonvision.PhotonVersion;
|
||||
import org.photonvision.common.configuration.ConfigManager;
|
||||
@@ -44,8 +38,11 @@ public class NetworkTablesManager {
|
||||
private final String kRootTableName = "/photonvision";
|
||||
public final NetworkTable kRootTable = ntInstance.getTable(kRootTableName);
|
||||
|
||||
private boolean isRetryingConnection = false;
|
||||
|
||||
private NetworkTablesManager() {
|
||||
ntInstance.addLogger(new NTLogger(), 0, 255); // to hide error messages
|
||||
TimedTaskManager.getInstance().addTask("NTManager", this::ntTick, 5000);
|
||||
}
|
||||
|
||||
private static NetworkTablesManager INSTANCE;
|
||||
@@ -86,6 +83,7 @@ public class NetworkTablesManager {
|
||||
private void broadcastConnectedStatusImpl() {
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
var subMap = new HashMap<String, Object>();
|
||||
|
||||
subMap.put("connected", ntInstance.isConnected());
|
||||
if (ntInstance.isConnected()) {
|
||||
var connections = getInstance().ntInstance.getConnections();
|
||||
@@ -98,73 +96,6 @@ public class NetworkTablesManager {
|
||||
map.put("ntConnectionInfo", subMap);
|
||||
DataChangeService.getInstance()
|
||||
.publishEvent(new OutgoingUIEvent<>("networkTablesConnected", map));
|
||||
|
||||
// Seperate from the above so we don't hold stuff up
|
||||
System.setProperty("java.net.preferIPv4Stack", "true");
|
||||
subMap.put(
|
||||
"deviceips",
|
||||
Arrays.stream(CameraServerJNI.getNetworkInterfaces())
|
||||
.filter(it -> !it.equals("0.0.0.0"))
|
||||
.toArray());
|
||||
logger.info("Searching for rios");
|
||||
List<String> possibleRioList = new ArrayList<>();
|
||||
for (var ip : CameraServerJNI.getNetworkInterfaces()) {
|
||||
logger.info("Trying " + ip);
|
||||
var possibleRioAddr = getPossibleRioAddress(ip);
|
||||
if (possibleRioAddr != null) {
|
||||
logger.info("Maybe found " + ip);
|
||||
searchForHost(possibleRioList, possibleRioAddr);
|
||||
} else {
|
||||
logger.info("Didn't match RIO IP");
|
||||
}
|
||||
}
|
||||
String name =
|
||||
"roboRIO-"
|
||||
+ ConfigManager.getInstance().getConfig().getNetworkConfig().teamNumber
|
||||
+ "-FRC.local";
|
||||
searchForHost(possibleRioList, name);
|
||||
name =
|
||||
"roboRIO-"
|
||||
+ ConfigManager.getInstance().getConfig().getNetworkConfig().teamNumber
|
||||
+ "-FRC.lan";
|
||||
searchForHost(possibleRioList, name);
|
||||
name =
|
||||
"roboRIO-"
|
||||
+ ConfigManager.getInstance().getConfig().getNetworkConfig().teamNumber
|
||||
+ "-FRC.frc-field.local";
|
||||
searchForHost(possibleRioList, name);
|
||||
subMap.put("possibleRios", possibleRioList.toArray());
|
||||
DataChangeService.getInstance()
|
||||
.publishEvent(new OutgoingUIEvent<>("networkTablesConnected", map));
|
||||
}
|
||||
|
||||
String getPossibleRioAddress(String ip) {
|
||||
try {
|
||||
InetAddress addr = InetAddress.getByName(ip);
|
||||
var address = addr.getAddress();
|
||||
if (address[0] != (byte) (10 & 0xff)) return null;
|
||||
address[3] = (byte) (2 & 0xff);
|
||||
return InetAddress.getByAddress(address).getHostAddress();
|
||||
} catch (UnknownHostException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void searchForHost(List<String> list, String hostname) {
|
||||
try {
|
||||
logger.info("Looking up " + hostname);
|
||||
InetAddress testAddr = InetAddress.getByName(hostname);
|
||||
logger.info("Pinging " + hostname);
|
||||
var canContact = testAddr.isReachable(500);
|
||||
if (canContact) {
|
||||
logger.info("Was able to connect to " + hostname);
|
||||
if (!list.contains(hostname)) list.add(hostname);
|
||||
} else {
|
||||
logger.info("Unable to reach " + hostname);
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcastVersion() {
|
||||
@@ -182,17 +113,11 @@ public class NetworkTablesManager {
|
||||
}
|
||||
|
||||
private void setClientMode(int teamNumber) {
|
||||
logger.info("Starting NT Client");
|
||||
if (!isRetryingConnection) logger.info("Starting NT Client");
|
||||
ntInstance.stopServer();
|
||||
|
||||
ntInstance.startClientTeam(teamNumber);
|
||||
ntInstance.startDSClient();
|
||||
if (ntInstance.isConnected()) {
|
||||
logger.info("[NetworkTablesManager] Connected to the robot!");
|
||||
} else {
|
||||
logger.error(
|
||||
"[NetworkTablesManager] Could not connect to the robot! Will retry in the background...");
|
||||
}
|
||||
broadcastVersion();
|
||||
}
|
||||
|
||||
@@ -202,4 +127,22 @@ public class NetworkTablesManager {
|
||||
ntInstance.startServer();
|
||||
broadcastVersion();
|
||||
}
|
||||
|
||||
// So it seems like if Photon starts before the robot NT server does, and both aren't static IP,
|
||||
// it'll never connect. This hack works around it by restarting the client/server while the nt
|
||||
// instance
|
||||
// isn't connected, same as clicking the save button in the settings menu (or restarting the
|
||||
// service)
|
||||
private void ntTick() {
|
||||
if (!ntInstance.isConnected()
|
||||
&& !ConfigManager.getInstance().getConfig().getNetworkConfig().runNTServer) {
|
||||
setConfig(ConfigManager.getInstance().getConfig().getNetworkConfig());
|
||||
}
|
||||
|
||||
if (!ntInstance.isConnected() && !isRetryingConnection) {
|
||||
isRetryingConnection = true;
|
||||
logger.error(
|
||||
"[NetworkTablesManager] Could not connect to the robot! Will retry in the background...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.dataflow.websocket;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware.GPIO;
|
||||
|
||||
import org.photonvision.common.configuration.HardwareConfig;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware.GPIO;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware.GPIO.pi;
|
||||
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware.GPIO.pi;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware.GPIO.pi;
|
||||
|
||||
import static org.photonvision.common.hardware.GPIO.pi.PigpioException.*;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware.GPIO.pi;
|
||||
|
||||
public class PigpioPulse {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware.GPIO.pi;
|
||||
|
||||
import static org.photonvision.common.hardware.GPIO.pi.PigpioException.*;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware.GPIO.pi;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware;
|
||||
|
||||
import edu.wpi.first.networktables.NetworkTableEntry;
|
||||
|
||||
@@ -14,13 +14,14 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware;
|
||||
|
||||
public enum PiVersion {
|
||||
PI_B("Pi Model B"),
|
||||
COMPUTE_MODULE("Compute Module Rev"),
|
||||
ZERO_W("Pi Zero W Rev 1.1"),
|
||||
ZERO_2_W("Raspberry Pi Zero 2 W"),
|
||||
ZERO_2_W("Raspberry Pi Zero 2"),
|
||||
PI_3("Pi 3"),
|
||||
PI_4("Pi 4"),
|
||||
COMPUTE_MODULE_3("Compute Module 3"),
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware;
|
||||
|
||||
import edu.wpi.first.util.RuntimeDetector;
|
||||
@@ -48,20 +49,20 @@ public enum Platform {
|
||||
|
||||
// These are queried on init and should never change after
|
||||
public static final Platform currentPlatform = getCurrentPlatform();
|
||||
protected static final String currentPiVersionStr = getPiVersionString();
|
||||
static final String currentPiVersionStr = getPiVersionString();
|
||||
public static final PiVersion currentPiVersion = PiVersion.getPiVersion();
|
||||
|
||||
private static String UnknownPlatformString =
|
||||
private static final String UnknownPlatformString =
|
||||
String.format("Unknown Platform. OS: %s, Architecture: %s", OS_NAME, OS_ARCH);
|
||||
|
||||
public boolean isWindows() {
|
||||
return this == WINDOWS_64 || this == WINDOWS_32;
|
||||
public static boolean isWindows() {
|
||||
return currentPlatform == WINDOWS_64 || currentPlatform == WINDOWS_32;
|
||||
}
|
||||
|
||||
public static boolean isLinux() {
|
||||
return getCurrentPlatform() == LINUX_64
|
||||
|| getCurrentPlatform() == LINUX_RASPBIAN
|
||||
|| getCurrentPlatform() == LINUX_ARM64;
|
||||
return currentPlatform == LINUX_64
|
||||
|| currentPlatform == LINUX_RASPBIAN
|
||||
|| currentPlatform == LINUX_ARM64;
|
||||
}
|
||||
|
||||
public static boolean isRaspberryPi() {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware;
|
||||
|
||||
import edu.wpi.first.networktables.EntryNotification;
|
||||
@@ -84,6 +85,8 @@ public class VisionLED {
|
||||
pigpioSocket.generateAndSendWaveform(pulseLengthMillis, blinkCount, ledPins);
|
||||
} catch (PigpioException e) {
|
||||
logger.error("Failed to blink!", e);
|
||||
} catch (NullPointerException e) {
|
||||
logger.error("Failed to blink, pigpio internal issue!", e);
|
||||
}
|
||||
} else {
|
||||
for (GPIOBase led : visionLEDs) {
|
||||
@@ -99,13 +102,19 @@ public class VisionLED {
|
||||
pigpioSocket.waveTxStop();
|
||||
} catch (PigpioException e) {
|
||||
logger.error("Failed to stop blink!", e);
|
||||
} catch (NullPointerException e) {
|
||||
logger.error("Failed to blink, pigpio internal issue!", e);
|
||||
}
|
||||
}
|
||||
// if the user has set an LED brightness other than 100%, use that instead
|
||||
if (mappedBrightnessPercentage == 100 || !state) {
|
||||
visionLEDs.forEach((led) -> led.setState(state));
|
||||
} else {
|
||||
visionLEDs.forEach((led) -> led.setBrightness(mappedBrightnessPercentage));
|
||||
try {
|
||||
// if the user has set an LED brightness other than 100%, use that instead
|
||||
if (mappedBrightnessPercentage == 100 || !state) {
|
||||
visionLEDs.forEach((led) -> led.setState(state));
|
||||
} else {
|
||||
visionLEDs.forEach((led) -> led.setBrightness(mappedBrightnessPercentage));
|
||||
}
|
||||
} catch (NullPointerException e) {
|
||||
logger.error("Failed to blink, pigpio internal issue!", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware.metrics;
|
||||
|
||||
public class CPUMetrics extends MetricsBase {
|
||||
@@ -39,4 +40,12 @@ public class CPUMetrics extends MetricsBase {
|
||||
public String getUtilization() {
|
||||
return execute(cpuUtilizationCommand);
|
||||
}
|
||||
|
||||
public String getUptime() {
|
||||
return execute(cpuUptimeCommand);
|
||||
}
|
||||
|
||||
public String getThrottleReason() {
|
||||
return execute(cpuThrottleReasonCmd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware.metrics;
|
||||
|
||||
public class DiskMetrics extends MetricsBase {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware.metrics;
|
||||
|
||||
public class GPUMetrics extends MetricsBase {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware.metrics;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
@@ -25,7 +26,7 @@ import org.photonvision.common.logging.Logger;
|
||||
import org.photonvision.common.util.ShellExec;
|
||||
|
||||
public abstract class MetricsBase {
|
||||
private static final Logger logger = new Logger(MetricsBase.class, LogGroup.General);
|
||||
static final Logger logger = new Logger(MetricsBase.class, LogGroup.General);
|
||||
// CPU
|
||||
public static String cpuMemoryCommand = "vcgencmd get_mem arm | grep -Eo '[0-9]+'";
|
||||
public static String cpuTemperatureCommand =
|
||||
@@ -33,6 +34,15 @@ public abstract class MetricsBase {
|
||||
public static String cpuUtilizationCommand =
|
||||
"top -bn1 | grep \"Cpu(s)\" | sed \"s/.*, *\\([0-9.]*\\)%* id.*/\\1/\" | awk '{print 100 - $1}'";
|
||||
|
||||
public static String cpuThrottleReasonCmd =
|
||||
"if (( $(( $(vcgencmd get_throttled | grep -Eo 0x[0-9a-fA-F]*) & 0x01 )) != 0x00 )); then echo \"LOW VOLTAGE\"; "
|
||||
+ "elif (( $(( $(vcgencmd get_throttled | grep -Eo 0x[0-9a-fA-F]*) & 0x08 )) != 0x00 )); then echo \"HIGH TEMP\"; "
|
||||
+ "elif (( $(( $(vcgencmd get_throttled | grep -Eo 0x[0-9a-fA-F]*) & 0x10000 )) != 0x00 )); then echo \"Prev. Low Voltage\"; "
|
||||
+ "elif (( $(( $(vcgencmd get_throttled | grep -Eo 0x[0-9a-fA-F]*) & 0x80000 )) != 0x00 )); then echo \"Prev. High Temp\"; "
|
||||
+ " else echo \"None\"; fi";
|
||||
|
||||
public static String cpuUptimeCommand = "uptime -p | cut -c 4-";
|
||||
|
||||
// GPU
|
||||
public static String gpuMemoryCommand = "vcgencmd get_mem gpu | grep -Eo '[0-9]+'";
|
||||
public static String gpuMemUsageCommand = "vcgencmd get_mem malloc | grep -Eo '[0-9]+'";
|
||||
@@ -50,6 +60,8 @@ public abstract class MetricsBase {
|
||||
cpuMemoryCommand = config.cpuMemoryCommand;
|
||||
cpuTemperatureCommand = config.cpuTempCommand;
|
||||
cpuUtilizationCommand = config.cpuUtilCommand;
|
||||
cpuThrottleReasonCmd = config.cpuThrottleReasonCmd;
|
||||
cpuUptimeCommand = config.cpuUptimeCommand;
|
||||
|
||||
gpuMemoryCommand = config.gpuMemoryCommand;
|
||||
gpuMemUsageCommand = config.gpuMemUsageCommand;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware.metrics;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -59,6 +60,8 @@ public class MetricsPublisher {
|
||||
metrics.put("cpuTemp", cpuMetrics.getTemp());
|
||||
metrics.put("cpuUtil", cpuMetrics.getUtilization());
|
||||
metrics.put("cpuMem", cpuMetrics.getMemory());
|
||||
metrics.put("cpuThr", cpuMetrics.getThrottleReason());
|
||||
metrics.put("cpuUptime", cpuMetrics.getUptime());
|
||||
metrics.put("gpuMem", gpuMetrics.getGPUMemorySplit());
|
||||
metrics.put("ramUtil", ramMetrics.getUsedRam());
|
||||
metrics.put("gpuMemUtil", gpuMetrics.getMallocedMemory());
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware.metrics;
|
||||
|
||||
public class RAMMetrics extends MetricsBase {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.logging;
|
||||
|
||||
public enum LogGroup {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.logging;
|
||||
|
||||
public enum LogLevel {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.logging;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.networking;
|
||||
|
||||
import java.net.InterfaceAddress;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.networking;
|
||||
|
||||
import org.photonvision.common.configuration.ConfigManager;
|
||||
@@ -45,7 +46,7 @@ public class NetworkManager {
|
||||
|
||||
var config = ConfigManager.getInstance().getConfig().getNetworkConfig();
|
||||
logger.info("Setting " + config.connectionType + " with team team " + config.teamNumber);
|
||||
if (Platform.isLinux()) {
|
||||
if (Platform.isRaspberryPi()) {
|
||||
if (!Platform.isRoot) {
|
||||
logger.error("Cannot manage network without root!");
|
||||
return;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.networking;
|
||||
|
||||
public enum NetworkMode {
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.networking;
|
||||
|
||||
import edu.wpi.first.cscore.CameraServerJNI;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import org.photonvision.common.dataflow.DataChangeService;
|
||||
import org.photonvision.common.dataflow.events.OutgoingUIEvent;
|
||||
import org.photonvision.common.logging.LogGroup;
|
||||
import org.photonvision.common.logging.Logger;
|
||||
|
||||
public class RoborioFinder {
|
||||
private static RoborioFinder instance;
|
||||
private static final Logger logger = new Logger(RoborioFinder.class, LogGroup.General);
|
||||
|
||||
public static RoborioFinder getInstance() {
|
||||
if (instance == null) instance = new RoborioFinder();
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void findRios() {
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
var subMap = new HashMap<String, Object>();
|
||||
// Seperate from the above so we don't hold stuff up
|
||||
System.setProperty("java.net.preferIPv4Stack", "true");
|
||||
subMap.put(
|
||||
"deviceips",
|
||||
Arrays.stream(CameraServerJNI.getNetworkInterfaces())
|
||||
.filter(it -> !it.equals("0.0.0.0"))
|
||||
.toArray());
|
||||
logger.info("Searching for rios");
|
||||
List<String> possibleRioList = new ArrayList<>();
|
||||
for (var ip : CameraServerJNI.getNetworkInterfaces()) {
|
||||
logger.info("Trying " + ip);
|
||||
var possibleRioAddr = getPossibleRioAddress(ip);
|
||||
if (possibleRioAddr != null) {
|
||||
logger.info("Maybe found " + ip);
|
||||
searchForHost(possibleRioList, possibleRioAddr);
|
||||
} else {
|
||||
logger.info("Didn't match RIO IP");
|
||||
}
|
||||
}
|
||||
|
||||
// String name =
|
||||
// "roboRIO-"
|
||||
// +
|
||||
// ConfigManager.getInstance().getConfig().getNetworkConfig().teamNumber
|
||||
// + "-FRC.local";
|
||||
// searchForHost(possibleRioList, name);
|
||||
// name =
|
||||
// "roboRIO-"
|
||||
// +
|
||||
// ConfigManager.getInstance().getConfig().getNetworkConfig().teamNumber
|
||||
// + "-FRC.lan";
|
||||
// searchForHost(possibleRioList, name);
|
||||
// name =
|
||||
// "roboRIO-"
|
||||
// +
|
||||
// ConfigManager.getInstance().getConfig().getNetworkConfig().teamNumber
|
||||
// + "-FRC.frc-field.local";
|
||||
// searchForHost(possibleRioList, name);
|
||||
// subMap.put("possibleRios", possibleRioList.toArray());
|
||||
|
||||
subMap.put("possibleRios", possibleRioList.toArray());
|
||||
map.put("networkInfo", subMap);
|
||||
DataChangeService.getInstance().publishEvent(new OutgoingUIEvent<>("deviceIpInfo", map));
|
||||
}
|
||||
|
||||
String getPossibleRioAddress(String ip) {
|
||||
try {
|
||||
InetAddress addr = InetAddress.getByName(ip);
|
||||
var address = addr.getAddress();
|
||||
if (address[0] != (byte) (10 & 0xff)) return null;
|
||||
address[3] = (byte) (2 & 0xff);
|
||||
return InetAddress.getByAddress(address).getHostAddress();
|
||||
} catch (UnknownHostException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void searchForHost(List<String> list, String hostname) {
|
||||
try {
|
||||
logger.info("Looking up " + hostname);
|
||||
InetAddress testAddr = InetAddress.getByName(hostname);
|
||||
logger.info("Pinging " + hostname);
|
||||
var canContact = testAddr.isReachable(500);
|
||||
if (canContact) {
|
||||
logger.info("Was able to connect to " + hostname);
|
||||
if (!list.contains(hostname)) list.add(hostname);
|
||||
} else {
|
||||
logger.info("Unable to reach " + hostname);
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.scripting;
|
||||
|
||||
public enum ScriptCommandType {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.scripting;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.scripting;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.scripting;
|
||||
|
||||
public enum ScriptEventType {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.scripting;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.util;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.util;
|
||||
|
||||
public class MemoryManager {
|
||||
|
||||
@@ -15,19 +15,26 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "photonlib/SimVisionTarget.h"
|
||||
package org.photonvision.common.util;
|
||||
|
||||
namespace photonlib {
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
SimVisionTarget::SimVisionTarget(frc::Pose2d& targetPos,
|
||||
units::meter_t targetHeightAboveGround,
|
||||
units::meter_t targetWidth,
|
||||
units::meter_t targetHeight)
|
||||
: targetPos(targetPos),
|
||||
targetHeightAboveGround(targetHeightAboveGround),
|
||||
targetWidth(targetWidth),
|
||||
targetHeight(targetHeight) {
|
||||
tgtArea = targetWidth * targetHeight;
|
||||
public class NativeLibHelper {
|
||||
private static NativeLibHelper INSTANCE;
|
||||
|
||||
public static NativeLibHelper getInstance() {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new NativeLibHelper();
|
||||
}
|
||||
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public final Path NativeLibPath;
|
||||
|
||||
private NativeLibHelper() {
|
||||
String home = System.getProperty("user.home");
|
||||
NativeLibPath = Paths.get(home, ".pvlibs", "nativecache");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace photonlib
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.util;
|
||||
|
||||
public class ReflectionUtils {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.util;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.util;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -28,6 +29,15 @@ import org.opencv.highgui.HighGui;
|
||||
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
|
||||
|
||||
public class TestUtils {
|
||||
public static void loadLibraries() {
|
||||
try {
|
||||
CameraServerCvJNI.forceLoad();
|
||||
// PicamJNI.forceLoad();
|
||||
} catch (IOException ex) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public enum WPI2019Image {
|
||||
kCargoAngledDark48in(1.2192),
|
||||
@@ -153,6 +163,23 @@ public class TestUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public enum ApriltagTestImages {
|
||||
kRobots,
|
||||
kTag1_640_480;
|
||||
|
||||
public final Path path;
|
||||
|
||||
Path getPath() {
|
||||
// Strip leading k
|
||||
var filename = this.toString().substring(1).toLowerCase();
|
||||
return Path.of("apriltag", filename + ".jpg");
|
||||
}
|
||||
|
||||
ApriltagTestImages() {
|
||||
this.path = getPath();
|
||||
}
|
||||
}
|
||||
|
||||
private static Path getResourcesFolderPath(boolean testMode) {
|
||||
System.out.println("CWD: " + Path.of("").toAbsolutePath().toString());
|
||||
return Path.of("test-resources").toAbsolutePath();
|
||||
@@ -167,7 +194,7 @@ public class TestUtils {
|
||||
public static Path getTestMode2020ImagePath() {
|
||||
return getResourcesFolderPath(true)
|
||||
.resolve("testimages")
|
||||
.resolve(WPI2020Image.kBlueGoal_108in_Center.path);
|
||||
.resolve(WPI2020Image.kBlueGoal_156in_Left.path);
|
||||
}
|
||||
|
||||
public static Path getTestMode2022ImagePath() {
|
||||
@@ -176,6 +203,12 @@ public class TestUtils {
|
||||
.resolve(WPI2022Image.kTerminal22ft6in.path);
|
||||
}
|
||||
|
||||
public static Path getTestModeApriltagPath() {
|
||||
return getResourcesFolderPath(true)
|
||||
.resolve("testimages")
|
||||
.resolve(ApriltagTestImages.kRobots.path);
|
||||
}
|
||||
|
||||
public static Path getTestImagesPath(boolean testMode) {
|
||||
return getResourcesFolderPath(testMode).resolve("testimages");
|
||||
}
|
||||
@@ -200,6 +233,10 @@ public class TestUtils {
|
||||
return getTestImagesPath(testMode).resolve(image.path);
|
||||
}
|
||||
|
||||
public static Path getApriltagImagePath(ApriltagTestImages image, boolean testMode) {
|
||||
return getTestImagesPath(testMode).resolve(image.path);
|
||||
}
|
||||
|
||||
public static Path getPowercellImagePath(PowercellTestImages image, boolean testMode) {
|
||||
return getPowercellPath(testMode).resolve(image.path);
|
||||
}
|
||||
@@ -242,12 +279,8 @@ public class TestUtils {
|
||||
return getCoeffs(LIFECAM_480P_CAL_FILE, testMode);
|
||||
}
|
||||
|
||||
public static void loadLibraries() {
|
||||
try {
|
||||
CameraServerCvJNI.forceLoad();
|
||||
} catch (IOException e) {
|
||||
// ignored
|
||||
}
|
||||
public static CameraCalibrationCoefficients getLaptop() {
|
||||
return getCoeffs("laptop.json", true);
|
||||
}
|
||||
|
||||
private static int DefaultTimeoutMillis = 5000;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.util;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.util.file;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.util.file;
|
||||
|
||||
import com.fasterxml.jackson.core.json.JsonReadFeature;
|
||||
@@ -33,7 +34,7 @@ import java.nio.file.Path;
|
||||
|
||||
public class JacksonUtils {
|
||||
public static <T> void serialize(Path path, T object) throws IOException {
|
||||
serialize(path, object, false);
|
||||
serialize(path, object, true);
|
||||
}
|
||||
|
||||
public static <T> void serialize(Path path, T object, boolean forceSync) throws IOException {
|
||||
@@ -79,7 +80,7 @@ public class JacksonUtils {
|
||||
|
||||
public static <T> void serialize(Path path, T object, Class<T> ref, StdSerializer<T> serializer)
|
||||
throws IOException {
|
||||
serialize(path, object, ref, serializer, false);
|
||||
serialize(path, object, ref, serializer, true);
|
||||
}
|
||||
|
||||
public static <T> void serialize(
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.util.file;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user