# vim: set et sw=4 sts=4 fileencoding=utf-8:
#
# Alternative API for the Sense HAT
# Copyright (c) 2016-2018 Dave Jones <dave@waveform.org.uk>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
"""
Defines the :class:`SenseEnviron` and :class:`EnvironReadings` classes for
querying the environmental sensors on the Sense HAT.
"""
from __future__ import (
unicode_literals,
absolute_import,
print_function,
division,
)
import time
from collections import namedtuple
from .settings import SenseSettings
[docs]class EnvironReadings(namedtuple('EnvironReadings', ('pressure', 'humidity', 'temperature'))):
"""
A :func:`~collections.namedtuple` representing the readings from the
environmental sensors as a named 3-tuple containing the fields *pressure*
(in mbar or hPa), *humidity* (in %RH), and *temperature* (in °C)
respectively.
"""
__slots__ = ()
def __repr__(self):
return 'EnvironReadings(pressure=%g, humidity=%g, temperature=%g)' % self
[docs]def temp_pressure(p_temp, h_temp):
"""
Use this function as :attr:`~SenseEnviron.temp_source` if you want
to read temperature from the pressure sensor only. This is the default.
"""
# pylint: disable=unused-argument
return p_temp
[docs]def temp_humidity(p_temp, h_temp):
"""
Use this function as :attr:`~SenseEnviron.temp_source` if you want
to read temperature from the humidity sensor only.
"""
# pylint: disable=unused-argument
return h_temp
[docs]def temp_average(p_temp, h_temp):
"""
Use this function as :attr:`~SenseEnviron.temp_source` if you wish
to read the average of both the pressure and humidity sensor's
temperatures.
"""
if p_temp is None:
return h_temp
elif h_temp is None:
return p_temp
else:
return (p_temp + h_temp) / 2
[docs]def temp_both(p_temp, h_temp):
"""
Use this function as :attr:`~SenseEnviron.temp_source` if you wish
to return both the pressure and humidity sensor's temperature readings as a
tuple from the :attr:`~SenseEnviron.temperature` attribute.
"""
return p_temp, h_temp
[docs]class SenseEnviron(object):
"""
The :class:`SenseEnviron` class represents the suite of environmental
sensors on the Sense HAT. Users can either instantiate this class
themselves, or can access an instance from :attr:`SenseHAT.environ`.
The :attr:`temperature`, :attr:`pressure`, and :attr:`humidity` attributes
can be queried to read the current values from the sensors. Alternatively,
the instance can be treated as an iterator in which case readings will be
yielded as they are detected::
hat = SenseHAT()
for reading in hat.environ:
print(reading.temperature)
Because both the pressure and humidity sensors contain a temperature
sensor, a source must be selected for the temperature reading. By default
this is from the pressure sensor only, but you can specify a function for
:attr:`temperature_source` which, given the two temperature readings
returns the reading you are interested in, or some combination there-of.
The *settings* parameter can be used to point to alternate settings files.
The *temp_source* parameter provides an initial value for the
:attr:`temp_source` attribute (this defaults to :func:`temp_humidity`). If
the *emulate* parameter is ``True``, the instance will connect to the
environment sensors in the `desktop Sense HAT emulator`_ instead of the
"real" Sense HAT's sensors.
.. _desktop Sense HAT emulator: https://sense-emu.readthedocs.io/
"""
__slots__ = (
'_settings',
'_p_sensor',
'_h_sensor',
'_readings',
'_temp_source',
'_interval',
'_last_read',
)
def __init__(self, settings=None, temp_source=temp_humidity,
emulate=False):
if emulate:
from sense_emu import RTIMU
else:
import RTIMU
if not isinstance(settings, SenseSettings):
settings = SenseSettings(settings)
self._settings = settings
self._p_sensor = RTIMU.RTPressure(self._settings.settings)
self._h_sensor = RTIMU.RTHumidity(self._settings.settings)
if not self._p_sensor.pressureInit():
raise RuntimeError('Pressure sensor initialization failed')
if not self._h_sensor.humidityInit():
raise RuntimeError('Humidity sensor initialization failed')
self._readings = EnvironReadings(None, None, None)
self._temp_source = temp_source
self._interval = 0.04
self._last_read = None
[docs] def close(self):
"""
Call the :meth:`close` method to close the environmental sensor
interface and free up any background resources. The method is
idempotent (you can call it multiple times without error) and after it
is called, any operations on the environmental sensors may return an
error (but are not guaranteed to do so).
"""
self._p_sensor = None
self._h_sensor = None
self._settings = None
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, exc_tb):
self.close()
def __iter__(self):
while True:
yield self.read()
[docs] def read(self):
"""
Return the current state of all environmental sensors as an
:class:`EnvironReadings` tuple.
.. note::
This method will wait until the next set of readings are available,
and then return them. Hence it is suitable for use in a loop
without additional waits, although it may be simpler to simply
treat the instance as an iterator in that case.
This is in contrast to reading the :attr:`pressure`,
:attr:`humidity`, and :attr:`temperature` attributes which always
return immediately.
"""
self._read(True)
return self._readings
def _read(self, wait):
now = time.time()
if self._last_read is not None:
if wait:
time.sleep(max(0.0, self._interval - (now - self._last_read)))
elif now - self._last_read < self._interval:
return
p_valid, p_value, tp_valid, tp_value = self._p_sensor.pressureRead()
h_valid, h_value, th_valid, th_value = self._h_sensor.humidityRead()
self._readings = EnvironReadings(
pressure=p_value if p_valid else None,
humidity=h_value if h_valid else None,
temperature=self._temp_source(
tp_value if tp_valid else None,
th_value if th_valid else None)
if tp_valid or th_valid else None)
self._last_read = now
@property
def pressure(self):
"""
Return the current pressure reading from the environmental sensors.
The pressure is measured in `millibars`_ (aka `hectopascals`_).
.. _millibars: https://en.wikipedia.org/wiki/Bar_(unit)
.. _hectopascals: https://en.wikipedia.org/wiki/Pascal_(unit)
"""
self._read(False)
return self._readings.pressure
@property
def humidity(self):
"""
Return the current humidity reading from the environmental sensors.
The humidity is measured as a % of `relative humidity`_.
.. _relative humidity: https://en.wikipedia.org/wiki/Relative_humidity
"""
self._read(False)
return self._readings.humidity
@property
def temperature(self):
"""
Return the current temperature reading from the environment sensors.
The temperature is measured in degrees `celsius`_.
.. _celsius: https://en.wikipedia.org/wiki/Celsius
"""
self._read(False)
return self._readings.temperature
@property
def temp_source(self):
"""
Specify the conversion function for the temperature sensors.
The Sense HAT contains two temperature sensors, one in the humidity
sensor, and one in the pressure sensor. The :attr:`temp_source`
property contains the function that is used to determine how the
temperature is reported from the two sources. The function must take
two parameters (the readings from the humidity and pressure sensors
respectively) and can return whatever you wish to see as the value of
the :attr:`temperature` property (including a tuple of both
temperatures).
The default value is :func:`temp_humidity` which simply returns the
reading from the humidity sensor, discarding the the pressure sensor
reading.
.. warning::
You may be tempted to average the two readings under the assumption
that this will provide more accuracy. This is almost certainly not
the case!
"""
return self._temp_source
@temp_source.setter
def temp_source(self, value):
try:
value(20, 22)
except TypeError:
raise ValueError('temp_source must be a callable that accepts '
'2 parameters')
self._temp_source = value
self._last_read = None