Browse Source

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

Brennen Bearnes 3 months ago
parent
commit
a4d3be9b0c

+ 4
- 0
code.py View File

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

+ 260
- 0
glitterpos.py View File

@@ -0,0 +1,260 @@
1
+"""glitter positioning system"""
2
+
3
+import time
4
+import gc
5
+import math
6
+import adafruit_lsm9ds1
7
+import adafruit_gps
8
+import adafruit_rfm9x
9
+import board
10
+import busio
11
+import digitalio
12
+import neopixel
13
+import rtc
14
+from glitterpos_util import timestamp, compass_bearing, bearing_to_pixel, map_range
15
+
16
+# glitterpos_id.py should be unique to each box, and formatted as follows:
17
+#
18
+# MY_ID = 0 # must be a unique integer
19
+# MAG_MIN = (-0.25046, -0.23506, -0.322)
20
+# MAG_MAX = (0.68278, 0.70882, 0.59654)
21
+#
22
+# From the CircuitPython REPL, use `import calibrate` to find values for
23
+# MAG_MIN and MAG_MAX.
24
+from glitterpos_id import MY_ID, MAG_MIN, MAG_MAX
25
+
26
+# Colors for status lights, etc.
27
+RED = (255, 0, 0)
28
+YELLOW = (255, 150, 0)
29
+GREEN = (0, 255, 0)
30
+CYAN = (0, 255, 255)
31
+BLUE = (0, 0, 255)
32
+PURPLE = (180, 0, 255)
33
+
34
+MAN_ID = 23
35
+
36
+# Magnetic North - should be customized for your location:
37
+DECLINATION_RAD = 235.27 / 1000.0 # Black Rock City in radians
38
+
39
+COLOR_LOOKUP = {
40
+    0: GREEN,
41
+    1: BLUE,
42
+    2: PURPLE,
43
+    3: YELLOW,
44
+    4: CYAN,
45
+    5: (100, 0, 255),
46
+    6: (0, 100, 200),
47
+    7: (100, 50, 100),
48
+    MAN_ID: RED,
49
+}
50
+
51
+RADIO_FREQ_MHZ = 915.0
52
+CS = digitalio.DigitalInOut(board.D10)
53
+RESET = digitalio.DigitalInOut(board.D11)
54
+
55
+class GlitterPOS:
56
+    """glitter positioning system"""
57
+
58
+    def __init__(self):
59
+        """configure sensors, radio, blinkenlights"""
60
+
61
+        # Our id and the dict for storing coords of other glitterpos_boxes:
62
+        self.glitterpos_id = MY_ID
63
+        self.glitterpos_boxes = {
64
+            MAN_ID: (40.786462, -119.206686),
65
+        }
66
+
67
+        # Set the RTC to an obviously bogus time for debugging purposes:
68
+        # time_struct takes: (tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec, tm_wday, tm_yday, tm_isdst)
69
+        rtc.RTC().datetime = time.struct_time((2000, 1, 1, 0, 0, 0, 0, 0, 0))
70
+        print("startup time: " + timestamp())
71
+        self.time_set = False
72
+        self.last_send = time.monotonic()
73
+
74
+        # A tuple for our lat/long:
75
+        self.coords = (0, 0)
76
+        self.heading = 0.0
77
+
78
+        # Status light on the board, we'll use to indicate GPS fix, etc.:
79
+        self.statuslight = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.005, auto_write=True)
80
+        self.statuslight.fill(RED)
81
+
82
+        # Neopixel ring:
83
+        self.pixels = neopixel.NeoPixel(board.A1, 16, brightness=0.01, auto_write=False)
84
+        self.startup_animation()
85
+        time.sleep(2)
86
+
87
+        self.init_radio()
88
+        self.init_gps()
89
+        self.init_compass()
90
+
91
+        self.statuslight.fill(YELLOW)
92
+
93
+    def startup_animation(self):
94
+        self.pixels[bearing_to_pixel(0)] = PURPLE
95
+        self.pixels.show()
96
+        time.sleep(.5)
97
+        self.pixels[bearing_to_pixel(90)] = GREEN
98
+        self.pixels.show()
99
+        time.sleep(.5)
100
+        self.pixels[bearing_to_pixel(180)] = YELLOW
101
+        self.pixels.show()
102
+        time.sleep(.5)
103
+        self.pixels[bearing_to_pixel(270)] = BLUE
104
+        self.pixels.show()
105
+
106
+    def init_radio(self):
107
+        """Set up RFM95."""
108
+        spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
109
+        self.rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)
110
+        self.rfm9x.tx_power = 18 # Default is 13 dB, but the RFM95 can go up to 23 dB
111
+        self.radio_tx('d', 'hello world')
112
+        time.sleep(1)
113
+
114
+    def init_gps(self):
115
+        """Some GPS module setup."""
116
+        uart = busio.UART(board.TX, board.RX, baudrate=9600, timeout=3000)
117
+        gps = adafruit_gps.GPS(uart)
118
+        time.sleep(1)
119
+
120
+        # https://cdn-shop.adafruit.com/datasheets/PMTK_A11.pdf
121
+        # Turn on the basic GGA and RMC info (what you typically want), then
122
+        # set update to once a second:
123
+        gps.send_command('PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0')
124
+        gps.send_command('PMTK220,1000')
125
+
126
+        self.gps = gps
127
+
128
+    def init_compass(self):
129
+        i2c = busio.I2C(board.SCL, board.SDA)
130
+        self.compass = adafruit_lsm9ds1.LSM9DS1_I2C(i2c)
131
+        time.sleep(1)
132
+
133
+    def advance_frame(self):
134
+        """Essentially our main program loop."""
135
+
136
+        current = time.monotonic()
137
+        self.radio_rx(timeout=0.5)
138
+        new_gps_data = self.gps.update()
139
+        self.update_heading()
140
+        self.display_pixels()
141
+
142
+        if not self.gps.has_fix:
143
+            # Try again if we don't have a fix yet.
144
+            self.statuslight.fill(RED)
145
+            return
146
+
147
+        # We want to send coordinates out either on new GPS data or roughly every 15 seconds.
148
+        if (not new_gps_data) and (current - self.last_send < 15):
149
+            return
150
+
151
+        # Set the RTC to GPS time (UTC):
152
+        if new_gps_data and not self.time_set:
153
+            rtc.RTC().datetime = self.gps.timestamp_utc
154
+            self.time_set = True
155
+
156
+        gps_coords = (self.gps.latitude, self.gps.longitude)
157
+        if gps_coords == self.coords:
158
+            return
159
+
160
+        self.coords = (self.gps.latitude, self.gps.longitude)
161
+
162
+        self.statuslight.fill(BLUE)
163
+        print(':: ' + str(current))  # Print a separator line.
164
+        print(timestamp())
165
+        send_packet = '{}:{}:{}:{}'.format(
166
+            self.gps.latitude,
167
+            self.gps.longitude,
168
+            self.gps.speed_knots,
169
+            self.heading
170
+        )
171
+
172
+        print('   quality: {}'.format(self.gps.fix_quality))
173
+        print('   ' + str(gc.mem_free()) + " bytes free")
174
+
175
+        # Send a location packet:
176
+        self.radio_tx('l', send_packet)
177
+
178
+    def update_heading(self):
179
+        mag_x, mag_y, mag_z = self.compass.magnetometer
180
+        # print('Magnetometer: ({0:10.3f}, {1:10.3f}, {2:10.3f})'.format(mag_x, mag_y, mag_z))
181
+        mag_x = map_range(mag_x, MAG_MIN[0], MAG_MAX[0], -1, 1)
182
+        mag_y = map_range(mag_y, MAG_MIN[1], MAG_MAX[1], -1, 1)
183
+        mag_z = map_range(mag_z, MAG_MIN[2], MAG_MAX[2], -1, 1)
184
+
185
+        heading_mag = (math.atan2(mag_y, mag_x) * 180) / math.pi
186
+        if heading_mag < 0:
187
+            heading_mag = 360 + heading_mag
188
+
189
+        # Account for declination (given in radians above):
190
+        heading = heading_mag + (DECLINATION_RAD * 180 / math.pi)
191
+        if heading > 360:
192
+            heading = heading - 360
193
+
194
+        print('heading: {}'.format(heading))
195
+        self.heading = heading
196
+
197
+    def radio_tx(self, msg_type, msg):
198
+        """send a packet over radio with id prefix and checksum"""
199
+        packet = 'e:' + msg_type + ':' + str(self.glitterpos_id) + ':' + msg
200
+        print('   sending: ' + packet)
201
+
202
+        # Blocking, max of 252 bytes:
203
+        self.rfm9x.send(packet)
204
+        self.last_send = time.monotonic()
205
+
206
+    def radio_rx(self, timeout=0.5):
207
+        """check radio for new packets, handle incoming data"""
208
+
209
+        packet = self.rfm9x.receive(timeout)
210
+
211
+        # If no packet was received during the timeout then None is returned:
212
+        if packet is None:
213
+            return
214
+
215
+        packet = bytes(packet)
216
+        print(timestamp())
217
+        print('   received signal strength: {0} dB'.format(self.rfm9x.rssi))
218
+        print('   received (raw bytes): {0}'.format(packet))
219
+        pieces = packet.split(b':')
220
+
221
+        if pieces[0] != b'e':
222
+            print('   bogus packet, bailing out')
223
+            return
224
+
225
+        msg_type = pieces[1].format()
226
+        sender_id = int(pieces[2].format())
227
+
228
+        # A location message:
229
+        if msg_type == 'l':
230
+            sender_lat = float(pieces[3].format())
231
+            sender_lon = float(pieces[4].format())
232
+            self.glitterpos_boxes[sender_id] = (sender_lat, sender_lon)
233
+
234
+        # packet_text = str(packet, 'ascii')
235
+        # print('Received (ASCII): {0}'.format(packet_text))
236
+
237
+    def display_pixels(self):
238
+        """Display current state on the NeoPixel ring."""
239
+        self.pixels.fill((0, 0, 0))
240
+
241
+        if not self.gps.has_fix:
242
+            return
243
+
244
+        for box in self.glitterpos_boxes:
245
+            bearing_to_box = compass_bearing(self.coords, self.glitterpos_boxes[box])
246
+
247
+            # Treat current compass heading as our origin point for display purposes:
248
+            display_bearing = bearing_to_box - self.heading
249
+            if display_bearing < 0:
250
+                display_bearing = display_bearing + 360
251
+
252
+            pixel = bearing_to_pixel(display_bearing)
253
+            # print('display pixel: {}'.format(pixel))
254
+
255
+            color = (15, 15, 15)
256
+            if box in COLOR_LOOKUP:
257
+                color = COLOR_LOOKUP[box]
258
+            self.pixels[pixel] = color
259
+
260
+        self.pixels.show()

+ 77
- 0
glitterpos_util.py View File

