Class: Denko::Sensor::BME280

Inherits:
Object
  • Object
show all
Includes:
Behaviors::Lifecycle, Behaviors::Poller, I2C::Peripheral, HumidityHelper, PressureHelper, TemperatureHelper
Defined in:
lib/denko/sensor/bme280.rb

Direct Known Subclasses

BMP280

Constant Summary collapse

I2C_ADDRESS =
0x76
SLEEP_MODE =

Reading Mode Settings

0b00
ONESHOT_MODE =

0b10 is also valid. Called “forced mode” in datasheet.

0b01
CONTINUOUS_MODE =

Called “normal mode” in datashseet.

0b11
STANDBY_TIMES =

Standby Times for Normal (Continuous) Mode in milliseconds

{
  0.5  => 0b000,
  62.5 => 0b001,
  125  => 0b010,
  250  => 0b011,
  500  => 0b100,
  1000 => 0b101,
  10   => 0b110,
  20   => 0b111,
}
OVERSAMPLE_FACTORS =

Oversample Setting Values

Note: Each of the 3 sensors has a separate oversample setting, but temperature cannot be skipped, since the other 2 calculations depend on it.

General formula:

2 ** (n-1), where n is the decimal value of the bits, up to 16x max oversampling.
{
  0  =>  0b000, # Sensor skipped. Value will be 0x800000.
  1  =>  0b001,
  2  =>  0b010,
  4  =>  0b011,
  8  =>  0b100,
  16 =>  0b101, # 0b110 and 0b111 are also valid for 16x.
}
IIR_COEFFICIENTS =

IIR Filter Coefficients

{
  0  =>  0b000,
  2  =>  0b001,
  4  =>  0b010,
  8  =>  0b011,
  16 =>  0b100, # 0b101, 0b110 and 0b111 are also valid for 16.
}

Constants included from Behaviors::Lifecycle

Behaviors::Lifecycle::CALLBACK_METHODS

Constants included from Behaviors::Reader

Behaviors::Reader::READ_WAIT_TIME

Constants included from I2C::Peripheral

I2C::Peripheral::I2C_FREQUENCY, I2C::Peripheral::I2C_REPEATED_START

Instance Attribute Summary collapse

Attributes included from Behaviors::Threaded

#interrupts_enabled, #thread

Attributes included from I2C::Peripheral

#i2c_frequency, #i2c_repeated_start

Attributes included from Behaviors::Component

#board, #params

Instance Method Summary collapse

Methods included from HumidityHelper

#humidity

Methods included from PressureHelper

#pressure, #pressure_atm, #pressure_bar

Methods included from TemperatureHelper

#temperature, #temperature_f, #temperature_k

Methods included from Behaviors::Lifecycle

included

Methods included from Behaviors::Poller

#poll, #poll_using, #stop

Methods included from Behaviors::Threaded

#enable_interrupts, included, #mruby_thread_check, #stop, #stop_thread, #threaded, #threaded_loop

Methods included from Behaviors::Reader

#read, #read_busy?, #read_nb, #read_raw, #read_using, #update

Methods included from Behaviors::Callbacks

#add_callback, #callbacks, #remove_callback, #update

Methods included from I2C::Peripheral

#address, #i2c_default, #i2c_read, #i2c_read_raw, #i2c_write

Methods included from Behaviors::BusPeripheralAddressed

#address

Methods included from Behaviors::BusPeripheral

#atomically

Methods included from Behaviors::Component

#initialize, #micro_delay

Instance Attribute Details

#calibration_data_loadedObject (readonly)

Calibration Methods



278
279
280
# File 'lib/denko/sensor/bme280.rb', line 278

def calibration_data_loaded
  @calibration_data_loaded
end

#measurement_timeObject (readonly)

Returns the value of attribute measurement_time.



99
100
101
# File 'lib/denko/sensor/bme280.rb', line 99

def measurement_time
  @measurement_time
end

Instance Method Details

#_readObject

Reading Methods



178
179
180
181
182
# File 'lib/denko/sensor/bme280.rb', line 178

def _read
  get_calibration_data unless calibration_data_loaded
  write_settings
  i2c_read 8, register: 0xF7
end

#config_register_bitsObject



