Source code for qwiic_as6212

#!/usr/bin/env python
#-----------------------------------------------------------------------------
# qwiic_as6212.py
#
# Python module for the AS6212 Digital Temperature Sensor Qwiic
# 
#------------------------------------------------------------------------
#
# Written by Pete Lewis, SparkFun Electronics, Aug 2021
# 
# Thanks to Alex Wende and Lori Croster @ SparkFun Electronics
# for code examples from TMP102 Python Package, May 2021
# (https://github.com/sparkfun/Qwiic_TMP102_Py)
#
# Thanks to Brandon Williams. This library was based off his 
# original library created 07/15/2020 and can be found here:
# https://github.com/will2055/AS6212-Arduino-Library/
#
# Thanks to Madison Chodikov @ SparkFun Electronics
# for code examples from TMP117 Arduino Library
# (https://github.com/sparkfun/SparkFun_TMP117_Arduino_Library)
# 
# This python library supports the SparkFun Electroncis qwiic 
# qwiic sensor/board ecosystem on a Raspberry Pi (and compatable) single
# board computers. 
#
# More information on qwiic is at https://www.sparkfun.com/qwiic
#
# Do you like this library? Help support SparkFun. Buy a board!
#
#==================================================================================
# Copyright (c) 2021 SparkFun Electronics
#
# Permission is hereby granted, free of charge, to any person obtaining a copy 
# of this software and associated documentation files (the "Software"), to deal 
# in the Software without restriction, including without limitation the rights 
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
# copies of the Software, and to permit persons to whom the Software is 
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all 
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
# SOFTWARE.
#==================================================================================

"""
qwiic_as6212
============
Python module for the [SparkFun Digital Temperature Sensor Breakout - AS6212 (Qwiic)](https://www.sparkfun.com/products/18521)

This python package is a port of the existing [SparkFun Qwiic AS6212 Sensor Arduino Library](https://github.com/sparkfun/SparkFun_TMP102_Arduino_Library/tree/master/examples)

This package can be used in conjunction with the overall [SparkFun qwiic Python Package](https://github.com/sparkfun/Qwiic_Py)

New to qwiic? Take a look at the entire [SparkFun qwiic ecosystem](https://www.sparkfun.com/qwiic).

"""
from __future__ import print_function, division

import qwiic_i2c

#======================================================================
# Basic setup of I2C commands and available I2C Addresses
#
#
# The name of this device - note this is private
_DEFAULT_NAME = "Qwiic AS6212 Sensor"

AS6212_DEFAULT_ADDRESS = 0x48

#Internal Register Addresses
TVAL_REG      =       0x0    #Temperature Register
CONFIG_REG    =       0x1    #Configuration Register
TLOW_REG      =       0x2    #Low Temperature Threshold
THIGH_REG     =       0x3    #High Temperature Threshold

#Helpful preset definitions for configuration register
DEFAULTM  =       0x40A0   #Default state
SLEEPMODE =       0x41A0   #Sleep Mode
SLEEPSS   =       0xC1A0   #Sleep Mode Single Shot

# Register values (MSB -> LSB)
SINGLESHOT	=	0x8000	#15
CFAULT_1		=	0x0800	#12
CFAULT_0		=	0x0400	#11
POLARITY		=	0x0200	#10
INTERRUPT		=	0x0100	#9
SLEEP			=	0x0080	#8
CONVER_RATE_1 =	0x0040	#7
CONVER_RATE_0 =	0x0020	#6
ALERT			=	0x0010	#5

AS6212_RESOLUTION = 0.0078125

AS6212_CONFIG_BIT_ALERT = 5
AS6212_CONFIG_BIT_CONVERSION_RATE_0 = 6
AS6212_CONFIG_BIT_CONVERSION_RATE_1 = 7
AS6212_CONFIG_BIT_SLEEP_MODE = 8
AS6212_CONFIG_BIT_INTERRUPT_MODE = 9
AS6212_CONFIG_BIT_ALERT_POL = 10
AS6212_CONFIG_BIT_CONSECUTIVE_FAULTS_0 = 11
AS6212_CONFIG_BIT_CONSECUTIVE_FAULTS_1 = 12
AS6212_CONFIG_BIT_SINGLE_SHOT = 15

#Address can be set using jumpers on bottom of board (default: 0x48)
_AVAILABLE_I2C_ADDRESS = [0x48, 0x44, 0x45, 0x46, 0x47, 0x49, 0x4A, 0x4B]

