Browse Source

re-add code at top level of repo; main.py -> code.py

master
Brennen Bearnes 2 years ago
parent
commit
a4d3be9b0c
19 changed files with 3254 additions and 0 deletions
  1. +4
    -0
      code.py
  2. +260
    -0
      glitterpos.py
  3. +77
    -0
      glitterpos_util.py
  4. +0
    -0
      lib/adafruit_bus_device/__init__.py
  5. +111
    -0
      lib/adafruit_bus_device/i2c_device.py
  6. +100
    -0
      lib/adafruit_bus_device/spi_device.py
  7. +251
    -0
      lib/adafruit_gps.py
  8. +241
    -0
      lib/adafruit_lsm303.py
  9. +431
    -0
      lib/adafruit_lsm9ds1.py
  10. +0
    -0
      lib/adafruit_register/__init__.py
  11. +185
    -0
      lib/adafruit_register/i2c_bcd_alarm.py
  12. +103
    -0
      lib/adafruit_register/i2c_bcd_datetime.py
  13. +73
    -0
      lib/adafruit_register/i2c_bit.py
  14. +88
    -0
      lib/adafruit_register/i2c_bits.py
  15. +93
    -0
      lib/adafruit_register/i2c_struct.py
  16. +112
    -0
      lib/adafruit_register/i2c_struct_array.py
  17. +626
    -0
      lib/adafruit_rfm9x.py
  18. +227
    -0
      lib/neopixel.py
  19. +272
    -0
      lib/simpleio.py

+ 4
- 0
code.py View File

@ -0,0 +1,4 @@
from glitterpos import GlitterPOS
gp = GlitterPOS()
while True:
gp.advance_frame()

+ 260
- 0
glitterpos.py View File

@ -0,0 +1,260 @@
"""glitter positioning system"""
import time
import gc
import math
import adafruit_lsm9ds1
import adafruit_gps
import adafruit_rfm9x
import board
import busio
import digitalio
import neopixel
import rtc
from glitterpos_util import timestamp, compass_bearing, bearing_to_pixel, map_range
# glitterpos_id.py should be unique to each box, and formatted as follows:
#
# MY_ID = 0 # must be a unique integer
# MAG_MIN = (-0.25046, -0.23506, -0.322)
# MAG_MAX = (0.68278, 0.70882, 0.59654)
#
# From the CircuitPython REPL, use `import calibrate` to find values for
# MAG_MIN and MAG_MAX.
from glitterpos_id import MY_ID, MAG_MIN, MAG_MAX
# Colors for status lights, etc.
RED = (255, 0, 0)
YELLOW = (255, 150, 0)
GREEN = (0, 255, 0)
CYAN = (0, 255, 255)
BLUE = (0, 0, 255)
PURPLE = (180, 0, 255)
MAN_ID = 23
# Magnetic North - should be customized for your location:
DECLINATION_RAD = 235.27 / 1000.0 # Black Rock City in radians
COLOR_LOOKUP = {
0: GREEN,
1: BLUE,
2: PURPLE,
3: YELLOW,
4: CYAN,
5: (100, 0, 255),
6: (0, 100, 200),
7: (100, 50, 100),
MAN_ID: RED,
}
RADIO_FREQ_MHZ = 915.0
CS = digitalio.DigitalInOut(board.D10)
RESET = digitalio.DigitalInOut(board.D11)
class GlitterPOS:
"""glitter positioning system"""
def __init__(self):
"""configure sensors, radio, blinkenlights"""
# Our id and the dict for storing coords of other glitterpos_boxes:
self.glitterpos_id = MY_ID
self.glitterpos_boxes = {
MAN_ID: (40.786462, -119.206686),
}
# Set the RTC to an obviously bogus time for debugging purposes:
# time_struct takes: (tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec, tm_wday, tm_yday, tm_isdst)
rtc.RTC().datetime = time.struct_time((2000, 1, 1, 0, 0, 0, 0, 0, 0))
print("startup time: " + timestamp())
self.time_set = False
self.last_send = time.monotonic()
# A tuple for our lat/long:
self.coords = (0, 0)
self.heading = 0.0
# Status light on the board, we'll use to indicate GPS fix, etc.:
self.statuslight = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.005, auto_write=True)
self.statuslight.fill(RED)
# Neopixel ring:
self.pixels = neopixel.NeoPixel(board.A1, 16, brightness=0.01, auto_write=False)
self.startup_animation()
time.sleep(2)
self.init_radio()
self.init_gps()
self.init_compass()
self.statuslight.fill(YELLOW)
def startup_animation(self):
self.pixels[bearing_to_pixel(0)] = PURPLE
self.pixels.show()
time.sleep(.5)
self.pixels[bearing_to_pixel(90)] = GREEN
self.pixels.show()
time.sleep(.5)
self.pixels[bearing_to_pixel(180)] = YELLOW
self.pixels.show()
time.sleep(.5)
self.pixels[bearing_to_pixel(270)] = BLUE
self.pixels.show()
def init_radio(self):
"""Set up RFM95."""
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
self.rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)
self.rfm9x.tx_power = 18 # Default is 13 dB, but the RFM95 can go up to 23 dB
self.radio_tx('d', 'hello world')
time.sleep(1)
def init_gps(self):
"""Some GPS module setup."""
uart = busio.UART(board.TX, board.RX, baudrate=9600, timeout=3000)
gps = adafruit_gps.GPS(uart)
time.sleep(1)
# https://cdn-shop.adafruit.com/datasheets/PMTK_A11.pdf
# Turn on the basic GGA and RMC info (what you typically want), then
# set update to once a second:
gps.send_command('PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0')
gps.send_command('PMTK220,1000')
self.gps = gps
def init_compass(self):
i2c = busio.I2C(board.SCL, board.SDA)
self.compass = adafruit_lsm9ds1.LSM9DS1_I2C(i2c)
time.sleep(1)
def advance_frame(self):
"""Essentially our main program loop."""
current = time.monotonic()
self.radio_rx(timeout=0.5)
new_gps_data = self.gps.update()
self.update_heading()
self.display_pixels()
if not self.gps.has_fix:
# Try again if we don't have a fix yet.
self.statuslight.fill(RED)
return
# We want to send coordinates out either on new GPS data or roughly every 15 seconds.
if (not new_gps_data) and (current - self.last_send < 15):
return
# Set the RTC to GPS time (UTC):
if new_gps_data and not self.time_set:
rtc.RTC().datetime = self.gps.timestamp_utc
self.time_set = True
gps_coords = (self.gps.latitude, self.gps.longitude)
if gps_coords == self.coords:
return
self.coords = (self.gps.latitude, self.gps.longitude)
self.statuslight.fill(BLUE)
print(':: ' + str(current)) # Print a separator line.
print(timestamp())
send_packet = '{}:{}:{}:{}'.format(
self.gps.latitude,
self.gps.longitude,
self.gps.speed_knots,
self.heading
)
print(' quality: {}'.format(self.gps.fix_quality))
print(' ' + str(gc.mem_free()) + " bytes free")
# Send a location packet:
self.radio_tx('l', send_packet)
def update_heading(self):
mag_x, mag_y, mag_z = self.compass.magnetometer
# print('Magnetometer: ({0:10.3f}, {1:10.3f}, {2:10.3f})'.format(mag_x, mag_y, mag_z))
mag_x = map_range(mag_x, MAG_MIN[0], MAG_MAX[0], -1, 1)
mag_y = map_range(mag_y, MAG_MIN[1], MAG_MAX[1], -1, 1)
mag_z = map_range(mag_z, MAG_MIN[2], MAG_MAX[2], -1, 1)
heading_mag = (math.atan2(mag_y, mag_x) * 180) / math.pi
if heading_mag < 0:
heading_mag = 360 + heading_mag
# Account for declination (given in radians above):
heading = heading_mag + (DECLINATION_RAD * 180 / math.pi)
if heading > 360:
heading = heading - 360
print('heading: {}'.format(heading))
self.heading = heading
def radio_tx(self, msg_type, msg):
"""send a packet over radio with id prefix and checksum"""
packet = 'e:' + msg_type + ':' + str(self.glitterpos_id) + ':' + msg
print(' sending: ' + packet)
# Blocking, max of 252 bytes:
self.rfm9x.send(packet)
self.last_send = time.monotonic()
def radio_rx(self, timeout=0.5):
"""check radio for new packets, handle incoming data"""
packet = self.rfm9x.receive(timeout)
# If no packet was received during the timeout then None is returned:
if packet is None:
return
packet = bytes(packet)
print(timestamp())
print(' received signal strength: {0} dB'.format(self.rfm9x.rssi))
print(' received (raw bytes): {0}'.format(packet))
pieces = packet.split(b':')
if pieces[0] != b'e':
print(' bogus packet, bailing out')
return
msg_type = pieces[1].format()
sender_id = int(pieces[2].format())
# A location message:
if msg_type == 'l':
sender_lat = float(pieces[3].format())
sender_lon = float(pieces[4].format())
self.glitterpos_boxes[sender_id] = (sender_lat, sender_lon)
# packet_text = str(packet, 'ascii')
# print('Received (ASCII): {0}'.format(packet_text))
def display_pixels(self):
"""Display current state on the NeoPixel ring."""
self.pixels.fill((0, 0, 0))
if not self.gps.has_fix:
return
for box in self.glitterpos_boxes:
bearing_to_box = compass_bearing(self.coords, self.glitterpos_boxes[box])
# Treat current compass heading as our origin point for display purposes:
display_bearing = bearing_to_box - self.heading
if display_bearing < 0:
display_bearing = display_bearing + 360
pixel = bearing_to_pixel(display_bearing)
# print('display pixel: {}'.format(pixel))
color = (15, 15, 15)
if box in COLOR_LOOKUP:
color = COLOR_LOOKUP[box]
self.pixels[pixel] = color
self.pixels.show()

