File Coverage

blib/lib/Device/Chip/Sensor.pm
Criterion Covered Total %
statement 83 83 100.0
branch 7 8 87.5
condition 10 11 90.9
subroutine 22 22 100.0
pod 7 9 77.7
total 129 133 96.9


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-2022 -- leonerd@leonerd.org.uk
5              
6 2     2   1470 use v5.26;
  2         6  
7 2     2   12 use Object::Pad 0.66 ':experimental(init_expr)';
  2         23  
  2         10  
8              
9             package Device::Chip::Sensor 0.25;
10              
11 2     2   286 use strict;
  2         4  
  2         44  
12 2     2   9 use warnings;
  2         4  
  2         53  
13              
14 2     2   10 use experimental 'signatures';
  2         4  
  2         10  
15 2     2   204 use Object::Pad ':experimental(mop adjust_params)';
  2         4  
  2         7  
16              
17 2     2   241 use Carp;
  2         4  
  2         823  
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 2     2   15 {
  2         6  
  2         3  
147 2         6 my $caller = caller;
148 2 100       4 declare_into( $caller ) if grep { $_ eq "-declare" } @opts;
  3         24  
149             }
150              
151             sub declare_into ( $caller )
152 1     1 0 2 {
  1         1  
  1         2  
153 1         3 my $classmeta = Object::Pad::MOP::Class->for_class( $caller );
154              
155 1   50     40 my $sensors = $SENSORS_FOR_CLASS{$classmeta->name} //= [];
156              
157 1     1   3 $classmeta->add_method( list_sensors => sub ( $self ) {
  1         8  
  1         2  
158             # TODO: some sort of superclass merge?
159 1         3 return map { $_->bind( $self ) } $sensors->@*;
  4         11  
160 1         12 } );
161              
162 4     4   5 my $declare = sub ( $name, %params ) {
  4         103  
  4         8  
  4         11  
163 4         36 push $sensors->@*, Device::Chip::Sensor->new(
164             name => $name,
165             %params,
166             );
167 1         7 };
168              
169 2     2   14 no strict 'refs';
  2         4  
  2         460  
170 1         3 *{"${caller}::declare_sensor"} = $declare;
  1         5  
171 1         2223 *{"${caller}::declare_sensor_counter"} = sub {
172 1     1   7 $declare->( @_, type => "counter", units => undef, precision => 0 );
173 1         3 };
174             }
175              
176             class Device::Chip::Sensor;
177              
178 2     2   307 use Future::AsyncAwait 0.38;
  2         30  
  2         15  
179              
180             =head1 SENSOR METHODS
181              
182             Each returned sensor object provides the following methods.
183              
184             =head2 name
185              
186             =head2 units
187              
188             =head2 precision
189              
190             $name = $sensor->name;
191              
192             $units = $sensor->units;
193              
194             $prec = $sensor->precision;
195              
196             Metadata fields from the sensor's declaration.
197              
198             =head2 chip
199              
200             $chip = $sensor->chip;
201              
202             The L instance this sensor is a part of.
203              
204             =cut
205              
206             my %TYPES = (
207             gauge => 1,
208             counter => 1,
209             );
210              
211 2     2 1 2021 field $_type :reader :param { "gauge" };
  2         10  
212 1     1 1 5 field $_name :reader :param;
  1         6  
213 1     1 1 3 field $_units :reader :param { undef };
  1         6  
214 1     1 1 4 field $_precision :reader :param { 0 };
  1         6  
215              
216             field $_lbound;
217             field $_ubound;
218              
219             field $_method :param { undef };
220              
221 1     1 1 4 field $_chip :reader :param { undef };
  1         6  
222              
223             ADJUST
224             {
225             $TYPES{$_type} or
226             croak "Unrecognised sensor type '$_type'";
227              
228             $_method //= "read_$_name";
229             }
230              
231             ADJUST :params ( :$sanity_bounds = [] )
232             {
233             ( $_lbound, $_ubound ) = $sanity_bounds->@*;
234             }
235              
236 4         5 method bind ( $chip )
  4         7  
  4         4  
237 4     4 0 7 {
238 4         17 return Device::Chip::Sensor->new(
239             chip => $chip,
240              
241             type => $_type,
242             name => $_name,
243             units => $_units,
244             precision => $_precision,
245             method => $_method,
246             sanity_bounds => [ $_lbound, $_ubound ],
247             );
248             }
249              
250             =head2 read
251              
252             $value = await $sensor->read;
253              
254             Performs an actual read operation on the sensor chip to return the currently
255             measured value.
256              
257             This method always returns a single scalar value, even if the underlying
258             method on the sensor chip returned more than one.
259              
260             If the value obtained from the sensor is outside of the sanity-check bounds
261             then an exception is thrown instead.
262              
263             =cut
264              
265 6         8 async method read ()
  6         9  
266 6         24 {
267 6 50       23 defined( my $value = scalar await $_chip->$_method() )
268             or return undef;
269              
270 6 100 100     400 if( defined $_lbound and $value < $_lbound or
      100        
      100        
271             defined $_ubound and $value > $_ubound ) {
272 2         10 die sprintf "Reading %s is out of range\n", $self->format( $value );
273             }
274              
275 4         28 return $value;
276 6     6 1 3856 }
277              
278             =head2 format
279              
280             $string = $sensor->format( $value );
281              
282             Returns a string by formatting an observed value to the required precision.
283              
284             =cut
285              
286 4         7 method format ( $value )
  4         8  
  4         5  
287 4     4 1 45 {
288 4 100       13 return undef if !defined $value;
289 3         49 return sprintf "%.*f", $_precision, $value;
290             }
291              
292             =head1 AUTHOR
293              
294             Paul Evans
295              
296             =cut
297              
298             0x55AA;