pax_global_header00006660000000000000000000000064127411674200014516gustar00rootroot0000000000000052 comment=86b03178a39813237618e58b69187ad0a113cabb python-sense-hat-2.2.0/000077500000000000000000000000001274116742000147255ustar00rootroot00000000000000python-sense-hat-2.2.0/.gitignore000066400000000000000000000000551274116742000167150ustar00rootroot00000000000000*.pyc *.egg-info/ build/ dist/ pythonhosted/ python-sense-hat-2.2.0/CONTRIBUTING.md000066400000000000000000000005501274116742000171560ustar00rootroot00000000000000# Contributing ## Issues Please report bugs and other issues as [GitHub issues](https://github.com/RPi-Distro/python-sense-hat/issues) ensuring to give as much detail about your problem as possible. ## Pull Requests Please create a new pull request for each change recommendation. ## Policy - Python 2/3 compatability - PEP8-compliance (with exceptions) python-sense-hat-2.2.0/LICENCE.txt000066400000000000000000000027441274116742000165370ustar00rootroot00000000000000Copyright 2015- Raspberry Pi Foundation 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. python-sense-hat-2.2.0/MANIFEST.in000066400000000000000000000002111274116742000164550ustar00rootroot00000000000000include README.rst include CONTRIBUTING.md include LICENCE.txt include sense_hat/sense_hat_text.png include sense_hat/sense_hat_text.txt python-sense-hat-2.2.0/README.rst000066400000000000000000000044441274116742000164220ustar00rootroot00000000000000========= Sense HAT ========= Python module to control the `Raspberry Pi`_ Sense HAT used in the `Astro Pi`_ mission - an education outreach programme for UK schools sending code experiments to the International Space Station. Hardware ======== The Sense HAT features an 8x8 RGB LED matrix, a mini joystick and the following sensors: * Gyroscope * Accelerometer * Magnetometer * Temperature * Humidity * Barometric pressure Buy === Buy the Sense HAT from: * `The Pi Hut`_ * `Pimoroni`_ * `Amazon (UK)`_ * `element14`_ * `adafruit`_ * `Amazon (USA)`_ Installation ============ To install the Sense HAT software, enter the following commands in a terminal:: sudo apt-get update sudo apt-get install sense-hat sudo reboot Usage ===== Import the sense_hat module and instantiate a SenseHat object:: from sense_hat import SenseHat sense = SenseHat() Documentation ============= Comprehensive documentation is available at `pythonhosted.org/sense-hat`_ Contributors ============ * `Dave Honess`_ * `Ben Nuttall`_ * `Serge Schneider`_ * `Dave Jones`_ * `Tyler Laws`_ Open Source =========== * The code is licensed under the `BSD Licence`_ * The project source code is hosted on `GitHub`_ * Please use `GitHub issues`_ to submit bugs and report issues .. _Raspberry Pi: https://www.raspberrypi.org/ .. _Astro Pi: http://www.astro-pi.org/ .. _pythonhosted.org/sense-hat: http://pythonhosted.org/sense-hat/ .. _Dave Honess: https://github.com/davidhoness .. _Ben Nuttall: https://github.com/bennuttall .. _Serge Schneider: https://github.com/XECDesign .. _Dave Jones: https://github.com/waveform80 .. _Tyler Laws: https://github.com/tyler-laws .. _BSD Licence: http://opensource.org/licenses/BSD-3-Clause .. _GitHub: https://github.com/RPi-Distro/python-sense-hat .. _GitHub Issues: https://github.com/RPi-Distro/python-sense-hat/issues .. _`The Pi Hut`: http://thepihut.com/products/raspberry-pi-sense-hat-astro-pi .. _`Pimoroni`: https://shop.pimoroni.com/products/raspberry-pi-sense-hat .. _`Amazon (UK)`: http://www.amazon.co.uk/Raspberry-Pi-2483095-Sense-HAT/dp/B014T2IHQ8/ .. _element14: https://www.element14.com/community/docs/DOC-78155/l/raspberry-pi-sense-hat .. _adafruit: https://www.adafruit.com/products/2738 .. _Amazon (USA): http://www.amazon.com/Raspberry-Pi-Sense-HAT-AstroPi/dp/B014HDG74S python-sense-hat-2.2.0/docs/000077500000000000000000000000001274116742000156555ustar00rootroot00000000000000python-sense-hat-2.2.0/docs/api.md000066400000000000000000000513721274116742000167600ustar00rootroot00000000000000# Sense HAT API Reference ## LED Matrix ### set_rotation If you're using the Pi upside down or sideways you can use this function to correct the orientation of the image being shown. Parameter | Type | Valid values | Explanation --- | --- | --- | --- `r` | Integer | `0` `90` `180` `270` | The angle to rotate the LED matrix though. `0` is with the Raspberry Pi HDMI port facing downwards. `redraw` | Boolean | `True` `False` | Whether or not to redraw what is already being displayed on the LED matrix. Defaults to `True` Returned type | Explanation --- | --- None | ```python from sense_hat import SenseHat sense = SenseHat() sense.set_rotation(180) # alternatives sense.rotation = 180 ``` - - - ### flip_h Flips the image on the LED matrix horizontally. Parameter | Type | Valid values | Explanation --- | --- | --- | --- `redraw` | Boolean | `True` `False` | Whether or not to redraw what is already being displayed on the LED matrix. Defaults to `True` Returned type | Explanation --- | --- List | A list containing 64 smaller lists of `[R, G, B]` pixels (red, green, blue) representing the flipped image. ```python from sense_hat import SenseHat sense = SenseHat() sense.flip_h() ``` - - - ### flip_v Flips the image on the LED matrix vertically. Parameter | Type | Valid values | Explanation --- | --- | --- | --- `redraw` | Boolean | `True` `False` | Whether or not to redraw what is already being displayed on the LED matrix when flipped. Defaults to `True` Returned type | Explanation --- | --- List | A list containing 64 smaller lists of `[R, G, B]` pixels (red, green, blue) representing the flipped image. ```python from sense_hat import SenseHat sense = SenseHat() sense.flip_v() ``` - - - ### set_pixels Updates the entire LED matrix based on a 64 length list of pixel values. Parameter | Type | Valid values | Explanation --- | --- | --- | --- `pixel_list` | List | `[[R, G, B] * 64]` | A list containing 64 smaller lists of `[R, G, B]` pixels (red, green, blue). Each R-G-B element must be an integer between 0 and 255. Returned type | Explanation --- | --- None | ```python from sense_hat import SenseHat sense = SenseHat() X = [255, 0, 0] # Red O = [255, 255, 255] # White question_mark = [ O, O, O, X, X, O, O, O, O, O, X, O, O, X, O, O, O, O, O, O, O, X, O, O, O, O, O, O, X, O, O, O, O, O, O, X, O, O, O, O, O, O, O, X, O, O, O, O, O, O, O, O, O, O, O, O, O, O, O, X, O, O, O, O ] sense.set_pixels(question_mark) ``` - - - ### get_pixels Returned type | Explanation --- | --- List | A list containing 64 smaller lists of `[R, G, B]` pixels (red, green, blue) representing the currently displayed image. ```python from sense_hat import SenseHat sense = SenseHat() pixel_list = sense.get_pixels() ``` Note: You will notice that the pixel values you pass into `set_pixels` sometimes change when you read them back with `get_pixels`. This is because we specify each pixel element as 8 bit numbers (0 to 255) but when they're passed into the Linux frame buffer for the LED matrix the numbers are bit shifted down to fit into RGB 565. 5 bits for red, 6 bits for green and 5 bits for blue. The loss of binary precision when performing this conversion (3 bits lost for red, 2 for green and 3 for blue) accounts for the discrepancies you see. The `get_pixels` function provides a correct representation of how the pixels end up in frame buffer memory after you've called `set_pixels`. - - - ### set_pixel Sets an individual LED matrix pixel at the specified X-Y coordinate to the specified colour. Parameter | Type | Valid values | Explanation --- | --- | --- | --- `x` | Integer | `0 - 7` | 0 is on the left, 7 on the right. `y` | Integer | `0 - 7` | 0 is at the top, 7 at the bottom. Colour can either be passed as an RGB tuple: ||| `pixel` | Tuple or List | `(r, g, b)` | Each element must be an integer between 0 and 255. Or three separate values for red, green and blue: ||| `r` | Integer | `0 - 255` | The Red element of the pixel. `g` | Integer | `0 - 255` | The Green element of the pixel. `b` | Integer | `0 - 255` | The Blue element of the pixel. Returned type | Explanation --- | --- None | ```python from sense_hat import SenseHat sense = SenseHat() # examples using (x, y, r, g, b) sense.set_pixel(0, 0, 255, 0, 0) sense.set_pixel(0, 7, 0, 255, 0) sense.set_pixel(7, 0, 0, 0, 255) sense.set_pixel(7, 7, 255, 0, 255) red = (255, 0, 0) green = (0, 255, 0) blue = (0, 0, 255) # examples using (x, y, pixel) sense.set_pixel(0, 0, red) sense.set_pixel(0, 0, green) sense.set_pixel(0, 0, blue) ``` - - - ### get_pixel Parameter | Type | Valid values | Explanation --- | --- | --- | --- `x` | Integer | `0 - 7` | 0 is on the left, 7 on the right. `y` | Integer | `0 - 7` | 0 is at the top, 7 at the bottom. Returned type | Explanation --- | --- List | Returns a list of `[R, G, B]` representing the colour of an individual LED matrix pixel at the specified X-Y coordinate. ```python from sense_hat import SenseHat sense = SenseHat() top_left_pixel = sense.get_pixel(0, 0) ``` Note: Please read the note under `get_pixels` - - - ### load_image Loads an image file, converts it to RGB format and displays it on the LED matrix. The image must be 8 x 8 pixels in size. Parameter | Type | Valid values | Explanation --- | --- | --- | --- `file_path` | String | Any valid file path. | The file system path to the image file to load. `redraw` | Boolean | `True` `False` | Whether or not to redraw the loaded image file on the LED matrix. Defaults to `True` ```python from sense_hat import SenseHat sense = SenseHat() sense.load_image("space_invader.png") ``` Returned type | Explanation --- | --- List | A list containing 64 smaller lists of `[R, G, B]` pixels (red, green, blue) representing the loaded image after RGB conversion. ```python from sense_hat import SenseHat sense = SenseHat() invader_pixels = sense.load_image("space_invader.png", redraw=False) ``` - - - ### clear Sets the entire LED matrix to a single colour, defaults to blank / off. Parameter | Type | Valid values | Explanation --- | --- | --- | --- `colour` | Tuple or List | `(r, g, b)` | A tuple or list containing the RGB (red, green, blue) values of the colour. Each element must be an integer between 0 and 255. Defaults to `(0, 0, 0)`. Alternatively, the RGB values can be passed individually:||| `r` | Integer | `0 - 255` | The Red element of the colour. `g` | Integer | `0 - 255` | The Green element of the colour. `b` | Integer | `0 - 255` | The Blue element of the colour. ```python from sense_hat import SenseHat from time import sleep sense = SenseHat() red = (255, 0, 0) sense.clear() # no arguments defaults to off sleep(1) sense.clear(red) # passing in an RGB tuple sleep(1) sense.clear(255, 255, 255) # passing in r, g and b values of a colour ``` - - - ### show_message Scrolls a text message from right to left across the LED matrix and at the specified speed, in the specified colour and background colour. Parameter | Type | Valid values | Explanation --- | --- | --- | --- `text_string` | String | Any text string. | The message to scroll. `scroll_speed` | Float | Any floating point number. | The speed at which the text should scroll. This value represents the time paused for between shifting the text to the left by one column of pixels. Defaults to `0.1` `text_colour` | List | `[R, G, B]` | A list containing the R-G-B (red, green, blue) colour of the text. Each R-G-B element must be an integer between 0 and 255. Defaults to `[255, 255, 255]` white. `back_colour` | List | `[R, G, B]` | A list containing the R-G-B (red, green, blue) colour of the background. Each R-G-B element must be an integer between 0 and 255. Defaults to `[0, 0, 0]` black / off. Returned type | Explanation --- | --- None | ```python from sense_hat import SenseHat sense = SenseHat() sense.show_message("One small step for Pi!", text_colour=[255, 0, 0]) ``` - - - ### show_letter Displays a single text character on the LED matrix. Parameter | Type | Valid values | Explanation --- | --- | --- | --- `s` | String | A text string of length 1. | The letter to show. `text_colour` | List | `[R, G, B]` | A list containing the R-G-B (red, green, blue) colour of the letter. Each R-G-B element must be an integer between 0 and 255. Defaults to `[255, 255, 255]` white. `back_colour` | List | `[R, G, B]` | A list containing the R-G-B (red, green, blue) colour of the background. Each R-G-B element must be an integer between 0 and 255. Defaults to `[0, 0, 0]` black / off. Returned type | Explanation --- | --- None | ```python import time from sense_hat import SenseHat sense = SenseHat() for i in reversed(range(0,10)): sense.show_letter(str(i)) time.sleep(1) ``` ### low_light Toggles the LED matrix low light mode, useful if the Sense HAT is being used in a dark environment. ```python import time from sense_hat import SenseHat sense = SenseHat() sense.clear(255, 255, 255) sense.low_light = True time.sleep(2) sense.low_light = False ``` ### gamma For advanced users. Most users will just need the `low_light` Boolean property above. The Sense HAT python API uses 8 bit (0 to 255) colours for R, G, B. When these are written to the Linux frame buffer they're bit shifted into RGB 5 6 5. The driver then converts them to RGB 5 5 5 before it passes them over to the ATTiny88 AVR for writing to the LEDs. The gamma property allows you to specify a gamma lookup table for the [final 5](http://en.battlestarwiki.org/wiki/Final_Five) bits of colour used. The lookup table is a list of 32 numbers that must be between 0 and 31. The value of the incoming 5 bit colour is used to index the lookup table and the value found at that position is then written to the LEDs. Type | Valid values | Explanation --- | --- | --- Tuple or List | Tuple or List of length 32 containing Integers between 0 and 31 | Gamma lookup table for the final 5 bits of colour ```python import time from sense_hat import SenseHat sense = SenseHat() sense.clear(255, 127, 0) print(sense.gamma) time.sleep(2) sense.gamma = reversed(sense.gamma) print(sense.gamma) time.sleep(2) sense.low_light = True print(sense.gamma) time.sleep(2) sense.low_light = False ``` ### gamma_reset A function to reset the gamma lookup table to default, ideal if you've been messing with it and want to get it back to a default state. Returned type | Explanation --- | --- None | ```python import time from sense_hat import SenseHat sense = SenseHat() sense.clear(255, 127, 0) time.sleep(2) sense.gamma = [0] * 32 # Will turn the LED matrix off time.sleep(2) sense.gamma_reset() ``` - - - ## Environmental sensors ### get_humidity Gets the percentage of relative humidity from the humidity sensor. Returned type | Explanation --- | --- Float | The percentage of relative humidity. ```python from sense_hat import SenseHat sense = SenseHat() humidity = sense.get_humidity() print("Humidity: %s %%rH" % humidity) # alternatives print(sense.humidity) ``` - - - ### get_temperature Calls `get_temperature_from_humidity` below. ```python from sense_hat import SenseHat sense = SenseHat() temp = sense.get_temperature() print("Temperature: %s C" % temp) # alternatives print(sense.temp) print(sense.temperature) ``` - - - ### get_temperature_from_humidity Gets the current temperature in degrees Celsius from the humidity sensor. Returned type | Explanation --- | --- Float | The current temperature in degrees Celsius. ```python from sense_hat import SenseHat sense = SenseHat() temp = sense.get_temperature_from_humidity() print("Temperature: %s C" % temp) ``` - - - ### get_temperature_from_pressure Gets the current temperature in degrees Celsius from the pressure sensor. Returned type | Explanation --- | --- Float | The current temperature in degrees Celsius. ```python from sense_hat import SenseHat sense = SenseHat() temp = sense.get_temperature_from_pressure() print("Temperature: %s C" % temp) ``` - - - ### get_pressure Gets the current pressure in Millibars from the pressure sensor. Returned type | Explanation --- | --- Float | The current pressure in Millibars. ```python from sense_hat import SenseHat sense = SenseHat() pressure = sense.get_pressure() print("Pressure: %s Millibars" % pressure) # alternatives print(sense.pressure) ``` - - - ## IMU Sensor The IMU (inertial measurement unit) sensor is a combination of three sensors, each with an x, y and z axis. For this reason it's considered to be a 9 dof (degrees of freedom) sensor. - Gyroscope - Accelerometer - Magnetometer (compass) This API allows you to use these sensors in any combination to measure orientation or as individual sensors in their own right. ### set_imu_config Enables and disables the gyroscope, accelerometer and/or magnetometer contribution to the get orientation functions below. Parameter | Type | Valid values | Explanation --- | --- | --- | --- `compass_enabled` | Boolean | `True` `False` | Whether or not the compass should be enabled. `gyro_enabled` | Boolean | `True` `False` | Whether or not the gyroscope should be enabled. `accel_enabled` | Boolean | `True` `False` | Whether or not the accelerometer should be enabled. Returned type | Explanation --- | --- None | ```python from sense_hat import SenseHat sense = SenseHat() sense.set_imu_config(False, True, False) # gyroscope only ``` - - - ### get_orientation_radians Gets the current orientation in radians using the aircraft principal axes of pitch, roll and yaw. Returned type | Explanation --- | --- Dictionary | A dictionary object indexed by the strings `pitch`, `roll` and `yaw`. The values are Floats representing the angle of the axis in radians. ```python from sense_hat import SenseHat sense = SenseHat() orientation_rad = sense.get_orientation_radians() print("p: {pitch}, r: {roll}, y: {yaw}".format(**orientation_rad)) # alternatives print(sense.orientation_radians) ``` - - - ### get_orientation_degrees Gets the current orientation in degrees using the aircraft principal axes of pitch, roll and yaw. Returned type | Explanation --- | --- Dictionary | A dictionary object indexed by the strings `pitch`, `roll` and `yaw`. The values are Floats representing the angle of the axis in degrees. ```python from sense_hat import SenseHat sense = SenseHat() orientation = sense.get_orientation_degrees() print("p: {pitch}, r: {roll}, y: {yaw}".format(**orientation)) ``` - - - ### get_orientation Calls `get_orientation_degrees` above. ```python from sense_hat import SenseHat sense = SenseHat() orientation = sense.get_orientation() print("p: {pitch}, r: {roll}, y: {yaw}".format(**orientation)) # alternatives print(sense.orientation) ``` - - - ### get_compass Calls `set_imu_config` to disable the gyroscope and accelerometer then gets the direction of North from the magnetometer in degrees. Returned type | Explanation --- | --- Float | The direction of North. ```python from sense_hat import SenseHat sense = SenseHat() north = sense.get_compass() print("North: %s" % north) # alternatives print(sense.compass) ``` - - - ### get_compass_raw Gets the raw x, y and z axis magnetometer data. Returned type | Explanation --- | --- Dictionary | A dictionary object indexed by the strings `x`, `y` and `z`. The values are Floats representing the magnetic intensity of the axis in **microteslas** (µT). ```python from sense_hat import SenseHat sense = SenseHat() raw = sense.get_compass_raw() print("x: {x}, y: {y}, z: {z}".format(**raw)) # alternatives print(sense.compass_raw) ``` - - - ### get_gyroscope Calls `set_imu_config` to disable the magnetometer and accelerometer then gets the current orientation from the gyroscope only. Returned type | Explanation --- | --- Dictionary | A dictionary object indexed by the strings `pitch`, `roll` and `yaw`. The values are Floats representing the angle of the axis in degrees. ```python from sense_hat import SenseHat sense = SenseHat() gyro_only = sense.get_gyroscope() print("p: {pitch}, r: {roll}, y: {yaw}".format(**gyro_only)) # alternatives print(sense.gyro) print(sense.gyroscope) ``` - - - ### get_gyroscope_raw Gets the raw x, y and z axis gyroscope data. Returned type | Explanation --- | --- Dictionary | A dictionary object indexed by the strings `x`, `y` and `z`. The values are Floats representing the rotational intensity of the axis in **radians per second**. ```python from sense_hat import SenseHat sense = SenseHat() raw = sense.get_gyroscope_raw() print("x: {x}, y: {y}, z: {z}".format(**raw)) # alternatives print(sense.gyro_raw) print(sense.gyroscope_raw) ``` - - - ### get_accelerometer Calls `set_imu_config` to disable the magnetometer and gyroscope then gets the current orientation from the accelerometer only. Returned type | Explanation --- | --- Dictionary | A dictionary object indexed by the strings `pitch`, `roll` and `yaw`. The values are Floats representing the angle of the axis in degrees. ```python from sense_hat import SenseHat sense = SenseHat() accel_only = sense.get_accelerometer() print("p: {pitch}, r: {roll}, y: {yaw}".format(**accel_only)) # alternatives print(sense.accel) print(sense.accelerometer) ``` - - - ### get_accelerometer_raw Gets the raw x, y and z axis accelerometer data. Returned type | Explanation --- | --- Dictionary | A dictionary object indexed by the strings `x`, `y` and `z`. The values are Floats representing the acceleration intensity of the axis in **Gs**. ```python from sense_hat import SenseHat sense = SenseHat() raw = sense.get_accelerometer_raw() print("x: {x}, y: {y}, z: {z}".format(**raw)) # alternatives print(sense.accel_raw) print(sense.accelerometer_raw) ``` - - - ## Joystick ### InputEvent A tuple describing a joystick event. Contains three named parameters: * `timestamp` - The time at which the event occurred, as a fractional number of seconds (the same format as the built-in `time` function) * `direction` - The direction the joystick was moved, as a string (`"up"`, `"down"`, `"left"`, `"right"`, `"push"`) * `action` - The action that occurred, as a string (`"pressed"`, `"released"`, `"held"`) This tuple type is used by several joystick methods either as the return type or the type of a parameter. - - - ### wait_for_event Blocks execution until a joystick event occurs, then returns an `InputEvent` representing the event that occurred. ```python from sense_hat import SenseHat from time import sleep sense = SenseHat() event = sense.stick.wait_for_event() print("The joystick was {} {}".format(event.action, event.direction)) sleep(0.1) event = sense.stick.wait_for_event() print("The joystick was {} {}".format(event.action, event.direction)) ``` In the above example, if you briefly push the joystick in a single direction you should see two events output: a pressed action and a released action. The optional *emptybuffer* can be used to flush any pending events before waiting for new events. Try the following script to see the difference: ```python from sense_hat import SenseHat from time import sleep sense = SenseHat() event = sense.stick.wait_for_event() print("The joystick was {} {}".format(event.action, event.direction)) sleep(0.1) event = sense.stick.wait_for_event(emptybuffer=True) print("The joystick was {} {}".format(event.action, event.direction)) ``` - - - ### get_events Returns a list of `InputEvent` tuples representing all events that have occurred since the last call to `get_events` or `wait_for_event`. ```python from sense_hat import SenseHat sense = SenseHat() while True: for event in sense.stick.get_events(): print("The joystick was {} {}".format(event.action, event.direction)) ``` - - - ### direction_up, direction_left, direction_right, direction_down, direction_middle, direction_any These attributes can be assigned a function which will be called whenever the joystick is pushed in the associated direction (or in any direction in the case of `direction_any`). The function assigned must either take no parameters or must take a single parameter which will be passed the associated `InputEvent`. ```python from sense_hat import SenseHat, ACTION_PRESSED, ACTION_HELD, ACTION_RELEASED from signal import pause x = 3 y = 3 sense = SenseHat() def clamp(value, min_value=0, max_value=7): return min(max_value, max(min_value, value)) def pushed_up(event): global y if event.action != ACTION_RELEASED: y = clamp(y - 1) def pushed_down(event): global y if event.action != ACTION_RELEASED: y = clamp(y + 1) def pushed_left(event): global x if event.action != ACTION_RELEASED: x = clamp(x - 1) def pushed_right(event): global x if event.action != ACTION_RELEASED: x = clamp(x + 1) def refresh(): sense.clear() sense.set_pixel(x, y, 255, 255, 255) sense.stick.direction_up = pushed_up sense.stick.direction_down = pushed_down sense.stick.direction_left = pushed_left sense.stick.direction_right = pushed_right sense.stick.direction_any = refresh refresh() pause() ``` Note that the `direction_any` event is always called *after* all other events making it an ideal hook for things like display refreshing (as in the example above). python-sense-hat-2.2.0/docs/changelog.md000066400000000000000000000007401274116742000201270ustar00rootroot00000000000000# Sense HAT Changelog ## v2 ### 2.1.0 - Added gamma, low light and other properties ### 2.0.0 - Library renamed from `astro_pi` to `sense_hat` - Class renamed from `AstroPi` to `SenseHat` - API otherwise unchanged ## v1 ### 1.1.6 - Updated IMU settings file path ### 1.1.5 - Introduced IMU settings file ### 1.1.4 - Made _get_char_pixels return cloned lists ### 1.1.0 - Fixed bug in `show_letter` ### 1.0.0 - API design confirmed ## v0 ### 0.0.0 - Alpha status python-sense-hat-2.2.0/docs/index.md000066400000000000000000000020401274116742000173020ustar00rootroot00000000000000# Sense HAT Python module to control the [Raspberry Pi Sense HAT](https://www.raspberrypi.org/products/sense-hat/) ## Features The Sense HAT features an 8x8 RGB LED matrix, a mini joystick and the following sensors: - Gyroscope - Accelerometer - Magnetometer - Temperature - Humidity - Barometric pressure ## Install Install the Sense HAT software by opening a Terminal window and entering the following commands (while connected to the Internet): ```bash sudo apt-get update sudo apt-get install sense-hat sudo reboot ``` ## Usage Hello world example: ```python from sense_hat import SenseHat sense = SenseHat() sense.show_message("Hello world!") ``` See the [API reference](api.md) for full documentation of the library's functions. See [examples](https://github.com/RPi-Distro/python-sense-hat/blob/master/examples/README.md). ## Development This library is maintained by the Raspberry Pi Foundation on GitHub at [github.com/RPi-Distro/python-sense-hat](https://github.com/RPi-Distro/python-sense-hat) See the [changelog](changelog.md). python-sense-hat-2.2.0/examples/000077500000000000000000000000001274116742000165435ustar00rootroot00000000000000python-sense-hat-2.2.0/examples/README.md000066400000000000000000000003561274116742000200260ustar00rootroot00000000000000# Sense HAT examples - [Colour cycle](colour_cycle.py) - [Compass](compass.py) - [PyGame Joystick](pygame_joystick.py) - [Rainbow](rainbow.py) - [Rotation](rotation.py) - [Space Invader](space_invader.py) - [Text scroll](text_scroll.py) python-sense-hat-2.2.0/examples/colour_cycle.py000077500000000000000000000011531274116742000216020ustar00rootroot00000000000000#!/usr/bin/python import time from sense_hat import SenseHat sense = SenseHat() r = 255 g = 0 b = 0 msleep = lambda x: time.sleep(x / 1000.0) def next_colour(): global r global g global b if (r == 255 and g < 255 and b == 0): g += 1 if (g == 255 and r > 0 and b == 0): r -= 1 if (g == 255 and b < 255 and r == 0): b += 1 if (b == 255 and g > 0 and r == 0): g -= 1 if (b == 255 and r < 255 and g == 0): r += 1 if (r == 255 and b > 0 and g == 0): b -= 1 while True: sense.clear([r, g, b]) msleep(2) next_colour() python-sense-hat-2.2.0/examples/compass.py000077500000000000000000000016471274116742000205750ustar00rootroot00000000000000#!/usr/bin/python import sys from sense_hat import SenseHat # To get good results with the magnetometer you must first calibrate it using # the program in RTIMULib/Linux/RTIMULibCal # The calibration program will produce the file RTIMULib.ini # Copy it into the same folder as your Python code led_loop = [4, 5, 6, 7, 15, 23, 31, 39, 47, 55, 63, 62, 61, 60, 59, 58, 57, 56, 48, 40, 32, 24, 16, 8, 0, 1, 2, 3] sense = SenseHat() sense.set_rotation(0) sense.clear() prev_x = 0 prev_y = 0 led_degree_ratio = len(led_loop) / 360.0 while True: dir = sense.get_compass() dir_inverted = 360 - dir # So LED appears to follow North led_index = int(led_degree_ratio * dir_inverted) offset = led_loop[led_index] y = offset // 8 # row x = offset % 8 # column if x != prev_x or y != prev_y: sense.set_pixel(prev_x, prev_y, 0, 0, 0) sense.set_pixel(x, y, 0, 0, 255) prev_x = x prev_y = y python-sense-hat-2.2.0/examples/evdev_joystick.py000066400000000000000000000030541274116742000221470ustar00rootroot00000000000000#!/usr/bin/python import sys import time from sense_hat import SenseHat from evdev import InputDevice, list_devices, ecodes print("Press Ctrl-C to quit") time.sleep(1) sense = SenseHat() sense.clear() # Blank the LED matrix found = False; devices = [InputDevice(fn) for fn in list_devices()] for dev in devices: if dev.name == 'Raspberry Pi Sense HAT Joystick': found = True; break if not(found): print('Raspberry Pi Sense HAT Joystick not found. Aborting ...') sys.exit() # 0, 0 = Top left # 7, 7 = Bottom right UP_PIXELS = [[3, 0], [4, 0]] DOWN_PIXELS = [[3, 7], [4, 7]] LEFT_PIXELS = [[0, 3], [0, 4]] RIGHT_PIXELS = [[7, 3], [7, 4]] CENTRE_PIXELS = [[3, 3], [4, 3], [3, 4], [4, 4]] def set_pixels(pixels, col): for p in pixels: sense.set_pixel(p[0], p[1], col[0], col[1], col[2]) def handle_code(code, colour): if code == ecodes.KEY_DOWN: set_pixels(DOWN_PIXELS, colour) elif code == ecodes.KEY_UP: set_pixels(UP_PIXELS, colour) elif code == ecodes.KEY_LEFT: set_pixels(LEFT_PIXELS, colour) elif code == ecodes.KEY_RIGHT: set_pixels(RIGHT_PIXELS, colour) elif code == ecodes.KEY_ENTER: set_pixels(CENTRE_PIXELS, colour) BLACK = [0, 0, 0] WHITE = [255, 255, 255] try: for event in dev.read_loop(): if event.type == ecodes.EV_KEY: if event.value == 1: # key down handle_code(event.code, WHITE) if event.value == 0: # key up handle_code(event.code, BLACK) except KeyboardInterrupt: sys.exit() python-sense-hat-2.2.0/examples/pygame_joystick.py000077500000000000000000000026401274116742000223230ustar00rootroot00000000000000#!/usr/bin/python from sense_hat import SenseHat import os import time import pygame # See http://www.pygame.org/docs from pygame.locals import * print("Press Escape to quit") time.sleep(1) pygame.init() pygame.display.set_mode((640, 480)) sense = SenseHat() sense.clear() # Blank the LED matrix # 0, 0 = Top left # 7, 7 = Bottom right UP_PIXELS = [[3, 0], [4, 0]] DOWN_PIXELS = [[3, 7], [4, 7]] LEFT_PIXELS = [[0, 3], [0, 4]] RIGHT_PIXELS = [[7, 3], [7, 4]] CENTRE_PIXELS = [[3, 3], [4, 3], [3, 4], [4, 4]] def set_pixels(pixels, col): for p in pixels: sense.set_pixel(p[0], p[1], col[0], col[1], col[2]) def handle_event(event, colour): if event.key == pygame.K_DOWN: set_pixels(DOWN_PIXELS, colour) elif event.key == pygame.K_UP: set_pixels(UP_PIXELS, colour) elif event.key == pygame.K_LEFT: set_pixels(LEFT_PIXELS, colour) elif event.key == pygame.K_RIGHT: set_pixels(RIGHT_PIXELS, colour) elif event.key == pygame.K_RETURN: set_pixels(CENTRE_PIXELS, colour) running = True BLACK = [0, 0, 0] WHITE = [255, 255, 255] while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False if event.type == KEYDOWN: if event.key == K_ESCAPE: running = False handle_event(event, WHITE) if event.type == KEYUP: handle_event(event, BLACK) python-sense-hat-2.2.0/examples/rainbow.py000077500000000000000000000031741274116742000205660ustar00rootroot00000000000000#!/usr/bin/python import time from sense_hat import SenseHat sense = SenseHat() pixels = [ [255, 0, 0], [255, 0, 0], [255, 87, 0], [255, 196, 0], [205, 255, 0], [95, 255, 0], [0, 255, 13], [0, 255, 122], [255, 0, 0], [255, 96, 0], [255, 205, 0], [196, 255, 0], [87, 255, 0], [0, 255, 22], [0, 255, 131], [0, 255, 240], [255, 105, 0], [255, 214, 0], [187, 255, 0], [78, 255, 0], [0, 255, 30], [0, 255, 140], [0, 255, 248], [0, 152, 255], [255, 223, 0], [178, 255, 0], [70, 255, 0], [0, 255, 40], [0, 255, 148], [0, 253, 255], [0, 144, 255], [0, 34, 255], [170, 255, 0], [61, 255, 0], [0, 255, 48], [0, 255, 157], [0, 243, 255], [0, 134, 255], [0, 26, 255], [83, 0, 255], [52, 255, 0], [0, 255, 57], [0, 255, 166], [0, 235, 255], [0, 126, 255], [0, 17, 255], [92, 0, 255], [201, 0, 255], [0, 255, 66], [0, 255, 174], [0, 226, 255], [0, 117, 255], [0, 8, 255], [100, 0, 255], [210, 0, 255], [255, 0, 192], [0, 255, 183], [0, 217, 255], [0, 109, 255], [0, 0, 255], [110, 0, 255], [218, 0, 255], [255, 0, 183], [255, 0, 74] ] msleep = lambda x: time.sleep(x / 1000.0) def next_colour(pix): r = pix[0] g = pix[1] b = pix[2] if (r == 255 and g < 255 and b == 0): g += 1 if (g == 255 and r > 0 and b == 0): r -= 1 if (g == 255 and b < 255 and r == 0): b += 1 if (b == 255 and g > 0 and r == 0): g -= 1 if (b == 255 and r < 255 and g == 0): r += 1 if (r == 255 and b > 0 and g == 0): b -= 1 pix[0] = r pix[1] = g pix[2] = b while True: for pix in pixels: next_colour(pix) sense.set_pixels(pixels) msleep(2) python-sense-hat-2.2.0/examples/rotation.py000077500000000000000000000012001274116742000207500ustar00rootroot00000000000000#!/usr/bin/python import sys import time from sense_hat import SenseHat X = (255, 0, 0) O = (255, 255, 255) question_mark = [ O, O, O, X, X, O, O, O, O, O, X, O, O, X, O, O, O, O, O, O, O, X, O, O, O, O, O, O, X, O, O, O, O, O, O, X, O, O, O, O, O, O, O, X, O, O, O, O, O, O, O, O, O, O, O, O, O, O, O, X, O, O, O, O ] sense = SenseHat() sense.set_pixels(question_mark) sense.set_pixel(0, 0, 255, 0, 0) sense.set_pixel(0, 7, 0, 255, 0) sense.set_pixel(7, 0, 0, 0, 255) sense.set_pixel(7, 7, 255, 0, 255) while True: for r in [0, 90, 180, 270]: sense.set_rotation(r) time.sleep(0.3) python-sense-hat-2.2.0/examples/space_invader.png000066400000000000000000000002771274116742000220620ustar00rootroot00000000000000PNG  IHDRKm) pHYs  tIME9'otEXtCommentCreated with GIMPW9IDATe0!s9{Ʌy IDZ%9brۤͣn]^=g;IENDB`python-sense-hat-2.2.0/examples/space_invader.py000077500000000000000000000001711274116742000217220ustar00rootroot00000000000000#!/usr/bin/python from sense_hat import SenseHat sense = SenseHat() sense.clear() sense.load_image("space_invader.png") python-sense-hat-2.2.0/examples/text_scroll.py000077500000000000000000000002551274116742000214640ustar00rootroot00000000000000#!/usr/bin/python from sense_hat import SenseHat sense = SenseHat() sense.set_rotation(180) red = (255, 0, 0) sense.show_message("One small step for Pi!", text_colour=red) python-sense-hat-2.2.0/mkdocs.yml000066400000000000000000000006731274116742000167360ustar00rootroot00000000000000site_name: Sense HAT theme: readthedocs site_url: https://pythonhosted.org/sense-hat/ repo_url: https://github.com/RPi-Distro/python-sense-hat site_description: Python module to control the Raspberry Pi Sense HAT used in the Astro Pi mission site_author: David Honess site_dir: pythonhosted google_analytics: ['UA-46270871-5', 'pythonhosted.org/sense-hat'] pages: - 'Home': 'index.md' - 'API Reference': 'api.md' - 'Changelog': 'changelog.md' python-sense-hat-2.2.0/sense_hat/000077500000000000000000000000001274116742000166765ustar00rootroot00000000000000python-sense-hat-2.2.0/sense_hat/__init__.py000066400000000000000000000005351274116742000210120ustar00rootroot00000000000000from __future__ import absolute_import from .sense_hat import SenseHat, SenseHat as AstroPi from .stick import ( SenseStick, InputEvent, DIRECTION_UP, DIRECTION_DOWN, DIRECTION_LEFT, DIRECTION_RIGHT, DIRECTION_MIDDLE, ACTION_PRESSED, ACTION_RELEASED, ACTION_HELD, ) __version__ = '2.2.0' python-sense-hat-2.2.0/sense_hat/sense_hat.py000066400000000000000000000625751274116742000212400ustar00rootroot00000000000000#!/usr/bin/python import struct import os import sys import math import time import numpy as np import shutil import glob import RTIMU # custom version import pwd import array import fcntl from PIL import Image # pillow from copy import deepcopy from .stick import SenseStick class SenseHat(object): SENSE_HAT_FB_NAME = 'RPi-Sense FB' SENSE_HAT_FB_FBIOGET_GAMMA = 61696 SENSE_HAT_FB_FBIOSET_GAMMA = 61697 SENSE_HAT_FB_FBIORESET_GAMMA = 61698 SENSE_HAT_FB_GAMMA_DEFAULT = 0 SENSE_HAT_FB_GAMMA_LOW = 1 SENSE_HAT_FB_GAMMA_USER = 2 SETTINGS_HOME_PATH = '.config/sense_hat' def __init__( self, imu_settings_file='RTIMULib', text_assets='sense_hat_text' ): self._fb_device = self._get_fb_device() if self._fb_device is None: raise OSError('Cannot detect %s device' % self.SENSE_HAT_FB_NAME) if not glob.glob('/dev/i2c*'): raise OSError('Cannot access I2C. Please ensure I2C is enabled in raspi-config') # 0 is With B+ HDMI port facing downwards pix_map0 = np.array([ [0, 1, 2, 3, 4, 5, 6, 7], [8, 9, 10, 11, 12, 13, 14, 15], [16, 17, 18, 19, 20, 21, 22, 23], [24, 25, 26, 27, 28, 29, 30, 31], [32, 33, 34, 35, 36, 37, 38, 39], [40, 41, 42, 43, 44, 45, 46, 47], [48, 49, 50, 51, 52, 53, 54, 55], [56, 57, 58, 59, 60, 61, 62, 63] ], int) pix_map90 = np.rot90(pix_map0) pix_map180 = np.rot90(pix_map90) pix_map270 = np.rot90(pix_map180) self._pix_map = { 0: pix_map0, 90: pix_map90, 180: pix_map180, 270: pix_map270 } self._rotation = 0 # Load text assets dir_path = os.path.dirname(__file__) self._load_text_assets( os.path.join(dir_path, '%s.png' % text_assets), os.path.join(dir_path, '%s.txt' % text_assets) ) # Load IMU settings and calibration data self._imu_settings = self._get_settings_file(imu_settings_file) self._imu = RTIMU.RTIMU(self._imu_settings) self._imu_init = False # Will be initialised as and when needed self._pressure = RTIMU.RTPressure(self._imu_settings) self._pressure_init = False # Will be initialised as and when needed self._humidity = RTIMU.RTHumidity(self._imu_settings) self._humidity_init = False # Will be initialised as and when needed self._last_orientation = {'pitch': 0, 'roll': 0, 'yaw': 0} raw = {'x': 0, 'y': 0, 'z': 0} self._last_compass_raw = deepcopy(raw) self._last_gyro_raw = deepcopy(raw) self._last_accel_raw = deepcopy(raw) self._compass_enabled = False self._gyro_enabled = False self._accel_enabled = False self._stick = SenseStick() #### # Text assets #### # Text asset files are rotated right through 90 degrees to allow blocks of # 40 contiguous pixels to represent one 5 x 8 character. These are stored # in a 8 x 640 pixel png image with characters arranged adjacently # Consequently we must rotate the pixel map left through 90 degrees to # compensate when drawing text def _load_text_assets(self, text_image_file, text_file): """ Internal. Builds a character indexed dictionary of pixels used by the show_message function below """ text_pixels = self.load_image(text_image_file, False) with open(text_file, 'r') as f: loaded_text = f.read() self._text_dict = {} for index, s in enumerate(loaded_text): start = index * 40 end = start + 40 char = text_pixels[start:end] self._text_dict[s] = char def _trim_whitespace(self, char): # For loading text assets only """ Internal. Trims white space pixels from the front and back of loaded text characters """ psum = lambda x: sum(sum(x, [])) if psum(char) > 0: is_empty = True while is_empty: # From front row = char[0:8] is_empty = psum(row) == 0 if is_empty: del char[0:8] is_empty = True while is_empty: # From back row = char[-8:] is_empty = psum(row) == 0 if is_empty: del char[-8:] return char def _get_settings_file(self, imu_settings_file): """ Internal. Logic to check for a system wide RTIMU ini file. This is copied to the home folder if one is not already found there. """ ini_file = '%s.ini' % imu_settings_file home_dir = pwd.getpwuid(os.getuid())[5] home_path = os.path.join(home_dir, self.SETTINGS_HOME_PATH) if not os.path.exists(home_path): os.makedirs(home_path) home_file = os.path.join(home_path, ini_file) home_exists = os.path.isfile(home_file) system_file = os.path.join('/etc', ini_file) system_exists = os.path.isfile(system_file) if system_exists and not home_exists: shutil.copyfile(system_file, home_file) return RTIMU.Settings(os.path.join(home_path, imu_settings_file)) # RTIMU will add .ini internally def _get_fb_device(self): """ Internal. Finds the correct frame buffer device for the sense HAT and returns its /dev name. """ device = None for fb in glob.glob('/sys/class/graphics/fb*'): name_file = os.path.join(fb, 'name') if os.path.isfile(name_file): with open(name_file, 'r') as f: name = f.read() if name.strip() == self.SENSE_HAT_FB_NAME: fb_device = fb.replace(os.path.dirname(fb), '/dev') if os.path.exists(fb_device): device = fb_device break return device #### # Joystick #### @property def stick(self): return self._stick #### # LED Matrix #### @property def rotation(self): return self._rotation @rotation.setter def rotation(self, r): self.set_rotation(r, True) def set_rotation(self, r=0, redraw=True): """ Sets the LED matrix rotation for viewing, adjust if the Pi is upside down or sideways. 0 is with the Pi HDMI port facing downwards """ if r in self._pix_map.keys(): if redraw: pixel_list = self.get_pixels() self._rotation = r if redraw: self.set_pixels(pixel_list) else: raise ValueError('Rotation must be 0, 90, 180 or 270 degrees') def _pack_bin(self, pix): """ Internal. Encodes python list [R,G,B] into 16 bit RGB565 """ r = (pix[0] >> 3) & 0x1F g = (pix[1] >> 2) & 0x3F b = (pix[2] >> 3) & 0x1F bits16 = (r << 11) + (g << 5) + b return struct.pack('H', bits16) def _unpack_bin(self, packed): """ Internal. Decodes 16 bit RGB565 into python list [R,G,B] """ output = struct.unpack('H', packed) bits16 = output[0] r = (bits16 & 0xF800) >> 11 g = (bits16 & 0x7E0) >> 5 b = (bits16 & 0x1F) return [int(r << 3), int(g << 2), int(b << 3)] def flip_h(self, redraw=True): """ Flip LED matrix horizontal """ pixel_list = self.get_pixels() flipped = [] for i in range(8): offset = i * 8 flipped.extend(reversed(pixel_list[offset:offset + 8])) if redraw: self.set_pixels(flipped) return flipped def flip_v(self, redraw=True): """ Flip LED matrix vertical """ pixel_list = self.get_pixels() flipped = [] for i in reversed(range(8)): offset = i * 8 flipped.extend(pixel_list[offset:offset + 8]) if redraw: self.set_pixels(flipped) return flipped def set_pixels(self, pixel_list): """ Accepts a list containing 64 smaller lists of [R,G,B] pixels and updates the LED matrix. R,G,B elements must intergers between 0 and 255 """ if len(pixel_list) != 64: raise ValueError('Pixel lists must have 64 elements') for index, pix in enumerate(pixel_list): if len(pix) != 3: raise ValueError('Pixel at index %d is invalid. Pixels must contain 3 elements: Red, Green and Blue' % index) for element in pix: if element > 255 or element < 0: raise ValueError('Pixel at index %d is invalid. Pixel elements must be between 0 and 255' % index) with open(self._fb_device, 'wb') as f: map = self._pix_map[self._rotation] for index, pix in enumerate(pixel_list): # Two bytes per pixel in fb memory, 16 bit RGB565 f.seek(map[index // 8][index % 8] * 2) # row, column f.write(self._pack_bin(pix)) def get_pixels(self): """ Returns a list containing 64 smaller lists of [R,G,B] pixels representing what is currently displayed on the LED matrix """ pixel_list = [] with open(self._fb_device, 'rb') as f: map = self._pix_map[self._rotation] for row in range(8): for col in range(8): # Two bytes per pixel in fb memory, 16 bit RGB565 f.seek(map[row][col] * 2) # row, column pixel_list.append(self._unpack_bin(f.read(2))) return pixel_list def set_pixel(self, x, y, *args): """ Updates the single [R,G,B] pixel specified by x and y on the LED matrix Top left = 0,0 Bottom right = 7,7 e.g. ap.set_pixel(x, y, r, g, b) or pixel = (r, g, b) ap.set_pixel(x, y, pixel) """ pixel_error = 'Pixel arguments must be given as (r, g, b) or r, g, b' if len(args) == 1: pixel = args[0] if len(pixel) != 3: raise ValueError(pixel_error) elif len(args) == 3: pixel = args else: raise ValueError(pixel_error) if x > 7 or x < 0: raise ValueError('X position must be between 0 and 7') if y > 7 or y < 0: raise ValueError('Y position must be between 0 and 7') for element in pixel: if element > 255 or element < 0: raise ValueError('Pixel elements must be between 0 and 255') with open(self._fb_device, 'wb') as f: map = self._pix_map[self._rotation] # Two bytes per pixel in fb memory, 16 bit RGB565 f.seek(map[y][x] * 2) # row, column f.write(self._pack_bin(pixel)) def get_pixel(self, x, y): """ Returns a list of [R,G,B] representing the pixel specified by x and y on the LED matrix. Top left = 0,0 Bottom right = 7,7 """ if x > 7 or x < 0: raise ValueError('X position must be between 0 and 7') if y > 7 or y < 0: raise ValueError('Y position must be between 0 and 7') pix = None with open(self._fb_device, 'rb') as f: map = self._pix_map[self._rotation] # Two bytes per pixel in fb memory, 16 bit RGB565 f.seek(map[y][x] * 2) # row, column pix = self._unpack_bin(f.read(2)) return pix def load_image(self, file_path, redraw=True): """ Accepts a path to an 8 x 8 image file and updates the LED matrix with the image """ if not os.path.exists(file_path): raise IOError('%s not found' % file_path) img = Image.open(file_path).convert('RGB') pixel_list = list(map(list, img.getdata())) if redraw: self.set_pixels(pixel_list) return pixel_list def clear(self, *args): """ Clears the LED matrix with a single colour, default is black / off e.g. ap.clear() or ap.clear(r, g, b) or colour = (r, g, b) ap.clear(colour) """ black = (0, 0, 0) # default if len(args) == 0: colour = black elif len(args) == 1: colour = args[0] elif len(args) == 3: colour = args else: raise ValueError('Pixel arguments must be given as (r, g, b) or r, g, b') self.set_pixels([colour] * 64) def _get_char_pixels(self, s): """ Internal. Safeguards the character indexed dictionary for the show_message function below """ if len(s) == 1 and s in self._text_dict.keys(): return list(self._text_dict[s]) else: return list(self._text_dict['?']) def show_message( self, text_string, scroll_speed=.1, text_colour=[255, 255, 255], back_colour=[0, 0, 0] ): """ Scrolls a string of text across the LED matrix using the specified speed and colours """ # We must rotate the pixel map left through 90 degrees when drawing # text, see _load_text_assets previous_rotation = self._rotation self._rotation -= 90 if self._rotation < 0: self._rotation = 270 dummy_colour = [None, None, None] string_padding = [dummy_colour] * 64 letter_padding = [dummy_colour] * 8 # Build pixels from dictionary scroll_pixels = [] scroll_pixels.extend(string_padding) for s in text_string: scroll_pixels.extend(self._trim_whitespace(self._get_char_pixels(s))) scroll_pixels.extend(letter_padding) scroll_pixels.extend(string_padding) # Recolour pixels as necessary coloured_pixels = [ text_colour if pixel == [255, 255, 255] else back_colour for pixel in scroll_pixels ] # Shift right by 8 pixels per frame to scroll scroll_length = len(coloured_pixels) // 8 for i in range(scroll_length - 8): start = i * 8 end = start + 64 self.set_pixels(coloured_pixels[start:end]) time.sleep(scroll_speed) self._rotation = previous_rotation def show_letter( self, s, text_colour=[255, 255, 255], back_colour=[0, 0, 0] ): """ Displays a single text character on the LED matrix using the specified colours """ if len(s) > 1: raise ValueError('Only one character may be passed into this method') # We must rotate the pixel map left through 90 degrees when drawing # text, see _load_text_assets previous_rotation = self._rotation self._rotation -= 90 if self._rotation < 0: self._rotation = 270 dummy_colour = [None, None, None] pixel_list = [dummy_colour] * 8 pixel_list.extend(self._get_char_pixels(s)) pixel_list.extend([dummy_colour] * 16) coloured_pixels = [ text_colour if pixel == [255, 255, 255] else back_colour for pixel in pixel_list ] self.set_pixels(coloured_pixels) self._rotation = previous_rotation @property def gamma(self): buffer = array.array('B', [0]*32) with open(self._fb_device) as f: fcntl.ioctl(f, self.SENSE_HAT_FB_FBIOGET_GAMMA, buffer) return list(buffer) @gamma.setter def gamma(self, buffer): if len(buffer) is not 32: raise ValueError('Gamma array must be of length 32') if not all(b <= 31 for b in buffer): raise ValueError('Gamma values must be bewteen 0 and 31') if not isinstance(buffer, array.array): buffer = array.array('B', buffer) with open(self._fb_device) as f: fcntl.ioctl(f, self.SENSE_HAT_FB_FBIOSET_GAMMA, buffer) def gamma_reset(self): """ Resets the LED matrix gamma correction to default """ with open(self._fb_device) as f: fcntl.ioctl(f, self.SENSE_HAT_FB_FBIORESET_GAMMA, self.SENSE_HAT_FB_GAMMA_DEFAULT) @property def low_light(self): return self.gamma == [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 10, 10] @low_light.setter def low_light(self, value): with open(self._fb_device) as f: cmd = self.SENSE_HAT_FB_GAMMA_LOW if value else self.SENSE_HAT_FB_GAMMA_DEFAULT fcntl.ioctl(f, self.SENSE_HAT_FB_FBIORESET_GAMMA, cmd) #### # Environmental sensors #### def _init_humidity(self): """ Internal. Initialises the humidity sensor via RTIMU """ if not self._humidity_init: self._humidity_init = self._humidity.humidityInit() if not self._humidity_init: raise OSError('Humidity Init Failed') def _init_pressure(self): """ Internal. Initialises the pressure sensor via RTIMU """ if not self._pressure_init: self._pressure_init = self._pressure.pressureInit() if not self._pressure_init: raise OSError('Pressure Init Failed') def get_humidity(self): """ Returns the percentage of relative humidity """ self._init_humidity() # Ensure humidity sensor is initialised humidity = 0 data = self._humidity.humidityRead() if (data[0]): # Humidity valid humidity = data[1] return humidity @property def humidity(self): return self.get_humidity() def get_temperature_from_humidity(self): """ Returns the temperature in Celsius from the humidity sensor """ self._init_humidity() # Ensure humidity sensor is initialised temp = 0 data = self._humidity.humidityRead() if (data[2]): # Temp valid temp = data[3] return temp def get_temperature_from_pressure(self): """ Returns the temperature in Celsius from the pressure sensor """ self._init_pressure() # Ensure pressure sensor is initialised temp = 0 data = self._pressure.pressureRead() if (data[2]): # Temp valid temp = data[3] return temp def get_temperature(self): """ Returns the temperature in Celsius """ return self.get_temperature_from_humidity() @property def temp(self): return self.get_temperature_from_humidity() @property def temperature(self): return self.get_temperature_from_humidity() def get_pressure(self): """ Returns the pressure in Millibars """ self._init_pressure() # Ensure pressure sensor is initialised pressure = 0 data = self._pressure.pressureRead() if (data[0]): # Pressure valid pressure = data[1] return pressure @property def pressure(self): return self.get_pressure() #### # IMU Sensor #### def _init_imu(self): """ Internal. Initialises the IMU sensor via RTIMU """ if not self._imu_init: self._imu_init = self._imu.IMUInit() if self._imu_init: self._imu_poll_interval = self._imu.IMUGetPollInterval() * 0.001 # Enable everything on IMU self.set_imu_config(True, True, True) else: raise OSError('IMU Init Failed') def set_imu_config(self, compass_enabled, gyro_enabled, accel_enabled): """ Enables and disables the gyroscope, accelerometer and/or magnetometer input to the orientation functions """ # If the consuming code always calls this just before reading the IMU # the IMU consistently fails to read. So prevent unnecessary calls to # IMU config functions using state variables self._init_imu() # Ensure imu is initialised if (not isinstance(compass_enabled, bool) or not isinstance(gyro_enabled, bool) or not isinstance(accel_enabled, bool)): raise TypeError('All set_imu_config parameters must be of boolean type') if self._compass_enabled != compass_enabled: self._compass_enabled = compass_enabled self._imu.setCompassEnable(self._compass_enabled) if self._gyro_enabled != gyro_enabled: self._gyro_enabled = gyro_enabled self._imu.setGyroEnable(self._gyro_enabled) if self._accel_enabled != accel_enabled: self._accel_enabled = accel_enabled self._imu.setAccelEnable(self._accel_enabled) def _read_imu(self): """ Internal. Tries to read the IMU sensor three times before giving up """ self._init_imu() # Ensure imu is initialised attempts = 0 success = False while not success and attempts < 3: success = self._imu.IMURead() attempts += 1 time.sleep(self._imu_poll_interval) return success def _get_raw_data(self, is_valid_key, data_key): """ Internal. Returns the specified raw data from the IMU when valid """ result = None if self._read_imu(): data = self._imu.getIMUData() if data[is_valid_key]: raw = data[data_key] result = { 'x': raw[0], 'y': raw[1], 'z': raw[2] } return result def get_orientation_radians(self): """ Returns a dictionary object to represent the current orientation in radians using the aircraft principal axes of pitch, roll and yaw """ raw = self._get_raw_data('fusionPoseValid', 'fusionPose') if raw is not None: raw['roll'] = raw.pop('x') raw['pitch'] = raw.pop('y') raw['yaw'] = raw.pop('z') self._last_orientation = raw return deepcopy(self._last_orientation) @property def orientation_radians(self): return self.get_orientation_radians() def get_orientation_degrees(self): """ Returns a dictionary object to represent the current orientation in degrees, 0 to 360, using the aircraft principal axes of pitch, roll and yaw """ orientation = self.get_orientation_radians() for key, val in orientation.items(): deg = math.degrees(val) # Result is -180 to +180 orientation[key] = deg + 360 if deg < 0 else deg return orientation def get_orientation(self): return self.get_orientation_degrees() @property def orientation(self): return self.get_orientation_degrees() def get_compass(self): """ Gets the direction of North from the magnetometer in degrees """ self.set_imu_config(True, False, False) orientation = self.get_orientation_degrees() if type(orientation) is dict and 'yaw' in orientation.keys(): return orientation['yaw'] else: return None @property def compass(self): return self.get_compass() def get_compass_raw(self): """ Magnetometer x y z raw data in uT (micro teslas) """ raw = self._get_raw_data('compassValid', 'compass') if raw is not None: self._last_compass_raw = raw return deepcopy(self._last_compass_raw) @property def compass_raw(self): return self.get_compass_raw() def get_gyroscope(self): """ Gets the orientation in degrees from the gyroscope only """ self.set_imu_config(False, True, False) return self.get_orientation_degrees() @property def gyro(self): return self.get_gyroscope() @property def gyroscope(self): return self.get_gyroscope() def get_gyroscope_raw(self): """ Gyroscope x y z raw data in radians per second """ raw = self._get_raw_data('gyroValid', 'gyro') if raw is not None: self._last_gyro_raw = raw return deepcopy(self._last_gyro_raw) @property def gyro_raw(self): return self.get_gyroscope_raw() @property def gyroscope_raw(self): return self.get_gyroscope_raw() def get_accelerometer(self): """ Gets the orientation in degrees from the accelerometer only """ self.set_imu_config(False, False, True) return self.get_orientation_degrees() @property def accel(self): return self.get_accelerometer() @property def accelerometer(self): return self.get_accelerometer() def get_accelerometer_raw(self): """ Accelerometer x y z raw data in Gs """ raw = self._get_raw_data('accelValid', 'accel') if raw is not None: self._last_accel_raw = raw return deepcopy(self._last_accel_raw) @property def accel_raw(self): return self.get_accelerometer_raw() @property def accelerometer_raw(self): return self.get_accelerometer_raw() python-sense-hat-2.2.0/sense_hat/sense_hat_text.png000066400000000000000000000016141274116742000224230ustar00rootroot00000000000000PNG  IHDR.@ pHYs  tIME ) C+tEXtCommentCreated with GIMPWIDAThZr 5顙-OH;SԚ`O/Iko[k[zFD1χsg hc֚| KCGcs*ƍJ``oXh 8\4wc 3 2"t_ 1`ψe[,1_ Iә v8k9X e8_p5B'ie͠J<cf`qt|?rtIٰ5\FVk2¨KWee\Ew]AjdT$Cmeg,:,@;ImL9NĎ֢}/hxhhUhɡA#evnێ=_B39r(:4F[YMAy =oY F#amnōXbÚj`/X$ cĎ千vq ,]hT}zWOHLJ\mZaq룴1&4? |f+#m-k+`ct1 [ą ;=JqX;WNn-|veM ٯM#<0123456789.=)(ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz?,;:|@%[&_']\~ python-sense-hat-2.2.0/sense_hat/stick.py000066400000000000000000000245031274116742000203710ustar00rootroot00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) native_str = str str = type('') import io import os import glob import errno import struct import select import inspect from functools import wraps from collections import namedtuple from threading import Thread, Event DIRECTION_UP = 'up' DIRECTION_DOWN = 'down' DIRECTION_LEFT = 'left' DIRECTION_RIGHT = 'right' DIRECTION_MIDDLE = 'middle' ACTION_PRESSED = 'pressed' ACTION_RELEASED = 'released' ACTION_HELD = 'held' InputEvent = namedtuple('InputEvent', ('timestamp', 'direction', 'action')) class SenseStick(object): """ Represents the joystick on the Sense HAT. """ SENSE_HAT_EVDEV_NAME = 'Raspberry Pi Sense HAT Joystick' EVENT_FORMAT = native_str('llHHI') EVENT_SIZE = struct.calcsize(EVENT_FORMAT) EV_KEY = 0x01 STATE_RELEASE = 0 STATE_PRESS = 1 STATE_HOLD = 2 KEY_UP = 103 KEY_LEFT = 105 KEY_RIGHT = 106 KEY_DOWN = 108 KEY_ENTER = 28 def __init__(self): self._stick_file = io.open(self._stick_device(), 'rb', buffering=0) self._callbacks = {} self._callback_thread = None self._callback_event = Event() def close(self): if self._stick_file: self._callbacks.clear() self._start_stop_thread() self._stick_file.close() self._stick_file = None def __enter__(self): return self def __exit__(self, exc_type, exc_value, exc_tb): self.close() def _stick_device(self): """ Discovers the filename of the evdev device that represents the Sense HAT's joystick. """ for evdev in glob.glob('/sys/class/input/event*'): try: with io.open(os.path.join(evdev, 'device', 'name'), 'r') as f: if f.read().strip() == self.SENSE_HAT_EVDEV_NAME: return os.path.join('/dev', 'input', os.path.basename(evdev)) except IOError as e: if e.errno != errno.ENOENT: raise raise RuntimeError('unable to locate SenseHAT joystick device') def _read(self): """ Reads a single event from the joystick, blocking until one is available. Returns `None` if a non-key event was read, or an `InputEvent` tuple describing the event otherwise. """ event = self._stick_file.read(self.EVENT_SIZE) (tv_sec, tv_usec, type, code, value) = struct.unpack(self.EVENT_FORMAT, event) if type == self.EV_KEY: return InputEvent( timestamp=tv_sec + (tv_usec / 1000000), direction={ self.KEY_UP: DIRECTION_UP, self.KEY_DOWN: DIRECTION_DOWN, self.KEY_LEFT: DIRECTION_LEFT, self.KEY_RIGHT: DIRECTION_RIGHT, self.KEY_ENTER: DIRECTION_MIDDLE, }[code], action={ self.STATE_PRESS: ACTION_PRESSED, self.STATE_RELEASE: ACTION_RELEASED, self.STATE_HOLD: ACTION_HELD, }[value]) else: return None def _wait(self, timeout=None): """ Waits *timeout* seconds until an event is available from the joystick. Returns `True` if an event became available, and `False` if the timeout expired. """ r, w, x = select.select([self._stick_file], [], [], timeout) return bool(r) def _wrap_callback(self, fn): # Shamelessley nicked (with some variation) from GPIO Zero :) @wraps(fn) def wrapper(event): return fn() if fn is None: return None elif not callable(fn): raise ValueError('value must be None or a callable') elif inspect.isbuiltin(fn): # We can't introspect the prototype of builtins. In this case we # assume that the builtin has no (mandatory) parameters; this is # the most reasonable assumption on the basis that pre-existing # builtins have no knowledge of InputEvent, and the sole parameter # we would pass is an InputEvent return wrapper else: # Try binding ourselves to the argspec of the provided callable. # If this works, assume the function is capable of accepting no # parameters and that we have to wrap it to ignore the event # parameter try: inspect.getcallargs(fn) return wrapper except TypeError: try: # If the above fails, try binding with a single tuple # parameter. If this works, return the callback as is inspect.getcallargs(fn, ()) return fn except TypeError: raise ValueError( 'value must be a callable which accepts up to one ' 'mandatory parameter') def _start_stop_thread(self): if self._callbacks and not self._callback_thread: self._callback_event.clear() self._callback_thread = Thread(target=self._callback_run) self._callback_thread.daemon = True self._callback_thread.start() elif not self._callbacks and self._callback_thread: self._callback_event.set() self._callback_thread.join() self._callback_thread = None def _callback_run(self): while not self._callback_event.wait(0): event = self._read() if event: callback = self._callbacks.get(event.direction) if callback: callback(event) callback = self._callbacks.get('*') if callback: callback(event) def wait_for_event(self, emptybuffer=False): """ Waits until a joystick event becomes available. Returns the event, as an `InputEvent` tuple. If *emptybuffer* is `True` (it defaults to `False`), any pending events will be thrown away first. This is most useful if you are only interested in "pressed" events. """ if emptybuffer: while self._wait(0): self._read() while self._wait(): event = self._read() if event: return event def get_events(self): """ Returns a list of all joystick events that have occurred since the last call to `get_events`. The list contains events in the order that they occurred. If no events have occurred in the intervening time, the result is an empty list. """ result = [] while self._wait(0): event = self._read() if event: result.append(event) return result @property def direction_up(self): """ The function to be called when the joystick is pushed up. The function can either take a parameter which will be the `InputEvent` tuple that has occurred, or the function can take no parameters at all. """ return self._callbacks.get(DIRECTION_UP) @direction_up.setter def direction_up(self, value): self._callbacks[DIRECTION_UP] = self._wrap_callback(value) self._start_stop_thread() @property def direction_down(self): """ The function to be called when the joystick is pushed down. The function can either take a parameter which will be the `InputEvent` tuple that has occurred, or the function can take no parameters at all. Assign `None` to prevent this event from being fired. """ return self._callbacks.get(DIRECTION_DOWN) @direction_down.setter def direction_down(self, value): self._callbacks[DIRECTION_DOWN] = self._wrap_callback(value) self._start_stop_thread() @property def direction_left(self): """ The function to be called when the joystick is pushed left. The function can either take a parameter which will be the `InputEvent` tuple that has occurred, or the function can take no parameters at all. Assign `None` to prevent this event from being fired. """ return self._callbacks.get(DIRECTION_LEFT) @direction_left.setter def direction_left(self, value): self._callbacks[DIRECTION_LEFT] = self._wrap_callback(value) self._start_stop_thread() @property def direction_right(self): """ The function to be called when the joystick is pushed right. The function can either take a parameter which will be the `InputEvent` tuple that has occurred, or the function can take no parameters at all. Assign `None` to prevent this event from being fired. """ return self._callbacks.get(DIRECTION_RIGHT) @direction_right.setter def direction_right(self, value): self._callbacks[DIRECTION_RIGHT] = self._wrap_callback(value) self._start_stop_thread() @property def direction_middle(self): """ The function to be called when the joystick middle click is pressed. The function can either take a parameter which will be the `InputEvent` tuple that has occurred, or the function can take no parameters at all. Assign `None` to prevent this event from being fired. """ return self._callbacks.get(DIRECTION_MIDDLE) @direction_middle.setter def direction_middle(self, value): self._callbacks[DIRECTION_MIDDLE] = self._wrap_callback(value) self._start_stop_thread() @property def direction_any(self): """ The function to be called when the joystick is used. The function can either take a parameter which will be the `InputEvent` tuple that has occurred, or the function can take no parameters at all. This event will always be called *after* events associated with a specific action. Assign `None` to prevent this event from being fired. """ return self._callbacks.get('*') @direction_any.setter def direction_any(self, value): self._callbacks['*'] = self._wrap_callback(value) self._start_stop_thread() python-sense-hat-2.2.0/setup.py000066400000000000000000000023571274116742000164460ustar00rootroot00000000000000import os from setuptools import setup, find_packages def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() setup( name="sense-hat", version="2.2.0", author="Dave Honess", author_email="dave@raspberrypi.org", description="Python module to control the Raspberry Pi Sense HAT used in the Astro Pi mission", long_description=read('README.rst'), license="BSD", keywords=[ "sense hat", "raspberrypi", "astro pi", ], url="https://github.com/RPi-Distro/python-sense-hat", packages=find_packages(), package_data={ "txt": ['sense_hat_text.txt'], "png": ['sense_hat_text.png'] }, include_package_data=True, install_requires=[ "pillow", "numpy" ], classifiers=[ "Development Status :: 5 - Production/Stable", "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", "Topic :: Scientific/Engineering :: Astronomy", "Topic :: Scientific/Engineering :: Atmospheric Science", "Topic :: Education", "Intended Audience :: Education", "Intended Audience :: Science/Research", "License :: OSI Approved :: BSD License", ], )