Do some more basic housekeeping to get to a building working version 0.0.1

This commit is contained in:
Paul Beech
2020-05-14 01:41:11 +01:00
parent bcf2c73bdf
commit 2c5c7a41b4
29 changed files with 288 additions and 1694 deletions

View File

@@ -1,15 +1,3 @@
0.0.3
-----
* Fix "self.noise_floor" bug in get_noise_profile
0.0.2
-----
* Add support for extra ADC channel in Gas
* Handle breaking change in new ltr559 library
* Add Noise functionality
0.0.1
-----

View File

@@ -2,4 +2,4 @@ include CHANGELOG.txt
include LICENSE.txt
include README.rst
include setup.py
recursive-include enviroplus *.py
recursive-include grow *.py

View File

@@ -1,26 +1,22 @@
Enviro+
Grow
=======
Designed for environmental monitoring, Enviro+ lets you measure air
quality (pollutant gases and particulates), temperature, pressure,
humidity, light, and noise level. Learn more -
https://shop.pimoroni.com/products/enviro-plus
Designed for looking after plants, Grow monitors moisture levels and runs simple pumps to water plants. Learn more -
https://shop.pimoroni.com/products/grow
|Build Status| |Coverage Status| |PyPi Package| |Python Versions|
Installing
==========
You're best using the "One-line" install method if you want all of the
UART serial configuration for the PMS5003 particulate matter sensor to
run automatically.
The one-line installer enables the correct interfaces,
One-line (Installs from GitHub)
-------------------------------
::
curl -sSL https://get.pimoroni.com/enviroplus | bash
curl -sSL https://get.pimoroni.com/grow | bash
**Note** report issues with one-line installer here:
https://github.com/pimoroni/get
@@ -28,8 +24,8 @@ https://github.com/pimoroni/get
Or... Install and configure dependencies from GitHub:
-----------------------------------------------------
- ``git clone https://github.com/pimoroni/enviroplus-python``
- ``cd enviroplus-python``
- ``git clone https://github.com/pimoroni/grow-python``
- ``cd grow-python``
- ``sudo ./install.sh``
**Note** Raspbian Lite users may first need to install git:
@@ -38,7 +34,7 @@ Or... Install and configure dependencies from GitHub:
Or... Install from PyPi and configure manually:
-----------------------------------------------
- Run ``sudo pip install enviroplus``
- Run ``sudo pip install grow``
**Note** this wont perform any of the required configuration changes on
your Pi, you may additionally need to:
@@ -46,13 +42,6 @@ your Pi, you may additionally need to:
- Enable i2c: ``raspi-config nonint do_i2c 0``
- Enable SPI: ``raspi-config nonint do_spi 0``
And if you're using a PMS5003 sensor you will need to:
- Enable serial:
``raspi-config nonint set_config_var enable_uart 1 /boot/config.txt``
- Disable serial terminal: ``sudo raspi-config nonint do_serial 1``
- Add ``dtoverlay=pi3-miniuart-bt`` to your ``/boot/config.txt``
And install additional dependencies:
::
@@ -62,30 +51,18 @@ And install additional dependencies:
Help & Support
--------------
- GPIO Pinout - https://pinout.xyz/pinout/enviro\_plus
- GPIO Pinout - https://pinout.xyz/pinout/grow
- Support forums - http://forums.pimoroni.com/c/support
- Discord - https://discord.gg/hr93ByC
.. |Build Status| image:: https://travis-ci.com/pimoroni/enviroplus-python.svg?branch=master
:target: https://travis-ci.com/pimoroni/enviroplus-python
.. |Coverage Status| image:: https://coveralls.io/repos/github/pimoroni/enviroplus-python/badge.svg?branch=master
:target: https://coveralls.io/github/pimoroni/enviroplus-python?branch=master
.. |PyPi Package| image:: https://img.shields.io/pypi/v/enviroplus.svg
:target: https://pypi.python.org/pypi/enviroplus
.. |Python Versions| image:: https://img.shields.io/pypi/pyversions/enviroplus.svg
:target: https://pypi.python.org/pypi/enviroplus
0.0.3
-----
* Fix "self.noise_floor" bug in get_noise_profile
0.0.2
-----
* Add support for extra ADC channel in Gas
* Handle breaking change in new ltr559 library
* Add Noise functionality
.. |Build Status| image:: https://travis-ci.com/pimoroni/grow-python.svg?branch=master
:target: https://travis-ci.com/pimoroni/grow-python
.. |Coverage Status| image:: https://coveralls.io/repos/github/pimoroni/grow-python/badge.svg?branch=master
:target: https://coveralls.io/github/pimoroni/grow-python?branch=master
.. |PyPi Package| image:: https://img.shields.io/pypi/v/growhat.svg
:target: https://pypi.python.org/pypi/growhat
.. |Python Versions| image:: https://img.shields.io/pypi/pyversions/growhat.svg
:target: https://pypi.python.org/pypi/growhat
0.0.1
-----

