Skip to content

Commit b3e20d9

Browse files
committed
Port to new i2cdevice API, tests and fixes
1 parent 9cda2e5 commit b3e20d9

3 files changed

Lines changed: 139 additions & 74 deletions

File tree

library/max30105/__init__.py

Lines changed: 62 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def _encode(self, value):
3434
try:
3535
return self.LOOKUP.index(value)
3636
except ValueError:
37-
return 0
37+
raise ValueError('Invalid slot mode {}'.format(value))
3838

3939

4040
class PulseAmplitudeAdapter(Adapter):
@@ -303,7 +303,7 @@ def __init__(self, i2c_addr=I2C_ADDRESS, i2c_dev=None):
303303
), bit_width=16)
304304
))
305305

306-
def setup(self, led_power=6.4, sample_average=4, leds_enable=3, sample_rate=400, pulse_width=215, adc_range=16384):
306+
def setup(self, led_power=6.4, sample_average=4, leds_enable=3, sample_rate=400, pulse_width=215, adc_range=16384, timeout=5.0):
307307
"""Set up the sensor."""
308308
if self._is_setup:
309309
return
@@ -313,62 +313,55 @@ def setup(self, led_power=6.4, sample_average=4, leds_enable=3, sample_rate=400,
313313

314314
self._max30105.select_address(self._i2c_addr)
315315

316-
self.soft_reset()
316+
self.soft_reset(timeout=timeout)
317317

318-
with self._max30105.FIFO_CONFIG as FIFO_CONFIG:
319-
# Average over 4 samples (the default value)
320-
FIFO_CONFIG.set_sample_average(sample_average)
321-
# Enable sample rollover
322-
FIFO_CONFIG.set_fifo_rollover_en(True)
323-
FIFO_CONFIG.write()
318+
self._max30105.set('FIFO_CONFIG',
319+
sample_average=sample_average,
320+
fifo_rollover_en=True)
324321

325-
with self._max30105.SPO2_CONFIG as SPO2_CONFIG:
326-
# Set the sample rate to 50 samples per second
327-
SPO2_CONFIG.set_sample_rate_sps(sample_rate)
328-
# And the ADC range to 16384
329-
SPO2_CONFIG.set_adc_range_nA(adc_range)
330-
# And the pulse width to 411us
331-
SPO2_CONFIG.set_led_pw_us(pulse_width)
332-
SPO2_CONFIG.write()
322+
self._max30105.set('SPO2_CONFIG',
323+
sample_rate_sps=sample_rate,
324+
adc_range_nA=adc_range,
325+
led_pw_us=pulse_width)
333326

334-
with self._max30105.LED_PULSE_AMPLITUDE as LPA:
335-
LPA.set_led1_mA(led_power)
336-
LPA.set_led2_mA(led_power)
337-
LPA.set_led3_mA(led_power)
338-
LPA.write()
327+
self._max30105.set('LED_PULSE_AMPLITUDE',
328+
led1_mA=led_power,
329+
led2_mA=led_power,
330+
led3_mA=led_power)
339331

340-
self._max30105.LED_PROX_PULSE_AMPLITUDE.set_pilot_mA(led_power)
332+
self._max30105.set('LED_PROX_PULSE_AMPLITUDE', pilot_mA=led_power)
341333

342334
# Set the LED mode based on the number of LEDs we want enabled
343-
self._max30105.MODE_CONFIG.set_mode(['red_only', 'red_ir', 'green_red_ir'][leds_enable - 1])
335+
self._max30105.set('MODE_CONFIG',
336+
mode=['red_only', 'red_ir', 'green_red_ir'][leds_enable - 1])
344337

345338
# Set up the LEDs requested in sequential slots
346-
with self._max30105.LED_MODE_CONTROL as LMC:
347-
LMC.set_slot1('red')
348-
if leds_enable >= 2:
349-
LMC.set_slot2('ir')
350-
if leds_enable >= 3:
351-
LMC.set_slot3('green')
352-
LMC.write()
339+
self._max30105.set('LED_MODE_CONTROL',
340+
slot1='red',
341+
slot2='ir' if leds_enable >= 2 else 'off',
342+
slot3='green' if leds_enable >= 3 else 'off')
353343

354344
self.clear_fifo()
355345

356-
def soft_reset(self):
346+
def soft_reset(self, timeout=5.0):
357347
"""Reset device."""
358-
self._max30105.MODE_CONFIG.set_reset(True)
359-
while self._max30105.MODE_CONFIG.get_reset():
348+
self._max30105.set('MODE_CONFIG', reset=True)
349+
t_start = time.time()
350+
while self._max30105.get('MODE_CONFIG').reset and time.time() - t_start < timeout:
360351
time.sleep(0.001)
352+
if self._max30105.get('MODE_CONFIG').reset:
353+
raise RuntimeError("Timeout: Failed to soft reset MAX30105.")
361354

362355
def clear_fifo(self):
363356
"""Clear samples FIFO."""
364-
self._max30105.FIFO_READ.set_pointer(0)
365-
self._max30105.FIFO_WRITE.set_pointer(0)
366-
self._max30105.FIFO_OVERFLOW.set_counter(0)
357+
self._max30105.set('FIFO_READ', pointer=0)
358+
self._max30105.set('FIFO_WRITE', pointer=0)
359+
self._max30105.set('FIFO_OVERFLOW', counter=0)
367360

368361
def get_samples(self):
369362
"""Return contents of sample FIFO."""
370-
ptr_r = self._max30105.FIFO_READ.get_pointer()
371-
ptr_w = self._max30105.FIFO_WRITE.get_pointer()
363+
ptr_r = self._max30105.get('FIFO_READ').pointer
364+
ptr_w = self._max30105.get('FIFO_WRITE').pointer
372365

373366
if ptr_r == ptr_w:
374367
return None
@@ -397,28 +390,30 @@ def get_chip_id(self):
397390
"""Return the revision and part IDs."""
398391
self.setup()
399392

400-
revision = self._max30105.PART_ID.get_revision()
401-
part = self._max30105.PART_ID.get_part()
393+
part_id = self._max30105.get('PART_ID')
402394

403-
return revision, part
395+
return part_id.revision, part_id.part
404396

405-
def get_temperature(self):
397+
def get_temperature(self, timeout=5.0):
406398
"""Return the die temperature."""
407399
self.setup()
408400

409-
self._max30105.DIE_TEMP_CONFIG.set_temp_en(True)
410-
self._max30105.INT_ENABLE_2.set_die_temp_ready_en(True)
411-
while not self._max30105.INT_STATUS_2.get_die_temp_ready():
401+
self._max30105.set('DIE_TEMP_CONFIG', temp_en=True)
402+
self._max30105.set('INT_ENABLE_2', die_temp_ready_en=True)
403+
t_start = time.time()
404+
while not self._max30105.get('INT_STATUS_2').die_temp_ready and time.time() - t_start < timeout:
412405
time.sleep(0.01)
413-
return self._max30105.DIE_TEMP.get_temperature()
406+
if not self._max30105.get('INT_STATUS_2').die_temp_ready:
407+
raise RuntimeError('Timeout: Waiting for INT_STATUS_2, die_temp_ready.')
408+
return self._max30105.get('DIE_TEMP').temperature
414409

415410
def set_mode(self, mode):
416411
"""Set the sensor mode.
417412
418413
:param mode: Mode, either red_only, red_ir or green_red_ir
419414
420415
"""
421-
self._max30105.MODE_CONFIG.set_mode(mode)
416+
self._max30105.set('MODE_CONFIG', mode=mode)
422417

423418
def set_slot_mode(self, slot, mode):
424419
"""Set the mode of a single slot.
@@ -428,13 +423,13 @@ def set_slot_mode(self, slot, mode):
428423
429424
"""
430425
if slot == 1:
431-
self._max30105.LED_MODE_CONTROL.set_slot1(mode)
426+
self._max30105.set('LED_MODE_CONTROL', slot1=mode)
432427
elif slot == 2:
433-
self._max30105.LED_MODE_CONTROL.set_slot2(mode)
428+
self._max30105.set('LED_MODE_CONTROL', slot2=mode)
434429
elif slot == 3:
435-
self._max30105.LED_MODE_CONTROL.set_slot3(mode)
430+
self._max30105.set('LED_MODE_CONTROL', slot3=mode)
436431
elif slot == 4:
437-
self._max30105.LED_MODE_CONTROL.set_slot4(mode)
432+
self._max30105.set('LED_MODE_CONTROL', slot4=mode)
438433
else:
439434
raise ValueError("Invalid LED slot: {}".format(slot))
440435

@@ -446,11 +441,11 @@ def set_led_pulse_amplitude(self, led, amplitude):
446441
447442
"""
448443
if led == 1:
449-
self._max30105.LED_PULSE_AMPLITUDE.set_led1_mA(amplitude)
444+
self._max30105.set('LED_PULSE_AMPLITUDE', led1_mA=amplitude)
450445
elif led == 2:
451-
self._max30105.LED_PULSE_AMPLITUDE.set_led2_mA(amplitude)
446+
self._max30105.set('LED_PULSE_AMPLITUDE', led2_mA=amplitude)
452447
elif led == 3:
453-
self._max30105.LED_PULSE_AMPLITUDE.set_led3_mA(amplitude)
448+
self._max30105.set('LED_PULSE_AMPLITUDE', led3_mA=amplitude)
454449
else:
455450
raise ValueError("Invalid LED: {}".format(led))
456451

@@ -460,23 +455,23 @@ def set_fifo_almost_full_count(self, count):
460455
:param count: Count of remaining samples, from 0 to 15
461456
462457
"""
463-
self._max30105.FIFO_CONFIG.set_fifo_almost_full(count)
458+
self._max30105.set('FIFO_CONFIG', fifo_almost_full=count)
464459

465460
def set_fifo_almost_full_enable(self, value):
466461
"""Enable the FIFO-almost-full flag."""
467-
self._max30105.INT_ENABLE_1.set_a_full_en(value)
462+
self._max30105.set('INT_ENABLE_1', a_full_en=value)
468463

469464
def set_data_ready_enable(self, value):
470465
"""Enable the data-ready flag."""
471-
self._max30105.INT_ENABLE_1.set_data_ready_en(value)
466+
self._max30105.set('INT_ENABLE_1', data_ready_en=value)
472467

473468
def set_ambient_light_compensation_overflow_enable(self, value):
474469
"""Enable the ambient light compensation overflow flag."""
475-
self._max30105.INT_ENABLE_1.set_alc_overflow_en(value)
470+
self._max30105.set('INT_ENABLE_1', alc_overflow_en=value)
476471

477472
def set_proximity_enable(self, value):
478473
"""Enable the proximity interrupt flag."""
479-
self._max30105.INT_ENABLE_1.set_prox_int_en(value)
474+
self._max30105.set('INT_ENABLE_1', prox_int_en=value)
480475

481476
def set_proximity_threshold(self, value):
482477
"""Set the threshold of the proximity sensor.
@@ -486,7 +481,7 @@ def set_proximity_threshold(self, value):
486481
:param value: threshold value from 0 to 255
487482
488483
"""
489-
self._max30105.PROX_INT_THRESHOLD.set_threshold(value)
484+
self._max30105.set('PROX_INT_THRESHOLD', threshold=value)
490485

491486
def get_fifo_almost_full_status(self):
492487
"""Get the FIFO-almost-full flag.
@@ -496,7 +491,7 @@ def get_fifo_almost_full_status(self):
496491
The flag is cleared upon read.
497492
498493
"""
499-
return self._max30105.INT_STATUS_1.get_a_full()
494+
return self._max30105.get('INT_STATUS_1').a_full
500495

501496
def get_data_ready_status(self):
502497
"""Get the data-ready flag.
@@ -506,7 +501,7 @@ def get_data_ready_status(self):
506501
This flag is cleared upon read, or upon `get_samples()`
507502
508503
"""
509-
return self._max30105.INT_STATUS_1.get_data_ready()
504+
return self._max30105.get('INT_STATUS_1').data_ready
510505

511506
def get_ambient_light_compensation_overflow_status(self):
512507
"""Get the ambient light compensation overflow status flag.
@@ -516,7 +511,7 @@ def get_ambient_light_compensation_overflow_status(self):
516511
This flag is cleared upon read.
517512
518513
"""
519-
return self._max30105.INT_STATUS_1.get_data_ready()
514+
return self._max30105.get('INT_STATUS_1').alc_overflow
520515

521516
def get_proximity_triggered_threshold_status(self):
522517
"""Get the proximity triggered threshold status flag.
@@ -526,15 +521,15 @@ def get_proximity_triggered_threshold_status(self):
526521
This flag is cleared upon read.
527522
528523
"""
529-
return self._max30105.INT_STATUS_1.get_prox_int()
524+
return self._max30105.get('INT_STATUS_1').prox_int
530525

531526
def get_power_ready_status(self):
532527
"""Get the power ready status flag.
533528
534529
Returns True if the sensor has successfully powered up and is ready to collect data.
535530
536531
"""
537-
return self._max30105.INT_STATUS_1.get_pwr_ready()
532+
return self._max30105.get('INT_STATUS_1').pwr_ready
538533

539534
def get_die_temp_ready_status(self):
540535
"""Get the die temperature ready flag.
@@ -544,4 +539,4 @@ def get_die_temp_ready_status(self):
544539
This flag is cleared upon read, or upon `get_temperature`.
545540
546541
"""
547-
return self._max30105.INT_STATUS_2.get_die_temp_ready()
542+
return self._max30105.get('INT_STATUS_2').die_temp_ready

library/tests/test_features.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from i2cdevice import MockSMBus
2+
import pytest
3+
4+
5+
class MockSMBusNoTimeout(MockSMBus):
6+
def write_i2c_block_data(self, i2c_address, register, values):
7+
# Prevent the reset bit from being written
8+
# simulating an immediate soft reset success
9+
if register == 0x09:
10+
values[0] &= ~0b01000000
11+
MockSMBus.write_i2c_block_data(self, i2c_address, register, values)
12+
13+
14+
def test_get_chip_id():
15+
from max30105 import MAX30105
16+
max30105 = MAX30105(i2c_dev=MockSMBusNoTimeout(1, default_registers={0x09: 0b00000111}))
17+
assert max30105.get_chip_id() == (0, 0)
18+
19+
20+
def test_get_temperature():
21+
from max30105 import MAX30105
22+
max30105 = MAX30105(i2c_dev=MockSMBusNoTimeout(1, default_registers={
23+
0x01: 0b00000010, # Die temp ready
24+
0x09: 0b00000111 # Hard default value to avoid error
25+
}))
26+
assert max30105.get_temperature() == 0
27+
28+
29+
def test_get_status():
30+
from max30105 import MAX30105
31+
max30105 = MAX30105(i2c_dev=MockSMBusNoTimeout(1, default_registers={
32+
0x01: 0b00000010, # Die temp ready
33+
0x09: 0b00000111 # Hard default value to avoid error
34+
}))
35+
36+
# We don't really care about the values,
37+
# just that these don't fail due to some typo
38+
max30105.get_data_ready_status()
39+
max30105.get_die_temp_ready_status()
40+
max30105.get_fifo_almost_full_status()
41+
max30105.get_ambient_light_compensation_overflow_status()
42+
max30105.get_power_ready_status()
43+
max30105.get_proximity_triggered_threshold_status()
44+
45+
46+
def test_set_slot_mode():
47+
from max30105 import MAX30105
48+
max30105 = MAX30105(i2c_dev=MockSMBusNoTimeout(1, default_registers={
49+
0x01: 0b00000010, # Die temp ready
50+
0x09: 0b00000111 # Hard default value to avoid error
51+
}))
52+
53+
max30105.set_slot_mode(1, 'green')
54+
max30105.set_slot_mode(1, 'red')
55+
56+
with pytest.raises(ValueError):
57+
max30105.set_slot_mode(1, 'puce')

library/tests/test_setup.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
1-
import sys
2-
import mock
1+
from i2cdevice import MockSMBus
2+
import pytest
3+
4+
5+
class MockSMBusNoTimeout(MockSMBus):
6+
def write_i2c_block_data(self, i2c_address, register, values):
7+
# Prevent the reset bit from being written
8+
# simulating an immediate soft reset success
9+
if register == 0x09:
10+
values[0] &= ~0b01000000
11+
MockSMBus.write_i2c_block_data(self, i2c_address, register, values)
312

413

514
def test_setup():
6-
sys.modules['smbus'] = mock.Mock()
7-
sys.modules['RPi'] = mock.Mock()
8-
sys.modules['RPi.GPIO'] = mock.Mock()
15+
from max30105 import MAX30105
16+
max30105 = MAX30105(i2c_dev=MockSMBusNoTimeout(1, default_registers={0x09: 0b00000111}))
17+
max30105.setup()
918

19+
20+
def test_setup_timeout():
1021
from max30105 import MAX30105
11-
max30105 = MAX30105()
12-
del max30105
22+
max30105 = MAX30105(i2c_dev=MockSMBus(1, default_registers={0x09: 0b00000111}))
23+
24+
with pytest.raises(RuntimeError):
25+
max30105.setup(timeout=0.5)

0 commit comments

Comments
 (0)