Class: Denko::Sensor::BME280

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

Direct Known Subclasses

BMP280

Constant Summary collapse

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.
}

Instance Attribute Summary collapse

Attributes included from Behaviors::Threaded

#interrupts_enabled, #thread

Attributes included from Behaviors::Callbacks

#callback_mutex

Attributes included from I2C::Peripheral

#i2c_frequency, #i2c_repeated_start

Attributes included from Behaviors::BusPeripheral

#address

Attributes included from Behaviors::Component

#board

Instance Method Summary collapse

Methods included from Behaviors::Poller

#poll, #poll_using, #stop

Methods included from Behaviors::Threaded

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

Methods included from Behaviors::Reader

#read, #read_using, #wait_for_read

Methods included from Behaviors::Callbacks

#add_callback, #callbacks, #initialize, #remove_callback, #update

Methods included from Behaviors::State

#initialize, #state

Methods included from I2C::Peripheral

#i2c_read, #i2c_write

Methods included from Behaviors::BusPeripheral

#atomically

Methods included from Behaviors::Component

#initialize, #micro_delay

Instance Attribute Details

#calibration_data_loadedObject (readonly)

Calibration Methods



296
297
298
# File 'lib/denko/sensor/bme280.rb', line 296

def calibration_data_loaded
  @calibration_data_loaded
end

#measurement_timeObject (readonly)

Returns the value of attribute measurement_time.



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

def measurement_time
  @measurement_time
end

Instance Method Details

#[](key) ⇒ Object



207
208
209
210
211
# File 'lib/denko/sensor/bme280.rb', line 207

def [](key)
  @state_mutex.synchronize do
    return @state[key]
  end
end

#_readObject

Reading Methods



175
176
177
178
179
# File 'lib/denko/sensor/bme280.rb', line 175

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

#after_initialize(options = {}) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/denko/sensor/bme280.rb', line 56

def after_initialize(options={})
  super(options)
  
  # Avoid repeated memory allocation for callback data and state.
  @reading   = { temperature: nil, humidity: nil, pressure: nil }
  self.state = { temperature: nil, humidity: nil, pressure: nil }

  #
  # Setup defaults for the config registers:
  #   Oneshot reading mode
  #   1x sampling for all sensors
  #   500ms standby time in continuous mode
  #   IIR filter off
  #
  @registers = {
    # Bits 0..1 control operating mode.
    # Bits 2..4 control the pressure oversampling factor
    # Bits 5..7 control the temperature oversampling factor.
    f4: 0b00100110,
  
    # Bits 0..1 should always be 0.
    # Bits 2..4 control the IIR filter coefficient.
    # Bits 5..7 control the standby time when in continuous reading mode.
    f5: 0b10000000,
  }
  # Bits 0..2 control the humidity oversampling factor, on BME280 only.
  # Bits 3+ are unused.
  @registers.merge!(f2: 0b00000001) if humidity_available?
  
  @calibration_data_loaded = false
end

#before_initialize(options = {}) ⇒ Object



51
52
53
54
# File 'lib/denko/sensor/bme280.rb', line 51

def before_initialize(options={})
  @i2c_address = 0x76
  super(options)
end

#config_register_bitsObject



164
165
166
167
168
169
170
# File 'lib/denko/sensor/bme280.rb', line 164

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

#continuous_modeObject



114
115
116
117
# File 'lib/denko/sensor/bme280.rb', line 114

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

#decode_humidity(bytes, t_fine) ⇒ Object



263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/denko/sensor/bme280.rb', line 263

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



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/denko/sensor/bme280.rb', line 240

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_reading(bytes) ⇒ Object

Decoding Methods



216
217
218
219
220
221
222
223
224
225
226
# File 'lib/denko/sensor/bme280.rb', line 216

def decode_reading(bytes)
  # 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

#decode_temperature(bytes) ⇒ Object



228
229
230
231
232
233
234
235
236
237
238
# File 'lib/denko/sensor/bme280.rb', line 228

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



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

def get_calibration_data        
  # First group of calibration bytes, sent to #process_calibration_a.
  read_using -> { i2c_read(0x88, 26) }

  # Second group of calibration bytes, only on BME280, sent to #process_calibration_b.
  if humidity_available?
    read_using -> { i2c_read 0xE1, 7 }
  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)


289
290
291
# File 'lib/denko/sensor/bme280.rb', line 289

def humidity_available?
  !self.class.to_s.match(/bmp/i)
end

#humidity_samples=(factor) ⇒ Object

Raises:

  • (ArgumentError)


141
142
143
144
145
146
# File 'lib/denko/sensor/bme280.rb', line 141

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)


148
149
150
151
152
153
# File 'lib/denko/sensor/bme280.rb', line 148

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



91
92
93
94
# File 'lib/denko/sensor/bme280.rb', line 91

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

#pre_callback_filter(data) ⇒ Object



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

def pre_callback_filter(data)
  if data.length == 8
    return decode_reading(data)
  elsif data.length == 26
    process_calibration_a(data)
    return nil
  elsif data.length == 7
    process_calibration_b(data)
    return nil
  else
    # Ignores readings that aren't 8 bytes.
    return nil
  end
end

#pressure_samples=(factor) ⇒ Object

Raises:

  • (ArgumentError)


134
135
136
137
138
139
# File 'lib/denko/sensor/bme280.rb', line 134

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



313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/denko/sensor/bme280.rb', line 313

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



334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/denko/sensor/bme280.rb', line 334

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

#reading_humidity?Boolean

Returns:

  • (Boolean)


282
283
284
285
286
# File 'lib/denko/sensor/bme280.rb', line 282

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)


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

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)


119
120
121
122
123
124
# File 'lib/denko/sensor/bme280.rb', line 119

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

#temperature_samples=(factor) ⇒ Object

Raises:

  • (ArgumentError)


126
127
128
129
130
131
132
# File 'lib/denko/sensor/bme280.rb', line 126

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



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

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



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

def update_state(reading)
  # Checking for Hash ignores calibration data and nil.
  if reading.class == Hash
    @state_mutex.synchronize do
      @state[:temperature] = reading[:temperature]
      @state[:pressure]    = reading[:pressure]
      @state[:humidity]    = reading[:humidity]
    end
  end
end

#write_settingsObject



155
156
157
158
159
160
161
162
# File 'lib/denko/sensor/bme280.rb', line 155

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