Skip to content

add set_gas_heater(); Was available on arduino but not CircuitPython #61

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions LICENSES/BSD-3-Clause.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
BOSCH LICENSE

Copyright (c) 2021 Bosch Sensortec GmbH. All rights reserved.

BSD-3-Clause

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

@file bme68x_defs.h
@date 2021-05-24
@version v4.4.6
Comment on lines +34 to +36
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these should be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay

255 changes: 254 additions & 1 deletion adafruit_bme680.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# SPDX-FileCopyrightText: 2017 ladyada for Adafruit Industries
#
# SPDX-License-Identifier: MIT
# SPDX-License-Identifier: MIT AND BSD-3-Clause

# We have a lot of attributes for this complex sensor.
# pylint: disable=too-many-instance-attributes
# pylint: disable=no_self_use
# pylint: disable=consider-using-f-string

"""
`adafruit_bme680`
Expand All @@ -14,6 +16,10 @@

* Author(s): Limor Fried


original Adafruit_BME680 with addition of set_gas_heater(). Other new members are private.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be removed, I think we can just use the GitHub release to announce things similar to a changelog.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay



Implementation Notes
--------------------

Expand All @@ -33,6 +39,12 @@
import math
from micropython import const


def delay_microseconds(nusec):
"""HELP must be same as dev->delay_us"""
time.sleep(nusec / 1000000.0)


try:
# Used only for type annotations.

Expand All @@ -49,6 +61,30 @@
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BME680.git"


# garberw added begin ===========================
# I2C ADDRESS/BITS/SETTINGS NEW
# -----------------------------------------------------------------------
_BME68X_ENABLE_HEATER = const(0x00)
_BME68X_DISABLE_HEATER = const(0x01)
_BME68X_DISABLE_GAS_MEAS = const(0x00)
_BME68X_ENABLE_GAS_MEAS_L = const(0x01)
_BME68X_ENABLE_GAS_MEAS_H = const(0x02)
_BME68X_SLEEP_MODE = const(0)
_BME68X_FORCED_MODE = const(1)
_BME68X_VARIANT_GAS_LOW = const(0x00)
_BME68X_VARIANT_GAS_HIGH = const(0x01)
_BME68X_HCTRL_MSK = const(0x08)
_BME68X_HCTRL_POS = const(3)
_BME68X_NBCONV_MSK = const(0x0F)
_BME68X_RUN_GAS_MSK = const(0x30)
_BME68X_RUN_GAS_POS = const(4)
_BME68X_MODE_MSK = const(0x03)
_BME68X_PERIOD_POLL = const(10000)
_BME68X_REG_CTRL_GAS_0 = const(0x70)
_BME68X_REG_CTRL_GAS_1 = const(0x71)


# garberw added end ===========================
# I2C ADDRESS/BITS/SETTINGS
# -----------------------------------------------------------------------
_BME680_CHIPID = const(0x61)
Expand Down Expand Up @@ -116,6 +152,43 @@
)


# garberw added begin ===========================
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be removed since it's a contextual comment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a few comments like these.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they were just meant to highlight changes. remove them for sure.

INT32 = int
INT16 = int
INT8 = int
UINT32 = int
UINT16 = int
UINT8 = int
Comment on lines +156 to +161
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think using int in all applicable places is fine since there isn't really a distinct "typewise" within Python. If needed, docstrings should explain limits (e.g., 0 - 255). That will save us using type aliases where not strictly needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay



def bme_set_bits(reg_data, bitname_msk, bitname_pos, data):
"""
Macro to set bits
data2 = data << bitname_pos
set masked bits from data2 in reg_data
"""
return (reg_data & ~bitname_msk) | ((data << bitname_pos) & bitname_msk)


def bme_set_bits_pos_0(reg_data, bitname_msk, data):
"""
Macro to set bits starting from position 0
set masked bits from data in reg_data
"""
return (reg_data & ~bitname_msk) | (data & bitname_msk)


class GasHeaterException(Exception):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a hyperspecific exception that could just be replaced with something like OSError, most likely.

"""
Error during set_gas_heater()
"""

def __init__(self, msg="GasHeaterException default"):
self.msg = msg
super().__init__(msg)


# garberw added end ===========================
def _read24(arr: ReadableBuffer) -> float:
"""Parse an unsigned 24-bit value as a floating point and return it."""
ret = 0.0
Expand Down Expand Up @@ -171,6 +244,12 @@ def __init__(self, *, refresh_rate: int = 10) -> None:
self._last_reading = 0
self._min_refresh_time = 1 / refresh_rate

# garberw added begin ===========================
self._amb_temp = 25 # Copy required parameters from reference bme68x_dev struct
self.set_gas_heater(320, 150) # heater 320 deg C for 150 msec

# garberw added end ===========================

@property
def pressure_oversample(self) -> int:
"""The oversampling for pressure sensor"""
Expand Down Expand Up @@ -403,6 +482,180 @@ def _read(self, register: int, length: int) -> bytearray:
def _write(self, register: int, values: bytearray) -> None:
raise NotImplementedError()

# garberw added begin ===========================
def set_gas_heater(self, heater_temp: UINT16, heater_time: UINT16) -> bool:
"""
* @brief Enable and configure gas reading + heater
* @param heater_temp
* Desired temperature in degrees Centigrade
* @param heater_time
* Time to keep heater on in milliseconds
* @return True on success, False on failure
Comment on lines +488 to +493
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the Arduino-style docstrings, so instances of these will need to be converted to the typical style for sphinx.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correct

