File Coverage

blib/lib/Device/Chip/SCD4x.pm
Criterion Covered Total %
statement 49 55 89.0
branch 3 4 75.0
condition 1 2 50.0
subroutine 13 14 92.8
pod 4 6 66.6
total 70 81 86.4


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, 2024 -- leonerd@leonerd.org.uk
5              
6 5     5   1284577 use v5.26;
  5         22  
7 5     5   38 use warnings;
  5         12  
  5         413  
8 5     5   40 use Object::Pad 0.800;
  5         47  
  5         273  
9              
10 5     5   867 use utf8;
  5         20  
  5         41  
11              
12             package Device::Chip::SCD4x 0.02;
13             class Device::Chip::SCD4x
14 4     4   3029 :isa(Device::Chip::From::Sensirion);
  4         14  
  4         743  
15              
16 5     5   852 use Future::AsyncAwait;
  5         19  
  5         28  
17              
18 5     5   3321 use Device::Chip::Sensor 0.23 -declare;
  5         22907  
  5         101  
19              
20             =encoding UTF-8
21              
22             =head1 NAME
23              
24             C - chip driver for F and F
25              
26             =head1 SYNOPSIS
27              
28             =for highlighter language=perl
29              
30             use Device::Chip::SCD4x;
31             use Future::AsyncAwait;
32              
33             my $chip = Device::Chip::SCD4x->new;
34             await $chip->mount( Device::Chip::Adapter::...->new );
35              
36             await $chip->start_periodic_measurement;
37              
38             while(1) {
39             await Future::IO->sleep(1);
40              
41             my ( $co2, $temp, $humid ) = await $chip->maybe_read_measurement
42             or next;
43              
44             printf "CO2 concentration=%dppm ", $co2;
45             printf "Temperature=%.2fC ", $temp;
46             printf "Humidity=%.2f%%\n", $hum;
47             }
48              
49             =head1 DESCRIPTION
50              
51             This L subclass provides specific communication to a
52             F F or F attached to a computer via an I²C adapter.
53              
54             The reader is presumed to be familiar with the general operation of this chip;
55             the documentation here will not attempt to explain or define chip-specific
56             concepts or features, only the use of this module to access them.
57              
58             =cut
59              
60             =head1 MOUNT PARAMETERS
61              
62             =head2 addr
63              
64             The I²C address of the device. Can be specified in decimal, octal or hex with
65             leading C<0> or C<0x> prefixes.
66              
67             =cut
68              
69 4     4 0 1966 method I2C_options ( %params )
  4         12  
  4         10  
  4         9  
70             {
71 4   50     31 my $addr = delete $params{addr} // 0x62;
72 4 50       23 $addr = oct $addr if $addr =~ m/^0/;
73              
74             return (
75 4         39 addr => $addr,
76             max_bitrate => 400E3,
77             );
78             }
79              
80             =head1 METHODS
81              
82             The following methods documented in an C expression return L
83             instances.
84              
85             =cut
86              
87             =head2 read_config
88              
89             $config = await $chip->read_config;
90              
91             Returns a C reference containing the compensation values from chip
92             config.
93              
94             =for highlighter
95              
96             temperature_offset # in degrees C
97             sensor_altitude # in metres
98             ambient_pressure # in hPa
99              
100             =cut
101              
102             field %config_f;
103              
104             async method read_config
105 2     2 1 2362 {
106             await Future->needs_all(
107             $config_f{temperature_offset} //= $self->_read( 0x2318, 1 ),
108             $config_f{sensor_altitude} //= $self->_read( 0x2322, 1 ),
109 2         20 $config_f{ambient_pressure} //= $self->_read( 0xe000, 1 ),
110             );
111              
112             return {
113             temperature_offset => ( $config_f{temperature_offset}->result * 175 ) / 0xFFFF,
114             sensor_altitude => $config_f{sensor_altitude}->result,
115 2         241 ambient_pressure => $config_f{ambient_pressure}->result * 100,
116             };
117             }
118              
119             =head2 start_periodic_measurement
120              
121             =for highlighter language=perl
122              
123             await $chip->start_periodic_measurement;
124              
125             Starts periodic measurement mode.
126              
127             =cut
128              
129 1     1 1 1770 async method start_periodic_measurement ()
  1         6  
  1         2  
130 1         2 {
131 1         19 await $self->_cmd( 0x21b1 );
132             }
133              
134             =head2 read_measurement
135              
136             ( $co2concentration, $temperature, $humidity ) = await $chip->read_measurement();
137              
138             Returns the latest sensor reading values. Returns a 3-element list, containing
139             the CO₂ concentration in PPM, temperature in degrees C, and humidity in %RH.
140              
141             =cut
142              
143 4     4 1 5530 async method read_measurement ()
  4         24  
  4         8  
144 4         12 {
145 4         31 my @words = await $self->_read( 0xec05, 3 );
146              
147             return (
148             # CO2
149 4         858 $words[0],
150             # Temperature
151             -45 + 175 * ( $words[1] / 0xFFFF ),
152             # Humidity
153             100 * ( $words[2] / 0xFFFF ),
154             );
155             }
156              
157             =head2 maybe_read_measurement
158              
159             ( $co2concentration, $temperature, $humidity ) = await $chip->maybe_read_measurement();
160              
161             If the sensor has a new measurement ready, returns it. Otherwise, returns the
162             last successful measurement reading. After initial startup, this will return
163             an empty list before the first reading is available.
164              
165             =cut
166              
167             field @last_measurement;
168 5     5 1 13 async method maybe_read_measurement ()
  5         15  
  5         37  
169 5         10 {
170             # Ready if the lowest 11 bits are nonzero
171 5         33 my $ready = 0x07FF & await $self->_read( 0xe4b8, 1 );
172 5 100       1061 $ready or return @last_measurement;
173              
174 3         16 return @last_measurement = await $self->read_measurement;
175             }
176              
177 0     0 0 0 async method initialize_sensors ()
  0         0  
  0         0  
178 0         0 {
179             # Startup delay
180 0         0 await Future::IO->sleep( 0.05 );
181              
182 0         0 await $self->start_periodic_measurement;
183             }
184              
185             field $_pending_next_read_f;
186             method _next_read
187             {
188             return $_pending_next_read_f //=
189 5     5   626 $self->maybe_read_measurement->on_ready(sub { undef $_pending_next_read_f });
190             }
191              
192             declare_sensor co2_concentration =>
193             method => async method {
194             return ( await $self->_next_read )[0];
195             },
196             units => "ppm",
197             sanity_bounds => [ 300, 8000 ],
198             precision => 0;
199              
200             declare_sensor temperature =>
201             method => async method {
202             return ( await $self->_next_read )[1];
203             },
204             units => "°C",
205             sanity_bounds => [ -50, 80 ],
206             precision => 2;
207              
208             declare_sensor humidity =>
209             method => async method {
210             return ( await $self->_next_read )[2];
211             },
212             units => "%RH",
213             sanity_bounds => [ -1, 101 ], # give it slight headroom beyond the 0-100 range for rounding errors/etc
214             precision => 2;
215              
216             =head1 AUTHOR
217              
218             Paul Evans
219              
220             =cut
221              
222             0x55AA;