167
168
169
170
171
172
173
# File 'lib/denko/sensor/bme280.rb', line 167

def config_register_bits
  str = String.new
  @registers.each_key do |key|
    str << "0x#{key.upcase}: #{@registers[key].to_s(2).rjust(8, '0')}\n"
  end
  str
end

#continuous_modeObject



117
118
119
120
# File 'lib/denko/sensor/bme280.rb', line 117

def continuous_mode
  @registers[:f4] = (@registers[:f4] & 0b11111100) | CONTINUOUS_MODE
  write_settings
end

#decode_humidity(bytes, t_fine) ⇒ Object



245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/denko/sensor/bme280.rb', line 245

def decode_humidity(bytes, t_fine)
  # Raw data for humidity is big-endian uint16.
  adc_h = (bytes[6] << 8) | bytes[7]

  # Floating point humidity calculation from datasheet. Result in % RH.
  humidity = t_fine - 76800.0
  humidity = (adc_h - (@calibration[:h4] * 64.0 + @calibration[:h5] / 16384.0 * humidity)) *
              (@calibration[:h2] / 65536.0 * (1.0 + @calibration[:h6] / 67108864.0 * humidity * (1.0 + @calibration[:h3] / 67108864.0 * humidity)))
  humidity = humidity * (1.0 - @calibration[:h1] * humidity / 524288.0)
  humidity = 100.0 if humidity > 100
  humidity = 0.0   if humidity < 0
  humidity
end

#decode_pressure(bytes, t_fine) ⇒ Object



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/denko/sensor/bme280.rb', line 222

def decode_pressure(bytes, t_fine)
  # Reformat raw pressure bytes (20-bits in 24) to uint32.
  adc_p = ((bytes[0] << 16) | (bytes[1] << 8) | (bytes[2])) >> 4

  # Floating point pressure calculation from datasheet. Result in Pascals.
  var1 = (t_fine / 2.0) - 64000.0
  var2 = var1 * var1 * @calibration[:p6] / 32768.0
  var2 = var2 + var1 * @calibration[:p5] * 2.0
  var2 = (var2 / 4.0) + (@calibration[:p4] * 65536.0)
  var1 = (@calibration[:p3] * var1 * var1 / 524288.0 + @calibration[:p2] * var1) / 524288.0
  var1 = (1.0 + var1 / 32768.0) * @calibration[:p1]
  if var1 == 0
    pressure = nil
  else
    pressure = 1048576.0 - adc_p
    pressure = (pressure - (var2 / 4096.0)) * 6250.0 / var1
    var1 = @calibration[:p9] * pressure * pressure / 2147483648.0
    var2 = pressure * @calibration[:p8] / 32768.0
    pressure = pressure + (var1 + var2 + @calibration[:p7]) / 16.0
  end
  pressure
end

#decode_temperature(bytes) ⇒ Object

Decoding Methods



210
211
212
213
214
215
216
217
218
219
220
# File 'lib/denko/sensor/bme280.rb', line 210

def decode_temperature(bytes)
  # Reformat raw temeprature bytes (20-bits in 24) to uint32.
  adc_t = ((bytes[3] << 16) | (bytes[4] << 8) | (bytes[5])) >> 4

  # Floating point temperature calculation from datasheet. Result in degrees Celsius.
  var1 = (adc_t /  16384.0 - @calibration[:t1] / 1024.0) * @calibration[:t2]
  var2 = (adc_t / 131072.0 - @calibration[:t1] / 8192.0) ** 2 * @calibration[:t3]
  t_fine = var1 + var2
  temperature = (var1 + var2) / 5120.0
  [temperature, t_fine]
end

#get_calibration_dataObject



280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/denko/sensor/bme280.rb', line 280

def get_calibration_data
  # Calibration A
  cal_a_bytes = i2c_read_raw(26, register: 0x88)
  process_calibration_a(cal_a_bytes) if cal_a_bytes

  # Calibration B, only on BME280.
  if humidity_available?
    cal_b_bytes = i2c_read_raw(7, register: 0xE1)
    process_calibration_b(cal_b_bytes) if cal_b_bytes
  end

  if (@calibration[:cal_a] && @calibration[:cal_b]) || (@calibration[:cal_a] && !humidity_available?)
    @calibration.delete(:cal_a).delete(:cal_b)
    @calibration_data_loaded = true
  end
