Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,6 @@ settingsTest.json

/rpanion-server_*

/python/.venv
/python/.venv

/media
1 change: 1 addition & 0 deletions deploy/RasPi3-4-5-deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ fi
source /etc/os-release
if [[ "$ID" == "debian" || "$ID" == "raspbian" ]] && [ "$VERSION_CODENAME" == "bookworm" ]; then
sudo apt install -y gstreamer1.0-libcamera
fi

## Only install picamera2 on RaspiOS
sudo apt -y install python3-picamera2 python3-libcamera python3-kms++
Expand Down
3 changes: 3 additions & 0 deletions deploy/install_common_libraries.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ sudo apt upgrade -y
sudo apt install -y gstreamer1.0-plugins-good libgstrtspserver-1.0-0 gir1.2-gst-rtsp-server-1.0 gstreamer1.0-plugins-base-apps gstreamer1.0-plugins-ugly gstreamer1.0-plugins-bad
sudo apt install -y network-manager python3 python3-gst-1.0 python3-pip dnsmasq git jq wireless-tools iw python3-dev gstreamer1.0-x ppp python3-venv

## Install OpenCV for camera management
sudo apt install -y python3-opencv

## Pymavlink
sudo apt install -y python3-lxml python3-numpy

Expand Down
189 changes: 189 additions & 0 deletions python/get_camera_caps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
#!/usr/bin/env python3
# -*- coding:utf-8 vi:ts=4:noexpandtab -*-

import subprocess
import re
import sys
import json
import os

