# 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.
"""
The :mod:`pisense` module is the main namespace for the pisense package; it
imports (and exposes) all publically accessible classes, functions, and
constants from all the modules beneath it for convenience. It also defines
the top-level :class:`SenseHAT` class.
"""
from __future__ import (
unicode_literals,
absolute_import,
print_function,
division,
)
import io
import os
import warnings
import subprocess
from .exc import (
SenseWarning,
SenseHATReinit,
SenseStickWarning,
SenseStickBufferFull,
SenseStickCallbackRead,
)
from .formats import (
color_dtype,
buf_to_image, buf_to_rgb888, buf_to_rgb, iter_to_rgb,
image_to_rgb565, rgb565_to_image,
image_to_rgb888, rgb888_to_image,
image_to_rgb, rgb_to_image,
rgb888_to_rgb565, rgb565_to_rgb888,
rgb_to_rgb888, rgb888_to_rgb,
rgb_to_rgb565, rgb565_to_rgb,
)
from .array import ScreenArray, array
from .screen import SenseScreen, DEFAULT_GAMMA, LOW_GAMMA
from .easings import linear, ease_in, ease_out, ease_in_out
from .anim import draw_text, scroll_text, fade_to, slide_to, zoom_to
from .stick import SenseStick, StickEvent
from .imu import SenseIMU, IMUState, IMUVector, IMUOrient
from .environ import (
SenseEnviron, EnvironReadings,
temp_pressure, temp_humidity, temp_average, temp_both
)
from .settings import SenseSettings
[docs]class SenseHAT(object):
"""
An instance of this class represents the Sense HAT as a whole. It provides
attributes for objects that represent each component of the HAT, including:
* :attr:`stick` for the joystick
* :attr:`screen` for the display
* :attr:`environ` for the environmental sensors
* :attr:`imu` for the Inertial Measurement Unit (IMU)
The *settings* parameter can be used to point to alternate settings files
but it is strongly recommended you leave this at the default as this can
affect the calibration of the IMU. Other keyword arguments are used in the
initialization of the subordinate objects; see the documentation for their
classes for further information.
One particular keyword argument, *emulate*, takes its default from an
environment variable: ``PISENSE_EMULATE``. If set, this must be an integer
number, typically 0 or 1 (0 is assumed if the variable is not set). This
argument indicates whether the instance should attach to the "real" Sense
HAT or the desktop `Sense HAT emulator`_. The environment variable is
particularly useful as it means scripts can be tested against the emulator
without alteration. For example:
.. code-block:: console
$ PISENSE_EMULATE=1 python examples/rainbow.py
.. warning::
Your script should not attempt to create more than one instance of this
class (given it represents a single piece of hardware). If you attempt
to do so a :exc:`SenseHATReinit` warning will be raised and the
existing instance will be returned.
.. _Sense HAT emulator: https://sense-emu.readthedocs.io/
"""
__slots__ = ('_settings', '_screen', '_stick', '_imu', '_environ')
hat = None
def __new__(cls, settings='/etc/RTIMULib.ini', **kwargs):
if SenseHAT.hat is not None:
warnings.warn(
SenseHATReinit("The SenseHAT class has already been "
"instantiated; returning existing instance"))
self = SenseHAT.hat
else:
self = super(SenseHAT, cls).__new__(cls)
self._settings = None
self._screen = None
self._stick = None
self._imu = None
self._environ = None
try:
SenseHAT.hat = self
# Old-style kw-only args...
fps = kwargs.pop('fps', 15)
easing = kwargs.pop('easing', linear)
max_events = kwargs.pop('max_events', 100)
flush_input = kwargs.pop('flush_input', True)
emulate_default = bool(int(os.environ.get(
'PISENSE_EMULATE', '0')))
emulate = kwargs.pop('emulate', emulate_default)
if kwargs:
raise TypeError("unexpected keyword argument %r" %
kwargs.popitem()[0])
if emulate:
from sense_emu.lock import EmulatorLock
lock = EmulatorLock('sense_emu')
if not lock.wait(1):
# XXX All this stuff should be a method on EmulatorLock
warnings.warn(Warning('No emulator detected; '
'spawning sense_emu_gui'))
with io.open(os.devnull, 'r+b') as devnull:
try:
setpgrp = os.setpgrp
except AttributeError:
setpgrp = None
# setpgrp is called to spawn a new process group,
# ensuring that signals from the interpreter (e.g.
# the user pressing Ctrl+C) don't get sent to the
# emulator too
subprocess.Popen(['sense_emu_gui'],
preexec_fn=setpgrp,
stdin=devnull, stdout=devnull,
stderr=devnull, close_fds=True)
if not lock.wait(10):
raise RuntimeError('Failed to launch emulator')
# pylint: disable=protected-access
self._settings = SenseSettings(settings, emulate=emulate)
self._screen = SenseScreen(fps, easing, emulate=emulate)
self._stick = SenseStick(max_events, flush_input, emulate=emulate)
self._imu = SenseIMU(self._settings, emulate=emulate)
self._environ = SenseEnviron(self._settings, emulate=emulate)
except:
self.close()
raise
return self
[docs] def close(self):
"""
Call the :meth:`close` method to close the Sense HAT 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 Sense HAT may return an error (but are not guaranteed to do so).
"""
if self._environ is not None:
self._environ.close()
self._environ = None
if self._imu is not None:
self._imu.close()
self._imu = None
if self._stick is not None:
self._stick.close()
self._stick = None
if self._screen is not None:
self._screen.close()
self._screen = None
SenseHAT.hat = None
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, exc_tb):
self.close()
@property
def settings(self):
"""
Returns a :class:`SenseSettings` object representing the Sense HAT's
configuration settings.
"""
return self._settings
@property
def screen(self):
"""
Returns a :class:`SenseScreen` object representing the Sense HAT's
display.
"""
return self._screen
@property
def stick(self):
"""
Returns a :class:`SenseStick` object representing the Sense HAT's
joystick.
"""
return self._stick
@property
def imu(self):
"""
Returns a :class:`SenseIMU` object representing the `Inertial
Measurement Unit`_ (IMU) on the Sense HAT.
.. _Inertial Measurement Unit: https://en.wikipedia.org/wiki/Inertial_measurement_unit
"""
return self._imu
@property
def environ(self):
"""
Returns a :class:`SenseEnviron` object representing the environmental
sensors on the Sense HAT.
"""
return self._environ
@property
def rotation(self):
"""
Gets or sets the rotation (around the Z-axis) of the Sense HAT. When
querying this attribute, only the screen's rotation is queried. When
set, the attribute affects the screen, joystick, *and* IMU.
"""
return self._screen.rotation
@rotation.setter
def rotation(self, value):
self._screen.rotation = value
self._stick.rotation = value
self._imu.rotation = value