Source code for qwiic_button

#-----------------------------------------------------------------------------
# qwiic_button.py
#
# Python library for the SparkFun qwiic button.
#   https://www.sparkfun.com/products/15932
#
#------------------------------------------------------------------------
#
# Written by 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) 2020 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_button
============
Python module for the Qwiic Button.

This python package is a port of the exisiting [SparkFun Qwiic Button Arduino Library](https://github.com/sparkfun/SparkFun_Qwiic_Button_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 Button"

# 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_BUTTON_DEFAULT_ADDRESS = 0x6F
_FULL_ADDRESS_LIST = list(range(0x08, 0x77+1))  # Full address list (excluding reserved addresses)
_FULL_ADDRESS_LIST.remove(_QWIIC_BUTTON_DEFAULT_ADDRESS >> 1)   # Remove default address from list
_AVAILABLE_I2C_ADDRESS = [_QWIIC_BUTTON_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 QwiicButton(object): """" QwiicButton :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 Buttons DEV_ID = 0x5D # Registers ID = 0x00 FIRMWARE_MINOR = 0x01 FIRMWARE_MAJOR = 0x02 BUTTON_STATUS = 0x03 INTERRUPT_CONFIG = 0x04 BUTTON_DEBOUNCE_TIME = 0x05 PRESSED_QUEUE_STATUS = 0x07 PRESSED_QUEUE_FRONT = 0x08 PRESSED_QUEUE_BACK = 0x0C CLICKED_QUEUE_STATUS = 0x10 CLICKED_QUEUE_FRONT = 0x11 CLICKED_QUEUE_BACK = 0x15 LED_BRIGHTNESS = 0x19 LED_PULSE_GRANULARITY = 0x1A LED_PULSE_CYCLE_TIME = 0x1B LED_PULSE_OFF_TIME = 0x1D I2C_ADDRESS = 0x1F # Status Flags event_available = 0 has_been_clicked = 0 is_pressed = 0 # Interrupt Configuration Flags clicked_enable = 0 pressed_enable = 0 # Pressed Queue Status Flags pressed_pop_request = 0 pressed_is_empty = 0 pressed_is_full = 0 # Clicked Queue Status Flags clicked_pop_request = 0 clicked_is_empty = 0 clicked_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 Button 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 Button 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 Button :param new_address: the new I2C address to set the Qwiic Button to The function itself checks if the entered parameter is a valid I2C address :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 Button self._i2c.writeByte(self.address, self.I2C_ADDRESS, new_address) 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 Button :return: current I2C address :rtype: int """ return self.address
# --------------------------------------------------- # is_button_pressed() # # Returns 1 if the button/switch is pressed, 0 otherwise
[docs] def is_button_pressed(self): """ Returns the value of the is_pressed status bit of the BUTTON_STATUS register :return: is_pressed bit :rtype: bool """ # Read the button status register button_status = self._i2c.readByte(self.address, self.BUTTON_STATUS) # Convert to binary and clear all bits but is_pressed self.is_pressed = int(button_status) & ~(0xFB) # Shift is_pressed to the zero bit self.is_pressed = self.is_pressed >> 2 # Return is_pressed as a bool return bool(self.is_pressed)
# ---------------------------------------------------- # has_button_been_clicked() # # Returns 1 if the button/switch is clicked, and 0 otherwise
[docs] def has_button_been_clicked(self): """ Returns the value of the has_been_clicked status bit of the BUTTON_STATUS register :return: has_been_clicked bit :rtype: bool """ # Read the button status register button_status = self._i2c.readByte(self.address, self.BUTTON_STATUS) # Convert to binary and clear all bits but has_been_clicked self.has_been_clicked = int(button_status) & ~(0xFD) # Shift has_been_clicked to the zero bit self.has_been_clicked = self.has_been_clicked >> 1 # Return has_been_clicked as a bool return bool(self.has_been_clicked)
# ------------------------------------------------------ # get_debounce_time() # # Returns the time that the button waits for the mechanical # contacts to settle in milliseconds.
[docs] def get_debounce_time(self): """ Returns the value in the BUTTON_DEBOUNCE_TIME register :return: debounce time in milliseconds :rtype: int """ time_list = self._i2c.readBlock(self.address, self.BUTTON_DEBOUNCE_TIME, 2) time = int(time_list[0]) + int(time_list[1]) * 16 ** (2) return time
# ------------------------------------------------------- # set_debounce_time(time) # # Sets the time that the button waits for the mechanical # contacts to settle in milliseconds.
[docs] def set_debounce_time(self, time): """ Write two bytes into the BUTTON_DEBOUNCE_TIME register :param time: the time in milliseconds to set debounce time to The max debounce 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 time1 = time & ~(0xFF00) time2 = time & ~(0x00FF) time2 = time2 >> 8 time_list = [time1, time2] # Then write two bytes self._i2c.writeWord(self.address, self.BUTTON_DEBOUNCE_TIME, time)
# ------------------------------------------------------- # enable_pressed_interrupt() # # The interrupt will be configured to trigger when the button # is pressed. If enableClickedInterrupt() has also been called, # then the interrupt will trigger on either a push or a click.
[docs] def enable_pressed_interrupt(self): """ Set pressed_enable bit of the INTERRUPT_CONFIG register to a 1 :return: Nothing :rtype: Void """ # First, read the INTERRUPT_CONFIG register interrupt_config = self._i2c.readByte(self.address, self.INTERRUPT_CONFIG) self.pressed_enable = 1 # Set the pressed_enable bit interrupt_config = interrupt_config | (self.pressed_enable << 1) # Write the new interrupt configure byte self._i2c.writeByte(self.address, self.INTERRUPT_CONFIG, interrupt_config)
# ------------------------------------------------------- # disable_pressed_interrupt() # # Interrupt will no longer be configured to trigger when the # button is pressed. If enable_clicked_interrupt() has also been called, # then the interrupt will still trigger on the button click.
[docs] def disable_pressed_interrupt(self): """ Clear the pressed_enable bit of the INTERRUPT_CONFIG register :return: Nothing :rtype: Void """ # First, read the INTERRUPT_CONFIG register interrupt_config = self._i2c.readByte(self.address, self.INTERRUPT_CONFIG) self.pressed_enable = 0 # Clear the pressed_enable bit interrupt_config = interrupt_config & ~(1 << 1) # Write the new interrupt configure byte self._i2c.writeByte(self.address, self.INTERRUPT_CONFIG, interrupt_config)
# ------------------------------------------------------- # enable_clicked_interrupt() # # The interrupt will be configured to trigger when the button # is clicked. If enable_pressed_interrupt() has also been called, # then the interrupt will trigger on either a push or a click.
[docs] def enable_clicked_interrupt(self): """ Set the clicked_enable bit of the INTERRUPT_CONFIG register :return: Nothing :rtype: Void """ # First, read the INTERRUPT_CONFIG register interrupt_config = self._i2c.readByte(self.address, self.INTERRUPT_CONFIG) self.clicked_enable = 1 # Set the clicked_enable bit interrupt_config = interrupt_config | self.clicked_enable # Write the new interrupt configure byte self._i2c.writeByte(self.address, self.INTERRUPT_CONFIG, interrupt_config)
# ------------------------------------------------------- # disable_clicked_interrupt() # # The interrupt will no longer be configured to trigger when # the button is clicked. If enable_pressed_interrupt() has also # been called, then the interrupt will still trigger on the # button press.
[docs] def disable_clicked_interrupt(self): """ Clear the clicked_enable bit of the INTERRUPT_CONFIG register :return: Nothing :rtype: Void """ # First, read the INTERRUPT_CONFIG register interrupt_config = self._i2c.readByte(self.address, self.INTERRUPT_CONFIG) self.clicked_enable = 0 # Clear the clicked_enable bit interrupt_config = interrupt_config & (self.clicked_enable) # Write the new interrupt configure byte self._i2c.writeByte(self.address, self.INTERRUPT_CONFIG, interrupt_config)
# ------------------------------------------------------- # available() # # Returns the eventAvailble bit. This bit is set to 1 if a # button click or press event occurred.
[docs] def available(self): """ Return the event_available bit of the BUTTON_STATUS register :return: event_available bit :rtye: bool """ # First, read BUTTON_STATUS register button_status = self._i2c.readByte(self.address, self.BUTTON_STATUS) # Convert to binary and clear all bits but the event_available bit self.event_available = int(button_status) & ~(0xFE) # Return event_available bit as a bool return bool(self.event_available)
# ------------------------------------------------------- # clear_event_bits() # # Sets all button status bits (is_pressed, has_been_clicked, # and event_available) to zero.
[docs] def clear_event_bits(self): """ Clear the is_pressed, has_been_clicked, and event_available bits of the BUTTON_STATUS register :return: Nothing :rtype: Void """ # First, read BUTTON_STATUS register button_status = self._i2c.readByte(self.address, self.BUTTON_STATUS) # Convert to binary and clear the last three bits button_status = int(button_status) & ~(0x7) # Write to BUTTON_STATUS register self._i2c.writeByte(self.address, self.BUTTON_STATUS, button_status)
# ------------------------------------------------------- # reset_interrupt_config() # # Resets the interrupt configuration back to defaults.
[docs] def reset_interrupt_config(self): """ Enable pressed and clicked interrupts and clear the event_available bit of BUTTON_STATUS register :return: Nothing :rtype: Void """ self.pressed_enable = 1 self.clicked_enable = 1 # write 0b11 to the INTERRUPT_CONFIG register self._i2c.writeByte(self.address, self.INTERRUPT_CONFIG, 0b11) self.event_available = 0 # Clear has_been_clicked, is_pressed too # TODO: not sure if this is right self.has_been_clicked = 0 self.is_pressed = 0 # Clear the BUTTON_STATUS register by writing a 0 self._i2c.writeByte(self.address, self.BUTTON_STATUS, 0x00)
# ------------------------------------------------------- # is_pressed_queue_full() # # Returns true if queue of button press time stamps is full, # and false otherwise.
[docs] def is_pressed_queue_full(self): """ Returns the is_full bit of the PRESSED_QUEUE_STATUS register :return: pressed_is_full :rtype: bool """ # First, read the PRESSED_QUEUE_STATUS register pressed_queue_stat = self._i2c.readByte(self.address, self.PRESSED_QUEUE_STATUS) # Convert to binary and clear all bits but isFull self.pressed_is_full = int(pressed_queue_stat) & ~(0xFB) self.pressed_is_full = self.pressed_is_full >> 2 # Return pressed_is_full as a bool return bool(self.pressed_is_full)
# ------------------------------------------------------- # is_pressed_queue_empty() # # Returns true if the queue of button press time stamps is # empty, and false otherwise.
[docs] def is_pressed_queue_empty(self): """ Returns the is_empty bit of the PRESSED_QUEUE_STATUS register :return: pressed_is_empty :rtype: bool """ # First, read the PRESSED_QUEUE_STATUS register pressed_queue_stat = self._i2c.readByte(self.address, self.PRESSED_QUEUE_STATUS) # Convert to binary and clear all bits but is_empty self.pressed_is_empty = int(pressed_queue_stat) & ~(0xFD) # Shift pressed_is_empty to the zero bit self.pressed_is_empty = self.pressed_is_empty >> 1 # Return pressed_is_empty as a bool return bool(self.pressed_is_empty)
# ------------------------------------------------------ # time_since_last_press() # # Returns how many milliseconds it has been since the last # button press. Since this returns a 32-bit int, it will # roll over about every 50 days.
[docs] def time_since_last_press(self): """ Returns the four bytes of PRESSED_QUEUE_FRONT. Time in milliseconds. :return: PRESSED_QUEUE_FRONT :rtype: int """ time_list = self._i2c.readBlock(self.address, self.PRESSED_QUEUE_FRONT, 4) 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_press() # # Returns how many milliseconds it has been since the first # button press. Since this returns a 32-bit int, it will # roll over about every 50 days.
[docs] def time_since_first_press(self): """ Returns the four bytes of PRESSED_QUEUE_BACK. Time in milliseconds :return: PRESSED_QUEUE_BACK :rtype: int """ time_list = self._i2c.readBlock(self.address, self.PRESSED_QUEUE_BACK, 4) 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_pressed_queue() # # Returns the oldest value in the queue (milliseconds since # first button press), and then removes it.
[docs] def pop_pressed_queue(self): """ Returns contents of PRESSED_QUEUE_BACK register and writes a 1 to popRequest bit of PRESSED_QUEUE_STATUS register. :return: PRESSED_QUEUE_BACK :rtype: int """ # Get the time in milliseconds since the button was first pressed temp_data = self.time_since_first_press() # Read PRESSED_QUEUE_STATUS register pressed_queue_stat = self._i2c.readByte(self.address, self.PRESSED_QUEUE_STATUS) self.pressed_pop_request = 1 # Set pop_request bit to 1 pressed_queue_stat = pressed_queue_stat | (self.pressed_pop_request) self._i2c.writeByte(self.address, self.PRESSED_QUEUE_STATUS, pressed_queue_stat) return temp_data
# --------------------------------------------------------- # is_clicked_queue_full() # # Returns true if the queue of button click timestamps is full # and false otherwise.
[docs] def is_clicked_queue_full(self): """ Reads the is_full bit of the CLICKED_QUEUE_STATUS register :return: clicked_is_full :rtype: bool """ # First, read the CLICKED_QUEUE_STATUS register clicked_queue_stat = self._i2c.readByte(self.address, self.CLICKED_QUEUE_STATUS) # Convert to binary and clear all bits but clicked_is_full self.clicked_is_full = int(clicked_queue_stat) & ~(0xFB) self.clicked_is_full = self.clicked_is_full >> 2 # Return clicked_is_full as a bool return bool(self.clicked_is_full)
# ---------------------------------------------------------- # is_clicked_queue_empty() # # Returns true if the queue click timestamps is empty and false # otherwise.
[docs] def is_clicked_queue_empty(self): """ Reads the is_empty bit of the CLICKED_QUEUE_STATUS register :return: clicked_is_empty :rtype: bool """ # First, read the CLICKED_QUEUE_STATUS register clicked_queue_stat = self._i2c.readByte(self.address, self.CLICKED_QUEUE_STATUS) # Convert to binary and clear all bits but clicked_is_empty self.clicked_is_empty = int(clicked_queue_stat) & ~(0xFD) # Shift clicked_is_empty to the zero bit self.clicked_is_empty = self.clicked_is_empty >> 1 # Return clicked_is_empty as a bool return bool(self.clicked_is_empty)
# ------------------------------------------------------------ # time_since_last_click() # # Returns how many milliseconds it has been since the last button # click. Since this returns a 32-bit int, it will roll over about # every 50 days
[docs] def time_since_last_click(self): """ Returns the four bytes of CLICKED_QUEUE_FRONT register. Time in milliseconds :return: CLICKED_QUEUE_FRONT :rtype: int """ time_list = self._i2c.readBlock(self.address, self.CLICKED_QUEUE_FRONT, 4) 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_click() # # Returns how many milliseconds it has been since the first button # click. Since this returns a 32-bit int, it will roll over about # every 50 days
[docs] def time_since_first_click(self): """ Returns the four bytes of CLICKED_QUEUE_BACK register. Time in milliseconds :return: CLICKED_QUEUE_BACK :rtype: int """ time_list = self._i2c.readBlock(self.address, self.CLICKED_QUEUE_BACK, 4) 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_clicked_queue() # # Returns the oldest value in the queue (milliseconds since first # button click), and then removes it.
[docs] def pop_clicked_queue(self): """ Returns contents of CLICKED_QUEUE_BACK register and writes a 1 to popRequest bit of CLICKED_QUEUE_STATUS register. :return: CLICKED_QUEUE_BACK :rtype: int """ # Get the time in milliseconds since the button was first clicked temp_data = self.time_since_first_click() # Read CLICKED_QUEUE_STATUS register clicked_queue_stat = self._i2c.readByte(self.address, self.CLICKED_QUEUE_STATUS) self.clicked_pop_request = 1 # Set pop_request bit to 1 clicked_queue_stat = clicked_queue_stat | (self.clicked_pop_request) self._i2c.writeByte(self.address, self.CLICKED_QUEUE_STATUS, clicked_queue_stat) return temp_data
# ------------------------------------------------------------- # LED_config(brightness, cycle_time, off_time, granularity) # # Configures the LED with the given max brightness, granularity # (1 is fine for most applications), cycle time, and off time.
[docs] def LED_config(self, brightness, cycle_time, off_time, granularity = 1): """ Write brightness, cycle_time, off_time, and granularity parameters to their respective registers: LED_BRIGHTNESS, LED_PULSE_CYCLE_TIME, LED_PULSE_OFF_TIME, LED_PULSE_GRANULARITY :param brightness: between 0 (led off) and 255 (max brightness) :param cycle_time: total pulse cycle in in milliseconds Range 0 to 0xFFFF :param off_time: off time between pulses in milliseconds Range 0 to 0xFFFF :param granularity: the amount of steps it takes to get to led brightness If not provided, granularity defaults to 1 :return: Nothing :rtype: Void """ # Write brightness self._i2c.writeByte(self.address, self.LED_BRIGHTNESS, brightness) # Write cycle_time self._i2c.writeWord(self.address, self.LED_PULSE_CYCLE_TIME, cycle_time) # Write off_time self._i2c.writeWord(self.address, self.LED_PULSE_OFF_TIME, off_time) # Write granularity self._i2c.writeByte(self.address, self.LED_PULSE_GRANULARITY, granularity)
# -------------------------------------------------------------- # LED_off() # # Turn the onboard LED off
[docs] def LED_off(self): """ Write zero's to all the LED registers: LED_BRIGHTNESS, LED_PULSE_CYCLE_TIME, LED_PULSE_OFF_TIME, and LED_PULSE_GRANULARITY defaults to zero. :return: Nothing :rtype: void """ self.LED_config(0, 0, 0)
# -------------------------------------------------------------- # LED_on(brightness) # # Turns the onboard LED on with specified brightness. Set brightness # to an integer between 0 and 255, where 0 is off and 255 is max # brightness.
[docs] def LED_on(self, brightness): """ Set LED on without pulse :param brightness: between 0 (led off) and 255 (max brightness) :return: Nothing :rtype: Void """ self.LED_config(brightness, 0, 0)