periph 1.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. periph/__init__.py +1 -0
  2. periph/chips/accelerometer/adxl345.py +524 -0
  3. periph/chips/accelerometer/adxl362.py +704 -0
  4. periph/chips/adc_dac/ad7705.py +476 -0
  5. periph/chips/adc_dac/ad7706.py +495 -0
  6. periph/chips/adc_dac/hx710a.py +187 -0
  7. periph/chips/adc_dac/hx710b.py +189 -0
  8. periph/chips/adc_dac/hx711.py +170 -0
  9. periph/chips/adc_dac/mcp4725.py +167 -0
  10. periph/chips/adc_dac/mcp4728.py +317 -0
  11. periph/chips/adc_dac/pcf8591.py +199 -0
  12. periph/chips/comms/mcp2515.py +651 -0
  13. periph/chips/comms/rda5807m.py +420 -0
  14. periph/chips/comms/rfm9x.py +594 -0
  15. periph/chips/display/pcf8576.py +223 -0
  16. periph/chips/environmental/aht21.py +150 -0
  17. periph/chips/environmental/bme280.py +384 -0
  18. periph/chips/environmental/bme680.py +501 -0
  19. periph/chips/gas/ens160.py +273 -0
  20. periph/chips/gnss/neo6.py +439 -0
  21. periph/chips/gyroscope/l3g4200d.py +448 -0
  22. periph/chips/gyroscope/l3gd20h.py +337 -0
  23. periph/chips/humidity/dht11.py +123 -0
  24. periph/chips/imu/mpu6050.py +251 -0
  25. periph/chips/imu/mpu9250.py +378 -0
  26. periph/chips/imu/mpu9255.py +434 -0
  27. periph/chips/io_expander/mcp23017.py +494 -0
  28. periph/chips/io_expander/pcf8574.py +401 -0
  29. periph/chips/io_expander/pcf8575.py +420 -0
  30. periph/chips/io_expander/tpic6b595.py +271 -0
  31. periph/chips/led/_color.py +23 -0
  32. periph/chips/led/_neopixel_rgb_base.py +175 -0
  33. periph/chips/led/_neopixel_rgbw_base.py +193 -0
  34. periph/chips/led/apa102.py +200 -0
  35. periph/chips/led/sk6812rgbw.py +52 -0
  36. periph/chips/led/ws2812b.py +46 -0
  37. periph/chips/led/ws2814.py +52 -0
  38. periph/chips/light/apds9960.py +496 -0
  39. periph/chips/light/apds_9930.py +364 -0
  40. periph/chips/magnetometer/as5600.py +290 -0
  41. periph/chips/magnetometer/hmc5883l.py +210 -0
  42. periph/chips/memory/_24aa02uid.py +175 -0
  43. periph/chips/motor/drv8830.py +236 -0
  44. periph/chips/other/mpr121.py +465 -0
  45. periph/chips/power/ade7953.py +1196 -0
  46. periph/chips/power/ina219.py +195 -0
  47. periph/chips/power/ina226.py +217 -0
  48. periph/chips/power/ina3221.py +359 -0
  49. periph/chips/pressure/bmp085.py +234 -0
  50. periph/chips/pressure/bmp180.py +234 -0
  51. periph/chips/pressure/bmp280.py +292 -0
  52. periph/chips/pressure/bmp384.py +471 -0
  53. periph/chips/pressure/bmp581.py +561 -0
  54. periph/chips/pressure/lps22df.py +394 -0
  55. periph/chips/pressure/lps28dfw.py +337 -0
  56. periph/chips/pressure/lps33hw.py +385 -0
  57. periph/chips/rfid/mfrc522.py +818 -0
  58. periph/chips/rtc/ds3231.py +522 -0
  59. periph/chips/rtc/pcf8523.py +549 -0
  60. periph/chips/temperature/mcp9808.py +453 -0
  61. periph/chips/temperature/tmp117.py +461 -0
  62. periph/chips/tof/_vl53_base.py +174 -0
  63. periph/chips/tof/vl53l0x.py +816 -0
  64. periph/chips/tof/vl53l1x.py +815 -0
  65. periph/connection/__init__.py +31 -0
  66. periph/connection/base.py +103 -0
  67. periph/connection/dhtxx_auto.py +52 -0
  68. periph/connection/dhtxx_circuitpython.py +138 -0
  69. periph/connection/dhtxx_linux.py +230 -0
  70. periph/connection/dhtxx_micropython.py +133 -0
  71. periph/connection/dhtxx_mock.py +34 -0
  72. periph/connection/hx711_auto.py +66 -0
  73. periph/connection/hx711_circuitpython.py +114 -0
  74. periph/connection/hx711_linux.py +121 -0
  75. periph/connection/hx711_micropython.py +113 -0
  76. periph/connection/hx711_mock.py +43 -0
  77. periph/connection/i2c_auto.py +54 -0
  78. periph/connection/i2c_circuitpython.py +84 -0
  79. periph/connection/i2c_linux.py +81 -0
  80. periph/connection/i2c_micropython.py +65 -0
  81. periph/connection/i2c_mock.py +57 -0
  82. periph/connection/input_pin.py +259 -0
  83. periph/connection/neo6_mock.py +36 -0
  84. periph/connection/neopixel_auto.py +75 -0
  85. periph/connection/neopixel_circuitpython.py +44 -0
  86. periph/connection/neopixel_linux.py +47 -0
  87. periph/connection/neopixel_micropython.py +38 -0
  88. periph/connection/output_pin.py +81 -0
  89. periph/connection/sipo_circuitpython.py +119 -0
  90. periph/connection/sipo_linux.py +149 -0
  91. periph/connection/sipo_micropython.py +104 -0
  92. periph/connection/smbus_auto.py +68 -0
  93. periph/connection/smbus_circuitpython.py +118 -0
  94. periph/connection/smbus_linux.py +119 -0
  95. periph/connection/smbus_micropython.py +100 -0
  96. periph/connection/spi_auto.py +79 -0
  97. periph/connection/spi_circuitpython.py +97 -0
  98. periph/connection/spi_linux.py +66 -0
  99. periph/connection/spi_micropython.py +64 -0
  100. periph/connection/spi_mock.py +96 -0
  101. periph/connection/uart_auto.py +76 -0
  102. periph/connection/uart_circuitpython.py +81 -0
  103. periph/connection/uart_linux.py +120 -0
  104. periph/connection/uart_micropython.py +81 -0
  105. periph-1.2.1.dist-info/METADATA +113 -0
  106. periph-1.2.1.dist-info/RECORD +108 -0
  107. periph-1.2.1.dist-info/WHEEL +5 -0
  108. periph-1.2.1.dist-info/top_level.txt +1 -0