View File

@@ -1 +1 @@
__version__ = '0.0.3'
__version__ = '0.0.1'

View File

@@ -1,140 +0,0 @@
"""Read the MICS6814 via an ads1015 ADC"""
import time
import atexit
import ads1015
import RPi.GPIO as GPIO
MICS6814_HEATER_PIN = 24
MICS6814_GAIN = 6.144
ads1015.I2C_ADDRESS_DEFAULT = ads1015.I2C_ADDRESS_ALTERNATE
_is_setup = False
_adc_enabled = False
_adc_gain = 6.148
class Mics6814Reading(object):
__slots__ = 'oxidising', 'reducing', 'nh3', 'adc'
def __init__(self, ox, red, nh3, adc=None):
self.oxidising = ox
self.reducing = red
self.nh3 = nh3
self.adc = adc
def __repr__(self):
fmt = """Oxidising: {ox:05.02f} Ohms
Reducing: {red:05.02f} Ohms
NH3: {nh3:05.02f} Ohms"""
if self.adc is not None:
fmt += """
ADC: {adc:05.02f} Volts
"""
return fmt.format(
ox=self.oxidising,
red=self.reducing,
nh3=self.nh3,
adc=self.adc)
__str__ = __repr__
def setup():
global adc, _is_setup
if _is_setup:
return
_is_setup = True
adc = ads1015.ADS1015(i2c_addr=0x49)
adc.set_mode('single')
adc.set_programmable_gain(MICS6814_GAIN)
adc.set_sample_rate(1600)
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(MICS6814_HEATER_PIN, GPIO.OUT)
GPIO.output(MICS6814_HEATER_PIN, 1)
atexit.register(cleanup)
def enable_adc(value=True):
"""Enable reading from the additional ADC pin."""
global _adc_enabled
_adc_enabled = value
def set_adc_gain(value):
"""Set gain value for the additional ADC pin."""
global _adc_gain
_adc_gain = value
def cleanup():
GPIO.output(MICS6814_HEATER_PIN, 0)
def read_all():
"""Return gas resistence for oxidising, reducing and NH3"""
setup()
ox = adc.get_voltage('in0/gnd')
red = adc.get_voltage('in1/gnd')
nh3 = adc.get_voltage('in2/gnd')
try:
ox = (ox * 56000) / (3.3 - ox)
except ZeroDivisionError:
ox = 0
try:
red = (red * 56000) / (3.3 - red)
except ZeroDivisionError:
red = 0
try:
nh3 = (nh3 * 56000) / (3.3 - nh3)
except ZeroDivisionError:
nh3 = 0
analog = None
if _adc_enabled:
if _adc_gain == MICS6814_GAIN:
analog = adc.get_voltage('ref/gnd')
else:
adc.set_programmable_gain(_adc_gain)
time.sleep(0.05)
analog = adc.get_voltage('ref/gnd')
adc.set_programmable_gain(MICS6814_GAIN)
return Mics6814Reading(ox, red, nh3, analog)
def read_oxidising():
"""Return gas resistance for oxidising gases.
Eg chlorine, nitrous oxide
"""
setup()
return read_all().oxidising
def read_reducing():
"""Return gas resistance for reducing gases.
Eg hydrogen, carbon monoxide
"""
setup()
return read_all().reducing
def read_nh3():
"""Return gas resistance for nh3/ammonia"""
setup()
return read_all().nh3
def read_adc():
"""Return spare ADC channel value"""
setup()
return read_all().adc

93
library/grow/moisture.py Normal file
View File

