# SPDX-FileCopyrightText: Copyright (c) 2023 Jose D. Montoya
#
# SPDX-License-Identifier: MIT
"""
`mma8452q`
================================================================================

MicroPython Driver for the NXP MMA8452Q Accelerometer


* Author(s): Jose D. Montoya


"""

from micropython import const
from micropython_mma8452q.i2c_helpers import CBits, RegisterStruct

try:
    from typing import Tuple
except ImportError:
    pass


__version__ = "0.1.0"
__repo__ = "https://github.com/jposada202020/MicroPython_MMA8452Q.git"

_REG_WHOAMI = const(0x0D)
_DATA = const(0x01)
_XYZ_DATA_CFG = const(0x0E)
_CTRL_REG1 = const(0x2A)
_HP_FILTER_CUTOFF = const(0x0F)

_GRAVITY = 9.80665

RANGE_2G = const(0b00)
RANGE_4G = const(0b01)
RANGE_8G = const(0b10)
scale_range_values = (RANGE_2G, RANGE_4G, RANGE_8G)
scale_conversion = {RANGE_2G: 1024.0, RANGE_4G: 512.0, RANGE_8G: 256.0}

STANDBY_MODE = const(0b0)
ACTIVE_MODE = const(0b1)
operation_mode_values = (STANDBY_MODE, ACTIVE_MODE)

DATARATE_800HZ = const(0b000)
DATARATE_400HZ = const(0b001)
DATARATE_200HZ = const(0b010)
DATARATE_100HZ = const(0b011)
DATARATE_50HZ = const(0b100)
DATARATE_12_5HZ = const(0b101)
DATARATE_6_25HZ = const(0b110)
DATARATE_1_56HZ = const(0b111)
data_rate_values = (
    DATARATE_800HZ,
    DATARATE_400HZ,
    DATARATE_200HZ,
    DATARATE_100HZ,
    DATARATE_50HZ,
    DATARATE_12_5HZ,
    DATARATE_6_25HZ,
    DATARATE_1_56HZ,
)

HPF_DISABLED = const(0b0)
HPF_ENABLED = const(0b1)
high_pass_filter_values = (HPF_DISABLED, HPF_ENABLED)

CUTOFF_16HZ = const(0b00)
CUTOFF_8HZ = const(0b01)
CUTOFF_4HZ = const(0b10)
CUTOFF_2HZ = const(0b11)
high_pass_filter_cutoff_values = (CUTOFF_16HZ, CUTOFF_8HZ, CUTOFF_4HZ, CUTOFF_2HZ)


