Repository: adafruit/Adafruit_CircuitPython_SI7021 (language: python)

Issue:
Heater control not implemented
the proccess in 5.2 of this https://www.silabs.com/documents/public/data-sheets/Si7021-A20.pdf

Arduino has it


Discussion / hints:

Possible new property interface?
```python
# get heater state
si7021.heater_level
# set heater on
si7021.heater_level = 0  # on levels 0 (min) to 15 (max)
# set heater off
si7021.heaterl_level = None
```
Looks like you can turn the heater on or off without changing the heater level.
Yep, it can. The suggestion above combines turning on/off and setting level in one step, with the goal of trying to be as simple as possible - i.e. it's one line of code.

Would separate on/off and level control be more useful? I think it would require two properties:
```python
si7021.heater_level = 5
si7021.heater_enabled = True
```

Fix patch:
diff --git a/adafruit_si7021.py b/adafruit_si7021/__init__.py
similarity index 80%
rename from adafruit_si7021.py
rename to adafruit_si7021/__init__.py
index 1d0e6d0..02cbe91 100644
--- a/adafruit_si7021.py
+++ b/adafruit_si7021/__init__.py
@@ -1,4 +1,4 @@
-# SPDX-FileCopyrightText: 2017 Radomir Dopieralski for Adafruit Industries
+# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries
 #
 # SPDX-License-Identifier: MIT
 
@@ -28,12 +28,17 @@
 
 from adafruit_bus_device.i2c_device import I2CDevice
 from micropython import const
+from adafruit_si7021.i2c_bits import _RWDifferentBit, _RWDifferentBits
 
 __version__ = "0.0.0-auto.0"
 __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_SI7021.git"
 
 HUMIDITY = const(0xF5)
 TEMPERATURE = const(0xF3)
+WRITE_HEATER_LEVEL = const(0x51)
+READ_HEATER_LEVEL = const(0x11)
+WRITE_HEATER_ENABLE = const(0xE6)
+READ_HEATER_ENABLE = const(0xE7)
 _RESET = const(0xFE)
 _READ_USER1 = const(0xE7)
 _USER1_VAL = const(0x3A)
@@ -120,6 +125,9 @@ class SI7021:
 
     """
 
+    _heater_enable = _RWDifferentBit(READ_HEATER_ENABLE, WRITE_HEATER_ENABLE, 2)
+    _heater_level = _RWDifferentBits(4, READ_HEATER_LEVEL, WRITE_HEATER_LEVEL, 0)
+
     def __init__(self, i2c_bus, address=0x40):
         self.i2c_device = I2CDevice(i2c_bus, address)
         self._command(_RESET)
@@ -138,6 +146,8 @@ def __init__(self, i2c_bus, address=0x40):
         if value != _USER1_VAL:
             raise RuntimeError("bad USER1 register (%x!=%x)" % (value, _USER1_VAL))
         self._measurement = 0
+        self._heater_level = 0
+        self.heater_level = 0
 
     def _command(self, command):
         with self.i2c_device as i2c:
@@ -177,6 +187,47 @@ def temperature(self):
         self._measurement = 0
         return value * 175.72 / 65536.0 - 46.85
 
+    @property
+    def heater_enable(self):
+        """Whether or not the heater is enabled"""
+        return self.heater_enable
+
+    @heater_enable.setter
+    def heater_enable(self, setting):
+        if not isinstance(setting, bool):
+            raise TypeError("Setting must be True (enable) or False (disable)")
+        self._heater_enable = setting
+
+    @property
+    def heater_level(self):
+        """The heater level of the integrated resistive heating element.  Per
+        the data sheet, the levels correspond to the following current draws:
+
+        ============  =================
+        Heater Level  Current Draw (mA)
+        ============  =================
+        0             3.09
+        1             9.18
+        2             15.24
+        .             .
+        4             27.39
+        .             .
+        8             51.69
+        .             .
+        15            94.20
+        ============  =================
+
+        """
+        return self._heater_level
+
+    @heater_level.setter
+    def heater_level(self, level):
+        if not isinstance(level, int):
+            raise TypeError("Heater level must be int between 0 and 15, inclusive")
+        if not 0 <= level < 16:
+            raise ValueError("Heater level must be int between 0 and 15, inclusive")
+        self._heater_level = level
+
     def start_measurement(self, what):
         """
         Starts a measurement.
