grafton_visca/lib.rs
1//! # grafton-visca
2//!
3//! Rust library for VISCA over IP protocol to control PTZ cameras.
4
5#![forbid(unsafe_code)]
6#![warn(
7 clippy::all,
8 missing_docs,
9 missing_debug_implementations,
10 missing_copy_implementations,
11 trivial_casts,
12 trivial_numeric_casts,
13 unused_qualifications
14)]
15#![deny(
16 clippy::unwrap_used,
17 clippy::expect_used,
18 clippy::panic,
19 clippy::unimplemented,
20 clippy::todo
21)]
22#![allow(async_fn_in_trait)]
23
24//! ## What is VISCA?
25//!
26//! VISCA (Video System Control Architecture) is a protocol developed by Sony for controlling Ptz cameras
27//! commonly used in robotics, broadcasting, video conferencing, and surveillance applications. This crate
28//! implements VISCA over IP, allowing you to control networked Ptz cameras from Rust applications.
29//!
30//! ## Features
31//!
32//! - **Unified API Architecture**: Single consistent interface across blocking and async modes
33//! - **Type-Safe Camera Profiles**: Compile-time validation with camera-specific profiles
34//! - **Feature-Gated Methods**: Choose blocking or async at compile time with zero runtime overhead
35//! - **Multi-Runtime Support**: Tokio, async-std, smol can coexist with priority-based selection
36//! - **Complete Command Coverage**: Full VISCA protocol support across all camera types
37//! - **Profile-Aware Conversions**: Automatic unit conversions based on camera model
38//! - **Comprehensive Inquiry**: Query camera state for all supported features
39//! - **Transport Abstraction**: TCP, UDP, Serial, and custom transport implementations
40//! - **Builder Patterns**: Flexible camera and transport configuration
41//! - **Unified Error Handling**: Consistent error mapping across all transport types
42//! - **Configurable Timeouts**: Per-category timeout configuration for different command types
43//! - **Command Cancellation**: Cancel specific commands or entire socket operations
44//! - **Async Completion Tracking**: Wait for camera movements to complete with await methods
45//! - **Serialization Support**: Optional serde/schemars integration for all value types
46//!
47//! ## Serialization Support
48//!
49//! All public value types support optional serialization through feature-gated `serde` and `schemars` derives:
50//!
51//! ```toml
52//! [dependencies]
53//! grafton-visca = { version = "*", features = ["serde", "schemars"] }
54//! ```
55//!
56//! With these features enabled, you can serialize/deserialize all value types directly:
57//!
58//! ```rust
59//! # #[cfg(feature = "serde")] {
60//! use grafton_visca::types::{PanSpeed, ZoomPosition, SpeedLevel};
61//!
62//! // Serialize to JSON
63//! let speed = PanSpeed::new(12).unwrap();
64//! let json = serde_json::to_string(&speed).unwrap();
65//! assert_eq!(json, "12");
66//!
67//! // Deserialize from JSON
68//! let speed: PanSpeed = serde_json::from_str("15").unwrap();
69//! assert_eq!(speed.value(), 15);
70//!
71//! // Works with enums too
72//! let level = SpeedLevel::Medium;
73//! let json = serde_json::to_string(&level).unwrap();
74//! assert_eq!(json, "\"medium\"");
75//! # }
76//! ```
77//!
78//! ### Configuration Types with Serialization
79//!
80//! Camera configuration types also support serialization, making it easy to save and load
81//! camera setups from configuration files or APIs:
82//!
83//! ```rust
84//! # #[cfg(feature = "serde")] {
85//! use grafton_visca::camera::config::TransportOptions;
86//! use grafton_visca::camera::profiles::ProfileId;
87//!
88//! // Serialize camera profile
89//! let profile = ProfileId::PtzOpticsG2;
90//! let json = serde_json::to_string(&profile).unwrap();
91//! assert_eq!(json, "\"ptz-optics-g2\"");
92//!
93//! // Serialize transport configuration
94//! let transport = TransportOptions::Tcp {
95//! address: "192.168.0.110:5678".to_string(),
96//! };
97//! let json = serde_json::to_string(&transport).unwrap();
98//! // Can be loaded from config files, environment variables, etc.
99//! # }
100//! ```
101//!
102//! With `schemars` feature, you can also generate JSON schemas for API documentation:
103//!
104//! ```rust
105//! # #[cfg(all(feature = "serde", feature = "schemars"))] {
106//! use grafton_visca::types::PanSpeed;
107//! use schemars::schema_for;
108//!
109//! let schema = schema_for!(PanSpeed);
110//! // Use schema for API documentation, validation, etc.
111//! # }
112//! ```
113//!
114//! ## Model-Aware Parameter Validation
115//!
116//! The library provides comprehensive parameter validation at multiple levels, ensuring
117//! commands are correct before being sent to the camera:
118//!
119//! ### Type-Safe Parameters with Conservative Defaults
120//! All parameter types provide conservative VISCA-compliant ranges by default:
121//! ```ignore
122//! use grafton_visca::types::{PanSpeed, ZoomPosition, ZoomSpeed};
123//!
124//! // All range types expose MIN/MAX constants for validation
125//! assert_eq!(PanSpeed::MIN.value(), 0);
126//! assert_eq!(PanSpeed::MAX.value(), 24);
127//!
128//! // Validated constructors provide clear error messages
129//! let speed = PanSpeed::new(15)?; // Valid: 0-24
130//! match PanSpeed::new(30) {
131//! Err(e) => println!("{}", e), // "PanSpeed must be between 0 and 24"
132//! _ => {}
133//! }
134//!
135//! // Speed types work seamlessly with SpeedLevel enum
136//! let zoom = ZoomSpeed::from(SpeedLevel::Fast); // Automatic conversion
137//! assert_eq!(zoom.value(), 6); // Fast = 6 for zoom
138//! ```
139//!
140//! ### Model-Specific Validation
141//! For precise control, use model-aware constructors that validate against
142//! specific camera capabilities:
143//! ```ignore
144//! use grafton_visca::types::{PanPosition, TiltSpeed};
145//! use grafton_visca::constants::CameraVariant;
146//!
147//! // Model-specific validation for PTZOptics G2
148//! let model = CameraVariant::PtzOpticsG2;
149//! let pan = PanPosition::new_for_model(2000, model)?; // Validates against G2 pan range
150//! let speed = TiltSpeed::new_for_model(18, model)?; // Validates against G2 tilt speed
151//! ```
152//!
153//! ### Profile-Based Compile-Time Safety
154//! When using camera profiles, validation happens at compile time through
155//! capability traits:
156//! ```ignore
157//! use grafton_visca::{Camera, camera::profiles::PtzOpticsG2};
158//!
159//! // Profile provides model-specific constants at compile time
160//! let camera = Camera::<PtzOpticsG2, _>::new(transport);
161//! // Methods automatically use profile's validated ranges
162//! camera.pan_tilt_absolute(1000, 500, 10, 10)?; // Validated against G2 limits
163//! ```
164//!
165//! ### Compile-Time Capability Gating
166//! Vendor-specific commands are gated by marker traits, ensuring compile-time safety:
167//! ```ignore
168//! use grafton_visca::{FocusLockControl, camera::profiles::PtzOpticsG2};
169//!
170//! // FocusLockControl is only available for profiles with HasFocusLock
171//! // PtzOpticsG2 implements HasFocusLock, so these methods are available
172//! camera.enable_focus_lock()?; // Compiles with PtzOpticsG2
173//! camera.disable_focus_lock()?;
174//!
175//! // With a different profile that doesn't have HasFocusLock, this wouldn't compile
176//! ```
177//!
178//! This multi-layered approach ensures:
179//! - Early error detection at construction time
180//! - Model-specific precision when needed
181//! - Conservative defaults for generic usage
182//! - Zero-cost abstractions through compile-time validation
183//!
184//! ## Quick Start
185//!
186//! ### Blocking Example
187//! ```ignore
188//! use grafton_visca::{
189//! camera::Connect,
190//! camera::profiles::PtzOpticsG2,
191//! Error,
192//! };
193//!
194//! fn main() -> Result<(), Error> {
195//! // Create camera using convenience Connect helper
196//! let camera = Connect::open_tcp_blocking::<PtzOpticsG2>("192.168.0.110")?;
197//!
198//! // Use accessor-style API
199//! camera.power().on()?;
200//! camera.zoom().tele()?;
201//! camera.pan_tilt().home()?;
202//!
203//! Ok(())
204//! }
205//! ```
206//!
207//! ### Async Example with Multi-Runtime Support
208//! ```ignore
209//! use grafton_visca::{
210//! camera::Connect,
211//! camera::profiles::PtzOpticsG2,
212//! runtime::{Runtime, TokioRuntime},
213//! Error,
214//! };
215//!
216//! #[tokio::main]
217//! async fn main() -> Result<(), Error> {
218//! // Create camera using Connect helper with runtime
219//! let runtime = TokioRuntime::from_current()?;
220//! let camera = Connect::open_tcp_async::<PtzOpticsG2, _>(
221//! "192.168.0.110",
222//! runtime
223//! ).await?;
224//!
225//! // Use accessor-style API with async
226//! camera.power().on().await?;
227//! camera.zoom().tele().await?;
228//! camera.pan_tilt().home().await?;
229//!
230//! // Wait for movements to complete
231//! camera.await_idle().await?;
232//!
233//! Ok(())
234//! }
235//! ```
236//!
237//! ### Runtime Coexistence Example
238//! ```ignore
239//! // Multiple runtimes can coexist! Priority: tokio → async-std → smol
240//! [dependencies]
241//! grafton-visca = { version = "*", features = ["runtime-tokio", "runtime-async-std"] }
242//!
243//! use grafton_visca::{
244//! CameraBuilder, Error,
245//! camera::profiles::PtzOpticsG2,
246//! transport::Transport,
247//! PowerControl, ZoomControl,
248//! };
249//!
250//! #[async_std::main]
251//! async fn main() -> Result<(), Error> {
252//! // Same Transport API - runtime auto-selected based on priority
253//! let transport = Transport::tcp()
254//! .address("192.168.0.110:5678")
255//! .connect() // Uses tokio if available, async-std otherwise
256//! .await?;
257//! use grafton_visca::runtime::{AsyncStdRuntime, Runtime};
258//! let runtime = AsyncStdRuntime::new();
259//! let camera = CameraBuilder::with_executor(runtime)
260//! .open_async::<PtzOpticsG2, _>(transport)
261//! .await?;
262//!
263//! // Use accessor-style API across all runtimes
264//! camera.power().on().await?;
265//! camera.zoom().tele().await?;
266//!
267//! Ok(())
268//! }
269//! ```
270//!
271//! ### Unified Trait Usage Example
272//! ```ignore
273//! use grafton_visca::{
274//! CameraBuilder, Error,
275//! camera::profiles::PtzOpticsG2,
276//! transport::Transport,
277//! PowerControl, ZoomControl, PanTiltControl,
278//! };
279//!
280//! fn main() -> Result<(), Error> {
281//! smol::block_on(async {
282//! // Same unified Transport API works across all runtimes
283//! let transport = Transport::tcp()
284//! .address("192.168.0.110:5678")
285//! .connect() // Runtime auto-selected based on enabled features
286//! .await?;
287//! use grafton_visca::runtime::{Runtime, SmolRuntime};
288//! let runtime = SmolRuntime::new();
289//! let camera = CameraBuilder::with_executor(runtime)
290//! .open_async::<PtzOpticsG2, _>(transport)
291//! .await?;
292//!
293//! // Use accessor-style API consistently across runtimes
294//! camera.power().on().await?;
295//! camera.pan_tilt().home().await?;
296//! camera.zoom().tele().await?;
297//!
298//! Ok(())
299//! })
300//! }
301//! ```
302//!
303//! ## Camera Profiles
304//!
305//! The library includes pre-defined profiles with type aliases:
306//! - `PtzOpticsG2Cam<T>` - PtzOptics G2 series cameras
307//! - `SonyFR7Cam<T>` - Sony FR7 cameras with ND filter support
308//! - `GenericViscaCam<T>` - Generic VISCA-compatible cameras (conservative feature set)
309//!
310//! ### Compile-Time Type Safety
311//!
312//! The generic API ensures type safety at compile time:
313//!
314//! ```ignore
315//! use grafton_visca::prelude::blocking::*;
316//!
317//! // This function only accepts cameras with ND filter support
318//! fn adjust_nd_filter<P, T>(camera: &Camera<P, T>) -> Result<(), Error>
319//! where
320//! P: Profile + NdFilter,
321//! T: Transport + Send + Sync,
322//! {
323//! camera.set_nd_filter_mode(NdFilterMode::Clear)
324//! }
325//!
326//! // This would compile for SonyFR7 but not for PtzOpticsG2
327//! let sony = SonyFR7Cam::new(transport);
328//! adjust_nd_filter(&sony)?; // OK - Sony FR7 has ND filter
329//!
330//! let g2 = PtzOpticsG2Cam::new(transport);
331//! // adjust_nd_filter(&g2)?; // Compile error - G2 doesn't have ND filter
332//! ```
333//!
334//! ## Transport Implementation
335//!
336//! The library provides transport traits that you can implement for any communication method:
337//!
338//! ```ignore
339//! use grafton_visca::{transport::BlockingTransport, command::CommandKind, Error};
340//! use bytes::Bytes;
341//! use std::time::Duration;
342//!
343//! struct MyTransport {
344//! // Your transport state
345//! }
346//!
347//! impl BlockingTransport for MyTransport {
348//! fn send_with_kind(&mut self, data: &[u8], kind: CommandKind) -> Result<(), Error> {
349//! // Send data over your transport with proper framing based on kind
350//! Ok(())
351//! }
352//!
353//! fn recv(&mut self) -> Result<Bytes, Error> {
354//! // Receive response from your transport
355//! Ok(Bytes::new())
356//! }
357//!
358//! fn recv_with_timeout(&mut self, timeout: Duration) -> Result<Bytes, Error> {
359//! // Receive response with timeout
360//! Ok(Bytes::new())
361//! }
362//! }
363//! ```
364//!
365//! Example transport implementations are demonstrated in:
366//! - `examples/quickstart.rs` - TCP/IP transport with blocking API
367//! - `examples/quickstart_async.rs` - TCP/IP transport with async API
368//! - `examples-advanced/transports.rs` - Custom and advanced transport examples
369//!
370//! ## Async Support
371//!
372//! The library provides runtime-agnostic async support, allowing you to use ANY async runtime
373//! (tokio, async-std, smol, etc.) or even create your own.
374//!
375//! ### Feature Flags
376//!
377//! - `mode-async` - Enables async support without any specific runtime. You must provide your own runtime.
378//! - `mode-blocking` - Explicit feature flag for blocking mode (blocking is always available, this is for feature detection).
379//! - `runtime-tokio` - Enables async with built-in Tokio runtime support (implies `mode-async`).
380//! - `runtime-async-std` - Enables async with built-in async-std runtime support (implies `mode-async`).
381//! - `runtime-smol` - Enables async with built-in smol runtime support (implies `mode-async`).
382//! - `transport-serial` - Enables serial port support for blocking mode.
383//! - `transport-serial-tokio` - Enables serial port support with Tokio (implies `runtime-tokio`).
384//! - `test-utils` - Testing utilities including ScriptedTransport and DeterministicExecutor (not for production).
385//!
386//! **Multiple Runtime Support**: As of version 0.7.0, runtime features can be enabled simultaneously.
387//! This allows libraries to support multiple runtime ecosystems without forcing users to choose.
388//! Use explicit executor selection (`CameraBuilder::with_executor(TokioRuntime::from_current())`, etc.) when multiple runtimes are available.
389//!
390//! ### Send Future Guarantees
391//!
392//! All public async traits in this crate guarantee that their returned futures are `Send`.
393//! This is enforced through explicit `+ Send` bounds in trait signatures using
394//! return-position impl trait in traits (RPITIT).
395//!
396//! This guarantee ensures spawn-safety across all async runtimes and prevents
397//! subtle `!Send` future errors in multi-threaded executors.
398//!
399//! ### ⚠️ Important: Runtime Requirements for Async
400//!
401//! **The async API REQUIRES a runtime to be configured.** Without a runtime, ALL async operations
402//! will fail with: `Error::InvalidState("No runtime configured for async operations")`.
403//!
404//! The runtime is essential for:
405//! - **Timeout handling** - All camera commands have configurable timeouts
406//! - **Power sequences** - Power on/off operations require delays
407//! - **Movement detection** - Polling for pan/tilt/zoom completion
408//! - **Background tasks** - Socket manager for concurrent operations
409//!
410//! ### Runtime Requirements for Async
411//!
412//! You have multiple options for configuring a runtime:
413//!
414//! #### Option 1: Use built-in runtime support (Easiest)
415//!
416//! Choose your runtime(s) and enable the corresponding feature(s) in `Cargo.toml`:
417//!
418//! ```toml
419//! [dependencies]
420//! # Single runtime:
421//! grafton-visca = { version = "*", features = ["runtime-tokio"] }
422//! grafton-visca = { version = "*", features = ["runtime-async-std"] }
423//! grafton-visca = { version = "*", features = ["runtime-smol"] }
424//!
425//! # Multiple runtimes (choose executor at construction time):
426//! grafton-visca = { version = "*", features = ["runtime-tokio", "runtime-async-std"] }
427//! grafton-visca = { version = "*", features = ["runtime-tokio", "runtime-smol", "runtime-async-std"] }
428//! ```
429//!
430//! Then use the corresponding `CameraBuilder` method:
431//!
432//! ```ignore
433//! // Tokio
434//! use grafton_visca::runtime::{Runtime, TokioRuntime};
435//! let runtime = TokioRuntime::from_current()?;
436//! let camera = CameraBuilder::with_executor(runtime)
437//! .open_async::<PtzOpticsG2, _>(transport)
438//! .await?;
439//!
440//! // async-std
441//! use grafton_visca::runtime::{AsyncStdRuntime, Runtime};
442//! let runtime = AsyncStdRuntime::new();
443//! let camera = CameraBuilder::with_executor(runtime)
444//! .open_async::<PtzOpticsG2, _>(transport)
445//! .await?;
446//!
447//! // smol
448//! use grafton_visca::runtime::{Runtime, SmolRuntime};
449//! let runtime = SmolRuntime::new();
450//! let camera = CameraBuilder::with_executor(runtime)
451//! .open_async::<PtzOpticsG2, _>(transport)
452//! .await?;
453//! ```
454//!
455//! #### Option 2: Provide your own runtime (Advanced)
456//!
457//! For complete runtime independence, use the unified Executor trait:
458//!
459//! ```ignore
460//! use grafton_visca::{
461//! Camera, CameraBuilder, Executor,
462//! prelude::r#async::*,
463//! };
464//! use std::{pin::Pin, time::Duration, future::Future};
465//!
466//! // Example: Custom executor implementation for async-std
467//! #[derive(Debug, Clone)]
468//! struct AsyncStdExecutor;
469//!
470//! impl Executor for AsyncStdExecutor {
471//! type Join<T> = Pin<Box<dyn Future<Output = Result<T, ExecError>> + Send + 'static>>
472//! where T: Send + 'static;
473//!
474//! fn spawn<F>(&self, fut: F) -> Self::Join<F::Output>
475//! where
476//! F: Future + Send + 'static,
477//! F::Output: Send + 'static,
478//! {
479//! // Implementation using async-std
480//! // ...
481//! }
482//!
483//! fn block_on<F: Future>(&self, fut: F) -> F::Output {
484//! async_std::task::block_on(fut)
485//! }
486//!
487//! fn sleep(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
488//! Box::pin(async_std::task::sleep(duration))
489//! }
490//!
491//! fn timeout<'a, F, T>(
492//! &'a self,
493//! duration: Duration,
494//! fut: F,
495//! ) -> Pin<Box<dyn Future<Output = Result<T, Error>> + Send + 'a>>
496//! where
497//! F: Future<Output = T> + Send + 'a,
498//! T: Send + 'a,
499//! {
500//! // Implementation using async-std timeout
501//! // ...
502//! }
503//! }
504//!
505//! #[async_std::main]
506//! async fn main() -> Result<(), Error> {
507//! // Create camera with custom executor
508//! let executor = AsyncStdExecutor;
509//! let camera = CameraBuilder::with_executor(executor)
510//! .open_async::<PtzOpticsG2, _>(transport)?;
511//!
512//! // All async operations now use async-std
513//! camera.power_on().await?;
514//! camera.zoom_in().await?;
515//! Ok(())
516//! }
517//! ```
518//!
519//! ### Common Runtime Errors and Solutions
520//!
521//! #### Error: `InvalidState("No runtime configured for async operations")`
522//! **Cause:** You're using async mode but haven't configured a runtime.
523//! **Solution:** Either:
524//! - Enable `runtime-tokio` feature and use `CameraBuilder::with_executor(TokioRuntime::from_current())`
525//! - Use `CameraBuilder::with_executor()` with your own runtime implementation
526//!
527//! #### Error: `InvalidState("Operation requires runtime for timeout handling")`
528//! **Cause:** The operation needs timeout support but no runtime is available.
529//! **Solution:** Same as above - configure a runtime.
530//!
531//! #### Error: Socket manager initialization issues
532//! **Cause:** The socket manager requires a runtime to spawn background tasks.
533//! **Solution:** Ensure your runtime's `Spawner` implementation is working correctly.
534//!
535//! ### Blocking vs Async Mode
536//!
537//! The library provides a clean separation between blocking and async APIs:
538//!
539//! - **Blocking mode** (default): No async dependencies, uses synchronous I/O
540//! - When no features are enabled, only blocking types are available
541//! - Zero async runtime overhead or dependencies
542//!
543//! - **Async mode** (`mode-async` feature): Native async implementation
544//! - When `mode-async` feature is enabled, blocking types are NOT exported
545//! - Provides true async I/O without blocking thread pools
546//! - REQUIRES runtime configuration (see Async Support section above)
547//!
548//! The API surface changes based on your feature selection - you get either blocking
549//! OR async types, never both. This ensures a clean, focused API for your use case.
550//!
551//! ## Supported Commands
552//!
553//! ### Camera Movement
554//! - Pan/Tilt/Zoom control with absolute and relative positioning
555//! - Variable speed control for smooth movements
556//! - Home position and preset management
557//!
558//! ### Exposure & Color
559//! - Exposure modes: Auto, Manual, Shutter Priority, Iris Priority, Bright
560//! - White balance modes including manual color temperature
561//! - Color adjustments: saturation, hue, RGB gain tuning
562//!
563//! ### Image Control
564//! - Focus control with auto/manual modes
565//! - Sharpness, brightness, and contrast adjustment
566//! - Noise reduction (2D and 3D)
567//! - Image flip and other effects
568//!
569//! ## Position Units
570//!
571//! The Camera API supports multiple position unit types with automatic conversion:
572//!
573//! ```ignore
574//! // Work in degrees (recommended)
575//! camera.set_position(Degrees(45.0), Degrees(-15.0))?;
576//!
577//! // Stop all movement
578//! camera.stop()?;
579//!
580//! // Move to home position
581//! camera.home()?;
582//! ```
583//!
584//! ## Timeout Configuration
585//!
586//! Configure timeouts per command category based on your network and camera:
587//!
588//! ```ignore
589//! use grafton_visca::{CameraBuilder, TimeoutConfig};
590//! use std::time::Duration;
591//!
592//! let config = TimeoutConfig::builder()
593//! .ack_timeout(Duration::from_millis(300))
594//! .quick_commands(Duration::from_secs(3))
595//! .movement_commands(Duration::from_secs(20))
596//! .preset_operations(Duration::from_secs(60))
597//! .open();
598//!
599//! // For async mode (default)
600//! use grafton_visca::runtime::{Runtime, TokioRuntime};
601//! let runtime = TokioRuntime::from_current()?;
602//! let camera = CameraBuilder::with_executor(runtime)
603//! .timeout_config(config)
604//! .open_async::<PtzOpticsG2, _>(transport).await?;
605//!
606//! // For blocking mode (when async feature is disabled)
607//! #[cfg(not(feature = "mode-async"))]
608//! let camera = CameraBuilder::new()
609//! .timeout_config(config)
610//! .build_blocking::<PtzOpticsG2, _>(transport)?;
611//! ```
612//!
613//! ## Command Cancellation (Async)
614//!
615//! Cancel specific commands or entire socket operations:
616//!
617//! ```ignore
618//! // Send a command and get its ID for cancellation
619//! let (cmd_id, response_future) = camera.send_command_with_id(command).await?;
620//!
621//! // Cancel the specific command
622//! camera.cancel_command(cmd_id).await?;
623//!
624//! // Or cancel all commands on a socket
625//! use grafton_visca::ViscaSocket;
626//! camera.cancel_socket(ViscaSocket::S1).await?;
627//! ```
628//!
629//! ## Movement Completion Tracking
630//!
631//! Wait for camera movements to complete using [`AwaitConfig`](camera::AwaitConfig):
632//!
633//! ```ignore
634//! use std::time::Duration;
635//! use grafton_visca::camera::{AwaitConfig, Axes};
636//!
637//! // Start a pan/tilt movement
638//! camera.pan_tilt_absolute(45.0, 15.0, 10, 10).await?;
639//!
640//! // Wait for all movements to complete (pan/tilt, zoom, focus)
641//! camera.await_idle(Duration::from_secs(30)).await?;
642//!
643//! // Or wait for specific axes with custom configuration
644//! let config = AwaitConfig::new(Duration::from_secs(10))
645//! .with_axes(Axes::PAN_TILT)
646//! .with_debug();
647//! camera.await_with_config(&config).await?;
648//!
649//! // Convenience methods for common scenarios
650//! camera.await_pan_tilt_idle(Duration::from_secs(20)).await?;
651//! camera.await_zoom_idle(Duration::from_secs(15)).await?;
652//! ```
653//!
654//! ## Error Handling
655//!
656//! The library provides comprehensive error types for all VISCA error conditions:
657//!
658//! ```ignore
659//! match camera.pan_tilt_absolute(180.0, 0.0, 10, 10).await {
660//! Ok(_) => println!("Position set successfully"),
661//! Err(Error::SyntaxError) => println!("Position out of range"),
662//! Err(Error::CommandNotExecutable) => println!("Camera busy or powered off"),
663//! Err(Error::CommandBufferFull) => {
664//! // This error is automatically retried by the runtime
665//! println!("Camera buffer full, command will retry");
666//! }
667//! Err(e) => println!("Other error: {e}"),
668//! }
669//! ```
670
671pub use grafton_visca_macros::{ViscaEnum, ViscaInquiry, ViscaValue};
672
673pub use crate::{
674 cache::{PanTiltLimits, StateCache},
675 camera::{Camera, CameraBuilder},
676 camera_id::CameraId,
677 command::{
678 exposure::ExposureMode,
679 focus::{AutoFocusSensitivity, FocusMode},
680 nd_filter::NdFilterMode,
681 pan_tilt::{PanTiltDirection, PanTiltLimitCorner},
682 preset::PresetNumber,
683 resolution::{PictureEffectMode, ResolutionMode},
684 system::{MotionSyncMode, MotionSyncPreset},
685 white_balance::{AutoWhiteBalanceSensitivity, WhiteBalanceMode},
686 },
687 error::{Error, ErrorKind, Result},
688 inquiry_conversions::{
689 zoom_from_normalized, Normalized, PanTiltPositionDeg, PanTiltPositionRaw, ZoomDomain,
690 ZoomPositionExt,
691 },
692 types::{Coarse, FocusSpeed, MotionSyncSpeed, SpeedLevel, ZoomSpeed},
693 visca_socket::ViscaSocket,
694};
695
696#[cfg(not(feature = "mode-async"))]
697pub use crate::camera::{blocking_api::BlockingClient, BlockingCamera};
698
699#[cfg(feature = "mode-async")]
700pub use crate::camera::AsyncCamera;
701
702#[cfg(all(feature = "mode-async", feature = "runtime-async-std"))]
703pub use crate::executor::AsyncStdExecutor;
704#[cfg(all(feature = "mode-async", feature = "runtime-smol"))]
705pub use crate::executor::SmolExecutor;
706#[cfg(all(feature = "mode-async", feature = "runtime-tokio"))]
707pub use crate::executor::TokioExecutor;
708#[cfg(feature = "mode-async")]
709pub use crate::executor::{ExecError, Executor};
710
711#[cfg(all(feature = "mode-async", feature = "runtime-async-std"))]
712pub use crate::runtime::AsyncStdRuntime;
713#[cfg(all(feature = "mode-async", feature = "runtime-smol"))]
714pub use crate::runtime::SmolRuntime;
715#[cfg(all(feature = "mode-async", feature = "runtime-tokio"))]
716pub use crate::runtime::TokioRuntime;
717#[cfg(feature = "mode-async")]
718pub use crate::runtime::{Runtime, TransportHandle};
719
720#[doc(hidden)]
721pub use crate::camera::controls::{
722 color::ColorControl,
723 exposure::ExposureControl,
724 focus::{FocusControl, FocusLockControl, PushAFControl},
725 image_processing::ImageProcessingControl,
726 inquiry::{InquiryControl, PanTiltInquiryControl},
727 menu::{DirectMenuControl, MenuControl},
728 motion::{MotionControl, MotionGuard},
729 nd_filter::NdFilterControl,
730 pan_tilt::PanTiltControl,
731 power::PowerControl,
732 presets::PresetsControl,
733 streaming::StreamingControl,
734 system::SystemControl,
735 tally::TallyControl,
736 variable_speed::VariableSpeedControl,
737 white_balance::WhiteBalanceControl,
738 zoom::ZoomControl,
739};
740
741mod error;
742pub(crate) mod macros;
743
744#[cfg(feature = "mode-async")]
745pub(crate) mod executor;
746
747#[cfg(not(feature = "mode-async"))]
748pub(crate) mod executor {
749 /// Dummy Executor trait for non-async mode.
750 /// This allows the code to remain uniform regardless of feature flags.
751 pub trait Executor {}
752
753 /// Unit type implements Executor for blocking mode
754 impl Executor for () {}
755}
756
757/// State cache for write-only VISCA properties.
758///
759/// This module provides optional state tracking for write-only properties
760/// that have no corresponding VISCA inquiry command. Values are cached
761/// automatically when setter commands succeed.
762pub mod cache;
763
764/// Camera profile system for type-safe, model-specific control
765pub mod camera;
766
767/// Camera ID type for VISCA protocol addressing
768pub mod camera_id;
769
770/// Capability traits for camera feature composition
771pub mod capabilities;
772
773/// Command definitions for VISCA protocol (advanced use only)
774///
775/// **⚠️ Advanced API**: This module contains low-level protocol implementation details.
776/// Most users should use the high-level camera accessor API instead:
777/// - `camera.power().on()` instead of manual command construction
778/// - `camera.zoom().position()` instead of response matching
779///
780/// This module remains public for extensibility but its direct use is discouraged.
781/// Consider it unstable and subject to breaking changes.
782pub mod command;
783
784/// Constants for VISCA protocol including default ports
785pub mod constants;
786
787/// Diagnostics and health check utilities
788pub mod diagnostics;
789
790/// Inquiry conversion utilities for raw to user-friendly values
791pub mod inquiry_conversions;
792
793pub mod mode;
794
795pub mod prelude;
796
797/// Protocol encoding and decoding utilities (internal use)
798///
799/// **⚠️ Internal API**: This module contains protocol-level utilities.
800/// Users should not need to interact with this module directly.
801pub mod protocol;
802
803/// VISCA runtime with flume-based scheduling
804pub mod runtime;
805
806/// Runtime-specific transport adapters
807#[cfg(feature = "mode-async")]
808pub mod runtime_adapters;
809
810/// Testing utilities (available with test-utils feature for deterministic testing)
811#[cfg(any(feature = "runtime-tokio", feature = "test-utils"))]
812pub mod testing;
813
814pub mod timeout;
815
816/// Transport layer for implementing custom transports
817pub mod transport;
818
819/// Type definitions and abstractions
820pub mod types;
821
822/// Semantic unit types for intuitive API usage
823pub mod units;
824
825/// Unified VISCA socket type
826pub mod visca_socket;
827
828/// Dynamic trait object API with per-operation timeout support.
829///
830/// This module provides object-safe trait definitions (`dyn DynCameraControl`)
831/// for runtime polymorphism. Use this when you need to work with cameras as
832/// trait objects rather than concrete generic types.
833///
834/// Enable with the `dyn-api` feature flag.
835#[cfg(feature = "dyn-api")]
836pub mod dynapi;
837
838/// Camera profiles with compositional capabilities
839pub mod profiles {
840 pub use crate::camera::profiles::{
841 GenericVisca, NearusBRC300, ProfileGroup, ProfileId, PtzOptics30X, PtzOpticsG2,
842 PtzOpticsG3, SonyBRC300, SonyBRCH900, SonyEVIH100, SonyFR7,
843 };
844}