use v5.32.1; use strict; use warnings; use Test2::V0; use File::Path qw(make_path); use File::Temp qw(tempdir); use FindBin; use lib "${FindBin::Bin}/../lib/"; use FAPG::DAQ::EZO::USB qw( discover_ezo_usb_device ezo_command read_probe_info detect_probe_type read_once parse_reading normalize_probe_type unit_for_probe_type ); { package Local::FakeSerial; use v5.32.1; use warnings; sub new { my ( $class, @responses ) = @_; return bless { responses => [ map { $_ =~ /\r\z/ ? $_ : "$_\r" } @responses ], rx => q{}, writes => [], }, $class; } sub write { my ( $self, $bytes ) = @_; push $self->{writes}->@*, $bytes; $self->{rx} .= shift( $self->{responses}->@* ) // q{}; return length $bytes; } sub read { my ( $self, $wanted ) = @_; return ( 0, q{} ) if $self->{rx} eq q{}; my $chunk = substr $self->{rx}, 0, $wanted, q{}; return ( length($chunk), $chunk ); } sub writes { my ($self) = @_; return $self->{writes}; } } subtest 'basic command framing' => sub { my $serial = Local::FakeSerial->new('7.12'); is ezo_command( $serial, 'R' ), '7.12', 'returns response without CR'; is $serial->writes, ["R\r"], 'writes command with CR terminator'; }; subtest 'probe detection from info command' => sub { my $serial = Local::FakeSerial->new('?I,pH,2.15'); my $info = read_probe_info($serial); is $info->{raw}, '?I,pH,2.15'; is $info->{device}, 'pH'; is $info->{firmware}, '2.15'; is $info->{probe}, 'ph'; is $info->{unit}, 'pH'; is detect_probe_type( Local::FakeSerial->new('?I,ORP,2.13') ), 'orp'; }; subtest 'single-value reading' => sub { my $serial = Local::FakeSerial->new('8.34'); my $reading = read_once( $serial, probe => 'DO' ); is $reading->{probe}, 'do'; is $reading->{unit}, 'mg/L'; is $reading->{raw}, '8.34'; is $reading->{value}, 8.34; is $reading->{values}, [8.34]; }; subtest 'EC multi-value reading keeps all values but uses first as primary' => sub { my $parsed = parse_reading( '1413,704,0.70,1.001', probe => 'ec' ); is $parsed->{value}, 1413; is $parsed->{values}, [ 1413, 704, 0.70, 1.001 ]; }; subtest 'normalization and units' => sub { is normalize_probe_type('pH'), 'ph'; is normalize_probe_type('Dissolved Oxygen'), 'do'; is normalize_probe_type('ORP'), 'orp'; is normalize_probe_type('EC'), 'ec'; is unit_for_probe_type('ph'), 'pH'; is unit_for_probe_type('orp'), 'mV'; is unit_for_probe_type('ec'), 'uS/cm'; }; subtest 'USB device discovery prefers UART by-id devices' => sub { my $tmp = tempdir( CLEANUP => 1 ); my $by_id = "$tmp/serial/by-id"; make_path($by_id); open my $other, '>', "$by_id/usb-Other_Device" or die "cannot create test device: $!"; close $other; open my $uart, '>', "$by_id/usb-FTDI_FT232R_USB_UART_A10" or die "cannot create test device: $!"; close $uart; is discover_ezo_usb_device( by_id_dir => $by_id, fallback_patterns => [] ), "$by_id/usb-FTDI_FT232R_USB_UART_A10"; }; done_testing;