1use grafton_visca_macros::ViscaEnum;
8
9use std::borrow::Cow;
10
11use crate::{
12 command::{encode::ViscaCommand, resolution::PictureEffectMode},
13 error::Error,
14 timeout::CommandCategory,
15 types::{
16 ContrastLevel, GammaLevel, LuminanceLevel, NoiseReduction2DLevel, NoiseReduction3DLevel,
17 },
18 visca_command,
19};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, ViscaEnum)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
25#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
26#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
27pub enum SharpnessMode {
28 Auto = 0x02,
30 Manual = 0x03,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, ViscaEnum)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
38#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
39#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
40pub enum NoiseReductionMode {
41 Off = 0x02,
43 On = 0x03,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, ViscaEnum)]
49#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
50#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
51#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
52#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
53pub enum NoiseReductionSpeed {
54 Slow = 0x00,
56 Normal = 0x01,
58 Fast = 0x02,
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, ViscaEnum)]
64#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
65#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
66#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
67#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
68pub enum BlackWhiteMode {
69 Color = 0x02,
71 BlackWhite = 0x03,
73}
74
75#[derive(Debug, Copy, Clone)]
80pub enum Sharpness {
81 Mode(SharpnessMode),
83 Reset,
85 Up,
87 Down,
89 SetLevel {
91 value: u8,
93 },
94}
95
96impl ViscaCommand for Sharpness {
97 const MAX_SIZE: usize = 9;
98 const TIMEOUT_CATEGORY: CommandCategory = CommandCategory::Custom;
99
100 fn write_into(
101 &self,
102 camera_id: crate::camera_id::CameraId,
103 buffer: &mut [u8],
104 ) -> Result<usize, Error> {
105 use crate::command::bytes::{constants, ConstCommandBuilder};
106
107 match self {
108 Self::Mode(mode) => {
109 let mode_byte = match mode {
110 SharpnessMode::Auto => 0x02,
111 SharpnessMode::Manual => 0x03,
112 };
113 let mut builder = ConstCommandBuilder::<6>::new();
114 builder.append_mut(constants::image::SHARPNESS_MODE_PREFIX);
115 builder.push_mut(mode_byte);
116 builder
117 .with_camera_id(camera_id)
118 .terminate()
119 .build_into(buffer)
120 }
121 Self::Reset => {
122 let mut builder = ConstCommandBuilder::<6>::new();
123 builder.append_mut(constants::image::SHARPNESS_CONTROL_PREFIX);
124 builder.push_mut(0x00);
125 builder
126 .with_camera_id(camera_id)
127 .terminate()
128 .build_into(buffer)
129 }
130 Self::Up => {
131 let mut builder = ConstCommandBuilder::<6>::new();
132 builder.append_mut(constants::image::SHARPNESS_CONTROL_PREFIX);
133 builder.push_mut(0x02);
134 builder
135 .with_camera_id(camera_id)
136 .terminate()
137 .build_into(buffer)
138 }
139 Self::Down => {
140 let mut builder = ConstCommandBuilder::<6>::new();
141 builder.append_mut(constants::image::SHARPNESS_CONTROL_PREFIX);
142 builder.push_mut(0x03);
143 builder
144 .with_camera_id(camera_id)
145 .terminate()
146 .build_into(buffer)
147 }
148 Self::SetLevel { value } => {
149 if *value > 15 {
150 return Err(Error::InvalidParameter {
151 parameter: "value",
152 value: Cow::Owned(value.to_string()),
153 reason: Cow::Borrowed("Sharpness value must be in the range 0..=15"),
154 });
155 }
156 let mut builder = ConstCommandBuilder::<9>::new();
157 builder.append_mut(constants::image::SHARPNESS_LEVEL_PREFIX);
158 builder.push_nibble_pair_mut(*value as u16);
159 builder
160 .with_camera_id(camera_id)
161 .terminate()
162 .build_into(buffer)
163 }
164 }
165 }
166}
167
168visca_command! {
169 pub struct Luminance { value: LuminanceLevel };
171 prefix = [0x01, 0x04, 0xA1, 0x00, 0x00, 0x00];
172 param = value.value();
173 max_param_size = 1;
174 category = CommandCategory::Quick;
175}
176
177impl Luminance {
178 pub fn new(value: LuminanceLevel) -> Self {
180 Self { value }
181 }
182}
183
184visca_command! {
185 pub struct Contrast { value: ContrastLevel };
187 prefix = [0x01, 0x04, 0xA2, 0x00, 0x00, 0x00];
188 param = value.value();
189 max_param_size = 1;
190 category = CommandCategory::Quick;
191}
192
193impl Contrast {
194 pub fn new(value: ContrastLevel) -> Self {
196 Self { value }
197 }
198}
199
200visca_command! {
201 pub struct GammaCommand { level: GammaLevel };
212 prefix = [0x01, 0x04, 0x5B];
213 param = level.value();
214 max_param_size = 1;
215 category = CommandCategory::Custom;
216}
217
218impl GammaCommand {
219 pub fn new(level: GammaLevel) -> Self {
221 Self { level }
222 }
223}
224
225visca_command! {
226 pub struct BacklightCommand { enabled: bool };
231 prefix = [0x01, 0x04, 0x33];
232 param = if *enabled { 0x02 } else { 0x03 };
233 max_param_size = 1;
234 category = CommandCategory::Quick;
235}
236
237impl BacklightCommand {
238 pub fn new(enabled: bool) -> Self {
240 Self { enabled }
241 }
242}
243
244visca_command! {
245 pub struct NoiseReduction2D { level: Option<NoiseReduction2DLevel> };
251 prefix = [0x01, 0x04, 0x53];
252 param = match level { None => 0x00, Some(l) => l.value() };
253 max_param_size = 1;
254 category = CommandCategory::Custom;
255}
256
257impl NoiseReduction2D {
258 pub const fn off() -> Self {
260 Self { level: None }
261 }
262
263 pub const fn with_level(level: NoiseReduction2DLevel) -> Self {
265 Self { level: Some(level) }
266 }
267}
268
269visca_command! {
270 pub struct NoiseReduction3D { level: Option<NoiseReduction3DLevel> };
276 prefix = [0x01, 0x04, 0x54];
277 param = match level { None => 0x00, Some(l) => l.value() };
278 max_param_size = 1;
279 category = CommandCategory::Custom;
280}
281
282impl NoiseReduction3D {
283 pub const fn off() -> Self {
285 Self { level: None }
286 }
287
288 pub const fn with_level(level: NoiseReduction3DLevel) -> Self {
290 Self { level: Some(level) }
291 }
292}
293
294#[derive(Debug, Copy, Clone)]
299#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
300#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
301#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
302#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
303pub enum ImageFlipMode {
304 Off,
306 Horizontal,
308 Vertical,
310 Both,
312}
313
314visca_command! {
315 pub struct ImageFlipCombinedCommand { mode: ImageFlipMode };
317 prefix = [0x01, 0x04, 0xA4];
318 param = match mode {
319 ImageFlipMode::Off => 0x00,
320 ImageFlipMode::Horizontal => 0x01,
321 ImageFlipMode::Vertical => 0x02,
322 ImageFlipMode::Both => 0x03,
323 };
324 max_param_size = 1;
325 category = CommandCategory::Custom;
326}
327
328impl ImageFlipCombinedCommand {
329 pub fn new(mode: ImageFlipMode) -> Self {
331 Self { mode }
332 }
333}
334
335#[derive(Debug, Copy, Clone)]
341pub struct PictureEffectCommand {
342 pub mode: PictureEffectMode,
344}
345
346impl ViscaCommand for PictureEffectCommand {
347 const MAX_SIZE: usize = 6;
348 const TIMEOUT_CATEGORY: CommandCategory = CommandCategory::Quick;
349
350 fn write_into(
351 &self,
352 camera_id: crate::camera_id::CameraId,
353 buffer: &mut [u8],
354 ) -> Result<usize, Error> {
355 use crate::command::bytes::ConstCommandBuilder;
356
357 let effect = match self.mode {
358 PictureEffectMode::Off => 0x00,
359 PictureEffectMode::BlackAndWhite => 0x04,
360 PictureEffectMode::Unknown(value) => value,
361 PictureEffectMode::Negative
362 | PictureEffectMode::Sepia
363 | PictureEffectMode::Sketch
364 | PictureEffectMode::Emboss
365 | PictureEffectMode::Mosaic => {
366 return Err(Error::InvalidParameter {
367 parameter: "mode",
368 value: Cow::Owned(format!("{:?}", self.mode)),
369 reason: Cow::Borrowed(
370 "picture effect mode is not validated for built-in VISCA profiles; use Unknown(value) for model-specific raw values",
371 ),
372 });
373 }
374 };
375
376 let mut builder = ConstCommandBuilder::<6>::new();
377 builder.push_mut(camera_id.to_address_byte());
378 builder.append_mut(&[0x01, 0x04, 0x63]);
379 builder.push_mut(effect);
380 builder.terminate().build_into(buffer)
381 }
382}
383
384#[cfg(test)]
385#[allow(
386 clippy::unwrap_used,
387 clippy::uninlined_format_args,
388 clippy::panic,
389 clippy::match_wildcard_for_single_variants
390)]
391mod tests {
392 use super::*;
393 use crate::{
394 command::{bytes::VISCA_TERMINATOR, encode::ViscaCommand},
395 macros::test_utils::visca_test,
396 timeout::{CommandCategory, CommandTimeout},
397 };
398
399 visca_test!(
400 BacklightCommand,
401 test_backlight_on,
402 BacklightCommand::new(true),
403 &[0x81, 0x01, 0x04, 0x33, 0x02, VISCA_TERMINATOR]
404 );
405
406 visca_test!(
407 BacklightCommand,
408 test_backlight_off,
409 BacklightCommand::new(false),
410 &[0x81, 0x01, 0x04, 0x33, 0x03, VISCA_TERMINATOR]
411 );
412
413 #[test]
414 fn test_backlight_command_properties() {
415 let cmd = BacklightCommand::new(true);
416 assert!(cmd.behavior().command_kind() == crate::command::CommandKind::Command);
417 assert!(matches!(cmd.timeout_class(), CommandCategory::Quick));
418 }
419
420 visca_test!(
421 NoiseReduction2D,
422 test_noise_reduction_2d_off,
423 NoiseReduction2D::off(),
424 &[0x81, 0x01, 0x04, 0x53, 0x00, VISCA_TERMINATOR]
425 );
426
427 visca_test!(
428 NoiseReduction2D,
429 test_noise_reduction_2d_level_1,
430 NoiseReduction2D::with_level(NoiseReduction2DLevel::new(1).unwrap()),
431 &[0x81, 0x01, 0x04, 0x53, 0x01, VISCA_TERMINATOR]
432 );
433
434 visca_test!(
435 NoiseReduction2D,
436 test_noise_reduction_2d_level_3,
437 NoiseReduction2D::with_level(NoiseReduction2DLevel::new(3).unwrap()),
438 &[0x81, 0x01, 0x04, 0x53, 0x03, VISCA_TERMINATOR]
439 );
440
441 visca_test!(
442 NoiseReduction2D,
443 test_noise_reduction_2d_level_5,
444 NoiseReduction2D::with_level(NoiseReduction2DLevel::new(5).unwrap()),
445 &[0x81, 0x01, 0x04, 0x53, 0x05, VISCA_TERMINATOR]
446 );
447
448 #[test]
449 fn test_noise_reduction_2d_properties() {
450 let cmd = NoiseReduction2D::off();
451 assert!(cmd.behavior().command_kind() == crate::command::CommandKind::Command);
452 assert!(matches!(cmd.timeout_class(), CommandCategory::Custom));
453 }
454
455 visca_test!(
456 NoiseReduction3D,
457 test_noise_reduction_3d_off,
458 NoiseReduction3D::off(),
459 &[0x81, 0x01, 0x04, 0x54, 0x00, VISCA_TERMINATOR]
460 );
461
462 visca_test!(
463 NoiseReduction3D,
464 test_noise_reduction_3d_level_1,
465 NoiseReduction3D::with_level(NoiseReduction3DLevel::new(1).unwrap()),
466 &[0x81, 0x01, 0x04, 0x54, 0x01, VISCA_TERMINATOR]
467 );
468
469 visca_test!(
470 NoiseReduction3D,
471 test_noise_reduction_3d_level_4,
472 NoiseReduction3D::with_level(NoiseReduction3DLevel::new(4).unwrap()),
473 &[0x81, 0x01, 0x04, 0x54, 0x04, VISCA_TERMINATOR]
474 );
475
476 visca_test!(
477 NoiseReduction3D,
478 test_noise_reduction_3d_level_8,
479 NoiseReduction3D::with_level(NoiseReduction3DLevel::new(8).unwrap()),
480 &[0x81, 0x01, 0x04, 0x54, 0x08, VISCA_TERMINATOR]
481 );
482
483 #[test]
484 fn test_noise_reduction_3d_properties() {
485 let cmd = NoiseReduction3D::off();
486 assert!(cmd.behavior().command_kind() == crate::command::CommandKind::Command);
487 assert!(matches!(cmd.timeout_class(), CommandCategory::Custom));
488 }
489
490 visca_test!(
491 ImageFlipCombinedCommand,
492 test_image_flip_off,
493 ImageFlipCombinedCommand::new(ImageFlipMode::Off),
494 &[0x81, 0x01, 0x04, 0xA4, 0x00, VISCA_TERMINATOR]
495 );
496
497 visca_test!(
498 ImageFlipCombinedCommand,
499 test_image_flip_horizontal,
500 ImageFlipCombinedCommand::new(ImageFlipMode::Horizontal),
501 &[0x81, 0x01, 0x04, 0xA4, 0x01, VISCA_TERMINATOR]
502 );
503
504 visca_test!(
505 ImageFlipCombinedCommand,
506 test_image_flip_vertical,
507 ImageFlipCombinedCommand::new(ImageFlipMode::Vertical),
508 &[0x81, 0x01, 0x04, 0xA4, 0x02, VISCA_TERMINATOR]
509 );
510
511 visca_test!(
512 ImageFlipCombinedCommand,
513 test_image_flip_both,
514 ImageFlipCombinedCommand::new(ImageFlipMode::Both),
515 &[0x81, 0x01, 0x04, 0xA4, 0x03, VISCA_TERMINATOR]
516 );
517
518 #[test]
519 fn test_image_flip_properties() {
520 let cmd = ImageFlipCombinedCommand::new(ImageFlipMode::Off);
521 assert!(cmd.behavior().command_kind() == crate::command::CommandKind::Command);
522 assert!(matches!(cmd.timeout_class(), CommandCategory::Custom));
523 }
524
525 #[test]
526 fn test_command_traits() {
527 let cmds: Vec<Box<dyn std::fmt::Debug>> = vec![
529 Box::new(BacklightCommand::new(true)),
530 Box::new(NoiseReduction2D::off()),
531 Box::new(NoiseReduction3D::off()),
532 Box::new(ImageFlipCombinedCommand::new(ImageFlipMode::Off)),
533 ];
534
535 for cmd in cmds {
536 let _ = format!("{cmd:?}");
537 }
538
539 let backlight_cmd1 = BacklightCommand::new(true);
541 let backlight_cmd2 = backlight_cmd1;
542 assert_eq!(
544 backlight_cmd1
545 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
546 .map(|b| b.to_vec())
547 .unwrap(),
548 backlight_cmd2
549 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
550 .map(|b| b.to_vec())
551 .unwrap()
552 );
553
554 let flip_cmd1 = ImageFlipCombinedCommand::new(ImageFlipMode::Horizontal);
555 let flip_cmd2 = flip_cmd1;
556 assert_eq!(
558 flip_cmd2
559 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
560 .map(|b| b.to_vec())
561 .unwrap(),
562 vec![0x81, 0x01, 0x04, 0xA4, 0x01, VISCA_TERMINATOR]
563 );
564 }
565
566 #[test]
567 fn test_response_type_none() {
568 assert!(
570 BacklightCommand::new(true).behavior().command_kind()
571 == crate::command::CommandKind::Command
572 );
573 assert!(
574 NoiseReduction2D::off().behavior().command_kind()
575 == crate::command::CommandKind::Command
576 );
577 assert!(
578 NoiseReduction3D::off().behavior().command_kind()
579 == crate::command::CommandKind::Command
580 );
581 assert!(
582 ImageFlipCombinedCommand::new(ImageFlipMode::Off)
583 .behavior()
584 .command_kind()
585 == crate::command::CommandKind::Command
586 );
587 }
588
589 #[test]
590 fn test_image_flip_mode_debug() {
591 let mode = ImageFlipMode::Off;
592 assert!(format!("{mode:?}").contains("Off"));
593 let mode = ImageFlipMode::Horizontal;
594 assert!(format!("{mode:?}").contains("Horizontal"));
595 let mode = ImageFlipMode::Vertical;
596 assert!(format!("{mode:?}").contains("Vertical"));
597 let mode = ImageFlipMode::Both;
598 assert!(format!("{mode:?}").contains("Both"));
599 }
600
601 #[test]
602 fn test_image_flip_mode_clone() {
603 let mode1 = ImageFlipMode::Horizontal;
604 let mode2 = mode1;
605 match (mode1, mode2) {
606 (ImageFlipMode::Horizontal, ImageFlipMode::Horizontal) => {}
607 _ => panic!("Copy didn't preserve mode"),
608 }
609 }
610
611 #[test]
612 fn test_command_consistency() {
613 let cmd1 = BacklightCommand::new(true);
615 let cmd2 = BacklightCommand::new(true);
616 assert_eq!(
617 cmd1.to_bytes(crate::camera_id::CameraId::CAMERA_1)
618 .map(|b| b.to_vec())
619 .unwrap(),
620 cmd2.to_bytes(crate::camera_id::CameraId::CAMERA_1)
621 .map(|b| b.to_vec())
622 .unwrap()
623 );
624
625 let level = NoiseReduction2DLevel::new(3).unwrap();
626 let cmd1 = NoiseReduction2D::with_level(level);
627 let cmd2 = NoiseReduction2D::with_level(level);
628 assert_eq!(
629 cmd1.to_bytes(crate::camera_id::CameraId::CAMERA_1)
630 .map(|b| b.to_vec())
631 .unwrap(),
632 cmd2.to_bytes(crate::camera_id::CameraId::CAMERA_1)
633 .map(|b| b.to_vec())
634 .unwrap()
635 );
636 }
637
638 #[test]
639 fn test_noise_reduction_2d_struct_creation() {
640 let off_cmd = NoiseReduction2D::off();
642 assert!(off_cmd.level.is_none());
643
644 let level = NoiseReduction2DLevel::new(3).unwrap();
646 let level_cmd = NoiseReduction2D::with_level(level);
647 assert_eq!(level_cmd.level.unwrap().value(), 3);
648 }
649
650 #[test]
651 fn test_noise_reduction_3d_struct_creation() {
652 let off_cmd = NoiseReduction3D::off();
654 assert!(off_cmd.level.is_none());
655
656 let level = NoiseReduction3DLevel::new(5).unwrap();
658 let level_cmd = NoiseReduction3D::with_level(level);
659 assert_eq!(level_cmd.level.unwrap().value(), 5);
660 }
661
662 #[test]
663 fn test_command_categories() {
664 let cmd = BacklightCommand::new(true);
666 assert!(matches!(cmd.timeout_class(), CommandCategory::Quick));
667
668 let cmd = NoiseReduction2D::off();
670 assert!(matches!(cmd.timeout_class(), CommandCategory::Custom));
671
672 let cmd = NoiseReduction3D::off();
673 assert!(matches!(cmd.timeout_class(), CommandCategory::Custom));
674
675 let cmd = ImageFlipCombinedCommand::new(ImageFlipMode::Off);
676 assert!(matches!(cmd.timeout_class(), CommandCategory::Custom));
677 }
678
679 #[test]
680 fn test_backlight_command_debug() {
681 let cmd = BacklightCommand::new(true);
682 let debug_str = format!("{cmd:?}");
683 assert!(debug_str.contains("BacklightCommand"));
684 assert!(debug_str.contains("enabled"));
686
687 let cmd = BacklightCommand::new(false);
688 let debug_str = format!("{cmd:?}");
689 assert!(debug_str.contains("BacklightCommand"));
690 }
691
692 visca_test!(
693 PictureEffectCommand,
694 test_picture_effect_off,
695 PictureEffectCommand {
696 mode: PictureEffectMode::Off
697 },
698 &[0x81, 0x01, 0x04, 0x63, 0x00, VISCA_TERMINATOR]
699 );
700
701 visca_test!(
702 PictureEffectCommand,
703 test_picture_effect_black_white,
704 PictureEffectCommand {
705 mode: PictureEffectMode::BlackAndWhite
706 },
707 &[0x81, 0x01, 0x04, 0x63, 0x04, VISCA_TERMINATOR]
708 );
709
710 visca_test!(
711 PictureEffectCommand,
712 test_picture_effect_unknown_raw_value,
713 PictureEffectCommand {
714 mode: PictureEffectMode::Unknown(0x05)
715 },
716 &[0x81, 0x01, 0x04, 0x63, 0x05, VISCA_TERMINATOR]
717 );
718
719 #[test]
720 fn test_picture_effect_rejects_unvalidated_named_modes() {
721 for mode in [
722 PictureEffectMode::Negative,
723 PictureEffectMode::Sepia,
724 PictureEffectMode::Sketch,
725 PictureEffectMode::Emboss,
726 PictureEffectMode::Mosaic,
727 ] {
728 let cmd = PictureEffectCommand { mode };
729 let mut buffer = [0; PictureEffectCommand::MAX_SIZE];
730 let error = match cmd.write_into(crate::camera_id::CameraId::default(), &mut buffer) {
731 Err(error) => error,
732 Ok(size) => panic!("unvalidated named picture effect encoded {size} bytes"),
733 };
734 assert!(matches!(
735 error,
736 Error::InvalidParameter {
737 parameter: "mode",
738 ..
739 }
740 ));
741 }
742 }
743
744 #[test]
745 fn test_picture_effect_properties() {
746 let cmd = PictureEffectCommand {
747 mode: PictureEffectMode::Off,
748 };
749 assert!(cmd.behavior().command_kind() == crate::command::CommandKind::Command);
750 assert!(matches!(cmd.timeout_class(), CommandCategory::Quick));
751 }
752
753 visca_test!(
754 Sharpness,
755 test_sharpness_mode_auto,
756 Sharpness::Mode(SharpnessMode::Auto),
757 &[0x81, 0x01, 0x04, 0x05, 0x02, VISCA_TERMINATOR]
758 );
759
760 visca_test!(
761 Sharpness,
762 test_sharpness_mode_manual,
763 Sharpness::Mode(SharpnessMode::Manual),
764 &[0x81, 0x01, 0x04, 0x05, 0x03, VISCA_TERMINATOR]
765 );
766
767 #[test]
768 fn test_sharpness_properties() {
769 let cmd = Sharpness::Mode(SharpnessMode::Auto);
770 assert!(cmd.behavior().command_kind() == crate::command::CommandKind::Command);
771 assert!(matches!(cmd.timeout_class(), CommandCategory::Custom));
772 }
773
774 visca_test!(
775 Sharpness,
776 test_sharpness_reset,
777 Sharpness::Reset,
778 &[0x81, 0x01, 0x04, 0x02, 0x00, VISCA_TERMINATOR]
779 );
780
781 visca_test!(
782 Sharpness,
783 test_sharpness_up,
784 Sharpness::Up,
785 &[0x81, 0x01, 0x04, 0x02, 0x02, VISCA_TERMINATOR]
786 );
787 visca_test!(
788 Sharpness,
789 test_sharpness_down,
790 Sharpness::Down,
791 &[0x81, 0x01, 0x04, 0x02, 0x03, VISCA_TERMINATOR]
792 );
793 visca_test!(
794 Sharpness,
795 test_sharpness_level_0,
796 Sharpness::SetLevel { value: 0 },
797 &[
798 0x81,
799 0x01,
800 0x04,
801 0x42,
802 0x00,
803 0x00,
804 0x00,
805 0x00,
806 VISCA_TERMINATOR
807 ]
808 );
809
810 visca_test!(
811 Sharpness,
812 test_sharpness_level_5,
813 Sharpness::SetLevel { value: 5 },
814 &[
815 0x81,
816 0x01,
817 0x04,
818 0x42,
819 0x00,
820 0x00,
821 0x00,
822 0x05,
823 VISCA_TERMINATOR
824 ]
825 );
826 visca_test!(
827 Sharpness,
828 test_sharpness_level_11,
829 Sharpness::SetLevel { value: 11 },
830 &[
831 0x81,
832 0x01,
833 0x04,
834 0x42,
835 0x00,
836 0x00,
837 0x00,
838 0x0B,
839 VISCA_TERMINATOR
840 ]
841 );
842 visca_test!(
843 Sharpness,
844 test_sharpness_level_15,
845 Sharpness::SetLevel { value: 15 },
846 &[
847 0x81,
848 0x01,
849 0x04,
850 0x42,
851 0x00,
852 0x00,
853 0x00,
854 0x0F,
855 VISCA_TERMINATOR
856 ]
857 );
858
859 #[test]
860 fn test_sharpness_valid_values() {
861 use crate::types::SharpnessLevel;
862
863 for value in 0..=15 {
865 let _cmd = Sharpness::SetLevel { value };
866 }
867
868 let result = SharpnessLevel::new(16);
871 assert!(result.is_err());
872 }
873
874 visca_test!(
875 Luminance,
876 test_luminance_level_0,
877 Luminance::new(LuminanceLevel::new(0).unwrap()),
878 &[
879 0x81,
880 0x01,
881 0x04,
882 0xA1,
883 0x00,
884 0x00,
885 0x00,
886 0x00,
887 VISCA_TERMINATOR
888 ]
889 );
890
891 visca_test!(
892 Luminance,
893 test_luminance_level_7,
894 Luminance::new(LuminanceLevel::new(7).unwrap()),
895 &[
896 0x81,
897 0x01,
898 0x04,
899 0xA1,
900 0x00,
901 0x00,
902 0x00,
903 0x07,
904 VISCA_TERMINATOR
905 ]
906 );
907
908 visca_test!(
909 Luminance,
910 test_luminance_level_14,
911 Luminance::new(LuminanceLevel::new(14).unwrap()),
912 &[
913 0x81,
914 0x01,
915 0x04,
916 0xA1,
917 0x00,
918 0x00,
919 0x00,
920 0x0E,
921 VISCA_TERMINATOR
922 ]
923 );
924
925 #[test]
926 fn test_luminance_properties() {
927 let cmd = Luminance::new(LuminanceLevel::new(7).unwrap());
928 assert!(cmd.behavior().command_kind() == crate::command::CommandKind::Command);
929 assert!(matches!(cmd.timeout_class(), CommandCategory::Quick));
930 }
931
932 #[test]
933 fn test_luminance_valid_values() {
934 for value in 0..=14 {
936 let level = LuminanceLevel::new(value)
937 .unwrap_or_else(|e| panic!("Test assertion failed: {e:?}"));
938 let _cmd = Luminance::new(level);
939 }
940 }
941
942 visca_test!(
943 Contrast,
944 test_contrast_level_0,
945 Contrast::new(ContrastLevel::new(0).unwrap()),
946 &[
947 0x81,
948 0x01,
949 0x04,
950 0xA2,
951 0x00,
952 0x00,
953 0x00,
954 0x00,
955 VISCA_TERMINATOR
956 ]
957 );
958
959 visca_test!(
960 Contrast,
961 test_contrast_level_7,
962 Contrast::new(ContrastLevel::new(7).unwrap()),
963 &[
964 0x81,
965 0x01,
966 0x04,
967 0xA2,
968 0x00,
969 0x00,
970 0x00,
971 0x07,
972 VISCA_TERMINATOR
973 ]
974 );
975
976 visca_test!(
977 Contrast,
978 test_contrast_level_14,
979 Contrast::new(ContrastLevel::new(14).unwrap()),
980 &[
981 0x81,
982 0x01,
983 0x04,
984 0xA2,
985 0x00,
986 0x00,
987 0x00,
988 0x0E,
989 VISCA_TERMINATOR
990 ]
991 );
992
993 #[test]
994 fn test_contrast_properties() {
995 let cmd = Contrast::new(ContrastLevel::new(7).unwrap());
996 assert!(cmd.behavior().command_kind() == crate::command::CommandKind::Command);
997 assert!(matches!(cmd.timeout_class(), CommandCategory::Quick));
998 }
999
1000 #[test]
1001 fn test_contrast_valid_values() {
1002 for value in 0..=14 {
1004 let level = ContrastLevel::new(value)
1005 .unwrap_or_else(|e| panic!("Test assertion failed: {e:?}"));
1006 let _cmd = Contrast::new(level);
1007 }
1008 }
1009
1010 visca_test!(
1011 GammaCommand,
1012 test_gamma_level_0,
1013 GammaCommand::new(GammaLevel::new(0).unwrap()),
1014 &[0x81, 0x01, 0x04, 0x5B, 0x00, VISCA_TERMINATOR]
1015 );
1016
1017 visca_test!(
1018 GammaCommand,
1019 test_gamma_level_2,
1020 GammaCommand::new(GammaLevel::new(2).unwrap()),
1021 &[0x81, 0x01, 0x04, 0x5B, 0x02, VISCA_TERMINATOR]
1022 );
1023
1024 visca_test!(
1025 GammaCommand,
1026 test_gamma_level_4,
1027 GammaCommand::new(GammaLevel::new(4).unwrap()),
1028 &[0x81, 0x01, 0x04, 0x5B, 0x04, VISCA_TERMINATOR]
1029 );
1030
1031 #[test]
1032 fn test_gamma_properties() {
1033 let cmd = GammaCommand::new(GammaLevel::new(0).unwrap());
1034 assert!(cmd.behavior().command_kind() == crate::command::CommandKind::Command);
1035 assert!(matches!(cmd.timeout_class(), CommandCategory::Custom));
1036 }
1037
1038 #[test]
1039 fn test_gamma_valid_values() {
1040 for value in 0..=4 {
1041 let level =
1042 GammaLevel::new(value).unwrap_or_else(|e| panic!("Test assertion failed: {e:?}"));
1043 let _cmd = GammaCommand::new(level);
1044 }
1045 }
1046
1047 #[test]
1048 fn test_sharpness_mode_equality() {
1049 assert_eq!(SharpnessMode::Auto, SharpnessMode::Auto);
1050 assert_eq!(SharpnessMode::Manual, SharpnessMode::Manual);
1051 assert_ne!(SharpnessMode::Auto, SharpnessMode::Manual);
1052 }
1053
1054 #[test]
1055 fn test_additional_command_traits() {
1056 let cmds: Vec<Box<dyn std::fmt::Debug>> = vec![
1058 Box::new(Sharpness::Reset),
1059 Box::new(Sharpness::Mode(SharpnessMode::Auto)),
1060 Box::new(Sharpness::SetLevel { value: 5 }),
1061 Box::new(Luminance::new(
1062 LuminanceLevel::new(7).unwrap_or_else(|e| panic!("Test assertion failed: {e:?}")),
1063 )),
1064 Box::new(Contrast::new(
1065 ContrastLevel::new(7).unwrap_or_else(|e| panic!("Test assertion failed: {e:?}")),
1066 )),
1067 Box::new(GammaCommand::new(
1068 GammaLevel::new(2).unwrap_or_else(|e| panic!("Test assertion failed: {e:?}")),
1069 )),
1070 ];
1071
1072 for cmd in cmds {
1073 let _ = format!("{cmd:?}");
1074 }
1075
1076 let sharp_cmd1 = Sharpness::SetLevel { value: 5 };
1078 let sharp_cmd2 = sharp_cmd1;
1079 match (sharp_cmd1, sharp_cmd2) {
1080 (Sharpness::SetLevel { value: v1 }, Sharpness::SetLevel { value: v2 }) => {
1081 assert_eq!(v1, v2);
1082 }
1083 _ => panic!("Clone didn't preserve variant"),
1084 }
1085 }
1086
1087 #[test]
1088 fn test_edge_cases_extended() {
1089 let cmd = Sharpness::SetLevel { value: 0 };
1091 assert!(cmd
1092 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
1093 .map(|b| b.to_vec())
1094 .is_ok());
1095
1096 let cmd = Sharpness::SetLevel { value: 15 };
1097 assert!(cmd
1098 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
1099 .map(|b| b.to_vec())
1100 .is_ok());
1101
1102 let level =
1104 LuminanceLevel::new(0).unwrap_or_else(|e| panic!("Test assertion failed: {e:?}"));
1105 let cmd = Luminance { value: level };
1106 assert!(cmd
1107 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
1108 .map(|b| b.to_vec())
1109 .is_ok());
1110
1111 let level =
1112 LuminanceLevel::new(14).unwrap_or_else(|e| panic!("Test assertion failed: {e:?}"));
1113 let cmd = Luminance { value: level };
1114 assert!(cmd
1115 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
1116 .map(|b| b.to_vec())
1117 .is_ok());
1118
1119 let level =
1121 ContrastLevel::new(0).unwrap_or_else(|e| panic!("Test assertion failed: {e:?}"));
1122 let cmd = Contrast { value: level };
1123 assert!(cmd
1124 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
1125 .map(|b| b.to_vec())
1126 .is_ok());
1127
1128 let level =
1129 ContrastLevel::new(14).unwrap_or_else(|e| panic!("Test assertion failed: {e:?}"));
1130 let cmd = Contrast { value: level };
1131 assert!(cmd
1132 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
1133 .map(|b| b.to_vec())
1134 .is_ok());
1135 }
1136
1137 #[test]
1138 fn test_nibble_encoding_sharpness() {
1139 let cmd = Sharpness::SetLevel { value: 0x0B };
1141 let bytes = cmd
1142 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
1143 .map(|b| b.to_vec())
1144 .unwrap_or_else(|e| panic!("Test assertion failed: {e:?}"));
1145 assert_eq!(bytes[6], 0x00); assert_eq!(bytes[7], 0x0B); let cmd = Sharpness::SetLevel { value: 0x05 };
1149 let bytes = cmd
1150 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
1151 .map(|b| b.to_vec())
1152 .unwrap_or_else(|e| panic!("Test assertion failed: {e:?}"));
1153 assert_eq!(bytes[6], 0x00); assert_eq!(bytes[7], 0x05); let cmd = Sharpness::SetLevel { value: 0x0F };
1157 let bytes = cmd
1158 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
1159 .map(|b| b.to_vec())
1160 .unwrap_or_else(|e| panic!("Test assertion failed: {e:?}"));
1161 assert_eq!(bytes[6], 0x00);
1162 assert_eq!(bytes[7], 0x0F);
1163 }
1164
1165 #[test]
1166 fn test_response_type_none_extended() {
1167 assert!(Sharpness::Reset.behavior().command_kind() == crate::command::CommandKind::Command);
1169 assert!(
1170 Sharpness::Mode(SharpnessMode::Auto)
1171 .behavior()
1172 .command_kind()
1173 == crate::command::CommandKind::Command
1174 );
1175 assert!(Sharpness::Up.behavior().command_kind() == crate::command::CommandKind::Command);
1176 assert!(Sharpness::Down.behavior().command_kind() == crate::command::CommandKind::Command);
1177 assert!(
1178 Sharpness::SetLevel { value: 5 }.behavior().command_kind()
1179 == crate::command::CommandKind::Command
1180 );
1181 assert!(
1182 Luminance::new(
1183 LuminanceLevel::new(7).unwrap_or_else(|e| panic!("Test assertion failed: {e:?}"))
1184 )
1185 .behavior()
1186 .command_kind()
1187 == crate::command::CommandKind::Command
1188 );
1189 assert!(
1190 Contrast::new(
1191 ContrastLevel::new(7).unwrap_or_else(|e| panic!("Test assertion failed: {e:?}"))
1192 )
1193 .behavior()
1194 .command_kind()
1195 == crate::command::CommandKind::Command
1196 );
1197 assert!(
1198 GammaCommand::new(
1199 GammaLevel::new(2).unwrap_or_else(|e| panic!("Test assertion failed: {e:?}"))
1200 )
1201 .behavior()
1202 .command_kind()
1203 == crate::command::CommandKind::Command
1204 );
1205 }
1206
1207 #[test]
1208 fn test_command_categories_extended() {
1209 let sharpness_cmds = vec![
1211 Sharpness::Reset,
1212 Sharpness::Mode(SharpnessMode::Auto),
1213 Sharpness::Up,
1214 Sharpness::Down,
1215 Sharpness::SetLevel { value: 5 },
1216 ];
1217
1218 for cmd in sharpness_cmds {
1219 assert!(matches!(cmd.timeout_class(), CommandCategory::Custom));
1220 }
1221
1222 let level =
1224 LuminanceLevel::new(7).unwrap_or_else(|e| panic!("Test assertion failed: {e:?}"));
1225 let cmd = Luminance { value: level };
1226 assert!(matches!(cmd.timeout_class(), CommandCategory::Quick));
1227
1228 let level =
1229 ContrastLevel::new(7).unwrap_or_else(|e| panic!("Test assertion failed: {e:?}"));
1230 let cmd = Contrast { value: level };
1231 assert!(matches!(cmd.timeout_class(), CommandCategory::Quick));
1232
1233 let level = GammaLevel::new(2).unwrap_or_else(|e| panic!("Test assertion failed: {e:?}"));
1235 let cmd = GammaCommand { level };
1236 assert!(matches!(cmd.timeout_class(), CommandCategory::Custom));
1237 }
1238}