denko 0.13.3 → 0.13.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,223 @@
1
+ module Denko
2
+ module Sensor
3
+ class BMP180
4
+ include I2C::Peripheral
5
+ include Behaviors::Poller
6
+
7
+ # Write this to register 0xE0 for soft reset
8
+ SOFT_RESET = 0xB6
9
+
10
+ #
11
+ # Pressure Oversample Setting Values
12
+ #
13
+ # General formula:
14
+ # 2 ** n, where n is the decimal value of the bits, up to 8x pressure oversampling.
15
+ #
16
+ OVERSAMPLE_FACTORS = {
17
+ 1 => 0b00,
18
+ 2 => 0b01,
19
+ 4 => 0b10,
20
+ 8 => 0b11,
21
+ }
22
+
23
+ def before_initialize(options={})
24
+ @i2c_address = 0x77
25
+ super(options)
26
+ end
27
+
28
+ def after_initialize(options={})
29
+ super(options)
30
+
31
+ # Avoid repeated memory allocation for callback data and state.
32
+ @reading = { temperature: nil, pressure: nil }
33
+ self.state = { temperature: nil, pressure: nil }
34
+
35
+ # Default to start conversion off, reading temperature, no pressure oversampling.
36
+ @register = 0b00001110
37
+ @calibration_data_loaded = false
38
+ @oss = 0b00
39
+
40
+ # Temporary storage for raw bytes, since two I2C reads are needed for temperature and pressure.
41
+ @raw_bytes = [0, 0, 0, 0, 0]
42
+
43
+ soft_reset
44
+ end
45
+
46
+ #
47
+ # Configuration Methods
48
+ #
49
+ def soft_reset
50
+ i2c_write(SOFT_RESET)
51
+ end
52
+
53
+ attr_reader :measurement_time
54
+
55
+ def update_measurement_time
56
+ # Get oversample bits from current register setting.
57
+ oversample_exponent = (@register & 0b11000000) >> 6
58
+
59
+ # Calculate time in milliseconds.
60
+ extra_samples = (2 ** oversample_exponent) - 1
61
+ extra_time = extra_samples * 3
62
+ total_time = 4.5 + extra_time
63
+
64
+ # Sleep 1ms extra for safety and convert it to seconds.
65
+ @measurement_time = (total_time + 1) / 1000.0
66
+ end
67
+
68
+ def pressure_samples=(factor)
69
+ raise ArgumentError, "invalid oversampling factor: #{factor}" unless OVERSAMPLE_FACTORS.keys.include? factor
70
+ @oss = OVERSAMPLE_FACTORS[factor]
71
+ end
72
+
73
+ def write_settings
74
+ update_measurement_time
75
+ i2c_write [0xF4, @register]
76
+ end
77
+
78
+ #
79
+ # Reading Methods
80
+ #
81
+ def _start_read_temperature
82
+ @register = 0x2E
83
+ write_settings
84
+ end
85
+
86
+ def _start_read_pressure
87
+ @register = 0x34 | (@oss << 6)
88
+ write_settings
89
+ end
90
+
91
+ def _read
92
+ get_calibration_data unless calibration_data_loaded
93
+
94
+ _start_read_temperature
95
+ sleep(@measurement_time)
96
+ i2c_read 0xF6, 2
97
+
98
+ _start_read_pressure
99
+ sleep(@measurement_time)
100
+ i2c_read 0xF6, 3
101
+ end
102
+
103
+ def pre_callback_filter(data)
104
+ # Temperature is 2 bytes.
105
+ if data.length == 2
106
+ @raw_bytes[0] = data[0]
107
+ @raw_bytes[1] = data[1]
108
+ # Pressure is 3 bytes and triggers callbacks.
109
+ elsif data.length == 3
110
+ @raw_bytes[2] = data[0]
111
+ @raw_bytes[3] = data[1]
112
+ @raw_bytes[4] = data[2]
113
+ return decode_reading(@raw_bytes)
114
+ # Calibration data is 22 bytes.
115
+ elsif data.length == 22
116
+ process_calibration(data)
117
+ end
118
+
119
+ # Anything other than pressure avoids callbacks.
120
+ return nil
121
+ end
122
+
123
+ def update_state(reading)
124
+ # Checking for Hash ignores calibration data and nil.
125
+ if reading.class == Hash
126
+ @state_mutex.synchronize do
127
+ @state[:temperature] = reading[:temperature]
128
+ @state[:pressure] = reading[:pressure]
129
+ end
130
+ end
131
+ end
132
+
133
+ def [](key)
134
+ @state_mutex.synchronize do
135
+ return @state[key]
136
+ end
137
+ end
138
+
139
+ #
140
+ # Decoding Methods
141
+ #
142
+ def decode_reading(bytes)
143
+ temperature, b5 = decode_temperature(bytes)
144
+ @reading[:temperature] = temperature
145
+ @reading[:pressure] = decode_pressure(bytes, b5)
146
+ @reading
147
+ end
148
+
149
+ def decode_temperature(bytes)
150
+ # Temperature is bytes [0..2], MSB first.
151
+ ut = bytes[0] << 8 | bytes[1]
152
+
153
+ # Calibration compensation from datasheet
154
+ x1 = (ut - @calibration[:ac6]) * @calibration[:ac5] / 32768
155
+ x2 = (@calibration[:mc] * 2048) / (x1 + @calibration[:md])
156
+ b5 = x1 + x2
157
+
158
+ # 160 instead of 16 since datasheet calculates to 0.1 C units.
159
+ # Float to force the final value into float, but keep b5 integer for pressure.
160
+ temperature = (b5 + 8) / 160.0
161
+
162
+ # Return temperature and b5 for pressure calculation.
163
+ [temperature, b5]
164
+ end
165
+
166
+ def decode_pressure(bytes, b5)
167
+ # Pressure is bytes [2..3], MSB first.
168
+ up = ((bytes[2] << 16) | (bytes[3] << 8) | (bytes[4])) >> (8 - @oss)
169
+
170
+ # Calibration compensation from datasheet
171
+ b6 = b5 - 4000
172
+ x1 = (@calibration[:b2] * (b6 * b6 / 4096)) / 2048
173
+ x2 = @calibration[:ac2] * b6 / 2048
174
+ x3 = x1 + x2
175
+ b3 = (((@calibration[:ac1]*4 + x3) << @oss) + 2) / 4
176
+ x1 = @calibration[:ac3] * b6 / 8192
177
+ x2 = (@calibration[:b1] * (b6 * b6 / 4096)) / 65536
178
+ x3 = (x1 + x2 + 2) / 4
179
+ b4 = (@calibration[:ac4] * ((x3+32768) & 0xFFFF_FFFF)) / 32768
180
+ b7 = ((up & 0xFFFF_FFFF) - b3) * (50000 >> @oss)
181
+ if (b7 < 0x8000_0000)
182
+ p = (b7 * 2) / b4
183
+ else
184
+ p = (b7 / b4) * 2
185
+ end
186
+ x1 = (p / 256) * (p / 256)
187
+ x1 = (x1 * 3038) / 65536
188
+ x2 = (-7357 * p) / 65536
189
+ p = p + (x1 + x2 + 3791) / 16
190
+ pressure = p.to_f
191
+ end
192
+
193
+ #
194
+ # Calibration Methods
195
+ #
196
+ attr_reader :calibration_data_loaded
197
+
198
+ def get_calibration_data
199
+ # Calibration data is 22 bytes starting at address 0xAA.
200
+ read_using -> { i2c_read(0xAA, 22) }
201
+ end
202
+
203
+ def process_calibration(bytes)
204
+ if bytes
205
+ @calibration = {
206
+ ac1: bytes[0..1].pack('C*').unpack('s>')[0],
207
+ ac2: bytes[2..3].pack('C*').unpack('s>')[0],
208
+ ac3: bytes[4..5].pack('C*').unpack('s>')[0],
209
+ ac4: bytes[6..7].pack('C*').unpack('S>')[0],
210
+ ac5: bytes[8..9].pack('C*').unpack('S>')[0],
211
+ ac6: bytes[10..11].pack('C*').unpack('S>')[0],
212
+ b1: bytes[12..13].pack('C*').unpack('s>')[0],
213
+ b2: bytes[14..15].pack('C*').unpack('s>')[0],
214
+ mb: bytes[16..17].pack('C*').unpack('s>')[0],
215
+ mc: bytes[18..19].pack('C*').unpack('s>')[0],
216
+ md: bytes[20..21].pack('C*').unpack('s>')[0],
217
+ }
218
+ @calibration_data_loaded = true
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,8 @@
1
+ module Denko
2
+ module Sensor
3
+ class GenericPIR < DigitalIO::Input
4
+ alias :on_motion_stop :on_low
5
+ alias :on_motion_start :on_high
6
+ end
7
+ end
8
+ end
@@ -128,8 +128,12 @@ module Denko
128
128
 
