Skip to content

Rearrange repository to make this lib a package #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,4 @@ dmypy.json
micropy.json
pymakr.conf
tinypico-20210418-v1.15.bin
pico-go.json
31 changes: 21 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ This work is a lot based on:

- [SparkFun MAX3010x Sensor Library](https://github.com/sparkfun/SparkFun_MAX3010x_Sensor_Library "GitHub | SparkFun MAX3010x Sensor Library")

Written by **Peter Jansen and Nathan Seidle** (SparkFun)
Written by **Peter Jansen** and **Nathan Seidle** (SparkFun)
This is a library written for the Maxim MAX30105 Optical Smoke Detector
It should also work with the MAX30102. However, the MAX30102 does not have a Green LED.
These sensors use I2C to communicate, as well as a single (optional)
Expand All @@ -27,27 +27,38 @@ This work is not intended to be used in professional environments, and there are

## Repository organisation

- Driver: `src/lib/MAX30102.py`
- Example: `src/main.py`
- Driver: `./max30102`
- Example: `./example`

## Additional information

This driver works for sure with Maxim Integrated MAX30102 sensor.
This driver has been tested with Maxim Integrated MAX30102 sensor.
However, it *should* work with MAX30105 sensor, too.

### Usage
### How to import the library and run the example

#### Including this library into your project
#### Including this library into your project (network-enabled MicroPython ports)
To include the library into a network-enabled MicroPython project, it's sufficient to install the package:

To include the library into a MicroPython project, it's sufficient to copy and include the `MAX30102.py` file.
```python
import upip
upip.install(micropython-max30102)
```

Make sure that your firmware runs these lines **after** an Internet connection has been established.

Example: create a *lib* directory inside your source code folder, and copy the `/src/lib/MAX30102.py` file inside it.
Then, it will be possible to include the sensor instance constructor by issuing:
To run the example in `./example` folder, upload `./example` content into your microcontroller, and run it.

#### Including this library into your project (manual way)

To directly include the library into a MicroPython project, it's sufficient to copy the `max30102` module next to your `main.py`, and then import it as follows:

```python
from lib.MAX30102 import MAX30102
from max30102 import MAX30102
```

For instance, to run the example in `./example` folder, copy the `./max30102` directory and paste it in the `./example` folder. Then, upload `./example` content into your microcontroller and run it.

#### Setup and configuration

At first, create a sensor instance. If you leave the arguments empty, the library will load the default TinyPico ESP32 board I2C configuration (SDA Pin 21, SCL Pin 22, 400kHz speed).
Expand Down
29 changes: 29 additions & 0 deletions example/boot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# This file is executed on every boot (including wake-boot from deepsleep)

def do_connect(ssid:str, password:str):
import network
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
print('connecting to network...')
wlan.connect(ssid, password)
while not wlan.isconnected():
pass
print('network config:', wlan.ifconfig())


if __name__ == '__main__':
# Put yor WiFi credentials here
my_ssid = "my_ssid"
my_pass = "my_password"

try:
import max30102
except:
try:
import upip
do_connect(my_ssid, my_pass)
upip.install("micropython-max30102")
except:
print("Unable to get 'micropython-max30102' package!")

8 changes: 2 additions & 6 deletions src/main.py → example/main.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
# main.py

from lib.MAX30102 import MAX30102
from machine import sleep
from max30102 import MAX30102
from utime import ticks_diff, ticks_ms
import logging

if __name__ == '__main__':
logging.basicConfig(level=logging.INFO)

# Sensor instance. If left empty, loads default ESP32 I2C configuration
sensor = MAX30102()
# Alternatively (for other boards):
Expand Down Expand Up @@ -70,4 +66,4 @@
f_HZ = samples_n/1
samples_n = 0
t_start = ticks_ms()
print("acquisition frequency = ",f_HZ)
print("acquisition frequency = ",f_HZ)
29 changes: 5 additions & 24 deletions src/lib/MAX30102.py → max30102/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,11 @@
n-elia
'''

from ustruct import unpack
from machine import SoftI2C, Pin
from utime import sleep_ms, ticks_ms, ticks_diff
from machine import Pin, SoftI2C
from ucollections import deque
from ustruct import unpack
from utime import sleep_ms, ticks_diff, ticks_ms

import logging

# Setup of logger
logger = logging.getLogger("MAX30102")

# These I2C default settings work for TinyPico (ESP32-based board)
MAX3010X_I2C_ADDRESS = 0x57
Expand Down Expand Up @@ -240,13 +236,9 @@ def __init__(self,
try:
self._i2c.readfrom(self._address, 1)
except OSError as error:
logger.error("(%s) I2C Error. Unable to find a MAX3010x sensor.", TAG)
raise SystemExit(error)
else:
logger.info("(%s) MAX3010x sensor found!", TAG)


if not (self.checkPartID()):
logger.error("(%s) Wrong PartID. Unable to find a MAX3010x sensor.", TAG)
raise SystemExit()

# Sensor setup method
Expand Down Expand Up @@ -286,7 +278,6 @@ def setup_sensor(self, LED_MODE=2, ADC_RANGE=16384, SAMPLE_RATE=400,

def __del__(self):
self.shutDown()
logger.info("(%s) Shutting down the sensor.", TAG)

# Methods to read the two interrupt flags
def getINT1(self):
Expand Down Expand Up @@ -367,7 +358,6 @@ def softReset(self):
# and data registers are reset to their power-on-state through
# a power-on reset. The RESET bit is cleared automatically back to zero
# after the reset sequence is completed. (datasheet pag. 19)
logger.debug("(%s) Resetting the sensor.", TAG)
self.set_bitMask(MAX30105_MODECONFIG,
MAX30105_RESET_MASK,
MAX30105_RESET)
Expand Down Expand Up @@ -475,8 +465,6 @@ def setSampleRate(self, sample_rate):
MAX30105_SAMPLERATE_MASK,
sr)

logger.debug("(%s) Setting sample rate to %d.", TAG, sample_rate)

# Store the sample rate and recompute the acq. freq.
self._sampleRate = sample_rate
self.updateAcquisitionFrequency()
Expand All @@ -501,8 +489,6 @@ def setPulseWidth(self, pulse_width):
MAX30105_PULSEWIDTH_MASK,
pw)

logger.debug("(%s) Setting pulse width to %d.", TAG, pulse_width)

# Store the pulse width
self._pulseWidth = pw

Expand Down Expand Up @@ -555,26 +541,21 @@ def setFIFOAverage(self, number_of_samples):
'Wrong number of samples:{0}!'.format(number_of_samples))
self.set_bitMask(MAX30105_FIFOCONFIG, MAX30105_SAMPLEAVG_MASK, ns)

logger.debug("(%s) Setting FIFO avg samples to %d.", TAG,
number_of_samples)

# Store the number of averaged samples and recompute the acq. freq.
self._sampleAvg = number_of_samples
self.updateAcquisitionFrequency()

def updateAcquisitionFrequency(self):
TAG = 'updateAcquisitionFrequency'
if (None in [self._sampleRate, self._sampleAvg]):
logger.debug("(%s) Unable to compute acq freq..", TAG)
return
else:
self._acqFrequency = self._sampleRate / self._sampleAvg
from math import ceil

# Compute the time interval to wait before taking a good measure
# (see note in setSampleRate() method)
self._acqFrequencyInv = int(ceil(1000/self._acqFrequency))
logger.info("(%s) Acq. frequency: %f Hz", TAG, self._acqFrequency)
logger.info("(%s) Acq. period: %f ms", TAG, self._acqFrequencyInv)

def getAcquisitionFrequency(self):
return self._acqFrequency
Expand Down
1 change: 0 additions & 1 deletion requirements.txt

This file was deleted.

145 changes: 145 additions & 0 deletions sdist_upip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#
# This module overrides distutils (also compatible with setuptools) "sdist"
# command to perform pre- and post-processing as required for MicroPython's
# upip package manager.
#
# Preprocessing steps:
# * Creation of Python resource module (R.py) from each top-level package's
# resources.
# Postprocessing steps:
# * Removing metadata files not used by upip (this includes setup.py)
# * Recompressing gzip archive with 4K dictionary size so it can be
# installed even on low-heap targets.
#
import sys
import os
import zlib
from subprocess import Popen, PIPE
import glob
import tarfile
import re
import io

from distutils.filelist import FileList
from setuptools.command.sdist import sdist as _sdist


def gzip_4k(inf, fname):
comp = zlib.compressobj(level=9, wbits=16 + 12)
with open(fname + ".out", "wb") as outf:
while 1:
data = inf.read(1024)
if not data:
break
outf.write(comp.compress(data))
outf.write(comp.flush())
os.rename(fname, fname + ".orig")
os.rename(fname + ".out", fname)


FILTERS = [
# include, exclude, repeat
(r".+\.egg-info/(PKG-INFO|requires\.txt)", r"setup.py$"),
(r".+\.py$", r"[^/]+$"),
(None, r".+\.egg-info/.+"),
]


outbuf = io.BytesIO()

def filter_tar(name):
fin = tarfile.open(name, "r:gz")
fout = tarfile.open(fileobj=outbuf, mode="w")
for info in fin:
# print(info)
if not "/" in info.name:
continue
fname = info.name.split("/", 1)[1]
include = None

for inc_re, exc_re in FILTERS:
if include is None and inc_re:
if re.match(inc_re, fname):
include = True

if include is None and exc_re:
if re.match(exc_re, fname):
include = False

if include is None:
include = True

if include:
print("including:", fname)
else:
print("excluding:", fname)
continue

farch = fin.extractfile(info)
fout.addfile(info, farch)
fout.close()
fin.close()


def make_resource_module(manifest_files):
resources = []
# Any non-python file included in manifest is resource
for fname in manifest_files:
ext = fname.rsplit(".", 1)
if len(ext) > 1:
ext = ext[1]
else:
ext = ""
if ext != "py":
resources.append(fname)

if resources:
print("creating resource module R.py")
resources.sort()
last_pkg = None
r_file = None
for fname in resources:
try:
pkg, res_name = fname.split("/", 1)
except ValueError:
print("not treating %s as a resource" % fname)
continue
if last_pkg != pkg:
last_pkg = pkg
if r_file:
r_file.write("}\n")
r_file.close()
r_file = open(pkg + "/R.py", "w")
r_file.write("R = {\n")

with open(fname, "rb") as f:
r_file.write("%r: %r,\n" % (res_name, f.read()))

if r_file:
r_file.write("}\n")
r_file.close()


class sdist(_sdist):

def run(self):
self.filelist = FileList()
self.get_file_list()
make_resource_module(self.filelist.files)

r = super().run()

assert len(self.archive_files) == 1
print("filtering files and recompressing with 4K dictionary")
filter_tar(self.archive_files[0])
outbuf.seek(0)
gzip_4k(outbuf, self.archive_files[0])

return r


# For testing only
if __name__ == "__main__":
filter_tar(sys.argv[1])
outbuf.seek(0)
gzip_4k(outbuf, sys.argv[1])
25 changes: 25 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from setuptools import setup, find_packages
import sdist_upip

setup(
name="micropython-max30102",
version="0.3.1",
description="MAX30102 driver for micropython.",
long_description=open("README.md").read(),
long_description_content_type='text/markdown',

url="https://github.com/n-elia/MAX30102-MicroPython-driver",
license="MIT",
keywords="micropython",

author="Nicola Elia",
maintainer="Nicola Elia",

classifiers=[
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: Implementation :: MicroPython",
],
cmdclass={"sdist": sdist_upip.sdist},

packages=find_packages()
)
1 change: 0 additions & 1 deletion src/boot.py

This file was deleted.

Loading