View raw

1 #!/usr/bin/env perl 2 3 use v5.32.1; 4 use strict; 5 use warnings; 6 7 use FindBin qw($Bin); 8 use lib "$Bin/../../../lib"; 9 use lib "$Bin/../lib"; 10 11 use FAPG::DAQ::EZO::USB qw( 12 discover_ezo_usb_device 13 normalize_probe_type 14 open_ezo_usb 15 read_probe_info 16 read_once 17 ); 18 19 use FAPG::DAQ::MQTT qw(publish_mqtt publish_status_mqtt); 20 use Sys::Hostname qw(hostname); 21 22 =head1 NAME 23 24 fapg-daq - read sensor data and send over MQTT 25 26 =cut 27 28 my $read_interval = positive_number_from_env( READ_INTERVAL => 5 ); 29 my $retry_delay = positive_number_from_env( EZO_RECONNECT_INTERVAL => 5 ); 30 my $expected_probe = expected_probe(); 31 32 my ( $serial, $info, $device, $probe ); 33 34 while (1) { 35 if ( !defined $serial || !defined $info ) { 36 ( $serial, $info ) = eval { 37 $device = serial_device(); 38 connect_probe($device); 39 }; 40 41 if ($@) { 42 my $error = $@; 43 warn "DAQ probe connection error: $error"; 44 publish_probe_status( 45 probe => $probe // $expected_probe, 46 status => 'probe_error', 47 error => $error, 48 device => $device, 49 message => 'Unable to connect to or interrogate EZO probe', 50 ); 51 close_serial($serial); 52 ( $serial, $info, $device, $probe ) = (); 53 sleep $retry_delay; 54 next; 55 } 56 57 $probe = $info->{probe}; 58 } 59 60 my $reading = eval { read_once( $serial, probe => $info->{probe} ) }; 61 62 if ($@) { 63 my $error = $@; 64 warn "DAQ probe read error, reconnecting to $device: $error"; 65 publish_probe_status( 66 probe => $probe // $expected_probe, 67 status => 'probe_error', 68 error => $error, 69 device => $device, 70 message => 'EZO probe did not return a valid reading', 71 ); 72 close_serial($serial); 73 ( $serial, $info, $device, $probe ) = (); 74 sleep $retry_delay; 75 next; 76 } 77 78 say "$reading->{probe}: $reading->{value} $reading->{unit}"; 79 80 eval { publish_mqtt($reading) } or warn "MQTT publish error: $@"; 81 publish_probe_status( 82 probe => $reading->{probe}, 83 status => 'ok', 84 device => $device, 85 message => 'Probe reading valid', 86 ); 87 88 sleep $read_interval; 89 } 90 91 sub connect_probe { 92 my ($device) = @_; 93 94 my $serial = open_ezo_usb( device => $device ); 95 my $info = eval { read_probe_info($serial) }; 96 97 if ($@) { 98 my $error = $@; 99 close_serial($serial); 100 die $error; 101 } 102 103 print <<"EOF"; 104 Device $device 105 Probe $info->{probe} 106 Firmware $info->{firmware} 107 Unit $info->{unit} 108 EOF 109 110 return ( $serial, $info ); 111 } 112 113 sub serial_device { 114 return $ENV{EZO_SERIAL_DEVICE} 115 if defined $ENV{EZO_SERIAL_DEVICE} && $ENV{EZO_SERIAL_DEVICE} ne q{}; 116 117 if ( defined $ENV{SERIAL_DEVICE} && $ENV{SERIAL_DEVICE} ne q{} ) { 118 return $ENV{SERIAL_DEVICE} if -e $ENV{SERIAL_DEVICE}; 119 warn 120 "Configured SERIAL_DEVICE=$ENV{SERIAL_DEVICE} does not exist; attempting USB serial discovery\n"; 121 } 122 123 return discover_ezo_usb_device(); 124 } 125 126 sub expected_probe { 127 for my $name (qw(FAPG_DAQ_PROBE EZO_PROBE PROBE_TYPE)) { 128 next if !defined $ENV{$name} || $ENV{$name} eq q{}; 129 return normalize_probe_type( $ENV{$name} ); 130 } 131 132 my $hostname = hostname(); 133 return $1 if $hostname =~ /(?:\A|-)(ph|do|ec|orp)(?:-|\z)/; 134 135 return undef; 136 } 137 138 sub publish_probe_status { 139 my (%status) = @_; 140 141 if ( !defined $status{probe} || $status{probe} eq q{} ) { 142 warn "DAQ status not published because probe type is unknown\n"; 143 return; 144 } 145 146 eval { publish_status_mqtt( \%status ); 1 } 147 or warn "MQTT status publish error: $@"; 148 } 149 150 sub close_serial { 151 my ($serial) = @_; 152 153 return if !defined $serial || !$serial->can('close'); 154 155 eval { $serial->close; 1 } 156 or warn "DAQ serial close error: $@"; 157 } 158 159 sub positive_number_from_env { 160 my ( $name, $default ) = @_; 161 my $value = $ENV{$name}; 162 163 return $default 164 if !defined $value 165 || $value !~ /\A(?:\d+(?:\.\d*)?|\.\d+)\z/ 166 || $value <= 0; 167 168 return 0 + $value; 169 } 170