2018-12-08 08:41:12 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
#----------------------------------------------------------------------------
|
|
|
|
# Copyright (c) 2018 FIRST. All Rights Reserved.
|
|
|
|
# Open Source Software - may be modified and shared by FRC teams. The code
|
|
|
|
# must be accompanied by the FIRST BSD license file in the root directory of
|
|
|
|
# the project.
|
|
|
|
#----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
import json
|
|
|
|
import time
|
|
|
|
import sys
|
|
|
|
|
2019-01-14 00:59:08 +00:00
|
|
|
from cscore import CameraServer, VideoSource, UsbCamera, MjpegServer
|
2018-12-08 08:41:12 +00:00
|
|
|
from networktables import NetworkTablesInstance
|
2019-02-19 03:47:05 +00:00
|
|
|
import ntcore
|
2018-12-08 08:41:12 +00:00
|
|
|
|
|
|
|
# JSON format:
|
|
|
|
# {
|
|
|
|
# "team": <team number>,
|
|
|
|
# "ntmode": <"client" or "server", "client" if unspecified>
|
|
|
|
# "cameras": [
|
|
|
|
# {
|
|
|
|
# "name": <camera name>
|
|
|
|
# "path": <path, e.g. "/dev/video0">
|
|
|
|
# "pixel format": <"MJPEG", "YUYV", etc> // optional
|
|
|
|
# "width": <video mode width> // optional
|
|
|
|
# "height": <video mode height> // optional
|
|
|
|
# "fps": <video mode fps> // optional
|
|
|
|
# "brightness": <percentage brightness> // optional
|
|
|
|
# "white balance": <"auto", "hold", value> // optional
|
|
|
|
# "exposure": <"auto", "hold", value> // optional
|
|
|
|
# "properties": [ // optional
|
|
|
|
# {
|
|
|
|
# "name": <property name>
|
|
|
|
# "value": <property value>
|
|
|
|
# }
|
2019-01-14 00:59:08 +00:00
|
|
|
# ],
|
|
|
|
# "stream": { // optional
|
|
|
|
# "properties": [
|
|
|
|
# {
|
|
|
|
# "name": <stream property name>
|
|
|
|
# "value": <stream property value>
|
|
|
|
# }
|
|
|
|
# ]
|
|
|
|
# }
|
2018-12-08 08:41:12 +00:00
|
|
|
# }
|
|
|
|
# ]
|
2019-02-19 03:47:05 +00:00
|
|
|
# "switched cameras": [
|
|
|
|
# {
|
|
|
|
# "name": <virtual camera name>
|
|
|
|
# "key": <network table key used for selection>
|
|
|
|
# // if NT value is a string, it's treated as a name
|
|
|
|
# // if NT value is a double, it's treated as an integer index
|
|
|
|
# }
|
|
|
|
# ]
|
2018-12-08 08:41:12 +00:00
|
|
|
# }
|
|
|
|
|
|
|
|
configFile = "/boot/frc.json"
|
|
|
|
|
|
|
|
class CameraConfig: pass
|
|
|
|
|
|
|
|
team = None
|
|
|
|
server = False
|
2019-01-03 07:12:32 +00:00
|
|
|
cameraConfigs = []
|
2019-02-19 03:47:05 +00:00
|
|
|
switchedCameraConfigs = []
|
|
|
|
cameras = []
|
2018-12-08 08:41:12 +00:00
|
|
|
|
|
|
|
def parseError(str):
|
2019-01-19 06:38:41 +00:00
|
|
|
"""Report parse error."""
|
2018-12-08 08:41:12 +00:00
|
|
|
print("config error in '" + configFile + "': " + str, file=sys.stderr)
|
|
|
|
|
|
|
|
def readCameraConfig(config):
|
2019-01-19 06:38:41 +00:00
|
|
|
"""Read single camera configuration."""
|
2018-12-08 08:41:12 +00:00
|
|
|
cam = CameraConfig()
|
|
|
|
|
|
|
|
# name
|
|
|
|
try:
|
|
|
|
cam.name = config["name"]
|
|
|
|
except KeyError:
|
|
|
|
parseError("could not read camera name")
|
|
|
|
return False
|
|
|
|
|
|
|
|
# path
|
|
|
|
try:
|
|
|
|
cam.path = config["path"]
|
|
|
|
except KeyError:
|
|
|
|
parseError("camera '{}': could not read path".format(cam.name))
|
|
|
|
return False
|
|
|
|
|
2019-01-14 00:59:08 +00:00
|
|
|
# stream properties
|
|
|
|
cam.streamConfig = config.get("stream")
|
|
|
|
|
2018-12-08 08:41:12 +00:00
|
|
|
cam.config = config
|
|
|
|
|
2019-01-03 07:12:32 +00:00
|
|
|
cameraConfigs.append(cam)
|
2018-12-08 08:41:12 +00:00
|
|
|
return True
|
|
|
|
|
2019-02-19 03:47:05 +00:00
|
|
|
def readSwitchedCameraConfig(config):
|
|
|
|
"""Read single switched camera configuration."""
|
|
|
|
cam = CameraConfig()
|
|
|
|
|
|
|
|
# name
|
|
|
|
try:
|
|
|
|
cam.name = config["name"]
|
|
|
|
except KeyError:
|
|
|
|
parseError("could not read switched camera name")
|
|
|
|
return False
|
|
|
|
|
|
|
|
# path
|
|
|
|
try:
|
|
|
|
cam.key = config["key"]
|
|
|
|
except KeyError:
|
|
|
|
parseError("switched camera '{}': could not read key".format(cam.name))
|
|
|
|
return False
|
|
|
|
|
|
|
|
switchedCameraConfigs.append(cam)
|
|
|
|
return True
|
|
|
|
|
2018-12-08 08:41:12 +00:00
|
|
|
def readConfig():
|
2019-01-19 06:38:41 +00:00
|
|
|
"""Read configuration file."""
|
2018-12-08 08:41:12 +00:00
|
|
|
global team
|
|
|
|
global server
|
|
|
|
|
|
|
|
# parse file
|
|
|
|
try:
|
2019-02-10 05:51:55 +00:00
|
|
|
with open(configFile, "rt", encoding="utf-8") as f:
|
2018-12-08 08:41:12 +00:00
|
|
|
j = json.load(f)
|
|
|
|
except OSError as err:
|
|
|
|
print("could not open '{}': {}".format(configFile, err), file=sys.stderr)
|
|
|
|
return False
|
|
|
|
|
|
|
|
# top level must be an object
|
|
|
|
if not isinstance(j, dict):
|
|
|
|
parseError("must be JSON object")
|
|
|
|
return False
|
|
|
|
|
|
|
|
# team number
|
|
|
|
try:
|
|
|
|
team = j["team"]
|
|
|
|
except KeyError:
|
|
|
|
parseError("could not read team number")
|
|
|
|
return False
|
|
|
|
|
|
|
|
# ntmode (optional)
|
|
|
|
if "ntmode" in j:
|
|
|
|
str = j["ntmode"]
|
|
|
|
if str.lower() == "client":
|
|
|
|
server = False
|
|
|
|
elif str.lower() == "server":
|
|
|
|
server = True
|
|
|
|
else:
|
|
|
|
parseError("could not understand ntmode value '{}'".format(str))
|
|
|
|
|
|
|
|
# cameras
|
|
|
|
try:
|
|
|
|
cameras = j["cameras"]
|
|
|
|
except KeyError:
|
|
|
|
parseError("could not read cameras")
|
|
|
|
return False
|
|
|
|
for camera in cameras:
|
|
|
|
if not readCameraConfig(camera):
|
|
|
|
return False
|
|
|
|
|
2019-02-19 03:47:05 +00:00
|
|
|
# switched cameras
|
|
|
|
if "switched cameras" in j:
|
|
|
|
for camera in j["switched cameras"]:
|
|
|
|
if not readSwitchedCameraConfig(camera):
|
|
|
|
return False
|
|
|
|
|
2018-12-08 08:41:12 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
def startCamera(config):
|
2019-01-19 06:38:41 +00:00
|
|
|
"""Start running the camera."""
|
2018-12-08 08:41:12 +00:00
|
|
|
print("Starting camera '{}' on {}".format(config.name, config.path))
|
2019-01-14 00:59:08 +00:00
|
|
|
inst = CameraServer.getInstance()
|
2019-01-14 05:23:44 +00:00
|
|
|
camera = UsbCamera(config.name, config.path)
|
2019-01-14 00:59:08 +00:00
|
|
|
server = inst.startAutomaticCapture(camera=camera, return_server=True)
|
2018-12-08 08:41:12 +00:00
|
|
|
|
|
|
|
camera.setConfigJson(json.dumps(config.config))
|
2019-01-08 09:38:57 +00:00
|
|
|
camera.setConnectionStrategy(VideoSource.ConnectionStrategy.kKeepOpen)
|
2018-12-08 08:41:12 +00:00
|
|
|
|
2019-01-14 00:59:08 +00:00
|
|
|
if config.streamConfig is not None:
|
|
|
|
server.setConfigJson(json.dumps(config.streamConfig))
|
|
|
|
|
2019-01-03 07:12:32 +00:00
|
|
|
return camera
|
|
|
|
|
2019-02-19 03:47:05 +00:00
|
|
|
def startSwitchedCamera(config):
|
|
|
|
"""Start running the switched camera."""
|
|
|
|
print("Starting switched camera '{}' on {}".format(config.name, config.key))
|
|
|
|
server = CameraServer.getInstance().addSwitchedCamera(config.name)
|
|
|
|
|
|
|
|
def listener(fromobj, key, value, isNew):
|
|
|
|
if isinstance(value, float):
|
|
|
|
i = int(value)
|
|
|
|
if i >= 0 and i < len(cameras):
|
|
|
|
server.setSource(cameras[i])
|
|
|
|
elif isinstance(value, str):
|
|
|
|
for i in range(len(cameraConfigs)):
|
|
|
|
if value == cameraConfigs[i].name:
|
|
|
|
server.setSource(cameras[i])
|
|
|
|
break
|
|
|
|
|
|
|
|
NetworkTablesInstance.getDefault().getEntry(config.key).addListener(
|
|
|
|
listener,
|
|
|
|
ntcore.constants.NT_NOTIFY_IMMEDIATE |
|
|
|
|
ntcore.constants.NT_NOTIFY_NEW |
|
|
|
|
ntcore.constants.NT_NOTIFY_UPDATE)
|
|
|
|
|
|
|
|
return server
|
|
|
|
|
2018-12-08 08:41:12 +00:00
|
|
|
if __name__ == "__main__":
|
|
|
|
if len(sys.argv) >= 2:
|
|
|
|
configFile = sys.argv[1]
|
|
|
|
|
|
|
|
# read configuration
|
|
|
|
if not readConfig():
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
# start NetworkTables
|
|
|
|
ntinst = NetworkTablesInstance.getDefault()
|
|
|
|
if server:
|
|
|
|
print("Setting up NetworkTables server")
|
|
|
|
ntinst.startServer()
|
|
|
|
else:
|
|
|
|
print("Setting up NetworkTables client for team {}".format(team))
|
|
|
|
ntinst.startClientTeam(team)
|
|
|
|
|
|
|
|
# start cameras
|
2019-02-19 03:47:05 +00:00
|
|
|
for config in cameraConfigs:
|
|
|
|
cameras.append(startCamera(config))
|
|
|
|
|
|
|
|
# start switched cameras
|
|
|
|
for config in switchedCameraConfigs:
|
|
|
|
startSwitchedCamera(config)
|
2018-12-08 08:41:12 +00:00
|
|
|
|
|
|
|
# loop forever
|
|
|
|
while True:
|
|
|
|
time.sleep(10)
|