Source code for qwiic_pir

#-----------------------------------------------------------------------------
# qwiic_pir.py
#
# Python library for the SparkFun qwiic PIR.
#   https://www.sparkfun.com/products/16407
#
#------------------------------------------------------------------------
#
# Written by Andy England and Priyanka Makin @ SparkFun Electronics, January 2021
# 
# This python library supports the SparkFun Electroncis qwiic 
# qwiic sensor/board ecosystem 
#
# 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_pir
============
Python module for the Qwiic PIR.
This python package is a port of the exisiting [SparkFun Qwiic PIR Arduino Library](https://github.com/sparkfun/SparkFun_Qwiic_PIR_Arduino_Library)
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).
"""
#-----------------------------------------------------------------------------------

import math
import qwiic_i2c

# Define the device name and I2C addresses. These are set in the class definition
# as class variables, making them available without having to create a class instance.
# This allows higher level logic to rapidly create an index of qwiic devices at runtime.

# This is the name of the device
_DEFAULT_NAME = "Qwiic PIR"

# 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.
_QWIIC_PIR_DEFAULT_ADDRESS = 0x12
_FULL_ADDRESS_LIST = list(range(0x08, 0x77+1))  # Full address list (excluding reserved addresses)
_FULL_ADDRESS_LIST.remove(_QWIIC_PIR_DEFAULT_ADDRESS >> 1)  # Remove default address from list
_AVAILABLE_I2C_ADDRESS = [_QWIIC_PIR_DEFAULT_ADDRESS]  # Initialize with default address
_AVAILABLE_I2C_ADDRESS.extend(_FULL_ADDRESS_LIST)   # Add full range of I2C addresses

# Define the class that encapsulates the device being created. All information associated 
# with this device is encapsulated by this class. The device class should be the only value
# exported from this module.

[docs]class QwiicPIR(object): """" QwiicPIR :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 GPIO device object. :rtype: Object """ # Constructor device_name = _DEFAULT_NAME available_addresses = _AVAILABLE_I2C_ADDRESS # Device ID for all Qwiic PIRs DEV_ID = 0x72 # Registers ID = 0x00 FIRMWARE_MINOR = 0x01 FIRMWARE_MAJOR = 0x02 EVENT_STATUS = 0x03 INTERRUPT_CONFIG = 0x04 EVENT_DEBOUNCE_TIME = 0x05 DETECTED_QUEUE_STATUS = 0x07 DETECTED_QUEUE_FRONT = 0x08 DETECTED_QUEUE_BACK = 0x0C REMOVED_QUEUE_STATUS = 0x10 REMOVED_QUEUE_FRONT = 0x11 REMOVED_QUEUE_BACK = 0x15 I2C_ADDRESS = 0x19 # Status Flags event_available = 0 object_remove = 0 object_detect = 0 raw_object_detected = 0 # Interrupt Configuration Flags interrupt_enable = 0 # Detected Queue Status Flags detected_pop_request = 0 detected_is_empty = 0 detected_is_full = 0 # Removed Queue Status Flags removed_pop_request = 0 removed_is_empty = 0 removed_is_full = 0 # Constructor def __init__(self, address=None, i2c_driver=None): # Did the user specify an I2C address? self.address = address if address != None else self.available_addresses[0] # Load the I2C driver if one isn't provided if i2c_driver == None: self._i2c = qwiic_i2c.getI2CDriver() if self._i2c == 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 Qwiic PIR device is connected to the system. :return: True if the device is connected, otherwise False. :rtype: bool """ return qwiic_i2c.isDeviceConnected(self.address)
# ------------------------------------------------ # begin() # # Initialize the system/validate the board.
[docs] def begin(self): """ Initialize the operation of the Qwiic PIR Run is_connected() and check the ID in the ID register :return: Returns true if the intialization was successful, otherwise False. :rtype: bool """ if self.is_connected() == True: id = self._i2c.readByte(self.address, self.ID) if id == self.DEV_ID: return True return False
# ------------------------------------------------ # get_firmware_version() # # Returns the firmware version of the attached devie as a 16-bit integer. # The leftmost (high) byte is the major revision number, # and the rightmost (low) byte is the minor revision number.
[docs] def get_firmware_version(self): """ Read the register and get the major and minor firmware version number. :return: 16 bytes version number :rtype: int """ version = self._i2c.readByte(self.address, self.FIRMWARE_MAJOR) << 8 version |= self._i2c.readByte(self.address, self.FIRMWARE_MINOR) return version
# ------------------------------------------------- # set_I2C_address(new_address) # # Configures the attached device to attach to the I2C bus using the specified address
[docs] def set_I2C_address(self, new_address): """ Change the I2C address of the Qwiic PIR :param new_address: the new I2C address to set the Qwiic PIR to :return: True if the change was successful, false otherwise. :rtype: bool """ # First, check if the specified address is valid if new_address < 0x08 or new_address > 0x77: return False # Write new address to the I2C address register of the Qwiic PIR self._i2c.writeByte(self.address, self.I2C_ADDRESS, new_address) # Check if the write was successful self.address = new_address
# --------------------------------------------------- # get_I2C_address() # # Returns the I2C address of the device
[docs] def get_I2C_address(self): """ Returns the current I2C address of the Qwiic PIR :return: current I2C address :rtype: int """ return self.address
# --------------------------------------------------- # raw_reading() # # Returns 1 if the PIR is detecting an object, 0 otherwise
[docs] def raw_reading(self): """ Returns the value of the raw_reading status bit of the EVENT_STATUS register :return: raw_object_detected bit :rtype: bool """ # Read the PIR status register pir_status = self._i2c.readByte(self.address, self.EVENT_STATUS) # Convert to binary and clear all bits but raw_object_detected self.raw_object_detected = int(pir_status) & ~(0xFE) # Return raw_object_detected as a bool return bool(self.raw_object_detected)
# --------------------------------------------------- # object_detected() # # Returns 1 if the PIR has a debounced detect object event
[docs] def object_detected(self): """ Returns the value of the object_detect status bit of the EVENT_STATUS register :return: object_detect bit :rtype: bool """ # Read the PIR status register pir_status = self._i2c.readByte(self.address, self.EVENT_STATUS) # Convert to binary and clear all bits but object_detect self.object_detect = int(pir_status) & ~(0xF7) # Shift object_detect bit to zero position self.object_detect = self.object_detect >> 3 # Return object_detect as a bool return bool(self.object_detect)
# ---------------------------------------------------- # object_removed() # # Returns 1 if the object in front of the PIR is removed, and 0 otherwise
[docs] def object_removed(self): """ Returns the value of the object_remove status bit of the EVENT_STATUS register :return: object_remove bit :rtype: bool """ # Read the PIR status register pir_status = self._i2c.readByte(self.address, self.EVENT_STATUS) # Convert to binary and clear all bits but object_remove self.object_remove = int(pir_status) & ~(0xFB) # Shift object_remove to the zero bit self.object_remove = self.object_remove >> 2 # Return object_remove as a bool return bool(self.object_remove)
# ------------------------------------------------------ # get_debounce_time() # # Returns the time that the PIR is using to debounce events
[docs] def get_debounce_time(self): """ Returns the value in the EVENT_DEBOUNCE_TIME register :return: debounce time in milliseconds :rtype: int """ time_list = self._i2c.readBlock(self.address, self.EVENT_DEBOUNCE_TIME, 2) # Convert list of bytes to time in milliseconds time = int(time_list[0]) + int(time_list[1]) * 16 ** (2) return time
# ------------------------------------------------------- # set_debounce_time(time) # # Sets the time that the PIR is using to debounce events
[docs] def set_debounce_time(self, time): """ Write two bytes into the EVENT_DEBOUNCE_TIME register :param time: the time in milliseconds to set debounce time to The max deounce time is 0xFFFF milliseconds, but the function checks if the entered parameter is valid :return: Nothing :rtype: void """ # First check that time is not too big if time > 0xFFFF: time = 0xFFFF # Then write two bytes self._i2c.writeWord(self.address, self.EVENT_DEBOUNCE_TIME, time)
# ------------------------------------------------------- # enable_interrupt() # # The interrupt will be configured to trigger when the PIR # detects an object.
[docs] def enable_interrupt(self): """ Set interrupt_enable bit of the INTERRUPT_CONFIG register to a 1 :return: Nothing :rtype: Void """ self.interrupt_enable = 1 # Write the new interrupt configure byte self._i2c.writeByte(self.address, self.INTERRUPT_CONFIG, self.interrupt_enable)
# ------------------------------------------------------- # disable_interrupt() # # Interrupt will no longer be configured to trigger when the # PIR is detecting.
[docs] def disable_interrupt(self): """ Clear the interrupt_enable bit of the INTERRUPT_CONFIG register :return: Nothing :rtype: Void """ self.interrupt_enable = 0 # Write the new interrupt configure byte self._i2c.writeByte(self.address, self.INTERRUPT_CONFIG, self.interrupt_enable)
# ------------------------------------------------------- # available() # # Returns the event_availble bit. This bit is set to 1 if a # PIR detect or remove event occurred
[docs] def available(self): """ Return the event_available bit of the EVENT_STATUS register :return: event_available bit :rtye: bool """ # First, read EVENT_STATUS register pir_status = self._i2c.readByte(self.address, self.EVENT_STATUS) # Convert to binary and clear all bits but the event_available bit self.event_available = int(pir_status) & ~(0xFD) # Shift event_availble to the zero bit self.event_available = self.event_available >> 1 # Return event_available bit as a bool return bool(self.event_available)
# ------------------------------------------------------- # clear_event_bits() # # Sets all PIR status bits (raw_object_detected, object_removed, # and event_available) to zero.
[docs] def clear_event_bits(self): """ Clear the object_remove, object_detect, and event_available bits of the EVENT_STATUS register :return: Nothing :rtype: Void """ # First, read EVENT_STATUS register pir_status = self._i2c.readByte(self.address, self.EVENT_STATUS) # Convert to binary and clear bits 1, 2, and 3 pir_status = int(pir_status) & ~(0x0E) # Write to EVENT_STATUS register self._i2c.writeByte(self.address, self.EVENT_STATUS, pir_status) # Update variables self.object_remove = 0 self.objected_detect = 0 self.event_available = 0
# ------------------------------------------------------- # reset_interrupt_config() # # Resets the interrupt configuration back to defaults.
[docs] def reset_interrupt_config(self): """ Enable detect interrupt and clear the event_available bit of EVENT_STATUS register :return: Nothing :rtype: Void """ # Enable interrupts on PIR detection self.enable_interrupt() # Clear the event bits self.clear_event_bits()
# ------------------------------------------------------- # is_detected_queue_full() # # Returns true if queue of PIR detect timestamps is full, # and false otherwise.
[docs] def is_detected_queue_full(self): """ Returns the is_full bit of the DETECTED_QUEUE_STATUS register :return: detected_is_full :rtype: bool """ # First, read the DETECTED_QUEUE_STATUS register detected_queue_stat = self._i2c.readByte(self.address, self.DETECTED_QUEUE_STATUS) # Convert to binary and clear all bits but is_full self.detected_is_full = int(detected_queue_stat) & ~(0xFB) # Shift detected_is_full bit to zero position self.detected_is_full = self.detected_is_full >> 2 # Return detected_is_full as a bool return bool(self.detected_is_full)
# ------------------------------------------------------- # is_detected_queue_empty() # # Returns true if the queue of PIR detect timestamps is # empty, and false otherwise.
[docs] def is_detected_queue_empty(self): """ Returns the is_empty bit of the DETECTED_QUEUE_STATUS register :return: detected_is_empty :rtype: bool """ # First, read the DETECTED_QUEUE_STATUS register detected_queue_stat = self._i2c.readByte(self.address, self.DETECTED_QUEUE_STATUS) # Convert to binary and clear all bits but is_empty self.detected_is_empty = int(detected_queue_stat) & ~(0xFD) # Shift detected_is_empty to the zero bit self.detected_is_empty = self.detected_is_empty >> 1 # Return is_empty as a bool return bool(self.detected_is_empty)
# ------------------------------------------------------ # time_since_last_detect() # # Returns how many milliseconds it has been since the last # detect event. Since this returns a 32-bit int, it will # roll over about every 50 days.
[docs] def time_since_last_detect(self): """ Returns the four bytes of DETECTED_QUEUE_FRONT. Time in milliseconds. :return: DETECTED_QUEUE_FRONT :rtype: int """ time_list = self._i2c.readBlock(self.address, self.DETECTED_QUEUE_FRONT, 4) # Convert list of bytes to a time in milliseconds time = int(time_list[0]) + int(time_list[1]) * 16 ** (2) + int(time_list[2]) * 16 ** (4) + int(time_list[3]) * 16 ** (6) return time
# ------------------------------------------------------- # time_since_first_detect() # # Returns how many milliseconds it has been since the first # detect event. Since this returns a 32-bit int, it will # roll over about every 50 days.
[docs] def time_since_first_detect(self): """ Returns the four bytes of DETECTED_QUEUE_BACK. Time in milliseconds :return: DETECTED_QUEUE_BACK :rtype: int """ time_list = self._i2c.readBlock(self.address, self.DETECTED_QUEUE_BACK, 4) # Convert the list of bytes into milliseconds time = int(time_list[0]) + int(time_list[1]) * 16 ** (2) + int(time_list[2]) * 16 ** (4) + int(time_list[3]) * 16 ** (6) return time
# ------------------------------------------------------- # pop_detected_queue() # # Returns the oldest value in the queue (milliseconds since # first PIR detect event), and then removes it.
[docs] def pop_detected_queue(self): """ Returns contents of DETECTED_QUEUE_BACK register and writes a 1 to pop_request bit of DETECTED_QUEUE_STATUS register. :return: DETECTED_QUEUE_BACK :rtype: int """ # Get the time in milliseconds since the PIR was first detect temp_data = self.time_since_first_detect() # Read DETECTED_QUEUE_STATUS register detected_queue_stat = self._i2c.readByte(self.address, self.DETECTED_QUEUE_STATUS) # Set detected_pop_request bit to 1 self.detected_pop_request = 1 detected_queue_stat = detected_queue_stat | (self.detected_pop_request) self._i2c.writeByte(self.address, self.DETECTED_QUEUE_STATUS, detected_queue_stat) return temp_data
# ------------------------------------------------------- # is_removed_queue_full() # # Returns true if queue of PIR remove timestamps is full, # and false otherwise.
[docs] def is_removed_queue_full(self): """ Returns the is_full bit of the REMOVED_QUEUE_STATUS register :return: removed_is_full :rtype: bool """ # First, read the REMOVED_QUEUE_STATUS register removed_queue_stat = self._i2c.readByte(self.address, self.REMOVED_QUEUE_STATUS) # Convert to binary and clear all bits but is_full self.removed_is_full = int(removed_queue_stat) & ~(0xFB) # Shift removed_is_full to the zero bit self.removed_is_full = self.removed_is_full >> 2 # Return removed_is_full as a bool return bool(self.removed_is_full)
# ------------------------------------------------------- # is_removed_queue_empty() # # Returns true if the queue of PIR remove timestamps is # empty, and false otherwise.
[docs] def is_removed_queue_empty(self): """ Returns the is_empty bit of the REMOVED_QUEUE_STATUS register :return: removed_is_empty :rtype: bool """ # First, read the REMOVED_QUEUE_STATUS register removed_queue_stat = self._i2c.readByte(self.address, self.REMOVED_QUEUE_STATUS) # Convert to binary and clear all bits but is_empty self.removed_is_empty = int(removed_queue_stat) & ~(0xFD) # Shift removed_is_empty to the zero bit self.removed_is_empty = self.removed_is_empty >> 1 # Return removed_is_empty as a bool return bool(self.removed_is_empty)
# ------------------------------------------------------ # time_since_last_remove() # # Returns how many milliseconds it has been since the last # remove event. Since this returns a 32-bit int, it will # roll over about every 50 days.
[docs] def time_since_last_remove(self): """ Returns the four bytes of REMOVED_QUEUE_FRONT. Time in milliseconds. :return: REMOVED_QUEUE_FRONT :rtype: int """ time_list = self._i2c.readBlock(self.address, self.REMOVED_QUEUE_FRONT, 4) # Convert list of bytes to time in milliseconds time = int(time_list[0]) + int(time_list[1]) * 16 ** (2) + int(time_list[2]) * 16 ** (4) + int(time_list[3]) * 16 ** (6) return time
# ------------------------------------------------------- # time_since_first_remove() # # Returns how many milliseconds it has been since the first # remove event. Since this returns a 32-bit int, it will # roll over about every 50 days.
[docs] def time_since_first_remove(self): """ Returns the four bytes of REMOVED_QUEUE_BACK. Time in milliseconds :return: REMOVED_QUEUE_BACK :rtype: int """ time_list = self._i2c.readBlock(self.address, self.REMOVED_QUEUE_BACK, 4) # Convert list of bytes into time in milliseconds time = int(time_list[0]) + int(time_list[1]) * 16 ** (2) + int(time_list[2]) * 16 ** (4) + int(time_list[3]) * 16 ** (6) return time
# ------------------------------------------------------- # pop_removed_queue() # # Returns the oldest value in the queue (milliseconds since # first PIR remove event), and then removes it.
[docs] def pop_removed_queue(self): """ Returns contents of REMOVED_QUEUE_BACK register and writes a 1 to pop_request bit of REMOVED_QUEUE_STATUS register. :return: REMOVED_QUEUE_BACK :rtype: int """ # Get the time in milliseconds since the PIR was first remove temp_data = self.time_since_first_remove() # Read REMOVED_QUEUE_STATUS register removed_queue_stat = self._i2c.readByte(self.address, self.REMOVED_QUEUE_STATUS) # Set removed_pop_request bit to 1 self.removed_pop_request = 1 removed_queue_stat = removed_queue_stat | (self.removed_pop_request) self._i2c.writeByte(self.address, self.REMOVED_QUEUE_STATUS, removed_queue_stat) return temp_data