def check_if_v4l2_ctl_avail():
try:
subprocess.run(['v4l2-ctl', '--help'], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
except (subprocess.CalledProcessError, FileNotFoundError):
print("Could not load v4l2-ctl or v4l2-ctl is not installed. Exiting.")
sys.exit(1)

def get_subdev_paths():
base_path = '/dev'
return sorted([os.path.join(base_path, dev) for dev in os.listdir(base_path) if dev.startswith('v4l-subdev')])

def get_video_device_paths():
base_path = '/dev'
return sorted([os.path.join(base_path, dev) for dev in os.listdir(base_path) if dev.startswith('video')])

def get_mbus_codes(dev_path):
command = f"v4l2-ctl -d {dev_path} --list-subdev-mbus-codes 0"
result = subprocess.run(command, shell=True, text=True, capture_output=True)
if result.returncode != 0:
return []
pattern = r"0x([0-9a-fA-F]+):\s+([A-Za-z0-9_]+)"
return re.findall(pattern, result.stdout)

def get_resolutions(dev_path, mbus_code):
command = f"v4l2-ctl -d {dev_path} --list-subdev-framesizes pad=0,code=0x{mbus_code}"
result = subprocess.run(command, shell=True, text=True, capture_output=True)
if result.returncode != 0:
return []
pattern = r"Size Range: (\d+)x(\d+)"
matches = re.findall(pattern, result.stdout)
return [{'width': int(w), 'height': int(h)} for w, h in matches]

def get_formats_and_resolutions(dev_path):
command = f"v4l2-ctl -d {dev_path} --list-formats-ext"
result = subprocess.run(command, shell=True, text=True, capture_output=True)
if result.returncode != 0:
return []

devices = []
fmt_pattern = r"^\s*\[\d+\]: '(\w+)' \(.*?\)"
size_pattern = r"\s+Size: Discrete (\d+)x(\d+)"
current_fmt = None

for line in result.stdout.splitlines():
fmt_match = re.match(fmt_pattern, line)
if fmt_match:
current_fmt = fmt_match.group(1)
continue

size_match = re.match(size_pattern, line)
if size_match and current_fmt:
width, height = map(int, size_match.groups())
devices.append({
'format': current_fmt,
'width': width,
'height': height,
'label': f"{width}x{height}_{current_fmt}",
'value': f"{current_fmt}_{width}x{height}"
})

return devices

def get_card_name(dev_path):
command = f"v4l2-ctl -d {dev_path} --all"
result = subprocess.run(command, shell=True, text=True, capture_output=True)
if result.returncode != 0:
return None

match = re.search(r"Card type\s+:\s+(.+)", result.stdout)
if match:
return match.group(1).strip()

return None

def get_libcamera_models():
"""Get CSI camera model names from libcamera"""
try:
from picamera2 import Picamera2
models = {}
camera_info = Picamera2.global_camera_info()

for idx, info in enumerate(camera_info):
model = info.get('Model', None)
if model and "usb@" not in info.get('Id', ''):
models[idx] = model

return list(models.values())
except:
return []

def get_capabilities():
"""Check for availability of required libraries for photo/video modes"""
capabilities = {
'cv2': False,
'picamera2': False
}

try:
import cv2
capabilities['cv2'] = True
except ImportError:
pass

try:
from picamera2 import Picamera2
capabilities['picamera2'] = True
except (ImportError, OSError):
pass

return capabilities

check_if_v4l2_ctl_avail()

devices = []

# Get libcamera model names first
libcamera_models = get_libcamera_models()
model_idx = 0

# Process CSI cameras
for dev_path in get_subdev_paths():
mbus_codes = get_mbus_codes(dev_path)
if not mbus_codes:
continue

# Use libcamera model name if available, otherwise fall back to the v4l2 name
if model_idx < len(libcamera_models):
card_name = libcamera_models[model_idx]
model_idx += 1
else:
card_name = get_card_name(dev_path) or "Unnamed CSI Camera"

device_caps = {
# Don't specify a device path for CSI cameras,
# but generate a unique ID for them.
'id': f"CSI-{re.sub(r'[^a-zA-Z0-9]', '_', card_name)}",
'device': None,
'type': 'CSI',
'card_name': card_name,
'caps': []
}

for mbus_code, pixel_format in mbus_codes:
resolutions = get_resolutions(dev_path, mbus_code)

for res in resolutions:
fmt = pixel_format.split("MEDIA_BUS_FMT_")[1] if "MEDIA_BUS_FMT_" in pixel_format else pixel_format
cap_info = {
'format': fmt,
'width': res['width'],
'height': res['height'],
'label': f"{res['width']}x{res['height']}_{fmt}",
'value': f"{mbus_code}_{fmt}_{res['width']}x{res['height']}"
}
device_caps['caps'].append(cap_info)

if device_caps['caps']:
devices.append(device_caps)

# Process UVC cameras
for dev_path in get_video_device_paths():
caps = get_formats_and_resolutions(dev_path)
if caps:
devices.append({
'id': dev_path, # use the device path as the unique ID for UVC cameras
'device': dev_path,
'type': 'UVC',
'card_name': get_card_name(dev_path) or "Unnamed UVC Camera",
'caps': caps
})

# Get library capabilities and output as a single JSON object
capabilities = get_capabilities()

output = {
'devices': devices,
'capabilities': capabilities
}

print(json.dumps(output, indent=4))
16 changes: 12 additions & 4 deletions python/gstcaps.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,18 @@ def is_raspberry_pi():
fps = []
if framerates:
for i in range(framerates.n_values):
fp = str(framerates.get_nth(i)).split('/')
if int(fp[1]) == 1:
fps.append({'value': str(int(fp[0])/int(fp[1])), 'label': (str(int(fp[0])/int(fp[1])) + " fps")})
#print(' - framerate = ', framerates.get_nth(i))
try:
frac = framerates.get_nth(i)
numerator = int(frac.numerator)
denominator = int(frac.denominator)
if denominator == 1:
fps.append({'value': str(int(numerator/denominator)), 'label': (str(int(numerator/denominator)) + " fps")})
else:
fps_val = numerator / denominator
fps.append({'value': str(fps_val), 'label': (str(fps_val) + " fps")})
except (AttributeError, TypeError, ValueError, ZeroDivisionError):
# Skip framerates that can't be parsed
pass
else:
fps.append({'value': "-1", 'label': "N/A"})

Expand Down
Loading
Loading