Files
PiTemperatureSensor/mcp9808/mcp9808.py
2023-11-12 10:30:29 +01:00

322 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# https://github.com/jposada202020/MicroPython_MCP9808/blob/master/micropython_mcp9808/mcp9808.py
# SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries
# SPDX-FileCopyrightText: 2021 Jose David Montoya
# SPDX-FileCopyrightText: Copyright (c) 2023 Jose D. Montoya
#
# SPDX-License-Identifier: MIT
"""
`mcp9808`
================================================================================
MicroPython Driver for the Microchip MCP9808 Temperature Sensor
* Author(s): Jose D. Montoya
"""
from collections import namedtuple
from micropython import const
from mcp9808.i2c_helpers import CBits, RegisterStruct
__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/jposada202020/MicroPython_MCP9808.git"
_CONFIG = const(0x01)
_UPPER_TEMP = const(0x02)
_LOWER_TEMP = const(0x03)
_CRITICAL_TEMP = const(0x04)
_TEMP = const(0x05)
_REG_WHOAMI = const(0x07)
_RESOLUTION = const(0x08)
HYSTERESIS_0 = const(0b00)
HYSTERESIS_1_5 = const(0b01)
HYSTERESIS_3 = const(0b10)
HYSTERESIS_6 = const(0b11)
hysteresis_values = (HYSTERESIS_0, HYSTERESIS_1_5, HYSTERESIS_3, HYSTERESIS_6)
CONTINUOUS = const(0b0)
SHUTDOWN = const(0b1)
power_mode_values = (CONTINUOUS, SHUTDOWN)
RESOLUTION_0_5_C = const(0b00)
RESOLUTION_0_625_C = const(0b01)
RESOLUTION_0_125_C = const(0b10)
RESOLUTION_0_0625_C = const(0b11)
temperature_resolution_values = (
RESOLUTION_0_5_C,
RESOLUTION_0_625_C,
RESOLUTION_0_125_C,
RESOLUTION_0_0625_C,
)
AlertStatus = namedtuple("AlertStatus", ["high_alert", "low_alert", "critical_alert"])
class MCP9808:
"""Driver for the MCP9808 Sensor connected over I2C.
:param ~machine.I2C i2c: The I2C bus the MCP9808 is connected to.
:param int address: The I2C device address. Defaults to :const:`0x18`
:raises RuntimeError: if the sensor is not found
**Quickstart: Importing and using the device**
Here is an example of using the :class:`MCP9808` class.
First you will need to import the libraries to use the sensor
.. code-block:: python
from machine import Pin, I2C
from micropython_mcp9808 import mcp9808
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))
mcp = mcp9808.MCP9808(i2c)
Now you have access to the attributes
.. code-block:: python
temp = mcp.temperature
"""
_device_id = RegisterStruct(_REG_WHOAMI, "B")
_config = RegisterStruct(_CONFIG, "H")
_hysteresis = CBits(2, _CONFIG, 9, 2, False)
_power_mode = CBits(1, _CONFIG, 8, 2, False)
_temperature_data = CBits(13, _TEMP, 0, 2, False)
_temperature_resolution = CBits(2, _RESOLUTION, 0)
critical_alert = CBits(1, _TEMP, 7, register_width=1)
high_alert = CBits(1, _TEMP, 6, register_width=1)
low_alert = CBits(1, _TEMP, 5, register_width=1)
def __init__(self, i2c, address: int = 0x18) -> None:
self._i2c = i2c
self._address = address
if self._device_id != 0x04:
raise RuntimeError("Failed to find MCP9808")
@property
def hysteresis(self) -> str:
"""
Sensor hysteresis
+------------------------------------+------------------+
| Mode | Value |
+====================================+==================+
| :py:const:`mcp9808.HYSTERESIS_0` | :py:const:`0b00` |
+------------------------------------+------------------+
| :py:const:`mcp9808.HYSTERESIS_1_5` | :py:const:`0b01` |
+------------------------------------+------------------+
| :py:const:`mcp9808.HYSTERESIS_3` | :py:const:`0b10` |
+------------------------------------+------------------+
| :py:const:`mcp9808.HYSTERESIS_6` | :py:const:`0b11` |
+------------------------------------+------------------+
"""
values = ("HYSTERESIS_0", "HYSTERESIS_1_5", "HYSTERESIS_3", "HYSTERESIS_6")
return values[self._hysteresis]
@hysteresis.setter
def hysteresis(self, value: int) -> None:
if value not in hysteresis_values:
raise ValueError("Value must be a valid hysteresis setting")
self._hysteresis = value
@property
def power_mode(self) -> str:
"""
Sensor power_mode
In shutdown, all power-consuming activities are disabled, though
all registers can be written to or read. This bit cannot be set
to 1 when either of the Lock bits is set (bit 6 and bit 7).
However, it can be cleared to 0 for continuous conversion while
locked
+--------------------------------+------------------+
| Mode | Value |
+================================+==================+
| :py:const:`mcp9808.CONTINUOUS` | :py:const:`0b00` |
+--------------------------------+------------------+
| :py:const:`mcp9808.SHUTDOWN` | :py:const:`0b1` |
+--------------------------------+------------------+
"""
values = ("CONTINUOUS", "SHUTDOWN")
return values[self._power_mode]
@power_mode.setter
def power_mode(self, value: int) -> None:
if value not in power_mode_values:
raise ValueError("Value must be a valid power_mode setting")
self._power_mode = value
@property
def temperature(self):
"""
Temperature in Celsius
"""
data = bytearray(2)
self._i2c.readfrom_mem_into(self._address, _TEMP, data)
return self._convert_temperature(data)
@staticmethod
def _convert_temperature(temp: bytearray) -> float:
temp[0] = temp[0] & 0x1F
if temp[0] & 0x10 == 0x10:
temp[0] = temp[0] & 0x0F
return (temp[0] * 16 + temp[1] / 16.0) - 256
return temp[0] * 16 + temp[1] / 16.0
@property
def temperature_upper(self) -> float:
"""
Upper temperature in Celsius
"""
return self._get_temperature(_UPPER_TEMP)
@temperature_upper.setter
def temperature_upper(self, value: int) -> None:
if not isinstance(value, int):
raise ValueError("Temperature must be an int value")
self._limit_temperatures(value, _UPPER_TEMP)
def _get_temperature(self, register_address):
data = bytearray(2)
self._i2c.readfrom_mem_into(self._address, register_address, data)
return self._convert_temperature(data)
def _limit_temperatures(self, temp: int, register_address):
"""Internal function to setup limit temperature
:param int temp: temperature limit
"""
if temp < 0:
negative = True
temp = abs(temp)
else:
negative = False
high_byte = temp >> 4
if negative:
high_byte = high_byte | 0x10
low_byte = (temp & 0x0F) << 4
self._i2c.writeto_mem(
self._address, register_address, bytes([high_byte, low_byte])
)
@property
def temperature_lower(self) -> float:
"""
Lower temperature in Celsius
"""
return self._get_temperature(_LOWER_TEMP)
@temperature_lower.setter
def temperature_lower(self, value: int) -> None:
if not isinstance(value, int):
raise ValueError("Temperature must be an int value")
self._limit_temperatures(value, _LOWER_TEMP)
@property
def temperature_critical(self) -> float:
"""
Critical temperature in Celsius
"""
return self._get_temperature(_CRITICAL_TEMP)
@temperature_critical.setter
def temperature_critical(self, value: int) -> None:
if not isinstance(value, int):
raise ValueError("Temperature must be an int value")
self._limit_temperatures(value, _CRITICAL_TEMP)
@property
def alert_status(self):
"""The current triggered status of the high and low temperature alerts as a AlertStatus
named tuple with attributes for the triggered status of each alert.
.. code-block :: python
import time
from machine import Pin, I2C
from micropython_mcp9808 import mcp9808
i2c = I2C(1, sda=Pin(2), scl=Pin(3)) # Correct I2C pins for RP2040
mcp = mcp9808.MCP9808(i2c)
mcp.temperature_lower = 20
mcp.temperature_upper = 23
mcp.temperature_critical = 30
print("High limit: {}".format(mcp.temperature_upper))
print("Low limit: {}".format(mcp.temperature_lower))
print("Critical limit: {}".format(mcp.temperature_critical))
while True:
print("Temperature: {:.2f}C".format(mcp.temperature))
alert_status = tmp.alert_status
if alert_status.high_alert:
print("Temperature above high set limit!")
if alert_status.low_alert:
print("Temperature below low set limit!")
if alert_status.critical_alert:
print("Temperature above critical set limit!")
time.sleep(1)
"""
return AlertStatus(
high_alert=self.high_alert,
low_alert=self.low_alert,
critical_alert=self.critical_alert,
)
@property
def temperature_resolution(self) -> str:
"""
Sensor temperature_resolution
+------------------------------------------+---------------------------+
| Mode | Value |
+==========================================+===========================+
| :py:const:`mcp9808.RESOLUTION_0_5_C` | :py:const:`0b00` 0.5°C |
+------------------------------------------+---------------------------+
| :py:const:`mcp9808.RESOLUTION_0_625_C` | :py:const:`0b01` 0.25°C |
+------------------------------------------+---------------------------+
| :py:const:`mcp9808.RESOLUTION_0_125_C` | :py:const:`0b10` 0.125°C |
+------------------------------------------+---------------------------+
| :py:const:`mcp9808.RESOLUTION_0_0625_C` | :py:const:`0b11` 0.0625°C |
+------------------------------------------+---------------------------+
"""
values = (
"RESOLUTION_0_5_C",
"RESOLUTION_0_625_C",
"RESOLUTION_0_125_C",
"RESOLUTION_0_0625_C",
)
return values[self._temperature_resolution]
@temperature_resolution.setter
def temperature_resolution(self, value: int) -> None:
if value not in temperature_resolution_values:
raise ValueError("Value must be a valid temperature_resolution setting")
self._temperature_resolution = value