├── library ├── .coveragerc ├── MANIFEST.in ├── setup.cfg ├── CHANGELOG.txt ├── tox.ini ├── tests │ ├── test_setup.py │ └── test_features.py ├── LICENSE.txt ├── README.md ├── test.py ├── setup.py └── max30105 │ └── __init__.py ├── .stickler.yml ├── .gitignore ├── examples ├── test.py ├── get-temperature.py ├── read-heartbeat.py ├── graph-heartbeat.py └── detect-particles.py ├── install.sh ├── uninstall.sh ├── .github └── workflows │ └── test.yml ├── LICENSE ├── README.md └── Makefile /library/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = max30105 3 | omit = 4 | .tox/* 5 | -------------------------------------------------------------------------------- /.stickler.yml: -------------------------------------------------------------------------------- 1 | --- 2 | linters: 3 | flake8: 4 | python: 3 5 | max-line-length: 160 6 | -------------------------------------------------------------------------------- /library/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGELOG.txt 2 | include LICENSE.txt 3 | include README.md 4 | include setup.py 5 | recursive-include max30105 *.py 6 | -------------------------------------------------------------------------------- /library/setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = 3 | test.py 4 | .tox, 5 | .eggs, 6 | .git, 7 | __pycache__, 8 | build, 9 | dist 10 | ignore = 11 | E501 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | _build/ 3 | *.o 4 | *.so 5 | *.a 6 | *.py[cod] 7 | *.egg-info 8 | dist/ 9 | __pycache__ 10 | .DS_Store 11 | *.deb 12 | *.dsc 13 | *.build 14 | *.changes 15 | *.orig.* 16 | packaging/*tar.xz 17 | library/debian/ 18 | .coverage 19 | .pytest_cache 20 | .tox 21 | -------------------------------------------------------------------------------- /examples/test.py: -------------------------------------------------------------------------------- 1 | import smbus2 2 | import time 3 | 4 | bus = smbus2.SMBus(1) 5 | 6 | while True: 7 | bus.write_byte_data(0x57, 0x03, 0x02) 8 | bus.write_byte_data(0x57, 0x21, 0x01) 9 | while bus.read_byte_data(0x57, 0x01) == 0: 10 | print("Waiting...") 11 | time.sleep(0.5) 12 | print("read") 13 | 14 | -------------------------------------------------------------------------------- /library/CHANGELOG.txt: -------------------------------------------------------------------------------- 1 | 0.0.5 2 | ----- 3 | 4 | * Bugfix: fix off-by-one error in sample average bitmask 5 | 6 | 0.0.4 7 | ----- 8 | 9 | * Bugfix: fix timeout bug in get_temperature(), requires i2cdevice>=0.0.7 10 | 11 | 0.0.3 12 | ----- 13 | 14 | * Bugfix: support 0 init value of MODE_CONFIG register 15 | 16 | 0.0.2 17 | ----- 18 | 19 | * Port to i2cdevice>=0.0.6 set/get API 20 | 21 | 0.0.1 22 | ----- 23 | 24 | * Initial Release 25 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | printf "MAX301015 Python Library: Installer\n\n" 4 | 5 | if [ $(id -u) -ne 0 ]; then 6 | printf "Script must be run as root. Try 'sudo ./install.sh'\n" 7 | exit 1 8 | fi 9 | 10 | cd library 11 | 12 | printf "Installing for Python 2..\n" 13 | python setup.py install 14 | 15 | if [ -f "/usr/bin/python3" ]; then 16 | printf "Installing for Python 3..\n" 17 | python3 setup.py install 18 | fi 19 | 20 | cd .. 21 | 22 | printf "Done!\n" 23 | -------------------------------------------------------------------------------- /uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PACKAGE="max30105" 4 | 5 | printf "MAX30105 Python Library: Uninstaller\n\n" 6 | 7 | if [ $(id -u) -ne 0 ]; then 8 | printf "Script must be run as root. Try 'sudo ./uninstall.sh'\n" 9 | exit 1 10 | fi 11 | 12 | cd library 13 | 14 | printf "Unnstalling for Python 2..\n" 15 | pip uninstall $PACKAGE 16 | 17 | if [ -f "/usr/bin/pip3" ]; then 18 | printf "Uninstalling for Python 3..\n" 19 | pip3 uninstall $PACKAGE 20 | fi 21 | 22 | cd .. 23 | 24 | printf "Done!\n" 25 | -------------------------------------------------------------------------------- /library/tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py{27,35,37,38},qa 3 | skip_missing_interpreters = True 4 | 5 | [testenv] 6 | commands = 7 | python setup.py install 8 | coverage run -m py.test -v -r wsx 9 | coverage report 10 | deps = 11 | mock 12 | pytest>=3.1 13 | pytest-cov 14 | 15 | [testenv:qa] 16 | commands = 17 | check-manifest --ignore test.py,tox.ini,tests/*,.coveragerc 18 | python setup.py sdist bdist_wheel 19 | twine check dist/* 20 | flake8 --ignore E501 21 | deps = 22 | check-manifest 23 | flake8 24 | twine 25 | -------------------------------------------------------------------------------- /examples/get-temperature.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # NOTE! This code should not be used as the basis for a real 4 | # smoke or fire detector, or in life-critical situations. It's 5 | # for fun/novelty use only, so bear that in mind while using it. 6 | 7 | import time 8 | import datetime 9 | from max30105 import MAX30105 10 | 11 | max30105 = MAX30105() 12 | max30105.setup(leds_enable=0) 13 | 14 | delay = 10 15 | 16 | print("Starting readings in {} seconds...\n".format(delay)) 17 | time.sleep(delay) 18 | 19 | try: 20 | while True: 21 | temp = max30105.get_temperature() 22 | print("{:.2f}\n".format(temp)) 23 | time.sleep(1.0) 24 | 25 | except KeyboardInterrupt: 26 | pass 27 | -------------------------------------------------------------------------------- /library/tests/test_setup.py: -------------------------------------------------------------------------------- 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_setup(): 15 | from max30105 import MAX30105 16 | max30105 = MAX30105(i2c_dev=MockSMBusNoTimeout(1, default_registers={0x09: 0b00000111})) 17 | max30105.setup() 18 | 19 | 20 | def test_setup_timeout(): 21 | from max30105 import MAX30105 22 | max30105 = MAX30105(i2c_dev=MockSMBus(1, default_registers={0x09: 0b00000111})) 23 | 24 | with pytest.raises(RuntimeError): 25 | max30105.setup(timeout=0.5) 26 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Python Tests 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | python: [2.7, 3.6, 3.7, 3.8] 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Set up Python ${{ matrix.python }} 19 | uses: actions/setup-python@v2 20 | with: 21 | python-version: ${{ matrix.python }} 22 | - name: Install Dependencies 23 | run: | 24 | python -m pip install --upgrade setuptools tox 25 | - name: Run Tests 26 | working-directory: library 27 | run: | 28 | tox -e py 29 | - name: Coverage 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | working-directory: library 33 | run: | 34 | python -m pip install coveralls 35 | coveralls --service=github 36 | if: ${{ matrix.python == '3.8' }} 37 | 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Pimoroni Ltd. 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /library/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Pimoroni Ltd. 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MAX30105 - Heart Rate, Oximeter, Smoke Sensor 2 | 3 | [![Build Status](https://travis-ci.com/pimoroni/max30105-python.svg?branch=master)](https://travis-ci.com/pimoroni/max30105-python) 4 | [![Coverage Status](https://coveralls.io/repos/github/pimoroni/max30105-python/badge.svg?branch=master)](https://coveralls.io/github/pimoroni/max30105-python?branch=master) 5 | [![PyPi Package](https://img.shields.io/pypi/v/max30105.svg)](https://pypi.python.org/pypi/max30105) 6 | [![Python Versions](https://img.shields.io/pypi/pyversions/max30105.svg)](https://pypi.python.org/pypi/max30105) 7 | 8 | The MAX30105 is an precision optical sensor that can be used to measure heart rate, pulse oximetry (SPO2 / blood oxygen saturation), and smoke (and other particles). 9 | 10 | # Installing 11 | 12 | Stable library from PyPi: 13 | 14 | * Just run `sudo pip install max30105` 15 | 16 | Latest/development library from GitHub: 17 | 18 | * `git clone https://github.com/pimoroni/max30105-python` 19 | * `cd max30105-python` 20 | * `sudo ./install.sh` 21 | 22 | # Important! 23 | 24 | **This code should not be used for medical diagnosis, as the basis for a real smoke or fire detector, or in life-critical situations. It's for fun/novelty use only, so bear that in mind while using it.** 25 | -------------------------------------------------------------------------------- /library/README.md: -------------------------------------------------------------------------------- 1 | # MAX30105 - Heart Rate, Oximeter, Smoke Sensor 2 | 3 | [![Build Status](https://travis-ci.com/pimoroni/max30105-python.svg?branch=master)](https://travis-ci.com/pimoroni/max30105-python) 4 | [![Coverage Status](https://coveralls.io/repos/github/pimoroni/max30105-python/badge.svg?branch=master)](https://coveralls.io/github/pimoroni/max30105-python?branch=master) 5 | [![PyPi Package](https://img.shields.io/pypi/v/max30105.svg)](https://pypi.python.org/pypi/max30105) 6 | [![Python Versions](https://img.shields.io/pypi/pyversions/max30105.svg)](https://pypi.python.org/pypi/max30105) 7 | 8 | The MAX30105 is an precision optical sensor that can be used to measure heart rate, pulse oximetry (SPO2 / blood oxygen saturation), and smoke (and other particles). 9 | 10 | # Installing 11 | 12 | Stable library from PyPi: 13 | 14 | * Just run `sudo pip install max30105` 15 | 16 | Latest/development library from GitHub: 17 | 18 | * `git clone https://github.com/pimoroni/max30105-python` 19 | * `cd max30105-python` 20 | * `sudo ./install.sh` 21 | 22 | # Important! 23 | 24 | **This code should not be used for medical diagnosis, as the basis for a real smoke or fire detector, or in life-critical situations. It's for fun/novelty use only, so bear that in mind while using it.** 25 | 26 | # Changelog 27 | 28 | -------------------------------------------------------------------------------- /library/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import time 4 | from max30105 import MAX30105, HeartRate 5 | 6 | max30105 = MAX30105() 7 | max30105.setup(leds_enable=3) 8 | 9 | max30105.set_led_pulse_amplitude(1, 0) 10 | max30105.set_led_pulse_amplitude(2, 0) 11 | max30105.set_led_pulse_amplitude(3, 0) 12 | 13 | max30105.set_slot_mode(1, 'red') 14 | max30105.set_slot_mode(2, 'ir') 15 | max30105.set_slot_mode(3, 'green') 16 | max30105.set_slot_mode(4, 'off') 17 | 18 | colours = {"red": 1, "ir": 2, "green": 3} 19 | 20 | hr = HeartRate(max30105) 21 | 22 | try: 23 | print("Temperature: {:.2f}C".format(max30105.get_temperature())) 24 | 25 | for c in colours: 26 | print("\nLighting {} LED".format(c.upper())) 27 | max30105.set_led_pulse_amplitude(colours[c], 12.5) 28 | time.sleep(0.5) 29 | print("Reading {} LED".format(c.upper())) 30 | i = 0 31 | 32 | while i < 10: 33 | samples = max30105.get_samples() 34 | if samples is not None: 35 | ir = samples[colours[c] - 1] & 0xff 36 | d = hr.low_pass_fir(ir) 37 | print(d) 38 | time.sleep(0.1) 39 | i += 1 40 | 41 | max30105.set_led_pulse_amplitude(colours[c], 0.0) 42 | 43 | print("\nTEST COMPLETE!!!") 44 | 45 | except KeyboardInterrupt: 46 | pass 47 | -------------------------------------------------------------------------------- /examples/read-heartbeat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # NOTE! This code should not be used for medical diagnosis. It's 4 | # for fun/novelty use only, so bear that in mind while using it. 5 | 6 | import time 7 | from max30105 import MAX30105, HeartRate 8 | 9 | max30105 = MAX30105() 10 | max30105.setup(leds_enable=2) 11 | 12 | max30105.set_led_pulse_amplitude(1, 0.2) 13 | max30105.set_led_pulse_amplitude(2, 12.5) 14 | max30105.set_led_pulse_amplitude(3, 0) 15 | 16 | max30105.set_slot_mode(1, 'red') 17 | max30105.set_slot_mode(2, 'ir') 18 | max30105.set_slot_mode(3, 'off') 19 | max30105.set_slot_mode(4, 'off') 20 | 21 | 22 | def display_heartrate(beat, bpm, avg_bpm): 23 | print("{} BPM: {:.2f} AVG: {:.2f}".format("<3" if beat else " ", 24 | bpm, avg_bpm)) 25 | 26 | 27 | hr = HeartRate(max30105) 28 | 29 | print(""" 30 | NOTE! This code should not be used for medical diagnosis. It's 31 | for fun/novelty use only, so bear that in mind while using it. 32 | 33 | This example shows a readout of your heart rate in BPM (beats per 34 | minute) and heartbeats detected using a heart emoticon <3. 35 | 36 | It's best to hold the sensor against your fingertip (the fleshy side) 37 | using a piece of wire or a rubber band looped through the mounting 38 | holes on the breakout, as the sensor is very sensitive to small 39 | movements and it's hard to hold your finger against the sensor with 40 | even pressure. 41 | 42 | If you're using your MAX30105 Breakout with Breakout Garden, then 43 | we'd recommend using one of our Breakout Garden Extender Kits with 44 | some female-to-female jumper jerky. 45 | 46 | https://shop.pimoroni.com/products/breakout-garden-extender-kit 47 | """) 48 | 49 | delay = 10 50 | 51 | print("Starting readings in {} seconds...\n".format(delay)) 52 | time.sleep(delay) 53 | 54 | try: 55 | hr.on_beat(display_heartrate, average_over=4) 56 | except KeyboardInterrupt: 57 | pass 58 | -------------------------------------------------------------------------------- /examples/graph-heartbeat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # NOTE! This code should not be used for medical diagnosis. It's 4 | # for fun/novelty use only, so bear that in mind while using it. 5 | 6 | import time 7 | from max30105 import MAX30105, HeartRate 8 | 9 | max30105 = MAX30105() 10 | max30105.setup(leds_enable=2) 11 | 12 | max30105.set_led_pulse_amplitude(1, 0.2) 13 | max30105.set_led_pulse_amplitude(2, 12.5) 14 | max30105.set_led_pulse_amplitude(3, 0) 15 | 16 | max30105.set_slot_mode(1, 'red') 17 | max30105.set_slot_mode(2, 'ir') 18 | max30105.set_slot_mode(3, 'off') 19 | max30105.set_slot_mode(4, 'off') 20 | 21 | hr = HeartRate(max30105) 22 | 23 | print(""" 24 | NOTE! This code should not be used for medical diagnosis. It's 25 | for fun/novelty use only, so bear that in mind while using it. 26 | 27 | This example shows your pulse (using photoplethysmography) as a 28 | histogram. 29 | 30 | It's best to hold the sensor against your fingertip (the fleshy side) 31 | using a piece of wire or a rubber band looped through the mounting 32 | holes on the breakout, as the sensor is very sensitive to small 33 | movements and it's hard to hold your finger against the sensor with 34 | even pressure. 35 | 36 | If you're using your MAX30105 Breakout with Breakout Garden, then 37 | we'd recommend using one of our Breakout Garden Extender Kits with 38 | some female to female jumper jerky. 39 | 40 | https://shop.pimoroni.com/products/breakout-garden-extender-kit 41 | """) 42 | 43 | delay = 10 44 | 45 | print("Starting readings in {} seconds...\n".format(delay)) 46 | time.sleep(delay) 47 | 48 | try: 49 | while True: 50 | samples = max30105.get_samples() 51 | if samples is not None: 52 | for i in range(0, len(samples), 2): 53 | # Process the least significant byte, where most wiggling is 54 | ir = samples[i + 1] & 0xff 55 | d = hr.low_pass_fir(ir) 56 | 57 | print("#" * int(d / 2)) 58 | time.sleep(1.0 / 100) # 400sps 4 sample averaging = 100sps 59 | 60 | except KeyboardInterrupt: 61 | pass 62 | -------------------------------------------------------------------------------- /library/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Copyright (c) 2019 Pimoroni 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 | of the Software, and to permit persons to whom the Software is furnished to do 11 | so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | 25 | try: 26 | from setuptools import setup 27 | except ImportError: 28 | from distutils.core import setup 29 | 30 | classifiers = ['Development Status :: 4 - Beta', 31 | 'Operating System :: POSIX :: Linux', 32 | 'License :: OSI Approved :: MIT License', 33 | 'Intended Audience :: Developers', 34 | 'Programming Language :: Python :: 2.6', 35 | 'Programming Language :: Python :: 2.7', 36 | 'Programming Language :: Python :: 3', 37 | 'Topic :: Software Development', 38 | 'Topic :: System :: Hardware'] 39 | 40 | setup( 41 | name='max30105', 42 | version='0.0.5', 43 | author='Philip Howard', 44 | author_email='phil@pimoroni.com', 45 | description="""Python library for the MAX30105 Smoke/Pulse Detector""", 46 | long_description=open('README.md').read() + '\n' + open('CHANGELOG.txt').read(), 47 | long_description_content_type="text/markdown", 48 | license='MIT', 49 | keywords='Raspberry Pi', 50 | url='http://www.pimoroni.com', 51 | classifiers=classifiers, 52 | packages=['max30105'], 53 | install_requires=['i2cdevice>=0.0.7'] 54 | ) 55 | -------------------------------------------------------------------------------- /library/tests/test_features.py: -------------------------------------------------------------------------------- 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_temperature_timeout(): 30 | from max30105 import MAX30105 31 | max30105 = MAX30105(i2c_dev=MockSMBusNoTimeout(1, default_registers={ 32 | 0x01: 0b00000000, # Die temp NOT ready 33 | 0x09: 0b00000111 # Hard default value to avoid error 34 | })) 35 | 36 | with pytest.raises(RuntimeError): 37 | max30105.get_temperature() 38 | 39 | 40 | def test_get_status(): 41 | from max30105 import MAX30105 42 | max30105 = MAX30105(i2c_dev=MockSMBusNoTimeout(1, default_registers={ 43 | 0x01: 0b00000010, # Die temp ready 44 | 0x09: 0b00000111 # Hard default value to avoid error 45 | })) 46 | 47 | # We don't really care about the values, 48 | # just that these don't fail due to some typo 49 | max30105.get_data_ready_status() 50 | max30105.get_die_temp_ready_status() 51 | max30105.get_fifo_almost_full_status() 52 | max30105.get_ambient_light_compensation_overflow_status() 53 | max30105.get_power_ready_status() 54 | max30105.get_proximity_triggered_threshold_status() 55 | 56 | 57 | def test_set_slot_mode(): 58 | from max30105 import MAX30105 59 | max30105 = MAX30105(i2c_dev=MockSMBusNoTimeout(1, default_registers={ 60 | 0x01: 0b00000010, # Die temp ready 61 | 0x09: 0b00000111 # Hard default value to avoid error 62 | })) 63 | 64 | max30105.set_slot_mode(1, 'green') 65 | max30105.set_slot_mode(1, 'red') 66 | 67 | with pytest.raises(ValueError): 68 | max30105.set_slot_mode(1, 'puce') 69 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LIBRARY_VERSION=$(shell cat library/setup.py | grep version | awk -F"'" '{print $$2}') 2 | LIBRARY_NAME=$(shell cat library/setup.py | grep name | awk -F"'" '{print $$2}') 3 | 4 | .PHONY: usage install uninstall 5 | usage: 6 | @echo "Library: ${LIBRARY_NAME}" 7 | @echo "Version: ${LIBRARY_VERSION}\n" 8 | @echo "Usage: make , where target is one of:\n" 9 | @echo "install: install the library locally from source" 10 | @echo "uninstall: uninstall the local library" 11 | @echo "check: peform basic integrity checks on the codebase" 12 | @echo "python-readme: generate library/README.rst from README.md" 13 | @echo "python-wheels: build python .whl files for distribution" 14 | @echo "python-sdist: build python source distribution" 15 | @echo "python-clean: clean python build and dist directories" 16 | @echo "python-dist: build all python distribution files" 17 | @echo "python-testdeploy: build all and deploy to test PyPi" 18 | @echo "tag: tag the repository with the current version" 19 | 20 | install: 21 | ./install.sh 22 | 23 | uninstall: 24 | ./uninstall.sh 25 | 26 | check: 27 | @echo "Checking for trailing whitespace" 28 | @! grep -IUrn --color "[[:blank:]]$$" --exclude-dir=sphinx --exclude-dir=.tox --exclude-dir=.git --exclude=PKG-INFO 29 | @echo "Checking for DOS line-endings" 30 | @! grep -IUrn --color " " --exclude-dir=sphinx --exclude-dir=.tox --exclude-dir=.git --exclude=Makefile 31 | @echo "Checking library/CHANGELOG.txt" 32 | @cat library/CHANGELOG.txt | grep ^${LIBRARY_VERSION} 33 | @echo "Checking library/${LIBRARY_NAME}/__init__.py" 34 | @cat library/${LIBRARY_NAME}/__init__.py | grep "^__version__ = '${LIBRARY_VERSION}'" 35 | 36 | tag: 37 | git tag -a "v${LIBRARY_VERSION}" -m "Version ${LIBRARY_VERSION}" 38 | 39 | python-readme: library/README.md 40 | 41 | python-license: library/LICENSE.txt 42 | 43 | library/README.md: README.md library/CHANGELOG.txt 44 | cp README.md library/README.md 45 | printf "\n# Changelog\n\n" >> library/README.md 46 | cat library/CHANGELOG.txt >> library/README. 47 | 48 | library/LICENSE.txt: LICENSE 49 | cp LICENSE library/LICENSE.txt 50 | 51 | python-wheels: python-readme python-license 52 | cd library; python3 setup.py bdist_wheel 53 | cd library; python setup.py bdist_wheel 54 | 55 | python-sdist: python-readme python-license 56 | cd library; python setup.py sdist 57 | 58 | python-clean: 59 | -rm -r library/dist 60 | -rm -r library/build 61 | -rm -r library/*.egg-info 62 | 63 | python-dist: python-clean python-wheels python-sdist 64 | ls library/dist 65 | 66 | python-testdeploy: python-dist 67 | twine upload --repository-url https://test.pypi.org/legacy/ library/dist/* 68 | 69 | python-deploy: check python-dist 70 | twine upload library/dist/* 71 | -------------------------------------------------------------------------------- /examples/detect-particles.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # NOTE! This code should not be used as the basis for a real 4 | # smoke or fire detector, or in life-critical situations. It's 5 | # for fun/novelty use only, so bear that in mind while using it. 6 | 7 | import time 8 | import datetime 9 | from max30105 import MAX30105, HeartRate 10 | 11 | max30105 = MAX30105() 12 | max30105.setup(leds_enable=3) 13 | 14 | 15 | max30105.set_led_pulse_amplitude(1, 0.0) 16 | max30105.set_led_pulse_amplitude(2, 0.0) 17 | max30105.set_led_pulse_amplitude(3, 12.5) 18 | 19 | max30105.set_slot_mode(1, 'red') 20 | max30105.set_slot_mode(2, 'ir') 21 | max30105.set_slot_mode(3, 'green') 22 | max30105.set_slot_mode(4, 'off') 23 | 24 | hr = HeartRate(max30105) 25 | 26 | # Smooths wobbly data. Increase to increase smoothing. 27 | mean_size = 20 28 | 29 | # Compares current smoothed value to smoothed value x 30 | # readings ago. Decrease this to increase detection 31 | # speed. 32 | delta_size = 10 33 | 34 | # The delta threshold at which a change is detected. 35 | # Decrease to make the detection more sensitive to 36 | # fluctuations, increase to make detection less 37 | # sensitive to fluctuations. 38 | threshold = 10 39 | 40 | data = [] 41 | means = [] 42 | 43 | timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S-%f") 44 | 45 | file_dir = "/home/pi/" # Where to save log file. 46 | 47 | print(""" 48 | NOTE! This code should not be used as the basis for a real 49 | smoke or fire detector, or in life-critical situations. It's 50 | for fun/novelty use only, so bear that in mind while using it. 51 | 52 | This example uses the green LED to detect the amount of green 53 | light reflected back to the sensor. An increase in relected 54 | light should correlate to an increase in particles in front of 55 | the sensor. 56 | 57 | Any movement of objects close to the sensor is likely to also 58 | trigger detection of a change. 59 | 60 | Values are printed to the terminal and to a datestamped text 61 | file in the directory file_dir. 62 | """) 63 | 64 | delay = 10 65 | 66 | print("Starting readings in {} seconds...\n".format(delay)) 67 | time.sleep(delay) 68 | 69 | try: 70 | with open(file_dir + timestamp + ".txt", "w") as f: 71 | f.write("time,green,mean,delta,change_detected,temp\n") 72 | while True: 73 | timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S-%f") 74 | samples = max30105.get_samples() 75 | if samples is not None: 76 | f.write(timestamp + ",") 77 | r = samples[2] & 0xff 78 | d = hr.low_pass_fir(r) 79 | data.append(d) 80 | if len(data) > mean_size: 81 | data.pop(0) 82 | mean = sum(data) / float(len(data)) 83 | means.append(mean) 84 | if len(means) > delta_size: 85 | delta = means[-1] - means[-delta_size] 86 | else: 87 | delta = 0 88 | if delta > threshold: 89 | detected = True 90 | else: 91 | detected = False 92 | print("Value: {:.2f} // Mean: {:.2f} // Delta: {:.2f} // \ 93 | Change detected: {}".format(d, mean, delta, detected)) 94 | f.write("{:.2f},".format(d)) 95 | f.write("{:.2f},".format(mean)) 96 | f.write("{:.2f},".format(delta)) 97 | f.write("{},".format(detected)) 98 | time.sleep(0.05) 99 | temp = max30105.get_temperature() 100 | f.write("{:.2f}\n".format(temp)) 101 | time.sleep(0.05) 102 | 103 | except KeyboardInterrupt: 104 | f.close() 105 | pass 106 | -------------------------------------------------------------------------------- /library/max30105/__init__.py: -------------------------------------------------------------------------------- 1 | """MAX30105 Driver.""" 2 | from i2cdevice import Device, Register, BitField, _int_to_bytes 3 | from i2cdevice.adapter import LookupAdapter, Adapter 4 | import struct 5 | import time 6 | 7 | 8 | __version__ = '0.0.5' 9 | 10 | CHIP_ID = 0x15 11 | I2C_ADDRESS = 0x57 12 | 13 | 14 | def bit(n): 15 | return 1 << n 16 | 17 | 18 | class LEDModeAdapter(Adapter): 19 | LOOKUP = [ 20 | 'off', 21 | 'red', 22 | 'ir', 23 | 'green', 24 | 'off', 25 | 'pilot_red', 26 | 'pilot_ir', 27 | 'pilot_green' 28 | ] 29 | 30 | def _decode(self, value): 31 | try: 32 | return self.LOOPUP[value] 33 | except IndexError: 34 | return 'off' 35 | 36 | def _encode(self, value): 37 | try: 38 | return self.LOOKUP.index(value) 39 | except ValueError: 40 | raise ValueError('Invalid slot mode {}'.format(value)) 41 | 42 | 43 | class PulseAmplitudeAdapter(Adapter): 44 | """Convert LED current control values approximately between LSBs and mA""" 45 | 46 | def _decode(self, value): 47 | return value * 0.2 48 | 49 | def _encode(self, value): 50 | return int(value / 0.2) 51 | 52 | 53 | class TemperatureAdapter(Adapter): 54 | """Convert fractional and integer temp readings to degrees C.""" 55 | 56 | def _decode(self, value): 57 | integer, fractional = struct.unpack('> 15 124 | 125 | def average_dc_estimator(self, sample): 126 | """Estimate the average DC.""" 127 | self.ir_avg += (((sample << 15) - self.ir_avg) >> 4) 128 | return self.ir_avg >> 15 129 | 130 | def check_for_beat(self, sample): 131 | """Check for a single beat.""" 132 | beat_detected = False 133 | ir_previous = self.ir_current 134 | ir_avg_est = self.average_dc_estimator(sample) 135 | self.ir_current = self.low_pass_fir(sample - ir_avg_est) 136 | 137 | if ir_previous < 0 and self.ir_current >= 0: 138 | self.ir_max = self.ir_signal_max 139 | self.ir_min = self.ir_signal_min 140 | self.pos_edge = 1 141 | self.neg_edge = 0 142 | self.ir_signal_max = 0 143 | 144 | if (self.ir_max - self.ir_min) > 20 and (self.ir_max - self.ir_min) < 1000: 145 | beat_detected = True 146 | 147 | if ir_previous > 0 and self.ir_current <= 0: 148 | self.pos_edge = 0 149 | self.neg_edge = 1 150 | self.ir_signal_min = 0 151 | 152 | if self.pos_edge and self.ir_current > ir_previous: 153 | self.ir_signal_max = self.ir_current 154 | 155 | if self.neg_edge and self.ir_current > ir_previous: 156 | self.ir_signal_min = self.ir_current 157 | 158 | return beat_detected 159 | 160 | def on_beat(self, handler, average_over=4, delay=0.5): 161 | """Watch for heartbeat and call a function on every beat. 162 | 163 | :param handler: Function to call, should accept beat_detected, bpm and bpm_avg arguments 164 | :param average_over: Number of samples to average over 165 | 166 | """ 167 | bpm_vals = [0 for x in range(average_over)] 168 | last_beat = time.time() 169 | last_update = time.time() 170 | bpm = 0 171 | bpm_avg = 0 172 | beat_detected = False 173 | 174 | while True: 175 | t = time.time() 176 | 177 | samples = self.max30105.get_samples() 178 | if samples is None: 179 | continue 180 | 181 | for sample_index in range(0, len(samples), 2): 182 | sample = samples[sample_index + 1] 183 | if self.check_for_beat(sample): 184 | beat_detected = True 185 | delta = t - last_beat 186 | last_beat = t 187 | bpm = 60 / delta 188 | bpm_vals = bpm_vals[1:] + [bpm] 189 | bpm_avg = sum(bpm_vals) / average_over 190 | 191 | if t - last_update >= delay: 192 | if handler(beat_detected, bpm, bpm_avg): 193 | return 194 | beat_detected = False 195 | last_update = t 196 | 197 | 198 | class MAX30105: 199 | def __init__(self, i2c_addr=I2C_ADDRESS, i2c_dev=None): 200 | self._is_setup = False 201 | self._i2c_addr = i2c_addr 202 | self._i2c_dev = i2c_dev 203 | self._active_leds = 0 204 | self._max30105 = Device(I2C_ADDRESS, i2c_dev=self._i2c_dev, bit_width=8, registers=( 205 | Register('INT_STATUS_1', 0x00, fields=( 206 | BitField('a_full', bit(7)), 207 | BitField('data_ready', bit(6)), 208 | BitField('alc_overflow', bit(5)), 209 | BitField('prox_int', bit(4)), 210 | BitField('pwr_ready', bit(0)) 211 | )), 212 | Register('INT_STATUS_2', 0x01, fields=( 213 | BitField('die_temp_ready', bit(1)), 214 | )), 215 | Register('INT_ENABLE_1', 0x02, fields=( 216 | BitField('a_full_en', bit(7)), 217 | BitField('data_ready_en', bit(6)), 218 | BitField('alc_overflow_en', bit(5)), 219 | BitField('prox_int_en', bit(4)), 220 | )), 221 | Register('INT_ENABLE_2', 0x03, fields=( 222 | BitField('die_temp_ready_en', bit(1)), 223 | )), 224 | # Points to MAX30105 write location in FIFO 225 | Register('FIFO_WRITE', 0x04, fields=( 226 | BitField('pointer', 0b00011111), 227 | )), 228 | # Counts the number of samples lost up to 0xf 229 | Register('FIFO_OVERFLOW', 0x05, fields=( 230 | BitField('counter', 0b00011111), 231 | )), 232 | # Points to read location in FIFO 233 | Register('FIFO_READ', 0x06, fields=( 234 | BitField('pointer', 0b00011111), 235 | )), 236 | Register('FIFO_CONFIG', 0x08, fields=( 237 | BitField('sample_average', 0b11100000, adapter=LookupAdapter({ 238 | 1: 0b000, 239 | 2: 0b001, 240 | 4: 0b010, 241 | 8: 0b011, 242 | 16: 0b100, 243 | 32: 0b101 244 | })), 245 | BitField('fifo_rollover_en', 0b00010000), 246 | BitField('fifo_almost_full', 0b00001111) 247 | )), 248 | Register('MODE_CONFIG', 0x09, fields=( 249 | BitField('shutdown', 0b10000000), 250 | BitField('reset', 0b01000000), 251 | BitField('mode', 0b00000111, adapter=LookupAdapter({ 252 | 'none': 0b00, 253 | 'red_only': 0b010, 254 | 'red_ir': 0b011, 255 | 'green_red_ir': 0b111 256 | })) 257 | )), 258 | Register('SPO2_CONFIG', 0x0A, fields=( 259 | BitField('adc_range_nA', 0b01100000, adapter=LookupAdapter({ 260 | 2048: 0b00, 261 | 4096: 0b01, 262 | 8192: 0b10, 263 | 16384: 0b11 264 | })), 265 | BitField('sample_rate_sps', 0b00011100, adapter=LookupAdapter({ 266 | 50: 0b000, 267 | 100: 0b001, 268 | 200: 0b010, 269 | 400: 0b011, 270 | 800: 0b100, 271 | 1000: 0b101, 272 | 1600: 0b110, 273 | 3200: 0b111 274 | })), 275 | BitField('led_pw_us', 0b00000011, adapter=LookupAdapter({ 276 | 69: 0b00, # 68.95us 277 | 118: 0b01, # 117.78us 278 | 215: 0b10, # 215.44us 279 | 411: 0b11 # 410.75us 280 | })) 281 | )), 282 | Register('LED_PULSE_AMPLITUDE', 0x0C, fields=( 283 | BitField('led1_mA', 0xff0000, adapter=PulseAmplitudeAdapter()), 284 | BitField('led2_mA', 0x00ff00, adapter=PulseAmplitudeAdapter()), 285 | BitField('led3_mA', 0x0000ff, adapter=PulseAmplitudeAdapter()) 286 | ), bit_width=24), 287 | Register('LED_PROX_PULSE_AMPLITUDE', 0x10, fields=( 288 | BitField('pilot_mA', 0xff, adapter=PulseAmplitudeAdapter()), 289 | )), 290 | # The below represent 4 timeslots 291 | Register('LED_MODE_CONTROL', 0x11, fields=( 292 | BitField('slot2', 0x7000, adapter=LEDModeAdapter()), 293 | BitField('slot1', 0x0700, adapter=LEDModeAdapter()), 294 | BitField('slot4', 0x0070, adapter=LEDModeAdapter()), 295 | BitField('slot3', 0x0007, adapter=LEDModeAdapter()) 296 | ), bit_width=16), 297 | Register('DIE_TEMP', 0x1f, fields=( 298 | BitField('temperature', 0xffff, adapter=TemperatureAdapter()), 299 | ), bit_width=16), 300 | Register('DIE_TEMP_CONFIG', 0x21, fields=( 301 | BitField('temp_en', bit(0)), 302 | )), 303 | Register('PROX_INT_THRESHOLD', 0x30, fields=( 304 | BitField('threshold', 0xff), 305 | )), 306 | Register('PART_ID', 0xfe, fields=( 307 | BitField('revision', 0xff00), 308 | BitField('part', 0x00ff) 309 | ), bit_width=16) 310 | )) 311 | 312 | 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): 313 | """Set up the sensor.""" 314 | if self._is_setup: 315 | return 316 | self._is_setup = True 317 | 318 | self._active_leds = leds_enable 319 | 320 | self._max30105.select_address(self._i2c_addr) 321 | 322 | self.soft_reset(timeout=timeout) 323 | 324 | self._max30105.set('FIFO_CONFIG', 325 | sample_average=sample_average, 326 | fifo_rollover_en=True) 327 | 328 | self._max30105.set('SPO2_CONFIG', 329 | sample_rate_sps=sample_rate, 330 | adc_range_nA=adc_range, 331 | led_pw_us=pulse_width) 332 | 333 | self._max30105.set('LED_PULSE_AMPLITUDE', 334 | led1_mA=led_power, 335 | led2_mA=led_power, 336 | led3_mA=led_power) 337 | 338 | self._max30105.set('LED_PROX_PULSE_AMPLITUDE', pilot_mA=led_power) 339 | 340 | # Set the LED mode based on the number of LEDs we want enabled 341 | self._max30105.set('MODE_CONFIG', 342 | mode=['red_only', 'red_ir', 'green_red_ir'][leds_enable - 1]) 343 | 344 | # Set up the LEDs requested in sequential slots 345 | self._max30105.set('LED_MODE_CONTROL', 346 | slot1='red', 347 | slot2='ir' if leds_enable >= 2 else 'off', 348 | slot3='green' if leds_enable >= 3 else 'off') 349 | 350 | self.clear_fifo() 351 | 352 | def soft_reset(self, timeout=5.0): 353 | """Reset device.""" 354 | self._max30105.set('MODE_CONFIG', reset=True) 355 | t_start = time.time() 356 | while self._max30105.get('MODE_CONFIG').reset and time.time() - t_start < timeout: 357 | time.sleep(0.001) 358 | if self._max30105.get('MODE_CONFIG').reset: 359 | raise RuntimeError("Timeout: Failed to soft reset MAX30105.") 360 | 361 | def clear_fifo(self): 362 | """Clear samples FIFO.""" 363 | self._max30105.set('FIFO_READ', pointer=0) 364 | self._max30105.set('FIFO_WRITE', pointer=0) 365 | self._max30105.set('FIFO_OVERFLOW', counter=0) 366 | 367 | def get_samples(self): 368 | """Return contents of sample FIFO.""" 369 | ptr_r = self._max30105.get('FIFO_READ').pointer 370 | ptr_w = self._max30105.get('FIFO_WRITE').pointer 371 | 372 | if ptr_r == ptr_w: 373 | return None 374 | 375 | sample_count = ptr_w - ptr_r 376 | if sample_count < 0: 377 | sample_count = 32 378 | 379 | byte_count = sample_count * 3 * self._active_leds 380 | 381 | data = [] 382 | 383 | while byte_count > 0: 384 | data += self._max30105._i2c.read_i2c_block_data(self._i2c_addr, 0x07, min(byte_count, 32)) 385 | byte_count -= 32 386 | 387 | self.clear_fifo() 388 | 389 | result = [] 390 | for x in range(0, len(data), 3): 391 | result.append((data[x] << 16) | (data[x + 1] << 8) | data[x + 2]) 392 | 393 | return result 394 | 395 | def get_chip_id(self): 396 | """Return the revision and part IDs.""" 397 | self.setup() 398 | 399 | part_id = self._max30105.get('PART_ID') 400 | 401 | return part_id.revision, part_id.part 402 | 403 | def get_temperature(self, timeout=5.0): 404 | """Return the die temperature.""" 405 | self.setup() 406 | 407 | self._max30105.set('INT_ENABLE_2', die_temp_ready_en=True) 408 | self._max30105.set('DIE_TEMP_CONFIG', temp_en=True) 409 | t_start = time.time() 410 | 411 | while not self._max30105.get('INT_STATUS_2').die_temp_ready: 412 | time.sleep(0.01) 413 | if time.time() - t_start > timeout: 414 | raise RuntimeError('Timeout: Waiting for INT_STATUS_2, die_temp_ready.') 415 | 416 | return self._max30105.get('DIE_TEMP').temperature 417 | 418 | def set_mode(self, mode): 419 | """Set the sensor mode. 420 | 421 | :param mode: Mode, either red_only, red_ir or green_red_ir 422 | 423 | """ 424 | self._max30105.set('MODE_CONFIG', mode=mode) 425 | 426 | def set_slot_mode(self, slot, mode): 427 | """Set the mode of a single slot. 428 | 429 | :param slot: Slot to set, either 1, 2, 3 or 4 430 | :param mode: Mode, either off, red, ir, green, pilot_red, pilot_ir or pilot_green 431 | 432 | """ 433 | if slot == 1: 434 | self._max30105.set('LED_MODE_CONTROL', slot1=mode) 435 | elif slot == 2: 436 | self._max30105.set('LED_MODE_CONTROL', slot2=mode) 437 | elif slot == 3: 438 | self._max30105.set('LED_MODE_CONTROL', slot3=mode) 439 | elif slot == 4: 440 | self._max30105.set('LED_MODE_CONTROL', slot4=mode) 441 | else: 442 | raise ValueError("Invalid LED slot: {}".format(slot)) 443 | 444 | def set_led_pulse_amplitude(self, led, amplitude): 445 | """Set the LED pulse amplitude in milliamps. 446 | 447 | :param led: LED to set, either 1, 2 or 3 448 | :param amplitude: LED amplitude in milliamps 449 | 450 | """ 451 | if led == 1: 452 | self._max30105.set('LED_PULSE_AMPLITUDE', led1_mA=amplitude) 453 | elif led == 2: 454 | self._max30105.set('LED_PULSE_AMPLITUDE', led2_mA=amplitude) 455 | elif led == 3: 456 | self._max30105.set('LED_PULSE_AMPLITUDE', led3_mA=amplitude) 457 | else: 458 | raise ValueError("Invalid LED: {}".format(led)) 459 | 460 | def set_fifo_almost_full_count(self, count): 461 | """Set number of FIFO slots remaining for Almost Full trigger. 462 | 463 | :param count: Count of remaining samples, from 0 to 15 464 | 465 | """ 466 | self._max30105.set('FIFO_CONFIG', fifo_almost_full=count) 467 | 468 | def set_fifo_almost_full_enable(self, value): 469 | """Enable the FIFO-almost-full flag.""" 470 | self._max30105.set('INT_ENABLE_1', a_full_en=value) 471 | 472 | def set_data_ready_enable(self, value): 473 | """Enable the data-ready flag.""" 474 | self._max30105.set('INT_ENABLE_1', data_ready_en=value) 475 | 476 | def set_ambient_light_compensation_overflow_enable(self, value): 477 | """Enable the ambient light compensation overflow flag.""" 478 | self._max30105.set('INT_ENABLE_1', alc_overflow_en=value) 479 | 480 | def set_proximity_enable(self, value): 481 | """Enable the proximity interrupt flag.""" 482 | self._max30105.set('INT_ENABLE_1', prox_int_en=value) 483 | 484 | def set_proximity_threshold(self, value): 485 | """Set the threshold of the proximity sensor. 486 | 487 | Sets the infra-red ADC count that will trigger the start of particle-sensing mode. 488 | 489 | :param value: threshold value from 0 to 255 490 | 491 | """ 492 | self._max30105.set('PROX_INT_THRESHOLD', threshold=value) 493 | 494 | def get_fifo_almost_full_status(self): 495 | """Get the FIFO-almost-full flag. 496 | 497 | This interrupt is set when the FIFO write pointer has N free spaces remaining, as defined in `set_fifo_almost_full_count`. 498 | 499 | The flag is cleared upon read. 500 | 501 | """ 502 | return self._max30105.get('INT_STATUS_1').a_full 503 | 504 | def get_data_ready_status(self): 505 | """Get the data-ready flag. 506 | 507 | In particle-sensing mode this interrupt triggeres when a new sample has been placed into the FIFO. 508 | 509 | This flag is cleared upon read, or upon `get_samples()` 510 | 511 | """ 512 | return self._max30105.get('INT_STATUS_1').data_ready 513 | 514 | def get_ambient_light_compensation_overflow_status(self): 515 | """Get the ambient light compensation overflow status flag. 516 | 517 | Returns True if the ALC has reached its limit, and ambient light is affecting the output of the ADC. 518 | 519 | This flag is cleared upon read. 520 | 521 | """ 522 | return self._max30105.get('INT_STATUS_1').alc_overflow 523 | 524 | def get_proximity_triggered_threshold_status(self): 525 | """Get the proximity triggered threshold status flag. 526 | 527 | Returns True if the proximity threshold has been reached and particle-sensing mode has begun. 528 | 529 | This flag is cleared upon read. 530 | 531 | """ 532 | return self._max30105.get('INT_STATUS_1').prox_int 533 | 534 | def get_power_ready_status(self): 535 | """Get the power ready status flag. 536 | 537 | Returns True if the sensor has successfully powered up and is ready to collect data. 538 | 539 | """ 540 | return self._max30105.get('INT_STATUS_1').pwr_ready 541 | 542 | def get_die_temp_ready_status(self): 543 | """Get the die temperature ready flag. 544 | 545 | Returns True if the die temperature value is ready to be read. 546 | 547 | This flag is cleared upon read, or upon `get_temperature`. 548 | 549 | """ 550 | return self._max30105.get('INT_STATUS_2').die_temp_ready 551 | --------------------------------------------------------------------------------