"""
if (heater_temp == 0) or (heater_time == 0):
return False
Comment on lines +495 to +496
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be:

Suggested change
if (heater_temp == 0) or (heater_time == 0):
return False
if not heater_temp or not heater_time:
return False

# enable = BME68X_ENABLE
try:
self._set_heatr_conf(heater_temp, heater_time)
except GasHeaterException:
return False
return True

def _set_heatr_conf(self, heater_temp: UINT16, heater_time: UINT16) -> None:
# restrict to BME68X_FORCED_MODE
op_mode: UINT8 = _BME68X_FORCED_MODE
# restrict to enable = True
enable: bool = True
nb_conv: UINT8 = 0
hctrl: UINT8 = _BME68X_ENABLE_HEATER
run_gas: UINT8 = 0
ctrl_gas_data_0: UINT8 = 0
ctrl_gas_data_1: UINT8 = 0
ctrl_gas_addr_0: UINT8 = _BME68X_REG_CTRL_GAS_0
ctrl_gas_addr_1: UINT8 = _BME68X_REG_CTRL_GAS_1
Comment on lines +506 to +515
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is using a lot of local variables where using what they equate to might be more efficient. This comment holds for the other methods as well.

try:
self._set_op_mode(_BME68X_SLEEP_MODE)
self._set_conf(heater_temp, heater_time, op_mode)
ctrl_gas_data_0 = self._read_byte(ctrl_gas_addr_0)
ctrl_gas_data_1 = self._read_byte(ctrl_gas_addr_1)
if enable:
hctrl = _BME68X_ENABLE_HEATER
if self._chip_variant == _BME68X_VARIANT_GAS_HIGH:
run_gas = _BME68X_ENABLE_GAS_MEAS_H
else:
run_gas = _BME68X_ENABLE_GAS_MEAS_L
else:
hctrl = _BME68X_DISABLE_HEATER
run_gas = _BME68X_DISABLE_GAS_MEAS

ctrl_gas_data_0 = bme_set_bits(
ctrl_gas_data_0, _BME68X_HCTRL_MSK, _BME68X_HCTRL_POS, hctrl
)
ctrl_gas_data_1 = bme_set_bits_pos_0(
ctrl_gas_data_1, _BME68X_NBCONV_MSK, nb_conv
)
ctrl_gas_data_1 = bme_set_bits(
ctrl_gas_data_1, _BME68X_RUN_GAS_MSK, _BME68X_RUN_GAS_POS, run_gas
)
self._write(ctrl_gas_addr_0, [ctrl_gas_data_0])
self._write(ctrl_gas_addr_1, [ctrl_gas_data_1])
# HELP check this
self._set_op_mode(_BME68X_FORCED_MODE)
except GasHeaterException as exc:
self._set_op_mode(_BME68X_FORCED_MODE)
raise exc

def _set_op_mode(self, op_mode: UINT8) -> None:
"""
* @brief This API is used to set the operation mode of the sensor
"""
tmp_pow_mode: UINT8 = 0
pow_mode: UINT8 = _BME68X_FORCED_MODE
reg_addr: UINT8 = _BME680_REG_CTRL_MEAS
# Call until in sleep
try:
# was a do {} while() loop
while pow_mode != _BME68X_SLEEP_MODE:
tmp_pow_mode = self._read_byte(_BME680_REG_CTRL_MEAS)
# Put to sleep before changing mode
pow_mode = tmp_pow_mode & _BME68X_MODE_MSK
if pow_mode != _BME68X_SLEEP_MODE:
tmp_pow_mode &= ~_BME68X_MODE_MSK # Set to sleep
self._write(reg_addr, [tmp_pow_mode])
# dev->delay_us(_BME68X_PERIOD_POLL, dev->intf_ptr) # HELP
delay_microseconds(_BME68X_PERIOD_POLL)
# Already in sleep
if op_mode != _BME68X_SLEEP_MODE:
tmp_pow_mode = (tmp_pow_mode & ~_BME68X_MODE_MSK) | (
op_mode & _BME68X_MODE_MSK
)
self._write(reg_addr, [tmp_pow_mode])
except GasHeaterException as exc:
raise exc
Comment on lines +573 to +574
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the exception is just being passed through, I don't think there's a need to try the above block. There's another instance of this below as well.


def _set_conf(
self, heater_temp: UINT16, heater_time: UINT16, op_mode: UINT8
) -> None:
"""
This internal API is used to set heater configurations
"""
try:
if op_mode != _BME68X_FORCED_MODE:
raise GasHeaterException("_set_conf not forced mode")
rh_reg_addr: UINT8 = _BME680_BME680_RES_HEAT_0
rh_reg_data: UINT8 = self._calc_res_heat(heater_temp)
gw_reg_addr: UINT8 = _BME680_BME680_GAS_WAIT_0
gw_reg_data: UINT8 = self._calc_gas_wait(heater_time)
self._write(rh_reg_addr, [rh_reg_data])
self._write(gw_reg_addr, [gw_reg_data])
except GasHeaterException as exc:
raise exc

def _calc_res_heat(self, temp: UINT16) -> UINT8:
"""
This internal API is used to calculate the heater resistance value using float
"""
gh1: INT8 = self._gas_calibration[0]
gh2: INT16 = self._gas_calibration[1]
gh3: INT8 = self._gas_calibration[2]
htr: UINT8 = self._heat_range
htv: INT8 = self._heat_val
amb: UINT8 = self._amb_temp

temp = min(temp, 400) # Cap temperature

var1: INT32 = ((INT32(amb) * gh3) / 1000) * 256
var2: INT32 = (gh1 + 784) * (
((((gh2 + 154009) * temp * 5) / 100) + 3276800) / 10
)
var3: INT32 = var1 + (var2 / 2)
var4: INT32 = var3 / (htr + 4)
var5: INT32 = (131 * htv) + 65536
heatr_res_x100: INT32 = INT32(((var4 / var5) - 250) * 34)
heatr_res: UINT8 = UINT8((heatr_res_x100 + 50) / 100)

return heatr_res

def _calc_res_heat(self, temp: UINT16) -> UINT8:
"""
This internal API is used to calculate the heater resistance value
"""
gh1: float = float(self._gas_calibration[0])
gh2: float = float(self._gas_calibration[1])
gh3: float = float(self._gas_calibration[2])
htr: float = float(self._heat_range)
htv: float = float(self._heat_val)
amb: float = float(self._amb_temp)

temp = min(temp, 400) # Cap temperature

var1: float = (gh1 / (16.0)) + 49.0
var2: float = ((gh2 / (32768.0)) * (0.0005)) + 0.00235
var3: float = gh3 / (1024.0)
var4: float = var1 * (1.0 + (var2 * float(temp)))
var5: float = var4 + (var3 * amb)
res_heat: UINT8 = UINT8(
3.4 * ((var5 * (4 / (4 + htr)) * (1 / (1 + (htv * 0.002)))) - 25)
)
return res_heat

def _calc_gas_wait(self, dur: UINT16) -> UINT8:
"""
This internal API is used to calculate the gas wait
"""
factor: UINT8 = 0
durval: UINT8 = 0xFF # Max duration

if dur < 0xFC0:
return durval
while dur > 0x3F:
dur = dur / 4
factor += 1
durval = UINT8(dur + (factor * 64))
return durval

# garberw added end ===========================


class Adafruit_BME680_I2C(Adafruit_BME680):
"""Driver for I2C connected BME680.
Expand Down
11 changes: 11 additions & 0 deletions contributors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# SPDX-FileCopyrightText: 2017 ladyada for Adafruit Industries
#
# SPDX-License-Identifier: MIT

Contributors to Adafruit_CircuitPython_BME680_modified
=============================================================

Limor (Ladyada) Fried

William Garber
many others
Comment on lines +1 to +11
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth just adding contributors' names to the SPDX headers as opposed to using a contribuitors file (for consistency reason).