@@ -0,0 +1,93 @@
import time
import atexit
import RPi.GPIO as GPIO
MOISTURE_1_PIN = 23
MOISTURE_2_PIN = 25
MOISTURE_3_PIN = 8
_is_setup = False
class Moisture(object):
__slots__ = 'in1', 'in2', 'in3'
def __init__(self, in1, in2, in3):
self.in1 = in1
self.in2 = in2
self.in3 = in3
def __repr__(self):
fmt = """Moisture 1: {in1} Hz
Moisture 1: {in2} Ohms
Moisture 1: {in3} Ohms"""
return fmt.format(
in1=self.in1,
in2=self.in2,
in3=self.in3)
__str__ = __repr__
def setup():
global _is_setup
global _moisture
if _is_setup:
return
_is_setup = True
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(MOISTURE_1_PIN, GPIO.IN)
GPIO.input(MOISTURE_1_PIN)
GPIO.setup(MOISTURE_2_PIN, GPIO.IN)
GPIO.input(MOISTURE_2_PIN)
GPIO.setup(MOISTURE_3_PIN, GPIO.IN)
GPIO.input(MOISTURE_3_PIN)
atexit.register(cleanup)
def set_moisture_wet(channel, value):
"""Set wet point for a moisture channel."""
_moisture[channel].wet = value
def set_moisture_wet(channel, value):
"""Set wet point for a moisture channel."""
_moisture[channel].dry = value
def read_moiture(channel):
"""Get current value for the additional ADC pin."""
setup()
return _moisture[channel].value
def cleanup():
GPIO.output(MOISTURE_1_PIN, 0)
GPIO.output(MOISTURE_2_PIN, 0)
GPIO.output(MOISTURE_3_PIN, 0)
def read_all():
"""Return gas resistence for oxidising, reducing and NH3"""
setup()
in1 = adc.get_voltage('in0/gnd')
in2 = adc.get_voltage('in1/gnd')
in3 = adc.get_voltage('in2/gnd')
try:
in1 = (ox * 56000) / (3.3 - ox)
except ZeroDivisionError:
in1 = 0
try:
in2 = (red * 56000) / (3.3 - red)
except ZeroDivisionError:
in2 = 0
try:
in3 = (nh3 * 56000) / (3.3 - nh3)
except ZeroDivisionError:
in3 = 0
return _moisture(in1, in2, in3)

View File

