File Coverage

blib/lib/Device/Chip/Sensor.pm
Criterion Covered Total %
statement 83 87 95.4
branch 6 8 75.0
condition 10 11 90.9
subroutine 23 24 95.8
pod 7 9 77.7
total 129 139 92.8


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, 2020-2023 -- leonerd@leonerd.org.uk
5              
6 2     2   1523 use v5.26;
  2         8  
7 2     2   14 use warnings;
  2         4  
  2         172  
8 2     2   15 use Object::Pad 0.800 ':experimental(mop adjust_params)';
  2         17  
  2         94  
9              
10             package Device::Chip::Sensor 0.26;
11              
12 2     2   431 use strict;
  2         4  
  2         62  
13 2     2   10 use warnings;
  2         11  
  2         168  
14              
15 2     2   15 use experimental 'signatures';
  2         9  
  2         13  
16              
17 2     2   317 use Carp;
  2         4  
  2         1609  
18              
19             =head1 NAME
20              
21             C - declarations of sensor readings for C
22              
23             =head1 SYNOPSIS
24              
25             class Device::Chip::MySensorChip
26             extends Device::Chip;
27              
28             use Device::Chip::Sensor -declare;
29              
30             ...
31              
32             declare_sensor voltage =>
33             units => "volts",
34             precision => 3;
35              
36             async method read_voltage () {
37             ...
38             }
39              
40             =head1 DESCRIPTION
41              
42             This package provides some helper methods for describing metadata on
43             L drivers that provide sensor values. The resulting metadata
44             helps to describe the quantities that the sensor chip can measure, and
45             provides a consistent API for accessing them.
46              
47             =cut
48              
49             my %SENSORS_FOR_CLASS;
50              
51             =head1 CHIP METHODS
52              
53             When imported into a C driver class using the C<-declare> option
54             the following methods are added to it.
55              
56             =cut
57              
58             =head2 list_sensors
59              
60             @sensors = $chip->list_sensors;
61              
62             Returns a list of individual sensor objects. Each object represents a single
63             sensor reading that can be measured.
64              
65             =head1 OPTIONAL CHIP METHODS
66              
67             The following methods may also be provided by the chip driver class if
68             required. Callers should check they are implemented (e.g. with C) before
69             attempting to call them.
70              
71             =head2 initialize_sensors
72              
73             await $chip->initialize_sensors;
74              
75             If the chip requires any special configuration changes, initial calibrations,
76             startup delay, or other operations before the sensors are available then this
77             method should perform it. It can presume that the application wishes to
78             interact with the chip primarily via the sensors API, and thus if required it
79             can presume particular settings to make this happen.
80              
81             =head1 SENSOR DECLARATIONS
82              
83             Sensor metadata is provided by the following function.
84              
85             =head2 declare_sensor
86              
87             declare_sensor $name => %params;
88              
89             Declares a new sensor object with the given name and parameters.
90              
91             The following named parameters are recognised:
92              
93             =over 4
94              
95             =item type => STRING
96              
97             Optional. A string specifying what overall type of data is being returned.
98             Normally this is C to indicate a quantity that is measured on every
99             observation. A type of C instead indicates that the value will be an
100             integer giving the total number of times some event has happened - typically
101             used to count interrupt events from chips.
102              
103             A convenience function L exists for making counters.
104              
105             =item units => STRING
106              
107             A string describing the units in which the value is returned. This should be
108             an empty string for purely abstract counting sensors, or else describe the
109             measured quantities in physical units (such as C, C,
110             C, C, ...)
111              
112             =item precision => INT
113              
114             The number of decimal places of floating-point accuracy that values should
115             be printed with. This should be 0 for integer readings.
116              
117             =item method => STRING or CODE
118              
119             Optional string or code reference giving the method on the main chip object to
120             call to obtain a new reading of this sensor's current value. If not provided a
121             default will be created by prefixing C<"read_"> onto the sensor name.
122              
123             =item sanity_bounds => ARRAY[ 2 * NUM ]
124              
125             I
126              
127             Optional bounding values to sanity-test reported readings. If a reading is
128             obtained that is lower than the first value or higher than the second, it is
129             declared to be out of bounds by the L method. Either bound may be set
130             to C to ignore that setting. For example, setting just a lower bound of
131             zero ensures that any negative values that are obtained are considered out of
132             the valid range.
133              
134             =back
135              
136             =head2 declare_sensor_counter
137              
138             declare_sensor_counter $name => %params;
139              
140             Declares a sensor of the C type. This will pass C for the
141             units and 0 for precision.
142              
143             =cut
144              
145             sub import ( @opts )
146 1     1   9 {
  1         3  
  1         2  
147 1         3 my $caller = caller;
148 1 50       2 declare_into( $caller ) if grep { $_ eq "-declare" } @opts;
  2         11  
149             }
150              
151             sub unimport ( @opts )
152 0     0   0 {
  0         0  
  0         0  
153 0         0 croak "This module cannot be unimported";
154             }
155              
156             sub declare_into ( $caller )
157 1     1 0 1 {
  1         3  
  1         1  
158 1         5 my $classmeta = Object::Pad::MOP::Class->for_class( $caller );
159              
160 1   50     70 my $sensors = $SENSORS_FOR_CLASS{$classmeta->name} //= [];
161              
162 1     1   6 $classmeta->add_method( list_sensors => sub ( $self ) {
  1     1   3  
  1         2  
163             # TODO: some sort of superclass merge?
164 1         4 return map { $_->bind( $self ) } $sensors->@*;
  4         11  
165 1         18 } );
166              
167 4     4   295437 my $declare = sub ( $name, %params ) {
  4         10  
  4         16  
  4         7  
168 4         83 push $sensors->@*, Device::Chip::Sensor->new(
169             name => $name,
170             %params,
171             );
172 1         8 };
173              
174 2     2   19 no strict 'refs';
  2         6  
  2         767  
175 1         26 *{"${caller}::declare_sensor"} = $declare;
  1         7  
176 1         3792 *{"${caller}::declare_sensor_counter"} = sub {
177 1     1   9 $declare->( @_, type => "counter", units => undef, precision => 0 );
178 1         7 };
179             }
180              
181             class Device::Chip::Sensor;
182              
183 2     2   272 use Future::AsyncAwait 0.38;
  2         38  
  2         17  
