Repository: adafruit/Adafruit_CircuitPython_MSA301 (language: python)

Issue:
Support MSA311
MSA311 is very similar, but has address `0x62` instead of `0x26`. The current MSA301 consturctor does not allow passing in an alternate I2C address.

@ladyada @kattni should we rename this library to MSA3x1 and/or provide an optional I2C address arg? You are familiar with other sensor families that also vary very little between different part numbers.


Discussion / hints:
If it doesn't quake too many users, I am in favor of making the library name cover both MPUs and adding the I2C address parameter.

_(currently, I've just been using a forked copy with the required changes to support the MSA311)_
@bradanlane We would welcome a PR and will rename the library appropriately. Thanks!
or @WhiteMatrix, who has forked this.
My two cents - I like the subclass approach in the @WhiteMatrix fork, since each variant has only one I2C address. I'd also suggest moving these lines from `__init__` into a new function in the base class that gets called from `__init__`. Just a code dupe thing.

```python
        self._disable_x = self._disable_y = self._disable_z = False
        self.power_mode = Mode.NORMAL
        self.data_rate = DataRate.RATE_500_HZ
        self.bandwidth = BandWidth.WIDTH_250_HZ
        self.range = Range.RANGE_4_G
        self.resolution = Resolution.RESOLUTION_14_BIT
        self._tap_count = 0
```
Hello,
the subclass idea is taken from [AdafruitSensor library](https://adafruit.github.io/Adafruit_MSA301/html/class_adafruit___m_s_a311.html). But I don't know if that's the correct way in CircuitPython, more common thing I see is an extra `address` parameter in the constructor.
I can extract the `__init__` function and make a pull request here. But I should I update everything else to say 
`Adafruit_MSA3x1`, like the library file itself and the docs? I rarely do open-source. Should the authors header be updated?
Also, the sensor isn't exactly the same, in the acceleration ranges the sensitivity differs between two ICs, there are some differences in the register map, but just by using the existing code the readings seem correct and tap detection works, but I don't have a MSA301 to compare.

Fix patch:
diff --git a/README.rst b/README.rst
index 3c54ec6..d625a6f 100644
--- a/README.rst
+++ b/README.rst
@@ -17,7 +17,7 @@ Introduction
     :target: https://github.com/psf/black
     :alt: Code Style: Black
 
-CircuitPython library for the MSA301 Accelerometer
+CircuitPython library for the MSA301/MSA311 Accelerometers
 
 
 Dependencies
@@ -63,10 +63,10 @@ Usage Example
 
     import time
     import board
-    import adafruit_msa301
+    from adafruit_msa3xx import MSA301
 
     i2c = board.I2C()  # uses board.SCL and board.SDA
-    msa = adafruit_msa301.MSA301(i2c)
+    msa = MSA301(i2c)
 
     while True:
         print("%f %f %f"%msa.acceleration)
diff --git a/adafruit_msa301.py b/adafruit_msa3xx.py
similarity index 80%
rename from adafruit_msa301.py
rename to adafruit_msa3xx.py
index 1b62c22..870e9f3 100644
--- a/adafruit_msa301.py
+++ b/adafruit_msa3xx.py
@@ -3,10 +3,10 @@
 # SPDX-License-Identifier: MIT
 
 """
-`MSA301`
+`adafruit_msa3xx`
 ================================================================================
 
-CircuitPython library for the MSA301 Accelerometer
+CircuitPython library for the MSA301 and MSA311 Accelerometers
 
 
 * Author(s): Bryan Siepert
@@ -18,6 +18,8 @@
 
 * Adafruit `MSA301 Triple Axis Accelerometer
   <https://www.adafruit.com/product/4344>`_
+* Adafruit `MSA311 Triple Axis Accelerometer
+  <https://www.adafruit.com/product/5309>`_
 
 **Software and Dependencies:**
 
@@ -39,25 +41,26 @@
 import adafruit_bus_device.i2c_device as i2cdevice
 
 _MSA301_I2CADDR_DEFAULT = const(0x26)
-
-_MSA301_REG_PARTID = const(0x01)
-_MSA301_REG_OUT_X_L = const(0x02)
-_MSA301_REG_OUT_X_H = const(0x03)
-_MSA301_REG_OUT_Y_L = const(0x04)
-_MSA301_REG_OUT_Y_H = const(0x05)
-_MSA301_REG_OUT_Z_L = const(0x06)
-_MSA301_REG_OUT_Z_H = const(0x07)
-_MSA301_REG_MOTIONINT = const(0x09)
-_MSA301_REG_DATAINT = const(0x0A)
-_MSA301_REG_RESRANGE = const(0x0F)
-_MSA301_REG_ODR = const(0x10)
-_MSA301_REG_POWERMODE = const(0x11)
-_MSA301_REG_INTSET0 = const(0x16)
-_MSA301_REG_INTSET1 = const(0x17)
-_MSA301_REG_INTMAP0 = const(0x19)
-_MSA301_REG_INTMAP1 = const(0x1A)
-_MSA301_REG_TAPDUR = const(0x2A)
-_MSA301_REG_TAPTH = const(0x2B)
+_MSA311_I2CADDR_DEFAULT = const(0x62)
+
+_REG_PARTID = const(0x01)
+_REG_OUT_X_L = const(0x02)
+_REG_OUT_X_H = const(0x03)
+_REG_OUT_Y_L = const(0x04)
+_REG_OUT_Y_H = const(0x05)
+_REG_OUT_Z_L = const(0x06)
+_REG_OUT_Z_H = const(0x07)
+_REG_MOTIONINT = const(0x09)
+_REG_DATAINT = const(0x0A)
+_REG_RESRANGE = const(0x0F)
+_REG_ODR = const(0x10)
+_REG_POWERMODE = const(0x11)
+_REG_INTSET0 = const(0x16)
+_REG_INTSET1 = const(0x17)
+_REG_INTMAP0 = const(0x19)
+_REG_INTMAP1 = const(0x1A)
+_REG_TAPDUR = const(0x2A)
+_REG_TAPTH = const(0x2B)
 
 
 _STANDARD_GRAVITY = 9.806
@@ -200,45 +203,38 @@ class TapDuration:  # pylint: disable=too-few-public-methods,too-many-instance-a
     DURATION_700_MS = 0b111  # < 50 millis700 millis
 
 
-class MSA301:  # pylint: disable=too-many-instance-attributes
-    """Driver for the MSA301 Accelerometer.
-
-    :param ~busio.I2C i2c_bus: The I2C bus the MSA is connected to.
-
-
-    **Quickstart: Importing and using the device**
-
-        Here is an example of using the :class:`MSA301` class.
-        First you will need to import the libraries to use the sensor
-
-        .. code-block:: python
-
-            import board
-            import adafruit_msa301
-
-        Once this is done you can define your `board.I2C` object and define your sensor object
-
-        .. code-block:: python
-
-            i2c = board.I2C()  # uses board.SCL and board.SDA
-            msa = adafruit_msa301.MSA301(i2c)
-
-        Now you have access to the :attr:`acceleration` attribute
+class MSA3XX:  # pylint: disable=too-many-instance-attributes
+    """Base driver class for the MSA301/311 Accelerometers."""
 
-        .. code-block:: python
+    _part_id = ROUnaryStruct(_REG_PARTID, "<B")
 
-            acc_x, acc_y, acc_z = msa.acceleration
+    _disable_x = RWBit(_REG_ODR, 7)
+    _disable_y = RWBit(_REG_ODR, 6)
+    _disable_z = RWBit(_REG_ODR, 5)
 
-    """
+    # _xyz_raw = ROBits(48, _REG_OUT_X_L, 0, 6)
+    _xyz_raw = Struct(_REG_OUT_X_L, "<hhh")
 
-    _part_id = ROUnaryStruct(_MSA301_REG_PARTID, "<B")
+    # tap INT enable and status
+    _single_tap_int_en = RWBit(_REG_INTSET0, 5)
+    _double_tap_int_en = RWBit(_REG_INTSET0, 4)
+    _motion_int_status = ROUnaryStruct(_REG_MOTIONINT, "B")
 
-    def __init__(self, i2c_bus):
-        self.i2c_device = i2cdevice.I2CDevice(i2c_bus, _MSA301_I2CADDR_DEFAULT)
+    # tap interrupt knobs
+    _tap_quiet = RWBit(_REG_TAPDUR, 7)
+    _tap_shock = RWBit(_REG_TAPDUR, 6)
+    _tap_duration = RWBits(3, _REG_TAPDUR, 0)
+    _tap_threshold = RWBits(5, _REG_TAPTH, 0)
+    reg_tapdur = ROUnaryStruct(_REG_TAPDUR, "B")
 
-        if self._part_id != 0x13:
-            raise AttributeError("Cannot find a MSA301")
+    # general settings knobs
+    power_mode = RWBits(2, _REG_POWERMODE, 6)
+    bandwidth = RWBits(4, _REG_POWERMODE, 1)
+    data_rate = RWBits(4, _REG_ODR, 0)
+    range = RWBits(2, _REG_RESRANGE, 0)
+    resolution = RWBits(2, _REG_RESRANGE, 2)
 
+    def __init__(self):
         self._disable_x = self._disable_y = self._disable_z = False
         self.power_mode = Mode.NORMAL
         self.data_rate = DataRate.RATE_500_HZ
@@ -247,32 +243,6 @@ def __init__(self, i2c_bus):
         self.resolution = Resolution.RESOLUTION_14_BIT
         self._tap_count = 0
 
-    _disable_x = RWBit(_MSA301_REG_ODR, 7)
-    _disable_y = RWBit(_MSA301_REG_ODR, 6)
-    _disable_z = RWBit(_MSA301_REG_ODR, 5)
-
-    # _xyz_raw = ROBits(48, _MSA301_REG_OUT_X_L, 0, 6)
-    _xyz_raw = Struct(_MSA301_REG_OUT_X_L, "<hhh")
-
-    # tap INT enable and status
-    _single_tap_int_en = RWBit(_MSA301_REG_INTSET0, 5)
-    _double_tap_int_en = RWBit(_MSA301_REG_INTSET0, 4)
-    _motion_int_status = ROUnaryStruct(_MSA301_REG_MOTIONINT, "B")
-
-    # tap interrupt knobs
-    _tap_quiet = RWBit(_MSA301_REG_TAPDUR, 7)
-    _tap_shock = RWBit(_MSA301_REG_TAPDUR, 6)
-    _tap_duration = RWBits(3, _MSA301_REG_TAPDUR, 0)
-    _tap_threshold = RWBits(5, _MSA301_REG_TAPTH, 0)
-    reg_tapdur = ROUnaryStruct(_MSA301_REG_TAPDUR, "B")
-
-    # general settings knobs
-    power_mode = RWBits(2, _MSA301_REG_POWERMODE, 6)
-    bandwidth = RWBits(4, _MSA301_REG_POWERMODE, 1)
-    data_rate = RWBits(4, _MSA301_REG_ODR, 0)
-    range = RWBits(2, _MSA301_REG_RESRANGE, 0)
-    resolution = RWBits(2, _MSA301_REG_RESRANGE, 2)
-
     @property
     def acceleration(self):
         """The x, y, z acceleration values returned in a
@@ -368,3 +338,33 @@ def tapped(self):
             return True
 
         return False
+
+
+class MSA301(MSA3XX):
+    """Driver for the MSA301 Accelerometer.
+
+    :param ~busio.I2C i2c_bus: The I2C bus the MSA is connected to.
+    """
+
+    def __init__(self, i2c_bus):
+        self.i2c_device = i2cdevice.I2CDevice(i2c_bus, _MSA301_I2CADDR_DEFAULT)
+
+        if self._part_id != 0x13:
+            raise AttributeError("Cannot find a MSA301")
+
+        super().__init__()
+
+
+class MSA311(MSA3XX):
+    """Driver for the MSA311 Accelerometer.
+
+    :param ~busio.I2C i2c_bus: The I2C bus the MSA is connected to.
+    """
+
+    def __init__(self, i2c_bus):
+        self.i2c_device = i2cdevice.I2CDevice(i2c_bus, _MSA311_I2CADDR_DEFAULT)
+
+        if self._part_id != 0x13:
+            raise AttributeError("Cannot find a MSA311")
+
+        super().__init__()
diff --git a/docs/api.rst b/docs/api.rst
index b6bc9eb..b4be273 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -4,5 +4,5 @@
 .. If your library file(s) are nested in a directory (e.g. /adafruit_foo/foo.py)
 .. use this format as the module name: "adafruit_foo.foo"
 
-.. automodule:: adafruit_msa301
+.. automodule:: adafruit_msa3xx
    :members:
diff --git a/docs/index.rst b/docs/index.rst
index f1c0b77..2030ff2 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -29,6 +29,7 @@ Table of Contents
     :caption: Related Products
 
     Adafruit MSA301 Triple Axis Accelerometer <https://www.adafruit.com/product/4344>
+    Adafruit MSA311 Triple Axis Accelerometer <https://www.adafruit.com/product/5309>
 
 .. toctree::
     :caption: Other Links
diff --git a/examples/msa301_tap_example.py b/examples/msa301_tap_example.py
index 72b6967..bb1dc03 100644
--- a/examples/msa301_tap_example.py
+++ b/examples/msa301_tap_example.py
@@ -3,10 +3,10 @@
 
 import time
 import board
-import adafruit_msa301
+from adafruit_msa3xx import MSA301
 
 i2c = board.I2C()  # uses board.SCL and board.SDA
-msa = adafruit_msa301.MSA301(i2c)
+msa = MSA301(i2c)
 
 msa.enable_tap_detection()
 
diff --git a/pyproject.toml b/pyproject.toml
index b94ec09..2c845c0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -11,7 +11,7 @@ requires = [
 
 [project]
 name = "adafruit-circuitpython-msa301"
-description = "CircuitPython library for the MSA301 Accelerometer"
+description = "CircuitPython library for the MSA301/MSA311 Accelerometers"
 version = "0.0.0+auto.0"
 readme = "README.rst"
 authors = [
@@ -24,6 +24,7 @@ keywords = [
     "circuitpython",
     "micropython",
     "msa301",
+    "msa311",
     "accelerometer",
     "msa",
 ]
@@ -39,7 +40,7 @@ classifiers = [
 dynamic = ["dependencies", "optional-dependencies"]
 
 [tool.setuptools]
-py-modules = ["adafruit_msa301"]
+py-modules = ["adafruit_msa3xx"]
 
 [tool.setuptools.dynamic]
 dependencies = {file = ["requirements.txt"]}


Test patch:
diff --git a/examples/msa301_simpletest.py b/examples/msa301_simpletest.py
index 8ea9b54..93fc3bf 100755
--- a/examples/msa301_simpletest.py
+++ b/examples/msa301_simpletest.py
@@ -3,10 +3,10 @@
 
 import time
 import board
-import adafruit_msa301
+from adafruit_msa3xx import MSA301
 
 i2c = board.I2C()  # uses board.SCL and board.SDA
-msa = adafruit_msa301.MSA301(i2c)
+msa = MSA301(i2c)
 
 while True:
     print("%f %f %f" % msa.acceleration)
diff --git a/examples/msa311_simpletest.py b/examples/msa311_simpletest.py
new file mode 100644
index 0000000..9bd475a
--- /dev/null
+++ b/examples/msa311_simpletest.py
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
+# SPDX-License-Identifier: MIT
+
+import time
+import board
+from adafruit_msa3xx import MSA311
+
+i2c = board.I2C()  # uses board.SCL and board.SDA
+msa = MSA311(i2c)
+
+while True:
+    print("%f %f %f" % msa.acceleration)
+    time.sleep(0.5)