A cross-platform C++17 library for controlling Sony VISCA PTZ cameras (zoom, focus, pan/tilt, presets, power, and more) over serial (RS-232) and IP (VISCA-over-IP / UDP), built on Boost.Asio.
#include <visca/visca.hpp>
visca::SerialConfig config;
config.port = "/dev/ttyUSB0";
config.baud_rate = 9600;
auto transport = std::make_shared<visca::SerialTransport>(config);
visca::Camera camera(transport, /*address=*/1);
camera.connect();
camera.power_on();
camera.zoom_tele(4);
camera.preset_recall(2);SerialTransport— RS-232/RS-422 serial control viaboost::asio::serial_port.IpTransport— VISCA-over-IP over UDP (port 52381 by default), with the standard 8-byte encapsulation header (payload type, length, sequence number), or raw mode for devices that skip it.Camera— a high-level class with named methods for power, zoom, focus, pan/tilt (including absolute/relative positioning), and presets, which automatically handles VISCA's ACK → Completion → Error reply sequence and throwsvisca::visca_errorwith the camera's error code on failure.visca::message— the underlying byte-level frame builder/parser, plus acommand()/inquiry()escape hatch so you can send any VISCA command from your camera's command list, not just the ones wrapped byCamera.- CMake install step that produces a
find_package(viscalib)-ready package (headers +.dll/.so+.lib/.a+ CMake config files) on both Windows and Linux, with an optional step to setVISCALIB_ROOTand updatePATH/LD_LIBRARY_PATH.
VISCA has grown a lot of vendor- and model-specific extensions over the
years (white balance, iris, gain, backlight compensation, PTZ trace, block
inquiries, etc). This library implements the common core — power, zoom,
focus, pan/tilt, presets — plus a generic command()/inquiry() call so you
can add anything else in a couple of lines, using the byte sequences from
your camera's own VISCA command list PDF (Sony publishes one per
model/firmware version). VISCA-over-IP framing in particular has minor
variations between vendors (Sony, Avonic, BirdDog, PTZOptics, AVer, etc.) —
if a camera doesn't respond, that's the first thing worth double-checking.
- CMake ≥ 3.16
- A C++17 compiler (GCC ≥ 9, Clang ≥ 10, MSVC ≥ 2019)
- Boost ≥ 1.71 (
systemcomponent; Asio itself is header-only)
sudo apt install cmake libboost-dev libboost-system-dev
git clone https://github.com/SJavad/viscalib.git
cd viscalib
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
cmake --install buildBy default this installs to ~/.local/viscalib (no sudo needed). Pass
-DCMAKE_INSTALL_PREFIX=/usr/local (with sudo cmake --install build) for a
system-wide install instead.
Easiest via vcpkg for Boost:
vcpkg install boost-asio boost-system
git clone https://github.com/SJavad/viscalib.git
cd viscalib
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=C:\path\to\vcpkg\scripts\buildsystems\vcpkg.cmake
cmake --build build --config Release
cmake --install build --config ReleaseBy default this installs to %LOCALAPPDATA%\viscalib.
| Option | Default | Description |
|---|---|---|
BUILD_SHARED_LIBS |
ON |
Build viscalib as a shared library (.dll/.so) instead of static |
VISCALIB_BUILD_EXAMPLES |
ON |
Build examples/serial_example.cpp and examples/ip_example.cpp |
VISCALIB_SET_ENV_VARS |
OFF |
During cmake --install, also set VISCALIB_ROOT and update PATH/LD_LIBRARY_PATH |
To have install set environment variables automatically:
cmake -S . -B build -DVISCALIB_SET_ENV_VARS=ON
cmake --build build -j
cmake --install buildOn Windows this sets VISCALIB_ROOT and adds <prefix>\bin to PATH for
the current user (no admin rights required; pass -Scope Machine to
scripts/set_env_windows.ps1 yourself for an all-users install). On Linux it
writes ~/.viscalib_env.sh and sources it from ~/.bashrc/~/.zshrc. You
can also just run the scripts in scripts/ manually at any time.
Open a new terminal after installing for the environment changes to apply.
find_package(viscalib REQUIRED)
target_link_libraries(your_target PRIVATE viscalib::viscalib)If you installed to a non-default prefix, point CMake at it:
cmake -S . -B build -DCMAKE_PREFIX_PATH=$VISCALIB_ROOT#include <visca/visca.hpp>
visca::SerialConfig config;
config.port = "/dev/ttyUSB0"; // Windows: "COM3"
config.baud_rate = 9600;
auto transport = std::make_shared<visca::SerialTransport>(config);
visca::Camera camera(transport, /*address=*/1);
camera.connect();
camera.power_on();
camera.zoom_tele(4);
camera.zoom_stop();
camera.focus_auto();
camera.pan_tilt(0x08, 0x08, visca::pan_tilt::Right);
camera.pan_tilt_stop();
camera.preset_recall(0);#include <visca/visca.hpp>
visca::IpConfig config;
config.host = "192.168.1.88";
config.port = 52381;
auto transport = std::make_shared<visca::IpTransport>(config);
visca::Camera camera(transport, /*address=*/1);
camera.connect();
transport->reset_sequence_number(); // recommended once, right after connecting
camera.preset_recall(1);
auto [pan, tilt] = camera.get_pan_tilt_position();Look up the bytes in your camera's VISCA command list, and send them directly — no need to touch framing:
// Example: CAM_WB (white balance) Auto = 8x 01 04 35 00 FF
camera.send_command({0x04, 0x35, 0x00});
// Example: CAM_WB inquiry = 8x 09 04 35 FF
auto data = camera.send_inquiry({0x04, 0x35});try {
camera.zoom_direct(0x4000);
} catch (const visca::visca_error& e) {
// The camera replied with an error frame (e.g. out-of-range value).
std::cerr << "VISCA error 0x" << std::hex << int(e.code()) << ": " << e.what() << "\n";
} catch (const visca::transport_timeout& e) {
// No reply within the configured timeout (default 1500 ms).
std::cerr << "Timeout: " << e.what() << "\n";
} catch (const visca::transport_error& e) {
// Serial port / socket problem (port not found, cable unplugged, etc).
std::cerr << "Transport error: " << e.what() << "\n";
}MIT — see LICENSE.