periph/__init__.py ADDED
@@ -0,0 +1 @@
1
+ from .connection import Connection
@@ -0,0 +1,524 @@
1
+ """ADXL345 — 3-axis MEMS accelerometer (Analog Devices).
2
+
3
+ Provides 3-axis acceleration readings with no configuration beyond the
4
+ connection. The chip supports both I²C and SPI; the same driver file is
5
+ used on MicroPython, CircuitPython, and Linux hosts.
6
+
7
+ Default configuration (written at construction):
8
+ - Data format: full-resolution (3.9 mg/LSB at all ranges), ±2 g
9
+ - Output data rate: 100 Hz
10
+ - FIFO: bypass
11
+ - All interrupts disabled
12
+
13
+ Args:
14
+ connection: Configured I²C or SPI connection pointing at the device.
15
+ bus_type: Bus type string, ``'i2c'`` (default) or ``'spi'``.
16
+ SPI writes prepend a command byte (R/W|MB|A5..A0) to each
17
+ transfer; multi-byte reads set MB=1 so the register pointer
18
+ auto-increments.
19
+ """
20
+
21
+
22
+ _BUS_I2C = 'i2c'
23
+ _BUS_SPI = 'spi'
24
+
25
+ # Default BW_RATE: 100 Hz output data rate, normal power.
26
+ _BW_RATE_DEFAULT = 0x0A
27
+
28
+ # Default DATA_FORMAT: FULL_RES=1, everything else 0 → ±2 g range.
29
+ _DATA_FORMAT_DEFAULT = 0x08
30
+ # Default POWER_CTL: Measure bit set; everything else cleared.
31
+ _POWER_CTL_DEFAULT = 0x08
32
+
33
+ # Conversion: full-resolution mode is constant 3.9 mg/LSB = 0.0039 g/LSB.
34
+ _FULL_RES_SCALE_G_PER_LSB = 3.9e-3
35
+
36
+ # 10-bit mode scales (g/LSB), indexed by range bits (00=±2, 01=±4, 10=±8, 11=±16).
37
+ _10BIT_SCALE_G_PER_LSB = (3.9e-3, 7.8e-3, 15.6e-3, 31.2e-3)
38
+
39
+ # Valid BW_RATE codes (Rate bits 3:0) and their actual output data rates.
40
+ _RATE_CODES = (
41
+ (0x0F, 3200.0),
42
+ (0x0E, 1600.0),
43
+ (0x0D, 800.0),
44
+ (0x0C, 400.0),
45
+ (0x0B, 200.0),
46
+ (0x0A, 100.0),
47
+ (0x09, 50.0),
48
+ (0x08, 25.0),
49
+ (0x07, 12.5),
50
+ (0x06, 6.25),
51
+ )
52
+
53
+
54
+ def _delay_ms(ms):
55
+ import time
56
+ if hasattr(time, 'sleep_ms'):
57
+ time.sleep_ms(ms)
58
+ else:
59
+ time.sleep(ms / 1000.0)
60
+
61
+
62
+ class ADXL345Minimal:
63
+ """ADXL345 3-axis accelerometer — minimal interface.
64
+
65
+ Reads X, Y, Z acceleration in *g* with sensible defaults; no
66
+ configuration is required beyond the connection.
67
+
68
+ Default configuration (baked in at construction):
69
+ - Full-resolution mode (3.9 mg/LSB at any range)
70
+ - ±2 g measurement range
71
+ - 100 Hz output data rate, normal power
72
+ - FIFO bypass, all interrupts disabled, no offsets
73
+
74
+ Args:
75
+ connection: Configured I²C or SPI connection pointing at the device.
76
+ bus_type: Bus type string, ``'i2c'`` (default) or ``'spi'``.
77
+ """
78
+
79
+ # Register map (6-bit addresses, 0x00–0x39).
80
+ _REG_DEVID = 0x00
81
+ _REG_THRESH_TAP = 0x1D
82
+ _REG_OFSX = 0x1E
83
+ _REG_OFSY = 0x1F
84
+ _REG_OFSZ = 0x20
85
+ _REG_DUR = 0x21
86
+ _REG_LATENT = 0x22
87
+ _REG_WINDOW = 0x23
88
+ _REG_THRESH_ACT = 0x24
89
+ _REG_THRESH_INACT = 0x25
90
+ _REG_TIME_INACT = 0x26
91
+ _REG_ACT_INACT_CTL = 0x27
92
+ _REG_THRESH_FF = 0x28
93
+ _REG_TIME_FF = 0x29
94
+ _REG_TAP_AXES = 0x2A
95
+ _REG_ACT_TAP_STATUS = 0x2B
96
+ _REG_BW_RATE = 0x2C
97
+ _REG_POWER_CTL = 0x2D
98
+ _REG_INT_ENABLE = 0x2E
99
+ _REG_INT_MAP = 0x2F
100
+ _REG_INT_SOURCE = 0x30
101
+ _REG_DATA_FORMAT = 0x31
102
+ _REG_DATAX0 = 0x32
103
+ _REG_FIFO_CTL = 0x38
104
+ _REG_FIFO_STATUS = 0x39
105
+
106
+ # Fixed device ID; reading any other value indicates wrong device/wiring.
107
+ _DEVID_VALUE = 0xE5
108
+
109
+ def __init__(self, connection, bus_type=_BUS_I2C):
110
+ self._connection = connection
111
+ self._bus_type = bus_type
112
+ self._range_bits = 0 # 0..3: 0=±2, 1=±4, 2=±8, 3=±16 g
113
+ self._full_res = True
114
+ self._write_reg(self._REG_DATA_FORMAT, _DATA_FORMAT_DEFAULT)
115
+ self._write_reg(self._REG_BW_RATE, _BW_RATE_DEFAULT)
116
+ self._write_reg(self._REG_POWER_CTL, _POWER_CTL_DEFAULT)
117
+ devid = self._read_reg(self._REG_DEVID)
118
+ if devid != self._DEVID_VALUE:
119
+ raise ValueError('ADXL345 DEVID: expected 0x{:02X}, got 0x{:02X}'.format(
120
+ self._DEVID_VALUE, devid))
121
+ # Per datasheet: allow at least one ODR period before reading data.
122
+ _delay_ms(11)
123
+
124
+ def _cmd_byte(self, reg, read=False, multi=False):
125
+ """Build the SPI command byte for the ADXL345.
126
+
127
+ I²C calls bypass this — the connection's address byte is independent.
128
+ """
129
+ addr = (reg & 0x3F)
130
+ if multi:
131
+ addr |= 0x40
132
+ if read:
133
+ addr |= 0x80
134
+ return addr
135
+
136
+ def _write_reg(self, reg, value):
137
+ if self._bus_type == _BUS_SPI:
138
+ cmd = self._cmd_byte(reg, read=False, multi=False)
139
+ self._connection.write(bytes([cmd, value & 0xFF]))
140
+ else:
141
+ self._connection.write(bytes([reg & 0xFF, value & 0xFF]))
142
+
143
+ def _read_reg(self, reg):
144
+ if self._bus_type == _BUS_SPI:
145
+ cmd = self._cmd_byte(reg, read=True, multi=False)
146
+ return self._connection.write_read(bytes([cmd]), 1)[0]
147
+ return self._connection.write_read(bytes([reg & 0xFF]), 1)[0]
148
+
149
+ def _read_burst(self, reg, n):
150
+ """Read n bytes starting at reg, auto-incrementing the register pointer."""
151
+ if self._bus_type == _BUS_SPI:
152
+ cmd = self._cmd_byte(reg, read=True, multi=(n > 1))
153
+ return self._connection.write_read(bytes([cmd]), n)
154
+ return self._connection.write_read(bytes([reg & 0xFF]), n)
155
+
156
+ def read(self):
157
+ """Read 3-axis linear acceleration.
158
+
159
+ Reads all six data bytes (DATAX0..DATAZ1) in one burst so the X, Y,
160
+ Z samples are guaranteed to come from a single measurement.
161
+
162
+ Returns:
163
+ tuple: (x, y, z) acceleration in *g*.
164
+ """
165
+ raw = self._read_burst(self._REG_DATAX0, 6)
166
+ # Each axis is little-endian, signed 16-bit two's complement.
167
+ rx = raw[0] | (raw[1] << 8)
168
+ ry = raw[2] | (raw[3] << 8)
169
+ rz = raw[4] | (raw[5] << 8)
170
+ if rx & 0x8000:
171
+ rx -= 0x10000
172
+ if ry & 0x8000:
173
+ ry -= 0x10000
174
+ if rz & 0x8000:
175
+ rz -= 0x10000
176
+ if self._full_res:
177
+ scale = _FULL_RES_SCALE_G_PER_LSB
178
+ else:
179
+ scale = _10BIT_SCALE_G_PER_LSB[self._range_bits & 0x03]
180
+ return (rx * scale, ry * scale, rz * scale)
181
+
182
+
183
+ class ADXL345Full(ADXL345Minimal):
184
+ """ADXL345 full interface — extends ADXL345Minimal with configuration, FIFO,
185
+ tap / activity / inactivity / free-fall detection, and interrupt routing.
186
+
187
+ Adds:
188
+ - Range and data-rate selection (FULL_RES preserved).
189
+ - Low-power mode and self-test.
190
+ - Per-axis offset calibration (in *g*).
191
+ - Single/double-tap detection with configurable threshold, duration,
192
+ latency, and window.
193
+ - Activity and inactivity detection with ac/dc coupling.
194
+ - Free-fall detection.
195
+ - 32-level FIFO (bypass / FIFO / stream / trigger modes).
196
+ - Interrupt routing (INT1 / INT2) and source read.
197
+ - Sleep, auto-sleep, and link mode for power management.
198
+
199
+ Args:
200
+ connection: Configured I²C or SPI connection pointing at the device.
201
+ bus_type: Bus type string, ``'i2c'`` (default) or ``'spi'``.
202
+ """
203
+
204
+ # Interrupt source bits — match INT_ENABLE / INT_MAP / INT_SOURCE layout.
205
+ INT_DATA_READY = 0x80
206
+ INT_SINGLE_TAP = 0x40
207
+ INT_DOUBLE_TAP = 0x20
208
+ INT_ACTIVITY = 0x10
209
+ INT_INACTIVITY = 0x08
210
+ INT_FREE_FALL = 0x04
211
+ INT_WATERMARK = 0x02
212
+ INT_OVERRUN = 0x01
213
+
214
+ # FIFO mode values — match FIFO_CTL bits 7:6.
215
+ FIFO_BYPASS = 0x00
216
+ FIFO_FIFO = 0x40
217
+ FIFO_STREAM = 0x80
218
+ FIFO_TRIGGER = 0xC0
219
+
220
+ # Sleep-mode wakeup sample rates (POWER_CTL Wakeup bits 2:1).
221
+ WAKEUP_8_HZ = 0x00
222
+ WAKEUP_4_HZ = 0x02
223
+ WAKEUP_2_HZ = 0x04
224
+ WAKEUP_1_HZ = 0x06
225
+
226
+ def __init__(self, connection, bus_type=_BUS_I2C):
227
+ super().__init__(connection, bus_type)
228
+
229
+ def set_range(self, range_g):
230
+ """Set the measurement range.
231
+
232
+ Args:
233
+ range_g: One of 2, 4, 8, 16 (g). FULL_RES is preserved, so the
234
+ scale factor stays 3.9 mg/LSB regardless of range.
235
+ """
236
+ code = {2: 0, 4: 1, 8: 2, 16: 3}.get(range_g)
237
+ if code is None:
238
+ raise ValueError('range_g must be one of 2, 4, 8, 16')
239
+ self._range_bits = code
240
+ df = self._read_reg(self._REG_DATA_FORMAT)
241
+ df = (df & ~0x03) | (code & 0x03)
242
+ if self._full_res:
243
+ df |= 0x08
244
+ self._write_reg(self._REG_DATA_FORMAT, df)
245
+
246
+ def set_data_rate(self, rate_hz):
247
+ """Set the output data rate to the nearest supported value.
248
+
249
+ Args:
250
+ rate_hz: Requested ODR in Hz; clamped to the nearest of
251
+ 6.25, 12.5, 25, 50, 100, 200, 400, 800, 1600, 3200 Hz.
252
+ """
253
+ best_code = _RATE_CODES[-1][0]
254
+ best_rate = _RATE_CODES[-1][1]
255
+ for code, actual in _RATE_CODES:
256
+ if abs(actual - rate_hz) < abs(best_rate - rate_hz):
257
+ best_code, best_rate = code, actual
258
+ bw = self._read_reg(self._REG_BW_RATE)
259
+ bw = (bw & ~0x0F) | (best_code & 0x0F)
260
+ self._write_reg(self._REG_BW_RATE, bw)
261
+
262
+ def set_low_power(self, enabled):
263
+ """Enable or disable low-power mode (higher noise)."""
264
+ bw = self._read_reg(self._REG_BW_RATE)
265
+ if enabled:
266
+ bw |= 0x10
267
+ else:
268
+ bw &= ~0x10
269
+ self._write_reg(self._REG_BW_RATE, bw)
270
+
271
+ def set_offset(self, x, y, z):
272
+ """Set per-axis offset in *g*.
273
+
274
+ Offsets are added to the output after filtering; the scale factor is
275
+ 15.6 mg/LSB and the signed value is clamped to ±2 *g* (≈ ±128 LSB).
276
+
277
+ Args:
278
+ x: X offset in *g*.
279
+ y: Y offset in *g*.
280
+ z: Z offset in *g*.
281
+ """
282
+ self._write_reg(self._REG_OFSX, self._encode_offset(x))
283
+ self._write_reg(self._REG_OFSY, self._encode_offset(y))
284
+ self._write_reg(self._REG_OFSZ, self._encode_offset(z))
285
+
286
+ @staticmethod
287
+ def _encode_offset(offset_g):
288
+ raw = int(round(offset_g / 15.6e-3))
289
+ if raw > 127:
290
+ raw = 127
291
+ if raw < -128:
292
+ raw = -128
293
+ return raw & 0xFF
294
+
295
+ def calibrate_offset(self, target_x=0.0, target_y=0.0, target_z=1.0, samples=128):
296
+ """Measure and write per-axis offsets to bring readings onto the targets.
297
+
298
+ Averages ``samples`` reads and computes the residual (target − mean),
299
+ then writes the negative residual into the offset registers. Call
300
+ with the sensor stationary and oriented so the Z axis points up
301
+ (target_z=1 *g*) for a typical gravity-referenced calibration.
302
+
303
+ Args:
304
+ target_x: Expected X reading during calibration (in *g*).
305
+ target_y: Expected Y reading during calibration (in *g*).
306
+ target_z: Expected Z reading during calibration (in *g*).
307
+ samples: Number of samples to average.
308
+ """
309
+ sx = sy = sz = 0.0
310
+ for _ in range(samples):
311
+ x, y, z = self.read()
312
+ sx += x
313
+ sy += y
314
+ sz += z
315
+ _delay_ms(11)
316
+ sx /= samples
317
+ sy /= samples
318
+ sz /= samples
319
+ self.set_offset(target_x - sx, target_y - sy, target_z - sz)
320
+
321
+ def set_tap_detection(self, threshold_g, duration_ms, axes=0x07, suppress=False):
322
+ """Configure single-tap detection and enable the SINGLE_TAP interrupt.
323
+
324
+ Args:
325
+ threshold_g: Tap acceleration threshold in *g* (62.5 mg/LSB).
326
+ duration_ms: Maximum tap duration in ms (625 µs/LSB).
327
+ axes: Bitmask of axes that participate (bit 2=X, 1=Y, 0=Z; default 0x07).
328
+ suppress: Suppress double-tap if acceleration persists between taps.
329
+ """
330
+ self._write_reg(self._REG_THRESH_TAP, int(round(threshold_g / 62.5e-3)) & 0xFF)
331
+ self._write_reg(self._REG_DUR, int(round(duration_ms / 0.625)) & 0xFF)
332
+ tap_axes = (axes & 0x07) | (0x08 if suppress else 0x00)
333
+ self._write_reg(self._REG_TAP_AXES, tap_axes)
334
+ self._enable_interrupt(self.INT_SINGLE_TAP)
335
+
336
+ def set_double_tap(self, latency_ms, window_ms):
337
+ """Configure double-tap latency and window; enables DOUBLE_TAP interrupt.
338
+
339
+ Args:
340
+ latency_ms: Time after a tap when a second tap is expected (1.25 ms/LSB).
341
+ window_ms: Time window after latency during which the second tap must occur (1.25 ms/LSB).
342
+ """
343
+ self._write_reg(self._REG_LATENT, int(round(latency_ms / 1.25)) & 0xFF)
344
+ self._write_reg(self._REG_WINDOW, int(round(window_ms / 1.25)) & 0xFF)
345
+ self._enable_interrupt(self.INT_DOUBLE_TAP)
346
+
347
+ def set_activity(self, threshold_g, axes=0x70, ac_coupled=True):
348
+ """Configure activity detection.
349
+
350
+ Args:
351
+ threshold_g: Activity threshold in *g* (62.5 mg/LSB).
352
+ axes: ACT_INACT_CTL activity bits (bits 7:4: ACT ac/dc, ACT_X
353
+ enable, ACT_Y enable, ACT_Z enable). Default 0x70 =
354
+ ac-coupled, all axes enabled.
355
+ ac_coupled: If True (default), use ac-coupled activity detection
356
+ (filters out the static gravity vector).
357
+ """
358
+ self._write_reg(self._REG_THRESH_ACT, int(round(threshold_g / 62.5e-3)) & 0xFF)
359
+ aic = self._read_reg(self._REG_ACT_INACT_CTL)
360
+ aic &= ~0xF0 # clear ACT ac/dc and ACT axis enables
361
+ if ac_coupled:
362
+ aic |= 0x80
363
+ aic |= axes & 0x70
364
+ self._write_reg(self._REG_ACT_INACT_CTL, aic)
365
+ self._enable_interrupt(self.INT_ACTIVITY)
366
+
367
+ def set_inactivity(self, threshold_g, time_sec, axes=0x07, ac_coupled=False):
368
+ """Configure inactivity detection.
369
+
370
+ Args:
371
+ threshold_g: Inactivity threshold in *g* (62.5 mg/LSB).
372
+ time_sec: Inactivity time in seconds (1 s/LSB).
373
+ axes: ACT_INACT_CTL inactivity bits (bits 3:0: INACT ac/dc,
374
+ INACT_X enable, INACT_Y enable, INACT_Z enable). Default 0x07 =
375
+ dc-coupled, all axes enabled.
376
+ ac_coupled: If True, use ac-coupled inactivity detection.
377
+ """
378
+ self._write_reg(self._REG_THRESH_INACT, int(round(threshold_g / 62.5e-3)) & 0xFF)
379
+ self._write_reg(self._REG_TIME_INACT, int(round(time_sec)) & 0xFF)
380
+ aic = self._read_reg(self._REG_ACT_INACT_CTL)
381
+ aic &= ~0x0F # clear INACT ac/dc and INACT axis enables
382
+ if ac_coupled:
383
+ aic |= 0x08
384
+ aic |= axes & 0x07
385
+ self._write_reg(self._REG_ACT_INACT_CTL, aic)
386
+ self._enable_interrupt(self.INT_INACTIVITY)
387
+
388
+ def set_free_fall(self, threshold_g, time_ms):
389
+ """Configure free-fall detection and enable the FREE_FALL interrupt.
390
+
391
+ Args:
392
+ threshold_g: Free-fall threshold in *g* (62.5 mg/LSB).
393
+ Recommended range 0.3–0.6 *g* (codes 0x05–0x09).
394
+ time_ms: Free-fall time in ms (5 ms/LSB). Recommended 100–350 ms
395
+ (codes 0x14–0x46).
396
+ """
397
+ self._write_reg(self._REG_THRESH_FF, int(round(threshold_g / 62.5e-3)) & 0xFF)
398
+ self._write_reg(self._REG_TIME_FF, int(round(time_ms / 5.0)) & 0xFF)
399
+ self._enable_interrupt(self.INT_FREE_FALL)
400
+
401
+ def set_interrupt(self, source, enabled, pin=1):
402
+ """Enable or disable an interrupt source and route it to INT1 or INT2.
403
+
404
+ Args:
405
+ source: One of the ``INT_*`` constants.
406
+ enabled: True to enable, False to disable.
407
+ pin: 1 (default) for INT1, 2 for INT2.
408
+ """
409
+ ie = self._read_reg(self._REG_INT_ENABLE)
410
+ im = self._read_reg(self._REG_INT_MAP)
411
+ if enabled:
412
+ ie |= source
413
+ if pin == 2:
414
+ im |= source
415
+ else:
416
+ im &= ~source
417
+ else:
418
+ ie &= ~source
419
+ self._write_reg(self._REG_INT_ENABLE, ie)
420
+ self._write_reg(self._REG_INT_MAP, im)
421
+
422
+ def _enable_interrupt(self, source):
423
+ self.set_interrupt(source, True, pin=1)
424
+
425
+ def read_interrupt_source(self):
426
+ """Read the INT_SOURCE register; clears latched interrupts.
427
+
428
+ Returns:
429
+ int: Bitmask of currently active interrupt sources (see ``INT_*``).
430
+ """
431
+ return self._read_reg(self._REG_INT_SOURCE)
432
+
433
+ def set_fifo_mode(self, mode, samples=16):
434
+ """Configure the FIFO.
435
+
436
+ Args:
437
+ mode: One of ``FIFO_BYPASS`` (0x00), ``FIFO_FIFO`` (0x40),
438
+ ``FIFO_STREAM`` (0x80), ``FIFO_TRIGGER`` (0xC0).
439
+ samples: Watermark level for FIFO / Stream mode, or number of
440
+ samples to retain before triggering for Trigger mode.
441
+ """
442
+ fifo_ctl = (mode & 0xC0) | (samples & 0x1F)
443
+ self._write_reg(self._REG_FIFO_CTL, fifo_ctl)
444
+
445
+ def fifo_count(self):
446
+ """Return the number of FIFO entries currently available (0–32)."""
447
+ status = self._read_reg(self._REG_FIFO_STATUS)
448
+ return status & 0x3F
449
+
450
+ def read_fifo(self):
451
+ """Drain the FIFO, returning all available samples.
452
+
453
+ Each sample is the same 6-byte X/Y/Z burst as ``read()`` returns.
454
+
455
+ Returns:
456
+ list[tuple]: List of (x, y, z) tuples in *g*. Empty if FIFO is empty.
457
+ """
458
+ n = self.fifo_count()
459
+ if n == 0:
460
+ return []
461
+ out = []
462
+ for _ in range(n):
463
+ raw = self._read_burst(self._REG_DATAX0, 6)
464
+ rx = raw[0] | (raw[1] << 8)
465
+ ry = raw[2] | (raw[3] << 8)
466
+ rz = raw[4] | (raw[5] << 8)
467
+ if rx & 0x8000:
468
+ rx -= 0x10000
469
+ if ry & 0x8000:
470
+ ry -= 0x10000
471
+ if rz & 0x8000:
472
+ rz -= 0x10000
473
+ if self._full_res:
474
+ scale = _FULL_RES_SCALE_G_PER_LSB
475
+ else:
476
+ scale = _10BIT_SCALE_G_PER_LSB[self._range_bits & 0x03]
477
+ out.append((rx * scale, ry * scale, rz * scale))
478
+ return out
479
+
480
+ def set_sleep(self, enabled, wakeup_hz=8):
481
+ """Enter or leave sleep mode.
482
+
483
+ Args:
484
+ enabled: True to sleep, False to wake.
485
+ wakeup_hz: Sample rate during sleep (8 / 4 / 2 / 1 Hz).
486
+ """
487
+ pwr = self._read_reg(self._REG_POWER_CTL)
488
+ if enabled:
489
+ wakeup_code = {8: self.WAKEUP_8_HZ, 4: self.WAKEUP_4_HZ,
490
+ 2: self.WAKEUP_2_HZ, 1: self.WAKEUP_1_HZ}.get(wakeup_hz)
491
+ if wakeup_code is None:
492
+ raise ValueError('wakeup_hz must be 8, 4, 2, or 1')
493
+ pwr = (pwr & ~0x06) | wakeup_code | 0x08 # keep Measure bit set
494
+ pwr |= 0x04 # Sleep=1
495
+ else:
496
+ pwr &= ~0x04
497
+ self._write_reg(self._REG_POWER_CTL, pwr)
498
+
499
+ def set_link_mode(self, enabled):
500
+ """Enable or disable the activity/inactivity serial-link mode."""
501
+ pwr = self._read_reg(self._REG_POWER_CTL)
502
+ if enabled:
503
+ pwr |= 0x40
504
+ else:
505
+ pwr &= ~0x40
506
+ self._write_reg(self._REG_POWER_CTL, pwr)
507
+
508
+ def set_auto_sleep(self, enabled):
509
+ """Enable or disable auto-sleep on inactivity (requires Link=1)."""
510
+ pwr = self._read_reg(self._REG_POWER_CTL)
511
+ if enabled:
512
+ pwr |= 0x20
513
+ else:
514
+ pwr &= ~0x20
515
+ self._write_reg(self._REG_POWER_CTL, pwr)
516
+
517
+ def self_test(self, enabled):
518
+ """Enable or disable the electrostatic self-test force on all axes."""
519
+ df = self._read_reg(self._REG_DATA_FORMAT)
520
+ if enabled:
521
+ df |= 0x80
522
+ else:
523
+ df &= ~0x80
524
+ self._write_reg(self._REG_DATA_FORMAT, df)