From 7762ec75f073b430600f6d7b8f0c70f8c4aef08c Mon Sep 17 00:00:00 2001 From: Thomas Kroll Date: Fri, 13 Nov 2026 11:52:32 +0100 Subject: [PATCH 1/2] Add guards for the groundwork move A byte guard records every frame and short command the builders emit for a fixed list of calls, a CLI guard records the help text of every subcommand, and the release dry run lists the files of every package. The workspace move must leave all three unchanged. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/release.yml | 52 +- scripts/groundwork/move-check.sh | 25 + scripts/groundwork/test-names.sh | 17 + scripts/package-contents.sh | 19 + tests/byte_guard.rs | 576 +++++++++++++++ tests/cli_surface.rs | 75 ++ tests/fixtures/byte-guard.txt | 831 ++++++++++++++++++++++ tests/fixtures/cli-surface.txt | 1126 ++++++++++++++++++++++++++++++ 8 files changed, 2720 insertions(+), 1 deletion(-) create mode 100755 scripts/groundwork/move-check.sh create mode 100755 scripts/groundwork/test-names.sh create mode 100755 scripts/package-contents.sh create mode 100644 tests/byte_guard.rs create mode 100644 tests/cli_surface.rs create mode 100644 tests/fixtures/byte-guard.txt create mode 100644 tests/fixtures/cli-surface.txt diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4db498c..313d644 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -276,10 +276,60 @@ jobs: path: dist/* if-no-files-found: error + # Lists the files of every package and compares the list with packaging/contents.txt, so that + # a change to what a package holds shows up in review. A release needs it too. + contents: + needs: [version, binary, deb, rpm, arch] + runs-on: ubuntu-22.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + path: dist + pattern: dist-* + merge-multiple: true + - run: sudo apt-get install -y --no-install-recommends libarchive-tools + # [profile.release] strip = true must reach every package (cargo ignores a member + # manifest's profile, and the file list alone would not show it) + - name: The packaged binaries are stripped + run: | + for pkg in dist/obsbot4linux*; do + case $pkg in + *.deb) bsdtar -xOf "$pkg" 'data.tar*' > payload ;; + *) cp "$pkg" payload ;; + esac + bin=$(bsdtar -tf payload | grep -E '(usr/bin|-linux)/obsbot4linux$') + bsdtar -xOf payload "$bin" > obsbot4linux.bin + info=$(file -b obsbot4linux.bin) + echo "$pkg: $info" + case $info in + *', stripped') ;; + *) echo "::error::$pkg ships an unstripped binary"; exit 1 ;; + esac + done + rm -f payload obsbot4linux.bin + - name: Compare the package contents + env: + VER: ${{ needs.version.outputs.ver }} + run: | + scripts/package-contents.sh dist "$VER" > contents.txt + cat contents.txt + diff -u packaging/contents.txt contents.txt + # the list itself, also when it differs, to update packaging/contents.txt from + - if: always() + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: package-contents + path: contents.txt + if-no-files-found: error + # Tags only: a pull request builds the packages above and publishes nothing. release: if: startsWith(github.ref, 'refs/tags/') - needs: [binary, deb, rpm, arch] + needs: [binary, deb, rpm, arch, contents] runs-on: ubuntu-22.04 timeout-minutes: 15 permissions: diff --git a/scripts/groundwork/move-check.sh b/scripts/groundwork/move-check.sh new file mode 100755 index 0000000..d131bec --- /dev/null +++ b/scripts/groundwork/move-check.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Move check of the groundwork stage. Prints the lines of Rust code that differ between two +# commits, compared as a multiset: the file a line is in and the order of lines do not count, +# and `use` statements, `mod name;` declarations, `pub(crate)` / `pub(super)`, indentation and +# blank lines are set aside. A pure move prints only the structural lines its task lists. +# Usage: scripts/move-check.sh OLD_REV [NEW_REV] (NEW_REV defaults to HEAD) +set -euo pipefail +old=${1:?usage: move-check.sh OLD_REV [NEW_REV]} +new=${2:-HEAD} + +lines() { + git ls-tree -r --name-only "$1" | grep '\.rs$' | while read -r f; do + git show "$1:$f" + done | awk ' + in_use { if (/;[[:space:]]*$/) in_use = 0; next } + /^[[:space:]]*(pub(\([a-z]+\))? )?use / { if (!/;[[:space:]]*$/) in_use = 1; next } + /^[[:space:]]*(pub(\([a-z]+\))? )?mod [a-z0-9_]+;[[:space:]]*$/ { next } + /^[[:space:]]*$/ { next } + { sub(/^[[:space:]]+/, ""); sub(/^pub\((crate|super)\) /, ""); print } + ' | LC_ALL=C sort +} + +diff <(lines "$old") <(lines "$new") | grep '^[<>]' || true diff --git a/scripts/groundwork/test-names.sh b/scripts/groundwork/test-names.sh new file mode 100755 index 0000000..fd0c97c --- /dev/null +++ b/scripts/groundwork/test-names.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Test-name guard of the groundwork stage. Lists the tests of a commit by the last two parts of +# their path (module and name, which a move keeps), sorted. Run it for the base and the head of +# a pull request and compare the two lists: a pure move changes no line. +# Usage: scripts/groundwork/test-names.sh REV > names.txt +set -euo pipefail +rev=${1:?usage: test-names.sh REV} +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT +git archive "$rev" | tar -x -C "$work" +cd "$work" +CARGO_TARGET_DIR=${CARGO_TARGET_DIR:-$work/target} nice -n 19 \ + cargo test --workspace --locked -q -j 3 -- --list --format terse 2>/dev/null | + awk -F': ' '$2 == "test" { n = split($1, p, "::"); print (n > 1 ? p[n - 1] "::" : "") p[n] }' | + LC_ALL=C sort diff --git a/scripts/package-contents.sh b/scripts/package-contents.sh new file mode 100755 index 0000000..a4cef16 --- /dev/null +++ b/scripts/package-contents.sh @@ -0,0 +1,19 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Lists the files of every package a release build made: one line per entry, " +# ", sorted, with the version replaced by VERSION and the architecture by ARCH, +# so that two builds can be compared. The release workflow compares the list with +# packaging/contents.txt. Needs bsdtar (libarchive). +# Usage: scripts/package-contents.sh DIR X.Y.Z (DIR holds the obsbot4linux* packages) +set -eu +dir=${1:?usage: package-contents.sh DIR X.Y.Z} +ver=${2:?usage: package-contents.sh DIR X.Y.Z} + +find "$dir" -type f -name 'obsbot4linux*' | while read -r pkg; do + name=$(basename "$pkg") + case $name in + *.deb) bsdtar -xOf "$pkg" 'data.tar*' | bsdtar -tvf - ;; + *) bsdtar -tvf "$pkg" ;; + esac | awk -v p="$name" '{ print p, $1, $NF }' +done | sed -e "s/$ver/VERSION/g" -e 's/x86_64\|aarch64\|amd64\|arm64/ARCH/g' | LC_ALL=C sort diff --git a/tests/byte_guard.rs b/tests/byte_guard.rs new file mode 100644 index 0000000..ff798c2 --- /dev/null +++ b/tests/byte_guard.rs @@ -0,0 +1,576 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +//! Byte guard of the groundwork stage (rebuild plan, stage G): every frame and short command the +//! builders emit for a fixed list of calls, one line per call, compared with +//! `tests/fixtures/byte-guard.txt`. That file was written from the 0.13 code before the +//! workspace move, so a move that changes one byte that would go to the camera fails here. +//! +//! `BYTE_GUARD_BLESS=1 cargo test --test byte_guard` writes the file: once when this test is +//! added, and on `main` in a change that alters what a builder sends on purpose (that change +//! lists the changed lines). A move never writes it; every other run compares. + +use clap::ValueEnum; +use obsbot4linux::commands::{self as cmd, *}; +use obsbot4linux::frame::{self, FRAME_SIZE}; +use obsbot4linux::presets::{self, Pose, StartupAi}; +use std::fmt::Display; + +const FIXTURE: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/byte-guard.txt"); +const SEQ: u16 = 0x1234; +const SEQS: [u16; 4] = [SEQ, SEQ + 1, SEQ + 2, SEQ + 3]; + +/// How one builder result is written into the fixture. +trait Show { + fn show(&self) -> String; +} + +impl Show for [u8; N] { + fn show(&self) -> String { + hex::encode(self) + } +} + +impl Show for [[u8; N]; M] { + fn show(&self) -> String { + self.iter().map(hex::encode).collect::>().join(" ") + } +} + +impl Show for u8 { + fn show(&self) -> String { + self.to_string() + } +} + +impl Show for u16 { + fn show(&self) -> String { + format!("{self:#06x}") + } +} + +impl Show for bool { + fn show(&self) -> String { + self.to_string() + } +} + +impl Show for Option { + fn show(&self) -> String { + self.as_ref().map_or("none".into(), Show::show) + } +} + +impl Show for Result { + fn show(&self) -> String { + match self { + Ok(v) => v.show(), + Err(e) => format!("error: {e}"), + } + } +} + +fn rec(out: &mut Vec, label: String, value: impl Show) { + out.push(format!("{label} = {}", value.show())); +} + +/// Poses at and beyond every bound the pose builders check, and an asymmetric one. +const POSES: [(f32, f32, f32); 10] = [ + (10.5, -5.25, 2.0), + (0.0, 0.0, 1.0), + (-150.0, -70.0, 1.0), + (150.0, 90.0, 4.0), + (150.1, 0.0, 1.0), + (0.0, 90.5, 1.0), + (0.0, -70.5, 1.0), + (0.0, 0.0, 4.01), + (0.0, 0.0, 0.99), + (f32::NAN, 0.0, 1.0), +]; + +fn poses() -> impl Iterator { + POSES + .into_iter() + .map(|(pan, tilt, zoom)| Pose { pan, tilt, zoom }) +} + +/// Area ranges (min, max) in the camera's angles, at and beyond the bounds, and wrong ways round. +const RANGES: [(f32, f32); 11] = [ + (-45.0, 45.0), + (-150.0, 150.0), + (-150.5, 0.0), + (0.0, 150.5), + (-90.0, 70.0), + (-90.5, 0.0), + (0.0, 70.5), + (-30.0, 10.0), + (10.0, 10.0), + (10.0, -10.0), + (f32::NAN, 1.0), +]; + +type Short3 = fn(bool) -> [u8; 3]; +type Framed = fn(bool, u16) -> [u8; FRAME_SIZE]; +type SeqOnly = fn(u16) -> [u8; FRAME_SIZE]; + +fn record() -> Vec { + let mut out = Vec::new(); + let o = &mut out; + + // frame codec + for seq in [0x0000, 0x0001, SEQ, 0xffff] { + let q = frame::query_frame(0x04, 0x0104, seq); + rec( + o, + format!("frame::query_frame(0x04, 0x0104, {seq:#06x})"), + q, + ); + let f = frame::frame(0x02, 0xa0c2, &[1, 0, 0, 0], seq); + rec( + o, + format!("frame::frame(0x02, 0xa0c2, [1 0 0 0], {seq:#06x})"), + f, + ); + let w = frame::query_frame_with(0x04, 0x1504, &[7, 0, 0, 0], seq); + rec( + o, + format!("frame::query_frame_with(0x04, 0x1504, [7 0 0 0], {seq:#06x})"), + w, + ); + } + let full = frame::frame(0x04, 0x3e44, &[0xa5; frame::MAX_DATA], SEQ); + rec( + o, + "frame::frame(0x04, 0x3e44, [0xa5; MAX_DATA])".into(), + full, + ); + rec( + o, + "frame::crc16_usb(\"123456789\")".into(), + frame::crc16_usb(b"123456789"), + ); + + // short commands (selector 6) + let short3: [(&str, Short3); 6] = [ + ("hdr", cmd::hdr), + ("led_blink", cmd::led_blink), + ("mirror", cmd::mirror), + ("mic_in_sleep", cmd::mic_in_sleep), + ("auto_gain", cmd::auto_gain), + ("microphone", cmd::microphone), + ]; + for (name, f) in short3 { + for on in [false, true] { + rec(o, format!("cmd::{name}({on})"), f(on)); + } + } + for on in [false, true] { + rec( + o, + format!("cmd::sleep_background_mirror({on})"), + cmd::sleep_background_mirror(on), + ); + } + for m in AiMode::value_variants() { + rec(o, format!("cmd::ai_mode({m:?})"), cmd::ai_mode(*m)); + rec( + o, + format!("cmd::default_work_mode({m:?})"), + cmd::default_work_mode(*m), + ); + } + for s in [i16::MIN, -600, -1, 0, 1, 600, i16::MAX] { + rec(o, format!("cmd::auto_sleep({s})"), cmd::auto_sleep(s)); + } + for s in [0u16, 1, 600, 32767, 32768, u16::MAX] { + rec( + o, + format!("cmd::auto_sleep_off({s})"), + cmd::auto_sleep_off(s), + ); + } + for b in SleepBackground::value_variants() { + rec( + o, + format!("cmd::sleep_background({b:?})"), + cmd::sleep_background(*b), + ); + } + for b in 0..=4u8 { + rec(o, format!("cmd::led({b})"), cmd::led(b)); + } + for p in [0u8, 1, 50, 100, 101, 255] { + rec(o, format!("cmd::wake_zoom({p})"), cmd::wake_zoom(p)); + } + for f in [0.99f32, 1.0, 1.5, 2.5, 4.0, 4.01, f32::NAN] { + rec( + o, + format!("cmd::wake_zoom_percent({f})"), + cmd::wake_zoom_percent(f), + ); + } + for v in FieldOfView::value_variants() { + rec( + o, + format!("cmd::field_of_view({v:?})"), + cmd::field_of_view(*v), + ); + } + for v in NoiseReduction::value_variants() { + rec( + o, + format!("cmd::noise_reduction({v:?})"), + cmd::noise_reduction(*v), + ); + } + for v in PickupDistance::value_variants() { + rec( + o, + format!("cmd::pickup_distance({v:?})"), + cmd::pickup_distance(*v), + ); + } + for v in ExposureMode::value_variants() { + rec( + o, + format!("cmd::exposure_mode_select({v:?})"), + cmd::exposure_mode_select(*v), + ); + } + rec(o, "cmd::startup_go()".into(), cmd::startup_go()); + + // framed commands (selector 2) + let framed: [(&str, Framed); 9] = [ + ("invert", cmd::invert), + ("zone_tracking", cmd::zone_tracking), + ("hand_pan", cmd::hand_pan), + ("hand_tilt", cmd::hand_tilt), + ("hand_gimbal", cmd::hand_gimbal), + ("autofocus_mode", cmd::autofocus_mode), + ("zone_follow", cmd::zone_follow), + ("sleep", cmd::sleep), + ("auto_zoom", cmd::auto_zoom), + ]; + for (name, f) in framed { + for on in [false, true] { + rec(o, format!("cmd::{name}({on})"), f(on, SEQ)); + } + } + let seq_only: [(&str, SeqOnly); 6] = [ + ("zone_return_go", cmd::zone_return_go), + ("hand_return_go", cmd::hand_return_go), + ("hand_return_reset", cmd::hand_return_reset), + ("factory_reset", cmd::factory_reset), + ("gimbal_stop", cmd::gimbal_stop), + ("zoom_stop", cmd::zoom_stop), + ]; + for (name, f) in seq_only { + rec(o, format!("cmd::{name}()"), f(SEQ)); + } + for v in TrackingSpeed::value_variants() { + rec( + o, + format!("cmd::tracking_speed({v:?})"), + cmd::tracking_speed(*v, SEQ), + ); + } + for v in SpeedMode::value_variants() { + rec( + o, + format!("cmd::speed_mode({v:?})"), + cmd::speed_mode(*v, SEQ), + ); + } + for v in FramingSize::value_variants() { + rec( + o, + format!("cmd::framing_size({v:?})"), + cmd::framing_size(*v, SEQ), + ); + } + for v in AutoZoomSpeed::value_variants() { + rec( + o, + format!("cmd::auto_zoom_speed({v:?})"), + cmd::auto_zoom_speed(*v, SEQ), + ); + } + for v in PartMode::value_variants() { + rec(o, format!("cmd::part_mode({v:?})"), cmd::part_mode(*v, SEQ)); + } + for v in HandSide::value_variants() { + rec(o, format!("cmd::hand_side({v:?})"), cmd::hand_side(*v, SEQ)); + } + for v in AutofocusMode::value_variants() { + rec( + o, + format!("cmd::autofocus_mode_select({v:?})"), + cmd::autofocus_mode_select(*v, SEQ), + ); + } + for g in Gesture::value_variants() { + for on in [false, true] { + rec( + o, + format!("cmd::gesture({g:?}, {on})"), + cmd::gesture(*g, on, SEQ), + ); + } + } + for r in [0.99f32, 1.0, 2.5, 4.0, 4.01, f32::NAN] { + rec( + o, + format!("cmd::gesture_zoom_ratio({r})"), + cmd::gesture_zoom_ratio(r, SEQ), + ); + } + for s in [-1.0f32, -0.0, 0.0, 2.5, 600.0, f32::INFINITY, f32::NAN] { + rec( + o, + format!("cmd::hand_return_after({s})"), + cmd::hand_return_after(s, SEQ), + ); + } + for v in [0u8, 1, 50, 100, 101, 255] { + rec( + o, + format!("cmd::audio_volume({v})"), + cmd::audio_volume(v, SEQ), + ); + } + for x in [-0.01f32, 0.0, 0.25, 1.0, 1.01] { + for y in [-0.01f32, 0.0, 0.75, 1.0, 1.01] { + rec( + o, + format!("cmd::focus_point({x}, {y})"), + cmd::focus_point(x, y, SEQ), + ); + rec( + o, + format!("cmd::view_center({x}, {y})"), + cmd::view_center(x, y, SEQ), + ); + } + } + for r in RANGES { + for (yaw, pitch) in [(r, (-30.0, 10.0)), ((-45.0, 45.0), r)] { + rec( + o, + format!("cmd::zone_area({yaw:?}, {pitch:?})"), + cmd::zone_area(yaw, pitch, SEQS), + ); + rec( + o, + format!("cmd::hand_area({yaw:?}, {pitch:?})"), + cmd::hand_area(yaw, pitch, SEQS), + ); + } + } + rec( + o, + "cmd::hand_area_reset()".into(), + cmd::hand_area_reset(SEQS), + ); + let axes = [ + None, + Some(-150.0f32), + Some(-70.0), + Some(0.0), + Some(12.5), + Some(90.0), + Some(150.0), + Some(150.5), + Some(-90.5), + ]; + for yaw in axes { + for pitch in axes { + rec( + o, + format!("cmd::gimbal_pose({yaw:?}, {pitch:?})"), + cmd::gimbal_pose(yaw, pitch, SEQ), + ); + } + } + for (yaw, pitch) in [ + (None, None), + (Some(12.5f32), None), + (None, Some(-5.0f32)), + (Some(12.5), Some(-5.0)), + (Some(150.5), Some(0.0)), + ] { + for speed in [0.99f32, 1.0, 45.0, 180.0, 180.5, f32::NAN] { + let f = cmd::gimbal_pose_speed(yaw, pitch, speed, SEQ); + rec( + o, + format!("cmd::gimbal_pose_speed({yaw:?}, {pitch:?}, {speed})"), + f, + ); + } + } + for pan in [-178.5f32, -178.0, -0.0, 0.0, 12.5, 178.0, 178.5, f32::NAN] { + for tilt in [-178.5f32, -178.0, -0.0, 0.0, 5.0, 178.0, 178.5] { + let f = cmd::gimbal_rate(pan, tilt, false, false, SEQ); + rec( + o, + format!("cmd::gimbal_rate({pan}, {tilt}, false, false)"), + f, + ); + } + } + for (mirror, invert) in [(true, false), (false, true), (true, true)] { + let f = cmd::gimbal_rate(12.5, 5.0, mirror, invert, SEQ); + rec( + o, + format!("cmd::gimbal_rate(12.5, 5, {mirror}, {invert})"), + f, + ); + } + for speed in [0i32, 1, 10, 30, 50, i32::MAX] { + for ratio in [0.99f32, 1.0, 2.5, 4.0, 4.01] { + rec( + o, + format!("cmd::zoom_to({speed}, {ratio})"), + cmd::zoom_to(speed, ratio, SEQ), + ); + } + } + for vertical in [false, true] { + for f in [-0.17f32, -0.16, -0.0, 0.0, 0.1, 0.16, 0.17, f32::NAN] { + let b = cmd::framing_offset(vertical, f, SEQ); + rec(o, format!("cmd::framing_offset({vertical}, {f})"), b); + } + } + for pan in [-150.5f32, -150.0, 0.0, 150.0, 150.5] { + for tilt in [-70.5f32, -70.0, 0.0, 90.0, 90.5] { + rec( + o, + format!("cmd::gimbal_angles_ok({pan}, {tilt})"), + cmd::gimbal_angles_ok(pan, tilt), + ); + } + } + + // presets, zone views, start pose and return positions + let names = [ + "", + "Desk", + "Bereich1", + "äöü", + "12345678901234567890123456789012", + "123456789012345678901234567890123", + ]; + for slot in [0u8, 1, 2, 3, 255] { + for p in poses() { + rec( + o, + format!("presets::create({slot}, {p:?})"), + presets::create(slot, p, SEQ), + ); + rec( + o, + format!("presets::update({slot}, {p:?})"), + presets::update(slot, p, SEQ), + ); + } + for name in names { + rec( + o, + format!("presets::rename({slot}, {name:?})"), + presets::rename(slot, name, SEQ), + ); + } + rec( + o, + format!("presets::delete({slot})"), + presets::delete(slot, SEQ, SEQ + 1), + ); + rec( + o, + format!("presets::recall({slot})"), + presets::recall(slot, SEQ), + ); + } + for id in [0u32, 1, 7, 255, u32::MAX] { + for p in poses() { + rec( + o, + format!("presets::view_add({id}, {p:?})"), + presets::view_add(id, p, SEQ), + ); + } + for name in names { + rec( + o, + format!("presets::view_rename({id}, {name:?})"), + presets::view_rename(id, name, SEQ), + ); + } + rec( + o, + format!("presets::view_remove({id})"), + presets::view_remove(id, SEQ), + ); + rec( + o, + format!("presets::view_go({id})"), + presets::view_go(id, SEQ), + ); + for command in [0x1504u16, 0x1444] { + let q = presets::view_query(command, id, SEQ); + rec(o, format!("presets::view_query({command:#06x}, {id})"), q); + } + } + for source in [0u32, 1, 2, 3, 4] { + for p in poses() { + let f = presets::startup_pose(source, p, SEQ); + rec(o, format!("presets::startup_pose({source}, {p:?})"), f); + } + } + for p in poses() { + rec( + o, + format!("presets::zone_return_set({p:?})"), + presets::zone_return_set(p, SEQ), + ); + rec( + o, + format!("presets::hand_return_set({p:?})"), + presets::hand_return_set(p, SEQ), + ); + } + for ai in StartupAi::value_variants() { + let f = presets::startup_picture_settings(ai.byte(), SEQ); + rec(o, format!("presets::startup_picture_settings({ai:?})"), f); + } + let block: Vec = (0u8..48).collect(); + for len in [0usize, 39, 40, 48] { + for ai in [0x00u8, 0xfe, 0xff] { + let f = presets::startup_with_ai(&block[..len], ai, SEQ); + rec( + o, + format!("presets::startup_with_ai(0..{len}, {ai:#04x})"), + f, + ); + } + } + out +} + +#[test] +fn builders_emit_the_recorded_bytes() { + let now = record().join("\n") + "\n"; + if std::env::var_os("BYTE_GUARD_BLESS").is_some() { + std::fs::write(FIXTURE, &now).unwrap(); + return; + } + let recorded = std::fs::read_to_string(FIXTURE).expect("tests/fixtures/byte-guard.txt"); + let (now, recorded): (Vec<&str>, Vec<&str>) = + (now.lines().collect(), recorded.lines().collect()); + assert_eq!( + now.len(), + recorded.len(), + "the number of recorded calls changed" + ); + for (n, r) in now.iter().zip(&recorded) { + assert_eq!(n, r, "a builder emits other bytes than before the move"); + } +} diff --git a/tests/cli_surface.rs b/tests/cli_surface.rs new file mode 100644 index 0000000..dcfadcc --- /dev/null +++ b/tests/cli_surface.rs @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +//! CLI surface guard of the groundwork stage (rebuild plan, stage G): the `--help` text of the +//! CLI and of every subcommand, compared with `tests/fixtures/cli-surface.txt`, which was +//! written from the 0.13 code before the workspace move, and `--version`. Only `--help` and +//! `--version` run: nothing opens a device or a socket. +//! +//! `CLI_SURFACE_BLESS=1 cargo test --test cli_surface` writes the file: once when this test is +//! added, and on `main` in a change that alters a help text on purpose. A move never writes it; +//! every other run compares. + +use std::process::Command; + +const FIXTURE: &str = concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/fixtures/cli-surface.txt" +); + +fn run(args: &[&str]) -> String { + let out = Command::new(env!("CARGO_BIN_EXE_obsbot4linux")) + .args(args) + .output() + .expect("obsbot4linux runs"); + assert!(out.status.success(), "obsbot4linux {args:?} failed"); + String::from_utf8(out.stdout).expect("UTF-8 output") +} + +/// The names in the "Commands:" section of a help text: the lines indented by exactly two +/// spaces (a longer indent continues a description). +fn subcommands(help: &str) -> Vec { + help.lines() + .skip_while(|l| *l != "Commands:") + .skip(1) + .take_while(|l| l.starts_with(' ')) + .filter(|l| !l.starts_with(" ")) + .filter_map(|l| l.split_whitespace().next()) + .filter(|name| *name != "help") + .map(String::from) + .collect() +} + +fn walk(path: &mut Vec, out: &mut String) { + let mut args: Vec<&str> = path.iter().map(String::as_str).collect(); + args.push("--help"); + let help = run(&args); + out.push_str(&format!("=== obsbot4linux {}\n{help}", args.join(" "))); + for sub in subcommands(&help) { + path.push(sub); + walk(path, out); + path.pop(); + } +} + +#[test] +fn help_texts_are_unchanged() { + let mut out = String::new(); + walk(&mut Vec::new(), &mut out); + // a hidden subcommand is not listed under "Commands:" + walk(&mut vec!["gimbal".into(), "center".into()], &mut out); + if std::env::var_os("CLI_SURFACE_BLESS").is_some() { + std::fs::write(FIXTURE, &out).unwrap(); + return; + } + let recorded = std::fs::read_to_string(FIXTURE).expect("tests/fixtures/cli-surface.txt"); + assert_eq!(out, recorded, "a help text of the CLI changed"); +} + +#[test] +fn version_is_the_package_version() { + // the workspace move must keep the version the binary reports + assert_eq!( + run(&["--version"]), + format!("obsbot4linux {}\n", env!("CARGO_PKG_VERSION")) + ); +} diff --git a/tests/fixtures/byte-guard.txt b/tests/fixtures/byte-guard.txt new file mode 100644 index 0000000..eb511c1 --- /dev/null +++ b/tests/fixtures/byte-guard.txt @@ -0,0 +1,831 @@ +frame::query_frame(0x04, 0x0104, 0x0000) = aa0100000c0085940a040401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +frame::frame(0x02, 0xa0c2, [1 0 0 0], 0x0000) = aa2500000c00497d0a02c2a00400bffb0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +frame::query_frame_with(0x04, 0x1504, [7 0 0 0], 0x0000) = aa2100000c002e5b0a0404150400bf730700000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +frame::query_frame(0x04, 0x0104, 0x0001) = aa0101000c00d4510a040401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +frame::frame(0x02, 0xa0c2, [1 0 0 0], 0x0001) = aa2501000c0018b80a02c2a00400bffb0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +frame::query_frame_with(0x04, 0x1504, [7 0 0 0], 0x0001) = aa2101000c007f9e0a0404150400bf730700000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +frame::query_frame(0x04, 0x0104, 0x1234) = aa0134120c00101e0a040401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +frame::frame(0x02, 0xa0c2, [1 0 0 0], 0x1234) = aa2534120c00dcf70a02c2a00400bffb0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +frame::query_frame_with(0x04, 0x1504, [7 0 0 0], 0x1234) = aa2134120c00bbd10a0404150400bf730700000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +frame::query_frame(0x04, 0x0104, 0xffff) = aa01ffff0c00f5930a040401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +frame::frame(0x02, 0xa0c2, [1 0 0 0], 0xffff) = aa25ffff0c00397a0a02c2a00400bffb0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +frame::query_frame_with(0x04, 0x1504, [7 0 0 0], 0xffff) = aa21ffff0c005e5c0a0404150400bf730700000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +frame::frame(0x04, 0x3e44, [0xa5; MAX_DATA]) = aa2534120c00df3e0a04443e2c000901a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5 +frame::crc16_usb("123456789") = 0xb4c8 +cmd::hdr(false) = 010100 +cmd::hdr(true) = 010101 +cmd::led_blink(false) = 180100 +cmd::led_blink(true) = 180101 +cmd::mirror(false) = 140100 +cmd::mirror(true) = 140101 +cmd::mic_in_sleep(false) = 130100 +cmd::mic_in_sleep(true) = 130101 +cmd::auto_gain(false) = 170100 +cmd::auto_gain(true) = 170101 +cmd::microphone(false) = 1c0100 +cmd::microphone(true) = 1c0101 +cmd::sleep_background_mirror(false) = 0e020200 +cmd::sleep_background_mirror(true) = 0e020201 +cmd::ai_mode(Static) = 16020000 +cmd::default_work_mode(Static) = 190100 +cmd::ai_mode(Normal) = 16020200 +cmd::default_work_mode(Normal) = 190120 +cmd::ai_mode(UpperBody) = 16020201 +cmd::default_work_mode(UpperBody) = 190121 +cmd::ai_mode(CloseUp) = 16020202 +cmd::default_work_mode(CloseUp) = 190122 +cmd::ai_mode(Headless) = 16020203 +cmd::default_work_mode(Headless) = 190123 +cmd::ai_mode(LowerBody) = 16020204 +cmd::default_work_mode(LowerBody) = 190124 +cmd::ai_mode(Hand) = 16020300 +cmd::default_work_mode(Hand) = 190130 +cmd::ai_mode(Group) = 16020100 +cmd::default_work_mode(Group) = 190110 +cmd::auto_sleep(-32768) = 0b020080 +cmd::auto_sleep(-600) = 0b02a8fd +cmd::auto_sleep(-1) = 0b02ffff +cmd::auto_sleep(0) = 0b020000 +cmd::auto_sleep(1) = 0b020100 +cmd::auto_sleep(600) = 0b025802 +cmd::auto_sleep(32767) = 0b02ff7f +cmd::auto_sleep_off(0) = 0b02a8fd +cmd::auto_sleep_off(1) = 0b02ffff +cmd::auto_sleep_off(600) = 0b02a8fd +cmd::auto_sleep_off(32767) = 0b020180 +cmd::auto_sleep_off(32768) = error: invalid value: auto-sleep time is at most 32767 s +cmd::auto_sleep_off(65535) = error: invalid value: auto-sleep time is at most 32767 s +cmd::sleep_background(Black) = 0e020000 +cmd::sleep_background(Image) = 0e020001 +cmd::led(0) = 1a0100 +cmd::led(1) = 1a0101 +cmd::led(2) = 1a0102 +cmd::led(3) = 1a0103 +cmd::led(4) = error: invalid value: LED brightness is 0-3 +cmd::wake_zoom(0) = 15026400 +cmd::wake_zoom(1) = 15026401 +cmd::wake_zoom(50) = 15026432 +cmd::wake_zoom(100) = 15026464 +cmd::wake_zoom(101) = error: invalid value: wake-up zoom is 0-100 percent +cmd::wake_zoom(255) = error: invalid value: wake-up zoom is 0-100 percent +cmd::wake_zoom_percent(0.99) = error: invalid value: wake-up zoom factor is 1.0-4.0 +cmd::wake_zoom_percent(1) = 0 +cmd::wake_zoom_percent(1.5) = 17 +cmd::wake_zoom_percent(2.5) = 50 +cmd::wake_zoom_percent(4) = 100 +cmd::wake_zoom_percent(4.01) = error: invalid value: wake-up zoom factor is 1.0-4.0 +cmd::wake_zoom_percent(NaN) = error: invalid value: wake-up zoom factor is 1.0-4.0 +cmd::field_of_view(Wide) = 040100 +cmd::field_of_view(Medium) = 040101 +cmd::field_of_view(Narrow) = 040102 +cmd::noise_reduction(Off) = 0a0100 +cmd::noise_reduction(Weak) = 0a0101 +cmd::noise_reduction(Medium) = 0a0102 +cmd::noise_reduction(Strong) = 0a0103 +cmd::pickup_distance(Near) = 1b0100 +cmd::pickup_distance(Standard) = 1b0101 +cmd::pickup_distance(Far) = 1b0102 +cmd::exposure_mode_select(Global) = 030100 +cmd::exposure_mode_select(Face) = 030101 +cmd::startup_go() = 000101 +cmd::invert(false) = aa2534120c004f3d0a04843b0100e63f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::invert(true) = aa2534120c004f3d0a04843b010027ff0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zone_tracking(false) = aa2534120c003ee80a04c4080100e63f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zone_tracking(true) = aa2534120c003ee80a04c408010027ff0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::hand_pan(false) = aa2534120c001f370a0444230100e63f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::hand_pan(true) = aa2534120c001f370a044423010027ff0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::hand_tilt(false) = aa2534120c007ef70a04c4230100e63f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::hand_tilt(true) = aa2534120c007ef70a04c423010027ff0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::hand_gimbal(false) = aa2534120c00bef40a04c4260100e63f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::hand_gimbal(true) = aa2534120c00bef40a04c426010027ff0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::autofocus_mode(false) = aa2534120c000c990a0202360400be070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::autofocus_mode(true) = aa2534120c000c990a0202360400bffb0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zone_follow(false) = aa2534120c00ef1a0a04044e0100e63f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zone_follow(true) = aa2534120c00ef1a0a04044e010027ff0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::sleep(false) = aa2534120c00dcf70a02c2a00400be070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::sleep(true) = aa2534120c00dcf70a02c2a00400bffb0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::auto_zoom(false) = aa2534120c00dee90a04440a0100e63f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::auto_zoom(true) = aa2534120c00dee90a04440a010027ff0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zone_return_go() = aa2534120c001f1a0a04444f0000ffdb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::hand_return_go() = aa2534120c00cf350a0484250000ffdb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::hand_return_reset() = aa2534120c009f350a0444250000ffdb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::factory_reset() = aa2534120c008d310a0202a8010027ff0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_stop() = aa2534120c002ec40a0404670000ffdb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zoom_stop() = aa2534120c003ec00a04c4680000ffdb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::tracking_speed(Standard) = aa2534120c003f2b0a04c40c0100e63f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::tracking_speed(Headroom) = aa2534120c003f2b0a04c40c010027ff0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::tracking_speed(Sport) = aa2534120c003f2b0a04c40c010067fe0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::speed_mode(Lazy) = aa2534120c009ee80a0444090100e63f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::speed_mode(Slow) = aa2534120c009ee80a044409010027ff0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::speed_mode(Normal) = aa2534120c009ee80a044409010067fe0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::speed_mode(Fast) = aa2534120c009ee80a0444090100a63e0300000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::speed_mode(VeryFast) = aa2534120c009ee80a0444090100e7fc0400000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::speed_mode(Auto) = aa2534120c009ee80a0444090100263c0500000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::framing_size(LongShot) = aa2534120c00bf290a04c40a0100e63f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::framing_size(MediumLongShot) = aa2534120c00bf290a04c40a010027ff0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::framing_size(MediumShot) = aa2534120c00bf290a04c40a010067fe0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::framing_size(MediumCloseUp) = aa2534120c00bf290a04c40a0100a63e0300000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::framing_size(CloseUp) = aa2534120c00bf290a04c40a0100e7fc0400000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::framing_size(BigCloseUp) = aa2534120c00bf290a04c40a0100263c0500000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::auto_zoom_speed(Slow) = aa2534120c001f290a04440b0100e63f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::auto_zoom_speed(Medium) = aa2534120c001f290a04440b010027ff0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::auto_zoom_speed(Fast) = aa2534120c001f290a04440b010067fe0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::part_mode(Normal) = aa2534120c00cf210a0484150100e63f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::part_mode(HeadRemoval) = aa2534120c00cf210a048415010027ff0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::part_mode(LowerBody) = aa2534120c00cf210a048415010067fe0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::hand_side(Right) = aa2534120c00def70a0444220100e63f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::hand_side(Left) = aa2534120c00def70a044422010027ff0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::autofocus_mode_select(Continuous) = aa2534120c00bc980a0242340400bffb0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::autofocus_mode_select(Single) = aa2534120c00bc980a0242340400bfbf0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::autofocus_mode_select(Manual) = aa2534120c00bc980a0242340400be430300000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gesture(TargetLock, false) = aa2534120c003f3a0a04c4300100e63f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gesture(TargetLock, true) = aa2534120c003f3a0a04c430010027ff0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gesture(Zoom, false) = aa2534120c009f3a0a0444310100e63f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gesture(Zoom, true) = aa2534120c009f3a0a044431010027ff0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gesture(DynamicZoom, false) = aa2534120c001efb0a0444330100e63f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gesture(DynamicZoom, true) = aa2534120c001efb0a044433010027ff0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gesture(DirectionChange, false) = aa2534120c007f3b0a04c4330100e63f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gesture(DirectionChange, true) = aa2534120c007f3b0a04c433010027ff0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gesture_zoom_ratio(0.99) = error: invalid value: gesture zoom ratio is 1.0-4.0 +cmd::gesture_zoom_ratio(1) = aa2534120c00df3b0a04443204009fd70000803f00000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gesture_zoom_ratio(2.5) = aa2534120c00df3b0a0444320400a6370000204000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gesture_zoom_ratio(4) = aa2534120c00df3b0a0444320400de370000804000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gesture_zoom_ratio(4.01) = error: invalid value: gesture zoom ratio is 1.0-4.0 +cmd::gesture_zoom_ratio(NaN) = error: invalid value: gesture zoom ratio is 1.0-4.0 +cmd::hand_return_after(-1) = error: invalid value: hand return time is 0 seconds or more +cmd::hand_return_after(-0) = aa2534120c005ef50a0444240400bfa70000008000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::hand_return_after(0) = aa2534120c005ef50a0444240400be070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::hand_return_after(2.5) = aa2534120c005ef50a0444240400a6370000204000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::hand_return_after(600) = aa2534120c005ef50a0444240400b0540000164400000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::hand_return_after(inf) = error: invalid value: hand return time is 0 seconds or more +cmd::hand_return_after(NaN) = error: invalid value: hand return time is 0 seconds or more +cmd::audio_volume(0) = aa2534120c004cef0a0202810200fe060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::audio_volume(1) = aa2534120c004cef0a0202810200ff960100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::audio_volume(50) = aa2534120c004cef0a0202810200eb663200000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::audio_volume(100) = aa2534120c004cef0a0202810200d4c66400000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::audio_volume(101) = error: invalid value: microphone volume is 0-100 +cmd::audio_volume(255) = error: invalid value: microphone volume is 0-100 +cmd::focus_point(-0.01, -0.01) = error: invalid value: focus point x and y are 0.0-1.0 +cmd::view_center(-0.01, -0.01) = error: invalid value: centre point x and y are 0.0-1.0 +cmd::focus_point(-0.01, 0) = error: invalid value: focus point x and y are 0.0-1.0 +cmd::view_center(-0.01, 0) = error: invalid value: centre point x and y are 0.0-1.0 +cmd::focus_point(-0.01, 0.75) = error: invalid value: focus point x and y are 0.0-1.0 +cmd::view_center(-0.01, 0.75) = error: invalid value: centre point x and y are 0.0-1.0 +cmd::focus_point(-0.01, 1) = error: invalid value: focus point x and y are 0.0-1.0 +cmd::view_center(-0.01, 1) = error: invalid value: centre point x and y are 0.0-1.0 +cmd::focus_point(-0.01, 1.01) = error: invalid value: focus point x and y are 0.0-1.0 +cmd::view_center(-0.01, 1.01) = error: invalid value: centre point x and y are 0.0-1.0 +cmd::focus_point(0, -0.01) = error: invalid value: focus point x and y are 0.0-1.0 +cmd::view_center(0, -0.01) = error: invalid value: centre point x and y are 0.0-1.0 +cmd::focus_point(0, 0) = aa2534120c002d580a0282351400df600000000000000000010000006400000064000000000000000000000000000000000000000000000000000000 +cmd::view_center(0, 0) = aa2534120c009eed0a04440508007a220000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::focus_point(0, 0.75) = aa2534120c002d580a0282351400cb1f000000000000403f010000006400000064000000000000000000000000000000000000000000000000000000 +cmd::view_center(0, 0.75) = aa2534120c009eed0a04440508000bf2000000000000403f000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::focus_point(0, 1) = aa2534120c002d580a02823514000bdf000000000000803f010000006400000064000000000000000000000000000000000000000000000000000000 +cmd::view_center(0, 1) = aa2534120c009eed0a04440508005bf2000000000000803f000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::focus_point(0, 1.01) = error: invalid value: focus point x and y are 0.0-1.0 +cmd::view_center(0, 1.01) = error: invalid value: centre point x and y are 0.0-1.0 +cmd::focus_point(0.25, -0.01) = error: invalid value: focus point x and y are 0.0-1.0 +cmd::view_center(0.25, -0.01) = error: invalid value: centre point x and y are 0.0-1.0 +cmd::focus_point(0.25, 0) = aa2534120c002d580a028235140086e40000803e00000000010000006400000064000000000000000000000000000000000000000000000000000000 +cmd::view_center(0.25, 0) = aa2534120c009eed0a04440508004c270000803e00000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::focus_point(0.25, 0.75) = aa2534120c002d580a0282351400929b0000803e0000403f010000006400000064000000000000000000000000000000000000000000000000000000 +cmd::view_center(0.25, 0.75) = aa2534120c009eed0a04440508003df70000803e0000403f000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::focus_point(0.25, 1) = aa2534120c002d580a0282351400525b0000803e0000803f010000006400000064000000000000000000000000000000000000000000000000000000 +cmd::view_center(0.25, 1) = aa2534120c009eed0a04440508006df70000803e0000803f000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::focus_point(0.25, 1.01) = error: invalid value: focus point x and y are 0.0-1.0 +cmd::view_center(0.25, 1.01) = error: invalid value: centre point x and y are 0.0-1.0 +cmd::focus_point(1, -0.01) = error: invalid value: focus point x and y are 0.0-1.0 +cmd::view_center(1, -0.01) = error: invalid value: centre point x and y are 0.0-1.0 +cmd::focus_point(1, 0) = aa2534120c002d580a0282351400d7740000803f00000000010000006400000064000000000000000000000000000000000000000000000000000000 +cmd::view_center(1, 0) = aa2534120c009eed0a044405080071e70000803f00000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::focus_point(1, 0.75) = aa2534120c002d580a0282351400c30b0000803f0000403f010000006400000064000000000000000000000000000000000000000000000000000000 +cmd::view_center(1, 0.75) = aa2534120c009eed0a044405080000370000803f0000403f000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::focus_point(1, 1) = aa2534120c002d580a028235140003cb0000803f0000803f010000006400000064000000000000000000000000000000000000000000000000000000 +cmd::view_center(1, 1) = aa2534120c009eed0a044405080050370000803f0000803f000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::focus_point(1, 1.01) = error: invalid value: focus point x and y are 0.0-1.0 +cmd::view_center(1, 1.01) = error: invalid value: centre point x and y are 0.0-1.0 +cmd::focus_point(1.01, -0.01) = error: invalid value: focus point x and y are 0.0-1.0 +cmd::view_center(1.01, -0.01) = error: invalid value: centre point x and y are 0.0-1.0 +cmd::focus_point(1.01, 0) = error: invalid value: focus point x and y are 0.0-1.0 +cmd::view_center(1.01, 0) = error: invalid value: centre point x and y are 0.0-1.0 +cmd::focus_point(1.01, 0.75) = error: invalid value: focus point x and y are 0.0-1.0 +cmd::view_center(1.01, 0.75) = error: invalid value: centre point x and y are 0.0-1.0 +cmd::focus_point(1.01, 1) = error: invalid value: focus point x and y are 0.0-1.0 +cmd::view_center(1.01, 1) = error: invalid value: centre point x and y are 0.0-1.0 +cmd::focus_point(1.01, 1.01) = error: invalid value: focus point x and y are 0.0-1.0 +cmd::view_center(1.01, 1.01) = error: invalid value: centre point x and y are 0.0-1.0 +cmd::zone_area((-45.0, 45.0), (-30.0, 10.0)) = aa2534120c006edb0a04044c04002956000034c200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2535120c005ede0a04844c040028f60000344200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2536120c000ed10a04044d04003b970000f0c100000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2537120c003ed40a04844d040067f70000204100000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::hand_area((-45.0, 45.0), (-30.0, 10.0)) = aa2534120c005f360a04442004002956000034c200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2535120c006f330a04c420040028f60000344200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2536120c003f3c0a04442104003b970000f0c100000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2537120c000f390a04c421040067f70000204100000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zone_area((-45.0, 45.0), (-45.0, 45.0)) = aa2534120c006edb0a04044c04002956000034c200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2535120c005ede0a04844c040028f60000344200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2536120c000ed10a04044d04002956000034c200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2537120c003ed40a04844d040028f60000344200000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::hand_area((-45.0, 45.0), (-45.0, 45.0)) = aa2534120c005f360a04442004002956000034c200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2535120c006f330a04c420040028f60000344200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2536120c003f3c0a04442104002956000034c200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2537120c000f390a04c421040028f60000344200000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zone_area((-150.0, 150.0), (-30.0, 10.0)) = aa2534120c006edb0a04044c0400f036000016c300000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2535120c005ede0a04844c0400f1960000164300000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2536120c000ed10a04044d04003b970000f0c100000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2537120c003ed40a04844d040067f70000204100000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::hand_area((-150.0, 150.0), (-30.0, 10.0)) = aa2534120c005f360a0444200400f036000016c300000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2535120c006f330a04c4200400f1960000164300000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2536120c003f3c0a04442104003b970000f0c100000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2537120c000f390a04c421040067f70000204100000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zone_area((-45.0, 45.0), (-150.0, 150.0)) = error: invalid value: zone area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::hand_area((-45.0, 45.0), (-150.0, 150.0)) = error: invalid value: hand area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::zone_area((-150.5, 0.0), (-30.0, 10.0)) = error: invalid value: zone area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::hand_area((-150.5, 0.0), (-30.0, 10.0)) = error: invalid value: hand area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::zone_area((-45.0, 45.0), (-150.5, 0.0)) = error: invalid value: zone area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::hand_area((-45.0, 45.0), (-150.5, 0.0)) = error: invalid value: hand area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::zone_area((0.0, 150.5), (-30.0, 10.0)) = error: invalid value: zone area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::hand_area((0.0, 150.5), (-30.0, 10.0)) = error: invalid value: hand area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::zone_area((-45.0, 45.0), (0.0, 150.5)) = error: invalid value: zone area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::hand_area((-45.0, 45.0), (0.0, 150.5)) = error: invalid value: hand area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::zone_area((-90.0, 70.0), (-30.0, 10.0)) = aa2534120c006edb0a04044c040048960000b4c200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2535120c005ede0a04844c04005af600008c4200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2536120c000ed10a04044d04003b970000f0c100000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2537120c003ed40a04844d040067f70000204100000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::hand_area((-90.0, 70.0), (-30.0, 10.0)) = aa2534120c005f360a044420040048960000b4c200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2535120c006f330a04c42004005af600008c4200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2536120c003f3c0a04442104003b970000f0c100000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2537120c000f390a04c421040067f70000204100000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zone_area((-45.0, 45.0), (-90.0, 70.0)) = aa2534120c006edb0a04044c04002956000034c200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2535120c005ede0a04844c040028f60000344200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2536120c000ed10a04044d040048960000b4c200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2537120c003ed40a04844d04005af600008c4200000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::hand_area((-45.0, 45.0), (-90.0, 70.0)) = aa2534120c005f360a04442004002956000034c200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2535120c006f330a04c420040028f60000344200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2536120c003f3c0a044421040048960000b4c200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2537120c000f390a04c42104005af600008c4200000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zone_area((-90.5, 0.0), (-30.0, 10.0)) = aa2534120c006edb0a04044c040049060000b5c200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2535120c005ede0a04844c0400be070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2536120c000ed10a04044d04003b970000f0c100000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2537120c003ed40a04844d040067f70000204100000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::hand_area((-90.5, 0.0), (-30.0, 10.0)) = aa2534120c005f360a044420040049060000b5c200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2535120c006f330a04c4200400be070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2536120c003f3c0a04442104003b970000f0c100000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2537120c000f390a04c421040067f70000204100000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zone_area((-45.0, 45.0), (-90.5, 0.0)) = error: invalid value: zone area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::hand_area((-45.0, 45.0), (-90.5, 0.0)) = error: invalid value: hand area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::zone_area((0.0, 70.5), (-30.0, 10.0)) = aa2534120c006edb0a04044c0400be070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2535120c005ede0a04844c04005b6600008d4200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2536120c000ed10a04044d04003b970000f0c100000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2537120c003ed40a04844d040067f70000204100000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::hand_area((0.0, 70.5), (-30.0, 10.0)) = aa2534120c005f360a0444200400be070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2535120c006f330a04c42004005b6600008d4200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2536120c003f3c0a04442104003b970000f0c100000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2537120c000f390a04c421040067f70000204100000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zone_area((-45.0, 45.0), (0.0, 70.5)) = error: invalid value: zone area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::hand_area((-45.0, 45.0), (0.0, 70.5)) = error: invalid value: hand area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::zone_area((-30.0, 10.0), (-30.0, 10.0)) = aa2534120c006edb0a04044c04003b970000f0c100000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2535120c005ede0a04844c040067f70000204100000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2536120c000ed10a04044d04003b970000f0c100000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2537120c003ed40a04844d040067f70000204100000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::hand_area((-30.0, 10.0), (-30.0, 10.0)) = aa2534120c005f360a04442004003b970000f0c100000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2535120c006f330a04c420040067f70000204100000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2536120c003f3c0a04442104003b970000f0c100000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2537120c000f390a04c421040067f70000204100000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zone_area((-45.0, 45.0), (-30.0, 10.0)) = aa2534120c006edb0a04044c04002956000034c200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2535120c005ede0a04844c040028f60000344200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2536120c000ed10a04044d04003b970000f0c100000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2537120c003ed40a04844d040067f70000204100000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::hand_area((-45.0, 45.0), (-30.0, 10.0)) = aa2534120c005f360a04442004002956000034c200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2535120c006f330a04c420040028f60000344200000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2536120c003f3c0a04442104003b970000f0c100000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2537120c000f390a04c421040067f70000204100000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zone_area((10.0, 10.0), (-30.0, 10.0)) = error: invalid value: zone area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::hand_area((10.0, 10.0), (-30.0, 10.0)) = error: invalid value: hand area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::zone_area((-45.0, 45.0), (10.0, 10.0)) = error: invalid value: zone area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::hand_area((-45.0, 45.0), (10.0, 10.0)) = error: invalid value: hand area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::zone_area((10.0, -10.0), (-30.0, 10.0)) = error: invalid value: zone area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::hand_area((10.0, -10.0), (-30.0, 10.0)) = error: invalid value: hand area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::zone_area((-45.0, 45.0), (10.0, -10.0)) = error: invalid value: zone area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::hand_area((-45.0, 45.0), (10.0, -10.0)) = error: invalid value: hand area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::zone_area((NaN, 1.0), (-30.0, 10.0)) = error: invalid value: zone area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::hand_area((NaN, 1.0), (-30.0, 10.0)) = error: invalid value: hand area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::zone_area((-45.0, 45.0), (NaN, 1.0)) = error: invalid value: zone area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::hand_area((-45.0, 45.0), (NaN, 1.0)) = error: invalid value: hand area: min must be below max, pan within -150..150, tilt within -70..90 +cmd::hand_area_reset() = aa2534120c00fef50a04c4250000ffdb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2535120c00bf310a0404260000ffdb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2536120c007efe0a0444260000ffdb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2537120c007f3b0a0484260000ffdb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(None, None) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(None, Some(-150.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(None, Some(-70.0)) = aa2534120c005f050a0444640c0040ea00007ac400008cc200007ac40000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(None, Some(0.0)) = aa2534120c005f050a0444640c00263700007ac40000000000007ac40000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(None, Some(12.5)) = aa2534120c005f050a0444640c0015b000007ac40000484100007ac40000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(None, Some(90.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(None, Some(150.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(None, Some(150.5)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(None, Some(-90.5)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(-150.0), None) = aa2534120c005f050a0444640c00b0be00007ac400007ac4000016c30000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(-150.0), Some(-150.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(-150.0), Some(-70.0)) = aa2534120c005f050a0444640c002c2800007ac400008cc2000016c30000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(-150.0), Some(0.0)) = aa2534120c005f050a0444640c004af500007ac400000000000016c30000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(-150.0), Some(12.5)) = aa2534120c005f050a0444640c00797200007ac400004841000016c30000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(-150.0), Some(90.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(-150.0), Some(150.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(-150.0), Some(150.5)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(-150.0), Some(-90.5)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(-70.0), None) = aa2534120c005f050a0444640c001bde00007ac400007ac400008cc20000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(-70.0), Some(-150.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(-70.0), Some(-70.0)) = aa2534120c005f050a0444640c00874800007ac400008cc200008cc20000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(-70.0), Some(0.0)) = aa2534120c005f050a0444640c00e19500007ac40000000000008cc20000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(-70.0), Some(12.5)) = aa2534120c005f050a0444640c00d21200007ac40000484100008cc20000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(-70.0), Some(90.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(-70.0), Some(150.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(-70.0), Some(150.5)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(-70.0), Some(-90.5)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(0.0), None) = aa2534120c005f050a0444640c00fe8f00007ac400007ac4000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(0.0), Some(-150.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(0.0), Some(-70.0)) = aa2534120c005f050a0444640c00621900007ac400008cc2000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(0.0), Some(0.0)) = aa2534120c005f050a0444640c0004c400007ac400000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(0.0), Some(12.5)) = aa2534120c005f050a0444640c00374300007ac400004841000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(0.0), Some(90.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(0.0), Some(150.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(0.0), Some(150.5)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(0.0), Some(-90.5)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(12.5), None) = aa2534120c005f050a0444640c0008bf00007ac400007ac4000048410000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(12.5), Some(-150.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(12.5), Some(-70.0)) = aa2534120c005f050a0444640c00942900007ac400008cc2000048410000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(12.5), Some(0.0)) = aa2534120c005f050a0444640c00f2f400007ac400000000000048410000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(12.5), Some(12.5)) = aa2534120c005f050a0444640c00c17300007ac400004841000048410000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(12.5), Some(90.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(12.5), Some(150.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(12.5), Some(150.5)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(12.5), Some(-90.5)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(90.0), None) = aa2534120c005f050a0444640c0009be00007ac400007ac40000b4420000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(90.0), Some(-150.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(90.0), Some(-70.0)) = aa2534120c005f050a0444640c00952800007ac400008cc20000b4420000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(90.0), Some(0.0)) = aa2534120c005f050a0444640c00f3f500007ac4000000000000b4420000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(90.0), Some(12.5)) = aa2534120c005f050a0444640c00c07200007ac4000048410000b4420000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(90.0), Some(90.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(90.0), Some(150.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(90.0), Some(150.5)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(90.0), Some(-90.5)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(150.0), None) = aa2534120c005f050a0444640c00b11e00007ac400007ac4000016430000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(150.0), Some(-150.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(150.0), Some(-70.0)) = aa2534120c005f050a0444640c002d8800007ac400008cc2000016430000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(150.0), Some(0.0)) = aa2534120c005f050a0444640c004b5500007ac400000000000016430000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(150.0), Some(12.5)) = aa2534120c005f050a0444640c0078d200007ac400004841000016430000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(150.0), Some(90.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(150.0), Some(150.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(150.0), Some(150.5)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(150.0), Some(-90.5)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(150.5), None) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(150.5), Some(-150.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(150.5), Some(-70.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(150.5), Some(0.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(150.5), Some(12.5)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(150.5), Some(90.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(150.5), Some(150.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(150.5), Some(150.5)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(150.5), Some(-90.5)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(-90.5), None) = aa2534120c005f050a0444640c00098e00007ac400007ac40000b5c20000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(-90.5), Some(-150.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(-90.5), Some(-70.0)) = aa2534120c005f050a0444640c00951800007ac400008cc20000b5c20000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(-90.5), Some(0.0)) = aa2534120c005f050a0444640c00f3c500007ac4000000000000b5c20000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(-90.5), Some(12.5)) = aa2534120c005f050a0444640c00c04200007ac4000048410000b5c20000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_pose(Some(-90.5), Some(90.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(-90.5), Some(150.0)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(-90.5), Some(150.5)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose(Some(-90.5), Some(-90.5)) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose_speed(None, None, 0.99) = error: invalid value: gimbal speed is 1-180 degrees per second +cmd::gimbal_pose_speed(None, None, 1) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose_speed(None, None, 45) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose_speed(None, None, 180) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose_speed(None, None, 180.5) = error: invalid value: gimbal speed is 1-180 degrees per second +cmd::gimbal_pose_speed(None, None, NaN) = error: invalid value: gimbal speed is 1-180 degrees per second +cmd::gimbal_pose_speed(Some(12.5), None, 0.99) = error: invalid value: gimbal speed is 1-180 degrees per second +cmd::gimbal_pose_speed(Some(12.5), None, 1) = aa2534120c009ec50a0444651800bcaf0000803f0000803f0000803f00007ac400007ac4000048410000000000000000000000000000000000000000 +cmd::gimbal_pose_speed(Some(12.5), None, 45) = aa2534120c009ec50a04446518002b5e00003442000034420000344200007ac400007ac4000048410000000000000000000000000000000000000000 +cmd::gimbal_pose_speed(Some(12.5), None, 180) = aa2534120c009ec50a0444651800689e00003443000034430000344300007ac400007ac4000048410000000000000000000000000000000000000000 +cmd::gimbal_pose_speed(Some(12.5), None, 180.5) = error: invalid value: gimbal speed is 1-180 degrees per second +cmd::gimbal_pose_speed(Some(12.5), None, NaN) = error: invalid value: gimbal speed is 1-180 degrees per second +cmd::gimbal_pose_speed(None, Some(-5.0), 0.99) = error: invalid value: gimbal speed is 1-180 degrees per second +cmd::gimbal_pose_speed(None, Some(-5.0), 1) = aa2534120c009ec50a04446518008a960000803f0000803f0000803f00007ac40000a0c000007ac40000000000000000000000000000000000000000 +cmd::gimbal_pose_speed(None, Some(-5.0), 45) = aa2534120c009ec50a04446518001d6700003442000034420000344200007ac40000a0c000007ac40000000000000000000000000000000000000000 +cmd::gimbal_pose_speed(None, Some(-5.0), 180) = aa2534120c009ec50a04446518005ea700003443000034430000344300007ac40000a0c000007ac40000000000000000000000000000000000000000 +cmd::gimbal_pose_speed(None, Some(-5.0), 180.5) = error: invalid value: gimbal speed is 1-180 degrees per second +cmd::gimbal_pose_speed(None, Some(-5.0), NaN) = error: invalid value: gimbal speed is 1-180 degrees per second +cmd::gimbal_pose_speed(Some(12.5), Some(-5.0), 0.99) = error: invalid value: gimbal speed is 1-180 degrees per second +cmd::gimbal_pose_speed(Some(12.5), Some(-5.0), 1) = aa2534120c009ec50a04446518005e550000803f0000803f0000803f00007ac40000a0c0000048410000000000000000000000000000000000000000 +cmd::gimbal_pose_speed(Some(12.5), Some(-5.0), 45) = aa2534120c009ec50a0444651800c9a400003442000034420000344200007ac40000a0c0000048410000000000000000000000000000000000000000 +cmd::gimbal_pose_speed(Some(12.5), Some(-5.0), 180) = aa2534120c009ec50a04446518008a6400003443000034430000344300007ac40000a0c0000048410000000000000000000000000000000000000000 +cmd::gimbal_pose_speed(Some(12.5), Some(-5.0), 180.5) = error: invalid value: gimbal speed is 1-180 degrees per second +cmd::gimbal_pose_speed(Some(12.5), Some(-5.0), NaN) = error: invalid value: gimbal speed is 1-180 degrees per second +cmd::gimbal_pose_speed(Some(150.5), Some(0.0), 0.99) = error: invalid value: gimbal speed is 1-180 degrees per second +cmd::gimbal_pose_speed(Some(150.5), Some(0.0), 1) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose_speed(Some(150.5), Some(0.0), 45) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose_speed(Some(150.5), Some(0.0), 180) = error: invalid value: gimbal pose needs pan -150..150 and/or tilt -70..90 +cmd::gimbal_pose_speed(Some(150.5), Some(0.0), 180.5) = error: invalid value: gimbal speed is 1-180 degrees per second +cmd::gimbal_pose_speed(Some(150.5), Some(0.0), NaN) = error: invalid value: gimbal speed is 1-180 degrees per second +cmd::gimbal_rate(-178.5, -178.5, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(-178.5, -178, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(-178.5, -0, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(-178.5, 0, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(-178.5, 5, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(-178.5, 178, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(-178.5, 178.5, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(-178, -178.5, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(-178, -178, false, false) = aa2534120c000f050a0484640c0051460000000000003243000032430000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(-178, -0, false, false) = aa2534120c000f050a0484640c00109b0000000000000000000032430000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(-178, 0, false, false) = aa2534120c000f050a0484640c00109b0000000000000000000032430000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(-178, 5, false, false) = aa2534120c000f050a0484640c00082a000000000000a0c0000032430000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(-178, 178, false, false) = aa2534120c000f050a0484640c00509800000000000032c3000032430000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(-178, 178.5, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(-0, -178.5, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(-0, -178, false, false) = aa2534120c000f050a0484640c0005d70000000000003243000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(-0, -0, false, false) = aa2534120c000f050a0484640c00440a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(-0, 0, false, false) = aa2534120c000f050a0484640c00440a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(-0, 5, false, false) = aa2534120c000f050a0484640c005cbb000000000000a0c0000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(-0, 178, false, false) = aa2534120c000f050a0484640c00040900000000000032c3000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(-0, 178.5, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(0, -178.5, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(0, -178, false, false) = aa2534120c000f050a0484640c0005d70000000000003243000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(0, -0, false, false) = aa2534120c000f050a0484640c00440a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(0, 0, false, false) = aa2534120c000f050a0484640c00440a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(0, 5, false, false) = aa2534120c000f050a0484640c005cbb000000000000a0c0000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(0, 178, false, false) = aa2534120c000f050a0484640c00040900000000000032c3000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(0, 178.5, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(12.5, -178.5, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(12.5, -178, false, false) = aa2534120c000f050a0484640c00f2470000000000003243000048c10000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(12.5, -0, false, false) = aa2534120c000f050a0484640c00b39a0000000000000000000048c10000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(12.5, 0, false, false) = aa2534120c000f050a0484640c00b39a0000000000000000000048c10000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(12.5, 5, false, false) = aa2534120c000f050a0484640c00ab2b000000000000a0c0000048c10000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(12.5, 178, false, false) = aa2534120c000f050a0484640c00f39900000000000032c3000048c10000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(12.5, 178.5, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(178, -178.5, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(178, -178, false, false) = aa2534120c000f050a0484640c0050e60000000000003243000032c30000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(178, -0, false, false) = aa2534120c000f050a0484640c00113b0000000000000000000032c30000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(178, 0, false, false) = aa2534120c000f050a0484640c00113b0000000000000000000032c30000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(178, 5, false, false) = aa2534120c000f050a0484640c00098a000000000000a0c0000032c30000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(178, 178, false, false) = aa2534120c000f050a0484640c00513800000000000032c3000032c30000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(178, 178.5, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(178.5, -178.5, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(178.5, -178, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(178.5, -0, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(178.5, 0, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(178.5, 5, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(178.5, 178, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(178.5, 178.5, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(NaN, -178.5, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(NaN, -178, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(NaN, -0, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(NaN, 0, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(NaN, 5, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(NaN, 178, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(NaN, 178.5, false, false) = error: invalid value: gimbal rate is -178 to 178 degrees per second +cmd::gimbal_rate(12.5, 5, true, false) = aa2534120c000f050a0484640c00aa8b000000000000a0c0000048410000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(12.5, 5, false, true) = aa2534120c000f050a0484640c00aa8b000000000000a0c0000048410000000000000000000000000000000000000000000000000000000000000000 +cmd::gimbal_rate(12.5, 5, true, true) = aa2534120c000f050a0484640c00ab2b000000000000a0c0000048c10000000000000000000000000000000000000000000000000000000000000000 +cmd::zoom_to(0, 0.99) = error: invalid value: zoom factor is 1.0-4.0 +cmd::zoom_to(0, 1) = error: invalid value: zoom speed must be positive +cmd::zoom_to(0, 2.5) = error: invalid value: zoom speed must be positive +cmd::zoom_to(0, 4) = error: invalid value: zoom speed must be positive +cmd::zoom_to(0, 4.01) = error: invalid value: zoom factor is 1.0-4.0 +cmd::zoom_to(1, 0.99) = error: invalid value: zoom factor is 1.0-4.0 +cmd::zoom_to(1, 1) = aa2534120c000f000a04846808009a3e010000000000803f000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zoom_to(1, 2.5) = aa2534120c000f000a0484680800a3de0100000000002040000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zoom_to(1, 4) = aa2534120c000f000a0484680800dbde0100000000008040000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zoom_to(1, 4.01) = error: invalid value: zoom factor is 1.0-4.0 +cmd::zoom_to(10, 0.99) = error: invalid value: zoom factor is 1.0-4.0 +cmd::zoom_to(10, 1) = aa2534120c000f000a0484680800db8d0a0000000000803f000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zoom_to(10, 2.5) = aa2534120c000f000a0484680800e26d0a00000000002040000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zoom_to(10, 4) = aa2534120c000f000a04846808009a6d0a00000000008040000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zoom_to(10, 4.01) = error: invalid value: zoom factor is 1.0-4.0 +cmd::zoom_to(30, 0.99) = error: invalid value: zoom factor is 1.0-4.0 +cmd::zoom_to(30, 1) = aa2534120c000f000a0484680800db721e0000000000803f000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zoom_to(30, 2.5) = aa2534120c000f000a0484680800e2921e00000000002040000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zoom_to(30, 4) = aa2534120c000f000a04846808009a921e00000000008040000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zoom_to(30, 4.01) = error: invalid value: zoom factor is 1.0-4.0 +cmd::zoom_to(50, 0.99) = error: invalid value: zoom factor is 1.0-4.0 +cmd::zoom_to(50, 1) = aa2534120c000f000a0484680800d93f320000000000803f000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zoom_to(50, 2.5) = aa2534120c000f000a0484680800e0df3200000000002040000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zoom_to(50, 4) = aa2534120c000f000a048468080098df3200000000008040000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zoom_to(50, 4.01) = error: invalid value: zoom factor is 1.0-4.0 +cmd::zoom_to(2147483647, 0.99) = error: invalid value: zoom factor is 1.0-4.0 +cmd::zoom_to(2147483647, 1) = aa2534120c000f000a04846808001a3cffffff7f0000803f000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zoom_to(2147483647, 2.5) = aa2534120c000f000a048468080023dcffffff7f00002040000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zoom_to(2147483647, 4) = aa2534120c000f000a04846808005bdcffffff7f00008040000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::zoom_to(2147483647, 4.01) = error: invalid value: zoom factor is 1.0-4.0 +cmd::framing_offset(false, -0.17) = error: invalid value: framing offset is -0.16 to 0.16 of the frame +cmd::framing_offset(false, -0.16) = aa2534120c007ee90a04c40b040094a70ad723be00000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::framing_offset(false, -0) = aa2534120c007ee90a04c40b0400bfa70000008000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::framing_offset(false, 0) = aa2534120c007ee90a04c40b0400be070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::framing_offset(false, 0.1) = aa2534120c007ee90a04c40b0400d445cdcccc3d00000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::framing_offset(false, 0.16) = aa2534120c007ee90a04c40b040095070ad7233e00000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::framing_offset(false, 0.17) = error: invalid value: framing offset is -0.16 to 0.16 of the frame +cmd::framing_offset(false, NaN) = error: invalid value: framing offset is -0.16 to 0.16 of the frame +cmd::framing_offset(true, -0.17) = error: invalid value: framing offset is -0.16 to 0.16 of the frame +cmd::framing_offset(true, -0.16) = aa2534120c005eeb0a04440c040094a70ad723be00000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::framing_offset(true, -0) = aa2534120c005eeb0a04440c0400bfa70000008000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::framing_offset(true, 0) = aa2534120c005eeb0a04440c0400be070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::framing_offset(true, 0.1) = aa2534120c005eeb0a04440c0400d445cdcccc3d00000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::framing_offset(true, 0.16) = aa2534120c005eeb0a04440c040095070ad7233e00000000000000000000000000000000000000000000000000000000000000000000000000000000 +cmd::framing_offset(true, 0.17) = error: invalid value: framing offset is -0.16 to 0.16 of the frame +cmd::framing_offset(true, NaN) = error: invalid value: framing offset is -0.16 to 0.16 of the frame +cmd::gimbal_angles_ok(-150.5, -70.5) = false +cmd::gimbal_angles_ok(-150.5, -70) = false +cmd::gimbal_angles_ok(-150.5, 0) = false +cmd::gimbal_angles_ok(-150.5, 90) = false +cmd::gimbal_angles_ok(-150.5, 90.5) = false +cmd::gimbal_angles_ok(-150, -70.5) = false +cmd::gimbal_angles_ok(-150, -70) = true +cmd::gimbal_angles_ok(-150, 0) = true +cmd::gimbal_angles_ok(-150, 90) = true +cmd::gimbal_angles_ok(-150, 90.5) = false +cmd::gimbal_angles_ok(0, -70.5) = false +cmd::gimbal_angles_ok(0, -70) = true +cmd::gimbal_angles_ok(0, 0) = true +cmd::gimbal_angles_ok(0, 90) = true +cmd::gimbal_angles_ok(0, 90.5) = false +cmd::gimbal_angles_ok(150, -70.5) = false +cmd::gimbal_angles_ok(150, -70) = true +cmd::gimbal_angles_ok(150, 0) = true +cmd::gimbal_angles_ok(150, 90) = true +cmd::gimbal_angles_ok(150, 90.5) = false +cmd::gimbal_angles_ok(150.5, -70.5) = false +cmd::gimbal_angles_ok(150.5, -70) = false +cmd::gimbal_angles_ok(150.5, 0) = false +cmd::gimbal_angles_ok(150.5, 90) = false +cmd::gimbal_angles_ok(150.5, 90.5) = false +presets::create(0, Pose { pan: 10.5, tilt: -5.25, zoom: 2.0 }) = aa2534120c009efc0a0444391800f3ad00000000000028410000a840000000000000004000007ac40000000000000000000000000000000000000000 +presets::update(0, Pose { pan: 10.5, tilt: -5.25, zoom: 2.0 }) = aa2534120c00eefe0a04043e1800f3ad00000000000028410000a840000000000000004000007ac40000000000000000000000000000000000000000 +presets::create(0, Pose { pan: 0.0, tilt: 0.0, zoom: 1.0 }) = aa2534120c009efc0a044439180047e3000000000000000000000000000000000000803f00007ac40000000000000000000000000000000000000000 +presets::update(0, Pose { pan: 0.0, tilt: 0.0, zoom: 1.0 }) = aa2534120c00eefe0a04043e180047e3000000000000000000000000000000000000803f00007ac40000000000000000000000000000000000000000 +presets::create(0, Pose { pan: -150.0, tilt: -70.0, zoom: 1.0 }) = aa2534120c009efc0a044439180077ad00000000000016c300008c42000000000000803f00007ac40000000000000000000000000000000000000000 +presets::update(0, Pose { pan: -150.0, tilt: -70.0, zoom: 1.0 }) = aa2534120c00eefe0a04043e180077ad00000000000016c300008c42000000000000803f00007ac40000000000000000000000000000000000000000 +presets::create(0, Pose { pan: 150.0, tilt: 90.0, zoom: 4.0 }) = aa2534120c009efc0a0444391800c5b600000000000016430000b4c2000000000000804000007ac40000000000000000000000000000000000000000 +presets::update(0, Pose { pan: 150.0, tilt: 90.0, zoom: 4.0 }) = aa2534120c00eefe0a04043e1800c5b600000000000016430000b4c2000000000000804000007ac40000000000000000000000000000000000000000 +presets::create(0, Pose { pan: 150.1, tilt: 0.0, zoom: 1.0 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::update(0, Pose { pan: 150.1, tilt: 0.0, zoom: 1.0 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::create(0, Pose { pan: 0.0, tilt: 90.5, zoom: 1.0 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::update(0, Pose { pan: 0.0, tilt: 90.5, zoom: 1.0 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::create(0, Pose { pan: 0.0, tilt: -70.5, zoom: 1.0 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::update(0, Pose { pan: 0.0, tilt: -70.5, zoom: 1.0 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::create(0, Pose { pan: 0.0, tilt: 0.0, zoom: 4.01 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::update(0, Pose { pan: 0.0, tilt: 0.0, zoom: 4.01 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::create(0, Pose { pan: 0.0, tilt: 0.0, zoom: 0.99 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::update(0, Pose { pan: 0.0, tilt: 0.0, zoom: 0.99 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::create(0, Pose { pan: NaN, tilt: 0.0, zoom: 1.0 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::update(0, Pose { pan: NaN, tilt: 0.0, zoom: 1.0 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::rename(0, "") = error: invalid value: name must be 1-32 printable ASCII characters +presets::rename(0, "Desk") = aa2534120c008efd0a04843a08001a12000000004465736b000000000000000000000000000000000000000000000000000000000000000000000000 +presets::rename(0, "Bereich1") = aa2534120c008efd0a04843a0c00b1a90000000042657265696368310000000000000000000000000000000000000000000000000000000000000000 +presets::rename(0, "äöü") = error: invalid value: name must be 1-32 printable ASCII characters +presets::rename(0, "12345678901234567890123456789012") = aa2534120c008efd0a04843a2400c1ee0000000031323334353637383930313233343536373839303132333435363738393031320000000000000000 +presets::rename(0, "123456789012345678901234567890123") = error: invalid value: name must be 1-32 printable ASCII characters +presets::delete(0) = aa2534120c00cefc0a0484390400be070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2535120c009efa0a04843d2c002a5f00000000feffffffffffffff80ffffffffffffff00000000ffffffff00ffffffffffffffff00000000000000 +presets::recall(0) = aa2534120c00ff3c0a04c4391400d6fb000000000000803f0000803f0000803f0000803f000000000000000000000000000000000000000000000000 +presets::create(1, Pose { pan: 10.5, tilt: -5.25, zoom: 2.0 }) = aa2534120c009efc0a0444391800ae3801000000000028410000a840000000000000004000007ac40000000000000000000000000000000000000000 +presets::update(1, Pose { pan: 10.5, tilt: -5.25, zoom: 2.0 }) = aa2534120c00eefe0a04043e1800ae3801000000000028410000a840000000000000004000007ac40000000000000000000000000000000000000000 +presets::create(1, Pose { pan: 0.0, tilt: 0.0, zoom: 1.0 }) = aa2534120c009efc0a04443918001a76010000000000000000000000000000000000803f00007ac40000000000000000000000000000000000000000 +presets::update(1, Pose { pan: 0.0, tilt: 0.0, zoom: 1.0 }) = aa2534120c00eefe0a04043e18001a76010000000000000000000000000000000000803f00007ac40000000000000000000000000000000000000000 +presets::create(1, Pose { pan: -150.0, tilt: -70.0, zoom: 1.0 }) = aa2534120c009efc0a04443918002a3801000000000016c300008c42000000000000803f00007ac40000000000000000000000000000000000000000 +presets::update(1, Pose { pan: -150.0, tilt: -70.0, zoom: 1.0 }) = aa2534120c00eefe0a04043e18002a3801000000000016c300008c42000000000000803f00007ac40000000000000000000000000000000000000000 +presets::create(1, Pose { pan: 150.0, tilt: 90.0, zoom: 4.0 }) = aa2534120c009efc0a0444391800982301000000000016430000b4c2000000000000804000007ac40000000000000000000000000000000000000000 +presets::update(1, Pose { pan: 150.0, tilt: 90.0, zoom: 4.0 }) = aa2534120c00eefe0a04043e1800982301000000000016430000b4c2000000000000804000007ac40000000000000000000000000000000000000000 +presets::create(1, Pose { pan: 150.1, tilt: 0.0, zoom: 1.0 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::update(1, Pose { pan: 150.1, tilt: 0.0, zoom: 1.0 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::create(1, Pose { pan: 0.0, tilt: 90.5, zoom: 1.0 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::update(1, Pose { pan: 0.0, tilt: 90.5, zoom: 1.0 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::create(1, Pose { pan: 0.0, tilt: -70.5, zoom: 1.0 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::update(1, Pose { pan: 0.0, tilt: -70.5, zoom: 1.0 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::create(1, Pose { pan: 0.0, tilt: 0.0, zoom: 4.01 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::update(1, Pose { pan: 0.0, tilt: 0.0, zoom: 4.01 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::create(1, Pose { pan: 0.0, tilt: 0.0, zoom: 0.99 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::update(1, Pose { pan: 0.0, tilt: 0.0, zoom: 0.99 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::create(1, Pose { pan: NaN, tilt: 0.0, zoom: 1.0 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::update(1, Pose { pan: NaN, tilt: 0.0, zoom: 1.0 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::rename(1, "") = error: invalid value: name must be 1-32 printable ASCII characters +presets::rename(1, "Desk") = aa2534120c008efd0a04843a0800dbde010000004465736b000000000000000000000000000000000000000000000000000000000000000000000000 +presets::rename(1, "Bereich1") = aa2534120c008efd0a04843a0c004c6a0100000042657265696368310000000000000000000000000000000000000000000000000000000000000000 +presets::rename(1, "äöü") = error: invalid value: name must be 1-32 printable ASCII characters +presets::rename(1, "12345678901234567890123456789012") = aa2534120c008efd0a04843a240000330100000031323334353637383930313233343536373839303132333435363738393031320000000000000000 +presets::rename(1, "123456789012345678901234567890123") = error: invalid value: name must be 1-32 printable ASCII characters +presets::delete(1) = aa2534120c00cefc0a0484390400bffb0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2535120c009efa0a04843d2c002a5f00000000feffffffffffffff80ffffffffffffff00000000ffffffff00ffffffffffffffff00000000000000 +presets::recall(1) = aa2534120c00ff3c0a04c4391400eb2a010000000000803f0000803f0000803f0000803f000000000000000000000000000000000000000000000000 +presets::create(2, Pose { pan: 10.5, tilt: -5.25, zoom: 2.0 }) = aa2534120c009efc0a04443918004ac702000000000028410000a840000000000000004000007ac40000000000000000000000000000000000000000 +presets::update(2, Pose { pan: 10.5, tilt: -5.25, zoom: 2.0 }) = aa2534120c00eefe0a04043e18004ac702000000000028410000a840000000000000004000007ac40000000000000000000000000000000000000000 +presets::create(2, Pose { pan: 0.0, tilt: 0.0, zoom: 1.0 }) = aa2534120c009efc0a0444391800fe89020000000000000000000000000000000000803f00007ac40000000000000000000000000000000000000000 +presets::update(2, Pose { pan: 0.0, tilt: 0.0, zoom: 1.0 }) = aa2534120c00eefe0a04043e1800fe89020000000000000000000000000000000000803f00007ac40000000000000000000000000000000000000000 +presets::create(2, Pose { pan: -150.0, tilt: -70.0, zoom: 1.0 }) = aa2534120c009efc0a0444391800cec702000000000016c300008c42000000000000803f00007ac40000000000000000000000000000000000000000 +presets::update(2, Pose { pan: -150.0, tilt: -70.0, zoom: 1.0 }) = aa2534120c00eefe0a04043e1800cec702000000000016c300008c42000000000000803f00007ac40000000000000000000000000000000000000000 +presets::create(2, Pose { pan: 150.0, tilt: 90.0, zoom: 4.0 }) = aa2534120c009efc0a04443918007cdc02000000000016430000b4c2000000000000804000007ac40000000000000000000000000000000000000000 +presets::update(2, Pose { pan: 150.0, tilt: 90.0, zoom: 4.0 }) = aa2534120c00eefe0a04043e18007cdc02000000000016430000b4c2000000000000804000007ac40000000000000000000000000000000000000000 +presets::create(2, Pose { pan: 150.1, tilt: 0.0, zoom: 1.0 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::update(2, Pose { pan: 150.1, tilt: 0.0, zoom: 1.0 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::create(2, Pose { pan: 0.0, tilt: 90.5, zoom: 1.0 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::update(2, Pose { pan: 0.0, tilt: 90.5, zoom: 1.0 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::create(2, Pose { pan: 0.0, tilt: -70.5, zoom: 1.0 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::update(2, Pose { pan: 0.0, tilt: -70.5, zoom: 1.0 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::create(2, Pose { pan: 0.0, tilt: 0.0, zoom: 4.01 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::update(2, Pose { pan: 0.0, tilt: 0.0, zoom: 4.01 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::create(2, Pose { pan: 0.0, tilt: 0.0, zoom: 0.99 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::update(2, Pose { pan: 0.0, tilt: 0.0, zoom: 0.99 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::create(2, Pose { pan: NaN, tilt: 0.0, zoom: 1.0 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::update(2, Pose { pan: NaN, tilt: 0.0, zoom: 1.0 }) = error: invalid value: preset pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::rename(2, "") = error: invalid value: name must be 1-32 printable ASCII characters +presets::rename(2, "Desk") = aa2534120c008efd0a04843a08009bcb020000004465736b000000000000000000000000000000000000000000000000000000000000000000000000 +presets::rename(2, "Bereich1") = aa2534120c008efd0a04843a0c00486e0200000042657265696368310000000000000000000000000000000000000000000000000000000000000000 +presets::rename(2, "äöü") = error: invalid value: name must be 1-32 printable ASCII characters +presets::rename(2, "12345678901234567890123456789012") = aa2534120c008efd0a04843a240040150200000031323334353637383930313233343536373839303132333435363738393031320000000000000000 +presets::rename(2, "123456789012345678901234567890123") = error: invalid value: name must be 1-32 printable ASCII characters +presets::delete(2) = aa2534120c00cefc0a0484390400bfbf0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000 aa2535120c009efa0a04843d2c002a5f00000000feffffffffffffff80ffffffffffffff00000000ffffffff00ffffffffffffffff00000000000000 +presets::recall(2) = aa2534120c00ff3c0a04c4391400af19020000000000803f0000803f0000803f0000803f000000000000000000000000000000000000000000000000 +presets::create(3, Pose { pan: 10.5, tilt: -5.25, zoom: 2.0 }) = error: invalid value: preset slot is 0-2 +presets::update(3, Pose { pan: 10.5, tilt: -5.25, zoom: 2.0 }) = error: invalid value: preset slot is 0-2 +presets::create(3, Pose { pan: 0.0, tilt: 0.0, zoom: 1.0 }) = error: invalid value: preset slot is 0-2 +presets::update(3, Pose { pan: 0.0, tilt: 0.0, zoom: 1.0 }) = error: invalid value: preset slot is 0-2 +presets::create(3, Pose { pan: -150.0, tilt: -70.0, zoom: 1.0 }) = error: invalid value: preset slot is 0-2 +presets::update(3, Pose { pan: -150.0, tilt: -70.0, zoom: 1.0 }) = error: invalid value: preset slot is 0-2 +presets::create(3, Pose { pan: 150.0, tilt: 90.0, zoom: 4.0 }) = error: invalid value: preset slot is 0-2 +presets::update(3, Pose { pan: 150.0, tilt: 90.0, zoom: 4.0 }) = error: invalid value: preset slot is 0-2 +presets::create(3, Pose { pan: 150.1, tilt: 0.0, zoom: 1.0 }) = error: invalid value: preset slot is 0-2 +presets::update(3, Pose { pan: 150.1, tilt: 0.0, zoom: 1.0 }) = error: invalid value: preset slot is 0-2 +presets::create(3, Pose { pan: 0.0, tilt: 90.5, zoom: 1.0 }) = error: invalid value: preset slot is 0-2 +presets::update(3, Pose { pan: 0.0, tilt: 90.5, zoom: 1.0 }) = error: invalid value: preset slot is 0-2 +presets::create(3, Pose { pan: 0.0, tilt: -70.5, zoom: 1.0 }) = error: invalid value: preset slot is 0-2 +presets::update(3, Pose { pan: 0.0, tilt: -70.5, zoom: 1.0 }) = error: invalid value: preset slot is 0-2 +presets::create(3, Pose { pan: 0.0, tilt: 0.0, zoom: 4.01 }) = error: invalid value: preset slot is 0-2 +presets::update(3, Pose { pan: 0.0, tilt: 0.0, zoom: 4.01 }) = error: invalid value: preset slot is 0-2 +presets::create(3, Pose { pan: 0.0, tilt: 0.0, zoom: 0.99 }) = error: invalid value: preset slot is 0-2 +presets::update(3, Pose { pan: 0.0, tilt: 0.0, zoom: 0.99 }) = error: invalid value: preset slot is 0-2 +presets::create(3, Pose { pan: NaN, tilt: 0.0, zoom: 1.0 }) = error: invalid value: preset slot is 0-2 +presets::update(3, Pose { pan: NaN, tilt: 0.0, zoom: 1.0 }) = error: invalid value: preset slot is 0-2 +presets::rename(3, "") = error: invalid value: preset slot is 0-2 +presets::rename(3, "Desk") = error: invalid value: preset slot is 0-2 +presets::rename(3, "Bereich1") = error: invalid value: preset slot is 0-2 +presets::rename(3, "äöü") = error: invalid value: preset slot is 0-2 +presets::rename(3, "12345678901234567890123456789012") = error: invalid value: preset slot is 0-2 +presets::rename(3, "123456789012345678901234567890123") = error: invalid value: preset slot is 0-2 +presets::delete(3) = error: invalid value: preset slot is 0-2 +presets::recall(3) = error: invalid value: preset slot is 0-2 +presets::create(255, Pose { pan: 10.5, tilt: -5.25, zoom: 2.0 }) = error: invalid value: preset slot is 0-2 +presets::update(255, Pose { pan: 10.5, tilt: -5.25, zoom: 2.0 }) = error: invalid value: preset slot is 0-2 +presets::create(255, Pose { pan: 0.0, tilt: 0.0, zoom: 1.0 }) = error: invalid value: preset slot is 0-2 +presets::update(255, Pose { pan: 0.0, tilt: 0.0, zoom: 1.0 }) = error: invalid value: preset slot is 0-2 +presets::create(255, Pose { pan: -150.0, tilt: -70.0, zoom: 1.0 }) = error: invalid value: preset slot is 0-2 +presets::update(255, Pose { pan: -150.0, tilt: -70.0, zoom: 1.0 }) = error: invalid value: preset slot is 0-2 +presets::create(255, Pose { pan: 150.0, tilt: 90.0, zoom: 4.0 }) = error: invalid value: preset slot is 0-2 +presets::update(255, Pose { pan: 150.0, tilt: 90.0, zoom: 4.0 }) = error: invalid value: preset slot is 0-2 +presets::create(255, Pose { pan: 150.1, tilt: 0.0, zoom: 1.0 }) = error: invalid value: preset slot is 0-2 +presets::update(255, Pose { pan: 150.1, tilt: 0.0, zoom: 1.0 }) = error: invalid value: preset slot is 0-2 +presets::create(255, Pose { pan: 0.0, tilt: 90.5, zoom: 1.0 }) = error: invalid value: preset slot is 0-2 +presets::update(255, Pose { pan: 0.0, tilt: 90.5, zoom: 1.0 }) = error: invalid value: preset slot is 0-2 +presets::create(255, Pose { pan: 0.0, tilt: -70.5, zoom: 1.0 }) = error: invalid value: preset slot is 0-2 +presets::update(255, Pose { pan: 0.0, tilt: -70.5, zoom: 1.0 }) = error: invalid value: preset slot is 0-2 +presets::create(255, Pose { pan: 0.0, tilt: 0.0, zoom: 4.01 }) = error: invalid value: preset slot is 0-2 +presets::update(255, Pose { pan: 0.0, tilt: 0.0, zoom: 4.01 }) = error: invalid value: preset slot is 0-2 +presets::create(255, Pose { pan: 0.0, tilt: 0.0, zoom: 0.99 }) = error: invalid value: preset slot is 0-2 +presets::update(255, Pose { pan: 0.0, tilt: 0.0, zoom: 0.99 }) = error: invalid value: preset slot is 0-2 +presets::create(255, Pose { pan: NaN, tilt: 0.0, zoom: 1.0 }) = error: invalid value: preset slot is 0-2 +presets::update(255, Pose { pan: NaN, tilt: 0.0, zoom: 1.0 }) = error: invalid value: preset slot is 0-2 +presets::rename(255, "") = error: invalid value: preset slot is 0-2 +presets::rename(255, "Desk") = error: invalid value: preset slot is 0-2 +presets::rename(255, "Bereich1") = error: invalid value: preset slot is 0-2 +presets::rename(255, "äöü") = error: invalid value: preset slot is 0-2 +presets::rename(255, "12345678901234567890123456789012") = error: invalid value: preset slot is 0-2 +presets::rename(255, "123456789012345678901234567890123") = error: invalid value: preset slot is 0-2 +presets::delete(255) = error: invalid value: preset slot is 0-2 +presets::recall(255) = error: invalid value: preset slot is 0-2 +presets::view_add(0, Pose { pan: 10.5, tilt: -5.25, zoom: 2.0 }) = aa2534120c004f230a0484131800f3ad00000000000028410000a840000000000000004000007ac40000000000000000000000000000000000000000 +presets::view_add(0, Pose { pan: 0.0, tilt: 0.0, zoom: 1.0 }) = aa2534120c004f230a048413180047e3000000000000000000000000000000000000803f00007ac40000000000000000000000000000000000000000 +presets::view_add(0, Pose { pan: -150.0, tilt: -70.0, zoom: 1.0 }) = aa2534120c004f230a048413180077ad00000000000016c300008c42000000000000803f00007ac40000000000000000000000000000000000000000 +presets::view_add(0, Pose { pan: 150.0, tilt: 90.0, zoom: 4.0 }) = aa2534120c004f230a0484131800c5b600000000000016430000b4c2000000000000804000007ac40000000000000000000000000000000000000000 +presets::view_add(0, Pose { pan: 150.1, tilt: 0.0, zoom: 1.0 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(0, Pose { pan: 0.0, tilt: 90.5, zoom: 1.0 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(0, Pose { pan: 0.0, tilt: -70.5, zoom: 1.0 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(0, Pose { pan: 0.0, tilt: 0.0, zoom: 4.01 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(0, Pose { pan: 0.0, tilt: 0.0, zoom: 0.99 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(0, Pose { pan: NaN, tilt: 0.0, zoom: 1.0 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_rename(0, "") = error: invalid value: name must be 1-32 printable ASCII characters +presets::view_rename(0, "Desk") = aa2534120c009f210a04441508001a12000000004465736b000000000000000000000000000000000000000000000000000000000000000000000000 +presets::view_rename(0, "Bereich1") = aa2534120c009f210a0444150c00b1a90000000042657265696368310000000000000000000000000000000000000000000000000000000000000000 +presets::view_rename(0, "äöü") = error: invalid value: name must be 1-32 printable ASCII characters +presets::view_rename(0, "12345678901234567890123456789012") = aa2534120c009f210a0444152400c1ee0000000031323334353637383930313233343536373839303132333435363738393031320000000000000000 +presets::view_rename(0, "123456789012345678901234567890123") = error: invalid value: name must be 1-32 printable ASCII characters +presets::view_remove(0) = aa2534120c007ee30a04c4130400be070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +presets::view_go(0) = aa2534120c00eee00a0404160400be070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +presets::view_query(0x1504, 0) = aa2134120c00bbd10a0404150400be070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +presets::view_query(0x1444, 0) = aa2134120c004bd10a0444140400be070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +presets::view_add(1, Pose { pan: 10.5, tilt: -5.25, zoom: 2.0 }) = aa2534120c004f230a0484131800ae3801000000000028410000a840000000000000004000007ac40000000000000000000000000000000000000000 +presets::view_add(1, Pose { pan: 0.0, tilt: 0.0, zoom: 1.0 }) = aa2534120c004f230a04841318001a76010000000000000000000000000000000000803f00007ac40000000000000000000000000000000000000000 +presets::view_add(1, Pose { pan: -150.0, tilt: -70.0, zoom: 1.0 }) = aa2534120c004f230a04841318002a3801000000000016c300008c42000000000000803f00007ac40000000000000000000000000000000000000000 +presets::view_add(1, Pose { pan: 150.0, tilt: 90.0, zoom: 4.0 }) = aa2534120c004f230a0484131800982301000000000016430000b4c2000000000000804000007ac40000000000000000000000000000000000000000 +presets::view_add(1, Pose { pan: 150.1, tilt: 0.0, zoom: 1.0 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(1, Pose { pan: 0.0, tilt: 90.5, zoom: 1.0 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(1, Pose { pan: 0.0, tilt: -70.5, zoom: 1.0 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(1, Pose { pan: 0.0, tilt: 0.0, zoom: 4.01 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(1, Pose { pan: 0.0, tilt: 0.0, zoom: 0.99 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(1, Pose { pan: NaN, tilt: 0.0, zoom: 1.0 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_rename(1, "") = error: invalid value: name must be 1-32 printable ASCII characters +presets::view_rename(1, "Desk") = aa2534120c009f210a0444150800dbde010000004465736b000000000000000000000000000000000000000000000000000000000000000000000000 +presets::view_rename(1, "Bereich1") = aa2534120c009f210a0444150c004c6a0100000042657265696368310000000000000000000000000000000000000000000000000000000000000000 +presets::view_rename(1, "äöü") = error: invalid value: name must be 1-32 printable ASCII characters +presets::view_rename(1, "12345678901234567890123456789012") = aa2534120c009f210a044415240000330100000031323334353637383930313233343536373839303132333435363738393031320000000000000000 +presets::view_rename(1, "123456789012345678901234567890123") = error: invalid value: name must be 1-32 printable ASCII characters +presets::view_remove(1) = aa2534120c007ee30a04c4130400bffb0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +presets::view_go(1) = aa2534120c00eee00a0404160400bffb0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +presets::view_query(0x1504, 1) = aa2134120c00bbd10a0404150400bffb0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +presets::view_query(0x1444, 1) = aa2134120c004bd10a0444140400bffb0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +presets::view_add(7, Pose { pan: 10.5, tilt: -5.25, zoom: 2.0 }) = aa2534120c004f230a0484131800658707000000000028410000a840000000000000004000007ac40000000000000000000000000000000000000000 +presets::view_add(7, Pose { pan: 0.0, tilt: 0.0, zoom: 1.0 }) = aa2534120c004f230a0484131800d1c9070000000000000000000000000000000000803f00007ac40000000000000000000000000000000000000000 +presets::view_add(7, Pose { pan: -150.0, tilt: -70.0, zoom: 1.0 }) = aa2534120c004f230a0484131800e18707000000000016c300008c42000000000000803f00007ac40000000000000000000000000000000000000000 +presets::view_add(7, Pose { pan: 150.0, tilt: 90.0, zoom: 4.0 }) = aa2534120c004f230a0484131800539c07000000000016430000b4c2000000000000804000007ac40000000000000000000000000000000000000000 +presets::view_add(7, Pose { pan: 150.1, tilt: 0.0, zoom: 1.0 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(7, Pose { pan: 0.0, tilt: 90.5, zoom: 1.0 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(7, Pose { pan: 0.0, tilt: -70.5, zoom: 1.0 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(7, Pose { pan: 0.0, tilt: 0.0, zoom: 4.01 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(7, Pose { pan: 0.0, tilt: 0.0, zoom: 0.99 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(7, Pose { pan: NaN, tilt: 0.0, zoom: 1.0 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_rename(7, "") = error: invalid value: name must be 1-32 printable ASCII characters +presets::view_rename(7, "Desk") = aa2534120c009f210a04441508005bf4070000004465736b000000000000000000000000000000000000000000000000000000000000000000000000 +presets::view_rename(7, "Bereich1") = aa2534120c009f210a0444150c0044620700000042657265696368310000000000000000000000000000000000000000000000000000000000000000 +presets::view_rename(7, "äöü") = error: invalid value: name must be 1-32 printable ASCII characters +presets::view_rename(7, "12345678901234567890123456789012") = aa2534120c009f210a0444152400807f0700000031323334353637383930313233343536373839303132333435363738393031320000000000000000 +presets::view_rename(7, "123456789012345678901234567890123") = error: invalid value: name must be 1-32 printable ASCII characters +presets::view_remove(7) = aa2534120c007ee30a04c4130400bf730700000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +presets::view_go(7) = aa2534120c00eee00a0404160400bf730700000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +presets::view_query(0x1504, 7) = aa2134120c00bbd10a0404150400bf730700000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +presets::view_query(0x1444, 7) = aa2134120c004bd10a0444140400bf730700000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +presets::view_add(255, Pose { pan: 10.5, tilt: -5.25, zoom: 2.0 }) = aa2534120c004f230a048413180083aaff000000000028410000a840000000000000004000007ac40000000000000000000000000000000000000000 +presets::view_add(255, Pose { pan: 0.0, tilt: 0.0, zoom: 1.0 }) = aa2534120c004f230a048413180037e4ff0000000000000000000000000000000000803f00007ac40000000000000000000000000000000000000000 +presets::view_add(255, Pose { pan: -150.0, tilt: -70.0, zoom: 1.0 }) = aa2534120c004f230a048413180007aaff000000000016c300008c42000000000000803f00007ac40000000000000000000000000000000000000000 +presets::view_add(255, Pose { pan: 150.0, tilt: 90.0, zoom: 4.0 }) = aa2534120c004f230a0484131800b5b1ff000000000016430000b4c2000000000000804000007ac40000000000000000000000000000000000000000 +presets::view_add(255, Pose { pan: 150.1, tilt: 0.0, zoom: 1.0 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(255, Pose { pan: 0.0, tilt: 90.5, zoom: 1.0 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(255, Pose { pan: 0.0, tilt: -70.5, zoom: 1.0 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(255, Pose { pan: 0.0, tilt: 0.0, zoom: 4.01 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(255, Pose { pan: 0.0, tilt: 0.0, zoom: 0.99 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(255, Pose { pan: NaN, tilt: 0.0, zoom: 1.0 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_rename(255, "") = error: invalid value: name must be 1-32 printable ASCII characters +presets::view_rename(255, "Desk") = aa2534120c009f210a04441508005516ff0000004465736b000000000000000000000000000000000000000000000000000000000000000000000000 +presets::view_rename(255, "Bereich1") = aa2534120c009f210a0444150c00e6bcff00000042657265696368310000000000000000000000000000000000000000000000000000000000000000 +presets::view_rename(255, "äöü") = error: invalid value: name must be 1-32 printable ASCII characters +presets::view_rename(255, "12345678901234567890123456789012") = aa2534120c009f210a04441524009ae5ff00000031323334353637383930313233343536373839303132333435363738393031320000000000000000 +presets::view_rename(255, "123456789012345678901234567890123") = error: invalid value: name must be 1-32 printable ASCII characters +presets::view_remove(255) = aa2534120c007ee30a04c41304008e13ff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +presets::view_go(255) = aa2534120c00eee00a04041604008e13ff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +presets::view_query(0x1504, 255) = aa2134120c00bbd10a04041504008e13ff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +presets::view_query(0x1444, 255) = aa2134120c004bd10a04441404008e13ff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +presets::view_add(4294967295, Pose { pan: 10.5, tilt: -5.25, zoom: 2.0 }) = aa2534120c004f230a0484131800d251ffffffff000028410000a840000000000000004000007ac40000000000000000000000000000000000000000 +presets::view_add(4294967295, Pose { pan: 0.0, tilt: 0.0, zoom: 1.0 }) = aa2534120c004f230a0484131800661fffffffff0000000000000000000000000000803f00007ac40000000000000000000000000000000000000000 +presets::view_add(4294967295, Pose { pan: -150.0, tilt: -70.0, zoom: 1.0 }) = aa2534120c004f230a04841318005651ffffffff000016c300008c42000000000000803f00007ac40000000000000000000000000000000000000000 +presets::view_add(4294967295, Pose { pan: 150.0, tilt: 90.0, zoom: 4.0 }) = aa2534120c004f230a0484131800e44affffffff000016430000b4c2000000000000804000007ac40000000000000000000000000000000000000000 +presets::view_add(4294967295, Pose { pan: 150.1, tilt: 0.0, zoom: 1.0 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(4294967295, Pose { pan: 0.0, tilt: 90.5, zoom: 1.0 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(4294967295, Pose { pan: 0.0, tilt: -70.5, zoom: 1.0 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(4294967295, Pose { pan: 0.0, tilt: 0.0, zoom: 4.01 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(4294967295, Pose { pan: 0.0, tilt: 0.0, zoom: 0.99 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_add(4294967295, Pose { pan: NaN, tilt: 0.0, zoom: 1.0 }) = error: invalid value: zone view pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::view_rename(4294967295, "") = error: invalid value: name must be 1-32 printable ASCII characters +presets::view_rename(4294967295, "Desk") = aa2534120c009f210a04441508005a02ffffffff4465736b000000000000000000000000000000000000000000000000000000000000000000000000 +presets::view_rename(4294967295, "Bereich1") = aa2534120c009f210a0444150c00a5acffffffff42657265696368310000000000000000000000000000000000000000000000000000000000000000 +presets::view_rename(4294967295, "äöü") = error: invalid value: name must be 1-32 printable ASCII characters +presets::view_rename(4294967295, "12345678901234567890123456789012") = aa2534120c009f210a044415240081c1ffffffff31323334353637383930313233343536373839303132333435363738393031320000000000000000 +presets::view_rename(4294967295, "123456789012345678901234567890123") = error: invalid value: name must be 1-32 printable ASCII characters +presets::view_remove(4294967295) = aa2534120c007ee30a04c4130400bf93ffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000 +presets::view_go(4294967295) = aa2534120c00eee00a0404160400bf93ffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000 +presets::view_query(0x1504, 4294967295) = aa2134120c00bbd10a0404150400bf93ffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000 +presets::view_query(0x1444, 4294967295) = aa2134120c004bd10a0444140400bf93ffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000 +presets::startup_pose(0, Pose { pan: 10.5, tilt: -5.25, zoom: 2.0 }) = aa2534120c00befe0a04c43e1800d15e00000000000028410000a8400000000000000040000000000000000000000000000000000000000000000000 +presets::startup_pose(0, Pose { pan: 0.0, tilt: 0.0, zoom: 1.0 }) = aa2534120c00befe0a04c43e18006510000000000000000000000000000000000000803f000000000000000000000000000000000000000000000000 +presets::startup_pose(0, Pose { pan: -150.0, tilt: -70.0, zoom: 1.0 }) = aa2534120c00befe0a04c43e1800555e00000000000016c300008c42000000000000803f000000000000000000000000000000000000000000000000 +presets::startup_pose(0, Pose { pan: 150.0, tilt: 90.0, zoom: 4.0 }) = aa2534120c00befe0a04c43e1800e74500000000000016430000b4c20000000000008040000000000000000000000000000000000000000000000000 +presets::startup_pose(0, Pose { pan: 150.1, tilt: 0.0, zoom: 1.0 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(0, Pose { pan: 0.0, tilt: 90.5, zoom: 1.0 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(0, Pose { pan: 0.0, tilt: -70.5, zoom: 1.0 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(0, Pose { pan: 0.0, tilt: 0.0, zoom: 4.01 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(0, Pose { pan: 0.0, tilt: 0.0, zoom: 0.99 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(0, Pose { pan: NaN, tilt: 0.0, zoom: 1.0 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(1, Pose { pan: 10.5, tilt: -5.25, zoom: 2.0 }) = aa2534120c00befe0a04c43e18008ccb01000000000028410000a8400000000000000040000000000000000000000000000000000000000000000000 +presets::startup_pose(1, Pose { pan: 0.0, tilt: 0.0, zoom: 1.0 }) = aa2534120c00befe0a04c43e18003885010000000000000000000000000000000000803f000000000000000000000000000000000000000000000000 +presets::startup_pose(1, Pose { pan: -150.0, tilt: -70.0, zoom: 1.0 }) = aa2534120c00befe0a04c43e180008cb01000000000016c300008c42000000000000803f000000000000000000000000000000000000000000000000 +presets::startup_pose(1, Pose { pan: 150.0, tilt: 90.0, zoom: 4.0 }) = aa2534120c00befe0a04c43e1800bad001000000000016430000b4c20000000000008040000000000000000000000000000000000000000000000000 +presets::startup_pose(1, Pose { pan: 150.1, tilt: 0.0, zoom: 1.0 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(1, Pose { pan: 0.0, tilt: 90.5, zoom: 1.0 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(1, Pose { pan: 0.0, tilt: -70.5, zoom: 1.0 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(1, Pose { pan: 0.0, tilt: 0.0, zoom: 4.01 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(1, Pose { pan: 0.0, tilt: 0.0, zoom: 0.99 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(1, Pose { pan: NaN, tilt: 0.0, zoom: 1.0 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(2, Pose { pan: 10.5, tilt: -5.25, zoom: 2.0 }) = aa2534120c00befe0a04c43e1800683402000000000028410000a8400000000000000040000000000000000000000000000000000000000000000000 +presets::startup_pose(2, Pose { pan: 0.0, tilt: 0.0, zoom: 1.0 }) = aa2534120c00befe0a04c43e1800dc7a020000000000000000000000000000000000803f000000000000000000000000000000000000000000000000 +presets::startup_pose(2, Pose { pan: -150.0, tilt: -70.0, zoom: 1.0 }) = aa2534120c00befe0a04c43e1800ec3402000000000016c300008c42000000000000803f000000000000000000000000000000000000000000000000 +presets::startup_pose(2, Pose { pan: 150.0, tilt: 90.0, zoom: 4.0 }) = aa2534120c00befe0a04c43e18005e2f02000000000016430000b4c20000000000008040000000000000000000000000000000000000000000000000 +presets::startup_pose(2, Pose { pan: 150.1, tilt: 0.0, zoom: 1.0 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(2, Pose { pan: 0.0, tilt: 90.5, zoom: 1.0 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(2, Pose { pan: 0.0, tilt: -70.5, zoom: 1.0 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(2, Pose { pan: 0.0, tilt: 0.0, zoom: 4.01 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(2, Pose { pan: 0.0, tilt: 0.0, zoom: 0.99 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(2, Pose { pan: NaN, tilt: 0.0, zoom: 1.0 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(3, Pose { pan: 10.5, tilt: -5.25, zoom: 2.0 }) = aa2534120c00befe0a04c43e180035a103000000000028410000a8400000000000000040000000000000000000000000000000000000000000000000 +presets::startup_pose(3, Pose { pan: 0.0, tilt: 0.0, zoom: 1.0 }) = aa2534120c00befe0a04c43e180081ef030000000000000000000000000000000000803f000000000000000000000000000000000000000000000000 +presets::startup_pose(3, Pose { pan: -150.0, tilt: -70.0, zoom: 1.0 }) = aa2534120c00befe0a04c43e1800b1a103000000000016c300008c42000000000000803f000000000000000000000000000000000000000000000000 +presets::startup_pose(3, Pose { pan: 150.0, tilt: 90.0, zoom: 4.0 }) = aa2534120c00befe0a04c43e180003ba03000000000016430000b4c20000000000008040000000000000000000000000000000000000000000000000 +presets::startup_pose(3, Pose { pan: 150.1, tilt: 0.0, zoom: 1.0 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(3, Pose { pan: 0.0, tilt: 90.5, zoom: 1.0 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(3, Pose { pan: 0.0, tilt: -70.5, zoom: 1.0 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(3, Pose { pan: 0.0, tilt: 0.0, zoom: 4.01 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(3, Pose { pan: 0.0, tilt: 0.0, zoom: 0.99 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(3, Pose { pan: NaN, tilt: 0.0, zoom: 1.0 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(4, Pose { pan: 10.5, tilt: -5.25, zoom: 2.0 }) = aa2534120c00befe0a04c43e1800a38b04000000000028410000a8400000000000000040000000000000000000000000000000000000000000000000 +presets::startup_pose(4, Pose { pan: 0.0, tilt: 0.0, zoom: 1.0 }) = aa2534120c00befe0a04c43e180017c5040000000000000000000000000000000000803f000000000000000000000000000000000000000000000000 +presets::startup_pose(4, Pose { pan: -150.0, tilt: -70.0, zoom: 1.0 }) = aa2534120c00befe0a04c43e1800278b04000000000016c300008c42000000000000803f000000000000000000000000000000000000000000000000 +presets::startup_pose(4, Pose { pan: 150.0, tilt: 90.0, zoom: 4.0 }) = aa2534120c00befe0a04c43e1800959004000000000016430000b4c20000000000008040000000000000000000000000000000000000000000000000 +presets::startup_pose(4, Pose { pan: 150.1, tilt: 0.0, zoom: 1.0 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(4, Pose { pan: 0.0, tilt: 90.5, zoom: 1.0 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(4, Pose { pan: 0.0, tilt: -70.5, zoom: 1.0 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(4, Pose { pan: 0.0, tilt: 0.0, zoom: 4.01 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(4, Pose { pan: 0.0, tilt: 0.0, zoom: 0.99 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_pose(4, Pose { pan: NaN, tilt: 0.0, zoom: 1.0 }) = error: invalid value: start pose needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::zone_return_set(Pose { pan: 10.5, tilt: -5.25, zoom: 2.0 }) = aa2534120c008eda0a04844e1800d15e00000000000028410000a8400000000000000040000000000000000000000000000000000000000000000000 +presets::hand_return_set(Pose { pan: 10.5, tilt: -5.25, zoom: 2.0 }) = aa2534120c003f350a04c4241800d15e00000000000028410000a8400000000000000040000000000000000000000000000000000000000000000000 +presets::zone_return_set(Pose { pan: 0.0, tilt: 0.0, zoom: 1.0 }) = aa2534120c008eda0a04844e18006510000000000000000000000000000000000000803f000000000000000000000000000000000000000000000000 +presets::hand_return_set(Pose { pan: 0.0, tilt: 0.0, zoom: 1.0 }) = aa2534120c003f350a04c42418006510000000000000000000000000000000000000803f000000000000000000000000000000000000000000000000 +presets::zone_return_set(Pose { pan: -150.0, tilt: -70.0, zoom: 1.0 }) = aa2534120c008eda0a04844e1800555e00000000000016c300008c42000000000000803f000000000000000000000000000000000000000000000000 +presets::hand_return_set(Pose { pan: -150.0, tilt: -70.0, zoom: 1.0 }) = aa2534120c003f350a04c4241800555e00000000000016c300008c42000000000000803f000000000000000000000000000000000000000000000000 +presets::zone_return_set(Pose { pan: 150.0, tilt: 90.0, zoom: 4.0 }) = aa2534120c008eda0a04844e1800e74500000000000016430000b4c20000000000008040000000000000000000000000000000000000000000000000 +presets::hand_return_set(Pose { pan: 150.0, tilt: 90.0, zoom: 4.0 }) = aa2534120c003f350a04c4241800e74500000000000016430000b4c20000000000008040000000000000000000000000000000000000000000000000 +presets::zone_return_set(Pose { pan: 150.1, tilt: 0.0, zoom: 1.0 }) = error: invalid value: return position needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::hand_return_set(Pose { pan: 150.1, tilt: 0.0, zoom: 1.0 }) = error: invalid value: return position needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::zone_return_set(Pose { pan: 0.0, tilt: 90.5, zoom: 1.0 }) = error: invalid value: return position needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::hand_return_set(Pose { pan: 0.0, tilt: 90.5, zoom: 1.0 }) = error: invalid value: return position needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::zone_return_set(Pose { pan: 0.0, tilt: -70.5, zoom: 1.0 }) = error: invalid value: return position needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::hand_return_set(Pose { pan: 0.0, tilt: -70.5, zoom: 1.0 }) = error: invalid value: return position needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::zone_return_set(Pose { pan: 0.0, tilt: 0.0, zoom: 4.01 }) = error: invalid value: return position needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::hand_return_set(Pose { pan: 0.0, tilt: 0.0, zoom: 4.01 }) = error: invalid value: return position needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::zone_return_set(Pose { pan: 0.0, tilt: 0.0, zoom: 0.99 }) = error: invalid value: return position needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::hand_return_set(Pose { pan: 0.0, tilt: 0.0, zoom: 0.99 }) = error: invalid value: return position needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::zone_return_set(Pose { pan: NaN, tilt: 0.0, zoom: 1.0 }) = error: invalid value: return position needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::hand_return_set(Pose { pan: NaN, tilt: 0.0, zoom: 1.0 }) = error: invalid value: return position needs pan -150..150, tilt -70..90 and zoom 1.0-4.0 +presets::startup_picture_settings(Static) = aa2534120c00df3e0a04443e2800e012ffffffffffffffff80ffffffffffffff00000000ffffffff0000000000000000000000000000000000000000 +presets::startup_picture_settings(Normal) = aa2534120c00df3e0a04443e280082f400ffffffffffffff80ffffffffffffff00000000ffffffff0000000000000000000000000000000000000000 +presets::startup_picture_settings(UpperBody) = aa2534120c00df3e0a04443e28002f3201ffffffffffffff80ffffffffffffff00000000ffffffff0000000000000000000000000000000000000000 +presets::startup_picture_settings(Headless) = aa2534120c00df3e0a04443e280076ff03ffffffffffffff80ffffffffffffff00000000ffffffff0000000000000000000000000000000000000000 +presets::startup_picture_settings(Group) = aa2534120c00df3e0a04443e28004ec709ffffffffffffff80ffffffffffffff00000000ffffffff0000000000000000000000000000000000000000 +presets::startup_picture_settings(None) = aa2534120c00df3e0a04443e28004dd4feffffffffffffff80ffffffffffffff00000000ffffffff0000000000000000000000000000000000000000 +presets::startup_with_ai(0..0, 0x00) = none +presets::startup_with_ai(0..0, 0xfe) = none +presets::startup_with_ai(0..0, 0xff) = none +presets::startup_with_ai(0..39, 0x00) = none +presets::startup_with_ai(0..39, 0xfe) = none +presets::startup_with_ai(0..39, 0xff) = none +presets::startup_with_ai(0..40, 0x00) = aa2534120c00df3e0a04443e2800187d000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262700000000 +presets::startup_with_ai(0..40, 0xfe) = aa2534120c00df3e0a04443e2800d75dfe0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262700000000 +presets::startup_with_ai(0..40, 0xff) = aa2534120c00df3e0a04443e28007a9bff0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262700000000 +presets::startup_with_ai(0..48, 0x00) = aa2534120c00df3e0a04443e2800187d000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262700000000 +presets::startup_with_ai(0..48, 0xfe) = aa2534120c00df3e0a04443e2800d75dfe0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262700000000 +presets::startup_with_ai(0..48, 0xff) = aa2534120c00df3e0a04443e28007a9bff0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262700000000 diff --git a/tests/fixtures/cli-surface.txt b/tests/fixtures/cli-surface.txt new file mode 100644 index 0000000..2dcd923 --- /dev/null +++ b/tests/fixtures/cli-surface.txt @@ -0,0 +1,1126 @@ +=== obsbot4linux --help +Control the OBSBOT Tiny 2 Lite + +Usage: obsbot4linux [OPTIONS] + +Commands: + daemon Run the daemon: owns the camera, serves the socket, broadcasts state changes + watch Stream state events as JSON lines and forward JSON requests from stdin (for widgets) + live-pose Ask the daemon to poll the gimbal pose for this connection (a `watch` request; the daemon polls it every second only while a connection wants it). Not usable as a one-off call + detect Exit 0 and print the model (--json: model and video device) when a supported camera is connected, exit 1 with no output when not. Enumeration only: no daemon, no camera command + status Show all settings (the camera's status block, tracking readback, zoom, image controls, remembered state) + sleep Put the camera to sleep + wake Wake the camera, or set the stored wake-up zoom (`wake zoom`) or the tracking mode at power-on (`wake mode`) + tracking AI tracking mode + speed Tracking type: standard, sport (fixed fast gain) or headroom (more room above the head) + speed-mode Fine tracking speed, from lazy to very-fast, or auto. Ignored while `speed sport` is set or the part mode is not normal + framing Framing options of the human tracking; an AI mode change overwrites them + hdr HDR + exposure Exposure: automatic metering over the whole picture (global) or on faces (face), or manual, optionally with the shutter time and gain (`image gain` sets the gain too; `image exposure_time` is refused unless the exposure is already manual) + zoom Zoom factor 1.0-4.0; with --speed the camera zooms smoothly instead of jumping + gimbal Move the gimbal to an absolute pose in degrees (switches tracking to static first), or `gimbal stop|rate` + fov Field of view (ignored by the camera while it sleeps) + led Status LED: brightness 0 (off) to 3, or `led blink on|off` + mirror Mirror the image + invert Invert view and gimbal (reverses the gimbal's pan direction) + auto-sleep Auto-sleep: "off" or a time in seconds + sleep-background Picture shown while the camera sleeps + sleep-background-mirror Mirror the sleep background + audio Microphone settings + gesture Gesture control + hand Hand tracking: which hand, return position, area, axes + zone Zone tracking + focus Focus: autofocus mode, continuous autofocus, manual focus + startup Initial state: the pose the camera moves to when it powers on + image UVC image control, e.g. "image brightness 128" (names are in "status") + preset Presets stored in the camera (slots 0-2) + factory-reset Reset the camera to factory settings (it restarts and deletes its presets) + help Print this message or the help of the given subcommand(s) + +Options: + --json Print machine-readable JSON (errors too, as {"ok":false,"error":...}) + --direct Do not use a running daemon; talk to the camera directly + -h, --help Print help + -V, --version Print version +=== obsbot4linux daemon --help +Run the daemon: owns the camera, serves the socket, broadcasts state changes + +Usage: obsbot4linux daemon [OPTIONS] + +Options: + --json Print machine-readable JSON (errors too, as {"ok":false,"error":...}) + --direct Do not use a running daemon; talk to the camera directly + -h, --help Print help +=== obsbot4linux watch --help +Stream state events as JSON lines and forward JSON requests from stdin (for widgets) + +Usage: obsbot4linux watch [OPTIONS] + +Options: + --json Print machine-readable JSON (errors too, as {"ok":false,"error":...}) + --direct Do not use a running daemon; talk to the camera directly + -h, --help Print help +=== obsbot4linux live-pose --help +Ask the daemon to poll the gimbal pose for this connection (a `watch` request; the daemon polls it every second only while a connection wants it). Not usable as a one-off call + +Usage: obsbot4linux live-pose [OPTIONS] + +Arguments: + [possible values: on, off] + +Options: + --json Print machine-readable JSON (errors too, as {"ok":false,"error":...}) + --direct Do not use a running daemon; talk to the camera directly + -h, --help Print help +=== obsbot4linux detect --help +Exit 0 and print the model (--json: model and video device) when a supported camera is connected, exit 1 with no output when not. Enumeration only: no daemon, no camera command + +Usage: obsbot4linux detect [OPTIONS] + +Options: + --json Print machine-readable JSON (errors too, as {"ok":false,"error":...}) + --direct Do not use a running daemon; talk to the camera directly + -h, --help Print help +=== obsbot4linux status --help +Show all settings (the camera's status block, tracking readback, zoom, image controls, remembered state) + +Usage: obsbot4linux status [OPTIONS] + +Options: + --json Print machine-readable JSON (errors too, as {"ok":false,"error":...}) + --direct Do not use a running daemon; talk to the camera directly + -h, --help Print help +=== obsbot4linux sleep --help +Put the camera to sleep + +Usage: obsbot4linux sleep [OPTIONS] + +Options: + --json Print machine-readable JSON (errors too, as {"ok":false,"error":...}) + --direct Do not use a running daemon; talk to the camera directly + -h, --help Print help +=== obsbot4linux wake --help +Wake the camera, or set the stored wake-up zoom (`wake zoom`) or the tracking mode at power-on (`wake mode`) + +Usage: obsbot4linux wake [OPTIONS] [COMMAND] + +Commands: + zoom Stored wake-up zoom, factor 1.0-4.0 (the camera stores 0-100 percent). Effect unknown on the Tiny 2 Lite: no change at power-on or on waking from sleep + mode Tracking mode the camera starts in at power-on, not on waking from sleep (the current mode stays) + help Print this message or the help of the given subcommand(s) + +Options: + --json Print machine-readable JSON (errors too, as {"ok":false,"error":...}) + --direct Do not use a running daemon; talk to the camera directly + -h, --help Print help +=== obsbot4linux wake zoom --help +Stored wake-up zoom, factor 1.0-4.0 (the camera stores 0-100 percent). Effect unknown on the Tiny 2 Lite: no change at power-on or on waking from sleep + +Usage: obsbot4linux wake zoom [OPTIONS] + +Arguments: + + +Options: + --json Print machine-readable JSON (errors too, as {"ok":false,"error":...}) + --direct Do not use a running daemon; talk to the camera directly + -h, --help Print help +=== obsbot4linux wake mode --help +Tracking mode the camera starts in at power-on, not on waking from sleep (the current mode stays) + +Usage: obsbot4linux wake mode [OPTIONS] + +Arguments: + [possible values: static, normal, upper-body, close-up, headless, lower-body, hand, group] + +Options: + --json Print machine-readable JSON (errors too, as {"ok":false,"error":...}) + --direct Do not use a running daemon; talk to the camera directly + -h, --help Print help +=== obsbot4linux tracking --help +AI tracking mode + +Usage: obsbot4linux tracking [OPTIONS] + +Arguments: + [possible values: static, normal, upper-body, close-up, headless, lower-body, hand, group] + +Options: + --json Print machine-readable JSON (errors too, as {"ok":false,"error":...}) + --direct Do not use a running daemon; talk to the camera directly + -h, --help Print help +=== obsbot4linux speed --help +Tracking type: standard, sport (fixed fast gain) or headroom (more room above the head) + +Usage: obsbot4linux speed [OPTIONS] + +Arguments: + [possible values: standard, headroom, sport] + +Options: + --json Print machine-readable JSON (errors too, as {"ok":false,"error":...}) + --direct Do not use a running daemon; talk to the camera directly + -h, --help Print help +=== obsbot4linux speed-mode --help +Fine tracking speed, from lazy to very-fast, or auto. Ignored while `speed sport` is set or the part mode is not normal + +Usage: obsbot4linux speed-mode [OPTIONS] + +Arguments: + [possible values: lazy, slow, normal, fast, very-fast, auto] + +Options: + --json Print machine-readable JSON (errors too, as {"ok":false,"error":...}) + --direct Do not use a running daemon; talk to the camera directly + -h, --help Print help +=== obsbot4linux framing --help +Framing options of the human tracking; an AI mode change overwrites them + +Usage: obsbot4linux framing [OPTIONS] + +Commands: + auto-zoom Auto zoom of the human tracking on or off + size Size of the auto zoom framing + zoom-speed How fast the auto zoom follows + offset Shift the framing by a fraction of the frame, -0.16 to 0.16 + part Which part of the body the framing follows + help Print this message or the help of the given subcommand(s) + +Options: + --json Print machine-readable JSON (errors too, as {"ok":false,"error":...}) + --direct Do not use a running daemon; talk to the camera directly + -h, --help Print help +=== obsbot4linux framing auto-zoom --help +Auto zoom of the human tracking on or off + +Usage: obsbot4linux framing auto-zoom [OPTIONS] + +Arguments: + [possible values: on, off] + +Options: + --json Print machine-readable JSON (errors too, as {"ok":false,"error":...}) + --direct Do not use a running daemon; talk to the camera directly + -h, --help Print help +=== obsbot4linux framing size --help +Size of the auto zoom framing + +Usage: obsbot4linux framing size [OPTIONS] + +Arguments: + [possible values: long-shot, medium-long-shot, medium-shot, medium-close-up, close-up, big-close-up] + +Options: + --json Print machine-readable JSON (errors too, as {"ok":false,"error":...}) + --direct Do not use a running daemon; talk to the camera directly + -h, --help Print help +=== obsbot4linux framing zoom-speed --help +How fast the auto zoom follows + +Usage: obsbot4linux framing zoom-speed [OPTIONS] + +Arguments: + [possible values: slow, medium, fast] + +Options: + --json Print machine-readable JSON (errors too, as {"ok":false,"error":...}) + --direct Do not use a running daemon; talk to the camera directly + -h, --help Print help +=== obsbot4linux framing offset --help +Shift the framing by a fraction of the frame, -0.16 to 0.16 + +Usage: obsbot4linux framing offset [OPTIONS] + +Options: + --json Print machine-readable JSON (errors too, as {"ok":false,"error":...}) + --x + --direct Do not use a running daemon; talk to the camera directly + --y + -h, --help Print help +=== obsbot4linux framing part --help +Which part of the body the framing follows + +Usage: obsbot4linux framing part [OPTIONS] + +Arguments: + [possible values: normal, head-removal, lower-body] + +Options: + --json Print machine-readable JSON (errors too, as {"ok":false,"error":...}) + --direct Do not use a running daemon; talk to the camera directly + -h, --help Print help +=== obsbot4linux hdr --help +HDR + +Usage: obsbot4linux hdr [OPTIONS] + +Arguments: + [possible values: on, off] + +Options: + --json Print machine-readable JSON (errors too, as {"ok":false,"error":...}) + --direct Do not use a running daemon; talk to the camera directly + -h, --help Print help +=== obsbot4linux exposure --help +Exposure: automatic metering over the whole picture (global) or on faces (face), or manual, optionally with the shutter time and gain (`image gain` sets the gain too; `image exposure_time` is refused unless the exposure is already manual) + +Usage: obsbot4linux exposure [OPTIONS] + +Arguments: + [possible values: global, face, manual] + +Options: + --json Print machine-readable JSON (errors too, as {"ok":false,"error":...}) + --time