class MMA8452Q:
    """Driver for the MMA8452Q Sensor connected over I2C.

    :param ~machine.I2C i2c: The I2C bus the MMA8452Q is connected to.
    :param int address: The I2C device address. Defaults to :const:`0x1D`

    :raises RuntimeError: if the sensor is not found

    **Quickstart: Importing and using the device**

    Here is an example of using the :class:`MMA8452Q` class.
    First you will need to import the libraries to use the sensor

    .. code-block:: python

        from machine import Pin, I2C
        from micropython_mma8452q import mma8452q

    Once this is done you can define your `machine.I2C` object and define your sensor object

    .. code-block:: python

        i2c = I2C(1, sda=Pin(2), scl=Pin(3))
        mma8452q = mma8452q.MMA8452Q(i2c)

    Now you have access to the attributes

    .. code-block:: python

    """

    _device_id = RegisterStruct(_REG_WHOAMI, "B")
    _raw_data = RegisterStruct(_DATA, ">hhh")
    _operation_mode = CBits(1, _CTRL_REG1, 0)
    _scale_range = CBits(2, _XYZ_DATA_CFG, 0)
    _data_rate = CBits(3, _CTRL_REG1, 3)

    _high_pass_filter = CBits(1, _XYZ_DATA_CFG, 4)
    _high_pass_filter_cutoff = CBits(2, _HP_FILTER_CUTOFF, 0)

    def __init__(self, i2c, address: int = 0x1C) -> None:
        self._i2c = i2c
        self._address = address

        if self._device_id != 0x2A:
            raise RuntimeError("Failed to find MMA8452Q")

        self._operation_mode = ACTIVE_MODE
        self._scale_range_cached = self._scale_range

    @property
    def acceleration(self) -> Tuple[float, float, float]:
        """
        Acceleration measured by the sensor in :math:`m/s^2`.
        """

        x, y, z = self._raw_data
        x >>= 4
        y >>= 4
        z >>= 4

        divisor = scale_conversion[self._scale_range_cached]

        return x / divisor * _GRAVITY, y / divisor * _GRAVITY, z / divisor * _GRAVITY

    @property
    def operation_mode(self) -> str:
        """
        Sensor operation_mode

        +----------------------------------+-----------------+
        | Mode                             | Value           |
        +==================================+=================+
        | :py:const:`mma8451.STANDBY_MODE` | :py:const:`0b0` |
        +----------------------------------+-----------------+
        | :py:const:`mma8451.ACTIVE_MODE`  | :py:const:`0b1` |
        +----------------------------------+-----------------+
        """
        values = ("STANDBY_MODE", "ACTIVE_MODE")
        return values[self._operation_mode]

    @operation_mode.setter
    def operation_mode(self, value: int) -> None:
        if value not in operation_mode_values:
            raise ValueError("Value must be a valid operation_mode setting")
        self._operation_mode = value

    @property
    def scale_range(self) -> str:
        """
        Sensor scale_range

        +------------------------------+------------------+
        | Mode                         | Value            |
        +==============================+==================+
        | :py:const:`mma8451.RANGE_2G` | :py:const:`0b00` |
        +------------------------------+------------------+
        | :py:const:`mma8451.RANGE_4G` | :py:const:`0b01` |
        +------------------------------+------------------+
        | :py:const:`mma8451.RANGE_8G` | :py:const:`0b10` |
        +------------------------------+------------------+
        """
        values = ("RANGE_2G", "RANGE_4G", "RANGE_8G")
        return values[self._scale_range]

    @scale_range.setter
    def scale_range(self, value: int) -> None:
        if value not in scale_range_values:
            raise ValueError("Value must be a valid scale_range setting")
        self._operation_mode = STANDBY_MODE
        self._scale_range = value
        self._scale_range_cached = value
        self._operation_mode = ACTIVE_MODE

    @property
    def data_rate(self) -> str:
        """
        Sensor data_rate

        +-------------------------------------+-------------------+
        | Mode                                | Value             |
        +=====================================+===================+
        | :py:const:`mma8451.DATARATE_800HZ`  | :py:const:`0b000` |
        +-------------------------------------+-------------------+
        | :py:const:`mma8451.DATARATE_400HZ`  | :py:const:`0b001` |
        +-------------------------------------+-------------------+
        | :py:const:`mma8451.DATARATE_200HZ`  | :py:const:`0b010` |
        +-------------------------------------+-------------------+
        | :py:const:`mma8451.DATARATE_100HZ`  | :py:const:`0b011` |
        +-------------------------------------+-------------------+
        | :py:const:`mma8451.DATARATE_50HZ`   | :py:const:`0b100` |
        +-------------------------------------+-------------------+
        | :py:const:`mma8451.DATARATE_12_5HZ` | :py:const:`0b101` |
        +-------------------------------------+-------------------+
        | :py:const:`mma8451.DATARATE_6_25HZ` | :py:const:`0b110` |
        +-------------------------------------+-------------------+
        | :py:const:`mma8451.DATARATE_1_56HZ` | :py:const:`0b111` |
        +-------------------------------------+-------------------+
        """
        values = (
            "DATARATE_800HZ",
            "DATARATE_400HZ",
            "DATARATE_200HZ",
            "DATARATE_100HZ",
            "DATARATE_50HZ",
            "DATARATE_12_5HZ",
            "DATARATE_6_25HZ",
            "DATARATE_1_56HZ",
        )
        return values[self._data_rate]

    @data_rate.setter
    def data_rate(self, value: int) -> None:
        if value not in data_rate_values:
            raise ValueError("Value must be a valid data_rate setting")
        self._operation_mode = STANDBY_MODE
        self._data_rate = value
        self._operation_mode = ACTIVE_MODE

    @property
    def high_pass_filter(self) -> str:
        """
        Sensor high_pass_filter

        +----------------------------------+-----------------+
        | Mode                             | Value           |
        +==================================+=================+
        | :py:const:`mma8451.HPF_DISABLED` | :py:const:`0b0` |
        +----------------------------------+-----------------+
        | :py:const:`mma8451.HPF_ENABLED`  | :py:const:`0b1` |
        +----------------------------------+-----------------+
        """
        values = ("HPF_DISABLED", "HPF_ENABLED")
        return values[self._high_pass_filter]

    @high_pass_filter.setter
    def high_pass_filter(self, value: int) -> None:
        if value not in high_pass_filter_values:
            raise ValueError("Value must be a valid high_pass_filter setting")
        self._operation_mode = STANDBY_MODE
        self._high_pass_filter = value
        self._operation_mode = ACTIVE_MODE

    @property
    def high_pass_filter_cutoff(self) -> str:
        """
        Sensor high_pass_filter_cutoff sets the high-pass filter cutoff
        frequency for removal of the offset and slower changing
        acceleration data. In order to filter the acceleration data
        :attr:`high_pass_filter` must be enabled.

        +---------------------------------+------------------+
        | Mode                            | Value            |
        +=================================+==================+
        | :py:const:`mma8451.CUTOFF_16HZ` | :py:const:`0b00` |
        +---------------------------------+------------------+
        | :py:const:`mma8451.CUTOFF_8HZ`  | :py:const:`0b01` |
        +---------------------------------+------------------+
        | :py:const:`mma8451.CUTOFF_4HZ`  | :py:const:`0b10` |
        +---------------------------------+------------------+
        | :py:const:`mma8451.CUTOFF_2HZ`  | :py:const:`0b11` |
        +---------------------------------+------------------+
        """
        values = ("CUTOFF_16HZ", "CUTOFF_8HZ", "CUTOFF_4HZ", "CUTOFF_2HZ")
        return values[self._high_pass_filter_cutoff]

    @high_pass_filter_cutoff.setter
    def high_pass_filter_cutoff(self, value: int) -> None:
        if value not in high_pass_filter_cutoff_values:
            raise ValueError("Value must be a valid high_pass_filter_cutoff setting")
        self._operation_mode = STANDBY_MODE
        self._high_pass_filter_cutoff = value
        self._operation_mode = ACTIVE_MODE
