Added accelerometer sensors plugin
This commit is contained in:
parent
53ba5f6628
commit
b3f20ca0de
2 changed files with 373 additions and 0 deletions
57
platypush/plugins/gpio/sensor/accelerometer/__init__.py
Normal file
57
platypush/plugins/gpio/sensor/accelerometer/__init__.py
Normal file
|
@ -0,0 +1,57 @@
|
|||
import threading
|
||||
import time
|
||||
|
||||
from platypush.plugins import action
|
||||
from platypush.plugins.gpio.sensor import GpioSensorPlugin
|
||||
|
||||
|
||||
class GpioSensorAccelerometerPlugin(GpioSensorPlugin):
|
||||
"""
|
||||
Plugin to interact with an accelerometer sensor and get X,Y,Z position.
|
||||
Tested with Adafruit LIS3DH accelerometer (https://www.adafruit.com/product/2809)
|
||||
with Raspberry Pi over I2C connection.
|
||||
|
||||
Requires:
|
||||
|
||||
* ``Adafruit_Python_GPIO`` (``pip install Adafruit_Python_GPIO``)
|
||||
"""
|
||||
|
||||
def __init__(self, g=4, *args, **kwargs):
|
||||
"""
|
||||
Only LIS3DH in I2C mode is currently supported: https://learn.adafruit.com/assets/59080.
|
||||
|
||||
:param g: Accelerometer range as a multiple of G - can be 2G, 4G, 8G or 16G
|
||||
:type g: int
|
||||
"""
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
from platypush.plugins.gpio.sensor.accelerometer.lib.LIS3DH import LIS3DH
|
||||
|
||||
if g == 2:
|
||||
self.g = LIS3DH.RANGE_2G
|
||||
elif g == 4:
|
||||
self.g = LIS3DH.RANGE_4G
|
||||
elif g == 8:
|
||||
self.g = LIS3DH.RANGE_8G
|
||||
elif g == 16:
|
||||
self.g = LIS3DH.RANGE_16G
|
||||
else:
|
||||
raise RuntimeError('Invalid G range: {}'.format(g))
|
||||
|
||||
self.sensor = LIS3DH()
|
||||
self.sensor.setRange(self.g)
|
||||
|
||||
|
||||
@action
|
||||
def get_measurement(self):
|
||||
"""
|
||||
Extends :func:`.GpioSensorPlugin.get_measurement`
|
||||
|
||||
:returns: The sensor's current position as a list with three elements (x,y,z)
|
||||
"""
|
||||
|
||||
return [self.sensor.getX(), self.sensor.getY(), self.sensor.getZ()]
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
316
platypush/plugins/gpio/sensor/accelerometer/lib/LIS3DH.py
Normal file
316
platypush/plugins/gpio/sensor/accelerometer/lib/LIS3DH.py
Normal file
|
@ -0,0 +1,316 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
LIS3DH Python Library for Raspberry Pi
|
||||
Created by Matt Dyson (mattdyson.org)
|
||||
Project hosted at https://github.com/mattdy/python-lis3dh
|
||||
Version 1.0 - 10/01/16
|
||||
Version 1.1 - 19/03/16 (Mal Smalley) Adding click detection
|
||||
Version 1.2 - 21/01/18 (holzfigure) Using new Adafruit library Adafruit_GPIO;
|
||||
Changing default busnumber (-1 -> 1);
|
||||
Fixing indentation issues;
|
||||
Increasing PEP8 and Python3 compatibility;
|
||||
Replacing variable name "range"
|
||||
(which is a keyword) with "g-range";
|
||||
|
||||
Requires the Adafruit_Python_GPIO library
|
||||
https://github.com/adafruit/Adafruit_Python_GPIO
|
||||
|
||||
Inspiration and assistance from:
|
||||
- https://github.com/adafruit/Adafruit_LIS3DH
|
||||
- https://www.adafruit.com/datasheets/LIS3DH.pdf
|
||||
"""
|
||||
|
||||
from Adafruit_GPIO import I2C
|
||||
import RPi.GPIO as GPIO # needed for Hardware interrupt
|
||||
|
||||
|
||||
class LIS3DH:
|
||||
|
||||
# I2C
|
||||
i2c = None
|
||||
I2C_ADDRESS_1 = 0x18
|
||||
I2C_ADDRESS_2 = 0x19
|
||||
# default
|
||||
I2C_DEFAULT = I2C_ADDRESS_1
|
||||
|
||||
# bus
|
||||
BUS_NUMBER = 1 # -1
|
||||
|
||||
# Ranges
|
||||
RANGE_2G = 0b00 # default
|
||||
RANGE_4G = 0b01
|
||||
RANGE_8G = 0b10
|
||||
RANGE_16G = 0b11
|
||||
# default
|
||||
RANGE_DEFAULT = RANGE_2G
|
||||
|
||||
# Refresh rates
|
||||
DATARATE_400HZ = 0b0111 # 400Hz # default
|
||||
DATARATE_200HZ = 0b0110 # 200Hz
|
||||
DATARATE_100HZ = 0b0101 # 100Hz
|
||||
DATARATE_50HZ = 0b0100 # 50Hz
|
||||
DATARATE_25HZ = 0b0011 # 25Hz
|
||||
DATARATE_10HZ = 0b0010 # 10Hz
|
||||
DATARATE_1HZ = 0b0001 # 1Hz
|
||||
DATARATE_POWERDOWN = 0 # Power down
|
||||
DATARATE_LOWPOWER_1K6HZ = 0b1000 # Low power mode (1.6KHz)
|
||||
DATARATE_LOWPOWER_5KHZ = 0b1001 # Low power mode (5KHz) / Normal power mode (1.25KHz)
|
||||
# default
|
||||
DATARATE_DEFAULT = DATARATE_400HZ
|
||||
|
||||
# Registers
|
||||
REG_STATUS1 = 0x07
|
||||
REG_OUTADC1_L = 0x08
|
||||
REG_OUTADC1_H = 0x09
|
||||
REG_OUTADC2_L = 0x0A
|
||||
REG_OUTADC2_H = 0x0B
|
||||
REG_OUTADC3_L = 0x0C
|
||||
REG_OUTADC3_H = 0x0D
|
||||
REG_INTCOUNT = 0x0E
|
||||
REG_WHOAMI = 0x0F # Device identification register
|
||||
REG_TEMPCFG = 0x1F
|
||||
REG_CTRL1 = 0x20 # Used for data rate selection, and enabling/disabling individual axis
|
||||
REG_CTRL2 = 0x21
|
||||
REG_CTRL3 = 0x22
|
||||
REG_CTRL4 = 0x23 # Used for BDU, scale selection, resolution selection and self-testing
|
||||
REG_CTRL5 = 0x24
|
||||
REG_CTRL6 = 0x25
|
||||
REG_REFERENCE = 0x26
|
||||
REG_STATUS2 = 0x27
|
||||
REG_OUT_X_L = 0x28
|
||||
REG_OUT_X_H = 0x29
|
||||
REG_OUT_Y_L = 0x2A
|
||||
REG_OUT_Y_H = 0x2B
|
||||
REG_OUT_Z_L = 0x2C
|
||||
REG_OUT_Z_H = 0x2D
|
||||
REG_FIFOCTRL = 0x2E
|
||||
REG_FIFOSRC = 0x2F
|
||||
REG_INT1CFG = 0x30
|
||||
REG_INT1SRC = 0x31
|
||||
REG_INT1THS = 0x32
|
||||
REG_INT1DUR = 0x33
|
||||
REG_CLICKCFG = 0x38
|
||||
REG_CLICKSRC = 0x39
|
||||
REG_CLICKTHS = 0x3A
|
||||
REG_TIMELIMIT = 0x3B
|
||||
REG_TIMELATENCY = 0x3C
|
||||
REG_TIMEWINDOW = 0x3D
|
||||
|
||||
# Values
|
||||
DEVICE_ID = 0x33
|
||||
INT_IO = 0x04 # GPIO pin for interrupt
|
||||
CLK_NONE = 0x00
|
||||
CLK_SINGLE = 0x01
|
||||
CLK_DOUBLE = 0x02
|
||||
|
||||
AXIS_X = 0x00
|
||||
AXIS_Y = 0x01
|
||||
AXIS_Z = 0x02
|
||||
|
||||
# changed busnumber to 1 (from -1)
|
||||
# alternative i2c address=0x19
|
||||
def __init__(self, address=I2C_DEFAULT, bus=BUS_NUMBER,
|
||||
g_range=RANGE_DEFAULT, datarate=DATARATE_DEFAULT,
|
||||
debug=False):
|
||||
self.isDebug = debug
|
||||
self.debug("Initialising LIS3DH")
|
||||
|
||||
# self.i2c = Adafruit_I2C(address, busnum=bus)
|
||||
self.i2c = I2C.Device(address, busnum=bus)
|
||||
self.address = address
|
||||
|
||||
try:
|
||||
val = self.i2c.readU8(self.REG_WHOAMI)
|
||||
if val != self.DEVICE_ID:
|
||||
# raise Exception(("Device ID incorrect - expected 0x%X, " +
|
||||
# "got 0x%X at address 0x%X") % (
|
||||
# self.DEVICE_ID, val, self.address))
|
||||
raise Exception(("Device ID incorrect - expected 0x{:x}, " +
|
||||
"got 0x{:x} at address 0x{:x}").format(
|
||||
self.DEVICE_ID, val, self.address))
|
||||
|
||||
self.debug(("Successfully connected to LIS3DH " +
|
||||
"at address 0x{:x}").format(self.address))
|
||||
except Exception as e:
|
||||
print("Error establishing connection with LIS3DH")
|
||||
print(e)
|
||||
|
||||
# Enable all axis
|
||||
self.setAxisStatus(self.AXIS_X, True)
|
||||
self.setAxisStatus(self.AXIS_Y, True)
|
||||
self.setAxisStatus(self.AXIS_Z, True)
|
||||
|
||||
# Set refresh rate (default: 400Hz)
|
||||
self.setDataRate(datarate)
|
||||
|
||||
self.setHighResolution()
|
||||
self.setBDU()
|
||||
|
||||
self.setRange(g_range)
|
||||
|
||||
# Get reading from X axis
|
||||
def getX(self):
|
||||
return self.getAxis(self.AXIS_X)
|
||||
|
||||
# Get reading from Y axis
|
||||
def getY(self):
|
||||
return self.getAxis(self.AXIS_Y)
|
||||
|
||||
# Get reading from Z axis
|
||||
def getZ(self):
|
||||
return self.getAxis(self.AXIS_Z)
|
||||
|
||||
# Get a reading from the desired axis
|
||||
def getAxis(self, axis):
|
||||
# Determine which register we need to read from (2 per axis)
|
||||
base = self.REG_OUT_X_L + (2 * axis)
|
||||
|
||||
# Read the first register (lower bits)
|
||||
low = self.i2c.readU8(base)
|
||||
# Read the next register (higher bits)
|
||||
high = self.i2c.readU8(base + 1)
|
||||
# Combine the two components
|
||||
res = low | (high << 8)
|
||||
# Calculate the twos compliment of the result
|
||||
res = self.twosComp(res)
|
||||
|
||||
# Fetch the range we're set to, so we can
|
||||
# accurately calculate the result
|
||||
g_range = self.getRange()
|
||||
divisor = 1
|
||||
if g_range == self.RANGE_2G: divisor = 16380
|
||||
elif g_range == self.RANGE_4G: divisor = 8190
|
||||
elif g_range == self.RANGE_8G: divisor = 4096
|
||||
elif g_range == self.RANGE_16G: divisor = 1365.33
|
||||
|
||||
return float(res) / divisor
|
||||
|
||||
# Get the range that the sensor is currently set to
|
||||
def getRange(self):
|
||||
val = self.i2c.readU8(self.REG_CTRL4) # Get value from register
|
||||
val = (val >> 4) # Remove lowest 4 bits
|
||||
val &= 0b0011 # Mask off two highest bits
|
||||
|
||||
if val == self.RANGE_2G: return self.RANGE_2G
|
||||
elif val == self.RANGE_4G: return self.RANGE_4G
|
||||
elif val == self.RANGE_8G: return self.RANGE_8G
|
||||
else: return self.RANGE_16G
|
||||
|
||||
# Set the range of the sensor (2G, 4G, 8G, 16G)
|
||||
def setRange(self, g_range):
|
||||
if g_range < 0 or g_range > 3:
|
||||
raise Exception("Tried to set invalid range")
|
||||
|
||||
val = self.i2c.readU8(self.REG_CTRL4) # Get value from register
|
||||
val &= ~(0b110000) # Mask off lowest 4 bits
|
||||
val |= (g_range << 4) # Write in our new range
|
||||
self.writeRegister(self.REG_CTRL4, val) # Write back to register
|
||||
|
||||
# Enable or disable an individual axis
|
||||
# Read status from CTRL_REG1, then write back with
|
||||
# appropriate status bit changed
|
||||
def setAxisStatus(self, axis, enable):
|
||||
if axis < 0 or axis > 2:
|
||||
raise Exception("Tried to modify invalid axis")
|
||||
|
||||
current = self.i2c.readU8(self.REG_CTRL1)
|
||||
status = 1 if enable else 0
|
||||
final = self.setBit(current, axis, status)
|
||||
self.writeRegister(self.REG_CTRL1, final)
|
||||
|
||||
def setInterrupt(self, mycallback):
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
GPIO.setup(self.INT_IO, GPIO.IN)
|
||||
GPIO.add_event_detect(self.INT_IO, GPIO.RISING, callback=mycallback)
|
||||
|
||||
def setClick(self, clickmode, clickthresh=80,
|
||||
timelimit=10, timelatency=20, timewindow=100,
|
||||
mycallback=None):
|
||||
if (clickmode == self.CLK_NONE):
|
||||
val = self.i2c.readU8(self.REG_CTRL3) # Get value from register
|
||||
val &= ~(0x80) # unset bit 8 to disable interrupt
|
||||
self.writeRegister(self.REG_CTRL3, val) # Write back to register
|
||||
self.writeRegister(self.REG_CLICKCFG, 0) # disable all interrupts
|
||||
return
|
||||
self.writeRegister(self.REG_CTRL3, 0x80) # turn on int1 click
|
||||
self.writeRegister(self.REG_CTRL5, 0x08) # latch interrupt on int1
|
||||
|
||||
if (clickmode == self.CLK_SINGLE):
|
||||
# turn on all axes & singletap
|
||||
self.writeRegister(self.REG_CLICKCFG, 0x15)
|
||||
if (clickmode == self.CLK_DOUBLE):
|
||||
# turn on all axes & doubletap
|
||||
self.writeRegister(self.REG_CLICKCFG, 0x2A)
|
||||
|
||||
# set timing parameters
|
||||
self.writeRegister(self.REG_CLICKTHS, clickthresh)
|
||||
self.writeRegister(self.REG_TIMELIMIT, timelimit)
|
||||
self.writeRegister(self.REG_TIMELATENCY, timelatency)
|
||||
self.writeRegister(self.REG_TIMEWINDOW, timewindow)
|
||||
|
||||
if mycallback is not None:
|
||||
self.setInterrupt(mycallback)
|
||||
|
||||
def getClick(self):
|
||||
reg = self.i2c.readU8(self.REG_CLICKSRC) # read click register
|
||||
self.i2c.readU8(self.REG_INT1SRC) # reset interrupt flag
|
||||
return reg
|
||||
|
||||
# Set the rate (cycles per second) at which data is gathered
|
||||
def setDataRate(self, dataRate):
|
||||
val = self.i2c.readU8(self.REG_CTRL1) # Get current value
|
||||
val &= 0b1111 # Mask off lowest 4 bits
|
||||
val |= (dataRate << 4) # Write in our new data rate to highest 4 bits
|
||||
self.writeRegister(self.REG_CTRL1, val) # Write back to register
|
||||
|
||||
# Set whether we want to use high resolution or not
|
||||
def setHighResolution(self, highRes=True):
|
||||
val = self.i2c.readU8(self.REG_CTRL4) # Get current value
|
||||
status = 1 if highRes else 0
|
||||
|
||||
# High resolution is bit 4 of REG_CTRL4
|
||||
final = self.setBit(val, 3, status)
|
||||
self.writeRegister(self.REG_CTRL4, final)
|
||||
|
||||
# Set whether we want to use block data update or not
|
||||
# False = output registers not updated until MSB and LSB reading
|
||||
def setBDU(self, bdu=True):
|
||||
val = self.i2c.readU8(self.REG_CTRL4) # Get current value
|
||||
status = 1 if bdu else 0
|
||||
|
||||
# Block data update is bit 8 of REG_CTRL4
|
||||
final = self.setBit(val, 7, status)
|
||||
self.writeRegister(self.REG_CTRL4, final)
|
||||
|
||||
# Write the given value to the given register
|
||||
def writeRegister(self, register, value):
|
||||
self.debug("WRT {} to register 0x{:x}".format(
|
||||
bin(value), register))
|
||||
self.i2c.write8(register, value)
|
||||
|
||||
# Set the bit at index 'bit' to 'value' on 'input' and return
|
||||
def setBit(self, input, bit, value):
|
||||
mask = 1 << bit
|
||||
input &= ~mask
|
||||
if value:
|
||||
input |= mask
|
||||
return input
|
||||
|
||||
# Return a 16-bit signed number (two's compliment)
|
||||
# Thanks to http://stackoverflow.com/questions/16124059/trying-to-
|
||||
# read-a-twos-complement-16bit-into-a-signed-decimal
|
||||
def twosComp(self, x):
|
||||
if (0x8000 & x):
|
||||
x = - (0x010000 - x)
|
||||
return x
|
||||
|
||||
# Print an output of all registers
|
||||
def dumpRegisters(self):
|
||||
for x in range(0x0, 0x3D):
|
||||
read = self.i2c.readU8(x)
|
||||
print("{:x}: {}".format(x, bin(read)))
|
||||
|
||||
def debug(self, message):
|
||||
if not self.isDebug:
|
||||
return
|
||||
print(message)
|
Loading…
Reference in a new issue