File Coverage

blib/lib/Device/Chip/AS3935.pm
Criterion Covered Total %
statement 93 93 100.0
branch 8 14 57.1
condition 3 6 50.0
subroutine 19 19 100.0
pod 7 10 70.0
total 130 142 91.5


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2021 -- leonerd@leonerd.org.uk
5              
6 7     7   791549 use v5.26;
  7         107  
7 7     7   616 use Object::Pad 0.19;
  7         11093  
  7         42  
8              
9             package Device::Chip::AS3935 0.02;
10             class Device::Chip::AS3935
11 1     1   580 extends Device::Chip;
  1         17145  
  1         62  
12              
13 7     7   5881 use Device::Chip::Sensor 0.19 -declare;
  7         15852  
  7         38  
14              
15 7     7   4575 use Data::Bitfield qw( bitfield boolfield enumfield intfield );
  7         16437  
  7         625  
16 7     7   65 use Future::AsyncAwait;
  7         15  
  7         59  
17              
18 7     7   362 use constant PROTOCOL => "I2C";
  7         15  
  7         3809  
19              
20             =encoding UTF-8
21              
22             =head1 NAME
23              
24             C - chip driver for F
25              
26             =head1 SYNOPSIS
27              
28             use Device::Chip::AS3935;
29             use Future::AsyncAwait;
30              
31             my $chip = Device::Chip::AS3935->new;
32             await $chip->mount( Device::Chip::Adapter::...->new );
33              
34             if( ( await $chip->read_int )->{INT_L} ) {
35             printf "Lightning detected %dkm away\n", await $chip->read_distance;
36             }
37              
38             =head1 DESCRIPTION
39              
40             This L subclass provides specific communcation to an F
41             F lightning detector chip attached to a computer via an I²C adapter.
42              
43             The reader is presumed to be familiar with the general operation of this chip;
44             the documentation here will not attempt to explain or define chip-specific
45             concepts or features, only the use of this module to access them.
46              
47             =cut
48              
49             method I2C_options
50 6     6 0 3244 {
51             return (
52 6         42 addr => 0x02,
53             max_bitrate => 400E3,
54             );
55             }
56              
57 1         2 async method initialize_sensors ()
  1         2  
58 1         3 {
59 1         5 await $self->reset;
60              
61 1         9962 await $self->calibrate_rco;
62 1     1 0 346 }
63              
64             =head1 METHODS
65              
66             The following methods documented in an C expression return L
67             instances.
68              
69             =cut
70              
71             =head2 reset
72              
73             await $chip->reset;
74              
75             Sends a reset command to initialise the configuration back to defaults.
76              
77             =cut
78              
79 2         5 async method reset ()
  2         4  
80 2         6 {
81 2         9 $self->protocol->write( pack "C C", 0x3C, 0x96 );
82 2     2 1 262 }
83              
84             =head2 calibrate_rco
85              
86             await $chip->calibrate_rco;
87              
88             Sends a calibrate command to request the chip perform its internal RC
89             oscillator calibration.
90              
91             =cut
92              
93 2         4 async method calibrate_rco ()
  2         4  
94 2         12 {
95 2         12 $self->protocol->write( pack "C C", 0x3D, 0x96 );
96 2     2 1 12113 }
97              
98             =head2 read_config
99              
100             $config = await $chip->read_config;
101              
102             Returns a C reference of the contents of configuration registers using
103             fields named from the data sheet.
104              
105             AFE_GB => 0 .. 31
106             PWD => "active" | "powerdown"
107             NF_LEV => 0 .. 7
108             WDTH => 0 .. 15
109             CL_STAT => bool
110             MIN_NUM_LIGH => 1 | 5 | 9 | 16
111             SREJ => 0 .. 15
112             LCO_FDIV => 16 | 32 | 64 | 128
113             MASK_DIST => bool
114             DISP_LCO => bool
115             DISP_SRCO => bool
116             DISP_TRCO => bool
117             TUN_CAP => 0 .. 15
118              
119             Additionally, the following keys are provided calculated from those, as a
120             convenience.
121              
122             afe => "indoor" | "outdoor"
123             noisefloor => int (in units of µVrms)
124              
125             =head2 change_config
126              
127             await $chip->change_config( %changes );
128              
129             Writes updates to the configuration registers.
130              
131             =cut
132              
133             use constant {
134             # The data sheet doesn't actually give names to registers, so we'll invent
135             # these
136 7         15083 REG_CONFIG0 => 0x00,
137             REG_INT => 0x03,
138             REG_DISTANCE => 0x07,
139             REG_CONFIG8 => 0x08,
140             REG_CALIB_STATUS => 0x3A,
141 7     7   61 };
  7         18  
142              
143             bitfield { format => "bytes-LE" }, CONFIG =>
144             # 0x00
145             AFE_GB => intfield ( 0*8+1, 5 ),
146             PWD => enumfield( 0, qw( active powerdown ) ),
147             # 0x01
148             NF_LEV => intfield ( 1*8+4, 3 ),
149             WDTH => intfield ( 1*8+0, 4 ),
150             # 0x02
151             CL_STAT => boolfield( 2*8+6 ),
152             MIN_NUM_LIGH => enumfield( 2*8+4, qw( 1 5 9 16 ) ),
153             SREJ => intfield ( 2*8+0, 4 ),
154             # 0x03
155             LCO_FDIV => enumfield( 3*8+6, qw( 16 32 64 128 ) ),
156             MASK_DIST => boolfield( 3*8+5 ),
157             # 0x08
158             DISP_LCO => boolfield( 4*8+7 ),
159             DISP_SRCO => boolfield( 4*8+6 ),
160             DISP_TRCO => boolfield( 4*8+5 ),
161             TUN_CAP => intfield ( 4*8+0, 4 ),
162             ;
163              
164             my @NF_MAP_INDOOR = ( 28, 45, 62, 78, 95, 112, 130, 146 );
165             my @NF_MAP_OURDOOR = ( 390, 630, 860, 1100, 1140, 1570, 1800, 2000 );
166              
167             has $_CONFIG;
168              
169 3         5 async method read_config ()
  3         4  