@@ -0,0 +1,77 @@
1
+"""a handful of utility functions used by GlitterPOS."""
2
+import math
3
+import time
4
+
5
+# https://gist.githubusercontent.com/jeromer/2005586/raw/5456a9386acce189ac6cc416c42e9c4b560a633b/compassbearing.py
6
+def compass_bearing(pointA, pointB):
7
+    """
8
+    Calculates the bearing between two points.
9
+
10
+    The formulae used is the following:
11
+        θ = atan2(sin(Δlong).cos(lat2),
12
+                  cos(lat1).sin(lat2) − sin(lat1).cos(lat2).cos(Δlong))
13
+
14
+    :Parameters:
15
+      - `pointA: The tuple representing the latitude/longitude for the
16
+        first point. Latitude and longitude must be in decimal degrees
17
+      - `pointB: The tuple representing the latitude/longitude for the
18
+        second point. Latitude and longitude must be in decimal degrees
19
+
20
+    :Returns:
21
+      The bearing in degrees
22
+
23
+    :Returns Type:
24
+      float
25
+    """
26
+    if (type(pointA) != tuple) or (type(pointB) != tuple):
27
+        raise TypeError("Only tuples are supported as arguments")
28
+
29
+    lat1 = math.radians(pointA[0])
30
+    lat2 = math.radians(pointB[0])
31
+
32
+    diffLong = math.radians(pointB[1] - pointA[1])
33
+
34
+    x = math.sin(diffLong) * math.cos(lat2)
35
+    y = math.cos(lat1) * math.sin(lat2) - (math.sin(lat1)
36
+            * math.cos(lat2) * math.cos(diffLong))
37
+
38
+    initial_bearing = math.atan2(x, y)
39
+
40
+    # Now we have the initial bearing but math.atan2 return values
41
+    # from -180° to + 180° which is not what we want for a compass bearing
42
+    # The solution is to normalize the initial bearing as shown below
43
+    initial_bearing = math.degrees(initial_bearing)
44
+    compass_bearing = (initial_bearing + 360) % 360
45
+
46
+    return compass_bearing
47
+
48
+def bearing_to_pixel(bearing, count=16):
49
+    # Subtract from count since the neopixel ring runs counterclockwise:
50
+    pixel = count - int(round((bearing / 360) * count))
51
+    if pixel == 16:
52
+      return 0
53
+    return pixel
54
+
55
+def map_range(x, in_min, in_max, out_min, out_max):
56
+    """
57
+    Maps a number from one range to another.
58
+    :return: Returns value mapped to new range
59
+    :rtype: float
60
+    """
61
+    mapped = (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
62
+    if out_min <= out_max:
63
+        return max(min(mapped, out_max), out_min)
64
+
65
+    return min(max(mapped, out_max), out_min)
66
+
67
+def timestamp():
68
+    """print a human-readable timestamp"""
69
+    timestamp = time.localtime()
70
+    return '{}/{}/{} {:02}:{:02}:{:02}'.format(
71
+        timestamp.tm_year,
72
+        timestamp.tm_mon,
73
+        timestamp.tm_mday,
74
+        timestamp.tm_hour,
75
+        timestamp.tm_min,
76
+        timestamp.tm_sec
77
+    )

+ 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 @@
1
+# The MIT License (MIT)
2
+#
3
+# Copyright (c) 2016 Scott Shawcroft for Adafruit Industries
4
+#
5
+# Permission is hereby granted, free of charge, to any person obtaining a copy
6
+# of this software and associated documentation files (the "Software"), to deal
7
+# in the Software without restriction, including without limitation the rights
8
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+# copies of the Software, and to permit persons to whom the Software is
10
+# furnished to do so, subject to the following conditions:
11
+#
12
+# The above copyright notice and this permission notice shall be included in
13
+# all copies or substantial portions of the Software.
14
+#
15
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+# THE SOFTWARE.
22
+
23
+"""
24
+`adafruit_bus_device.i2c_device` - I2C Bus Device
25
+====================================================
26
+"""
27
+
28
+__version__ = "2.2.2"
29
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BusDevice.git"
30
+
31
+class I2CDevice:
32
+    """
33
+    Represents a single I2C device and manages locking the bus and the device
34
+    address.
35
+
36
+    :param ~busio.I2C i2c: The I2C bus the device is on
37
+    :param int device_address: The 7 bit device address
38
+
39
+    .. note:: This class is **NOT** built into CircuitPython. See
40
+      :ref:`here for install instructions <bus_device_installation>`.
41
+
42
+    Example:
43
+
44
+    .. code-block:: python
45
+
46
+        import busio
47
+        from board import *
48
+        from adafruit_bus_device.i2c_device import I2CDevice
49
+
50
+        with busio.I2C(SCL, SDA) as i2c:
51
+            device = I2CDevice(i2c, 0x70)
52
+            bytes_read = bytearray(4)
53
+            with device:
54
+                device.readinto(bytes_read)
55
+            # A second transaction
56
+            with device:
57
+                device.write(bytes_read)
58
+    """
59
+    def __init__(self, i2c, device_address):
60
+        # Verify that a deivce with that address exists.
61
+        while not i2c.try_lock():
62
+            pass
63
+        try:
64
+            i2c.writeto(device_address, b'')
65
+        except OSError:
66
+            raise ValueError("No I2C device at address: %x" % device_address)
67
+        finally:
68
+            i2c.unlock()
69
+
70
+        self.i2c = i2c
71
+        self.device_address = device_address
72
+
73
+    def readinto(self, buf, **kwargs):
74
+        """
75
+        Read into ``buf`` from the device. The number of bytes read will be the
76
+        length of ``buf``.
77
+
78
+        If ``start`` or ``end`` is provided, then the buffer will be sliced
79
+        as if ``buf[start:end]``. This will not cause an allocation like
80
+        ``buf[start:end]`` will so it saves memory.
81
+
82
+        :param bytearray buffer: buffer to write into
83
+        :param int start: Index to start writing at
84
+        :param int end: Index to write up to but not include
85
+        """
86
+        self.i2c.readfrom_into(self.device_address, buf, **kwargs)
87
+
88
+    def write(self, buf, **kwargs):
89
+        """
90
+        Write the bytes from ``buffer`` to the device. Transmits a stop bit if
91
+        ``stop`` is set.
92
+
93
+        If ``start`` or ``end`` is provided, then the buffer will be sliced
94
+        as if ``buffer[start:end]``. This will not cause an allocation like
95
+        ``buffer[start:end]`` will so it saves memory.
96
+
97
+        :param bytearray buffer: buffer containing the bytes to write
98
+        :param int start: Index to start writing from
99
+        :param int end: Index to read up to but not include
100
+        :param bool stop: If true, output an I2C stop condition after the buffer is written
101
+        """
102
+        self.i2c.writeto(self.device_address, buf, **kwargs)
103
+
104
+    def __enter__(self):
105
+        while not self.i2c.try_lock():
106
+            pass
107
+        return self
108
+
109
+    def __exit__(self, *exc):
110
+        self.i2c.unlock()
111
+        return False

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

@@ -0,0 +1,100 @@
1
+# The MIT License (MIT)
2
+#
3
+# Copyright (c) 2016 Scott Shawcroft for Adafruit Industries
4
+#
5
+# Permission is hereby granted, free of charge, to any person obtaining a copy
6
+# of this software and associated documentation files (the "Software"), to deal
7
+# in the Software without restriction, including without limitation the rights
8
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+# copies of the Software, and to permit persons to whom the Software is
10
+# furnished to do so, subject to the following conditions:
11
+#
12
+# The above copyright notice and this permission notice shall be included in
13
+# all copies or substantial portions of the Software.
14
+#
15
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+# THE SOFTWARE.
22
+# pylint: disable=too-few-public-methods
23
+
24
+"""
25
+`adafruit_bus_device.spi_device` - SPI Bus Device
26
+====================================================
27
+"""
28
+
29
+__version__ = "2.2.2"
30
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BusDevice.git"
31
+
32
+class SPIDevice:
33
+    """
34
+    Represents a single SPI device and manages locking the bus and the device
35
+    address.
36
+
37
+    :param ~busio.SPI spi: The SPI bus the device is on
38
+    :param ~digitalio.DigitalInOut chip_select: The chip select pin object that implements the
39
+        DigitalInOut API.
40
+    :param int extra_clocks: The minimum number of clock cycles to cycle the bus after CS is high.
41
+        (Used for SD cards.)
42
+
43
+    .. note:: This class is **NOT** built into CircuitPython. See
44
+      :ref:`here for install instructions <bus_device_installation>`.
45
+
46
+    Example:
47
+
48
+    .. code-block:: python
49
+
50
+        import busio
51
+        import digitalio
52
+        from board import *
53
+        from adafruit_bus_device.spi_device import SPIDevice
54
+
55
+        with busio.SPI(SCK, MOSI, MISO) as spi_bus:
56
+            cs = digitalio.DigitalInOut(D10)
57
+            device = SPIDevice(spi_bus, cs)
58
+            bytes_read = bytearray(4)
59
+            # The object assigned to spi in the with statements below
60
+            # is the original spi_bus object. We are using the busio.SPI
61
+            # operations busio.SPI.readinto() and busio.SPI.write().
62
+            with device as spi:
63
+                spi.readinto(bytes_read)
64
+            # A second transaction
65
+            with device as spi:
66
+                spi.write(bytes_read)
67
+    """
68
+    def __init__(self, spi, chip_select=None, *,
69
+                 baudrate=100000, polarity=0, phase=0, extra_clocks=0):
70
+        self.spi = spi
71
+        self.baudrate = baudrate
72
+        self.polarity = polarity
73
+        self.phase = phase
74
+        self.extra_clocks = extra_clocks
75
+        self.chip_select = chip_select
76
+        if self.chip_select:
77
+            self.chip_select.switch_to_output(value=True)
78
+
79
+    def __enter__(self):
80
+        while not self.spi.try_lock():
81
+            pass
82
+        self.spi.configure(baudrate=self.baudrate, polarity=self.polarity,
83
+                           phase=self.phase)
84
+        if self.chip_select:
85
+            self.chip_select.value = False
86
+        return self.spi
87
+
88
+    def __exit__(self, *exc):
89
+        if self.chip_select:
90
+            self.chip_select.value = True
91
+        if self.extra_clocks > 0:
92
+            buf = bytearray(1)
93
+            buf[0] = 0xff
94
+            clocks = self.extra_clocks // 8
95
+            if self.extra_clocks % 8 != 0:
96
+                clocks += 1
97
+            for _ in range(clocks):
98
+                self.spi.write(buf)
99
+        self.spi.unlock()
100
+        return False

+ 251
- 0
lib/adafruit_gps.py View File

@@ -0,0 +1,251 @@
1
+# The MIT License (MIT)
2
+#
3
+# Copyright (c) 2017 Tony DiCola for Adafruit Industries
4
+#
5
+# Permission is hereby granted, free of charge, to any person obtaining a copy
6
+# of this software and associated documentation files (the "Software"), to deal
7
+# in the Software without restriction, including without limitation the rights
8
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+# copies of the Software, and to permit persons to whom the Software is
10
+# furnished to do so, subject to the following conditions:
11
+#
12
+# The above copyright notice and this permission notice shall be included in
13
+# all copies or substantial portions of the Software.
14
+#
15
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+# THE SOFTWARE.
22
+"""
23
+`adafruit_gps`
24
+====================================================
25
+
26
+GPS parsing module.  Can parse simple NMEA data sentences from serial GPS
27
+modules to read latitude, longitude, and more.
28
+
29
+* Author(s): Tony DiCola
30
+
31
+Implementation Notes
32
+--------------------
33
+
34
+**Hardware:**
35
+
36
+* Adafruit `Ultimate GPS Breakout <https://www.adafruit.com/product/746>`_
37
+* Adafruit `Ultimate GPS FeatherWing <https://www.adafruit.com/product/3133>`_
38
+
39
+**Software and Dependencies:**
40
+
41
+* Adafruit CircuitPython firmware for the ESP8622 and M0-based boards:
42
+  https://github.com/adafruit/circuitpython/releases
43
+
44
+"""
45
+import time
46
+
47
+__version__ = "3.0.2"
48
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_GPS.git"
49
+
50
+# Internal helper parsing functions.
51
+# These handle input that might be none or null and return none instead of
52
+# throwing errors.
53
+def _parse_degrees(nmea_data):
54
+    # Parse a NMEA lat/long data pair 'dddmm.mmmm' into a pure degrees value.
55
+    # Where ddd is the degrees, mm.mmmm is the minutes.
56
+    if nmea_data is None or len(nmea_data) < 3:
57
+        return None
58
+    raw = float(nmea_data)
59
+    deg = raw // 100
60
+    minutes = raw % 100
61
+    return deg + minutes/60
62
+
63
+def _parse_int(nmea_data):
64
+    if nmea_data is None or nmea_data == '':
65
+        return None
66
+    return int(nmea_data)
67
+
68
+def _parse_float(nmea_data):
69
+    if nmea_data is None or nmea_data == '':
70
+        return None
71
+    return float(nmea_data)
72
+
73
+# lint warning about too many attributes disabled
74
+#pylint: disable-msg=R0902
75
+class GPS:
76
+    """GPS parsing module.  Can parse simple NMEA data sentences from serial GPS
77
+    modules to read latitude, longitude, and more.
78
+    """
79
+    def __init__(self, uart):
80
+        self._uart = uart
81
+        # Initialize null starting values for GPS attributes.
82
+        self.timestamp_utc = None
83
+        self.latitude = None
84
+        self.longitude = None
85
+        self.fix_quality = None
86
+        self.satellites = None
87
+        self.horizontal_dilution = None
88
+        self.altitude_m = None
89
+        self.height_geoid = None
90
+        self.velocity_knots = None
91
+        self.speed_knots = None
92
+        self.track_angle_deg = None
93
+
94
+    def update(self):
95
+        """Check for updated data from the GPS module and process it
96
+        accordingly.  Returns True if new data was processed, and False if
97
+        nothing new was received.
98
+        """
99
+        # Grab a sentence and check its data type to call the appropriate
100
+        # parsing function.
101
+        sentence = self._parse_sentence()
102
+        if sentence is None:
103
+            return False
104
+        data_type, args = sentence
105
+        data_type = data_type.upper()
106
+        if data_type == 'GPGGA':      # GGA, 3d location fix
107
+            self._parse_gpgga(args)
108
+        elif data_type == 'GPRMC':    # RMC, minimum location info
109
+            self._parse_gprmc(args)
110
+        return True
111
+
112
+    def send_command(self, command, add_checksum=True):
113
+        """Send a command string to the GPS.  If add_checksum is True (the
114
+        default) a NMEA checksum will automatically be computed and added.
115
+        Note you should NOT add the leading $ and trailing * to the command
116
+        as they will automatically be added!
117
+        """
118
+        self._uart.write('$')
119
+        self._uart.write(command)
120
+        if add_checksum:
121
+            checksum = 0
122
+            for char in command:
123
+                checksum ^= ord(char)
124
+            self._uart.write('*')
125
+            self._uart.write('{:02x}'.format(checksum).upper())
126
+        self._uart.write('\r\n')
127
+
128
+    @property
129
+    def has_fix(self):
130
+        """True if a current fix for location information is available."""
131
+        return self.fix_quality is not None and self.fix_quality >= 1
132
+
133
+    def _parse_sentence(self):
134
+        # Parse any NMEA sentence that is available.
135
+        sentence = self._uart.readline()
136
+        if sentence is None or sentence == b'' or len(sentence) < 1:
137
+            return None
138
+        sentence = str(sentence, 'ascii').strip()
139
+        # Look for a checksum and validate it if present.
140
+        if len(sentence) > 7 and sentence[-3] == '*':
141
+            # Get included checksum, then calculate it and compare.
142
+            expected = int(sentence[-2:], 16)
143
+            actual = 0
144
+            for i in range(1, len(sentence)-3):
145
+                actual ^= ord(sentence[i])
146
+            if actual != expected:
147
+                return None  # Failed to validate checksum.
148
+            # Remove checksum once validated.
149
+            sentence = sentence[:-3]
150
+        # Parse out the type of sentence (first string after $ up to comma)
151
+        # and then grab the rest as data within the sentence.
152
+        delineator = sentence.find(',')
153
+        if delineator == -1:
154
+            return None  # Invalid sentence, no comma after data type.
155
+        data_type = sentence[1:delineator]
156
+        return (data_type, sentence[delineator+1:])
157
+
158
+    def _parse_gpgga(self, args):
159
+        # Parse the arguments (everything after data type) for NMEA GPGGA
160
+        # 3D location fix sentence.
161
+        data = args.split(',')
162
+        if data is None or len(data) != 14:
163
+            return  # Unexpected number of params.
164
+        # Parse fix time.
165
+        time_utc = int(_parse_float(data[0]))
166
+        if time_utc is not None:
167
+            hours = time_utc // 10000
168
+            mins = (time_utc // 100) % 100
169
+            secs = time_utc % 100
170
+            # Set or update time to a friendly python time struct.
171
+            if self.timestamp_utc is not None:
172
+                self.timestamp_utc = time.struct_time((
173
+                    self.timestamp_utc.tm_year, self.timestamp_utc.tm_mon,
174
+                    self.timestamp_utc.tm_mday, hours, mins, secs, 0, 0, -1))
175
+            else:
176
+                self.timestamp_utc = time.struct_time((0, 0, 0, hours, mins,
177
+                                                       secs, 0, 0, -1))
178
+        # Parse latitude and longitude.
179
+        self.latitude = _parse_degrees(data[1])
180
+        if self.latitude is not None and \
181
+           data[2] is not None and data[2].lower() == 's':
182
+            self.latitude *= -1.0
183
+        self.longitude = _parse_degrees(data[3])
184
+        if self.longitude is not None and \
185
+           data[4] is not None and data[4].lower() == 'w':
186
+            self.longitude *= -1.0
187
+        # Parse out fix quality and other simple numeric values.
188
+        self.fix_quality = _parse_int(data[5])
189
+        self.satellites = _parse_int(data[6])
190
+        self.horizontal_dilution = _parse_float(data[7])
191
+        self.altitude_m = _parse_float(data[8])
192
+        self.height_geoid = _parse_float(data[10])
193
+
194
+    def _parse_gprmc(self, args):
195
+        # Parse the arguments (everything after data type) for NMEA GPRMC
196
+        # minimum location fix sentence.
197
+        data = args.split(',')
198
+        if data is None or len(data) < 11:
199
+            return  # Unexpected number of params.
200
+        # Parse fix time.
201
+        time_utc = int(_parse_float(data[0]))
202
+        if time_utc is not None:
203
+            hours = time_utc // 10000
204
+            mins = (time_utc // 100) % 100
205
+            secs = time_utc % 100
206
+            # Set or update time to a friendly python time struct.
207
+            if self.timestamp_utc is not None:
208
+                self.timestamp_utc = time.struct_time((
209
+                    self.timestamp_utc.tm_year, self.timestamp_utc.tm_mon,
210
+                    self.timestamp_utc.tm_mday, hours, mins, secs, 0, 0, -1))
211
+            else:
212
+                self.timestamp_utc = time.struct_time((0, 0, 0, hours, mins,
213
+                                                       secs, 0, 0, -1))
214
+        # Parse status (active/fixed or void).
215
+        status = data[1]
216
+        self.fix_quality = 0
217
+        if status is not None and status.lower() == 'a':
218
+            self.fix_quality = 1
219
+        # Parse latitude and longitude.
220
+        self.latitude = _parse_degrees(data[2])
221
+        if self.latitude is not None and \
222
+           data[3] is not None and data[3].lower() == 's':
223
+            self.latitude *= -1.0
224
+        self.longitude = _parse_degrees(data[4])
225
+        if self.longitude is not None and \
226
+           data[5] is not None and data[5].lower() == 'w':
227
+            self.longitude *= -1.0
228
+        # Parse out speed and other simple numeric values.
229
+        self.speed_knots = _parse_float(data[6])
230
+        self.track_angle_deg = _parse_float(data[7])
231
+        # Parse date.
232
+        if data[8] is not None and len(data[8]) == 6:
233
+            day = int(data[8][0:2])
234
+            month = int(data[8][2:4])
235
+            year = 2000 + int(data[8][4:6])  # Y2k bug, 2 digit date assumption.
236
+                                             # This is a problem with the NMEA
237
+                                             # spec and not this code.
238
+            if self.timestamp_utc is not None:
239
+                # Replace the timestamp with an updated one.
240
+                # (struct_time is immutable and can't be changed in place)
241
+                self.timestamp_utc = time.struct_time((year, month, day,
242
+                                                       self.timestamp_utc.tm_hour,
243
+                                                       self.timestamp_utc.tm_min,
244
+                                                       self.timestamp_utc.tm_sec,
245
+                                                       0,
246
+                                                       0,
247
+                                                       -1))
248
+            else:
249
+                # Time hasn't been set so create it.
250
+                self.timestamp_utc = time.struct_time((year, month, day, 0, 0,
251
+                                                       0, 0, 0, -1))

+ 241
- 0
lib/adafruit_lsm303.py View File

@@ -0,0 +1,241 @@
1
+# The MIT License (MIT)
2
+#
3
+# Copyright (c) 2017 Dave Astels for Adafruit Industries
4
+#
5
+# Permission is hereby granted, free of charge, to any person obtaining a copy
6
+# of this software and associated documentation files (the "Software"), to deal
7
+# in the Software without restriction, including without limitation the rights
8
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+# copies of the Software, and to permit persons to whom the Software is
10
+# furnished to do so, subject to the following conditions:
11
+#
12
+# The above copyright notice and this permission notice shall be included in
13
+# all copies or substantial portions of the Software.
14
+#
15
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+# THE SOFTWARE.
22
+"""
23
+`adafruit_LSM303`
24
+====================================================
25
+
26
+
27
+CircuitPython driver for the LSM303 accelerometer + magnetometer.
28
+
29
+* Author(s): Dave Astels
30
+"""
31
+
32
+try:
33
+    import struct
34
+except ImportError:
35
+    import ustruct as struct
36
+from micropython import const
37
+from adafruit_bus_device.i2c_device import I2CDevice
38
+
39
+__version__ = "1.1.0"
40
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LSM303.git"
41
+
42
+# pylint: disable=bad-whitespace
43
+_ADDRESS_ACCEL             = const(0x19)  # (0x32 >> 1)       // 0011001x
44
+_ADDRESS_MAG               = const(0x1E)  # (0x3C >> 1)       // 0011110x
45
+_ID                        = const(0xD4)  # (0b11010100)
46
+
47
+# Accelerometer registers
48
+_REG_ACCEL_CTRL_REG1_A     = const(0x20)
49
+_REG_ACCEL_CTRL_REG2_A     = const(0x21)
50
+_REG_ACCEL_CTRL_REG3_A     = const(0x22)
51
+_REG_ACCEL_CTRL_REG4_A     = const(0x23)
52
+_REG_ACCEL_CTRL_REG5_A     = const(0x24)
53
+_REG_ACCEL_CTRL_REG6_A     = const(0x25)
54
+_REG_ACCEL_REFERENCE_A     = const(0x26)
55
+_REG_ACCEL_STATUS_REG_A    = const(0x27)
56
+_REG_ACCEL_OUT_X_L_A       = const(0x28)
57
+_REG_ACCEL_OUT_X_H_A       = const(0x29)
58
+_REG_ACCEL_OUT_Y_L_A       = const(0x2A)
59
+_REG_ACCEL_OUT_Y_H_A       = const(0x2B)
60
+_REG_ACCEL_OUT_Z_L_A       = const(0x2C)
61
+_REG_ACCEL_OUT_Z_H_A       = const(0x2D)
62
+_REG_ACCEL_FIFO_CTRL_REG_A = const(0x2E)
63
+_REG_ACCEL_FIFO_SRC_REG_A  = const(0x2F)
64
+_REG_ACCEL_INT1_CFG_A      = const(0x30)
65
+_REG_ACCEL_INT1_SOURCE_A   = const(0x31)
66
+_REG_ACCEL_INT1_THS_A      = const(0x32)
67
+_REG_ACCEL_INT1_DURATION_A = const(0x33)
68
+_REG_ACCEL_INT2_CFG_A      = const(0x34)
69
+_REG_ACCEL_INT2_SOURCE_A   = const(0x35)
70
+_REG_ACCEL_INT2_THS_A      = const(0x36)
71
+_REG_ACCEL_INT2_DURATION_A = const(0x37)
72
+_REG_ACCEL_CLICK_CFG_A     = const(0x38)
73
+_REG_ACCEL_CLICK_SRC_A     = const(0x39)
74
+_REG_ACCEL_CLICK_THS_A     = const(0x3A)
75
+_REG_ACCEL_TIME_LIMIT_A    = const(0x3B)
76
+_REG_ACCEL_TIME_LATENCY_A  = const(0x3C)
77
+_REG_ACCEL_TIME_WINDOW_A   = const(0x3D)
78
+
79
+# Magnetometer registers
80
+_REG_MAG_CRA_REG_M         = const(0x00)
81
+_REG_MAG_CRB_REG_M         = const(0x01)
82
+_REG_MAG_MR_REG_M          = const(0x02)
83
+_REG_MAG_OUT_X_H_M         = const(0x03)
84
+_REG_MAG_OUT_X_L_M         = const(0x04)
85
+_REG_MAG_OUT_Z_H_M         = const(0x05)
86
+_REG_MAG_OUT_Z_L_M         = const(0x06)
87
+_REG_MAG_OUT_Y_H_M         = const(0x07)
88
+_REG_MAG_OUT_Y_L_M         = const(0x08)
89
+_REG_MAG_SR_REG_M          = const(0x09)
90
+_REG_MAG_IRA_REG_M         = const(0x0A)
91
+_REG_MAG_IRB_REG_M         = const(0x0B)
92
+_REG_MAG_IRC_REG_M         = const(0x0C)
93
+_REG_MAG_TEMP_OUT_H_M      = const(0x31)
94
+_REG_MAG_TEMP_OUT_L_M      = const(0x32)
95
+
96
+# Magnetometer gains
97
+MAGGAIN_1_3                = const(0x20)  # +/- 1.3
98
+MAGGAIN_1_9                = const(0x40)  # +/- 1.9
99
+MAGGAIN_2_5                = const(0x60)  # +/- 2.5
100
+MAGGAIN_4_0                = const(0x80)  # +/- 4.0
101
+MAGGAIN_4_7                = const(0xA0)  # +/- 4.7
102
+MAGGAIN_5_6                = const(0xC0)  # +/- 5.6
103
+MAGGAIN_8_1                = const(0xE0)  # +/- 8.1
104
+
105
+# Magentometer rates
106
+MAGRATE_0_7                = const(0x00)  # 0.75 Hz
107
+MAGRATE_1_5                = const(0x01)  # 1.5 Hz
108
+MAGRATE_3_0                = const(0x62)  # 3.0 Hz
109
+MAGRATE_7_5                = const(0x03)  # 7.5 Hz
110
+MAGRATE_15                 = const(0x04)  # 15 Hz
111
+MAGRATE_30                 = const(0x05)  # 30 Hz
112
+MAGRATE_75                 = const(0x06)  # 75 Hz
113
+MAGRATE_220                = const(0x07)  # 200 Hz
114
+
115
+# Conversion constants
116
+_LSM303ACCEL_MG_LSB        = 16704.0
117
+_GRAVITY_STANDARD          = 9.80665      # Earth's gravity in m/s^2
118
+_GAUSS_TO_MICROTESLA       = 100.0        # Gauss to micro-Tesla multiplier
119
+# pylint: enable=bad-whitespace
120
+
121
+
122
+class LSM303(object):
123
+    """Driver for the LSM303 accelerometer/magnetometer."""
124
+
125
+    # Class-level buffer for reading and writing data with the sensor.
126
+    # This reduces memory allocations but means the code is not re-entrant or
127
+    # thread safe!
128
+    _BUFFER = bytearray(6)
129
+
130
+    def __init__(self, i2c):
131
+        self._accel_device = I2CDevice(i2c, _ADDRESS_ACCEL)
132
+        self._mag_device = I2CDevice(i2c, _ADDRESS_MAG)
133
+        self._write_u8(self._accel_device, _REG_ACCEL_CTRL_REG1_A, 0x27)  # Enable the accelerometer
134
+        self._write_u8(self._mag_device, _REG_MAG_MR_REG_M, 0x00)  # Enable the magnetometer
135
+        self._lsm303mag_gauss_lsb_xy = 1100.0
136
+        self._lsm303mag_gauss_lsb_z = 980.0
137
+        self._mag_gain = MAGGAIN_1_3
138
+        self._mag_rate = MAGRATE_0_7
139
+
140
+    @property
141
+    def raw_acceleration(self):
142
+        """The raw accelerometer sensor values.
143
+        A 3-tuple of X, Y, Z axis values that are 16-bit signed integers.
144
+        """
145
+        self._read_bytes(self._accel_device, _REG_ACCEL_OUT_X_L_A | 0x80, 6, self._BUFFER)
146
+        return struct.unpack_from('<hhh', self._BUFFER[0:6])
147
+
148
+    @property
149
+    def acceleration(self):
150
+        """The processed accelerometer sensor values.
151
+        A 3-tuple of X, Y, Z axis values in meters per second squared that are signed floats.
152
+        """
153
+        raw_accel_data = self.raw_acceleration
154
+        return tuple([n / _LSM303ACCEL_MG_LSB * _GRAVITY_STANDARD for n in raw_accel_data])
155
+
156
+    @property
157
+    def raw_magnetic(self):
158
+        """The raw magnetometer sensor values.
159
+        A 3-tuple of X, Y, Z axis values that are 16-bit signed integers.
160
+        """
161
+        self._read_bytes(self._mag_device, _REG_MAG_OUT_X_H_M, 6, self._BUFFER)
162
+        raw_values = struct.unpack_from('>hhh', self._BUFFER[0:6])
163
+        return tuple([n >> 4 for n in raw_values])
164
+
165
+    @property
166
+    def magnetic(self):
167
+        """The processed magnetometer sensor values.
168
+        A 3-tuple of X, Y, Z axis values in microteslas that are signed floats.
169
+        """
170
+        mag_x, mag_y, mag_z = self.raw_magnetic
171
+        return (mag_x / self._lsm303mag_gauss_lsb_xy * _GAUSS_TO_MICROTESLA,
172
+                mag_y / self._lsm303mag_gauss_lsb_xy * _GAUSS_TO_MICROTESLA,
173
+                mag_z / self._lsm303mag_gauss_lsb_z * _GAUSS_TO_MICROTESLA)
174
+
175
+    @property
176
+    def mag_gain(self):
177
+        """The magnetometer's gain."""
178
+        return self._mag_gain
179
+
180
+    @mag_gain.setter
181
+    def mag_gain(self, value):
182
+        assert value in (MAGGAIN_1_3, MAGGAIN_1_9, MAGGAIN_2_5, MAGGAIN_4_0, MAGGAIN_4_7,
183
+                         MAGGAIN_5_6, MAGGAIN_8_1)
184
+
185
+        self._mag_gain = value
186
+        self._write_u8(self._mag_device, _REG_MAG_CRB_REG_M, self._mag_gain)
187
+        if self._mag_gain == MAGGAIN_1_3:
188
+            self._lsm303mag_gauss_lsb_xy = 1100.0
189
+            self._lsm303mag_gauss_lsb_z = 980.0
190
+        elif self._mag_gain == MAGGAIN_1_9:
191
+            self._lsm303mag_gauss_lsb_xy = 855.0
192
+            self._lsm303mag_gauss_lsb_z = 760.0
193
+        elif self._mag_gain == MAGGAIN_2_5:
194
+            self._lsm303mag_gauss_lsb_xy = 670.0
195
+            self._lsm303mag_gauss_lsb_z = 600.0
196
+        elif self._mag_gain == MAGGAIN_4_0:
197
+            self._lsm303mag_gauss_lsb_xy = 450.0
198
+            self._lsm303mag_gauss_lsb_z = 400.0
199
+        elif self._mag_gain == MAGGAIN_4_7:
200
+            self._lsm303mag_gauss_lsb_xy = 400.0
201
+            self._lsm303mag_gauss_lsb_z = 355.0
202
+        elif self._mag_gain == MAGGAIN_5_6:
203
+            self._lsm303mag_gauss_lsb_xy = 330.0
204
+            self._lsm303mag_gauss_lsb_z = 295.0
205
+        elif self._mag_gain == MAGGAIN_8_1:
206
+            self._lsm303mag_gauss_lsb_xy = 230.0
207
+            self._lsm303mag_gauss_lsb_z = 205.0
208
+
209
+    @property
210
+    def mag_rate(self):
211
+        """The magnetometer update rate."""
212
+        return self._mag_rate
213
+
214
+    @mag_rate.setter
215
+    def mag_rate(self, value):
216
+        assert value in (MAGRATE_0_7, MAGRATE_1_5, MAGRATE_3_0, MAGRATE_7_5, MAGRATE_15, MAGRATE_30,
217
+                         MAGRATE_75, MAGRATE_220)
218
+
219
+        self._mag_rate = value
220
+        reg_m = ((value & 0x07) << 2) & 0xFF
221
+        self._write_u8(self._mag_device, _REG_MAG_CRA_REG_M, reg_m)
222
+
223
+    def _read_u8(self, device, address):
224
+        with device as i2c:
225
+            self._BUFFER[0] = address & 0xFF
226
+            i2c.write(self._BUFFER, end=1, stop=False)
227
+            i2c.readinto(self._BUFFER, end=1)
228
+        return self._BUFFER[0]
229
+
230
+    def _write_u8(self, device, address, val):
231
+        with device as i2c:
232
+            self._BUFFER[0] = address & 0xFF
233
+            self._BUFFER[1] = val & 0xFF
234
+            i2c.write(self._BUFFER, end=2)
235
+
236
+    @staticmethod
237
+    def _read_bytes(device, address, count, buf):
238
+        with device as i2c:
239
+            buf[0] = address & 0xFF
240
+            i2c.write(buf, end=1, stop=False)
241
+            i2c.readinto(buf, end=count)

+ 431
- 0
lib/adafruit_lsm9ds1.py View File

@@ -0,0 +1,431 @@
1
+# The MIT License (MIT)
2
+#
3
+# Copyright (c) 2017 Tony DiCola for Adafruit Industries
4
+#
5
+# Permission is hereby granted, free of charge, to any person obtaining a copy
6
+# of this software and associated documentation files (the "Software"), to deal
7
+# in the Software without restriction, including without limitation the rights
8
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+# copies of the Software, and to permit persons to whom the Software is
10
+# furnished to do so, subject to the following conditions:
11
+#
12
+# The above copyright notice and this permission notice shall be included in
13
+# all copies or substantial portions of the Software.
14
+#
15
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+# THE SOFTWARE.
22
+"""
23
+`adafruit_lsm9ds1`
24
+====================================================
25
+
26
+CircuitPython module for the LSM9DS1 accelerometer, magnetometer, gyroscope.
27
+Based on the driver from:
28
+  https://github.com/adafruit/Adafruit_LSM9DS1
29
+
30
+See examples/simpletest.py for a demo of the usage.
31
+
32
+* Author(s): Tony DiCola
33
+"""
34
+
35
+__version__ = "1.0.1"
36
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LSM9DS1.git"
37
+
38
+import time
39
+try:
40
+    import struct
41
+except ImportError:
42
+    import ustruct as struct
43
+
44
+import adafruit_bus_device.i2c_device as i2c_device
45
+import adafruit_bus_device.spi_device as spi_device
46
+from micropython import const
47
+
48
+# Internal constants and register values:
49
+# pylint: disable=bad-whitespace
50
+_LSM9DS1_ADDRESS_ACCELGYRO       = const(0x6B)
51
+_LSM9DS1_ADDRESS_MAG             = const(0x1E)
52
+_LSM9DS1_XG_ID                   = const(0b01101000)
53
+_LSM9DS1_MAG_ID                  = const(0b00111101)
54
+_LSM9DS1_ACCEL_MG_LSB_2G         = 0.061
55
+_LSM9DS1_ACCEL_MG_LSB_4G         = 0.122
56
+_LSM9DS1_ACCEL_MG_LSB_8G         = 0.244
57
+_LSM9DS1_ACCEL_MG_LSB_16G        = 0.732
58
+_LSM9DS1_MAG_MGAUSS_4GAUSS       = 0.14
59
+_LSM9DS1_MAG_MGAUSS_8GAUSS       = 0.29
60
+_LSM9DS1_MAG_MGAUSS_12GAUSS      = 0.43
61
+_LSM9DS1_MAG_MGAUSS_16GAUSS      = 0.58
62
+_LSM9DS1_GYRO_DPS_DIGIT_245DPS   = 0.00875
63
+_LSM9DS1_GYRO_DPS_DIGIT_500DPS   = 0.01750
64
+_LSM9DS1_GYRO_DPS_DIGIT_2000DPS  = 0.07000
65
+_LSM9DS1_TEMP_LSB_DEGREE_CELSIUS = 8 # 1°C = 8, 25° = 200, etc.
66
+_LSM9DS1_REGISTER_WHO_AM_I_XG    = const(0x0F)
67
+_LSM9DS1_REGISTER_CTRL_REG1_G    = const(0x10)
68
+_LSM9DS1_REGISTER_CTRL_REG2_G    = const(0x11)
69
+_LSM9DS1_REGISTER_CTRL_REG3_G    = const(0x12)
70
+_LSM9DS1_REGISTER_TEMP_OUT_L     = const(0x15)
71
+_LSM9DS1_REGISTER_TEMP_OUT_H     = const(0x16)
72
+_LSM9DS1_REGISTER_STATUS_REG     = const(0x17)
73
+_LSM9DS1_REGISTER_OUT_X_L_G      = const(0x18)
74
+_LSM9DS1_REGISTER_OUT_X_H_G      = const(0x19)
75
+_LSM9DS1_REGISTER_OUT_Y_L_G      = const(0x1A)
76
+_LSM9DS1_REGISTER_OUT_Y_H_G      = const(0x1B)
77
+_LSM9DS1_REGISTER_OUT_Z_L_G      = const(0x1C)
78
+_LSM9DS1_REGISTER_OUT_Z_H_G      = const(0x1D)
79
+_LSM9DS1_REGISTER_CTRL_REG4      = const(0x1E)
80
+_LSM9DS1_REGISTER_CTRL_REG5_XL   = const(0x1F)
81
+_LSM9DS1_REGISTER_CTRL_REG6_XL   = const(0x20)
82
+_LSM9DS1_REGISTER_CTRL_REG7_XL   = const(0x21)
83
+_LSM9DS1_REGISTER_CTRL_REG8      = const(0x22)
84
+_LSM9DS1_REGISTER_CTRL_REG9      = const(0x23)
85
+_LSM9DS1_REGISTER_CTRL_REG10     = const(0x24)
86
+_LSM9DS1_REGISTER_OUT_X_L_XL     = const(0x28)
87
+_LSM9DS1_REGISTER_OUT_X_H_XL     = const(0x29)
88
+_LSM9DS1_REGISTER_OUT_Y_L_XL     = const(0x2A)
89
+_LSM9DS1_REGISTER_OUT_Y_H_XL     = const(0x2B)
90
+_LSM9DS1_REGISTER_OUT_Z_L_XL     = const(0x2C)
91
+_LSM9DS1_REGISTER_OUT_Z_H_XL     = const(0x2D)
92
+_LSM9DS1_REGISTER_WHO_AM_I_M     = const(0x0F)
93
+_LSM9DS1_REGISTER_CTRL_REG1_M    = const(0x20)
94
+_LSM9DS1_REGISTER_CTRL_REG2_M    = const(0x21)
95
+_LSM9DS1_REGISTER_CTRL_REG3_M    = const(0x22)
96
+_LSM9DS1_REGISTER_CTRL_REG4_M    = const(0x23)
97
+_LSM9DS1_REGISTER_CTRL_REG5_M    = const(0x24)
98
+_LSM9DS1_REGISTER_STATUS_REG_M   = const(0x27)
99
+_LSM9DS1_REGISTER_OUT_X_L_M      = const(0x28)
100
+_LSM9DS1_REGISTER_OUT_X_H_M      = const(0x29)
101
+_LSM9DS1_REGISTER_OUT_Y_L_M      = const(0x2A)
102
+_LSM9DS1_REGISTER_OUT_Y_H_M      = const(0x2B)
103
+_LSM9DS1_REGISTER_OUT_Z_L_M      = const(0x2C)
104
+_LSM9DS1_REGISTER_OUT_Z_H_M      = const(0x2D)
105
+_LSM9DS1_REGISTER_CFG_M          = const(0x30)
106
+_LSM9DS1_REGISTER_INT_SRC_M      = const(0x31)
107
+_MAGTYPE                         = True
108
+_XGTYPE                          = False
109
+_SENSORS_GRAVITY_STANDARD        = 9.80665
110
+
111
+# User facing constants/module globals.
112
+ACCELRANGE_2G                = (0b00 << 3)
113
+ACCELRANGE_16G               = (0b01 << 3)
114
+ACCELRANGE_4G                = (0b10 << 3)
115
+ACCELRANGE_8G                = (0b11 << 3)
116
+MAGGAIN_4GAUSS               = (0b00 << 5)  # +/- 4 gauss
117
+MAGGAIN_8GAUSS               = (0b01 << 5)  # +/- 8 gauss
118
+MAGGAIN_12GAUSS              = (0b10 << 5)  # +/- 12 gauss
119
+MAGGAIN_16GAUSS              = (0b11 << 5)  # +/- 16 gauss
120
+GYROSCALE_245DPS             = (0b00 << 4)  # +/- 245 degrees/s rotation
121
+GYROSCALE_500DPS             = (0b01 << 4)  # +/- 500 degrees/s rotation
122
+GYROSCALE_2000DPS            = (0b11 << 4)  # +/- 2000 degrees/s rotation
123
+# pylint: enable=bad-whitespace
124
+
125
+
126
+def _twos_comp(val, bits):
127
+    # Convert an unsigned integer in 2's compliment form of the specified bit
128
+    # length to its signed integer value and return it.
129
+    if val & (1 << (bits - 1)) != 0:
130
+        return val - (1 << bits)
131
+    return val
132
+
133
+
134
+class LSM9DS1:
135
+    """Driver for the LSM9DS1 accelerometer, magnetometer, gyroscope."""
136
+
137
+    # Class-level buffer for reading and writing data with the sensor.
138
+    # This reduces memory allocations but means the code is not re-entrant or
139
+    # thread safe!
140
+    _BUFFER = bytearray(6)
141
+
142
+    def __init__(self):
143
+        # soft reset & reboot accel/gyro
144
+        self._write_u8(_XGTYPE, _LSM9DS1_REGISTER_CTRL_REG8, 0x05)
145
+        # soft reset & reboot magnetometer
146
+        self._write_u8(_MAGTYPE, _LSM9DS1_REGISTER_CTRL_REG2_M, 0x0C)
147
+        time.sleep(0.01)
148
+        # Check ID registers.
149
+        if self._read_u8(_XGTYPE, _LSM9DS1_REGISTER_WHO_AM_I_XG) != _LSM9DS1_XG_ID or \
150
+           self._read_u8(_MAGTYPE, _LSM9DS1_REGISTER_WHO_AM_I_M) != _LSM9DS1_MAG_ID:
151
+            raise RuntimeError('Could not find LSM9DS1, check wiring!')
152
+        # enable gyro continuous
153
+        self._write_u8(_XGTYPE, _LSM9DS1_REGISTER_CTRL_REG1_G, 0xC0) # on XYZ
154
+        # Enable the accelerometer continous
155
+        self._write_u8(_XGTYPE, _LSM9DS1_REGISTER_CTRL_REG5_XL, 0x38)
156
+        self._write_u8(_XGTYPE, _LSM9DS1_REGISTER_CTRL_REG6_XL, 0xC0)
157
+        # enable mag continuous
158
+        self._write_u8(_MAGTYPE, _LSM9DS1_REGISTER_CTRL_REG3_M, 0x00)
159
+        # Set default ranges for the various sensors
160
+        self._accel_mg_lsb = None
161
+        self._mag_mgauss_lsb = None
162
+        self._gyro_dps_digit = None
163
+        self.accel_range = ACCELRANGE_2G
164
+        self.mag_gain = MAGGAIN_4GAUSS
165
+        self.gyro_scale = GYROSCALE_245DPS
166
+
167
+    @property
168
+    def accel_range(self):
169
+        """Get and set the accelerometer range.  Must be a value of:
170
+          - ACCELRANGE_2G
171
+          - ACCELRANGE_4G
172
+          - ACCELRANGE_8G
173
+          - ACCELRANGE_16G
174
+        """
175
+        reg = self._read_u8(_XGTYPE, _LSM9DS1_REGISTER_CTRL_REG6_XL)
176
+        return (reg & 0b00011000) & 0xFF
177
+
178
+    @accel_range.setter
179
+    def accel_range(self, val):
180
+        assert val in (ACCELRANGE_2G, ACCELRANGE_4G, ACCELRANGE_8G,
181
+                       ACCELRANGE_16G)
182
+        reg = self._read_u8(_XGTYPE, _LSM9DS1_REGISTER_CTRL_REG6_XL)
183
+        reg = (reg & ~(0b00011000)) & 0xFF
184
+        reg |= val
185
+        self._write_u8(_XGTYPE, _LSM9DS1_REGISTER_CTRL_REG6_XL, reg)
186
+        if val == ACCELRANGE_2G:
187
+            self._accel_mg_lsb = _LSM9DS1_ACCEL_MG_LSB_2G
188
+        elif val == ACCELRANGE_4G:
189
+            self._accel_mg_lsb = _LSM9DS1_ACCEL_MG_LSB_4G
190
+        elif val == ACCELRANGE_8G:
191
+            self._accel_mg_lsb = _LSM9DS1_ACCEL_MG_LSB_8G
192
+        elif val == ACCELRANGE_16G:
193
+            self._accel_mg_lsb = _LSM9DS1_ACCEL_MG_LSB_16G
194
+
195
+    @property
196
+    def mag_gain(self):
197
+        """Get and set the magnetometer gain.  Must be a value of:
198
+          - MAGGAIN_4GAUSS
199
+          - MAGGAIN_8GAUSS
200
+          - MAGGAIN_12GAUSS
201
+          - MAGGAIN_16GAUSS
202
+        """
203
+        reg = self._read_u8(_MAGTYPE, _LSM9DS1_REGISTER_CTRL_REG2_M)
204
+        return (reg & 0b01100000) & 0xFF
205
+
206
+    @mag_gain.setter
207
+    def mag_gain(self, val):
208
+        assert val in (MAGGAIN_4GAUSS, MAGGAIN_8GAUSS, MAGGAIN_12GAUSS,
209
+                       MAGGAIN_16GAUSS)
210
+        reg = self._read_u8(_MAGTYPE, _LSM9DS1_REGISTER_CTRL_REG2_M)
211
+        reg = (reg & ~(0b01100000)) & 0xFF
212
+        reg |= val
213
+        self._write_u8(_MAGTYPE, _LSM9DS1_REGISTER_CTRL_REG2_M, reg)
214
+        if val == MAGGAIN_4GAUSS:
215
+            self._mag_mgauss_lsb = _LSM9DS1_MAG_MGAUSS_4GAUSS
216
+        elif val == MAGGAIN_8GAUSS:
217
+            self._mag_mgauss_lsb = _LSM9DS1_MAG_MGAUSS_8GAUSS
218
+        elif val == MAGGAIN_12GAUSS:
219
+            self._mag_mgauss_lsb = _LSM9DS1_MAG_MGAUSS_12GAUSS
220
+        elif val == MAGGAIN_16GAUSS:
221
+            self._mag_mgauss_lsb = _LSM9DS1_MAG_MGAUSS_16GAUSS
222
+
223
+    @property
224
+    def gyro_scale(self):
225
+        """Get and set the gyroscope scale.  Must be a value of:
226
+          - GYROSCALE_245DPS
227
+          - GYROSCALE_500DPS
228
+          - GYROSCALE_2000DPS
229
+        """
230
+        reg = self._read_u8(_XGTYPE, _LSM9DS1_REGISTER_CTRL_REG1_G)
231
+        return (reg & 0b00110000) & 0xFF
232
+
233
+    @gyro_scale.setter
234
+    def gyro_scale(self, val):
235
+        assert val in (GYROSCALE_245DPS, GYROSCALE_500DPS, GYROSCALE_2000DPS)
236
+        reg = self._read_u8(_XGTYPE, _LSM9DS1_REGISTER_CTRL_REG1_G)
237
+        reg = (reg & ~(0b00110000)) & 0xFF
238
+        reg |= val
239
+        self._write_u8(_XGTYPE, _LSM9DS1_REGISTER_CTRL_REG1_G, reg)
240
+        if val == GYROSCALE_245DPS:
241
+            self._gyro_dps_digit = _LSM9DS1_GYRO_DPS_DIGIT_245DPS
242
+        elif val == GYROSCALE_500DPS:
243
+            self._gyro_dps_digit = _LSM9DS1_GYRO_DPS_DIGIT_500DPS
244
+        elif val == GYROSCALE_2000DPS:
245
+            self._gyro_dps_digit = _LSM9DS1_GYRO_DPS_DIGIT_2000DPS
246
+
247
+    def read_accel_raw(self):
248
+        """Read the raw accelerometer sensor values and return it as a
249
+        3-tuple of X, Y, Z axis values that are 16-bit unsigned values.  If you
250
+        want the acceleration in nice units you probably want to use the
251
+        accelerometer property!
252
+        """
253
+        # Read the accelerometer
254
+        self._read_bytes(_XGTYPE, 0x80 | _LSM9DS1_REGISTER_OUT_X_L_XL, 6,
255
+                         self._BUFFER)
256
+        raw_x, raw_y, raw_z = struct.unpack_from('<hhh', self._BUFFER[0:6])
257
+        return (raw_x, raw_y, raw_z)
258
+
259
+    @property
260
+    def accelerometer(self):
261
+        """Get the accelerometer X, Y, Z axis values as a 3-tuple of
262
+        m/s^2 values.
263
+        """
264
+        raw = self.read_accel_raw()
265
+        return map(lambda x: x * self._accel_mg_lsb / 1000.0 * _SENSORS_GRAVITY_STANDARD,
266
+                   raw)
267
+
268
+    def read_mag_raw(self):
269
+        """Read the raw magnetometer sensor values and return it as a
270
+        3-tuple of X, Y, Z axis values that are 16-bit unsigned values.  If you
271
+        want the magnetometer in nice units you probably want to use the
272
+        magnetometer property!
273
+        """
274
+        # Read the magnetometer
275
+        self._read_bytes(_MAGTYPE, 0x80 | _LSM9DS1_REGISTER_OUT_X_L_M, 6,
276
+                         self._BUFFER)
277
+        raw_x, raw_y, raw_z = struct.unpack_from('<hhh', self._BUFFER[0:6])
278
+        return (raw_x, raw_y, raw_z)
279
+
280
+    @property
281
+    def magnetometer(self):
282
+        """Get the magnetometer X, Y, Z axis values as a 3-tuple of
283
+        gauss values.
284
+        """
285
+        raw = self.read_mag_raw()
286
+        return map(lambda x: x * self._mag_mgauss_lsb / 1000.0, raw)
287
+
288
+    def read_gyro_raw(self):
289
+        """Read the raw gyroscope sensor values and return it as a
290
+        3-tuple of X, Y, Z axis values that are 16-bit unsigned values.  If you
291
+        want the gyroscope in nice units you probably want to use the
292
+        gyroscope property!
293
+        """
294
+        # Read the gyroscope
295
+        self._read_bytes(_XGTYPE, 0x80 | _LSM9DS1_REGISTER_OUT_X_L_G, 6,
296
+                         self._BUFFER)
297
+        raw_x, raw_y, raw_z = struct.unpack_from('<hhh', self._BUFFER[0:6])
298
+        return (raw_x, raw_y, raw_z)
299
+
300
+    @property
301
+    def gyroscope(self):
302
+        """Get the gyroscope X, Y, Z axis values as a 3-tuple of
303
+        degrees/second values.
304
+        """
305
+        raw = self.read_mag_raw()
306
+        return map(lambda x: x * self._gyro_dps_digit, raw)
307
+
308
+    def read_temp_raw(self):
309
+        """Read the raw temperature sensor value and return it as a 12-bit
310
+        signed value.  If you want the temperature in nice units you probably
311
+        want to use the temperature property!
312
+        """
313
+        # Read temp sensor
314
+        self._read_bytes(_XGTYPE, 0x80 | _LSM9DS1_REGISTER_TEMP_OUT_L, 2,
315
+                         self._BUFFER)
316
+        temp = ((self._BUFFER[1] << 8) | self._BUFFER[0]) >> 4
317
+        return _twos_comp(temp, 12)
318
+
319
+    @property
320
+    def temperature(self):
321
+        """Get the temperature of the sensor in degrees Celsius."""
322
+        # This is just a guess since the starting point (21C here) isn't documented :(
323
+        # See discussion from:
324
+        #  https://github.com/kriswiner/LSM9DS1/issues/3
325
+        temp = self.read_temp_raw()
326
+        temp = 27.5 + temp/16
327
+        return temp
328
+
329
+    def _read_u8(self, sensor_type, address):
330
+        # Read an 8-bit unsigned value from the specified 8-bit address.
331
+        # The sensor_type boolean should be _MAGTYPE when talking to the
332
+        # magnetometer, or _XGTYPE when talking to the accel or gyro.
333
+        # MUST be implemented by subclasses!
334
+        raise NotImplementedError()
335
+
336
+    def _read_bytes(self, sensor_type, address, count, buf):
337
+        # Read a count number of bytes into buffer from the provided 8-bit
338
+        # register address.  The sensor_type boolean should be _MAGTYPE when
339
+        # talking to the magnetometer, or _XGTYPE when talking to the accel or
340
+        # gyro.  MUST be implemented by subclasses!
341
+        raise NotImplementedError()
342
+
343
+    def _write_u8(self, sensor_type, address, val):
344
+        # Write an 8-bit unsigned value to the specified 8-bit address.
345
+        # The sensor_type boolean should be _MAGTYPE when talking to the
346
+        # magnetometer, or _XGTYPE when talking to the accel or gyro.
347
+        # MUST be implemented by subclasses!
348
+        raise NotImplementedError()
349
+
350
+
351
+class LSM9DS1_I2C(LSM9DS1):
352
+    """Driver for the LSM9DS1 connect over I2C."""
353
+
354
+    def __init__(self, i2c):
355
+        self._mag_device = i2c_device.I2CDevice(i2c, _LSM9DS1_ADDRESS_MAG)
356
+        self._xg_device = i2c_device.I2CDevice(i2c, _LSM9DS1_ADDRESS_ACCELGYRO)
357
+        super().__init__()
358
+
359
+    def _read_u8(self, sensor_type, address):
360
+        if sensor_type == _MAGTYPE:
361
+            device = self._mag_device
362
+        else:
363
+            device = self._xg_device
364
+        with device as i2c:
365
+            self._BUFFER[0] = address & 0xFF
366
+            i2c.write(self._BUFFER, end=1, stop=False)
367
+            i2c.readinto(self._BUFFER, end=1)
368
+        return self._BUFFER[0]
369
+
370
+    def _read_bytes(self, sensor_type, address, count, buf):
371
+        if sensor_type == _MAGTYPE:
372
+            device = self._mag_device
373
+        else:
374
+            device = self._xg_device
375
+        with device as i2c:
376
+            buf[0] = address & 0xFF
377
+            i2c.write(buf, end=1, stop=False)
378
+            i2c.readinto(buf, end=count)
379
+
380
+    def _write_u8(self, sensor_type, address, val):
381
+        if sensor_type == _MAGTYPE:
382
+            device = self._mag_device
383
+        else:
384
+            device = self._xg_device
385
+        with device as i2c:
386
+            self._BUFFER[0] = address & 0xFF
387
+            self._BUFFER[1] = val & 0xFF
388
+            i2c.write(self._BUFFER, end=2)
389
+
390
+
391
+class LSM9DS1_SPI(LSM9DS1):
392
+    """Driver for the LSM9DS1 connect over SPI."""
393
+
394
+    def __init__(self, spi, xgcs, mcs):
395
+        self._mag_device = spi_device.I2CDevice(spi, mcs)
396
+        self._xg_device = spi_device.I2CDevice(spi, xgcs)
397
+        super().__init__()
398
+
399
+    def _read_u8(self, sensor_type, address):
400
+        if sensor_type == _MAGTYPE:
401
+            device = self._mag_device
402
+        else:
403
+            device = self._xg_device
404
+        with device as spi:
405
+            spi.configure(baudrate=200000, phase=0, polarity=0)
406
+            self._BUFFER[0] = (address | 0x80) & 0xFF
407
+            spi.write(self._BUFFER, end=1)
408
+            spi.readinto(self._BUFFER, end=1)
409
+        return self._BUFFER[0]
410
+
411
+    def _read_bytes(self, sensor_type, address, count, buf):
412
+        if sensor_type == _MAGTYPE:
413
+            device = self._mag_device
414
+        else:
415
+            device = self._xg_device
416
+        with device as spi:
417
+            spi.configure(baudrate=200000, phase=0, polarity=0)
418
+            buf[0] = (address | 0x80) & 0xFF
419
+            spi.write(buf, end=1)
420
+            spi.readinto(buf, end=count)
421
+
422
+    def _write_u8(self, sensor_type, address, val):
423
+        if sensor_type == _MAGTYPE:
424
+            device = self._mag_device
425
+        else:
426
+            device = self._xg_device
427
+        with device as spi:
428
+            spi.configure(baudrate=200000, phase=0, polarity=0)
429
+            self._BUFFER[0] = (address & 0x7F) & 0xFF
430
+            self._BUFFER[1] = val & 0xFF
431
+            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 @@
1
+# The MIT License (MIT)
2
+#
3
+# Copyright (c) 2016 Scott Shawcroft for Adafruit Industries
4
+#
5
+# Permission is hereby granted, free of charge, to any person obtaining a copy
6
+# of this software and associated documentation files (the "Software"), to deal
7
+# in the Software without restriction, including without limitation the rights
8
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+# copies of the Software, and to permit persons to whom the Software is
10
+# furnished to do so, subject to the following conditions:
11
+#
12
+# The above copyright notice and this permission notice shall be included in
13
+# all copies or substantial portions of the Software.
14
+#
15
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+# THE SOFTWARE.
22
+# pylint: disable=too-few-public-methods
23
+
24
+"""
25
+`adafruit_register.i2c_bcd_alarm`
26
+====================================================
27
+
28
+Binary Coded Decimal alarm register
29
+
30
+* Author(s): Scott Shawcroft
31
+"""
32
+
33
+__version__ = "1.3.1"
34
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git"
35
+
36
+import time
37
+
38
+def _bcd2bin(value):
39
+    """Convert binary coded decimal to Binary
40
+
41
+    :param value: the BCD value to convert to binary (required, no default)
42
+    """
43
+    return value - 6 * (value >> 4)
44
+
45
+
46
+def _bin2bcd(value):
47
+    """Convert a binary value to binary coded decimal.
48
+
49
+    :param value: the binary value to convert to BCD. (required, no default)
50
+    """
51
+    return value + 6 * (value // 10)
52
+
53
+ALARM_COMPONENT_DISABLED = 0x80
54
+FREQUENCY = ["secondly", "minutely", "hourly", "daily", "weekly", "monthly"]
55
+
56
+class BCDAlarmTimeRegister:
57
+    """
58
+    Alarm date and time register using binary coded decimal structure.
59
+
60
+    The byte order of the registers must* be: [second], minute, hour, day,
61
+    weekday. Each byte must also have a high enable bit where 1 is disabled and
62
+    0 is enabled.
63
+
64
+    * If weekday_shared is True, then weekday and day share a register.
65
+    * If has_seconds is True, then there is a seconds register.
66
+
67
+    Values are a tuple of (`time.struct_time`, `str`) where the struct represents
68
+    a date and time that would alarm. The string is the frequency:
69
+
70
+    * "secondly", once a second (only if alarm has_seconds)
71
+    * "minutely", once a minute when seconds match (if alarm doesn't seconds then when seconds = 0)
72
+    * "hourly", once an hour when ``tm_min`` and ``tm_sec`` match
73
+    * "daily", once a day when ``tm_hour``, ``tm_min`` and ``tm_sec`` match
74
+    * "weekly", once a week when ``tm_wday``, ``tm_hour``, ``tm_min``, ``tm_sec`` match
75
+    * "monthly", once a month when ``tm_mday``, ``tm_hour``, ``tm_min``, ``tm_sec`` match
76
+
77
+    :param int register_address: The register address to start the read
78
+    :param bool has_seconds: True if the alarm can happen minutely.
79
+    :param bool weekday_shared: True if weekday and day share the same register
80
+    :param int weekday_start: 0 or 1 depending on the RTC's representation of the first day of the
81
+      week (Monday)
82
+    """
83
+    # Defaults are based on alarm1 of the DS3231.
84
+    def __init__(self, register_address, has_seconds=True, weekday_shared=True, weekday_start=1):
85
+        buffer_size = 5
86
+        if weekday_shared:
87
+            buffer_size -= 1
88
+        if has_seconds:
89
+            buffer_size += 1
90
+        self.has_seconds = has_seconds
91
+        self.buffer = bytearray(buffer_size)
92
+        self.buffer[0] = register_address
93
+        self.weekday_shared = weekday_shared
94
+        self.weekday_start = weekday_start
95
+
96
+    def __get__(self, obj, objtype=None):
97
+        # Read the alarm register.
98
+        with obj.i2c_device:
99
+            obj.i2c_device.write(self.buffer, end=1, stop=False)
100
+            obj.i2c_device.readinto(self.buffer, start=1)
101
+
102
+        frequency = None
103
+        i = 1
104
+        seconds = 0
105
+        if self.has_seconds:
106
+            if (self.buffer[1] & 0x80) != 0:
107
+                frequency = "secondly"
108
+            else:
109
+                frequency = "minutely"
110
+                seconds = _bcd2bin(self.buffer[1] & 0x7f)
111
+            i = 2
112
+        minute = 0
113
+        if (self.buffer[i] & 0x80) == 0:
114
+            frequency = "hourly"
115
+            minute = _bcd2bin(self.buffer[i] & 0x7f)
116
+
117
+        hour = 0
118
+        if (self.buffer[i + 1] & 0x80) == 0:
119
+            frequency = "daily"
120
+            hour = _bcd2bin(self.buffer[i + 1] & 0x7f)
121
+
122
+        mday = None
123
+        wday = None
124
+        if (self.buffer[i + 2] & 0x80) == 0:
125
+            # day of the month
126
+            if not self.weekday_shared or (self.buffer[i + 2] & 0x40) == 0:
127
+                frequency = "monthly"
128
+                mday = _bcd2bin(self.buffer[i + 2] & 0x3f)
129
+            else: # weekday
130
+                frequency = "weekly"
131
+                wday = _bcd2bin(self.buffer[i + 2] & 0x3f) - self.weekday_start
132
+
133
+        # weekday
134
+        if not self.weekday_shared and (self.buffer[i + 3] & 0x80) == 0:
135
+            frequency = "monthly"
136
+            mday = _bcd2bin(self.buffer[i + 3] & 0x7f)
137
+
138
+        if mday is not None:
139
+            wday = (mday - 2) % 7
140
+        elif wday is not None:
141
+            mday = wday + 2
142
+        else:
143
+            # Jan 1, 2017 was a Sunday (6)
144
+            wday = 6
145
+            mday = 1
146
+
147
+        return (time.struct_time((2017, 1, mday, hour, minute, seconds, wday, mday, -1)), frequency)
148
+
149
+    def __set__(self, obj, value):
150
+        if len(value) != 2:
151
+            raise ValueError("Value must be sequence of length two")
152
+        # Turn all components off by default.
153
+        for i in range(len(self.buffer) - 1):
154
+            self.buffer[i + 1] = ALARM_COMPONENT_DISABLED
155
+        frequency_name = value[1]
156
+        error_message = "%s is not a supported frequency" % frequency_name
157
+        if frequency_name not in FREQUENCY:
158
+            raise ValueError(error_message)
159
+
160
+        frequency = FREQUENCY.index(frequency_name)
161
+        if frequency <= 1 and not self.has_seconds:
162
+            raise ValueError(error_message)
163
+
164
+        # i is the index of the minute byte
165
+        i = 2 if self.has_seconds else 1
166
+
167
+        if frequency > 0 and self.has_seconds: # minutely at least
168
+            self.buffer[1] = _bin2bcd(value[0].tm_sec)
169
+
170
+        if frequency > 1: # hourly at least
171
+            self.buffer[i] = _bin2bcd(value[0].tm_min)
172
+
173
+        if frequency > 2: # daily at least
174
+            self.buffer[i + 1] = _bin2bcd(value[0].tm_hour)
175
+
176
+        if value[1] == "weekly":
177
+            if self.weekday_shared:
178
+                self.buffer[i + 2] = _bin2bcd(value[0].tm_wday + self.weekday_start) | 0x40
179
+            else:
180
+                self.buffer[i + 3] = _bin2bcd(value[0].tm_wday + self.weekday_start)
181
+        elif value[1] == "monthly":
182
+            self.buffer[i + 2] = _bin2bcd(value[0].tm_mday)
183
+
184
+        with obj.i2c_device:
185
+            obj.i2c_device.write(self.buffer)

+ 103
- 0
lib/adafruit_register/i2c_bcd_datetime.py View File

@@ -0,0 +1,103 @@
1
+# The MIT License (MIT)
2
+#
3
+# Copyright (c) 2016 Scott Shawcroft for Adafruit Industries
4
+#
5
+# Permission is hereby granted, free of charge, to any person obtaining a copy
6
+# of this software and associated documentation files (the "Software"), to deal
7
+# in the Software without restriction, including without limitation the rights
8
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+# copies of the Software, and to permit persons to whom the Software is
10
+# furnished to do so, subject to the following conditions:
11
+#
12
+# The above copyright notice and this permission notice shall be included in
13
+# all copies or substantial portions of the Software.
14
+#
15
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+# THE SOFTWARE.
22
+# pylint: disable=too-few-public-methods
23
+"""
24
+`adafruit_register.i2c_bcd_datetime`
25
+====================================================
26
+
27
+Binary Coded Decimal date and time register
28
+
29
+* Author(s): Scott Shawcroft
30
+"""
31
+
32
+__version__ = "1.3.1"
33
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git"
34
+
35
+import time
36
+
37
+def _bcd2bin(value):
38
+    """Convert binary coded decimal to Binary
39
+
40
+    :param value: the BCD value to convert to binary (required, no default)
41
+    """
42
+    return value - 6 * (value >> 4)
43
+
44
+
45
+def _bin2bcd(value):
46
+    """Convert a binary value to binary coded decimal.
47
+
48
+    :param value: the binary value to convert to BCD. (required, no default)
49
+    """
50
+    return value + 6 * (value // 10)
51
+
52
+class BCDDateTimeRegister:
53
+    """
54
+    Date and time register using binary coded decimal structure.
55
+
56
+    The byte order of the register must* be: second, minute, hour, weekday, day (1-31), month, year
57
+    (in years after 2000).
58
+
59
+    * Setting weekday_first=False will flip the weekday/day order so that day comes first.
60
+
61
+    Values are `time.struct_time`
62
+
63
+    :param int register_address: The register address to start the read
64
+    :param bool weekday_first: True if weekday is in a lower register than the day of the month
65
+        (1-31)
66
+    :param int weekday_start: 0 or 1 depending on the RTC's representation of the first day of the
67
+        week
68
+    """
69
+    def __init__(self, register_address, weekday_first=True, weekday_start=1):
70
+        self.buffer = bytearray(8)
71
+        self.buffer[0] = register_address
72
+        if weekday_first:
73
+            self.weekday_offset = 0
74
+        else:
75
+            self.weekday_offset = 1
76
+        self.weekday_start = weekday_start
77
+
78
+    def __get__(self, obj, objtype=None):
79
+        # Read and return the date and time.
80
+        with obj.i2c_device:
81
+            obj.i2c_device.write(self.buffer, end=1, stop=False)
82
+            obj.i2c_device.readinto(self.buffer, start=1)
83
+        return time.struct_time((_bcd2bin(self.buffer[7]) + 2000,
84
+                                 _bcd2bin(self.buffer[6]),
85
+                                 _bcd2bin(self.buffer[5 - self.weekday_offset]),
86
+                                 _bcd2bin(self.buffer[3]),
87
+                                 _bcd2bin(self.buffer[2]),
88
+                                 _bcd2bin(self.buffer[1] & 0x7F),
89
+                                 _bcd2bin(self.buffer[4 + self.weekday_offset] -
90
+                                          self.weekday_start),
91
+                                 -1,
92
+                                 -1))
93
+
94
+    def __set__(self, obj, value):
95
+        self.buffer[1] = _bin2bcd(value.tm_sec) & 0x7F   # format conversions
96
+        self.buffer[2] = _bin2bcd(value.tm_min)
97
+        self.buffer[3] = _bin2bcd(value.tm_hour)
98
+        self.buffer[4 + self.weekday_offset] = _bin2bcd(value.tm_wday + self.weekday_start)
99
+        self.buffer[5 - self.weekday_offset] = _bin2bcd(value.tm_mday)
100
+        self.buffer[6] = _bin2bcd(value.tm_mon)
101
+        self.buffer[7] = _bin2bcd(value.tm_year - 2000)
102
+        with obj.i2c_device:
103
+            obj.i2c_device.write(self.buffer)

+ 73
- 0
lib/adafruit_register/i2c_bit.py View File

@@ -0,0 +1,73 @@
1
+# The MIT License (MIT)
2
+#
3
+# Copyright (c) 2016 Scott Shawcroft for Adafruit Industries
4
+#
5
+# Permission is hereby granted, free of charge, to any person obtaining a copy
6
+# of this software and associated documentation files (the "Software"), to deal
7
+# in the Software without restriction, including without limitation the rights
8
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+# copies of the Software, and to permit persons to whom the Software is
10
+# furnished to do so, subject to the following conditions:
11
+#
12
+# The above copyright notice and this permission notice shall be included in
13
+# all copies or substantial portions of the Software.
14
+#
15
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+# THE SOFTWARE.
22
+# pylint: disable=too-few-public-methods
23
+"""
24
+`adafruit_register.i2c_bit`
25
+====================================================
26
+
27
+Single bit registers
28
+
29
+* Author(s): Scott Shawcroft
30
+"""
31
+
32
+__version__ = "1.3.1"
33
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git"
34
+
35
+class RWBit:
36
+    """
37
+    Single bit register that is readable and writeable.
38
+
39
+    Values are `bool`
40
+
41
+    :param int register_address: The register address to read the bit from
42
+    :param type bit: The bit index within the byte at ``register_address``
43
+    """
44
+    def __init__(self, register_address, bit):
45
+        self.bit_mask = 1 << bit
46
+        self.buffer = bytearray(2)
47
+        self.buffer[0] = register_address
48
+
49
+    def __get__(self, obj, objtype=None):
50
+        with obj.i2c_device as i2c:
51
+            i2c.write(self.buffer, end=1, stop=False)
52
+            i2c.readinto(self.buffer, start=1)
53
+        return bool(self.buffer[1] & self.bit_mask)
54
+
55
+    def __set__(self, obj, value):
56
+        with obj.i2c_device as i2c:
57
+            i2c.write(self.buffer, end=1, stop=False)
58
+            i2c.readinto(self.buffer, start=1)
59
+            if value:
60
+                self.buffer[1] |= self.bit_mask
61
+            else:
62
+                self.buffer[1] &= ~self.bit_mask
63
+            i2c.write(self.buffer)
64
+
65
+class ROBit(RWBit):
66
+    """Single bit register that is read only. Subclass of `RWBit`.
67
+
68
+    Values are `bool`
69
+
70
+    :param int register_address: The register address to read the bit from
71
+    :param type bit: The bit index within the byte at ``register_address``"""
72
+    def __set__(self, obj, value):
73
+        raise AttributeError()

+ 88
- 0
lib/adafruit_register/i2c_bits.py View File

@@ -0,0 +1,88 @@
1
+# The MIT License (MIT)
2
+#
3
+# Copyright (c) 2016 Scott Shawcroft for Adafruit Industries
4
+#
5
+# Permission is hereby granted, free of charge, to any person obtaining a copy
6
+# of this software and associated documentation files (the "Software"), to deal
7
+# in the Software without restriction, including without limitation the rights
8
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+# copies of the Software, and to permit persons to whom the Software is
10
+# furnished to do so, subject to the following conditions:
11
+#
12
+# The above copyright notice and this permission notice shall be included in
13
+# all copies or substantial portions of the Software.
14
+#
15
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+# THE SOFTWARE.
22
+# pylint: disable=too-few-public-methods
23
+"""
24
+`adafruit_register.i2c_bits`
25
+====================================================
26
+
27
+Multi bit registers
28
+
29
+* Author(s): Scott Shawcroft
30
+"""
31
+
32
+__version__ = "1.3.1"
33
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git"
34
+
35
+class RWBits:
36
+    """
37
+    Multibit register (less than a full byte) that is readable and writeable.
38
+    This must be within a byte register.
39
+
40
+    Values are `int` between 0 and 2 ** ``num_bits`` - 1.
41
+
42
+    :param int num_bits: The number of bits in the field.
43
+    :param int register_address: The register address to read the bit from
44
+    :param type lowest_bit: The lowest bits index within the byte at ``register_address``
45
+    """
46
+    def __init__(self, num_bits, register_address, lowest_bit):
47
+        self.bit_mask = 0
48
+        for _ in range(num_bits):
49
+            self.bit_mask = (self.bit_mask << 1) + 1
50
+        self.bit_mask = self.bit_mask << lowest_bit
51
+        if self.bit_mask >= (1 << 8):
52
+            raise ValueError()
53
+        self.buffer = bytearray(2)
54
+        self.buffer[0] = register_address
55
+        self.lowest_bit = lowest_bit
56
+
57
+    def __get__(self, obj, objtype=None):
58
+        with obj.i2c_device as i2c:
59
+            i2c.write(self.buffer, end=1, stop=False)
60
+            i2c.readinto(self.buffer, start=1)
61
+        return (self.buffer[1] & self.bit_mask) >> self.lowest_bit
62
+
63
+    def __set__(self, obj, value):
64
+        # Shift the value to the appropriate spot and set all bits that aren't
65
+        # ours to 1 (the negation of the bitmask.)
66
+        value = (value << self.lowest_bit) | ~self.bit_mask
67
+        with obj.i2c_device as i2c:
68
+            i2c.write(self.buffer, end=1, stop=False)
69
+            i2c.readinto(self.buffer, start=1)
70
+            # Set all of our bits to 1.
71
+            self.buffer[1] |= self.bit_mask
72
+            # Set all 0 bits to 0 by anding together.
73
+            self.buffer[1] &= value
74
+            i2c.write(self.buffer)
75
+
76
+class ROBits(RWBits):
77
+    """
78
+    Multibit register (less than a full byte) that is read-only. This must be
79
+    within a byte register.
80
+
81
+    Values are `int` between 0 and 2 ** ``num_bits`` - 1.
82
+
83
+    :param int num_bits: The number of bits in the field.
84
+    :param int register_address: The register address to read the bit from
85
+    :param type lowest_bit: The lowest bits index within the byte at ``register_address``
86
+    """
87
+    def __set__(self, obj, value):
88
+        raise AttributeError()

+ 93
- 0
lib/adafruit_register/i2c_struct.py View File

@@ -0,0 +1,93 @@
1
+# The MIT License (MIT)
2
+#
3
+# Copyright (c) 2016 Adafruit Industries
4
+#
5
+# Permission is hereby granted, free of charge, to any person obtaining a copy
6
+# of this software and associated documentation files (the "Software"), to deal
7
+# in the Software without restriction, including without limitation the rights
8
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+# copies of the Software, and to permit persons to whom the Software is
10
+# furnished to do so, subject to the following conditions:
11
+#
12
+# The above copyright notice and this permission notice shall be included in
13
+# all copies or substantial portions of the Software.
14
+#
15
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+# THE SOFTWARE.
22
+# pylint: disable=too-few-public-methods
23
+"""
24
+`adafruit_register.i2c_struct`
25
+====================================================
26
+
27
+Generic structured registers based on `struct`
28
+
29
+* Author(s): Scott Shawcroft
30
+"""
31
+
32
+try:
33
+    import struct
34
+except ImportError:
35
+    import ustruct as struct
36
+
37
+__version__ = "1.3.1"
38
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git"
39
+
40
+class Struct:
41
+    """
42
+    Arbitrary structure register that is readable and writeable.
43
+
44
+    Values are tuples that map to the values in the defined struct.  See struct
45
+    module documentation for struct format string and its possible value types.
46
+
47
+    :param int register_address: The register address to read the bit from
48
+    :param type struct_format: The struct format string for this register.
49
+    """
50
+    def __init__(self, register_address, struct_format):
51
+        self.format = struct_format
52
+        self.buffer = bytearray(1+struct.calcsize(self.format))
53
+        self.buffer[0] = register_address
54
+
55
+    def __get__(self, obj, objtype=None):
56
+        with obj.i2c_device:
57
+            obj.i2c_device.write(self.buffer, end=1, stop=False)
58
+            obj.i2c_device.readinto(self.buffer, start=1)
59
+        return struct.unpack_from(self.format, memoryview(self.buffer)[1:])
60
+
61
+    def __set__(self, obj, value):
62
+        struct.pack_into(self.format, self.buffer, 1, *value)
63
+        with obj.i2c_device:
64
+            obj.i2c_device.write(self.buffer)
65
+
66
+class UnaryStruct:
67
+    """
68
+    Arbitrary single value structure register that is readable and writeable.
69
+
70
+    Values map to the first value in the defined struct.  See struct
71
+    module documentation for struct format string and its possible value types.
72
+
73
+    :param int register_address: The register address to read the bit from
74
+    :param type struct_format: The struct format string for this register.
75
+    """
76
+    def __init__(self, register_address, struct_format):
77
+        self.format = struct_format
78
+        self.address = register_address
79
+
80
+    def __get__(self, obj, objtype=None):
81
+        buf = bytearray(1+struct.calcsize(self.format))
82
+        buf[0] = self.address
83
+        with obj.i2c_device:
84
+            obj.i2c_device.write(buf, end=1, stop=False)
85
+            obj.i2c_device.readinto(buf, start=1)
86
+        return struct.unpack_from(self.format, buf, 1)[0]
87
+
88
+    def __set__(self, obj, value):
89
+        buf = bytearray(1+struct.calcsize(self.format))
90
+        buf[0] = self.address
91
+        struct.pack_into(self.format, buf, 1, value)
92
+        with obj.i2c_device:
93
+            obj.i2c_device.write(buf)

+ 112
- 0
lib/adafruit_register/i2c_struct_array.py View File

@@ -0,0 +1,112 @@
1
+# The MIT License (MIT)
2
+#
3
+# Copyright (c) 2017 Scott Shawcroft for Adafruit Industries
4
+#
5
+# Permission is hereby granted, free of charge, to any person obtaining a copy
6
+# of this software and associated documentation files (the "Software"), to deal
7
+# in the Software without restriction, including without limitation the rights
8
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+# copies of the Software, and to permit persons to whom the Software is
10
+# furnished to do so, subject to the following conditions:
11
+#
12
+# The above copyright notice and this permission notice shall be included in
13
+# all copies or substantial portions of the Software.
14
+#
15
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+# THE SOFTWARE.
22
+# pylint: disable=too-few-public-methods
23
+"""
24
+`adafruit_register.i2c_struct_array`
25
+====================================================
26
+
27
+Array of structured registers based on `struct`
28
+
29
+* Author(s): Scott Shawcroft
30
+"""
31
+
32
+try:
33
+    import struct
34
+except ImportError:
35
+    import ustruct as struct
36
+
37
+__version__ = "1.3.1"
38
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git"
39
+
40
+class _BoundStructArray:
41
+    """
42
+    Array object that `StructArray` constructs on demand.
43
+
44
+    :param object obj: The device object to bind to. It must have a `i2c_device` attribute
45
+    :param int register_address: The register address to read the bit from
46
+    :param type struct_format: The struct format string for each register element
47
+    :param int count: Number of elements in the array
48
+    """
49
+    def __init__(self, obj, register_address, struct_format, count):
50
+        self.format = struct_format
51
+        self.first_register = register_address
52
+        self.obj = obj
53
+        self.count = count
54
+
55
+    def _get_buffer(self, index):
56
+        """Shared bounds checking and buffer creation."""
57
+        if not 0 <= index < self.count:
58
+            raise IndexError()
59
+        size = struct.calcsize(self.format)
60
+        # We create the buffer every time instead of keeping the buffer (which is 32 bytes at least)
61
+        # around forever.
62
+        buf = bytearray(size + 1)
63
+        buf[0] = self.first_register + size * index
64
+        return buf
65
+
66
+    def __getitem__(self, index):
67
+        buf = self._get_buffer(index)
68
+        with self.obj.i2c_device:
69
+            self.obj.i2c_device.write(buf, end=1, stop=False)
70
+            self.obj.i2c_device.readinto(buf, start=1)
71
+        return struct.unpack_from(self.format, buf, 1)  # offset=1
72
+
73
+    def __setitem__(self, index, value):
74
+        buf = self._get_buffer(index)
75
+        struct.pack_into(self.format, buf, 1, *value)
76
+        with self.obj.i2c_device:
77
+            self.obj.i2c_device.write(buf)
78
+
79
+    def __len__(self):
80
+        return self.count
81
+
82
+class StructArray:
83
+    """
84
+    Repeated array of structured registers that are readable and writeable.
85
+
86
+    Based on the index, values are offset by the size of the structure.
87
+
88
+    Values are tuples that map to the values in the defined struct.  See struct
89
+    module documentation for struct format string and its possible value types.
90
+
91
+    .. note:: This assumes the device addresses correspond to 8-bit bytes. This is not suitable for
92
+      devices with registers of other widths such as 16-bit.
93
+
94
+    :param int register_address: The register address to begin reading the array from
95
+    :param str struct_format: The struct format string for this register.
96
+    :param int count: Number of elements in the array
97
+    """
98
+    def __init__(self, register_address, struct_format, count):
99
+        self.format = struct_format
100
+        self.address = register_address
101
+        self.count = count
102
+        self.array_id = "_structarray{}".format(register_address)
103
+
104
+    def __get__(self, obj, objtype=None):
105
+        # We actually can't handle the indexing ourself due to data descriptor limits. So, we return
106
+        # an object that can instead. This object is bound to the object passed in here by its
107
+        # initializer and then cached on the object itself. That way its lifetime is tied to the
108
+        # lifetime of the object itself.
109
+        if not hasattr(obj, self.array_id):
110
+            setattr(obj, self.array_id,
111
+                    _BoundStructArray(obj, self.address, self.format, self.count))
112
+        return getattr(obj, self.array_id)

+ 626
- 0
lib/adafruit_rfm9x.py View File

@@ -0,0 +1,626 @@
1
+# The MIT License (MIT)
2
+#
3
+# Copyright (c) 2017 Tony DiCola for Adafruit Industries
4
+#
5
+# Permission is hereby granted, free of charge, to any person obtaining a copy
6
+# of this software and associated documentation files (the "Software"), to deal
7
+# in the Software without restriction, including without limitation the rights
8
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+# copies of the Software, and to permit persons to whom the Software is
10
+# furnished to do so, subject to the following conditions:
11
+#
12
+# The above copyright notice and this permission notice shall be included in
13
+# all copies or substantial portions of the Software.
14
+#
15
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+# THE SOFTWARE.
22
+"""
23
+`adafruit_rfm9x`
24
+====================================================
25
+
26
+CircuitPython module for the RFM95/6/7/8 LoRa 433/915mhz radio modules.  This is
27
+adapted from the Radiohead library RF95 code from:
28
+http: www.airspayce.com/mikem/arduino/RadioHead/
29
+
30
+* Author(s): Tony DiCola, Jerry Needell
31
+"""
32
+import time
33
+import digitalio
34
+from micropython import const
35
+
36
+import adafruit_bus_device.spi_device as spi_device
37
+
38
+
39
+__version__ = "1.0.3"
40
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_rfm95.git"
41
+
42
+
43
+# pylint: disable=bad-whitespace
44
+# Internal constants:
45
+# Register names (FSK Mode even though we use LoRa instead, from table 85)
46
+_RH_RF95_REG_00_FIFO                              = const(0x00)
47
+_RH_RF95_REG_01_OP_MODE                           = const(0x01)
48
+_RH_RF95_REG_02_RESERVED                          = const(0x02)
49
+_RH_RF95_REG_03_RESERVED                          = const(0x03)
50
+_RH_RF95_REG_04_RESERVED                          = const(0x04)
51
+_RH_RF95_REG_05_RESERVED                          = const(0x05)
52
+_RH_RF95_REG_06_FRF_MSB                           = const(0x06)
53
+_RH_RF95_REG_07_FRF_MID                           = const(0x07)
54
+_RH_RF95_REG_08_FRF_LSB                           = const(0x08)
55
+_RH_RF95_REG_09_PA_CONFIG                         = const(0x09)
56
+_RH_RF95_REG_0A_PA_RAMP                           = const(0x0a)
57
+_RH_RF95_REG_0B_OCP                               = const(0x0b)
58
+_RH_RF95_REG_0C_LNA                               = const(0x0c)
59
+_RH_RF95_REG_0D_FIFO_ADDR_PTR                     = const(0x0d)
60
+_RH_RF95_REG_0E_FIFO_TX_BASE_ADDR                 = const(0x0e)
61
+_RH_RF95_REG_0F_FIFO_RX_BASE_ADDR                 = const(0x0f)
62
+_RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR              = const(0x10)
63
+_RH_RF95_REG_11_IRQ_FLAGS_MASK                    = const(0x11)
64
+_RH_RF95_REG_12_IRQ_FLAGS                         = const(0x12)
65
+_RH_RF95_REG_13_RX_NB_BYTES                       = const(0x13)
66
+_RH_RF95_REG_14_RX_HEADER_CNT_VALUE_MSB           = const(0x14)
67
+_RH_RF95_REG_15_RX_HEADER_CNT_VALUE_LSB           = const(0x15)
68
+_RH_RF95_REG_16_RX_PACKET_CNT_VALUE_MSB           = const(0x16)
69
+_RH_RF95_REG_17_RX_PACKET_CNT_VALUE_LSB           = const(0x17)
70
+_RH_RF95_REG_18_MODEM_STAT                        = const(0x18)
71
+_RH_RF95_REG_19_PKT_SNR_VALUE                     = const(0x19)
72
+_RH_RF95_REG_1A_PKT_RSSI_VALUE                    = const(0x1a)
73
+_RH_RF95_REG_1B_RSSI_VALUE                        = const(0x1b)
74
+_RH_RF95_REG_1C_HOP_CHANNEL                       = const(0x1c)
75
+_RH_RF95_REG_1D_MODEM_CONFIG1                     = const(0x1d)
76
+_RH_RF95_REG_1E_MODEM_CONFIG2                     = const(0x1e)
77
+_RH_RF95_REG_1F_SYMB_TIMEOUT_LSB                  = const(0x1f)
78
+_RH_RF95_REG_20_PREAMBLE_MSB                      = const(0x20)
79
+_RH_RF95_REG_21_PREAMBLE_LSB                      = const(0x21)
80
+_RH_RF95_REG_22_PAYLOAD_LENGTH                    = const(0x22)
81
+_RH_RF95_REG_23_MAX_PAYLOAD_LENGTH                = const(0x23)
82
+_RH_RF95_REG_24_HOP_PERIOD                        = const(0x24)
83
+_RH_RF95_REG_25_FIFO_RX_BYTE_ADDR                 = const(0x25)
84
+_RH_RF95_REG_26_MODEM_CONFIG3                     = const(0x26)
85
+
86
+_RH_RF95_REG_40_DIO_MAPPING1                      = const(0x40)
87
+_RH_RF95_REG_41_DIO_MAPPING2                      = const(0x41)
88
+_RH_RF95_REG_42_VERSION                           = const(0x42)
89
+
90
+_RH_RF95_REG_4B_TCXO                              = const(0x4b)
91
+_RH_RF95_REG_4D_PA_DAC                            = const(0x4d)
92
+_RH_RF95_REG_5B_FORMER_TEMP                       = const(0x5b)
93
+_RH_RF95_REG_61_AGC_REF                           = const(0x61)
94
+_RH_RF95_REG_62_AGC_THRESH1                       = const(0x62)
95
+_RH_RF95_REG_63_AGC_THRESH2                       = const(0x63)
96
+_RH_RF95_REG_64_AGC_THRESH3                       = const(0x64)
97
+
98
+# RH_RF95_REG_01_OP_MODE                             0x01
99
+_RH_RF95_LONG_RANGE_MODE                     = const(0x80)
100
+_RH_RF95_ACCESS_SHARED_REG                   = const(0x40)
101
+_RH_RF95_MODE                                = const(0x07)
102
+_RH_RF95_MODE_SLEEP                          = const(0x00)
103
+_RH_RF95_MODE_STDBY                          = const(0x01)
104
+_RH_RF95_MODE_FSTX                           = const(0x02)
105
+_RH_RF95_MODE_TX                             = const(0x03)
106
+_RH_RF95_MODE_FSRX                           = const(0x04)
107
+_RH_RF95_MODE_RXCONTINUOUS                   = const(0x05)
108
+_RH_RF95_MODE_RXSINGLE                       = const(0x06)
109
+_RH_RF95_MODE_CAD                            = const(0x07)
110
+
111
+# RH_RF95_REG_09_PA_CONFIG                           0x09
112
+_RH_RF95_PA_SELECT                           = const(0x80)
113
+_RH_RF95_MAX_POWER                           = const(0x70)
114
+_RH_RF95_OUTPUT_POWER                        = const(0x0f)
115
+
116
+# RH_RF95_REG_0A_PA_RAMP                             0x0a
117
+_RH_RF95_LOW_PN_TX_PLL_OFF                   = const(0x10)
118
+_RH_RF95_PA_RAMP                             = const(0x0f)
119
+_RH_RF95_PA_RAMP_3_4MS                       = const(0x00)
120
+_RH_RF95_PA_RAMP_2MS                         = const(0x01)
121
+_RH_RF95_PA_RAMP_1MS                         = const(0x02)
122
+_RH_RF95_PA_RAMP_500US                       = const(0x03)
123
+_RH_RF95_PA_RAMP_250US                       = const(0x04)
124
+_RH_RF95_PA_RAMP_125US                       = const(0x05)
125
+_RH_RF95_PA_RAMP_100US                       = const(0x06)
126
+_RH_RF95_PA_RAMP_62US                        = const(0x07)
127
+_RH_RF95_PA_RAMP_50US                        = const(0x08)
128
+_RH_RF95_PA_RAMP_40US                        = const(0x09)
129
+_RH_RF95_PA_RAMP_31US                        = const(0x0a)
130
+_RH_RF95_PA_RAMP_25US                        = const(0x0b)
131
+_RH_RF95_PA_RAMP_20US                        = const(0x0c)
132
+_RH_RF95_PA_RAMP_15US                        = const(0x0d)
133
+_RH_RF95_PA_RAMP_12US                        = const(0x0e)
134
+_RH_RF95_PA_RAMP_10US                        = const(0x0f)
135
+
136
+# RH_RF95_REG_0B_OCP                                 0x0b
137
+_RH_RF95_OCP_ON                              = const(0x20)
138
+_RH_RF95_OCP_TRIM                            = const(0x1f)
139
+
140
+# RH_RF95_REG_0C_LNA                                 0x0c
141
+_RH_RF95_LNA_GAIN                            = const(0xe0)
142
+_RH_RF95_LNA_BOOST                           = const(0x03)
143
+_RH_RF95_LNA_BOOST_DEFAULT                   = const(0x00)
144
+_RH_RF95_LNA_BOOST_150PC                     = const(0x11)
145
+
146
+# RH_RF95_REG_11_IRQ_FLAGS_MASK                      0x11
147
+_RH_RF95_RX_TIMEOUT_MASK                     = const(0x80)
148
+_RH_RF95_RX_DONE_MASK                        = const(0x40)
149
+_RH_RF95_PAYLOAD_CRC_ERROR_MASK              = const(0x20)
150
+_RH_RF95_VALID_HEADER_MASK                   = const(0x10)
151
+_RH_RF95_TX_DONE_MASK                        = const(0x08)
152
+_RH_RF95_CAD_DONE_MASK                       = const(0x04)
153
+_RH_RF95_FHSS_CHANGE_CHANNEL_MASK            = const(0x02)
154
+_RH_RF95_CAD_DETECTED_MASK                   = const(0x01)
155
+
156
+# RH_RF95_REG_12_IRQ_FLAGS                           0x12
157
+_RH_RF95_RX_TIMEOUT                          = const(0x80)
158
+_RH_RF95_RX_DONE                             = const(0x40)
159
+_RH_RF95_PAYLOAD_CRC_ERROR                   = const(0x20)
160
+_RH_RF95_VALID_HEADER                        = const(0x10)
161
+_RH_RF95_TX_DONE                             = const(0x08)
162
+_RH_RF95_CAD_DONE                            = const(0x04)
163
+_RH_RF95_FHSS_CHANGE_CHANNEL                 = const(0x02)
164
+_RH_RF95_CAD_DETECTED                        = const(0x01)
165
+
166
+# RH_RF95_REG_18_MODEM_STAT                          0x18
167
+_RH_RF95_RX_CODING_RATE                      = const(0xe0)
168
+_RH_RF95_MODEM_STATUS_CLEAR                  = const(0x10)
169
+_RH_RF95_MODEM_STATUS_HEADER_INFO_VALID      = const(0x08)
170
+_RH_RF95_MODEM_STATUS_RX_ONGOING             = const(0x04)
171
+_RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED    = const(0x02)
172
+_RH_RF95_MODEM_STATUS_SIGNAL_DETECTED        = const(0x01)
173
+
174
+# RH_RF95_REG_1C_HOP_CHANNEL                         0x1c
175
+_RH_RF95_PLL_TIMEOUT                         = const(0x80)
176
+_RH_RF95_RX_PAYLOAD_CRC_IS_ON                = const(0x40)
177
+_RH_RF95_FHSS_PRESENT_CHANNEL                = const(0x3f)
178
+
179
+# RH_RF95_REG_1D_MODEM_CONFIG1                       0x1d
180
+_RH_RF95_BW                                  = const(0xc0)
181
+_RH_RF95_BW_125KHZ                           = const(0x00)
182
+_RH_RF95_BW_250KHZ                           = const(0x40)
183
+_RH_RF95_BW_500KHZ                           = const(0x80)
184
+_RH_RF95_BW_RESERVED                         = const(0xc0)
185
+_RH_RF95_CODING_RATE                         = const(0x38)
186
+_RH_RF95_CODING_RATE_4_5                     = const(0x00)
187
+_RH_RF95_CODING_RATE_4_6                     = const(0x08)
188
+_RH_RF95_CODING_RATE_4_7                     = const(0x10)
189
+_RH_RF95_CODING_RATE_4_8                     = const(0x18)
190
+_RH_RF95_IMPLICIT_HEADER_MODE_ON             = const(0x04)
191
+_RH_RF95_RX_PAYLOAD_CRC_ON                   = const(0x02)
192
+_RH_RF95_LOW_DATA_RATE_OPTIMIZE              = const(0x01)
193
+
194
+# RH_RF95_REG_1E_MODEM_CONFIG2                       0x1e
195
+_RH_RF95_SPREADING_FACTOR                    = const(0xf0)
196
+_RH_RF95_SPREADING_FACTOR_64CPS              = const(0x60)
197
+_RH_RF95_SPREADING_FACTOR_128CPS             = const(0x70)
198
+_RH_RF95_SPREADING_FACTOR_256CPS             = const(0x80)
199
+_RH_RF95_SPREADING_FACTOR_512CPS             = const(0x90)
200
+_RH_RF95_SPREADING_FACTOR_1024CPS            = const(0xa0)
201
+_RH_RF95_SPREADING_FACTOR_2048CPS            = const(0xb0)
202
+_RH_RF95_SPREADING_FACTOR_4096CPS            = const(0xc0)
203
+_RH_RF95_TX_CONTINUOUS_MOE                   = const(0x08)
204
+_RH_RF95_AGC_AUTO_ON                         = const(0x04)
205
+_RH_RF95_SYM_TIMEOUT_MSB                     = const(0x03)
206
+
207
+# RH_RF95_REG_4D_PA_DAC                              0x4d
208
+_RH_RF95_PA_DAC_DISABLE                      = const(0x04)
209
+_RH_RF95_PA_DAC_ENABLE                       = const(0x07)
210
+
211
+# The crystal oscillator frequency of the module
212
+_RH_RF95_FXOSC = 32000000.0
213
+
214
+# The Frequency Synthesizer step = RH_RF95_FXOSC / 2^^19
215
+_RH_RF95_FSTEP = (_RH_RF95_FXOSC / 524288)
216
+
217
+# RadioHead specific compatibility constants.
218
+_RH_BROADCAST_ADDRESS = const(0xFF)
219
+
220
+# User facing constants:
221
+SLEEP_MODE   = 0b000
222
+STANDBY_MODE = 0b001
223
+FS_TX_MODE   = 0b010
224
+TX_MODE      = 0b011
225
+FS_RX_MODE   = 0b100
226
+RX_MODE      = 0b101
227
+# pylint: enable=bad-whitespace
228
+
229
+
230
+# Disable the too many instance members warning.  Pylint has no knowledge
231
+# of the context and is merely guessing at the proper amount of members.  This
232
+# is a complex chip which requires exposing many attributes and state.  Disable
233
+# the warning to work around the error.
234
+# pylint: disable=too-many-instance-attributes
235
+
236
+class RFM9x:
237
+    """Interface to a RFM95/6/7/8 LoRa radio module.  Allows sending and
238
+    receivng bytes of data in long range LoRa mode at a support board frequency
239
+    (433/915mhz).
240
+
241
+    You must specify the following parameters:
242
+    - spi: The SPI bus connected to the radio.
243
+    - cs: The CS pin DigitalInOut connected to the radio.
244
+    - reset: The reset/RST pin DigialInOut connected to the radio.
245
+    - frequency: The frequency (in mhz) of the radio module (433/915mhz typically).
246
+
247
+    You can optionally specify:
248
+    - preamble_length: The length in bytes of the packet preamble (default 8).
249
+    - high_power: Boolean to indicate a high power board (RFM95, etc.).  Default
250
+    is True for high power.
251
+    - baudrate: Baud rate of the SPI connection, default is 10mhz but you might
252
+    choose to lower to 1mhz if using long wires or a breadboard.
253
+
254
+    Remember this library makes a best effort at receiving packets with pure
255
+    Python code.  Trying to receive packets too quickly will result in lost data
256
+    so limit yourself to simple scenarios of sending and receiving single
257
+    packets at a time.
258
+
259
+    Also note this library tries to be compatible with raw RadioHead Arduino
260
+    library communication. This means the library sets up the radio modulation
261
+    to match RadioHead's defaults. Features like addressing and guaranteed
262
+    delivery need to be implemented at an application level.
263
+    """
264
+
265
+    # Global buffer to hold data sent and received with the chip.  This must be
266
+    # at least as large as the FIFO on the chip (256 bytes)!  Keep this on the
267
+    # class level to ensure only one copy ever exists (with the trade-off that
268
+    # this is NOT re-entrant or thread safe code by design).
269
+    _BUFFER = bytearray(10)
270
+
271
+    class _RegisterBits:
272
+        # Class to simplify access to the many configuration bits avaialable
273
+        # on the chip's registers.  This is a subclass here instead of using
274
+        # a higher level module to increase the efficiency of memory usage
275
+        # (all of the instances of this bit class will share the same buffer
276
+        # used by the parent RFM69 class instance vs. each having their own
277
+        # buffer and taking too much memory).
278
+
279
+        # Quirk of pylint that it requires public methods for a class.  This
280
+        # is a decorator class in Python and by design it has no public methods.
281
+        # Instead it uses dunder accessors like get and set below.  For some
282
+        # reason pylint can't figure this out so disable the check.
283
+        # pylint: disable=too-few-public-methods
284
+
285
+        # Again pylint fails to see the true intent of this code and warns
286
+        # against private access by calling the write and read functions below.
287
+        # This is by design as this is an internally used class.  Disable the
288
+        # check from pylint.
289
+        # pylint: disable=protected-access
290
+
291
+        def __init__(self, address, *, offset=0, bits=1):
292
+            assert 0 <= offset <= 7
293
+            assert 1 <= bits <= 8
294
+            assert (offset + bits) <= 8
295
+            self._address = address
296
+            self._mask = 0
297
+            for _ in range(bits):
298
+                self._mask <<= 1
299
+                self._mask |= 1
300
+            self._mask <<= offset
301
+            self._offset = offset
302
+
303
+        def __get__(self, obj, objtype):
304
+            reg_value = obj._read_u8(self._address)
305
+            return (reg_value & self._mask) >> self._offset
306
+
307
+        def __set__(self, obj, val):
308
+            reg_value = obj._read_u8(self._address)
309
+            reg_value &= ~self._mask
310
+            reg_value |= (val & 0xFF) << self._offset
311
+            obj._write_u8(self._address, reg_value)
312
+
313
+    operation_mode = _RegisterBits(_RH_RF95_REG_01_OP_MODE, bits=3)
314
+
315
+    low_frequency_mode = _RegisterBits(_RH_RF95_REG_01_OP_MODE, offset=3, bits=1)
316
+
317
+    modulation_type = _RegisterBits(_RH_RF95_REG_01_OP_MODE, offset=5, bits=2)
318
+
319
+    # Long range/LoRa mode can only be set in sleep mode!
320
+    long_range_mode = _RegisterBits(_RH_RF95_REG_01_OP_MODE, offset=7, bits=1)
321
+
322
+    output_power = _RegisterBits(_RH_RF95_REG_09_PA_CONFIG, bits=4)
323
+
324
+    max_power = _RegisterBits(_RH_RF95_REG_09_PA_CONFIG, offset=4, bits=3)
325
+
326
+    pa_select = _RegisterBits(_RH_RF95_REG_09_PA_CONFIG, offset=7, bits=1)
327
+
328
+    pa_dac = _RegisterBits(_RH_RF95_REG_4D_PA_DAC, bits=3)
329
+
330
+    dio0_mapping = _RegisterBits(_RH_RF95_REG_40_DIO_MAPPING1, offset=6, bits=2)
331
+
332
+    tx_done = _RegisterBits(_RH_RF95_REG_12_IRQ_FLAGS, offset=3, bits=1)
333
+
334
+    rx_done = _RegisterBits(_RH_RF95_REG_12_IRQ_FLAGS, offset=6, bits=1)
335
+
336
+    def __init__(self, spi, cs, reset, frequency, *, preamble_length=8,
337
+                 high_power=True, baudrate=5000000):
338
+        self.high_power = high_power
339
+        # Device support SPI mode 0 (polarity & phase = 0) up to a max of 10mhz.
340
+        # Set Default Baudrate to 5MHz to avoid problems
341
+        self._device = spi_device.SPIDevice(spi, cs, baudrate=baudrate,
342
+                                            polarity=0, phase=0)
343
+        # Setup reset as a digital input (default state for reset line according
344
+        # to the datasheet).  This line is pulled low as an output quickly to
345
+        # trigger a reset.  Note that reset MUST be done like this and set as
346
+        # a high impedence input or else the chip cannot change modes (trust me!).
347
+        self._reset = reset
348
+        self._reset.switch_to_input(pull=digitalio.Pull.UP)
349
+        self.reset()
350
+        # No device type check!  Catch an error from the very first request and
351
+        # throw a nicer message to indicate possible wiring problems.
352
+        try:
353
+            # Set sleep mode, wait 10s and confirm in sleep mode (basic device check).
354
+            # Also set long range mode (LoRa mode) as it can only be done in sleep.
355
+            self.sleep()
356
+            self.long_range_mode = True
357
+            self._write_u8(_RH_RF95_REG_01_OP_MODE, 0b10001000)
358
+            time.sleep(0.01)
359
+            val = self._read_u8(_RH_RF95_REG_01_OP_MODE)
360
+            print('op mode: {0}'.format(bin(val)))
361
+            if self.operation_mode != SLEEP_MODE or not self.long_range_mode:
362
+                raise RuntimeError('Failed to configure radio for LoRa mode, check wiring!')
363
+        except OSError:
364
+            raise RuntimeError('Failed to communicate with radio, check wiring!')
365
+        # clear default setting for access to LF registers if frequency > 525MHz
366
+        if frequency > 525:
367
+            self.low_frequency_mode = 0
368
+        # Setup entire 256 byte FIFO
369
+        self._write_u8(_RH_RF95_REG_0E_FIFO_TX_BASE_ADDR, 0x00)
370
+        self._write_u8(_RH_RF95_REG_0F_FIFO_RX_BASE_ADDR, 0x00)
371
+        # Set mode idle
372
+        self.idle()
373
+        # Set modem config to RadioHead compatible Bw125Cr45Sf128 mode.
374
+        # Note no sync word is set for LoRa mode either!
375
+        self._write_u8(_RH_RF95_REG_1D_MODEM_CONFIG1, 0x72)  # Fei msb?
376
+        self._write_u8(_RH_RF95_REG_1E_MODEM_CONFIG2, 0x74)  # Fei lsb?
377
+        self._write_u8(_RH_RF95_REG_26_MODEM_CONFIG3, 0x00)  # Preamble lsb?
378
+        # Set preamble length (default 8 bytes to match radiohead).
379
+        self.preamble_length = preamble_length
380
+        # Set frequency
381
+        self.frequency_mhz = frequency
382
+        # Set TX power to low defaut, 13 dB.
383
+        self.tx_power = 13
384
+
385
+    def _read_into(self, address, buf, length=None):
386
+        # Read a number of bytes from the specified address into the provided
387
+        # buffer.  If length is not specified (the default) the entire buffer
388
+        # will be filled.
389
+        if length is None:
390
+            length = len(buf)
391
+        with self._device as device:
392
+            self._BUFFER[0] = address & 0x7F  # Strip out top bit to set 0
393
+                                              # value (read).
394
+            device.write(self._BUFFER, end=1)
395
+            device.readinto(buf, end=length)
396
+
397
+    def _read_u8(self, address):
398
+        # Read a single byte from the provided address and return it.
399
+        self._read_into(address, self._BUFFER, length=1)
400
+        return self._BUFFER[0]
401
+
402
+    def _write_from(self, address, buf, length=None):
403
+        # Write a number of bytes to the provided address and taken from the
404
+        # provided buffer.  If no length is specified (the default) the entire
405
+        # buffer is written.
406
+        if length is None:
407
+            length = len(buf)
408
+        with self._device as device:
409
+            self._BUFFER[0] = (address | 0x80) & 0xFF  # Set top bit to 1 to
410
+                                                       # indicate a write.
411
+            device.write(self._BUFFER, end=1)
412
+            device.write(buf, end=length)
413
+
414
+    def _write_u8(self, address, val):
415
+        # Write a byte register to the chip.  Specify the 7-bit address and the
416
+        # 8-bit value to write to that address.
417
+        with self._device as device:
418
+            self._BUFFER[0] = (address | 0x80) & 0xFF  # Set top bit to 1 to
419
+                                                       # indicate a write.
420
+            self._BUFFER[1] = val & 0xFF
421
+            device.write(self._BUFFER, end=2)
422
+
423
+    def reset(self):
424
+        """Perform a reset of the chip."""
425
+        # See section 7.2.2 of the datasheet for reset description.
426
+        self._reset.switch_to_output(value=False)
427
+        time.sleep(0.0001)  # 100 us
428
+        self._reset.switch_to_input(pull=digitalio.Pull.UP)
429
+        time.sleep(0.005)   # 5 ms
430
+
431
+    def idle(self):
432
+        """Enter idle standby mode."""
433
+        self.operation_mode = STANDBY_MODE
434
+
435
+    def sleep(self):
436
+        """Enter sleep mode."""
437
+        self.operation_mode = SLEEP_MODE
438
+
439
+    def listen(self):
440
+        """Listen for packets to be received by the chip.  Use :py:func:`receive`
441
+        to listen, wait and retrieve packets as they're available.
442
+        """
443
+        self.operation_mode = RX_MODE
444
+        self.dio0_mapping = 0b00  # Interrupt on rx done.
445
+
446
+    def transmit(self):
447
+        """Transmit a packet which is queued in the FIFO.  This is a low level
448
+        function for entering transmit mode and more.  For generating and
449
+        transmitting a packet of data use :py:func:`send` instead.
450
+        """
451
+        self.operation_mode = TX_MODE
452
+        self.dio0_mapping = 0b01  # Interrupt on tx done.
453
+
454
+    @property
455
+    def preamble_length(self):
456
+        """The length of the preamble for sent and received packets, an unsigned
457
+        16-bit value.  Received packets must match this length or they are
458
+        ignored! Set to 8 to match the RadioHead RFM95 library.
459
+        """
460
+        msb = self._read_u8(_RH_RF95_REG_20_PREAMBLE_MSB)
461
+        lsb = self._read_u8(_RH_RF95_REG_21_PREAMBLE_LSB)
462
+        return ((msb << 8) | lsb) & 0xFFFF
463
+
464
+    @preamble_length.setter
465
+    def preamble_length(self, val):
466
+        assert 0 <= val <= 65535
467
+        self._write_u8(_RH_RF95_REG_20_PREAMBLE_MSB, (val >> 8) & 0xFF)
468
+        self._write_u8(_RH_RF95_REG_21_PREAMBLE_LSB, val & 0xFF)
469
+
470
+    @property
471
+    def frequency_mhz(self):
472
+        """The frequency of the radio in Megahertz. Only the allowed values for
473
+        your radio must be specified (i.e. 433 vs. 915 mhz)!
474
+        """
475
+        msb = self._read_u8(_RH_RF95_REG_06_FRF_MSB)
476
+        mid = self._read_u8(_RH_RF95_REG_07_FRF_MID)
477
+        lsb = self._read_u8(_RH_RF95_REG_08_FRF_LSB)
478
+        frf = ((msb << 16) | (mid << 8) | lsb) & 0xFFFFFF
479
+        frequency = (frf * _RH_RF95_FSTEP) / 1000000.0
480
+        return frequency
481
+