184              
185             =head1 SENSOR METHODS
186              
187             Each returned sensor object provides the following methods.
188              
189             =head2 name
190              
191             =head2 units
192              
193             =head2 precision
194              
195             $name = $sensor->name;
196              
197             $units = $sensor->units;
198              
199             $prec = $sensor->precision;
200              
201             Metadata fields from the sensor's declaration.
202              
203             =head2 chip
204              
205             $chip = $sensor->chip;
206              
207             The L instance this sensor is a part of.
208              
209             =cut
210              
211             my %TYPES = (
212             gauge => 1,
213             counter => 1,
214             );
215              
216 2     2 1 12558 field $_type :reader :param = "gauge";
217 2     1 1 15 field $_name :reader :param;
  1         6  
  1         7  
218             field $_units :reader :param = undef;
219 1     1 1 5 field $_precision :reader :param = 0;
  1     1 1 6  
  1         6  
220              
221 1         6 field $_lbound;
222             field $_ubound;
223              
224             field $_method :param = undef;
225              
226             field $_chip :reader :param = undef;
227              
228 1     1 1 5 ADJUST
  1         47  
229             {
230             $TYPES{$_type} or
231             croak "Unrecognised sensor type '$_type'";
232              
233             $_method //= "read_$_name";
234             }
235              
236             ADJUST :params ( :$sanity_bounds = [] )
237             {
238             ( $_lbound, $_ubound ) = $sanity_bounds->@*;
239             }
240              
241 4     4 0 8 method bind ( $chip )
  4         11  
  4         7  
  4         6  
242             {
243 4         39 return Device::Chip::Sensor->new(
244             chip => $chip,
245              
246             type => $_type,
247             name => $_name,
248             units => $_units,
249             precision => $_precision,
250             method => $_method,
251             sanity_bounds => [ $_lbound, $_ubound ],
252             );
253             }
254              
255             =head2 read
256              
257             $value = await $sensor->read;
258              
259             Performs an actual read operation on the sensor chip to return the currently
260             measured value.
261              
262             This method always returns a single scalar value, even if the underlying
263             method on the sensor chip returned more than one.
264              
265             If the value obtained from the sensor is outside of the sanity-check bounds
266             then an exception is thrown instead.
267              
268             =cut
269              
270 6     6 1 2890 async method read ()
  6         27  
  6         11  
271 6         15 {
272 6 50       29 defined( my $value = scalar await $_chip->$_method() )
273             or return undef;
274              
275 6 100 100     546 if( defined $_lbound and $value < $_lbound or
      100        
      100        
276             defined $_ubound and $value > $_ubound ) {
277 2         9 die sprintf "Reading %s is out of range\n", $self->format( $value );
278             }
279              
280 4         31 return $value;
281             }
282              
283             =head2 format
284              
285             $string = $sensor->format( $value );
286              
287             Returns a string by formatting an observed value to the required precision.
288              
289             =cut
290              
291 4     4 1 54 method format ( $value )
  4         39  
  4         7  
  4         8  
292             {
293 4 100       17 return undef if !defined $value;
294 3         59 return sprintf "%.*f", $_precision, $value;
295             }
296              
297             =head1 AUTHOR
298              
299             Paul Evans
300              
301             =cut
302              
303             0x55AA;