denko 0.13.3 → 0.13.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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