170 3         8 {
171             # TODO: second region too
172 3   66     12 $_CONFIG //= join "",
173             await $self->protocol->write_then_read( ( pack "C", REG_CONFIG0 ), 4 ),
174             await $self->protocol->write_then_read( ( pack "C", REG_CONFIG8 ), 1 );
175              
176 3         10343 my %config = unpack_CONFIG( $_CONFIG );
177              
178 3         473 $config{afe} = $config{AFE_GB};
179 3 50       10 $config{afe} = "indoor" if $config{AFE_GB} == 18;
180 3 50       7 $config{afe} = "outdoor" if $config{AFE_GB} == 14;
181              
182 3         6 my $noisefloor_map;
183 3 50       7 $noisefloor_map = \@NF_MAP_INDOOR if $config{AFE_GB} == 18;
184 3 50       8 $noisefloor_map = \@NF_MAP_OURDOOR if $config{AFE_GB} == 14;
185 3 50       11 $config{noisefloor} = $noisefloor_map->[ $config{NF_LEV} ] if $noisefloor_map;
186              
187 3         26 return \%config;
188 3     3 1 340 }
189              
190 1         3 async method change_config ( %changes )
  1         3  
  1         2  
191 1         3 {
192 1         3 my $config = await $self->read_config;
193              
194 1         50 $config->{$_} = $changes{$_} for keys %changes;
195              
196 1         3 delete $config->{afe};
197 1         2 delete $config->{noisefloor};
198              
199 1         6 my $bytes = pack_CONFIG( %$config );
200              
201 1 50       268 if( $_CONFIG ne $bytes ) {
202 1         4 await $self->protocol->write( pack "C a*", REG_CONFIG0, substr $bytes, 0, 4 );
203 1         1225 await $self->protocol->write( pack "C a*", REG_CONFIG8, substr $bytes, 4, 1 );
204              
205 1         1171 $_CONFIG = $bytes;
206             }
207 1     1 1 4170 }
208              
209             =head2 read_calib_status
210              
211             $status = await $chip->read_calib_status;
212              
213             Returns a 4-element C reference indicating the calibration status:
214              
215             TRCO_CALIB_DONE => bool
216             TRCO_CALIB_NOK => bool
217             SRCO_CALIB_DONE => bool
218             SRCO_CALIB_NOK => bool
219              
220             =cut
221              
222 2         5 async method read_calib_status ()
  2         5  
223 2         8 {
224 2         12 my ( $trco, $srco ) = unpack "C C",
225             await $self->protocol->write_then_read( ( pack "C", REG_CALIB_STATUS ), 2 );
226              
227             return {
228 2         11235 TRCO_CALIB_DONE => !!( $trco & 0x80 ),
229             TRCO_CALIB_NOK => !!( $trco & 0x40 ),
230             SRCO_CALIB_DONE => !!( $srco & 0x80 ),
231             SRCO_CALIB_NOK => !!( $srco & 0x40 ),
232             };
233 2     2 1 1383 }
234              
235             =head2 read_int
236              
237             $ints = await $chip->read_int;
238              
239             Returns a 3-element C reference containing the three interrupt flags:
240              
241             INT_NH => bool
242             INT_D => bool
243             INT_L => bool
244              
245             =cut
246              
247 5         11 async method read_int ()
  5         7  
248 5         12 {
249 5         22 my $int = unpack "C",
250             await $self->protocol->write_then_read( ( pack "C", REG_INT ), 1 );
251 5         15049 $int &= 0x0F;
252              
253             return {
254 5         40 INT_NH => !!( $int & 0x01 ),
255             INT_D => !!( $int & 0x04 ),
256             INT_L => !!( $int & 0x08 ),
257             };
258 5     5 1 2460 }
259              
260             =head2 read_distance
261              
262             $distance = await $chip->read_distance;
263              
264             Returns an integer giving the estimated lightning distance, in km, or C
265             if it is below the detection limit.
266              
267             =cut
268              
269 3         7 async method read_distance ()
  3         6  
270 3         14 {
271 3         21 my $distance = unpack "C",
272             await $self->protocol->write_then_read( ( pack "C", REG_DISTANCE ), 1 );
273 3         13031 $distance &= 0x3F;
274              
275 3 100       16 undef $distance if $distance == 0x3F;
276              
277 3         20 return $distance;
278 3     3 1 6560 }
279              
280             has $_pending_read_int_f;
281              
282             method next_read_int
283 1     1 0 4 {
284 1   33 1   9 return $_pending_read_int_f //= $self->read_int->on_ready( sub { undef $_pending_read_int_f } );
  1         54  
285             }
286              
287             foreach ( [ noise_high => "INT_NH" ], [ disturbance => "INT_D" ], [ strike => "INT_L" ] ) {
288             my ( $name, $intflag ) = @$_;
289              
290             declare_sensor_counter "lightning_${name}_events" =>
291             method => async method {
292             my $ints = await $self->next_read_int;
293             return $ints->{$intflag};
294             };
295             }
296              
297             declare_sensor lightning_distance =>
298             units => "km",
299             precision => 0,
300             method => "read_distance";
301              
302             =head1 AUTHOR
303              
304             Paul Evans
305              
306             =cut
307              
308             0x55AA;