end

#humidity_available?Boolean

No humidity on the BMP280.

Returns:

  • (Boolean)


271
272
273
# File 'lib/denko/sensor/bme280.rb', line 271

def humidity_available?
  self.class.name.split('::').last.downcase.start_with? "bme"
end

#humidity_samples=(factor) ⇒ Object

Raises:

  • (ArgumentError)


144
145
146
147
148
149
# File 'lib/denko/sensor/bme280.rb', line 144

def humidity_samples=(factor)
  raise ArgumentError, "invalid oversampling factor: #{factor}" unless OVERSAMPLE_FACTORS.keys.include? factor

  @registers[:f2] = (@registers[:f2] & 0b11111000) | OVERSAMPLE_FACTORS[factor]
  write_settings
end

#iir_coefficient=(coeff) ⇒ Object

Raises:

  • (ArgumentError)


151
152
153
154
155
156
# File 'lib/denko/sensor/bme280.rb', line 151

def iir_coefficient=(coeff)
  raise ArgumentError, "invalid IIR coefficient: #{coeff}" unless IIR_COEFFICIENTS.keys.include? coeff

  @registers[:f5] = (@registers[:f5] & 0b11100011) | (IIR_COEFFICIENTS[coeff] << 2)
  write_settings
end

#oneshot_modeObject

Configuration Methods



94
95
96
97
# File 'lib/denko/sensor/bme280.rb', line 94

def oneshot_mode
  @registers[:f4] = (@registers[:f4] & 0b11111100) | ONESHOT_MODE
  write_settings
end

#pre_callback_filter(bytes) ⇒ Object



184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/denko/sensor/bme280.rb', line 184

def pre_callback_filter(bytes)
  return nil unless bytes.length == 8

  # Always read temperature since t_fine is needed to calibrate other values.
  temperature, t_fine = decode_temperature(bytes)
  reading[:temperature] = temperature

  # Pressure and humidity are optional. Humidity is not available on the BMP280.
  reading[:pressure] = decode_pressure(bytes, t_fine) if reading_pressure?
  reading[:humidity] = decode_humidity(bytes, t_fine) if reading_humidity?

  reading
end

#pressure_samples=(factor) ⇒ Object

Raises:

  • (ArgumentError)


137
138
139
140
141
142
# File 'lib/denko/sensor/bme280.rb', line 137

def pressure_samples=(factor)
  raise ArgumentError, "invalid oversampling factor: #{factor}" unless OVERSAMPLE_FACTORS.keys.include? factor

  @registers[:f4] = (@registers[:f4] & 0b11100011) | (OVERSAMPLE_FACTORS[factor] << 2)
  write_settings
end

#process_calibration_a(cal_a) ⇒ Object



297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# File 'lib/denko/sensor/bme280.rb', line 297

def process_calibration_a(cal_a)
  if cal_a
    @calibration = {
      t1: cal_a[0..1].pack('C*').unpack('S<')[0],
      t2: cal_a[2..3].pack('C*').unpack('s<')[0],
      t3: cal_a[4..5].pack('C*').unpack('s<')[0],

      p1: cal_a[6..7].pack('C*').unpack('S<')[0],
      p2: cal_a[8..9].pack('C*').unpack('s<')[0],
      p3: cal_a[10..11].pack('C*').unpack('s<')[0],
      p4: cal_a[12..13].pack('C*').unpack('s<')[0],
      p5: cal_a[14..15].pack('C*').unpack('s<')[0],
      p6: cal_a[16..17].pack('C*').unpack('s<')[0],
      p7: cal_a[18..19].pack('C*').unpack('s<')[0],
      p8: cal_a[20..21].pack('C*').unpack('s<')[0],
      p9: cal_a[22..23].pack('C*').unpack('s<')[0],
    }
    @calibration[:cal_a] = cal_a
  end
end

#process_calibration_b(cal_b) ⇒ Object



318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/denko/sensor/bme280.rb', line 318

