[Perl] DAQ system for the FAPG.
1
use v5.32.1;
2
use strict;
3
use warnings;
4
5
use Test2::V0;
6
use JSON::PP qw(decode_json);
7
8
use FindBin;
9
use lib "${FindBin::Bin}/../../../lib/";
10
use lib "${FindBin::Bin}/../lib/";
11
12
use FAPG::DAQ::MQTT qw(
13
publish_mqtt
14
publish_status_mqtt
15
mqtt_payload_for_reading
16
mqtt_payload_for_status
17
mqtt_topic_for_reading
18
mqtt_topic_for_status
19
mqtt_broker_endpoint
20
);
21
22
{
23
24
packageLocal::FakeMQTT;
25
26
use v5.32.1;
27
use warnings;
28
29
subnew{
30
my ($class) = @_;
31
return bless { published => [] }, $class;
32
}
33
34
subpublish{
35
my ( $self, $topic, $payload ) = @_;
36
push $self->{}->@*, [ $topic, $payload ];
37
return 1;
38
}
39
40
subpublished{
41
my ($self) = @_;
42
return $self->{};
43
}
44
}
45
46
subtest 'topic shape' => sub{
47
my $topic
48
= mqtt_topic_for_reading(
49
{ probe => 'pH', value => 7.12, unit => 'pH' },
50
node => 'fapg-daq-zero-01', );
51
52
is $topic, 'fapg/daq/ph/fapg-daq-zero-01/reading';
53
54
my $status_topic
55
= mqtt_topic_for_status( { probe => 'pH', status => 'ok' },
56
node => 'fapg-daq-zero-01', );
57
58
is $status_topic, 'fapg/daq/ph/fapg-daq-zero-01/status';
59
};
60
61
subtest 'payload shape' => sub{
62
my $payload = mqtt_payload_for_reading(
63
{ probe => 'pH',
64
value => 7.12,
65
unit => 'pH',
66
raw => '7.12',
67
timestamp => '2026-07-06T10:00:00Z',
68
},
69
node => 'fapg-daq-zero-01',
70
);
71
72
my $decoded = decode_json($payload);
73
74
is $decoded->{}, 'fapg.daq.reading.v1';
75
is $decoded->{}, '2026-07-06T10:00:00Z';
76
is $decoded->{}, 'fapg-daq-zero-01';
77
is $decoded->{}, 'ph';
78
is $decoded->{}, 7.12;
79
is $decoded->{}, 'pH';
80
is $decoded->{}, '7.12';
81
is $decoded->{}, 'fapg-daq-node';
82
};
83
84
subtest 'status payload shape' => sub{
85
my $payload = mqtt_payload_for_status(
86
{ probe => 'pH',
87
status => 'probe_error',
88
error => 'timeout waiting for EZO response',
89
device => '/dev/serial/by-id/usb-FTDI_USB_UART',
90
timestamp => '2026-07-06T10:00:05Z',
91
},
92
node => 'fapg-daq-zero-01',
93
);
94
95
my $decoded = decode_json($payload);
96
97
is $decoded->{}, 'fapg.daq.status.v1';
98
is $decoded->{}, '2026-07-06T10:00:05Z';
99
is $decoded->{}, 'fapg-daq-zero-01';
100
is $decoded->{}, 'ph';
101
is $decoded->{}, 'probe_error';
102
is $decoded->{}, 'timeout waiting for EZO response';
103
is $decoded->{}, '/dev/serial/by-id/usb-FTDI_USB_UART';
104
is $decoded->{}, 'fapg-daq-node';
105
};
106
107
subtest 'hub status shape' => sub{
108
my $topic = mqtt_topic_for_status(
109
{ probe => 'hub',
110
status => 'ok',
111
},
112
node => 'fapg-daq-five-01',
113
);
114
115
is $topic, 'fapg/daq/hub/fapg-daq-five-01/status';
116
117
my $payload = mqtt_payload_for_status(
118
{ probe => 'hub',
119
status => 'ok',
120
message => 'DAQ hub MQTT broker reachable',
121
timestamp => '2026-07-06T10:00:10Z',
122
},
123
node => 'fapg-daq-five-01',
124
source => 'fapg-daq-hub',
125
);
126
127
my $decoded = decode_json($payload);
128
129
is $decoded->{}, 'fapg.daq.status.v1';
130
is $decoded->{}, '2026-07-06T10:00:10Z';
131
is $decoded->{}, 'fapg-daq-five-01';
132
is $decoded->{}, 'hub';
133
is $decoded->{}, 'ok';
134
is $decoded->{}, 'DAQ hub MQTT broker reachable';
135
is $decoded->{}, 'fapg-daq-hub';
136
};
137
138
subtest 'publish uses injected client, so unit tests need no broker' => sub{
139
my $fake = Local::FakeMQTT->new;
140
141
my $result = publish_mqtt(
142
{ probe => 'DO',
143
value => 8.34,
144
unit => 'mg/L',
145
timestamp => '2026-07-06T10:00:00Z',
146
},
147
node => 'fapg-daq-zero-02',
148
client => $fake,
149
);
150
151
is $result->{}, 'fapg/daq/do/fapg-daq-zero-02/reading';
152
is scalar $fake->published->@*, 1;
153
is $fake->published->[0][0], 'fapg/daq/do/fapg-daq-zero-02/reading';
154
155
my $decoded = decode_json( $fake->published->[0][1] );
156
is $decoded->{}, 'do';
157
is $decoded->{}, 8.34;
158
159
my $status = publish_status_mqtt(
160
{ probe => 'DO',
161
status => 'ok',
162
},
163
node => 'fapg-daq-zero-02',
164
client => $fake,
165
);
166
167
is $status->{}, 'fapg/daq/do/fapg-daq-zero-02/status';
168
is scalar $fake->published->@*, 2;
169
is $fake->published->[1][0], 'fapg/daq/do/fapg-daq-zero-02/status';
170
};
171
172
subtest 'broker endpoint defaults to the Pi 5 broker' => sub{
173
local $ENV{};
174
local $ENV{};
175
local $ENV{};
176
local $ENV{};
177
178
is mqtt_broker_endpoint(), 'fapg-daq-five-01:1883';
179
};
180
181
done_testing;
182