diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af2f537 --- /dev/null +++ b/.gitignore @@ -0,0 +1,104 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +.static_storage/ +.media/ +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ diff --git a/README.md b/README.md index 5f99928..4331a8b 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,15 @@ Relies on [Adafruit Raspberry Pi Python Code](https://github.com/adafruit/Adafru ## Run Instructions -1. wire up to Pi's I2C +1. wire up to Pi's I2C and DR to GPIO17 -2. navigate to /zxsensor/ +2. navigate to /examples/ 3. then run the following terminal command: ```bash -python zx_sensor_demo.py +python i2c_zx_demo.py ``` ## Notes -* only basic 'x' & 'z' value retrieval so far, no gestures yet +* now with gestures - try examples/i2c_gesture_interrupt.py diff --git a/examples/i2c_gesture_demo.py b/examples/i2c_gesture_demo.py new file mode 100755 index 0000000..888e86f --- /dev/null +++ b/examples/i2c_gesture_demo.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python + +""" +i2c_gesture_Interrupt.py +XYZ Interactive ZX Sensor +Shawn Hymel @ SparkFun Electronics +May 6, 2015 +Ported by: Christian Erhardt +Jan 6, 2018 +https://github.com/sparkfun/SparkFun_ZX_Distance_and_Gesture_Sensor_Arduino_Library +Tests the ZX sensor's ability to read gesture data over I2C using +an interrupt pin. This program configures I2C and sets up an +interrupt to occur whenever the ZX Sensor throws its DR pin high. +The gesture is displayed along with its "speed" (how long it takes +to complete the gesture). Note that higher numbers of "speed" +indicate a slower speed. +Hardware Connections: + + Raspberry Pin ZX Sensor Board Function + --------------------------------------- + 5V VCC Power + GND GND Ground + GPIO1 DA I2C Data + GPIO0 CL I2C Clock +Development environment specifics: +Written in python 2.7 +Tested with a SparkFun RedBoard +Distributed as-is; no warranty is given. +""" + +# standard +import time, sys, os +import logging +# project +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) +from zxsensor import * + +logging.basicConfig(level=logging.WARN) + +# Initialise the ZxSensor device using the default address +zx_sensor = ZxSensor() + +# Read the model version number and ensure the library will work +ver = zx_sensor.get_model_version() +if (ver == ZX_ERROR): + print("Error reading model version!") + exit() +else: + print("Model version {}".format(ver)) + +if (not ver == ZX_MODEL_VER): + print("Model version needs to be {} to work with this library. Stopping".format(ZX_MODEL_VER)) + exit() + +# Read the register map version and ensure the library will work +ver = zx_sensor.get_reg_map_version() +if (ver == ZX_ERROR): + print("Error reading register map version number") + exit() +else: + print("Register map version {}".format(ver)) + +if (not ver == ZX_REG_MAP_VER): + print("Register map version needs to be {} to work with this library. Stopping".format(ZX_REG_MAP_VER)) + exit() + +# Endless loop +while (True): + if (zx_sensor.gesture_available()): + gesture = zx_sensor.read_gesture() + gesture_speed = zx_sensor.read_gesture_speed() + + if (gesture == gesture_type.NO_GESTURE): + print("No Gesture") + elif (gesture == gesture_type.RIGHT_SWIPE): + print("Right Swipe. Speed: {}".format(gesture_speed)) + elif (gesture == gesture_type.LEFT_SWIPE): + print("Left Swipe. Speed: {}".format(gesture_speed)) + elif (gesture == gesture_type.UP_SWIPE): + print("Up Swipe. Speed: {}".format(gesture_speed)) + + time.sleep(.1) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/examples/i2c_gesture_interrupt.py b/examples/i2c_gesture_interrupt.py new file mode 100755 index 0000000..970b1dc --- /dev/null +++ b/examples/i2c_gesture_interrupt.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python + +""" +i2c_gesture_Interrupt.py +XYZ Interactive ZX Sensor +Shawn Hymel @ SparkFun Electronics +May 6, 2015 +Ported by: Christian Erhardt +Jan 6, 2018 +https://github.com/sparkfun/SparkFun_ZX_Distance_and_Gesture_Sensor_Arduino_Library +Tests the ZX sensor's ability to read gesture data over I2C using +an interrupt pin. This program configures I2C and sets up an +interrupt to occur whenever the ZX Sensor throws its DR pin high. +The gesture is displayed along with its "speed" (how long it takes +to complete the gesture). Note that higher numbers of "speed" +indicate a slower speed. +Hardware Connections: + + Raspberry Pin ZX Sensor Board Function + --------------------------------------- + 5V VCC Power + GND GND Ground + GPIO1 DA I2C Data + GPIO0 CL I2C Clock + GPIO17 DR Data Ready +Development environment specifics: +Written in python 2.7 +Tested with a SparkFun RedBoard +Distributed as-is; no warranty is given. +""" + +# standard +import time, sys, os +import logging +# project +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) +from zxsensor import * +# external +import RPi.GPIO as GPIO + +logging.basicConfig(level=logging.DEBUG) + +# Set the DA pin to GPIO 17 of the Raspberry Pi +channel = 17 +# Initialise the ZxSensor device using the default address +zx_sensor = ZxSensor(interrupts=interrupt_type.GESTURE_INTERRUPTS) + +# Read the model version number and ensure the library will work +ver = zx_sensor.get_model_version() +if (ver == ZX_ERROR): + print("Error reading model version!") + exit() +else: + print("Model version {}".format(ver)) + +if (not ver == ZX_MODEL_VER): + print("Model version needs to be {} to work with this library. Stopping".format(ZX_MODEL_VER)) + exit() + +# Read the register map version and ensure the library will work +ver = zx_sensor.get_reg_map_version() +if (ver == ZX_ERROR): + print("Error reading register map version number") + exit() +else: + print("Register map version {}".format(ver)) + +if (not ver == ZX_REG_MAP_VER): + print("Register map version needs to be {} to work with this library. Stopping".format(ZX_REG_MAP_VER)) + exit() + +def interrupt_callback(channel): + # You MUST read the STATUS register to clear the interrupt + zx_sensor.clear_interrupts() + + gesture = zx_sensor.read_gesture() + gesture_speed = zx_sensor.read_gesture_speed() + + print(gesture) + + if (gesture == gesture_type.NO_GESTURE): + print("No Gesture") + elif (gesture == gesture_type.RIGHT_SWIPE): + print("Right Swipe. Speed: {}".format(gesture_speed)) + elif (gesture == gesture_type.LEFT_SWIPE): + print("Left Swipe. Speed: {}".format(gesture_speed)) + elif (gesture == gesture_type.UP_SWIPE): + print("Up Swipe. Speed: {}".format(gesture_speed)) + +GPIO.setmode(GPIO.BCM) +GPIO.setup(channel, GPIO.IN) +GPIO.add_event_detect(channel, GPIO.RISING, callback=interrupt_callback) + +try: + # Clear Sensor interrupt and wait for pin to go low + zx_sensor.clear_interrupts() + time.sleep(.10) + + # Endless loop + while (True): + time.sleep(1) + +except KeyboardInterrupt: + GPIO.cleanup() + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/zxsensor/zx_sensor_demo.py b/examples/i2c_zx_demo.py similarity index 82% rename from zxsensor/zx_sensor_demo.py rename to examples/i2c_zx_demo.py index 4731a7f..7591c2c 100755 --- a/zxsensor/zx_sensor_demo.py +++ b/examples/i2c_zx_demo.py @@ -4,9 +4,10 @@ a Rasberry Pi. Tested with Pi2. When run, prints 'x' & 'z' to console """ -import time +import time, sys, os # project -from zx_sensor import ZxSensor +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) +from zxsensor import ZxSensor # Initialise the ZxSensor device using the default address zx_sensor = ZxSensor(0x10) diff --git a/zxsensor/.gitignore b/zxsensor/.gitignore new file mode 100644 index 0000000..d93ea15 --- /dev/null +++ b/zxsensor/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/zxsensor/Adafruit_I2C.pyc b/zxsensor/Adafruit_I2C.pyc deleted file mode 100755 index ea8b82c..0000000 Binary files a/zxsensor/Adafruit_I2C.pyc and /dev/null differ diff --git a/zxsensor/__init__.py b/zxsensor/__init__.py new file mode 100644 index 0000000..3d2ba9c --- /dev/null +++ b/zxsensor/__init__.py @@ -0,0 +1,5 @@ + +from Adafruit_I2C import Adafruit_I2C +# project +from i2c_registers import * +from zx_sensor import ZxSensor diff --git a/zxsensor/i2c_registers.py b/zxsensor/i2c_registers.py index fcf03e8..1de5e40 100755 --- a/zxsensor/i2c_registers.py +++ b/zxsensor/i2c_registers.py @@ -1,4 +1,5 @@ +from enum import Enum """ Register map and other constaints relating the zx_sensor I2C interface """ @@ -52,17 +53,15 @@ SET_ALL_DRE = 0b00111111 # Enumeration for possible gestures -gesture_type = { - 'RIGHT_SWIPE': 0x01, - 'LEFT_SWIPE': 0x02, - 'UP_SWIPE': 0x03, - 'NO_GESTURE': 0xFF, -} +class gesture_type(Enum): + RIGHT_SWIPE = 0x01 + LEFT_SWIPE = 0x02 + UP_SWIPE = 0x08 + NO_GESTURE = 0x00 # Enumeration for possible interrupt enables -interrupt_type = { - 'NO_INTERRUPTS': 0x00, - 'POSITION_INTERRUPTS': 0x01, - 'GESTURE_INTERRUPTS': 0x02, - 'ALL_INTERRUPTS': 0x03 -} +class interrupt_type(Enum): + NO_INTERRUPTS = 0x00 + POSITION_INTERRUPTS = 0x01 + GESTURE_INTERRUPTS = 0x02 + ALL_INTERRUPTS = 0x03 diff --git a/zxsensor/i2c_registers.pyc b/zxsensor/i2c_registers.pyc deleted file mode 100644 index 798e7ed..0000000 Binary files a/zxsensor/i2c_registers.pyc and /dev/null differ diff --git a/zxsensor/zx_sensor.py b/zxsensor/zx_sensor.py index caa6b1d..97043cc 100755 --- a/zxsensor/zx_sensor.py +++ b/zxsensor/zx_sensor.py @@ -1,45 +1,298 @@ +# -*- coding: utf-8 -*- # standard from __future__ import division, print_function +import logging # external from Adafruit_I2C import Adafruit_I2C # project from i2c_registers import * - class ZxSensor: """ Main class for interfacing with the zx_sensor """ - def __init__(self, address=0x10): + + def __init__(self, address=0x10, interrupts=interrupt_type.NO_INTERRUPTS, active_high = True): + """ + Main constructor for the class ZxSensor. Initializes the sensor and the interrupts + + Args: + address(:obj:`int`, optional): The i2c address of the sensor. Defaults to 0x10 + interrupts(:obj:`interrupt_type`, optional): which types of interrupts that + enables DR pin to assert on events. Defaults to NO_INTERRUPTS + active_high(bool, optional): sets the interrupt pin to active high or low. Defaults to True + """ + self.logger = logging.getLogger('ZxSensor') + self.i2c = Adafruit_I2C(address) + self.i2c.debug = False + + self.logger.info("model version %s", self.get_model_version()) + self.logger.info(self.get_reg_map_version()) - print("model version", self.get_model_version()) - print(self.get_reg_map_version()) - print(self.position_available()) + # Enable DR interrupts based on desired interrupts + if (not self.set_interrupt_trigger(interrupts)): + print("Could not set interrupt triggers!") + self.configure_interrupts(active_high, False); + if (interrupts == interrupt_type.NO_INTERRUPTS): + self.disable_interrupts() + else: + self.enable_interrupts() def get_model_version(self): + """Reads the sensor model version + + Returns: + sensor model version number + """ return self.i2c.readU8(ZX_MODEL) def get_reg_map_version(self): + """Reads the register map version + + Returns: + register map version number + """ return self.i2c.readU8(ZX_REGVER) + # ======================= + # Interrupt Configuration + # ======================= + + def set_interrupt_trigger(self, interrupts): + """Sets the triggers that cause the DR pin to change + + Args: + interrupts(interrupt_type): which types of interrupts to enable + + Returns: + True if operation successful. False otherwise. + """ + if (interrupts == interrupt_type.POSITION_INTERRUPTS): + if (not self.set_register_bit(ZX_DRE, DRE_CRD)): + return False + elif (interrupts == interrupt_type.GESTURE_INTERRUPTS): + if (not self.set_register_bit(ZX_DRE, DRE_SWP)): + return False + if (not self.set_register_bit(ZX_DRE, DRE_HOVER)): + return False + if (not self.set_register_bit(ZX_DRE, DRE_HVG)): + return False + elif (interrupts == interrupt_type.ALL_INTERRUPTS): + if (self.i2c.write8(ZX_DRE, SET_ALL_DRE) != None): + return False + else: + if (self.i2c.write8(ZX_DRE, 0x00) != None): + return False + return True + + def configure_interrupts(self, active_high=False, pin_pulse=False): + """Configures the behavior of the DR pin on an interrupt + + Args: + active_high(bool, optional): active_high true for DR active-high. + False for active-low. Defaults to False. + pin_pulse(bool, optional): true: DR pulse. False: DR pin asserts + until STATUS read. Defaults to False + + Returns: + True if operation successful. False otherwise. + """ + self.logger.debug("configuring interrupts, active_high: %s, pin_pulse: %s", active_high, pin_pulse) + # Set or clear polarity bit to make DR active-high or active-low + if (active_high): + if (not self.set_register_bit(ZX_DRCFG, DRCFG_POLARITY)): + return False + else: + if (not self.clear_register_bit(ZX_DRCFG, DRCFG_POLARITY)): + return False + + # Set or clear edge bit to make DR pulse or remain set until STATUS read + if (pin_pulse): + if (not self.set_register_bit(ZX_DRCFG, DRCFG_EDGE)): + return False + else: + if (not self.clear_register_bit(ZX_DRCFG, DRCFG_EDGE)): + return False + + def enable_interrupts(self): + """Turns on interrupts so that DR asserts on desired events. + + Returns: + True if operation successful. False otherwise. + """ + if (self.set_register_bit(ZX_DRCFG, DRCFG_EN)): + return True + return False + + def disable_interrupts(self): + """Turns off interrupts so that DR will never assert. + + Returns: + True if operation successful. False otherwise. + """ + if (self.clear_register_bit(ZX_DRCFG, DRCFG_EN)): + return True + return False + + def clear_interrupts(self): + """Reads the STATUS register to clear an interrupt (de-assert DR) + + Returns: + True if operation successful. False otherwise. + """ + self.logger.debug("Clearing interupts") + val = self.i2c.readU8(ZX_STATUS) + if (val == None): + self.logger.error("Could not read from register {}, error: {}".format(ZX_STATUS, val)) + return False + return True + + # ============== + # Data available + # ============== + + def gesture_available(self): + """Indicates that new gesture data is available + + Returns: + True if data is ready to be read. False otherwise. + """ + # read STATUS register + status = self.i2c.readU8(ZX_STATUS) + # extract bits and return + return status & 0b11100 + def position_available(self): + """Indicates that new position (X or Z) data is available + + Returns: + True if data is ready to be read. False otherwise. + """ # read STATUS register status = self.i2c.readU8(ZX_STATUS) # extract DAV bit and return return status & 1 + # ================ + # Sensor data read + # ================ + def read_x(self): + """Reads the X position data from the sensor + + Returns: + 0-240 for X position. 0xFF on read error. + """ x_pos = self.i2c.readU8(ZX_XPOS) if (not x_pos) or (x_pos > MAX_X): return ZX_ERROR return x_pos def read_z(self): + """Reads the Z position data from the sensor + + Returns: + 0-240 for Z position. 0xFF on read error. + """ z_pos = self.i2c.readU8(ZX_ZPOS) if (not z_pos) or (z_pos > MAX_Z): return ZX_ERROR return z_pos + def read_gesture(self): + """Reads the last detected gesture from the sensor + 0x01 Right Swipe + 0x02 Left Swipe + 0x08 Up Swipe + + Returns: + a number corresponding to a gesture. 0xFF on error. + + """ + # Read GESTURE register and return the value + gesture = self.i2c.readU8(ZX_GESTURE) + self.logger.debug("Read gesture {} from register {}".format(gesture, ZX_GESTURE)) + + if (gesture == None): + return gesture_type.NO_GESTURE + elif (gesture == gesture_type.RIGHT_SWIPE.value): + return gesture_type.RIGHT_SWIPE + elif (gesture == gesture_type.LEFT_SWIPE.value): + return gesture_type.LEFT_SWIPE + elif (gesture == gesture_type.UP_SWIPE.value): + return gesture_type.UP_SWIPE + else: + return gesture_type.NO_GESTURE + + def read_gesture_speed(self): + """Reads the speed of the last gesture from the sensor + + Returns: + a number corresponding to the speed of the gesture. 0xFF on error. + """ + val = self.i2c.readU8(ZX_GSPEED) + return val + + # ================ + # Bit Manipulation + # ================ + + def set_register_bit(self, reg, bit): + """sets a bit in a register over I2C + + Args: + reg(:obj:`int`): the register to set the bit + bit(:obj:`int`): the number of the bit (0-7) to set + + Returns: + True if successful write operation. False otherwise. + """ + self.logger.debug("Setting bit {} in register {:02X}".format(bit, reg)) + # Read value from register + val = self.i2c.readU8(reg) + if (val == None): + self.logger.error("Read from i2c register %s returned no value!", reg) + return False + + self.logger.debug("Read value {:08b} from register {}".format(val, reg)) + # Set bits in register and write back to the register + val |= (1 << bit) + self.logger.debug("Setting value {:08b} to register {}".format(val, reg)) + + retval = self.i2c.write8(reg, val) + if (retval != None): + self.logger.error("Writing value %s to register %s was not successfull. Error message: %s", val, reg, retval) + return False + return True + + def clear_register_bit(self, reg, bit): + """clears a bit in a register over I2C + + Args: + reg(:obj:`int`): the register to clear the bit + bit(:obj:`int`): the number of the bit (0-7) to clear + + Returns: + True if successful write operation. False otherwise. + """ + self.logger.debug("Clearing bit {} from register {:02X}".format(bit, reg)) + # clear the value from register + val = self.i2c.readU8(reg) + if (val == None): + self.logger.error("Read from i2c register %s returned no value!", reg) + return False + + self.logger.debug("Read value {:08b} from register {}".format(val, reg)) + val &= ~(1 << bit) + self.logger.debug("Setting value {:08b} to register {}, bit: {}".format(val, reg, bit)) + retval = self.i2c.write8(reg, val) + if (retval != None): + self.logger.error("Writing value %s to register %s was not successfull. Error message: %s", val, reg, retval) + return False + return True + if __name__ == '__main__': pass + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/zxsensor/zx_sensor.pyc b/zxsensor/zx_sensor.pyc deleted file mode 100644 index 3a45298..0000000 Binary files a/zxsensor/zx_sensor.pyc and /dev/null differ