|
|
3 weeks ago | |
|---|---|---|
| .gitignore | 8 months ago | |
| LICENSE | 9 months ago | |
| README.md | 4 weeks ago | |
| dummy_cam.py | 4 weeks ago | |
| ls_com_ports.py | 8 months ago | |
| requirements.txt | 4 weeks ago | |
| switch.py | 4 weeks ago | |
| test3djoystick.py | 3 weeks ago |
For a typical livestream production environment consisting of 2 ptz cameras and one joystick / controller board, most commercial options like the Skaarhoj PTZ Fly are
To fix this, I built a Python-based PTZ router that enabled a single, simple PTZ Joystick to easily control two modern IP-based PTZ cameras. The server selects which camera receives the joystick input and provides a HTTP API for selecting the target camera and managing presets.
The router receives a) Pelco-D packets from the joystick via a USB-to-RS485 converter OR b) normal packets from a USB Joystick and translates them into VISCA-over-IP commands, which are sent to the cameras over the network (UDP port 52381).
See the following ascii diagram for the architecture.
+------------------------+
| PTZ Joystick |
| (Pelco-D via RS-485) |
| OR USB Joystick |
+------------------------+
|
v
+--------------------------------+
| /dev/ttyUSBX (JOYSTICK_PORT) |
| [async serial reader] |
| OR [async evedv reader] |
+--------------------------------+
|
v
+------------------------------+
| Python asyncio PTZ Router |
|------------------------------|
| - Parse Pelco-D/evdev packets|
| - Translate to VISCA commands|
| - current_target: cam1/cam2 |
| - Forward to selected cam(IP)|
| - Handle HTTP API requests |
| |
| +----------------------+ |
| | HTTP API | |
| |----------------------| |
| | POST /target/set | |
| | GET /target/get | |
| | POST /preset/goto | |
| | POST /preset/save | |
| | GET /mode/get | |
| | POST /mode/set | |
| +----------------------+ |
+------------------------------+
|
+--------------------+------------------+
| (Network / UDP) | (Network / UDP)
v v
+----------------------------+ +----------------------------+
| Camera 1 (192.168.1.3) | | Camera 2 (192.168.1.4) |
| [VISCA over IP] | | [VISCA over IP] |
+----------------------------+ +----------------------------+
The project uses a YAML configuration file located at ~/.config/diy_ptz_switch/config.yml.
Example configuration:
joystick_type: "usb_joystick" # "usb_joystick" or "pelco_serial" (default)
location_roles:
"1-4.4": joystick
"1-4.1": cam1 # Optional if using IP
"1-4.2": cam2 # Optional if using IP
cameras:
cam1: "192.168.1.3"
cam2: "192.168.1.4"
joystick_type: Sets the input method.
pelco_serial (default): Uses a traditional Pelco-D serial joystick via a USB-to-RS485 converter.usb_joystick: Uses a modern USB 3D PTZ joystick (HID device) using the evdev library.location_roles: Maps USB port locations to roles (like joystick). Required for pelco_serial.cameras: Maps camera names to their IP addresses for VISCA-over-IP communication.You want to test the switch.py server, but you don't have two IP-enabled PTZ Cameras in your network? Just run two instances of dummy_cam.py like this:
python3 dummy_cam.py --ip 127.0.0.1 --name "Dummy Cam 1"
python3 dummy_cam.py --ip 127.0.0.2 --name "Dummy Cam 2"
Obviously, the camera targets in your config.yml need to point to the dummy cam IP's:
cameras:
cam1: "127.0.0.1"
cam2: "127.0.0.2"
There are basically two technologies to choose from when getting a joystick:
If you ever encountered joystick drift while controlling cameras live using a potentiometer joystick, you will never ever go back to using potentiometer again. The inclusion of the links for the potentiometer hardware is only here for completeness, and not that i would recommend them to anyone.
| Product | Link | Estimated Cost |
|---|---|---|
| Anxinshi USB PTZ Controller | https://de.aliexpress.com/item/32825990133.html | 120€ |
| Product | Link | Estimated Cost |
|---|---|---|
| Cheapeast PTZ Joystick with RS-485 Output | https://www.amazon.de/dp/B0DX3BFXM1 | 50€ |
| RS-485 to USB Converter | https://de.aliexpress.com/item/1005007539998595.html | 2€ |
Note that out of 4 RS-485 to USB Converters I bought, only 3 worked.
Possible changes in future releases:
PUT instead of POST requests)config.yml