129
129
  # Bit 1 of LSB determines type of reading; 0 for temperature, 1 for humidity.
130
130
  if (bytes[1] & 0b00000010) > 0
131
+ # Calculate humidity and limit within 0-100 range.
132
+ humidity = (raw_value.to_f / 524.288) - 6
133
+ humidity = 0.0 if humidity < 0.0
134
+ humidity = 100.0 if humidity > 100.0
131
135
  @reading[0] = :humidity
132
- @reading[1] = (raw_value.to_f / 524.288) - 6
136
+ @reading[1] = humidity
133
137
  @humidity.update(@reading[1])
134
138
  else
135
139
  @reading[0] = :temperature
@@ -138,7 +142,7 @@ module Denko
138
142
  end
139
143
  @reading
140
144
  end
141
-
145
+
142
146
  def update_state(reading)
143
147
  @state_mutex.synchronize do
144
148
  @state[reading[0]] = reading[1]
@@ -0,0 +1,308 @@
1
+ module Denko
2
+ module Sensor
3
+ class QMP6988
4
+ include I2C::Peripheral
5
+ include Behaviors::Poller
6
+
7
+ UPDATE_TIME = 0.020
8
+ RESET_REGISTER = 0xE0
9
+ RESET_COMMAND = 0xE6
10
+ CTRL_MEAS_REGISTER = 0xF4
11
+ STANDBY_TIME_REGISTER = 0xF5
12
+ IIR_REGISTER = 0xF1
13
+ CONFIG_LENGTH = 5
14
+ DATA_REGISTER = 0xF7
15
+ DATA_LENGTH = 6
16
+ CALIBRATION_REGISTER = 0xA0
17
+ CALIBRATION_LENGTH = 25
18
+ CHIP_ID_REGISTER = 0xD1
19
+ CHIP_ID_LENGTH = 1
20
+ FORCED_MODE = 0b01
21
+ NORMAL_MODE = 0b11
22
+
23
+ # Standby Times for Normal (Continuous) Mode in milliseconds
24
+ STANDBY_TIMES = {
25
+ 1 => 0b000,
26
+ 5 => 0b001,
27
+ 50 => 0b010,
28
+ 250 => 0b011,
29
+ 500 => 0b100,
30
+ 1000 => 0b101,
31
+ 2000 => 0b110,
32
+ 4000 => 0b111,
33
+ }
34
+
35
+ #
36
+ # Oversample Setting Values
37
+ # Note: Each sensor has a separate oversample setting.
38
+ #
39
+ # General formula:
40
+ # 2 ** (n-1), where n is the decimal value of the bits, up to 16x max oversampling.
41
+ #
42
+ OVERSAMPLE_FACTORS = {
43
+ # 0 => 0b000, # Sensor skipped. Value will be 0x800000.
44
+ 1 => 0b001,
45
+ 2 => 0b010,
46
+ 4 => 0b011,
47
+ 8 => 0b100,
48
+ 16 => 0b101,
49
+ 32 => 0b110,
50
+ 64 => 0b111,
51
+ }
52
+ #
53
+ # Single sample times (in milliseconds) for each sensor, derived from datasheet examples.
54
+ TEMPERATURE_SAMPLE_TIME = 0.9
55
+ PRESSURE_SAMPLE_TIME = 0.85
56
+
57
+ # IIR Filter Coefficients
58
+ IIR_COEFFICIENTS = {
59
+ 0 => 0b000,
60
+ 2 => 0b001,
61
+ 4 => 0b010,
62
+ 8 => 0b011,
63
+ 16 => 0b100,
64
+ 32 => 0b101, # 0b110 and 0b111 are also valid for 16.
65
+ }
66
+
67
+ def before_initialize(options={})
68
+ @i2c_address = 0x70
69
+ super(options)
70
+ end
71
+
72
+ def after_initialize(options={})
73
+ super(options)
74
+
75
+ # Avoid repeated memory allocation for callback data and state.
76
+ @reading = { temperature: nil, pressure: nil }
77
+ self.state = { temperature: nil, pressure: nil }
78
+
79
+ reset
80
+
81
+ # Get 5 config registers. Copy 0xF4 to modify it for control.
82
+ get_config_registers
83
+ @ctrl_meas_register = @registers[:f4].dup
84
+
85
+ # Default settings
86
+ self.iir_coefficient = 0
87
+ self.temperature_samples = 1
88
+ self.pressure_samples = 1
89
+ self.forced_mode
90
+
91
+ # self.forced mode triggered an initial measurement so IIR works properly if enabled.
92
+ # Wait for those values to enter the data registers, but don't read them back.
93
+ sleep @measurement_time
94
+
95
+ get_calibration_data
96
+ end
97
+
98
+ #
99
+ # Configuration Methods
100
+ #
101
+ def reset
102
+ i2c_write [RESET_REGISTER, RESET_COMMAND]
103
+ sleep UPDATE_TIME
104
+ end
105
+
106
+ def iir_coefficient=(coeff)
107
+ raise ArgumentError, "invalid IIR coefficient: #{coeff}" unless IIR_COEFFICIENTS.keys.include? coeff
108
+ i2c_write [IIR_REGISTER, IIR_COEFFICIENTS[coeff]]
109
+ @iir_coefficient = coeff
110
+ end
111
+ attr_reader :iir_coefficient
112
+
113
+ def standby_time=(ms)
114
+ raise ArgumentError, "invalid standby time: #{ms}" unless self.class::STANDBY_TIMES.keys.include? ms
115
+ byte = STANDBY_TIMES[ms] << 5
116
+ @standby_time = ms
117
+ i2c_write [STANDBY_TIME_REGISTER, byte]
118
+ sleep UPDATE_TIME
119
+ end
120
+ attr_reader :standby_time
121
+
122
+ def temperature_samples=(factor)
123
+ raise ArgumentError, "invalid oversampling factor: #{factor}" unless OVERSAMPLE_FACTORS.keys.include? factor
124
+ @ctrl_meas_register = (@ctrl_meas_register & 0b00011111) | (OVERSAMPLE_FACTORS[factor] << 5)
125
+ @temperature_samples = factor
126
+ calculate_measurement_time
127
+ i2c_write [CTRL_MEAS_REGISTER, @ctrl_meas_register]
128
+ sleep UPDATE_TIME
129
+ end
130
+ attr_reader :temperature_samples
131
+
132
+ def pressure_samples=(factor)
133
+ raise ArgumentError, "invalid oversampling factor: #{factor}" unless OVERSAMPLE_FACTORS.keys.include? factor
134
+ @ctrl_meas_register = (@ctrl_meas_register & 0b11100011) | (OVERSAMPLE_FACTORS[factor] << 2)
135
+ @pressure_samples = factor
136
+ calculate_measurement_time
137
+ i2c_write [CTRL_MEAS_REGISTER, @ctrl_meas_register]
138
+ sleep UPDATE_TIME
139
+ end
140
+ attr_reader :pressure_samples
141
+
142
+ def calculate_measurement_time
143
+ @measurement_time = (@temperature_samples.to_i * TEMPERATURE_SAMPLE_TIME) +
144
+ (@pressure_samples.to_i * PRESSURE_SAMPLE_TIME)
145
+ # Add 5ms for safety and convert to seconds.
146
+ @measurement_time = (@measurement_time + 5) * 0.001
147
+ end
148
+
149
+ def forced_mode
150
+ @ctrl_meas_register = (@ctrl_meas_register & 0b11111100) | FORCED_MODE
151
+ i2c_write [CTRL_MEAS_REGISTER, @ctrl_meas_register]
152
+ @forced_mode = true
153
+ sleep UPDATE_TIME
154
+ end
155
+
156
+ def continuous_mode
157
+ @ctrl_meas_register = (@ctrl_meas_register & 0b11111100) | NORMAL_MODE
158
+ i2c_write [CTRL_MEAS_REGISTER, @ctrl_meas_register]
159
+ @forced_mode = false
160
+ sleep UPDATE_TIME
161
+ end
162
+
163
+ def chip_id
164
+ return @chip_id if @chip_id
165
+ i2c_read(CHIP_ID_REGISTER, 1)
166
+ sleep 0.001 while !@chip_id
167
+ @chip_id
168
+ end
169
+
170
+ #
171
+ # Reading & Processing
172
+ #
173
+ def _read
174
+ if @forced_mode
175
+ # Write CTRL_MEAS register to trigger reading, then wait for measurement.
176
+ i2c_write [CTRL_MEAS_REGISTER, @ctrl_meas_register]
177
+ sleep @measurement_time
178
+ end
179
+
180
+ # Read the data bytes.
181
+ i2c_read(DATA_REGISTER, DATA_LENGTH)
182
+ end
183
+
184
+ def pre_callback_filter(bytes)
185
+ if bytes.length == DATA_LENGTH
186
+ return process_reading(bytes)
187
+ elsif bytes.length == CONFIG_LENGTH
188
+ process_config(bytes)
189
+ elsif bytes.length == CALIBRATION_LENGTH
190
+ process_calibration(bytes)
191
+ elsif bytes.length == CHIP_ID_LENGTH
192
+ @chip_id = bytes[0]
193
+ end
194
+ return nil
195
+ end
196
+
197
+ def process_reading(bytes)
198
+ # Temperature and pressure are 24-bits long each, and need 2^23 subtracted.
199
+ dt = ((bytes[3] << 16) + (bytes[4] << 8) + bytes[5]) - (0b1 << 23)
200
+ dp = ((bytes[0] << 16) + (bytes[1] << 8) + bytes[2]) - (0b1 << 23)
201
+
202
+ # Compensated temperature calculated in 1/256 of a degree Celsius.
203
+ tr = @calibration[:a0] +
204
+ @calibration[:a1] * dt +
205
+ @calibration[:a2] * (dt ** 2)
206
+ @reading[:temperature] = tr / 256.0
207
+
208
+ # Compensated pressure calculated in Pascals.
209
+ @reading[:pressure] = @calibration[:b00] +
210
+ @calibration[:bt1] * tr +
211
+ @calibration[:bp1] * dp +
212
+ @calibration[:b11] * (tr * dp) +
213
+ @calibration[:bt2] * (tr ** 2) +
214
+ @calibration[:bp2] * (dp ** 2) +
215
+ @calibration[:b12] * (dp * (tr ** 2)) +
216
+ @calibration[:b21] * ((dp ** 2) * tr) +
217
+ @calibration[:bp3] * (dp ** 3)
218
+
219
+ # Return reading for callbacks.
220
+ @reading
221
+ end
222
+
223
+ def update_state(reading)
224
+ @state_mutex.synchronize do
225
+ @state[:temperature] = reading[:temperature]
226
+ @state[:pressure] = reading[:pressure]
227
+ end
228
+ end
229
+
230
+ def get_config_registers
231
+ @registers = {}
232
+ i2c_read(IIR_REGISTER, CONFIG_LENGTH)
233
+ sleep 0.001 while @registers.empty?
234
+ @registers
235
+ end
236
+
237
+ def process_config(bytes)
238
+ @registers = { f1: bytes[0], f2: bytes[1], f3: bytes[2], f4: bytes[3], f5: bytes[4] }
239
+ end
240
+ attr_reader :registers
241
+
242
+ #
243
+ # Calibration
244
+ #
245
+ attr_reader :calibration_data_loaded
246
+
247
+ CONVERSION_FACTORS = {
248
+ a1: { A: -6.3e-03, S: 4.3e-04 },
249
+ a2: { A: -1.9e-11, S: 1.2e-10 },
250
+ bt1: { A: 1.0e-01, S: 9.1e-02 },
251
+ bt2: { A: 1.2e-08, S: 1.2e-06 },
252
+ bp1: { A: 3.3e-02, S: 1.9e-02 },
253
+ b11: { A: 2.1e-07, S: 1.4e-07 },
254
+ bp2: { A: -6.3e-10, S: 3.5e-10 },
255
+ b12: { A: 2.9e-13, S: 7.6e-13 },
256
+ b21: { A: 2.1e-15, S: 1.2e-14 },
257
+ bp3: { A: 1.3e-16, S: 7.9e-17 },
258
+ a0: 16.0,
259
+ b00: 16.0,
260
+ }
261
+
262
+ def get_calibration_data
263
+ i2c_read(CALIBRATION_REGISTER, CALIBRATION_LENGTH)
264
+ end
265
+
266
+ def process_calibration(bytes)
267
+ if bytes
268
+ # These 2 values are 20-bit instead of 16-bit, so can't combine them with #pack.
269
+ a0_unsigned = (bytes[18] << 12) + (bytes[19] << 4) + (bytes[24] & 0b00001111)
270
+ b00_unsigned = (bytes[0] << 12) + (bytes[1] << 4) + ((bytes[24] & 0b11110000) >> 4)
271
+
272
+ # Cast the raw bytes as big-endian signed.
273
+ @calibration_raw = {
274
+ # Shift these to 32-bit before converting to signed, then reverse the shift after.
275
+ a0: [(a0_unsigned << 12)].pack('L>').unpack('l>')[0] >> 12,
276
+ b00: [(b00_unsigned << 12)].pack('L>').unpack('l>')[0] >> 12,
277
+
278
+ a1: bytes[20..21].pack('C*').unpack('s>')[0],
279
+ a2: bytes[22..23].pack('C*').unpack('s>')[0],
280
+
281
+ b11: bytes[8..9].pack('C*').unpack('s>')[0],
282
+ b12: bytes[12..13].pack('C*').unpack('s>')[0],
283
+ b21: bytes[14..15].pack('C*').unpack('s>')[0],
284
+
285
+ bp1: bytes[6..7].pack('C*').unpack('s>')[0],
286
+ bp2: bytes[10..11].pack('C*').unpack('s>')[0],
287
+ bp3: bytes[16..17].pack('C*').unpack('s>')[0],
288
+
289
+ bt1: bytes[2..3].pack('C*').unpack('s>')[0],
290
+ bt2: bytes[4..5].pack('C*').unpack('s>')[0],
291
+ }
292
+
293
+ # Use conversion formulae to calculate compensation coefficients, all as floats.
294
+ @calibration = {}
295
+ @calibration_raw.keys.each do |key|
296
+ if CONVERSION_FACTORS[key].class == Float
297
+ @calibration[key] = @calibration_raw[key] / CONVERSION_FACTORS[key]
298
+ else
299
+ @calibration[key] = CONVERSION_FACTORS[key][:A] + (CONVERSION_FACTORS[key][:S] * @calibration_raw[key] / 32767.0)
300
+ end
301
+ end
302
+
303
+ @calibration_data_loaded = true
304
+ end
305
+ end
306
+ end
307
+ end
308
+ end
@@ -0,0 +1,34 @@
1
+ module Denko
2
+ module Sensor
3
+ class RCWL9620
4
+ include I2C::Peripheral
5
+ include Behaviors::Poller
6
+
7
+ def before_initialize(options={})
8
+ @i2c_address = 0x57
9
+ super(options)
10
+ end
11
+
12
+ def _read
13
+ i2c_write(0x01)
14
+ sleep(0.120)
15
+ i2c_read(nil, 3)
16
+ end
17
+
18
+ def pre_callback_filter(bytes)
19
+ # Data is in micrometers, 3 bytes, big-endian.
20
+ um = (bytes[0] << 16) + (bytes[1] << 8) + bytes[2]
21
+ mm = um / 1000.0
22
+
23
+ # Limit output between 20 and 4500mm.
24
+ if mm > 4500.0
25
+ return 4500.0
26
+ elsif mm < 20.0
27
+ return 20.0
28
+ else
29
+ return mm
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end