###############################################################################
###############################################################################
# Some devices have multiple available addresses - this is a list of these addresses.
# NOTE: The first address in this list is considered the default I2C address for the
# device.

[docs]class QwiicAs6212Sensor(object): """ QwiicAs6212Sensor :param address: The I2C address to use for the device. If not provided, the default address is used. :param i2c_driver: An existing i2c driver object. If not provided a driver object is created. :return: The AS6212 Sensor device object. :rtype: Object """ device_name = _DEFAULT_NAME available_addresses = _AVAILABLE_I2C_ADDRESS AS6212_MODE_COMPARATOR = 0 AS6212_MODE_INTERRUPT = 1 AS6212_CONVERSION_CYCLE_TIME_125MS = 3 AS6212_CONVERSION_CYCLE_TIME_250MS = 2 AS6212_CONVERSION_CYCLE_TIME_1000MS = 1 AS6212_CONVERSION_CYCLE_TIME_4000MS = 0 AS6212_ALERT_ACTIVE_HIGH = 1 AS6212_ALERT_ACTIVE_LOW = 0 # Constructor def __init__(self, address=None, i2c_driver=None): # Did the user specify an I2C address? self.address = self.available_addresses[0] if address is None else address # load the I2C driver if one isn't provided if i2c_driver is None: self._i2c = qwiic_i2c.getI2CDriver() if self._i2c is None: print("Unable to load I2C driver for this platform.") return else: self._i2c = i2c_driver # ---------------------------------- # is_connected() # # Is an actual board connected to our system?
[docs] def is_connected(self): """ Determine if a Soil MoistureSensor device is conntected to the system.. :return: True if the device is connected, otherwise False. :rtype: bool """ return qwiic_i2c.isDeviceConnected(self.address)
connected = property(is_connected) # ---------------------------------- # begin() # # Initialize the system/validate the board.
[docs] def begin(self): """ Initialize the operation of the Soil Moisture Sensor module :return: Returns true of the initialization was successful, otherwise False. :rtype: bool """ # Set variables self.tempC = 0 self.tempF = 0 # Basically return True if we are connected... return self.is_connected()
#****************************************************************************# # # Sensor functions # # ****************************************************************************#
[docs] def get_address(self): """ Returns the device address """ return self.address
[docs] def read_2_byte_register(self, register_to_read): """ Reads two bytes of data from a desired register. Combines them into a single 16 bit value Returns single value """ data = self._i2c.readBlock(self.address, register_to_read, 2) #Combine bytes to create a single signed int return ( (data[0] << 8 ) | data[1] )
def write_register(self, reg, data): data_bytes = [0,0] data_bytes[1] |= (data & 0xFF) data_bytes[0] |= data >> 8 self._i2c.writeBlock(self.address, reg, data_bytes) def read_config(self): return self.read_2_byte_register(CONFIG_REG) def write_config(self, targetState): self.write_register(CONFIG_REG, targetState)
[docs] def read_temp_c(self): """ Reads the results from the sensor :rtype: integer """ digitalTempC = self.read_2_byte_register(TVAL_REG) if (digitalTempC < 32768): finalTempC = digitalTempC * 0.0078125 if (digitalTempC >= 32768): finalTempC = ((digitalTempC - 1) * 0.0078125) * -1 self.tempC = finalTempC return self.tempC
[docs] def read_temp_f(self): """ Reads the results from the sensor :rtype: integer """ self.tempF = self.read_temp_c() * 9.0 / 5.0 + 32.0 return self.tempF
[docs] def set_alert_polarity(self, polarity): """ Set the polarity of Alert AS6212_ALERT_ACTIVE_HIGH (1) AS6212_ALERT_ACTIVE_LOW (0) """ configReg = self.read_config() configReg = self.bit_write(configReg, AS6212_CONFIG_BIT_ALERT_POL, polarity) self.write_config(configReg)
[docs] def get_alert_polarity(self): """ Get the polarity of Alert AS6212_ALERT_ACTIVE_HIGH (1) AS6212_ALERT_ACTIVE_LOW (0) """ configReg = self.read_config() return self.bit_read(configReg, AS6212_CONFIG_BIT_ALERT_POL)
[docs] def set_interrupt_mode(self, mode): """ sets the interrupt mode bits in the config register valid options are: AS6212_MODE_COMPARATOR (0) AS6212_MODE_INTERRUPT (1) """ configReg = self.read_config() configReg = self.bit_write(configReg, AS6212_CONFIG_BIT_INTERRUPT_MODE, mode) self.write_config(configReg)
[docs] def get_interrupt_mode(self): """ Get the interrupt mode bit AS6212_MODE_COMPARATOR (0) AS6212_MODE_INTERRUPT (1) """ configReg = self.read_config() return self.bit_read(configReg, AS6212_CONFIG_BIT_INTERRUPT_MODE)
[docs] def get_alert_status(self): """ Get the status of the alert bit (0 or 1) """ configReg = self.read_config() return self.bit_read(configReg, AS6212_CONFIG_BIT_ALERT)
[docs] def set_consecutive_faults(self, faults): """ Set the number of consecutive faults 1 - 1 fault 2 - 2 faults 3 - 3 faults 4 - 4 faults """ if (faults > 4) or (faults < 1): return NaN faults = faults - 1 # consecutive faults value is stored in just 2 bits in the config reg, # so we must convert from "human readable" ints 1-4 to stored values (0-3). configReg = self.read_config() configBit_11 = self.bit_read(faults, 0) configBit_12 = self.bit_read(faults, 1) configReg = self.bit_write(configReg, AS6212_CONFIG_BIT_CONSECUTIVE_FAULTS_0, configBit_11) configReg = self.bit_write(configReg, AS6212_CONFIG_BIT_CONSECUTIVE_FAULTS_1, configBit_12) self.write_config(configReg)
[docs] def get_consecutive_faults(self): """ Gets the number of consecutive faults that need to happen in a row before alert is changed. valid settings are 1,2,3 or 4 but this correspond to other bit values in the configuration register bits 11 and 12 """ configReg = self.read_config() consecutive_faults_bit_0 = self.bit_read(configReg, AS6212_CONFIG_BIT_CONSECUTIVE_FAULTS_0) consecutive_faults_bit_1 = self.bit_read(configReg, AS6212_CONFIG_BIT_CONSECUTIVE_FAULTS_1) faults = 0 faults = self.bit_write(faults, 0, consecutive_faults_bit_0) faults = self.bit_write(faults, 1, consecutive_faults_bit_1) faults = ( faults + 1 ) # consecutive faults is stored in just two bits in teh config reg # so we must +1 to make it the stored values (0-3) human readable (1-4) return faults
[docs] def set_conversion_cycletime(self, cycletime): """ sets the conversion cylce time (aka convertion rate) in the config register valid settings are: AS6212_CONVERSION_CYCLE_TIME_125MS AS6212_CONVERSION_CYCLE_TIME_250MS AS6212_CONVERSION_CYCLE_TIME_1000MS AS6212_CONVERSION_CYCLE_TIME_4000MS """ #discard out of range values if cycletime > 3 or cycletime < 0: return nan configReg = self.read_config() configBit_6 = self.bit_read(cycletime, 0) configBit_7 = self.bit_read(cycletime, 1) configReg = self.bit_write(configReg, AS6212_CONFIG_BIT_CONVERSION_RATE_0, configBit_6) configReg = self.bit_write(configReg, AS6212_CONFIG_BIT_CONVERSION_RATE_1, configBit_7) self.write_config(configReg)
[docs] def get_conversion_cycletime(self): """ Gets the conversion cycle time (aka conversion rate) in teh config reg Returns the cycle time in milliseconds: (125/250/1000/4000) """ configReg = self.read_config() conversion_rate_bit_0 = self.bit_read(configReg, AS6212_CONFIG_BIT_CONVERSION_RATE_0) conversion_rate_bit_1 = self.bit_read(configReg, AS6212_CONFIG_BIT_CONVERSION_RATE_1) cycletime_val = 0 cycletime_val = self.bit_write(cycletime_val, 0, conversion_rate_bit_0) cyceltime_val = self.bit_write(cycletime_val, 1, conversion_rate_bit_1) if cycletime_val == AS6212_CONVERSION_CYCLE_TIME_125MS: return 125 if cycletime_val == AS6212_CONVERSION_CYCLE_TIME_250MS: return 250 if cycletime_val == AS6212_CONVERSION_CYCLE_TIME_1000MS: return 1000 if cycletime_val == AS6212_CONVERSION_CYCLE_TIME_4000MS: return 4000
[docs] def set_sleep_mode(self, mode): """ sets the sleep mode bit (on or off) in the config register valid options are: 0 = SLEEP MODE OFF 1 = SLEEP MODE ON """ configReg = self.read_config() configReg = self.bit_write(configReg, AS6212_CONFIG_BIT_SLEEP_MODE, mode) if mode == 1: # as recommended in datasheet section 6.2.4 configReg = self.bit_write(configReg, AS6212_CONFIG_BIT_SINGLE_SHOT, 1) self.write_config(configReg)
[docs] def get_sleep_mode(self): """ gets the status of the sleep mode bit from the config register """ configReg = self.read_config() return self.bit_read(configReg, AS6212_CONFIG_BIT_SLEEP_MODE)
[docs] def trigger_single_shot_conversion(self): """ Sets the SS mode bit in the config register Note, you must be in sleep mode for this to work """ configReg = self.read_config() if self.bit_read(configReg, AS6212_CONFIG_BIT_SLEEP_MODE) == 1: configReg = self.bit_write(configReg, AS6212_CONFIG_BIT_SINGLE_SHOT, 1) self.write_config(configReg)
[docs] def get_single_shot_status(self): """ gets the status of the single shot bit from the config register 0 = No conversion ongoing / conversion finished 1 = Start single shot conversion / conversion ongoing """ configReg = self.read_config() return self.bit_read(configReg, AS6212_CONFIG_BIT_SINGLE_SHOT)
[docs] def set_low_temp_c(self, temperature): """ Sets T_LOW (degrees C) alert threshold """ if temperature >= 0: # positive number or zero low_temp = int(temperature / 0.0078125) if temperature < 0: #negative number temperature /= 0.0078125 temp_int = int(temperature) temp_int &= 0xFFFF low_temp = ( ~(temp_int) + 1 ) * -1 self.write_register(TLOW_REG, low_temp)
[docs] def set_high_temp_c(self, temperature): """ Sets THIGH (degrees C) alert threshold """ if temperature >= 0: # positive number or zero high_temp = int(temperature / 0.0078125) if temperature < 0: #negative number temperature /= 0.0078125 temp_int = int(temperature) temp_int &= 0xFFFF high_temp = ( ~(temp_int) + 1 ) * -1 self.write_register(THIGH_REG, high_temp)
[docs] def set_low_temp_f(self, temperature): """ Sets T_LOW (degrees F) alert threshold """ new_temp = (temperature - 32)*5/9 # Convert temperature to C self.set_low_temp_c(new_temp) # Set T_LOW
[docs] def set_high_temp_f(self, temperature): """ Sets T_HIGH (degrees F) alert threshold """ new_temp = (temperature - 32)*5/9 # Convert temperature to C self.set_high_temp_c(new_temp) # Set T_HIGH
[docs] def read_low_temp_c(self): """ Gets T_LOW (degrees C) alert threshold """ digital_temp = self.read_2_byte_register(TLOW_REG) if (digital_temp < 32768): finalTempC = float(digital_temp) * 0.0078125 if (digital_temp >= 32768): digital_temp = ~digital_temp digital_temp &= 0xFFFF finalTempC = (( digital_temp + 1 ) * 0.0078125) * -1 return finalTempC
[docs] def read_high_temp_c(self): """ Gets T_HIGH (degrees C) alert threshold """ digital_temp = self.read_2_byte_register(THIGH_REG) if (digital_temp < 32768): finalTempC = digital_temp * 0.0078125 if (digital_temp >= 32768): digital_temp = ~digital_temp digital_temp &= 0xFFFF finalTempC = (( digital_temp + 1 ) * 0.0078125) * -1 return finalTempC
[docs] def read_low_temp_f(self): """ Reads T_LOW register in F """ return self.read_low_temp_c()*9.0/5.0 + 32.0
[docs] def read_high_temp_f(self): """ Reads T_HIGH register in F """ return self.read_high_temp_c()*9.0/5.0 + 32.0
def bit_read(self, value, bit): return (((value) >> (bit)) & 0x01) def bit_set(self, value, bit): return ((value) | (1 << (bit))) def bit_clear(self, value, bit): return ((value) & ~(1 << (bit))) def bit_write(self, value, bit, bitvalue): if (bitvalue == 1): return self.bit_set(value, bit) if (bitvalue == 0): return self.bit_clear(value, bit) def print_config_binary(self): configReg = self.read_config() print("\nConfig: ") configBin = "" for i in range(15, -1, -1): configBin += str(self.bit_read(configReg, i)) if i == 8: configBin += " " print(configBin) def print_binary(self, data): configReg = self.read_config() print("\ndata: ") dataBin = "" for i in range(15, -1, -1): dataBin += str(self.bit_read(data, i)) if i == 8: dataBin += " " print(dataBin)