enviroplus-python
enviroplus-python copied to clipboard
Support MEMS microphone
I received my enviro+ last week and over the weekend I managed to install the examples and build my own InfluxDB bridge to show all those measurements via Grafana. Had a lot of tinkering fun, thanks :)
But I'm missing a way to interface the MEMS microphone to read out noise levels. I suppose that support is under way, can someone give an ETA or directions where to read further.
I agree to me it would be very useful to have code for mems mic readout. If someone is doing prioritization of todo list I'd vote for that :) thanks
The microphone is set up as a regular audio input via i2s, so any software capable of reading a standard audio device can access it.
The reason Python support is currently missing is because it's something of a challenge sampling a microphone input and performing any meaningful analysis on it on realtime in Python. It's a work in progress, though.
The solution involves a C daemon called FFsockeT the sole purpose of which is to sample the microphone, apply a (libfftw3) FFT and output the transformed data via a socket into a client application- the intent being that Python can consume this datae easily. The source for that is here- https://github.com/pimoroni/FFsockeT
I've been using sndpeek as a mechanism to visualise noise data, albeit I appreciate it has no practical application for those wishing to sample and log data as part of their environmental monitoring solution - https://soundlab.cs.princeton.edu/software/sndpeek/
Internally we also have a rough-and-ready Python application for sampling the microphone:
#
#""Show a text-mode spectrogram using live microphone data."""
import argparse
import math
import numpy as np
import shutil
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
import colorsys
import ST7735
#setup display
LCD_CS = 7
LCD_DC = 9
LCD_BACKLIGHT = 12
disp = ST7735.ST7735(
port=0,
cs=ST7735.BG_SPI_CS_FRONT,
dc=LCD_DC,
backlight=LCD_BACKLIGHT,
rotation=90,
)
WIDTH = disp.width
HEIGHT = disp.height
disp.begin()
img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0))
draw = ImageDraw.Draw(img)
usage_line = ' press <enter> to quit, +<enter> or -<enter> to change scaling '
def int_or_str(text):
#"""Helper function for argument parsing."""
try:
return int(text)
except ValueError:
return text
try:
columns, _ = shutil.get_terminal_size()
except AttributeError:
columns = 80
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('-l', '--list-devices', action='store_true',
help='list audio devices and exit')
parser.add_argument('-b', '--block-duration', type=float,
metavar='DURATION', default=50,
help='block size (default %(default)s milliseconds)')
parser.add_argument('-c', '--columns', type=int, default=columns,
help='width of spectrogram')
parser.add_argument('-d', '--device', type=int_or_str,
help='input device (numeric ID or substring)')
parser.add_argument('-g', '--gain', type=float, default=10,
help='initial gain factor (default %(default)s)')
parser.add_argument('-r', '--range', type=float, nargs=2,
metavar=('LOW', 'HIGH'), default=[100, 2000],
help='frequency range (default %(default)s Hz)')
args = parser.parse_args()
low, high = args.range
if high <= low:
parser.error('HIGH must be greater than LOW')
# Create a nice output gradient using ANSI escape sequences.
# Stolen from https://gist.github.com/maurisvh/df919538bcef391bc89f
colors = 30, 34, 35, 91, 93, 97
chars = ' :%#\t#%:'
gradient = []
for bg, fg in zip(colors, colors[1:]):
for char in chars:
if char == '\t':
bg, fg = fg, bg
else:
gradient.append('\x1b[{};{}m{}'.format(fg, bg + 10, char))
try:
import sounddevice as sd
if args.list_devices:
print(sd.query_devices())
parser.exit(0)
samplerate = 4000 # sd.query_devices(args.device, 'input')['default_samplerate']
delta_f = (high - low) / (WIDTH - 1)
fftsize = math.ceil(samplerate / delta_f)
low_bin = math.floor(low / delta_f)
def callback(indata, frames, time, status):
if status:
text = ' ' + str(status) + ' '
print('\x1b[34;40m', text.center(args.columns, '#'),
'\x1b[0m', sep='')
if any(indata):
#print(indata)
cpy_img = img.copy()
img.paste(cpy_img, (0,5))
magnitude = np.abs(np.fft.rfft(indata[:, 0], n=fftsize))
magnitude *= 500 / fftsize
#print( magnitude)
for index in range(len(magnitude[low_bin:low_bin + WIDTH])):
colour = colorsys.hsv_to_rgb(magnitude[index] , 1 ,1)
colour = tuple(int(255 * x) for x in colour)
#print(colour)
draw.line((index , 0, index + 1, 5), fill = colour)
disp.display(img)
#print(*line, sep='', end='\x1b[0m\n')
else:
print('no input')
with sd.InputStream(device=args.device, channels=1, callback=callback,
blocksize=int(samplerate * args.block_duration / 1000),
samplerate=samplerate):
while True:
response = input()
if response in ('', 'q', 'Q'):
break
for ch in response:
if ch == '+':
args.gain *= 2
elif ch == '-':
args.gain /= 2
else:
#print('\x1b[31;40m', usage_line.center(args.columns, '#'),
# '\x1b[0m', sep='')
break
except KeyboardInterrupt:
parser.exit('Interrupted by user')
except Exception as e:
parser.exit(type(e).__name__ + ': ' + str(e))
@Bugierek the someone would be me, and this is on my radar! (in fact I'm more or less the only software wrangler at the moment, for better or worse)
Thanks for reply. I appreciate your effort to make it all working, great job! At some point I managed to make the adafruit mems i2s mic work on Rpi so hopefully it's a similar task that I'll look into although I'm not a programmer ;) In the beginning it would be enough just to get the signal from mic and scale/calibrate the level into absolute value of total SPL. I think nice FFT analysis could be interesting in the future but having a simple dB SPL or A weighted dBA level readout would be enough. Thanks for working on it :)
I'm certainly no expert on these matters, but as I understand it A-weighting requires a FFT step in order to produce the per-frequency amplitude bands which are then weighted. The alternative being to use a digital filter to isolate the individual frequency bands one at a time before A-weighting them.
Getting a raw dB SPL without any kind of time to frequency domain conversion is - I believe - a folly since it wouldn't account for either the characterists of human hearing nor the variable frequency response of the microphone itself. Granted it may provide a useful relative measurement of noise level for a particular environment - ie: which times of day are more reletively noisy than others.
I also suspect for most environmental noise analysis that a certain subset of frequencies or events will be particularly of interest- things like obnoxiously driven cars, horns, large trucks passing, etc.
Honestly, this is one of the most difficult challenges I've faced in terms of general understanding of the core concepts. Sound is decidedly more information dense than light levels or temperature.
This is one of those problems where capturing chunks of audio throughout the day and throwing them at machine learning to classify frequency and density of "annoying noise events" would be, I guess, ideal. This looks like a deep rabbit hole, though- https://github.com/tyiannak/pyAudioAnalysis/
Some useful information here, too (for posterity) - https://makersportal.com/blog/2018/9/17/audio-processing-in-python-part-ii-exploring-windowing-sound-pressure-levels-and-a-weighting-using-an-iphone-x
And while I'm bundling information here, the datasheet for the MEMS microphone part - https://media.digikey.com/pdf/Data%20Sheets/Knowles%20Acoustics%20PDFs/SPH0645LM4H-B.pdf
Yes you're right that it'd be best to have at least A weighted SPL values and even better some sort of FFT graph.Another way that people look at noise are full octave or third octave bands of frequencies, but it also requires FFT step to calculate bands of frequencies. Actually the Full or 3rd octave bands are the default way that acoustic consultants look at noise data :)
btw that's another project I've found on the subject. A bit outdated but still: https://github.com/SuperShinyEyes/spl-meter-with-RPi
I received my enviro+ last week and over the weekend I managed to install the examples and build my own InfluxDB bridge to show all those measurements via Grafana. Had a lot of tinkering fun, thanks :)
But I'm missing a way to interface the MEMS microphone to read out noise levels. I suppose that support is under way, can someone give an ETA or directions where to read further.
Hi @jk , can you share your updates to push data to InfluxDB and Grafana? I would love to have that data locally for historical purposes. I already have a TIG stack running.
@Esteidinger Sure: https://gist.github.com/jk/dc58ef3759b1d61f021281531858a2a9
A few notes for people following this issue.
I'm new to Raspberry Pi, and what follows is from my poking at this with a new Pi Zero and Enviro+ with a fresh Raspbian Stretch install from the latest 2019-04-08 image.
But I did get sound working!
If you Google search getting the MEMS mic to work, you'll like land on this article at Adafruit. Don't follow that as-is, it's pretty old and leads your through a lot of unnecessary work.
First, do not use rpi-update to get your system up to date. For the driver stuff, a simple apt-get update and apt-get upgrade is good enough. Or you can use the Pimoroni update script that does that plus updates your python libraries.
Second, you do not need to run rpi-source to download and recompile the kernel. Just install the kernel headers with sudo apt-get install raspberrypi-kernel-headers as mentioned in the second answer here on StackExchange
After that, get the kernel module source and follow the rest of the instructions from the Adafruit article.
Since I have a Pi Zero, I had to change the module source as instructed. And to test the volume control (in the bottom of the Adafruit article) I had to change the name from "Boost Capture Volume" to "Master Capture Volume" (which was down a ways in this thread).
But after modifying the source and building and loading the module I was able to record a wav file using arecord!
Then I had to modify the FFsocket source to open the MEMS mic kernel module by changing it from hw:adau7002 to hw:sndrpisimplecar. And with that I managed to get ffsocket to run and was able to use client.py to read the output and can see varying values in the output array.
I don't know what any of those values mean :-) but it seems to be doing something in response to noise near the Enviro+
David thank you for your post I will follow your instructions on my Enviro+ plus board. I am working on using the ACD pin to read an external adafruit 1733 anemometer as well by modifying the Gas.py script to include a read function and convert voltage to m/s reading , Then adding another mode to enviro_demo.py to be able to get a display of wind speed.
Regarding obtaining the A-Weighted SPL I have found this example: https://docs.smartcitizen.me/Components/Urban%20Sensor%20Board/Noise%20Sensor/ and here are some interesting Github repos related to it:
- https://github.com/fablabbcn/smartcitizen-kit-audio
- https://github.com/oscgonfer/AudioI2S
I have found another implementation here, so it might be helpful to see how it was done on other platform : https://github.com/Tinkerforge/sound-pressure-level-bricklet/
https://www.tinkerforge.com/en/doc/Hardware/Bricklets/Sound_Pressure_Level.html
Hi @Gadgetoid I just wanted to follow up on this issue. It would be really great to have a python script to measure noise pollution. I am currently developing a project that will use the Enviro + sensor in 30+ classrooms and getting a measure of noise would be really amazing. Thank you.
You have to adapt a dead cat attachment to the noise sensor otherwise the hard walls and floors and ceiling will amplify your levels and give you improper levels .
On Mon, Oct 14, 2019 at 10:47 AM vschinazi [email protected] wrote:
Hi @Gadgetoid https://github.com/Gadgetoid I just wanted to follow up on this issue. It would be really great to have a python script to measure noise pollution. I am currently developing a project that will use the Enviro + sensor in 30+ classrooms and getting a measure of noise would be really amazing. Thank you.
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/pimoroni/enviroplus-python/issues/11?email_source=notifications&email_token=AKFAE2ZCQYLDSCW4TPSJJR3QOSBB3A5CNFSM4H62SO7KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEBFAWOQ#issuecomment-541723450, or unsubscribe https://github.com/notifications/unsubscribe-auth/AKFAE24Q22DKTVSFSVMKNHDQOSBB3ANCNFSM4H62SO7A .
You have to adapt a dead cat attachment to the noise sensor otherwise the hard walls and floors and ceiling will amplify your levels and give you improper levels .
Oh wow, thank you for the heads up! What do you recommend for that? Do you actually have a working python script for the microphone? Would you be able to share that?
Sorry I should have mentioned that the noise/microphone code is currently pending review and merge in a pull-request which you can find here: https://github.com/pimoroni/enviroplus-python/pull/33
I'll keep this issue open because I don't believe Python is the best/only way to do this, and alternate setups which access the i2s microphone could be useful or interesting to others.
Hi, I have found another piece of code that might help with getting the fft noise levels: https://github.com/colin-guyon/rpi-audio-levels
Sorry for flooding this topic with bunch of links, I'm not a dev and can't really code, but hopefully in some of these examples there will be something useful and helpful to get it working well on Rpi.
There is some sound code written now, but when I do (on a fresh install of Raspbian):
git clone https://github.com/pimoroni/enviroplus-python
cd enviroplus-python
sudo ./install.sh
I get 'SyntaxError: invalid syntax' in the install script (see attached), and then (after reboot):
pi@raspberrypi:~/enviroplus-python/examples $ python3 ./noise-profile.py
Traceback (most recent call last):
File "./noise-profile.py", line 3, in
in case anyone is wondering, this seemed to help:
pip3 install sounddevice
Hi, I am another one interested in being able to measure noise pollution in terms of dBA, which would clearly be useful in an Environmental monitoring board. (In fact we wrongly assumed this would be provided before buying the board). Before I try out some of the ideas suggested by @Bugierek, since most of a year has elapsed since this issue was first raised, I wondered if perhaps something has changed and Pimoroni (@Gadgetoid) might have some usable library either available or in development?
Now, I notice that there is now a noise.py library with three functions to obtain noise profile and mean amplitudes at a frequency range or ranges. But it's not clear what units these return and I don't think they fit the bill for dBA (which involves root-squared-mean values and A-weighting according to a curve). Am I wrong @Gadgetoid - or can these be easily adapted for dBA?
(Apologies if you see this twice - I thought I posted it yesterday - but it seems to have been my imagination :-) )
Thanks a lot.
Hi. Another who bought several Enviro+ hoping to measure dBA. Looking around other repo's dBA looks quite intense. I hope Pimoroni can help us all. Thanks
Hi. Also bought an Enviro+ hoping to measure dBA. Would love to get some input here! Thank you :-)
Hello to everyone! I bought Enviro+ mainly to acquire data related to indoor environments and mainly for noise level (dB or dBA). I'm not an experienced programmer and I can't find an example "ready-to-use" on the Web to detect the noise level in dB or dBA. Has anyone found something? it would be crucial for me! THANK YOU very much to anyone who has suggestions on this!
Anybody else got this error on running the noise-profile.py:
Traceback (most recent call last):
File "noise-profile.py", line 29, in <module>
low, mid, high, amp = noise.get_noise_profile()
File "/root/.local/lib/python3.7/site-packages/enviroplus/noise.py", line 67, in get_noise_profile
recording = self._record()
File "/root/.local/lib/python3.7/site-packages/enviroplus/noise.py", line 89, in _record
dtype='float64'
File "/root/.local/lib/python3.7/site-packages/sounddevice.py", line 275, in rec
ctx.input_dtype, callback, blocking, **kwargs)
File "/root/.local/lib/python3.7/site-packages/sounddevice.py", line 2578, in start_stream
**kwargs)
File "/root/.local/lib/python3.7/site-packages/sounddevice.py", line 1416, in __init__
**_remove_self(locals()))
File "/root/.local/lib/python3.7/site-packages/sounddevice.py", line 812, in __init__
extra_settings, samplerate)
File "/root/.local/lib/python3.7/site-packages/sounddevice.py", line 2651, in _get_stream_parameters
info = query_devices(device)
File "/root/.local/lib/python3.7/site-packages/sounddevice.py", line 564, in query_devices
raise PortAudioError('Error querying device {}'.format(device))
Installed also the sounddevice and the libportaudio2 I am using a raspberry pi zero w. Everything else of the example scripts is working well.
Hi Gadgetoid. Is there any news on the development obtaining dBA/SPL with the MEMs microphone? I would find this extremely useful in my project. I now have 20 of these HAT's with the intention of buying at least 60 for a factory monitoring system. If you have any code you need testing, I would be happy to help. I can also gain access to a handheld calibrated SPL meter to check accuracy. Thanks Steve PS: Have also been mailing your colleague Matt at Pimoroni about this subject and he pointed me here.
Hey Steve, drop me a line - follow the link to my website. I have some ideas for you.
@seaniedan @ftosteve, do you have any updates from your discussions that you can share publicly?
Nothing from me sorry. I have been offline for a few weeks. I did look at seaniedan's link but nothing related to this subject.
Sorry for the lack of response here, but I've had nothing useful to add. Not only does my fundamental lack of understanding somewhat hamstring me here, but a variety of other factors mean I haven't had the time to even look into it.
Thanks for the quick replies.
Though admittedly I find it quite frustrating that a company specifically advertising a product to be useful for noise measurements (see the screenshot below from the Enviro product page) still hasn't managed to even provide a basic tutorial on how to achieve that since mid-2019... :-/