def process_calibration_b(cal_b)
  if cal_b && @calibration[:cal_a]
    @calibration.merge!(
      h1: @calibration[:cal_a][25],
      h2: cal_b[0..1].pack('C*').unpack('s<')[0],
      h3: cal_b[2],
      h4: [(cal_b[3] << 4) | (cal_b[4] & 0b00001111)].pack('S').unpack('s')[0],
      h5: [(cal_b[5] << 4) | (cal_b[4] >> 4)        ].pack('S').unpack('s')[0],
      h6: [cal_b[6]].pack('C').unpack('c')[0]
    )
    @calibration[:cal_b] = cal_b
  end
end

#readingObject



87
88
89
# File 'lib/denko/sensor/bme280.rb', line 87

def reading
  @reading ||= { temperature: nil, humidity: nil, pressure: nil }
end

#reading_humidity?Boolean

Returns:

  • (Boolean)


264
265
266
267
268
# File 'lib/denko/sensor/bme280.rb', line 264

def reading_humidity?
  return false unless humidity_available?
  # Lowest 3 bits of 0xF2 register must not be 0.
  (@registers[:f2] & 0b111) != OVERSAMPLE_FACTORS[0]
end

#reading_pressure?Boolean

Returns:

  • (Boolean)


259
260
261
262
# File 'lib/denko/sensor/bme280.rb', line 259

def reading_pressure?
  # Bits 2..4 of 0xF4 register must not be 0.
  (@registers[:f4] >> 2 & 0b111) != OVERSAMPLE_FACTORS[0]
end

#standby_time=(ms) ⇒ Object

Raises:

  • (ArgumentError)


122
123
124
125
126
127
# File 'lib/denko/sensor/bme280.rb', line 122

def standby_time=(ms)
  raise ArgumentError, "invalid standby time: #{ms}" unless self.class::STANDBY_TIMES.keys.include? ms

  @registers[:f5] = (@registers[:f5] & 0b00011111) | (self.class::STANDBY_TIMES[ms] << 5)
  write_settings
end

#stateObject



83
84
85
# File 'lib/denko/sensor/bme280.rb', line 83

def state
  @state ||= { temperature: nil, humidity: nil, pressure: nil }
end

#temperature_samples=(factor) ⇒ Object

Raises:

  • (ArgumentError)


129
130
131
132
133
134
135
# File 'lib/denko/sensor/bme280.rb', line 129

def temperature_samples=(factor)
  raise ArgumentError, "invalid oversampling factor: #{factor}" unless OVERSAMPLE_FACTORS.keys.include? factor
  raise ArgumentError, "temperature must be read. Invalid oversampling factor: #{factor}" if factor == 0

  @registers[:f4] = (@registers[:f4] & 0b00011111) | (OVERSAMPLE_FACTORS[factor] << 5)
  write_settings
end

#update_measurement_timeObject



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/denko/sensor/bme280.rb', line 101

def update_measurement_time
  t_oversampling = 2 ** (((@registers[:f4] & 0b11100000) >> 5) - 1)
  p_oversampling = 2 ** (((@registers[:f4] & 0b00011100) >> 2) - 1)

  # Use the maximum measurement time from the datasheet.
  @measurement_time = 1.25 + (2.3 * t_oversampling) + (2.3 * p_oversampling) + 0.575

  if humidity_available?
    h_oversampling = 2 ** ((@registers[:f2] & 0b00000111) - 1) if humidity_available?
    @measurement_time = @measurement_time + (2.3 * h_oversampling) + 0.575
  end

  # Milliseconds to seconds
  @measurement_time = @measurement_time / 1000
end

#update_state(reading) ⇒ Object



198
199
200
201
202
203
204
205
# File 'lib/denko/sensor/bme280.rb', line 198

def update_state(reading)
  @state_mutex.lock
  @state[:temperature] = reading[:temperature]
  @state[:pressure]    = reading[:pressure]
  @state[:humidity]    = reading[:humidity]
  @state_mutex.unlock
  @state
end

#write_settingsObject



158
159
160
161
162
163
164
165
# File 'lib/denko/sensor/bme280.rb', line 158

def write_settings
  if humidity_available?
    i2c_write [0xF2, @registers[:f2], 0xF4, @registers[:f4], 0xF5, @registers[:f5]]
  else
    i2c_write [0xF4, @registers[:f4], 0xF5, @registers[:f5]]
  end
  update_measurement_time
end