File Coverage

blib/lib/Device/Chip/AS3935.pm
Criterion Covered Total %
statement 96 96 100.0
branch 8 14 57.1
condition 3 6 50.0
subroutine 20 20 100.0
pod 7 10 70.0
total 134 146 91.7


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