@@ -1,90 +0,0 @@
import sounddevice
import numpy
class Noise():
def __init__(self,
sample_rate=16000,
duration=0.5):
"""Noise measurement.
:param sample_rate: Sample rate in Hz
:param duraton: Duration, in seconds, of noise sample capture
"""
self.duration = duration
self.sample_rate = sample_rate
def get_amplitudes_at_frequency_ranges(self, ranges):
"""Return the mean amplitude of frequencies in the given ranges.
:param ranges: List of ranges including a start and end range
"""
recording = self._record()
magnitude = numpy.abs(numpy.fft.rfft(recording[:, 0], n=self.sample_rate))
result = []
for r in ranges:
start, end = r
result.append(numpy.mean(magnitude[start:end]))
return result
def get_amplitude_at_frequency_range(self, start, end):
"""Return the mean amplitude of frequencies in the specified range.
:param start: Start frequency (in Hz)
:param end: End frequency (in Hz)
"""
n = self.sample_rate // 2
if start > n or end > n:
raise ValueError("Maxmimum frequency is {}".format(n))
recording = self._record()
magnitude = numpy.abs(numpy.fft.rfft(recording[:, 0], n=self.sample_rate))
return numpy.mean(magnitude[start:end])
def get_noise_profile(self,
noise_floor=100,
low=0.12,
mid=0.36,
high=None):
"""Returns a noise charateristic profile.
Bins all frequencies into 3 weighted groups expressed as a percentage of the total frequency range.
:param noise_floor: "High-pass" frequency, exclude frequencies below this value
:param low: Percentage of frequency ranges to count in the low bin (as a float, 0.5 = 50%)
:param mid: Percentage of frequency ranges to count in the mid bin (as a float, 0.5 = 50%)
:param high: Optional percentage for high bin, effectively creates a "Low-pass" if total percentage is less than 100%
"""
if high is None:
high = 1.0 - low - mid
recording = self._record()
magnitude = numpy.abs(numpy.fft.rfft(recording[:, 0], n=self.sample_rate))
sample_count = (self.sample_rate // 2) - noise_floor
mid_start = noise_floor + int(sample_count * low)
high_start = mid_start + int(sample_count * mid)
noise_ceiling = high_start + int(sample_count * high)
amp_low = numpy.mean(magnitude[noise_floor:mid_start])
amp_mid = numpy.mean(magnitude[mid_start:high_start])
amp_high = numpy.mean(magnitude[high_start:noise_ceiling])
amp_total = (amp_low + amp_mid + amp_high) / 3.0
return amp_low, amp_mid, amp_high, amp_total
def _record(self):
return sounddevice.rec(
int(self.duration * self.sample_rate),
samplerate=self.sample_rate,
blocking=True,
channels=1,
dtype='float64'
)

78
library/grow/pump.py Normal file
View File

@@ -0,0 +1,78 @@
import time
import atexit
import RPi.GPIO as GPIO
PUMP_1_PIN = 17
PUMP_2_PIN = 27
PUMP_3_PIN = 22
_is_setup = False
class Pump(object):
__slots__ = 'out1', 'out2', 'out3'
def __init__(self, out1, out2, out3):
self.out1 = out1
self.out2 = out2
self.out3 = out3
def __repr__(self):
fmt = """Pump 1: {out1}
Pump 1: {out2} Ohms
Pump 1: {out3} Ohms"""
return fmt.format(
out1=self.out1,
out2=self.out2,
out3=self.out3)
__str__ = __repr__
def setup():
global _is_setup
global _pump
if _is_setup:
return
_is_setup = True
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(PUMP_1_PIN, GPIO.OUT)
GPIO.input(PUMP_1_PIN, 0)
GPIO.setup(PUMP_2_PIN, GPIO.OUT)
GPIO.input(PUMP_2_PIN, 0)
GPIO.setup(PUMP_3_PIN, GPIO.OUT)
GPIO.input(PUMP_3_PIN, 0)
atexit.register(cleanup)
def set_pump_on(channel):
"""Set wet point for a moisture channel."""
_pump[channel] = 1
def set_pump_off(channel, value):
"""Set wet point for a moisture channel."""
_pump[channel] = 0
def read_pump(channel):
"""Get current value for the additional ADC pin."""
setup()
return _pump[channel]
def cleanup():
GPIO.output(PUMP_1_PIN, 0)
GPIO.output(PUMP_2_PIN, 0)
GPIO.output(PUMP_3_PIN, 0)
def read_all():
"""Return pump state"""
setup()
in1 = _pump[1]
in2 = _pump[2]
in3 = _pump[3]
return _pump(in1, in2, in3)

View File

@@ -1,15 +1,15 @@
# -*- coding: utf-8 -*-
[metadata]
name = enviroplus
version = 0.0.3
author = Philip Howard
author_email = phil@pimoroni.com
description = Enviro pHAT Plus environmental monitoring add-on for Raspberry Pi
name = grow
version = 0.0.1
author = Philip Howard, Paul Beech
author_email = paul@pimoroni.com
description = Grow HAT Mini. A plant valet add-on for the Raspberry Pi
long_description = file: README.rst
keywords = Raspberry Pi
url = https://www.pimoroni.com
project_urls =
GitHub=https://www.github.com/pimoroni/enviroplus-python
GitHub=https://www.github.com/pimoroni/grow-python
license = MIT
# This includes the license file(s) in the wheel.
# https://wheel.readthedocs.io/en/stable/user_guide.html#including-license-files-in-the-generated-wheel-file
@@ -26,13 +26,9 @@ classifiers =
Topic :: System :: Hardware
[options]
packages = enviroplus
packages = grow
install_requires =
pimoroni-bme280
pms5003
ltr559
st7735
ads1015
fonts
font-roboto
astral
@@ -58,7 +54,6 @@ py2deps =
python-pil
python-spidev
python-rpi.gpio
libportaudio2
py3deps =
python3-pip
python3-numpy
@@ -66,14 +61,8 @@ py3deps =
python3-pil
python3-spidev
python3-rpi.gpio
libportaudio2
configtxt =
dtoverlay=pi3-miniuart-bt
dtoverlay=adau7002-simple
commands =
printf "Setting up i2c and SPI..\n"
raspi-config nonint do_spi 0
raspi-config nonint do_i2c 0
printf "Setting up serial for PMS5003..\n"
raspi-config nonint do_serial 1 # Disable serial terminal over /dev/ttyAMA0
raspi-config nonint set_config_var enable_uart 1 $CONFIG # Enable serial port

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python
"""
Copyright (c) 2016 Pimoroni
Copyright (c) 2020 Pimoroni
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

View File

@@ -18,15 +18,15 @@ class SMBusFakeDevice(MockSMBus):
def cleanup():
yield None
try:
del sys.modules['enviroplus']
del sys.modules['grow']
except KeyError:
pass
try:
del sys.modules['enviroplus.noise']
del sys.modules['grow.moisture']
except KeyError:
pass
try:
del sys.modules['enviroplus.gas']
del sys.modules['grow.pump']
except KeyError:
pass
@@ -72,15 +72,6 @@ def atexit():
del sys.modules['atexit']
@pytest.fixture(scope='function', autouse=False)
def sounddevice():
"""Mock sounddevice module."""
sounddevice = mock.MagicMock()
sys.modules['sounddevice'] = sounddevice
yield sounddevice
del sys.modules['sounddevice']
@pytest.fixture(scope='function', autouse=False)
def numpy():
"""Mock numpy module."""

View File

@@ -1,48 +0,0 @@
import pytest
def test_noise_setup(sounddevice, numpy):
from enviroplus.noise import Noise
noise = Noise(sample_rate=16000, duration=0.1)
del noise
def test_noise_get_amplitudes_at_frequency_ranges(sounddevice, numpy):
from enviroplus.noise import Noise
noise = Noise(sample_rate=16000, duration=0.1)
noise.get_amplitudes_at_frequency_ranges([
(100, 500),
(501, 1000)
])
sounddevice.rec.assert_called_with(0.1 * 16000, samplerate=16000, blocking=True, channels=1, dtype='float64')
def test_noise_get_noise_profile(sounddevice, numpy):
from enviroplus.noise import Noise
numpy.mean.return_value = 10.0
noise = Noise(sample_rate=16000, duration=0.1)
amp_low, amp_mid, amp_high, amp_total = noise.get_noise_profile(
noise_floor=100,
low=0.12,
mid=0.36,
high=None)
sounddevice.rec.assert_called_with(0.1 * 16000, samplerate=16000, blocking=True, channels=1, dtype='float64')
assert amp_total == 10.0
def test_get_amplitude_at_frequency_range(sounddevice, numpy):
from enviroplus.noise import Noise
noise = Noise(sample_rate=16000, duration=0.1)
noise.get_amplitude_at_frequency_range(0, 8000)
with pytest.raises(ValueError):
noise.get_amplitude_at_frequency_range(0, 16000)

View File

@@ -1,66 +1,56 @@
def test_gas_setup(GPIO, smbus):
from enviroplus import gas
gas._is_setup = False
gas.setup()
gas.setup()
def test_moisture_setup(GPIO, smbus):
from grow import moisture
moisture._is_setup = False
moisture.setup()
moisture.setup()
def test_gas_read_all(GPIO, smbus):
from enviroplus import gas
gas._is_setup = False
result = gas.read_all()
def test_moisture_read_all(GPIO, smbus):
from grow import moisture
moisture._is_setup = False
result = moisture.read_all()
assert type(result.oxidising) == float
assert int(result.oxidising) == 16641
assert type(result(1)) == float
assert int(result(1)) == 100
assert type(result.reducing) == float
assert int(result.reducing) == 16727
assert type(result(2)) == float
assert int(result(2)) == 500
assert type(result.nh3) == float
assert int(result.nh3) == 16813
assert type(result.(3)) == float
assert int(result.(3)) == 5000
assert "Oxidising" in str(result)
assert "Moisture" in str(result)
def test_gas_read_each(GPIO, smbus):
from enviroplus import gas
gas._is_setup = False
def test_moisture_read_each(GPIO, smbus):
from grow import moisture
moisture._is_setup = False
assert int(gas.read_oxidising()) == 16641
assert int(gas.read_reducing()) == 16727
assert int(gas.read_nh3()) == 16813
assert int(moisture.read(1)) == 100
assert int(moisture.read(2)) == 500
assert int(moisture.read(3)) == 5000
def test_gas_read_adc(GPIO, smbus):
from enviroplus import gas
gas._is_setup = False
def test_moisture_cleanup(GPIO, smbus):
from grow import moisture
moisture.cleanup()
gas.enable_adc(True)
gas.set_adc_gain(2.048)
assert gas.read_adc() == 0.255
GPIO.input.assert_called_with(moisture.MOISTURE_1_PIN, 0)
GPIO.input.assert_called_with(moisture.MOISTURE_2_PIN, 0)
GPIO.input.assert_called_with(moisture.MOISTURE_3_PIN, 0)
def test_pump_setup(GPIO, smbus):
from grow import pump
moisture._is_setup = False
moisture.setup()
moisture.setup()
def test_gas_read_adc_default_gain(GPIO, smbus):
from enviroplus import gas
gas._is_setup = False
def test_pump_cleanup(GPIO, smbus):
from grow import pump
pump.cleanup()
gas.enable_adc(True)
gas.set_adc_gain(gas.MICS6814_GAIN)
assert gas.read_adc() == 0.765
def test_gas_read_adc_str(GPIO, smbus):
from enviroplus import gas
gas._is_setup = False
gas.enable_adc(True)
gas.set_adc_gain(2.048)
assert 'ADC' in str(gas.read_all())
def test_gas_cleanup(GPIO, smbus):
from enviroplus import gas
gas.cleanup()
GPIO.output.assert_called_with(gas.MICS6814_HEATER_PIN, 0)
GPIO.input.assert_called_with(moisture.PUMP_1_PIN, 0)
GPIO.input.assert_called_with(moisture.PUMP_2_PIN, 0)
GPIO.input.assert_called_with(moisture.PUMP_3_PIN, 0)