diff --git a/adafruit_si7021/i2c_bits.py b/adafruit_si7021/i2c_bits.py
new file mode 100644
index 0000000..52867e5
--- /dev/null
+++ b/adafruit_si7021/i2c_bits.py
@@ -0,0 +1,105 @@
+# SPDX-FileCopyrightText: 2017 Radomir Dopieralski for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+"""
+Version of adafruit_register classes that account for the read
+and write registers being different
+"""
+
+# pylint: disable=too-many-arguments
+class _RWDifferentBit:
+    def __init__(
+        self,
+        read_register_address,
+        write_register_address,
+        bit,
+        register_width=1,
+        lsb_first=True,
+    ):
+        self._read_register = read_register_address
+        self._write_register = write_register_address
+        self.bit_mask = 1 << (bit % 8)  # the bitmask *within* the byte!
+        self.buffer = bytearray(1 + register_width)
+        if lsb_first:
+            self.byte = bit // 8 + 1  # the byte number within the buffer
+        else:
+            self.byte = register_width - (bit // 8)  # the byte number within the buffer
+
+    def __get__(self, obj, objtype=None):
+        self.buffer[0] = self._read_register
+        with obj.i2c_device as i2c:
+            i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1)
+        return bool(self.buffer[self.byte] & self.bit_mask)
+
+    def __set__(self, obj, value):
+        self.buffer[0] = self._read_register
+        with obj.i2c_device as i2c:
+            i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1)
+            if value:
+                self.buffer[self.byte] |= self.bit_mask
+            else:
+                self.buffer[self.byte] &= ~self.bit_mask
+            self.buffer[0] = self._write_register
+            i2c.write(self.buffer)
+
+
+# pylint: disable=too-many-arguments
+class _RWDifferentBits:
+    def __init__(  # pylint: disable=too-many-arguments
+        self,
+        num_bits,
+        read_register_address,
+        write_register_address,
+        lowest_bit,
+        register_width=1,
+        lsb_first=True,
+        signed=False,
+    ):
+        self.bit_mask = ((1 << num_bits) - 1) << lowest_bit
+        # print("bitmask: ",hex(self.bit_mask))
+        if self.bit_mask >= 1 << (register_width * 8):
+            raise ValueError("Cannot have more bits than register size")
+        self._read_register = read_register_address
+        self._write_register = write_register_address
+        self.lowest_bit = lowest_bit
+        self.buffer = bytearray(1 + register_width)
+        self.lsb_first = lsb_first
+        self.sign_bit = (1 << (num_bits - 1)) if signed else 0
+
+    def __get__(self, obj, objtype=None):
+        self.buffer[0] = self._read_register
+        with obj.i2c_device as i2c:
+            i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1)
+        # read the number of bytes into a single variable
+        reg = 0
+        order = range(len(self.buffer) - 1, 0, -1)
+        if not self.lsb_first:
+            order = reversed(order)
+        for i in order:
+            reg = (reg << 8) | self.buffer[i]
+        reg = (reg & self.bit_mask) >> self.lowest_bit
+        # If the value is signed and negative, convert it
+        if reg & self.sign_bit:
+            reg -= 2 * self.sign_bit
+        return reg
+
+    def __set__(self, obj, value):
+        self.buffer[0] = self._read_register
+        value <<= self.lowest_bit  # shift the value over to the right spot
+        with obj.i2c_device as i2c:
+            i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1)
+            reg = 0
+            order = range(len(self.buffer) - 1, 0, -1)
+            if not self.lsb_first:
+                order = range(1, len(self.buffer))
+            for i in order:
+                reg = (reg << 8) | self.buffer[i]
+            # print("old reg: ", hex(reg))
+            reg &= ~self.bit_mask  # mask off the bits we're about to change
+            reg |= value  # then or in our new value
+            # print("new reg: ", hex(reg))
+            for i in reversed(order):
+                self.buffer[i] = reg & 0xFF
+                reg >>= 8
+            self.buffer[0] = self._write_register
+            i2c.write(self.buffer)
diff --git a/setup.py b/setup.py
index e740337..01b48c2 100644
--- a/setup.py
+++ b/setup.py
@@ -34,7 +34,10 @@
     # Author details
     author="Adafruit Industries",
     author_email="circuitpython@adafruit.com",
-    install_requires=["Adafruit-Blinka", "adafruit-circuitpython-busdevice"],
+    install_requires=[
+        "Adafruit-Blinka",
+        "adafruit-circuitpython-busdevice",
+    ],
     # Choose your license
     license="MIT",
     # See https://pypi.python.org/pypi?%3Aaction=list_classifiers


Test patch:
diff --git a/examples/si7021_simpletest.py b/examples/si7021_simpletest.py
index a951704..46b6af0 100644
--- a/examples/si7021_simpletest.py
+++ b/examples/si7021_simpletest.py
@@ -11,6 +11,12 @@
 # Create library object using our Bus I2C port
 sensor = adafruit_si7021.SI7021(board.I2C())
 
+# If you'd like to use the heater, you can uncomment the code below
+# and pick a heater level that works for your purposes
+#
+# sensor.heater_enable = True
+# sensor.heater_level = 0  # Use any level from 0 to 15 inclusive
+
 while True:
     print("\nTemperature: %0.1f C" % sensor.temperature)
     print("Humidity: %0.1f %%" % sensor.relative_humidity)