View raw

1 use v5.32.1; 2 use strict; 3 use warnings; 4 5 use Test2::V0; 6 use File::Path qw(make_path); 7 use File::Temp qw(tempdir); 8 9 use FindBin; 10 use lib "${FindBin::Bin}/../lib/"; 11 12 use FAPG::DAQ::EZO::USB qw( 13 discover_ezo_usb_device 14 ezo_command 15 read_probe_info 16 detect_probe_type 17 read_once 18 parse_reading 19 normalize_probe_type 20 unit_for_probe_type 21 ); 22 23 { 24 25 package Local::FakeSerial; 26 27 use v5.32.1; 28 use warnings; 29 30 sub new { 31 my ( $class, @responses ) = @_; 32 return bless { 33 responses => [ map { $_ =~ /\r\z/ ? $_ : "$_\r" } @responses ], 34 rx => q{}, 35 writes => [], 36 }, $class; 37 } 38 39 sub write { 40 my ( $self, $bytes ) = @_; 41 push $self->{writes}->@*, $bytes; 42 $self->{rx} .= shift( $self->{responses}->@* ) // q{}; 43 return length $bytes; 44 } 45 46 sub read { 47 my ( $self, $wanted ) = @_; 48 return ( 0, q{} ) if $self->{rx} eq q{}; 49 50 my $chunk = substr $self->{rx}, 0, $wanted, q{}; 51 return ( length($chunk), $chunk ); 52 } 53 54 sub writes { 55 my ($self) = @_; 56 return $self->{writes}; 57 } 58 } 59 60 subtest 'basic command framing' => sub { 61 my $serial = Local::FakeSerial->new('7.12'); 62 63 is ezo_command( $serial, 'R' ), '7.12', 'returns response without CR'; 64 is $serial->writes, ["R\r"], 'writes command with CR terminator'; 65 }; 66 67 subtest 'probe detection from info command' => sub { 68 my $serial = Local::FakeSerial->new('?I,pH,2.15'); 69 70 my $info = read_probe_info($serial); 71 72 is $info->{raw}, '?I,pH,2.15'; 73 is $info->{device}, 'pH'; 74 is $info->{firmware}, '2.15'; 75 is $info->{probe}, 'ph'; 76 is $info->{unit}, 'pH'; 77 78 is detect_probe_type( Local::FakeSerial->new('?I,ORP,2.13') ), 'orp'; 79 }; 80 81 subtest 'single-value reading' => sub { 82 my $serial = Local::FakeSerial->new('8.34'); 83 84 my $reading = read_once( $serial, probe => 'DO' ); 85 86 is $reading->{probe}, 'do'; 87 is $reading->{unit}, 'mg/L'; 88 is $reading->{raw}, '8.34'; 89 is $reading->{value}, 8.34; 90 is $reading->{values}, [8.34]; 91 }; 92 93 subtest 'EC multi-value reading keeps all values but uses first as primary' => 94 sub { 95 my $parsed = parse_reading( '1413,704,0.70,1.001', probe => 'ec' ); 96 97 is $parsed->{value}, 1413; 98 is $parsed->{values}, [ 1413, 704, 0.70, 1.001 ]; 99 }; 100 101 subtest 'normalization and units' => sub { 102 is normalize_probe_type('pH'), 'ph'; 103 is normalize_probe_type('Dissolved Oxygen'), 'do'; 104 is normalize_probe_type('ORP'), 'orp'; 105 is normalize_probe_type('EC'), 'ec'; 106 107 is unit_for_probe_type('ph'), 'pH'; 108 is unit_for_probe_type('orp'), 'mV'; 109 is unit_for_probe_type('ec'), 'uS/cm'; 110 }; 111 112 subtest 'USB device discovery prefers UART by-id devices' => sub { 113 my $tmp = tempdir( CLEANUP => 1 ); 114 my $by_id = "$tmp/serial/by-id"; 115 116 make_path($by_id); 117 118 open my $other, '>', "$by_id/usb-Other_Device" 119 or die "cannot create test device: $!"; 120 close $other; 121 122 open my $uart, '>', "$by_id/usb-FTDI_FT232R_USB_UART_A10" 123 or die "cannot create test device: $!"; 124 close $uart; 125 126 is discover_ezo_usb_device( 127 by_id_dir => $by_id, 128 fallback_patterns => [] 129 ), 130 "$by_id/usb-FTDI_FT232R_USB_UART_A10"; 131 }; 132 133 done_testing; 134