1use crate::{
7 command::{bytes::ConstCommandBuilder, encode::ViscaCommand},
8 error::Error,
9 timeout::CommandCategory,
10 types::{BlueTuning, HueLevel, RedTuning, SaturationLevel},
11 visca_command,
12};
13
14visca_command! {
15 pub struct OnePushTriggerCommand;
21 bytes = [0x01, 0x04, 0x10, 0x05];
22 category = CommandCategory::Quick;
23}
24
25impl Default for OnePushTriggerCommand {
26 fn default() -> Self {
27 Self::new()
28 }
29}
30
31impl OnePushTriggerCommand {
32 pub fn new() -> Self {
34 OnePushTriggerCommand
35 }
36}
37
38visca_command! {
39 pub struct RedTuningCommand { level: RedTuning };
45 prefix = [0x01, 0x04, 0x43, 0x00, 0x00];
46 param = {
47 #[allow(clippy::cast_sign_loss)]
51 let encoded = (level.value() + 10) as u8;
52 [0x00, encoded]
53 };
54 max_param_size = 2;
55 category = CommandCategory::Quick;
56}
57
58impl RedTuningCommand {
59 pub fn new(level: RedTuning) -> Self {
61 Self { level }
62 }
63}
64
65visca_command! {
66 pub struct BlueTuningCommand { level: BlueTuning };
72 prefix = [0x01, 0x04, 0x44, 0x00, 0x00];
73 param = {
74 #[allow(clippy::cast_sign_loss)]
78 let encoded = (level.value() + 10) as u8;
79 [0x00, encoded]
80 };
81 max_param_size = 2;
82 category = CommandCategory::Quick;
83}
84
85impl BlueTuningCommand {
86 pub fn new(level: BlueTuning) -> Self {
88 Self { level }
89 }
90}
91
92visca_command! {
93 pub struct SaturationCommand {
99 level: SaturationLevel
100 };
101 prefix = [0x01, 0x04, 0x49, 0x00, 0x00, 0x00];
102 param = level.value();
103 max_param_size = 1;
104 category = CommandCategory::Quick;
105}
106
107impl SaturationCommand {
108 pub fn new(level: SaturationLevel) -> Self {
110 Self { level }
111 }
112}
113
114visca_command! {
115 pub struct HueCommand {
121 level: HueLevel
122 };
123 prefix = [0x01, 0x04, 0x4F, 0x00, 0x00, 0x00];
124 param = level.value();
125 max_param_size = 1;
126 category = CommandCategory::Quick;
127}
128
129impl HueCommand {
130 pub fn new(level: HueLevel) -> Self {
132 Self { level }
133 }
134}
135
136#[derive(Debug, Copy, Clone)]
141#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
142#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
143#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
144#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
145pub enum ColorTemperature {
146 Reset,
148 Up,
150 Down,
152 SetTemperature(crate::types::ColorTemp),
157}
158
159impl ViscaCommand for ColorTemperature {
160 const MAX_SIZE: usize = 8;
161 const TIMEOUT_CATEGORY: CommandCategory = CommandCategory::Quick;
162
163 fn write_into(
164 &self,
165 camera_id: crate::camera_id::CameraId,
166 buffer: &mut [u8],
167 ) -> Result<usize, Error> {
168 match self {
169 ColorTemperature::Reset => {
170 let builder = ConstCommandBuilder::<6>::from_prefix(
171 crate::command::bytes::constants::color::TEMPERATURE_PREFIX,
172 )
173 .with_camera_id(camera_id)
174 .push(0x00);
175 builder.terminate().build_into(buffer)
176 }
177 ColorTemperature::Up => {
178 let builder = ConstCommandBuilder::<6>::from_prefix(
179 crate::command::bytes::constants::color::TEMPERATURE_PREFIX,
180 )
181 .with_camera_id(camera_id)
182 .push(0x02);
183 builder.terminate().build_into(buffer)
184 }
185 ColorTemperature::Down => {
186 let builder = ConstCommandBuilder::<6>::from_prefix(
187 crate::command::bytes::constants::color::TEMPERATURE_PREFIX,
188 )
189 .with_camera_id(camera_id)
190 .push(0x03);
191 builder.terminate().build_into(buffer)
192 }
193 ColorTemperature::SetTemperature(temp) => {
194 let builder = ConstCommandBuilder::<7>::from_prefix(
195 crate::command::bytes::constants::color::TEMPERATURE_PREFIX,
196 )
197 .with_camera_id(camera_id)
198 .push_nibble_pair(temp.value());
199 builder.terminate().build_into(buffer)
200 }
201 }
202 }
203}
204
205#[derive(Debug, Copy, Clone)]
210#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
211#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
212#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
213#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
214pub enum RedGain {
215 Reset,
217 Up,
219 Down,
221 SetValue(crate::types::RedChannel),
225}
226
227impl ViscaCommand for RedGain {
228 const MAX_SIZE: usize = 9;
229 const TIMEOUT_CATEGORY: CommandCategory = CommandCategory::Quick;
230
231 fn write_into(
232 &self,
233 camera_id: crate::camera_id::CameraId,
234 buffer: &mut [u8],
235 ) -> Result<usize, Error> {
236 match self {
237 RedGain::Reset => {
238 let mut builder = ConstCommandBuilder::<6>::from_prefix(
239 crate::command::bytes::constants::color::RED_GAIN_CONTROL_PREFIX,
240 );
241 builder = builder.with_camera_id(camera_id).push(0x00);
242 builder.terminate().build_into(buffer)
243 }
244 RedGain::Up => {
245 let mut builder = ConstCommandBuilder::<6>::from_prefix(
246 crate::command::bytes::constants::color::RED_GAIN_CONTROL_PREFIX,
247 );
248 builder = builder.with_camera_id(camera_id).push(0x02);
249 builder.terminate().build_into(buffer)
250 }
251 RedGain::Down => {
252 let mut builder = ConstCommandBuilder::<6>::from_prefix(
253 crate::command::bytes::constants::color::RED_GAIN_CONTROL_PREFIX,
254 );
255 builder = builder.with_camera_id(camera_id).push(0x03);
256 builder.terminate().build_into(buffer)
257 }
258 RedGain::SetValue(value) => {
259 let builder = ConstCommandBuilder::<9>::from_prefix(
261 crate::command::bytes::constants::color::RED_GAIN_DIRECT_PREFIX,
262 )
263 .with_camera_id(camera_id)
264 .push_nibble_pair(u16::from(value.value()));
265 builder.terminate().build_into(buffer)
266 }
267 }
268 }
269}
270
271#[derive(Debug, Copy, Clone)]
276#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
277#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
278#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
279#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
280pub enum BlueGain {
281 Reset,
283 Up,
285 Down,
287 SetValue(crate::types::BlueChannel),
291}
292
293impl ViscaCommand for BlueGain {
294 const MAX_SIZE: usize = 9;
295 const TIMEOUT_CATEGORY: CommandCategory = CommandCategory::Quick;
296
297 fn write_into(
298 &self,
299 camera_id: crate::camera_id::CameraId,
300 buffer: &mut [u8],
301 ) -> Result<usize, Error> {
302 match self {
303 BlueGain::Reset => {
304 let builder = ConstCommandBuilder::<6>::from_prefix(
305 crate::command::bytes::constants::color::BLUE_GAIN_CONTROL_PREFIX,
306 )
307 .with_camera_id(camera_id)
308 .push(0x00);
309 builder.terminate().build_into(buffer)
310 }
311 BlueGain::Up => {
312 let builder = ConstCommandBuilder::<6>::from_prefix(
313 crate::command::bytes::constants::color::BLUE_GAIN_CONTROL_PREFIX,
314 )
315 .with_camera_id(camera_id)
316 .push(0x02);
317 builder.terminate().build_into(buffer)
318 }
319 BlueGain::Down => {
320 let builder = ConstCommandBuilder::<6>::from_prefix(
321 crate::command::bytes::constants::color::BLUE_GAIN_CONTROL_PREFIX,
322 )
323 .with_camera_id(camera_id)
324 .push(0x03);
325 builder.terminate().build_into(buffer)
326 }
327 BlueGain::SetValue(value) => {
328 let builder = ConstCommandBuilder::<9>::from_prefix(
330 crate::command::bytes::constants::color::BLUE_GAIN_DIRECT_PREFIX,
331 )
332 .with_camera_id(camera_id)
333 .push_nibble_pair(u16::from(value.value()));
334 builder.terminate().build_into(buffer)
335 }
336 }
337 }
338}
339
340#[cfg(test)]
341#[allow(
342 clippy::unwrap_used,
343 clippy::cast_sign_loss,
344 clippy::uninlined_format_args
345)]
346mod tests {
347 use super::*;
348 use crate::{
349 command::bytes::VISCA_TERMINATOR, macros::test_utils::visca_test, timeout::CommandTimeout,
350 };
351
352 visca_test!(
353 OnePushTriggerCommand,
354 test_one_push_trigger_command,
355 OnePushTriggerCommand::new(),
356 &[0x81, 0x01, 0x04, 0x10, 0x05, VISCA_TERMINATOR]
357 );
358
359 #[test]
360 fn test_red_tuning_command() {
361 for level in -10..=10 {
363 let tuning = RedTuning::new(level).unwrap();
364 let cmd = RedTuningCommand::new(tuning);
365 let bytes = cmd
366 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
367 .map(|b| b.to_vec())
368 .unwrap();
369 assert_eq!(bytes.len(), 9);
370 assert_eq!(bytes[0..6], [0x81, 0x01, 0x04, 0x43, 0x00, 0x00]);
371 assert_eq!(bytes[6], 0x00);
372 assert_eq!(bytes[7], (level + 10) as u8);
373 assert_eq!(bytes[8], 0xFF);
374 assert!(cmd.behavior().command_kind() == crate::command::CommandKind::Command);
375 assert!(matches!(cmd.timeout_class(), CommandCategory::Quick));
376 }
377
378 assert!(RedTuning::new(-11).is_err());
380 assert!(RedTuning::new(11).is_err());
381 }
382
383 #[test]
384 fn test_blue_tuning_command() {
385 for level in -10..=10 {
387 let tuning = BlueTuning::new(level).unwrap();
388 let cmd = BlueTuningCommand::new(tuning);
389 let bytes = cmd
390 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
391 .map(|b| b.to_vec())
392 .unwrap();
393 assert_eq!(bytes.len(), 9);
394 assert_eq!(bytes[0..6], [0x81, 0x01, 0x04, 0x44, 0x00, 0x00]);
395 assert_eq!(bytes[6], 0x00);
396 assert_eq!(bytes[7], (level + 10) as u8);
397 assert_eq!(bytes[8], 0xFF);
398 assert!(cmd.behavior().command_kind() == crate::command::CommandKind::Command);
399 assert!(matches!(cmd.timeout_class(), CommandCategory::Quick));
400 }
401
402 assert!(BlueTuning::new(-11).is_err());
404 assert!(BlueTuning::new(11).is_err());
405 }
406
407 #[test]
408 fn test_saturation_command() {
409 for level in 0x00..=0x0E {
411 let sat_level = SaturationLevel::new(level).unwrap();
412 let cmd = SaturationCommand::new(sat_level);
413 let bytes = cmd
414 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
415 .map(|b| b.to_vec())
416 .unwrap();
417 assert_eq!(
418 bytes,
419 vec![
420 0x81,
421 0x01,
422 0x04,
423 0x49,
424 0x00,
425 0x00,
426 0x00,
427 level,
428 VISCA_TERMINATOR
429 ]
430 );
431 assert!(cmd.behavior().command_kind() == crate::command::CommandKind::Command);
432 assert!(matches!(cmd.timeout_class(), CommandCategory::Quick));
433 }
434
435 assert!(SaturationLevel::new(0x0F).is_err());
437 }
438
439 #[test]
440 fn test_hue_command() {
441 for level in 0x00..=0x0E {
443 let hue_level = HueLevel::new(level).unwrap();
444 let cmd = HueCommand::new(hue_level);
445 let bytes = cmd
446 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
447 .map(|b| b.to_vec())
448 .unwrap();
449 assert_eq!(
450 bytes,
451 vec![
452 0x81,
453 0x01,
454 0x04,
455 0x4F,
456 0x00,
457 0x00,
458 0x00,
459 level,
460 VISCA_TERMINATOR
461 ]
462 );
463 assert!(cmd.behavior().command_kind() == crate::command::CommandKind::Command);
464 assert!(matches!(cmd.timeout_class(), CommandCategory::Quick));
465 }
466
467 assert!(HueLevel::new(0x0F).is_err());
469 }
470
471 #[test]
472 fn test_color_temperature_command() {
473 let cmd = ColorTemperature::Reset;
475 assert_eq!(
476 cmd.to_bytes(crate::camera_id::CameraId::CAMERA_1)
477 .map(|b| b.to_vec())
478 .unwrap(),
479 vec![0x81, 0x01, 0x04, 0x20, 0x00, VISCA_TERMINATOR]
480 );
481
482 let cmd = ColorTemperature::Up;
484 assert_eq!(
485 cmd.to_bytes(crate::camera_id::CameraId::CAMERA_1)
486 .map(|b| b.to_vec())
487 .unwrap(),
488 vec![0x81, 0x01, 0x04, 0x20, 0x02, VISCA_TERMINATOR]
489 );
490
491 let cmd = ColorTemperature::Down;
493 assert_eq!(
494 cmd.to_bytes(crate::camera_id::CameraId::CAMERA_1)
495 .map(|b| b.to_vec())
496 .unwrap(),
497 vec![0x81, 0x01, 0x04, 0x20, 0x03, VISCA_TERMINATOR]
498 );
499
500 let test_values = vec![0x00, 0x10, 0x20, 0x37];
502 for temp in test_values {
503 let color_temp = crate::types::ColorTemp::new(temp).unwrap();
504 let cmd = ColorTemperature::SetTemperature(color_temp);
505 let bytes = cmd
506 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
507 .map(|b| b.to_vec())
508 .unwrap();
509 assert_eq!(bytes.len(), 7);
510 assert_eq!(bytes[0..4], [0x81, 0x01, 0x04, 0x20]);
511 assert_eq!(bytes[4], ((temp >> 4) & 0x0F) as u8);
512 assert_eq!(bytes[5], (temp & 0x0F) as u8);
513 assert_eq!(bytes[6], 0xFF);
514 }
515
516 assert!(crate::types::ColorTemp::new(0x38).is_err());
518 }
519
520 #[test]
521 fn test_red_gain_command() {
522 let cmd = RedGain::Reset;
524 assert_eq!(
525 cmd.to_bytes(crate::camera_id::CameraId::CAMERA_1)
526 .map(|b| b.to_vec())
527 .unwrap(),
528 vec![0x81, 0x01, 0x04, 0x03, 0x00, VISCA_TERMINATOR]
529 );
530
531 let cmd = RedGain::Up;
533 assert_eq!(
534 cmd.to_bytes(crate::camera_id::CameraId::CAMERA_1)
535 .map(|b| b.to_vec())
536 .unwrap(),
537 vec![0x81, 0x01, 0x04, 0x03, 0x02, VISCA_TERMINATOR]
538 );
539
540 let cmd = RedGain::Down;
542 assert_eq!(
543 cmd.to_bytes(crate::camera_id::CameraId::CAMERA_1)
544 .map(|b| b.to_vec())
545 .unwrap(),
546 vec![0x81, 0x01, 0x04, 0x03, 0x03, VISCA_TERMINATOR]
547 );
548
549 let test_values = vec![0x00, 0x55, 0xAA, VISCA_TERMINATOR];
551 for gain in test_values {
552 let red_gain = crate::types::RedChannel::new(gain).unwrap();
553 let cmd = RedGain::SetValue(red_gain);
554 let bytes = cmd
555 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
556 .map(|b| b.to_vec())
557 .unwrap();
558 assert_eq!(bytes.len(), 9);
559 assert_eq!(bytes[0..5], [0x81, 0x01, 0x04, 0x43, 0x00]);
560 assert_eq!(bytes[5], 0x00);
561 assert_eq!(bytes[6], (gain >> 4) & 0x0F);
562 assert_eq!(bytes[7], gain & 0x0F);
563 assert_eq!(bytes[8], 0xFF);
564 }
565 }
566
567 #[test]
568 fn test_blue_gain_command() {
569 let cmd = BlueGain::Reset;
571 assert_eq!(
572 cmd.to_bytes(crate::camera_id::CameraId::CAMERA_1)
573 .map(|b| b.to_vec())
574 .unwrap(),
575 vec![0x81, 0x01, 0x04, 0x04, 0x00, VISCA_TERMINATOR]
576 );
577
578 let cmd = BlueGain::Up;
580 assert_eq!(
581 cmd.to_bytes(crate::camera_id::CameraId::CAMERA_1)
582 .map(|b| b.to_vec())
583 .unwrap(),
584 vec![0x81, 0x01, 0x04, 0x04, 0x02, VISCA_TERMINATOR]
585 );
586
587 let cmd = BlueGain::Down;
589 assert_eq!(
590 cmd.to_bytes(crate::camera_id::CameraId::CAMERA_1)
591 .map(|b| b.to_vec())
592 .unwrap(),
593 vec![0x81, 0x01, 0x04, 0x04, 0x03, VISCA_TERMINATOR]
594 );
595
596 let test_values = vec![0x00, 0x55, 0xAA, VISCA_TERMINATOR];
598 for gain in test_values {
599 let blue_gain = crate::types::BlueChannel::new(gain).unwrap();
600 let cmd = BlueGain::SetValue(blue_gain);
601 let bytes = cmd
602 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
603 .map(|b| b.to_vec())
604 .unwrap();
605 assert_eq!(bytes.len(), 9);
606 assert_eq!(bytes[0..5], [0x81, 0x01, 0x04, 0x44, 0x00]);
607 assert_eq!(bytes[5], 0x00);
608 assert_eq!(bytes[6], (gain >> 4) & 0x0F);
609 assert_eq!(bytes[7], gain & 0x0F);
610 assert_eq!(bytes[8], 0xFF);
611 }
612 }
613
614 #[test]
615 fn test_command_traits() {
616 let cmds: Vec<Box<dyn std::fmt::Debug>> = vec![
618 Box::new(OnePushTriggerCommand::new()),
619 Box::new(RedTuningCommand::new(RedTuning::NEUTRAL)),
620 Box::new(BlueTuningCommand::new(BlueTuning::NEUTRAL)),
621 Box::new(SaturationCommand::new(SaturationLevel::MIN)),
622 Box::new(HueCommand::new(HueLevel::MIN)),
623 Box::new(ColorTemperature::Reset),
624 Box::new(RedGain::Reset),
625 Box::new(BlueGain::Reset),
626 ];
627
628 for cmd in cmds {
629 let _ = format!("{:?}", cmd);
630 }
631
632 let red_cmd1 = RedTuningCommand::new(RedTuning::new(5).unwrap());
634 let red_cmd2 = red_cmd1;
635 assert_eq!(
636 red_cmd1
637 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
638 .map(|b| b.to_vec())
639 .unwrap(),
640 red_cmd2
641 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
642 .map(|b| b.to_vec())
643 .unwrap()
644 );
645 }
646
647 #[test]
648 fn test_edge_cases() {
649 let red_min = RedTuningCommand::new(RedTuning::new(-10).unwrap());
651 assert!(red_min
652 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
653 .map(|b| b.to_vec())
654 .is_ok());
655 assert_eq!(
656 red_min
657 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
658 .map(|b| b.to_vec())
659 .unwrap()[7],
660 0x00
661 );
662
663 let red_max = RedTuningCommand::new(RedTuning::MAX);
664 assert!(red_max
665 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
666 .map(|b| b.to_vec())
667 .is_ok());
668 assert_eq!(
669 red_max
670 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
671 .map(|b| b.to_vec())
672 .unwrap()[7],
673 0x14
674 );
675
676 let blue_min = BlueTuningCommand::new(BlueTuning::new(-10).unwrap());
677 assert!(blue_min
678 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
679 .map(|b| b.to_vec())
680 .is_ok());
681 assert_eq!(
682 blue_min
683 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
684 .map(|b| b.to_vec())
685 .unwrap()[7],
686 0x00
687 );
688
689 let blue_max = BlueTuningCommand::new(BlueTuning::MAX);
690 assert!(blue_max
691 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
692 .map(|b| b.to_vec())
693 .is_ok());
694 assert_eq!(
695 blue_max
696 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
697 .map(|b| b.to_vec())
698 .unwrap()[7],
699 0x14
700 );
701
702 let sat_min = SaturationCommand::new(SaturationLevel::new(0x00).unwrap());
704 assert!(sat_min
705 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
706 .map(|b| b.to_vec())
707 .is_ok());
708
709 let sat_max = SaturationCommand::new(SaturationLevel::new(0x0E).unwrap());
710 assert!(sat_max
711 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
712 .map(|b| b.to_vec())
713 .is_ok());
714
715 let hue_min = HueCommand::new(HueLevel::new(0x00).unwrap());
716 assert!(hue_min
717 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
718 .map(|b| b.to_vec())
719 .is_ok());
720
721 let hue_max = HueCommand::new(HueLevel::new(0x0E).unwrap());
722 assert!(hue_max
723 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
724 .map(|b| b.to_vec())
725 .is_ok());
726
727 let temp_min =
729 ColorTemperature::SetTemperature(crate::types::ColorTemp::new(0x00).unwrap());
730 assert!(temp_min
731 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
732 .map(|b| b.to_vec())
733 .is_ok());
734
735 let temp_max =
736 ColorTemperature::SetTemperature(crate::types::ColorTemp::new(0x37).unwrap());
737 assert!(temp_max
738 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
739 .map(|b| b.to_vec())
740 .is_ok());
741 }
742
743 #[test]
744 fn test_nibble_encoding() {
745 let cmd = ColorTemperature::SetTemperature(crate::types::ColorTemp::new(0x25).unwrap());
747 let bytes = cmd
748 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
749 .map(|b| b.to_vec())
750 .unwrap();
751 assert_eq!(bytes[4], 0x02);
752 assert_eq!(bytes[5], 0x05);
753
754 let cmd = RedGain::SetValue(crate::types::RedChannel::new(0xAB).unwrap());
755 let bytes = cmd
756 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
757 .map(|b| b.to_vec())
758 .unwrap();
759 assert_eq!(bytes[6], 0x0A);
760 assert_eq!(bytes[7], 0x0B);
761
762 let cmd = BlueGain::SetValue(crate::types::BlueChannel::new(0xF0).unwrap());
763 let bytes = cmd
764 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
765 .map(|b| b.to_vec())
766 .unwrap();
767 assert_eq!(bytes[6], 0x0F);
768 assert_eq!(bytes[7], 0x00);
769 }
770
771 #[test]
772 #[allow(clippy::manual_range_contains)]
773 fn test_tuning_encoding_formula() {
774 for level in -10..=10 {
776 let expected = (level + 10) as u8;
777
778 let red_cmd = RedTuningCommand::new(RedTuning::new(level).unwrap());
779 let red_bytes = red_cmd
780 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
781 .map(|b| b.to_vec())
782 .unwrap();
783 assert_eq!(red_bytes[7], expected);
784
785 let blue_cmd = BlueTuningCommand::new(BlueTuning::new(level).unwrap());
786 let blue_bytes = blue_cmd
787 .to_bytes(crate::camera_id::CameraId::CAMERA_1)
788 .map(|b| b.to_vec())
789 .unwrap();
790 assert_eq!(blue_bytes[7], expected);
791 }
792 }
793}