+ 77
- 0
glitterpos_util.py View File

@ -0,0 +1,77 @@
"""a handful of utility functions used by GlitterPOS."""
import math
import time
# https://gist.githubusercontent.com/jeromer/2005586/raw/5456a9386acce189ac6cc416c42e9c4b560a633b/compassbearing.py
def compass_bearing(pointA, pointB):
"""
Calculates the bearing between two points.
The formulae used is the following:
θ = atan2(sin(Δlong).cos(lat2),
cos(lat1).sin(lat2) sin(lat1).cos(lat2).cos(Δlong))
:Parameters:
- `pointA: The tuple representing the latitude/longitude for the
first point. Latitude and longitude must be in decimal degrees
- `pointB: The tuple representing the latitude/longitude for the
second point. Latitude and longitude must be in decimal degrees
:Returns:
The bearing in degrees
:Returns Type:
float
"""
if (type(pointA) != tuple) or (type(pointB) != tuple):
raise TypeError("Only tuples are supported as arguments")
lat1 = math.radians(pointA[0])
lat2 = math.radians(pointB[0])
diffLong = math.radians(pointB[1] - pointA[1])
x = math.sin(diffLong) * math.cos(lat2)
y = math.cos(lat1) * math.sin(lat2) - (math.sin(lat1)
* math.cos(lat2) * math.cos(diffLong))
initial_bearing = math.atan2(x, y)
# Now we have the initial bearing but math.atan2 return values
# from -180° to + 180° which is not what we want for a compass bearing
# The solution is to normalize the initial bearing as shown below
initial_bearing = math.degrees(initial_bearing)
compass_bearing = (initial_bearing + 360) % 360
return compass_bearing
def bearing_to_pixel(bearing, count=16):
# Subtract from count since the neopixel ring runs counterclockwise:
pixel = count - int(round((bearing / 360) * count))
if pixel == 16:
return 0
return pixel
def map_range(x, in_min, in_max, out_min, out_max):
"""
Maps a number from one range to another.
:return: Returns value mapped to new range
:rtype: float
"""
mapped = (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
if out_min <= out_max:
return max(min(mapped, out_max), out_min)
return min(max(mapped, out_max), out_min)
def timestamp():
"""print a human-readable timestamp"""
timestamp = time.localtime()
return '{}/{}/{} {:02}:{:02}:{:02}'.format(
timestamp.tm_year,
timestamp.tm_mon,
timestamp.tm_mday,
timestamp.tm_hour,
timestamp.tm_min,
timestamp.tm_sec
)

+ 0
- 0
lib/adafruit_bus_device/__init__.py View File


+ 111
- 0
lib/adafruit_bus_device/i2c_device.py View File

@ -0,0 +1,111 @@
# The MIT License (MIT)
#
# Copyright (c) 2016 Scott Shawcroft for Adafruit Industries
#
# 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.
"""
`adafruit_bus_device.i2c_device` - I2C Bus Device
====================================================
"""
__version__ = "2.2.2"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BusDevice.git"
class I2CDevice:
"""
Represents a single I2C device and manages locking the bus and the device
address.
:param ~busio.I2C i2c: The I2C bus the device is on
:param int device_address: The 7 bit device address
.. note:: This class is **NOT** built into CircuitPython. See
:ref:`here for install instructions <bus_device_installation>`.
Example:
.. code-block:: python
import busio
from board import *
from adafruit_bus_device.i2c_device import I2CDevice
with busio.I2C(SCL, SDA) as i2c:
device = I2CDevice(i2c, 0x70)
bytes_read = bytearray(4)
with device:
device.readinto(bytes_read)
# A second transaction
with device:
device.write(bytes_read)
"""
def __init__(self, i2c, device_address):
# Verify that a deivce with that address exists.
while not i2c.try_lock():
pass
try:
i2c.writeto(device_address, b'')
except OSError:
raise ValueError("No I2C device at address: %x" % device_address)
finally:
i2c.unlock()
self.i2c = i2c
self.device_address = device_address
def readinto(self, buf, **kwargs):
"""
Read into ``buf`` from the device. The number of bytes read will be the
length of ``buf``.
If ``start`` or ``end`` is provided, then the buffer will be sliced
as if ``buf[start:end]``. This will not cause an allocation like
``buf[start:end]`` will so it saves memory.
:param bytearray buffer: buffer to write into
:param int start: Index to start writing at
:param int end: Index to write up to but not include
"""
self.i2c.readfrom_into(self.device_address, buf, **kwargs)
def write(self, buf, **kwargs):
"""
Write the bytes from ``buffer`` to the device. Transmits a stop bit if
``stop`` is set.
If ``start`` or ``end`` is provided, then the buffer will be sliced
as if ``buffer[start:end]``. This will not cause an allocation like
``buffer[start:end]`` will so it saves memory.
:param bytearray buffer: buffer containing the bytes to write
:param int start: Index to start writing from
:param int end: Index to read up to but not include
:param bool stop: If true, output an I2C stop condition after the buffer is written
"""
self.i2c.writeto(self.device_address, buf, **kwargs)
def __enter__(self):
while not self.i2c.try_lock():
pass
return self
def __exit__(self, *exc):
self.i2c.unlock()
return False

+ 100
- 0
lib/adafruit_bus_device/spi_device.py View File

@ -0,0 +1,100 @@
# The MIT License (MIT)
#
# Copyright (c) 2016 Scott Shawcroft for Adafruit Industries
#
# 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.
# pylint: disable=too-few-public-methods
"""
`adafruit_bus_device.spi_device` - SPI Bus Device
====================================================
"""
__version__ = "2.2.2"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BusDevice.git"
class SPIDevice:
"""
Represents a single SPI device and manages locking the bus and the device
address.
:param ~busio.SPI spi: The SPI bus the device is on
:param ~digitalio.DigitalInOut chip_select: The chip select pin object that implements the
DigitalInOut API.
:param int extra_clocks: The minimum number of clock cycles to cycle the bus after CS is high.
(Used for SD cards.)
.. note:: This class is **NOT** built into CircuitPython. See
:ref:`here for install instructions <bus_device_installation>`.
Example:
.. code-block:: python
import busio
import digitalio
from board import *
from adafruit_bus_device.spi_device import SPIDevice
with busio.SPI(SCK, MOSI, MISO) as spi_bus:
cs = digitalio.DigitalInOut(D10)
device = SPIDevice(spi_bus, cs)
bytes_read = bytearray(4)
# The object assigned to spi in the with statements below
# is the original spi_bus object. We are using the busio.SPI
# operations busio.SPI.readinto() and busio.SPI.write().
with device as spi:
spi.readinto(bytes_read)
# A second transaction
with device as spi:
spi.write(bytes_read)
"""
def __init__(self, spi, chip_select=None, *,
baudrate=100000, polarity=0, phase=0, extra_clocks=0):
self.spi = spi
self.baudrate = baudrate
self.polarity = polarity
self.phase = phase
self.extra_clocks = extra_clocks
self.chip_select = chip_select
if self.chip_select:
self.chip_select.switch_to_output(value=True)
def __enter__(self):
while not self.spi.try_lock():
pass
self.spi.configure(baudrate=self.baudrate, polarity=self.polarity,
phase=self.phase)
if self.chip_select:
self.chip_select.value = False
return self.spi
def __exit__(self, *exc):
if self.chip_select:
self.chip_select.value = True
if self.extra_clocks > 0:
buf = bytearray(1)
buf[0] = 0xff
clocks = self.extra_clocks // 8
if self.extra_clocks % 8 != 0:
clocks += 1
for _ in range(clocks):
self.spi.write(buf)
self.spi.unlock()
return False

+ 251
- 0
lib/adafruit_gps.py View File

@ -0,0 +1,251 @@
# The MIT License (MIT)
#
# Copyright (c) 2017 Tony DiCola for Adafruit Industries
#
# 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.
"""
`adafruit_gps`
====================================================
GPS parsing module. Can parse simple NMEA data sentences from serial GPS
modules to read latitude, longitude, and more.
* Author(s): Tony DiCola
Implementation Notes
--------------------
**Hardware:**
* Adafruit `Ultimate GPS Breakout <https://www.adafruit.com/product/746>`_
* Adafruit `Ultimate GPS FeatherWing <https://www.adafruit.com/product/3133>`_
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the ESP8622 and M0-based boards:
https://github.com/adafruit/circuitpython/releases
"""
import time
__version__ = "3.0.2"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_GPS.git"
# Internal helper parsing functions.
# These handle input that might be none or null and return none instead of
# throwing errors.
def _parse_degrees(nmea_data):
# Parse a NMEA lat/long data pair 'dddmm.mmmm' into a pure degrees value.
# Where ddd is the degrees, mm.mmmm is the minutes.
if nmea_data is None or len(nmea_data) < 3:
return None
raw = float(nmea_data)
deg = raw // 100
minutes = raw % 100
return deg + minutes/60
def _parse_int(nmea_data):
if nmea_data is None or nmea_data == '':
return None
return int(nmea_data)
def _parse_float(nmea_data):
if nmea_data is None or nmea_data == '':
return None
return float(nmea_data)
# lint warning about too many attributes disabled
#pylint: disable-msg=R0902
class GPS:
"""GPS parsing module. Can parse simple NMEA data sentences from serial GPS
modules to read latitude, longitude, and more.
"""
def __init__(self, uart):
self._uart = uart
# Initialize null starting values for GPS attributes.
self.timestamp_utc = None
self.latitude = None
self.longitude = None
self.fix_quality = None
self.satellites = None
self.horizontal_dilution = None
self.altitude_m = None
self.height_geoid = None
self.velocity_knots = None
self.speed_knots = None
self.track_angle_deg = None
def update(self):
"""Check for updated data from the GPS module and process it
accordingly. Returns True if new data was processed, and False if
nothing new was received.
"""
# Grab a sentence and check its data type to call the appropriate
# parsing function.
sentence = self._parse_sentence()
if sentence is None:
return False
data_type, args = sentence
data_type = data_type.upper()
if data_type == 'GPGGA': # GGA, 3d location fix
self._parse_gpgga(args)
elif data_type == 'GPRMC': # RMC, minimum location info
self._parse_gprmc(args)
return True
def send_command(self, command, add_checksum=True):
"""Send a command string to the GPS. If add_checksum is True (the
default) a NMEA checksum will automatically be computed and added.
Note you should NOT add the leading $ and trailing * to the command
as they will automatically be added!
"""
self._uart.write('$')
self._uart.write(command)
if add_checksum:
checksum = 0
for char in command:
checksum ^= ord(char)
self._uart.write('*')
self._uart.write('{:02x}'.format(checksum).upper())
self._uart.write('\r\n')
@property
def has_fix(self):
"""True if a current fix for location information is available."""
return self.fix_quality is not None and self.fix_quality >= 1
def _parse_sentence(self):
# Parse any NMEA sentence that is available.
sentence = self._uart.readline()
if sentence is None or sentence == b'' or len(sentence) < 1:
return None
sentence = str(sentence, 'ascii').strip()
# Look for a checksum and validate it if present.
if len(sentence) > 7 and sentence[-3] == '*':
# Get included checksum, then calculate it and compare.
expected = int(sentence[-2:], 16)
actual = 0
for i in range(1, len(sentence)-3):
actual ^= ord(sentence[i])
if actual != expected:
return None # Failed to validate checksum.
# Remove checksum once validated.
sentence = sentence[:-3]
# Parse out the type of sentence (first string after $ up to comma)
# and then grab the rest as data within the sentence.
delineator = sentence.find(',')
if delineator == -1:
return None # Invalid sentence, no comma after data type.
data_type = sentence[1:delineator]
return (data_type, sentence[delineator+1:])
def _parse_gpgga(self, args):
# Parse the arguments (everything after data type) for NMEA GPGGA
# 3D location fix sentence.
data = args.split(',')
if data is None or len(data) != 14:
return # Unexpected number of params.
# Parse fix time.
time_utc = int(_parse_float(data[0]))
if time_utc is not None:
hours = time_utc // 10000
mins = (time_utc // 100) % 100
secs = time_utc % 100
# Set or update time to a friendly python time struct.
if self.timestamp_utc is not None:
self.timestamp_utc = time.struct_time((
self.timestamp_utc.tm_year, self.timestamp_utc.tm_mon,
self.timestamp_utc.tm_mday, hours, mins, secs, 0, 0, -1))
else:
self.timestamp_utc = time.struct_time((0, 0, 0, hours, mins,
secs, 0, 0, -1))
# Parse latitude and longitude.
self.latitude = _parse_degrees(data[1])
if self.latitude is not None and \
data[2] is not None and data[2].lower() == 's':
self.latitude *= -1.0
self.longitude = _parse_degrees(data[3])
if self.longitude is not None and \
data[4] is not None and data[4].lower() == 'w':
self.longitude *= -1.0
# Parse out fix quality and other simple numeric values.
self.fix_quality = _parse_int(data[5])
self.satellites = _parse_int(data[6])
self.horizontal_dilution = _parse_float(data[7])
self.altitude_m = _parse_float(data[8])
self.height_geoid = _parse_float(data[10])
def _parse_gprmc(self, args):
# Parse the arguments (everything after data type) for NMEA GPRMC
# minimum location fix sentence.
data = args.split(',')
if data is None or len(data) < 11:
return # Unexpected number of params.
# Parse fix time.
time_utc = int(_parse_float(data[0]))
if time_utc is not None:
hours = time_utc // 10000
mins = (time_utc // 100) % 100
secs = time_utc % 100
# Set or update time to a friendly python time struct.
if self.timestamp_utc is not None:
self.timestamp_utc = time.struct_time((
self.timestamp_utc.tm_year, self.timestamp_utc.tm_mon,
self.timestamp_utc.tm_mday, hours, mins, secs, 0, 0, -1))
else:
self.timestamp_utc = time.struct_time((0, 0, 0, hours, mins,
secs, 0, 0, -1))
# Parse status (active/fixed or void).
status = data[1]
self.fix_quality = 0
if status is not None and status.lower() == 'a':
self.fix_quality = 1
# Parse latitude and longitude.
self.latitude = _parse_degrees(data[2])
if self.latitude is not None and \
data[3] is not None and data[3].lower() == 's':
self.latitude *= -1.0
self.longitude = _parse_degrees(data[4])
if self.longitude is not None and \
data[5] is not None and data[5].lower() == 'w':
self.longitude *= -1.0
# Parse out speed and other simple numeric values.
self.speed_knots = _parse_float(data[6])
self.track_angle_deg = _parse_float(data[7])
# Parse date.
if data[8] is not None and len(data[8]) == 6:
day = int(data[8][0:2])
month = int(data[8][2:4])
year = 2000 + int(data[8][4:6]) # Y2k bug, 2 digit date assumption.
# This is a problem with the NMEA
# spec and not this code.
if self.timestamp_utc is not None:
# Replace the timestamp with an updated one.
# (struct_time is immutable and can't be changed in place)
self.timestamp_utc = time.struct_time((year, month, day,
self.timestamp_utc.tm_hour,
self.timestamp_utc.tm_min,
self.timestamp_utc.tm_sec,
0,
0,
-1))
else:
# Time hasn't been set so create it.
self.timestamp_utc = time.struct_time((year, month, day, 0, 0,
0, 0, 0, -1))

+ 241
- 0
lib/adafruit_lsm303.py View File

@ -0,0 +1,241 @@
# The MIT License (MIT)
#
# Copyright (c) 2017 Dave Astels for Adafruit Industries
#
# 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.
"""
`adafruit_LSM303`
====================================================
CircuitPython driver for the LSM303 accelerometer + magnetometer.
* Author(s): Dave Astels
"""
try:
import struct
except ImportError:
import ustruct as struct
from micropython import const
from adafruit_bus_device.i2c_device import I2CDevice
__version__ = "1.1.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LSM303.git"
# pylint: disable=bad-whitespace
_ADDRESS_ACCEL = const(0x19) # (0x32 >> 1) // 0011001x
_ADDRESS_MAG = const(0x1E) # (0x3C >> 1) // 0011110x
_ID = const(0xD4) # (0b11010100)
# Accelerometer registers
_REG_ACCEL_CTRL_REG1_A = const(0x20)
_REG_ACCEL_CTRL_REG2_A = const(0x21)
_REG_ACCEL_CTRL_REG3_A = const(0x22)
_REG_ACCEL_CTRL_REG4_A = const(0x23)
_REG_ACCEL_CTRL_REG5_A = const(0x24)
_REG_ACCEL_CTRL_REG6_A = const(0x25)
_REG_ACCEL_REFERENCE_A = const(0x26)
_REG_ACCEL_STATUS_REG_A = const(0x27)
_REG_ACCEL_OUT_X_L_A = const(0x28)
_REG_ACCEL_OUT_X_H_A = const(0x29)
_REG_ACCEL_OUT_Y_L_A = const(0x2A)
_REG_ACCEL_OUT_Y_H_A = const(0x2B)
_REG_ACCEL_OUT_Z_L_A = const(0x2C)
_REG_ACCEL_OUT_Z_H_A = const(0x2D)
_REG_ACCEL_FIFO_CTRL_REG_A = const(0x2E)
_REG_ACCEL_FIFO_SRC_REG_A = const(0x2F)
_REG_ACCEL_INT1_CFG_A = const(0x30)
_REG_ACCEL_INT1_SOURCE_A = const(0x31)
_REG_ACCEL_INT1_THS_A = const(0x32)
_REG_ACCEL_INT1_DURATION_A = const(0x33)
_REG_ACCEL_INT2_CFG_A = const(0x34)
_REG_ACCEL_INT2_SOURCE_A = const(0x35)
_REG_ACCEL_INT2_THS_A = const(0x36)
_REG_ACCEL_INT2_DURATION_A = const(0x37)
_REG_ACCEL_CLICK_CFG_A = const(0x38)
_REG_ACCEL_CLICK_SRC_A = const(0x39)
_REG_ACCEL_CLICK_THS_A = const(0x3A)
_REG_ACCEL_TIME_LIMIT_A = const(0x3B)
_REG_ACCEL_TIME_LATENCY_A = const(0x3C)
_REG_ACCEL_TIME_WINDOW_A = const(0x3D)
# Magnetometer registers
_REG_MAG_CRA_REG_M = const(0x00)
_REG_MAG_CRB_REG_M = const(0x01)
_REG_MAG_MR_REG_M = const(0x02)
_REG_MAG_OUT_X_H_M = const(0x03)
_REG_MAG_OUT_X_L_M = const(0x04)
_REG_MAG_OUT_Z_H_M = const(0x05)
_REG_MAG_OUT_Z_L_M = const(0x06)
_REG_MAG_OUT_Y_H_M = const(0x07)
_REG_MAG_OUT_Y_L_M = const(0x08)
_REG_MAG_SR_REG_M = const(0x09)
_REG_MAG_IRA_REG_M = const(0x0A)
_REG_MAG_IRB_REG_M = const(0x0B)
_REG_MAG_IRC_REG_M = const(0x0C)
_REG_MAG_TEMP_OUT_H_M = const(0x31)
_REG_MAG_TEMP_OUT_L_M = const(0x32)
# Magnetometer gains
MAGGAIN_1_3 = const(0x20) # +/- 1.3
MAGGAIN_1_9 = const(0x40) # +/- 1.9
MAGGAIN_2_5 = const(0x60) # +/- 2.5
MAGGAIN_4_0 = const(0x80) # +/- 4.0
MAGGAIN_4_7 = const(0xA0) # +/- 4.7
MAGGAIN_5_6 = const(0xC0) # +/- 5.6
MAGGAIN_8_1 = const(0xE0) # +/- 8.1
# Magentometer rates
MAGRATE_0_7 = const(0x00) # 0.75 Hz
MAGRATE_1_5 = const(0x01) # 1.5 Hz
MAGRATE_3_0 = const(0x62) # 3.0 Hz
MAGRATE_7_5 = const(0x03) # 7.5 Hz
MAGRATE_15 = const(0x04) # 15 Hz
MAGRATE_30 = const(0x05) # 30 Hz
MAGRATE_75 = const(0x06) # 75 Hz
MAGRATE_220 = const(0x07) # 200 Hz
# Conversion constants
_LSM303ACCEL_MG_LSB = 16704.0
_GRAVITY_STANDARD = 9.80665 # Earth's gravity in m/s^2
_GAUSS_TO_MICROTESLA = 100.0 # Gauss to micro-Tesla multiplier
# pylint: enable=bad-whitespace
class LSM303(object):
"""Driver for the LSM303 accelerometer/magnetometer."""
# Class-level buffer for reading and writing data with the sensor.
# This reduces memory allocations but means the code is not re-entrant or
# thread safe!
_BUFFER = bytearray(6)
def __init__(self, i2c):
self._accel_device = I2CDevice(i2c, _ADDRESS_ACCEL)
self._mag_device = I2CDevice(i2c, _ADDRESS_MAG)
self._write_u8(self._accel_device, _REG_ACCEL_CTRL_REG1_A, 0x27) # Enable the accelerometer
self._write_u8(self._mag_device, _REG_MAG_MR_REG_M, 0x00) # Enable the magnetometer
self._lsm303mag_gauss_lsb_xy = 1100.0
self._lsm303mag_gauss_lsb_z = 980.0
self._mag_gain = MAGGAIN_1_3
self._mag_rate = MAGRATE_0_7
@property
def raw_acceleration(self):
"""The raw accelerometer sensor values.
A 3-tuple of X, Y, Z axis values that are 16-bit signed integers.
"""
self._read_bytes(self._accel_device, _REG_ACCEL_OUT_X_L_A | 0x80, 6, self._BUFFER)
return struct.unpack_from('<hhh', self._BUFFER[0:6])
@property
def acceleration(self):
"""The processed accelerometer sensor values.
A 3-tuple of X, Y, Z axis values in meters per second squared that are signed floats.
"""
raw_accel_data = self.raw_acceleration
return tuple([n / _LSM303ACCEL_MG_LSB * _GRAVITY_STANDARD for n in raw_accel_data])
@property
def raw_magnetic(self):
"""The raw magnetometer sensor values.
A 3-tuple of X, Y, Z axis values that are 16-bit signed integers.
"""
self._read_bytes(self._mag_device, _REG_MAG_OUT_X_H_M, 6, self._BUFFER)
raw_values = struct.unpack_from('>hhh', self._BUFFER[0:6])
return tuple([n >> 4 for n in raw_values])
@property
def magnetic(self):
"""The processed magnetometer sensor values.
A 3-tuple of X, Y, Z axis values in microteslas that are signed floats.
"""
mag_x, mag_y, mag_z = self.raw_magnetic
return (mag_x / self._lsm303mag_gauss_lsb_xy * _GAUSS_TO_MICROTESLA,
mag_y / self._lsm303mag_gauss_lsb_xy * _GAUSS_TO_MICROTESLA,
mag_z / self._lsm303mag_gauss_lsb_z * _GAUSS_TO_MICROTESLA)
@property
def mag_gain(self):
"""The magnetometer's gain."""
return self._mag_gain
@mag_gain.setter
def mag_gain(self, value):
assert value in (MAGGAIN_1_3, MAGGAIN_1_9, MAGGAIN_2_5, MAGGAIN_4_0, MAGGAIN_4_7,
MAGGAIN_5_6, MAGGAIN_8_1)
self._mag_gain = value
self._write_u8(self._mag_device, _REG_MAG_CRB_REG_M, self._mag_gain)
if self._mag_gain == MAGGAIN_1_3:
self._lsm303mag_gauss_lsb_xy = 1100.0
self._lsm303mag_gauss_lsb_z = 980.0
elif self._mag_gain == MAGGAIN_1_9:
self._lsm303mag_gauss_lsb_xy = 855.0
self._lsm303mag_gauss_lsb_z = 760.0
elif self._mag_gain == MAGGAIN_2_5:
self._lsm303mag_gauss_lsb_xy = 670.0
self._lsm303mag_gauss_lsb_z = 600.0
elif self._mag_gain == MAGGAIN_4_0:
self._lsm303mag_gauss_lsb_xy = 450.0
self._lsm303mag_gauss_lsb_z = 400.0
elif self._mag_gain == MAGGAIN_4_7:
self._lsm303mag_gauss_lsb_xy = 400.0
self._lsm303mag_gauss_lsb_z = 355.0
elif self._mag_gain == MAGGAIN_5_6:
self._lsm303mag_gauss_lsb_xy = 330.0
self._lsm303mag_gauss_lsb_z = 295.0
elif self._mag_gain == MAGGAIN_8_1:
self._lsm303mag_gauss_lsb_xy = 230.0
self._lsm303mag_gauss_lsb_z = 205.0
@property
def mag_rate(self):
"""The magnetometer update rate."""
return self._mag_rate
@mag_rate.setter
def mag_rate(self, value):
assert value in (MAGRATE_0_7, MAGRATE_1_5, MAGRATE_3_0, MAGRATE_7_5, MAGRATE_15, MAGRATE_30,
MAGRATE_75, MAGRATE_220)
self._mag_rate = value
reg_m = ((value & 0x07) << 2) & 0xFF
self._write_u8(self._mag_device, _REG_MAG_CRA_REG_M, reg_m)
def _read_u8(self, device, address):
with device as i2c:
self._BUFFER[0] = address & 0xFF
i2c.write(self._BUFFER, end=1, stop=False)
i2c.readinto(self._BUFFER, end=1)
return self._BUFFER[0]
def _write_u8(self, device, address, val):
with device as i2c:
self._BUFFER[0] = address & 0xFF
self._BUFFER[1] = val & 0xFF
i2c.write(self._BUFFER, end=2)
@staticmethod
def _read_bytes(device, address, count, buf):
with device as i2c:
buf[0] = address & 0xFF
i2c.write(buf, end=1, stop=False)
i2c.readinto(buf, end=count)

+ 431
- 0
lib/adafruit_lsm9ds1.py View File

@ -0,0 +1,431 @@
# The MIT License (MIT)
#
# Copyright (c) 2017 Tony DiCola for Adafruit Industries
#
# 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.
"""
`adafruit_lsm9ds1`
====================================================
CircuitPython module for the LSM9DS1 accelerometer, magnetometer, gyroscope.
Based on the driver from:
https://github.com/adafruit/Adafruit_LSM9DS1
See examples/simpletest.py for a demo of the usage.
* Author(s): Tony DiCola
"""
__version__ = "1.0.1"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LSM9DS1.git"
import time
try:
import struct
except ImportError:
import ustruct as struct
import adafruit_bus_device.i2c_device as i2c_device
import adafruit_bus_device.spi_device as spi_device
from micropython import const
# Internal constants and register values:
# pylint: disable=bad-whitespace
_LSM9DS1_ADDRESS_ACCELGYRO = const(0x6B)
_LSM9DS1_ADDRESS_MAG = const(0x1E)
_LSM9DS1_XG_ID = const(0b01101000)
_LSM9DS1_MAG_ID = const(0b00111101)
_LSM9DS1_ACCEL_MG_LSB_2G = 0.061
_LSM9DS1_ACCEL_MG_LSB_4G = 0.122
_LSM9DS1_ACCEL_MG_LSB_8G = 0.244
_LSM9DS1_ACCEL_MG_LSB_16G = 0.732
_LSM9DS1_MAG_MGAUSS_4GAUSS = 0.14
_LSM9DS1_MAG_MGAUSS_8GAUSS = 0.29
_LSM9DS1_MAG_MGAUSS_12GAUSS = 0.43
_LSM9DS1_MAG_MGAUSS_16GAUSS = 0.58
_LSM9DS1_GYRO_DPS_DIGIT_245DPS = 0.00875
_LSM9DS1_GYRO_DPS_DIGIT_500DPS = 0.01750
_LSM9DS1_GYRO_DPS_DIGIT_2000DPS = 0.07000
_LSM9DS1_TEMP_LSB_DEGREE_CELSIUS = 8 # 1°C = 8, 25° = 200, etc.
_LSM9DS1_REGISTER_WHO_AM_I_XG = const(0x0F)
_LSM9DS1_REGISTER_CTRL_REG1_G = const(0x10)
_LSM9DS1_REGISTER_CTRL_REG2_G = const(0x11)
_LSM9DS1_REGISTER_CTRL_REG3_G = const(0x12)
_LSM9DS1_REGISTER_TEMP_OUT_L = const(0x15)
_LSM9DS1_REGISTER_TEMP_OUT_H = const(0x16)
_LSM9DS1_REGISTER_STATUS_REG = const(0x17)
_LSM9DS1_REGISTER_OUT_X_L_G = const(0x18)
_LSM9DS1_REGISTER_OUT_X_H_G = const(0x19)
_LSM9DS1_REGISTER_OUT_Y_L_G = const(0x1A)
_LSM9DS1_REGISTER_OUT_Y_H_G = const(0x1B)
_LSM9DS1_REGISTER_OUT_Z_L_G = const(0x1C)
_LSM9DS1_REGISTER_OUT_Z_H_G = const(0x1D)
_LSM9DS1_REGISTER_CTRL_REG4 = const(0x1E)
_LSM9DS1_REGISTER_CTRL_REG5_XL = const(0x1F)
_LSM9DS1_REGISTER_CTRL_REG6_XL = const(0x20)
_LSM9DS1_REGISTER_CTRL_REG7_XL = const(0x21)
_LSM9DS1_REGISTER_CTRL_REG8 = const(0x22)
_LSM9DS1_REGISTER_CTRL_REG9 = const(0x23)
_LSM9DS1_REGISTER_CTRL_REG10 = const(0x24)
_LSM9DS1_REGISTER_OUT_X_L_XL = const(0x28)
_LSM9DS1_REGISTER_OUT_X_H_XL = const(0x29)
_LSM9DS1_REGISTER_OUT_Y_L_XL = const(0x2A)
_LSM9DS1_REGISTER_OUT_Y_H_XL = const(0x2B)
_LSM9DS1_REGISTER_OUT_Z_L_XL = const(0x2C)
_LSM9DS1_REGISTER_OUT_Z_H_XL = const(0x2D)
_LSM9DS1_REGISTER_WHO_AM_I_M = const(0x0F)
_LSM9DS1_REGISTER_CTRL_REG1_M = const(0x20)
_LSM9DS1_REGISTER_CTRL_REG2_M = const(0x21)
_LSM9DS1_REGISTER_CTRL_REG3_M = const(0x22)
_LSM9DS1_REGISTER_CTRL_REG4_M = const(0x23)
_LSM9DS1_REGISTER_CTRL_REG5_M = const(0x24)
_LSM9DS1_REGISTER_STATUS_REG_M = const(0x27)
_LSM9DS1_REGISTER_OUT_X_L_M = const(0x28)
_LSM9DS1_REGISTER_OUT_X_H_M = const(0x29)
_LSM9DS1_REGISTER_OUT_Y_L_M = const(0x2A)
_LSM9DS1_REGISTER_OUT_Y_H_M = const(0x2B)
_LSM9DS1_REGISTER_OUT_Z_L_M = const(0x2C)
_LSM9DS1_REGISTER_OUT_Z_H_M = const(0x2D)
_LSM9DS1_REGISTER_CFG_M = const(0x30)
_LSM9DS1_REGISTER_INT_SRC_M = const(0x31)
_MAGTYPE = True
_XGTYPE = False
_SENSORS_GRAVITY_STANDARD = 9.80665
# User facing constants/module globals.
ACCELRANGE_2G = (0b00 << 3)
ACCELRANGE_16G = (0b01 << 3)
ACCELRANGE_4G = (0b10 << 3)
ACCELRANGE_8G = (0b11 << 3)
MAGGAIN_4GAUSS = (0b00 << 5) # +/- 4 gauss
MAGGAIN_8GAUSS = (0b01 << 5) # +/- 8 gauss
MAGGAIN_12GAUSS = (0b10 << 5) # +/- 12 gauss
MAGGAIN_16GAUSS = (0b11 << 5) # +/- 16 gauss
GYROSCALE_245DPS = (0b00 << 4) # +/- 245 degrees/s rotation
GYROSCALE_500DPS = (0b01 << 4) # +/- 500 degrees/s rotation
GYROSCALE_2000DPS = (0b11 << 4) # +/- 2000 degrees/s rotation
# pylint: enable=bad-whitespace
def _twos_comp(val, bits):
# Convert an unsigned integer in 2's compliment form of the specified bit
# length to its signed integer value and return it.
if val & (1 << (bits - 1)) != 0:
return val - (1 << bits)
return val
class LSM9DS1:
"""Driver for the LSM9DS1 accelerometer, magnetometer, gyroscope."""
# Class-level buffer for reading and writing data with the sensor.
# This reduces memory allocations but means the code is not re-entrant or
# thread safe!
_BUFFER = bytearray(6)
def __init__(self):
# soft reset & reboot accel/gyro
self._write_u8(_XGTYPE, _LSM9DS1_REGISTER_CTRL_REG8, 0x05)
# soft reset & reboot magnetometer
self._write_u8(_MAGTYPE, _LSM9DS1_REGISTER_CTRL_REG2_M, 0x0C)
time.sleep(0.01)
# Check ID registers.
if self._read_u8(_XGTYPE, _LSM9DS1_REGISTER_WHO_AM_I_XG) != _LSM9DS1_XG_ID or \
self._read_u8(_MAGTYPE, _LSM9DS1_REGISTER_WHO_AM_I_M) != _LSM9DS1_MAG_ID:
raise RuntimeError('Could not find LSM9DS1, check wiring!')
# enable gyro continuous
self._write_u8(_XGTYPE, _LSM9DS1_REGISTER_CTRL_REG1_G, 0xC0) # on XYZ
# Enable the accelerometer continous
self._write_u8(_XGTYPE, _LSM9DS1_REGISTER_CTRL_REG5_XL, 0x38)
self._write_u8(_XGTYPE, _LSM9DS1_REGISTER_CTRL_REG6_XL, 0xC0)
# enable mag continuous
self._write_u8(_MAGTYPE, _LSM9DS1_REGISTER_CTRL_REG3_M, 0x00)
# Set default ranges for the various sensors
self._accel_mg_lsb = None
self._mag_mgauss_lsb = None
self._gyro_dps_digit = None
self.accel_range = ACCELRANGE_2G
self.mag_gain = MAGGAIN_4GAUSS
self.gyro_scale = GYROSCALE_245DPS
@property
def accel_range(self):
"""Get and set the accelerometer range. Must be a value of:
- ACCELRANGE_2G
- ACCELRANGE_4G
- ACCELRANGE_8G
- ACCELRANGE_16G
"""
reg = self._read_u8(_XGTYPE, _LSM9DS1_REGISTER_CTRL_REG6_XL)
return (reg & 0b00011000) & 0xFF
@accel_range.setter
def accel_range(self, val):
assert val in (ACCELRANGE_2G, ACCELRANGE_4G, ACCELRANGE_8G,
ACCELRANGE_16G)
reg = self._read_u8(_XGTYPE, _LSM9DS1_REGISTER_CTRL_REG6_XL)
reg = (reg & ~(0b00011000)) & 0xFF
reg |= val
self._write_u8(_XGTYPE, _LSM9DS1_REGISTER_CTRL_REG6_XL, reg)
if val == ACCELRANGE_2G:
self._accel_mg_lsb = _LSM9DS1_ACCEL_MG_LSB_2G
elif val == ACCELRANGE_4G:
self._accel_mg_lsb = _LSM9DS1_ACCEL_MG_LSB_4G
elif val == ACCELRANGE_8G:
self._accel_mg_lsb = _LSM9DS1_ACCEL_MG_LSB_8G
elif val == ACCELRANGE_16G:
self._accel_mg_lsb = _LSM9DS1_ACCEL_MG_LSB_16G
@property
def mag_gain(self):
"""Get and set the magnetometer gain. Must be a value of:
- MAGGAIN_4GAUSS
- MAGGAIN_8GAUSS
- MAGGAIN_12GAUSS
- MAGGAIN_16GAUSS
"""
reg = self._read_u8(_MAGTYPE, _LSM9DS1_REGISTER_CTRL_REG2_M)
return (reg & 0b01100000) & 0xFF
@mag_gain.setter
def mag_gain(self, val):
assert val in (MAGGAIN_4GAUSS, MAGGAIN_8GAUSS, MAGGAIN_12GAUSS,
MAGGAIN_16GAUSS)
reg = self._read_u8(_MAGTYPE, _LSM9DS1_REGISTER_CTRL_REG2_M)
reg = (reg & ~(0b01100000)) & 0xFF
reg |= val
self._write_u8(_MAGTYPE, _LSM9DS1_REGISTER_CTRL_REG2_M, reg)
if val == MAGGAIN_4GAUSS:
self._mag_mgauss_lsb = _LSM9DS1_MAG_MGAUSS_4GAUSS
elif val == MAGGAIN_8GAUSS:
self._mag_mgauss_lsb = _LSM9DS1_MAG_MGAUSS_8GAUSS
elif val == MAGGAIN_12GAUSS:
self._mag_mgauss_lsb = _LSM9DS1_MAG_MGAUSS_12GAUSS
elif val == MAGGAIN_16GAUSS:
self._mag_mgauss_lsb = _LSM9DS1_MAG_MGAUSS_16GAUSS
@property
def gyro_scale(self):
"""Get and set the gyroscope scale. Must be a value of:
- GYROSCALE_245DPS
- GYROSCALE_500DPS
- GYROSCALE_2000DPS
"""
reg = self._read_u8(_XGTYPE, _LSM9DS1_REGISTER_CTRL_REG1_G)
return (reg & 0b00110000) & 0xFF
@gyro_scale.setter
def gyro_scale(self, val):
assert val in (GYROSCALE_245DPS, GYROSCALE_500DPS, GYROSCALE_2000DPS)
reg = self._read_u8(_XGTYPE, _LSM9DS1_REGISTER_CTRL_REG1_G)
reg = (reg & ~(0b00110000)) & 0xFF
reg |= val
self._write_u8(_XGTYPE, _LSM9DS1_REGISTER_CTRL_REG1_G, reg)
if val == GYROSCALE_245DPS:
self._gyro_dps_digit = _LSM9DS1_GYRO_DPS_DIGIT_245DPS
elif val == GYROSCALE_500DPS:
self._gyro_dps_digit = _LSM9DS1_GYRO_DPS_DIGIT_500DPS
elif val == GYROSCALE_2000DPS:
self._gyro_dps_digit = _LSM9DS1_GYRO_DPS_DIGIT_2000DPS
def read_accel_raw(self):
"""Read the raw accelerometer sensor values and return it as a
3-tuple of X, Y, Z axis values that are 16-bit unsigned values. If you
want the acceleration in nice units you probably want to use the
accelerometer property!
"""
# Read the accelerometer
self._read_bytes(_XGTYPE, 0x80 | _LSM9DS1_REGISTER_OUT_X_L_XL, 6,
self._BUFFER)
raw_x, raw_y, raw_z = struct.unpack_from('<hhh', self._BUFFER[0:6])
return (raw_x, raw_y, raw_z)
@property
def accelerometer(self):
"""Get the accelerometer X, Y, Z axis values as a 3-tuple of
m/s^2 values.
"""
raw = self.read_accel_raw()
return map(lambda x: x * self._accel_mg_lsb / 1000.0 * _SENSORS_GRAVITY_STANDARD,
raw)
def read_mag_raw(self):
"""Read the raw magnetometer sensor values and return it as a
3-tuple of X, Y, Z axis values that are 16-bit unsigned values. If you
want the magnetometer in nice units you probably want to use the
magnetometer property!
"""
# Read the magnetometer
self._read_bytes(_MAGTYPE, 0x80 | _LSM9DS1_REGISTER_OUT_X_L_M, 6,
self._BUFFER)
raw_x, raw_y, raw_z = struct.unpack_from('<hhh', self._BUFFER[0:6])
return (raw_x, raw_y, raw_z)
@property
def magnetometer(self):
"""Get the magnetometer X, Y, Z axis values as a 3-tuple of
gauss values.
"""
raw = self.read_mag_raw()
return map(lambda x: x * self._mag_mgauss_lsb / 1000.0, raw)
def read_gyro_raw(self):
"""Read the raw gyroscope sensor values and return it as a
3-tuple of X, Y, Z axis values that are 16-bit unsigned values. If you
want the gyroscope in nice units you probably want to use the
gyroscope property!
"""
# Read the gyroscope
self._read_bytes(_XGTYPE, 0x80 | _LSM9DS1_REGISTER_OUT_X_L_G, 6,
self._BUFFER)
raw_x, raw_y, raw_z = struct.unpack_from('<hhh', self._BUFFER[0:6])
return (raw_x, raw_y, raw_z)
@property
def gyroscope(self):
"""Get the gyroscope X, Y, Z axis values as a 3-tuple of
degrees/second values.
"""
raw = self.read_mag_raw()
return map(lambda x: x * self._gyro_dps_digit, raw)
def read_temp_raw(self):
"""Read the raw temperature sensor value and return it as a 12-bit
signed value. If you want the temperature in nice units you probably
want to use the temperature property!
"""
# Read temp sensor
self._read_bytes(_XGTYPE, 0x80 | _LSM9DS1_REGISTER_TEMP_OUT_L, 2,
self._BUFFER)
temp = ((self._BUFFER[1] << 8) | self._BUFFER[0]) >> 4
return _twos_comp(temp, 12)
@property
def temperature(self):
"""Get the temperature of the sensor in degrees Celsius."""
# This is just a guess since the starting point (21C here) isn't documented :(
# See discussion from:
# https://github.com/kriswiner/LSM9DS1/issues/3
temp = self.read_temp_raw()
temp = 27.5 + temp/16
return temp
def _read_u8(self, sensor_type, address):
# Read an 8-bit unsigned value from the specified 8-bit address.
# The sensor_type boolean should be _MAGTYPE when talking to the
# magnetometer, or _XGTYPE when talking to the accel or gyro.
# MUST be implemented by subclasses!
raise NotImplementedError()
def _read_bytes(self, sensor_type, address, count, buf):
# Read a count number of bytes into buffer from the provided 8-bit
# register address. The sensor_type boolean should be _MAGTYPE when
# talking to the magnetometer, or _XGTYPE when talking to the accel or
# gyro. MUST be implemented by subclasses!
raise NotImplementedError()
def _write_u8(self, sensor_type, address, val):
# Write an 8-bit unsigned value to the specified 8-bit address.
# The sensor_type boolean should be _MAGTYPE when talking to the
# magnetometer, or _XGTYPE when talking to the accel or gyro.
# MUST be implemented by subclasses!
raise NotImplementedError()
class LSM9DS1_I2C(LSM9DS1):
"""Driver for the LSM9DS1 connect over I2C."""
def __init__(self, i2c):
self._mag_device = i2c_device.I2CDevice(i2c, _LSM9DS1_ADDRESS_MAG)
self._xg_device = i2c_device.I2CDevice(i2c, _LSM9DS1_ADDRESS_ACCELGYRO)
super().__init__()
def _read_u8(self, sensor_type, address):
if sensor_type == _MAGTYPE:
device = self._mag_device
else:
device = self._xg_device
with device as i2c:
self._BUFFER[0] = address & 0xFF
i2c.write(self._BUFFER, end=1, stop=False)
i2c.readinto(self._BUFFER, end=1)
return self._BUFFER[0]
def _read_bytes(self, sensor_type, address, count, buf):
if sensor_type == _MAGTYPE:
device = self._mag_device
else:
device = self._xg_device
with device as i2c:
buf[0] = address & 0xFF
i2c.write(buf, end=1, stop=False)
i2c.readinto(buf, end=count)
def _write_u8(self, sensor_type, address, val):
if sensor_type == _MAGTYPE:
device = self._mag_device
else:
device = self._xg_device
with device as i2c:
self._BUFFER[0] = address & 0xFF
self._BUFFER[1] = val & 0xFF
i2c.write(self._BUFFER, end=2)
class LSM9DS1_SPI(LSM9DS1):
"""Driver for the LSM9DS1 connect over SPI."""
def __init__(self, spi, xgcs, mcs):
self._mag_device = spi_device.I2CDevice(spi, mcs)
self._xg_device = spi_device.I2CDevice(spi, xgcs)
super().__init__()
def _read_u8(self, sensor_type, address):
if sensor_type == _MAGTYPE:
device = self._mag_device
else:
device = self._xg_device
with device as spi:
spi.configure(baudrate=200000, phase=0, polarity=0)
self._BUFFER[0] = (address | 0x80) & 0xFF
spi.write(self._BUFFER, end=1)
spi.readinto(self._BUFFER, end=1)
return self._BUFFER[0]
def _read_bytes(self, sensor_type, address, count, buf):
if sensor_type == _MAGTYPE:
device = self._mag_device
else:
device = self._xg_device
with device as spi:
spi.configure(baudrate=200000, phase=0, polarity=0)
buf[0] = (address | 0x80) & 0xFF
spi.write(buf, end=1)
spi.readinto(buf, end=count)
def _write_u8(self, sensor_type, address, val):
if sensor_type == _MAGTYPE:
device = self._mag_device
else:
device = self._xg_device
with device as spi:
spi.configure(baudrate=200000, phase=0, polarity=0)
self._BUFFER[0] = (address & 0x7F) & 0xFF
self._BUFFER[1] = val & 0xFF
spi.write(self._BUFFER, end=2)

+ 0
- 0
lib/adafruit_register/__init__.py View File


+ 185
- 0
lib/adafruit_register/i2c_bcd_alarm.py View File

@ -0,0 +1,185 @@
# The MIT License (MIT)
#
# Copyright (c) 2016 Scott Shawcroft for Adafruit Industries
#
# 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.
# pylint: disable=too-few-public-methods
"""
`adafruit_register.i2c_bcd_alarm`
====================================================
Binary Coded Decimal alarm register
* Author(s): Scott Shawcroft
"""
__version__ = "1.3.1"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git"
import time
def _bcd2bin(value):
"""Convert binary coded decimal to Binary
:param value: the BCD value to convert to binary (required, no default)
"""
return value - 6 * (value >> 4)
def _bin2bcd(value):
"""Convert a binary value to binary coded decimal.
:param value: the binary value to convert to BCD. (required, no default)
"""
return value + 6 * (value // 10)
ALARM_COMPONENT_DISABLED = 0x80
FREQUENCY = ["secondly", "minutely", "hourly", "daily", "weekly", "monthly"]
class BCDAlarmTimeRegister:
"""
Alarm date and time register using binary coded decimal structure.
The byte order of the registers must* be: [second], minute, hour, day,
weekday. Each byte must also have a high enable bit where 1 is disabled and
0 is enabled.
* If weekday_shared is True, then weekday and day share a register.
* If has_seconds is True, then there is a seconds register.
Values are a tuple of (`time.struct_time`, `str`) where the struct represents
a date and time that would alarm. The string is the frequency:
* "secondly", once a second (only if alarm has_seconds)
* "minutely", once a minute when seconds match (if alarm doesn't seconds then when seconds = 0)
* "hourly", once an hour when ``tm_min`` and ``tm_sec`` match
* "daily", once a day when ``tm_hour``, ``tm_min`` and ``tm_sec`` match
* "weekly", once a week when ``tm_wday``, ``tm_hour``, ``tm_min``, ``tm_sec`` match
* "monthly", once a month when ``tm_mday``, ``tm_hour``, ``tm_min``, ``tm_sec`` match
:param int register_address: The register address to start the read
:param bool has_seconds: True if the alarm can happen minutely.
:param bool weekday_shared: True if weekday and day share the same register
:param int weekday_start: 0 or 1 depending on the RTC's representation of the first day of the
week (Monday)
"""
# Defaults are based on alarm1 of the DS3231.
def __init__(self, register_address, has_seconds=True, weekday_shared=True, weekday_start=1):
buffer_size = 5
if weekday_shared:
buffer_size -= 1
if has_seconds:
buffer_size += 1
self.has_seconds = has_seconds
self.buffer = bytearray(buffer_size)
self.buffer[0] = register_address
self.weekday_shared = weekday_shared
self.weekday_start = weekday_start
def __get__(self, obj, objtype=None):
# Read the alarm register.
with obj.i2c_device:
obj.i2c_device.write(self.buffer, end=1, stop=False)
obj.i2c_device.readinto(self.buffer, start=1)
frequency = None
i = 1
seconds = 0
if self.has_seconds:
if (self.buffer[1] & 0x80) != 0:
frequency = "secondly"
else:
frequency = "minutely"
seconds = _bcd2bin(self.buffer[1] & 0x7f)
i = 2
minute = 0
if (self.buffer[i] & 0x80) == 0:
frequency = "hourly"
minute = _bcd2bin(self.buffer[i] & 0x7f)
hour = 0
if (self.buffer[i + 1] & 0x80) == 0:
frequency = "daily"
hour = _bcd2bin(self.buffer[i + 1] & 0x7f)
mday = None