├── .github └── workflows │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── daemon ├── etc │ ├── plasma │ │ ├── README.txt │ │ ├── bluenwhite.png │ │ ├── default.png │ │ ├── plasma-rainbow.png │ │ ├── plasma.conf │ │ ├── rainbow-cycle.png │ │ ├── rednwhite.png │ │ └── trippy.png │ └── systemd │ │ └── system │ │ └── plasma.service ├── install.sh └── usr │ └── bin │ ├── plasma │ └── plasmactl ├── documentation ├── LEGACY.md └── REFERENCE.md ├── examples ├── example-config.yml ├── larson_hue.py ├── led-strip-mini-hat-apa102.yml ├── led-strip-mini-hat-ws2812.yml ├── rainbow.py ├── setup.cfg └── solid-colour.py ├── fx ├── config.yml ├── plasmafx │ ├── CHANGELOG.txt │ ├── LICENSE.txt │ ├── MANIFEST.in │ ├── README.md │ ├── plasmafx │ │ ├── __init__.py │ │ ├── core.py │ │ └── plugins.py │ ├── setup.cfg │ ├── setup.py │ └── tox.ini ├── plasmafx_plugin_cycle │ ├── CHANGELOG.txt │ ├── LICENSE.txt │ ├── MANIFEST.in │ ├── README.md │ ├── plasmafx_plugin_cycle │ │ └── __init__.py │ ├── setup.cfg │ ├── setup.py │ └── tox.ini └── test.py ├── install.sh ├── library ├── .coveragerc ├── CHANGELOG.txt ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── plasma │ ├── __init__.py │ ├── apa102.py │ ├── core.py │ ├── gpio.py │ ├── legacy.py │ ├── matrix.py │ ├── serial.py │ ├── usb.py │ └── ws281x.py ├── setup.cfg ├── setup.py ├── tests │ ├── conftest.py │ ├── test_apa102.py │ ├── test_auto.py │ ├── test_fx.py │ ├── test_get_device.py │ ├── test_legacy_gpio.py │ ├── test_matrix.py │ ├── test_serial.py │ ├── test_setup.py │ └── test_ws281x.py └── tox.ini ├── packaging ├── CHANGELOG ├── debian │ ├── README │ ├── changelog │ ├── clean │ ├── compat │ ├── control │ ├── copyright │ ├── docs │ ├── rules │ └── source │ │ ├── format │ │ └── options ├── makeall.sh ├── makedeb.sh ├── makedoc.sh └── makelog.sh ├── sphinx ├── _static │ └── custom.css ├── _templates │ ├── breadcrumbs.html │ └── layout.html ├── conf.py ├── favicon.png ├── index.rst └── shop-logo.png └── terminal.jpg /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Python Tests 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | python: [3.7, 3.9] 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Set up Python ${{ matrix.python }} 19 | uses: actions/setup-python@v2 20 | with: 21 | python-version: ${{ matrix.python }} 22 | - name: Install Dependencies 23 | run: | 24 | python -m pip install --upgrade setuptools tox 25 | - name: Run Tests 26 | working-directory: library 27 | run: | 28 | tox -e py 29 | - name: Coverage 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | working-directory: library 33 | run: | 34 | python -m pip install coveralls 35 | coveralls --service=github 36 | if: ${{ matrix.python == '3.9' }} 37 | 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | _build/ 3 | *.o 4 | *.so 5 | *.a 6 | *.py[cod] 7 | *.egg-info 8 | dist/ 9 | __pycache__ 10 | .DS_Store 11 | *.deb 12 | *.dsc 13 | *.build 14 | *.changes 15 | *.orig.* 16 | packaging/*tar.xz 17 | library/debian/ 18 | .coverage 19 | .tox/ 20 | .vscode/ 21 | .pytest_cache/ 22 | venv/ 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Pimoroni Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Plasma: LED Sequencing 2 | 3 | Plasma is an LED/Light sequencing suite written to harmonise a variety of LED strand/board types and interfaces into a standard API for write-once-run-anywhere lighting code. 4 | 5 | Plasma also includes plasmad, a system daemon for sequencing light strips using PNG images to provide animation frames. 6 | 7 | [![Build Status](https://shields.io/github/workflow/status/pimoroni/plasma/Python%20Tests.svg)](https://github.com/pimoroni/plasma/actions/workflows/test.yml) 8 | [![Coverage Status](https://coveralls.io/repos/github/pimoroni/plasma/badge.svg?branch=master)](https://coveralls.io/github/pimoroni/plasma?branch=master) 9 | [![PyPi Package](https://img.shields.io/pypi/v/plasmalights.svg)](https://pypi.python.org/pypi/plasmalights) 10 | [![Python Versions](https://img.shields.io/pypi/pyversions/plasmalights.svg)](https://pypi.python.org/pypi/plasmalights) 11 | 12 | ## Compatible Products 13 | 14 | Plasma was originally written to provide an easy way to sequence lights and swap out patterns for the Pimoroni Plasma kit. 15 | 16 | - https://shop.pimoroni.com/products/picade-plasma-kit-illuminated-arcade-buttons 17 | - https://shop.pimoroni.com/products/player-x-usb-games-controller-pcb 18 | - https://shop.pimoroni.com/products/blinkt 19 | - https://shop.pimoroni.com/products/unicorn-hat 20 | - https://shop.pimoroni.com/products/unicorn-phat 21 | 22 | ## Installing 23 | 24 | ### Full install (recommended): 25 | 26 | We've created an easy installation script that will install all pre-requisites and get your Plasma Arcade Button Lights 27 | up and running with minimal efforts. To run it, fire up Terminal which you'll find in Menu -> Accessories -> Terminal 28 | on your Raspberry Pi desktop, as illustrated below: 29 | 30 | ![Finding the terminal](http://get.pimoroni.com/resources/github-repo-terminal.png) 31 | 32 | In the new terminal window type the command exactly as it appears below (check for typos) and follow the on-screen instructions: 33 | 34 | ```bash 35 | curl https://get.pimoroni.com/plasma | bash 36 | ``` 37 | 38 | If you choose to download examples you'll find them in `/home/pi/Pimoroni/plasma/`. 39 | 40 | ### Manual install: 41 | 42 | ```bash 43 | sudo pip3 install plasmalights 44 | ``` 45 | 46 | ### Using Plasma Daemon 47 | 48 | To install the Plasma daemon you should clone this repository, navigate to the "daemon" directory and run the installer: 49 | 50 | ``` 51 | git clone https://github.com/pimoroni/plasma 52 | cd plasma/daemon 53 | sudo ./install 54 | ``` 55 | 56 | --- 57 | 58 | Note: If you're using Picade Player X you should edit daemon/etc/systemd/system/plasma.service and change the output device option from `-o GPIO:15:14` to `-o SERIAL:/dev/ttyACM0`. If you're using Unicorn HAT or pHAT you should use `-o WS281X:WS2812:18:0`. 59 | 60 | If you're using GPIO on a Picade HAT you can adjust the pins accordingly using `-o GPIO::` where data and clock are valid BCM pins. If you're using the old Plasma/Hack header you may need to swap from `-o GPIO:15:14` to `-o GPIO:14:15` depending on how your connections are wired. 61 | 62 | --- 63 | 64 | The Plasma daemon installer installs two programs onto your Raspberry Pi. `plasma` itself and a tool called `plasmactl` you can use to install and switch lighting effects. Plasma runs as a service on your system. 65 | 66 | `plasmactl` commands: 67 | 68 | * `plasmactl 255 0 0` - Set Plasma lights to R, G, B colour. Red in this case. 69 | * `plasmactl ` - Set Plasma lights to pattern image 70 | * `plasmactl fps ` - Change plasma effect framerate (default is 30, lower FPS = less CPU) 71 | * `plasmactl --list` - List all available patterns 72 | * `sudo plasmactl --install ` - Install a new pattern, where `` is the filename of a 24bit PNG image file 73 | 74 | ### Development: 75 | 76 | If you want to contribute, or like living on the edge of your seat by having the latest code, you should clone this repository, `cd` to the library directory, and run: 77 | 78 | ```bash 79 | sudo python3 setup.py install 80 | ``` 81 | 82 | ## Documentation & Support 83 | 84 | * Guides and tutorials - https://learn.pimoroni.com/ 85 | * Get help - http://forums.pimoroni.com/c/support 86 | -------------------------------------------------------------------------------- /daemon/etc/plasma/README.txt: -------------------------------------------------------------------------------- 1 | Plasma LEDs Colour Cycle Patterns 2 | --------------------------------- 3 | 4 | These 24-bit PNGs are animated across Plasma's LEDs to produce dynamic lighting effects. 5 | 6 | To create your own you must create a 24-bit PNG file. 7 | 8 | The width of that PNG file should be: 9 | 10 | * 1 pixel - will be duplicated to every LED 11 | * 4 pixels - will be duplicated to every button 12 | * 36 pixels - individual LED values for 6 buttons 13 | * 40 pixels - individual LED values for 10 buttons 14 | 15 | The height of the PNG file in pixels corresponds to the number of frames in the animation. 16 | 17 | Since Plasma is animated at 60fps, a 60 pixel high animation will last for 1 second. 18 | -------------------------------------------------------------------------------- /daemon/etc/plasma/bluenwhite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimoroni/plasma-python/7857c44255285aac061a9064dd033fd63bbbda29/daemon/etc/plasma/bluenwhite.png -------------------------------------------------------------------------------- /daemon/etc/plasma/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimoroni/plasma-python/7857c44255285aac061a9064dd033fd63bbbda29/daemon/etc/plasma/default.png -------------------------------------------------------------------------------- /daemon/etc/plasma/plasma-rainbow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimoroni/plasma-python/7857c44255285aac061a9064dd033fd63bbbda29/daemon/etc/plasma/plasma-rainbow.png -------------------------------------------------------------------------------- /daemon/etc/plasma/plasma.conf: -------------------------------------------------------------------------------- 1 | pixels: 40 2 | devices: 3 | picade_hat: 4 | type: APA102 5 | pixels: 40 6 | offset: 0 7 | gpio_data: 14 8 | gpio_clock: 15 9 | picade_player_x: 10 | type: serial 11 | port: /dev/ttyACM0 12 | pixels: 40 13 | offset: 0 14 | enabled: False 15 | -------------------------------------------------------------------------------- /daemon/etc/plasma/rainbow-cycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimoroni/plasma-python/7857c44255285aac061a9064dd033fd63bbbda29/daemon/etc/plasma/rainbow-cycle.png -------------------------------------------------------------------------------- /daemon/etc/plasma/rednwhite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimoroni/plasma-python/7857c44255285aac061a9064dd033fd63bbbda29/daemon/etc/plasma/rednwhite.png -------------------------------------------------------------------------------- /daemon/etc/plasma/trippy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimoroni/plasma-python/7857c44255285aac061a9064dd033fd63bbbda29/daemon/etc/plasma/trippy.png -------------------------------------------------------------------------------- /daemon/etc/systemd/system/plasma.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Plasma LED Sequencer Daemon 3 | DefaultDependencies=no 4 | After=local-fs.target 5 | 6 | [Service] 7 | # Change -o to SERIAL:/dev/ttyACM0 for Plasma via Picade Player X 8 | ExecStart=/usr/bin/plasma -d -o GPIO:15:14 9 | Restart=on-failure 10 | Type=forking 11 | PIDFile=/var/run/plasma.pid 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /daemon/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $(id -u) -ne 0 ]; then 4 | printf "Script must be run as root. Try 'sudo ./install.sh'\n" 5 | exit 1 6 | fi 7 | 8 | if [ -d "/etc/plasma" ]; then 9 | printf "Directory /etc/plasma already exists, I'm not going to overwrite it!\n" 10 | exit 11 | fi 12 | 13 | printf "Installing requirements\n" 14 | sudo apt install python3 python3-pip 15 | sudo pip3 install pypng plasmalights 16 | 17 | printf "Installing plasma\n" 18 | mkdir /etc/plasma 19 | cp etc/plasma/* /etc/plasma 20 | cp usr/bin/* /usr/bin/ 21 | 22 | printf "Installing /etc/systemd/system/plasma.service\n" 23 | cp etc/systemd/system/plasma.service /etc/systemd/system/plasma.service 24 | systemctl reenable plasma.service 25 | systemctl start plasma.service 26 | -------------------------------------------------------------------------------- /daemon/usr/bin/plasma: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import png 4 | import time 5 | import signal 6 | import os 7 | import sys 8 | import threading 9 | from datetime import datetime 10 | import argparse 11 | 12 | # Application Defaults 13 | CONFIG_FILE = "/etc/plasma/plasma.conf" 14 | PIPE_FILE = "/tmp/plasma" 15 | PATTERNS = "/etc/plasma/" 16 | FPS = 30 17 | BRIGHTNESS = 1.0 18 | LIGHTS = 10 # Actual number of pixels is 4x this number 19 | DEBUG = False 20 | 21 | # Log & PID files 22 | PID_FILE = "/var/run/plasma.pid" 23 | LOG_FILE = "/var/log/plasma.log" 24 | ERR_FILE = "/var/log/plasma.err" 25 | 26 | 27 | stopped = threading.Event() 28 | 29 | 30 | class FIFO(): 31 | def __init__(self, filename): 32 | self.filename = filename 33 | try: 34 | os.mkfifo(self.filename) 35 | except OSError: 36 | pass 37 | print("Opening...") 38 | self.fifo = os.open(self.filename, os.O_RDONLY | os.O_NONBLOCK) 39 | print("Open...") 40 | 41 | def readline(self, timeout=1): 42 | t_start = time.time() 43 | try: 44 | buf = os.read(self.fifo, 1) 45 | except BlockingIOError: 46 | return None 47 | 48 | if len(buf) == 0: 49 | return None 50 | 51 | while time.time() - t_start < timeout: 52 | try: 53 | c = os.read(self.fifo, 1) 54 | if c == b"\n": 55 | return buf 56 | if len(c) == 1: 57 | buf += c 58 | except BlockingIOError as err: 59 | continue 60 | return None 61 | 62 | def __enter__(self): 63 | return self 64 | 65 | def __exit__(self, e_type, e_value, traceback): 66 | os.close(self.fifo) 67 | os.remove(self.filename) 68 | 69 | 70 | def main(): 71 | args = get_args() 72 | 73 | if args.daemonize: 74 | fork() 75 | 76 | from plasma import auto 77 | 78 | plasma = auto(f"GPIO:14:15:pixel_count={LIGHTS * 4}", CONFIG_FILE) 79 | 80 | log("Starting Plasma in the {daemon} with framerate {fps}fps".format( 81 | daemon='background' if args.daemonize else 'foreground', 82 | fps=args.fps)) 83 | 84 | log("Plasma input pipe: {}".format(PIPE_FILE)) 85 | 86 | signal.signal(signal.SIGINT, signal_handler) 87 | signal.signal(signal.SIGTERM, signal_handler) 88 | 89 | with FIFO(PIPE_FILE) as fifo: 90 | r, g, b = 0, 0, 0 91 | pattern, pattern_w, pattern_h, pattern_meta = load_pattern("default") 92 | alpha = pattern_meta['alpha'] 93 | channels = 4 if alpha else 3 94 | 95 | while not stopped.wait(1.0 / args.fps): 96 | delta = time.time() * 60 97 | command = fifo.readline() 98 | if command is not None: 99 | command = command.decode('utf-8').strip() 100 | 101 | if command == "stop": 102 | stopped.set() 103 | log('Received user command "stop". Stopping.') 104 | break 105 | 106 | rgb = command.split(' ') 107 | if len(rgb) == 3: 108 | try: 109 | r, g, b = [min(255, int(c)) for c in rgb] 110 | pattern, pattern_w, pattern_h, pattern_meta = None, 0, 0, None 111 | except ValueError: 112 | log("Invalid colour: {}".format(command)) 113 | elif len(rgb) == 2 and rgb[0] == "fps": 114 | try: 115 | args.fps = int(rgb[1]) 116 | log("Framerate set to: {}fps".format(rgb[1])) 117 | except ValueError: 118 | log("Invalid framerate: {}".format(rgb[1])) 119 | elif len(rgb) == 2 and rgb[0] == "brightness": 120 | try: 121 | args.brightness = float(rgb[1]) 122 | log("Brightness set to: {}".format(args.brightness)) 123 | except ValueError: 124 | log("Invalid brightness {}".format(rgb[1])) 125 | else: 126 | pattern, pattern_w, pattern_h, pattern_meta = load_pattern(command) 127 | alpha = pattern_meta['alpha'] 128 | channels = 4 if alpha else 3 129 | 130 | if pattern is not None: 131 | offset_y = int(delta % pattern_h) 132 | row = pattern[offset_y] 133 | for x in range(plasma.get_pixel_count()): 134 | offset_x = (x * channels) % (pattern_w * channels) 135 | r, g, b = row[offset_x:offset_x + 3] 136 | plasma.set_pixel(x, r, g, b, brightness=args.brightness) 137 | else: 138 | plasma.set_all(r, g, b, brightness=args.brightness) 139 | 140 | plasma.show() 141 | 142 | 143 | def load_pattern(pattern_name): 144 | pattern_file = os.path.join(PATTERNS, "{}.png".format(pattern_name)) 145 | if os.path.isfile(pattern_file): 146 | r = png.Reader(file=open(pattern_file, 'rb')) 147 | pattern_w, pattern_h, pattern, pattern_meta = r.read() 148 | pattern = list(pattern) 149 | log("Loaded pattern file: {}".format(pattern_file)) 150 | return pattern, pattern_w, pattern_h, pattern_meta 151 | else: 152 | log("Invalid pattern file: {}".format(pattern_file)) 153 | return None, 0, 0, None 154 | 155 | 156 | def get_args(): 157 | parser = argparse.ArgumentParser() 158 | parser.add_argument("-d", "--daemonize", action="store_true", default=False, 159 | help="run plasma as a daemon") 160 | parser.add_argument("-f", "--fps", type=int, default=FPS, 161 | help="set plasma LED update framerate") 162 | parser.add_argument("-b", "--brightness", type=float, default=BRIGHTNESS, 163 | help="set plasma LED brightness") 164 | parser.add_argument("-c", "--config", type=str, default=CONFIG_FILE, 165 | help="path to plasma config file") 166 | return parser.parse_known_args()[0] 167 | 168 | 169 | def fork(): 170 | try: 171 | pid = os.fork() 172 | if pid > 0: 173 | sys.exit(0) 174 | 175 | except OSError as e: 176 | print("Fork #1 failed: {} ({})".format(e.errno, e.strerror)) 177 | sys.exit(1) 178 | 179 | os.chdir("/") 180 | os.setsid() 181 | os.umask(0) 182 | 183 | try: 184 | pid = os.fork() 185 | if pid > 0: 186 | with open(PID_FILE, 'w') as f: 187 | f.write(str(pid)) 188 | sys.exit(0) 189 | 190 | except OSError as e: 191 | print("Fork #2 failed: {} ({})".format(e.errno, e.strerror)) 192 | sys.exit(1) 193 | 194 | si = open("/dev/null", 'r') 195 | so = open(LOG_FILE, 'a+') 196 | se = open(ERR_FILE, 'a+') 197 | 198 | os.dup2(si.fileno(), sys.stdin.fileno()) 199 | os.dup2(so.fileno(), sys.stdout.fileno()) 200 | os.dup2(se.fileno(), sys.stderr.fileno()) 201 | 202 | return pid 203 | 204 | 205 | def log(msg): 206 | sys.stdout.write(str(datetime.now())) 207 | sys.stdout.write(": ") 208 | sys.stdout.write(msg) 209 | sys.stdout.write("\n") 210 | sys.stdout.flush() 211 | 212 | 213 | def signal_handler(sig, frame): 214 | log("Received SIGNAL {}. Stopping.".format(sig)) 215 | stopped.set() 216 | 217 | 218 | if __name__ == "__main__": 219 | main() 220 | -------------------------------------------------------------------------------- /daemon/usr/bin/plasmactl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import pathlib 5 | import sys 6 | 7 | ROOT = pathlib.Path('/etc/plasma') 8 | FIFO = pathlib.Path('/tmp/plasma') 9 | 10 | 11 | def Color(value): 12 | try: 13 | return int(value) 14 | except ValueError: 15 | return int(value, 16) 16 | 17 | 18 | def get_patterns(): 19 | return ROOT.glob('*.png') 20 | 21 | 22 | def valid_patterns(): 23 | for pattern in get_patterns(): 24 | yield pattern.stem 25 | 26 | 27 | def open_fifo(filename): 28 | if filename.exists(): 29 | return open(filename, 'wb') 30 | else: 31 | raise RuntimeError(f"FIFO {filename} does not exit! Is plasma running?") 32 | 33 | 34 | if __name__ == "__main__": 35 | parser = argparse.ArgumentParser() 36 | parser.add_argument('--install', type=pathlib.Path, help='Install an animation file') 37 | parser.add_argument('--list', action='store_true', help='List available animations') 38 | parser.add_argument('--colour', nargs=3, type=Color, help='Display an RGB colour (all values 0-255)') 39 | parser.add_argument('--fps', type=int, help='Set the update framerate') 40 | parser.add_argument('--brightness', type=float, help='Set the LED brightness') 41 | parser.add_argument('--pattern', type=str, help='Display an image-based aniamtion from /etc/plasma', choices=list(valid_patterns())) 42 | 43 | args = parser.parse_args() 44 | 45 | if args.list: 46 | print("Available patterns:") 47 | for pattern in get_patterns(): 48 | print(f"{pattern.stem:30} {pattern}") 49 | sys.exit(0) 50 | 51 | if args.pattern: 52 | print(f"Setting pattern to {args.pattern}") 53 | with open_fifo(FIFO) as fifo: 54 | fifo.write(f"{args.pattern}\n".encode("utf-8")) 55 | fifo.flush() 56 | sys.exit(0) 57 | 58 | if args.colour: 59 | r, g, b = args.colour 60 | print(f"Setting colour to {r}, {g}, {b}") 61 | with open_fifo(FIFO) as fifo: 62 | fifo.write(f"{r} {g} {b}\n".encode("utf-8")) 63 | fifo.flush() 64 | sys.exit(0) 65 | 66 | if args.brightness is not None: 67 | brightness = args.brightness 68 | print(f"Setting brightness to {brightness}") 69 | with open_fifo(FIFO) as fifo: 70 | fifo.write(f"brightness {brightness}\n".encode("utf-8")) 71 | fifo.flush() 72 | sys.exit(0) 73 | 74 | parser.print_help() 75 | sys.exit(1) 76 | -------------------------------------------------------------------------------- /documentation/LEGACY.md: -------------------------------------------------------------------------------- 1 | # Plasma Function Reference 2 | 3 | Before getting started you should tell Plasma how may lights you have: 4 | 5 | ``` 6 | import plasma 7 | plasma.set_light_count(10) 8 | ``` 9 | 10 | The two Plasma methods you'll most commonly use are `set_pixel` and `show`. Here's a simple example: 11 | 12 | ``` 13 | from plasma import set_pixel, show 14 | 15 | set_pixel(0,255,0,0) 16 | show() 17 | ``` 18 | 19 | `set_pixel` takes an optional fifth parameter; the brightness from 0.0 to 1.0. 20 | 21 | `set_pixel(pixel_no, red, green, blue, brightness)` 22 | 23 | You can also change the brightness with `set_brightness` from 0.0 to 1.0, for example: 24 | 25 | ``` 26 | from plasma import set_brightness 27 | 28 | set_brightness(0.5) 29 | show() 30 | ``` 31 | 32 | Additionally, there exists a helper function `set_all` which allows you to set all pixels to the same color and brightness. The `brightness` parameter is just like the one in `set_pixel`: it's optional and ranges from 0.0 to 1.0. 33 | 34 | `set_all(red, green, blue, brightness)` 35 | -------------------------------------------------------------------------------- /documentation/REFERENCE.md: -------------------------------------------------------------------------------- 1 | # Plasma: Reference 2 | 3 | - [Installing](#installing) 4 | - [Raspberry Pi / Linux](#raspberry-pi--linux) 5 | 6 | ## Introduction 7 | 8 | Plasma is a multi-device LED control library originally intended to animate patterns across the arcade button illumination kits of the same name. 9 | 10 | Plasma now supports a variety of LED types: APA102, WS281X (WS2811, WS2812, SK6812), USB/Serial (Plasma USB) and can drive them simultaneously configured as a single continuous strip. 11 | 12 | You can use a command-line argument to specify a particular type and configuration of output device, or a .yml config file to specify any permutation. 13 | 14 | ## Installing 15 | 16 | ### Raspberry Pi / Linux 17 | 18 | Plasma *requires* Python 3, so you should ensure it is installed first: 19 | 20 | ``` 21 | sudo apt install python3 python3-pip 22 | ``` 23 | 24 | You can then install the Python library with pip3: 25 | 26 | ``` 27 | sudo pip3 install plasmalights 28 | ``` 29 | 30 | ## Configuring 31 | 32 | Plasma can be configured either via a single command-line argument understood by the `auto` constructor. 33 | 34 | This argument can either be a device type followed by configuration options, or a path to a `config.yml` file describing multiple devices. 35 | 36 | ### Available Options 37 | 38 | All devices have *required* options which are positional and do not need to be named. 39 | 40 | For example the APA102 output requires a data and clock pin like so: `APA102:14:15` 41 | 42 | And the Serial output requires a port: `SERIAL:/dev/ttyAMA0` 43 | 44 | #### APA102 45 | 46 | ``` 47 | APA102:: 48 | ``` 49 | 50 | * `gpio_data` - BCM pin for data (first argument) 51 | * `gpio_clock` - BCM pin for clock (second argument) 52 | * `gpio_cs` - BCM pin for chip-select (third argument) 53 | 54 | #### WS2812 55 | 56 | ``` 57 | WS281X:: 58 | ``` 59 | 60 | * `gpio_pin` - The BCM pin that the LEDs are connected to 61 | * `strip_type` - A supported strip type, most of the time this will be `WS2812` 62 | * `channel` - For PWM this will be either channel 0 or 1 depending on the pin used, Plasma will try to guess the right channel when it's not supplied 63 | * `brightness` - Global brightness scale (0-255) 64 | * `freq_hz` - Output signal frequency (usually 800khz) 65 | * `dma` - DMA channel 66 | * `invert` - Invert output signal for NPN-transistor based (inverting) level shifters 67 | 68 | Available strip types (note, setting the white element of LEDs is currently not supported): 69 | 70 | * `WS2812` 71 | * `SK6812` 72 | * `SK6812W` 73 | * `SK6812_RGBW` 74 | * `SK6812_RBGW` 75 | * `SK6812_GRBW` 76 | * `SK6812_GBRW` 77 | * `SK6812_BRGW` 78 | * `SK6812_BGRW` 79 | * `WS2811_RGB` 80 | * `WS2811_RBG` 81 | * `WS2811_GRB` 82 | * `WS2811_GBR` 83 | * `WS2811_BRG` 84 | * `WS2811_BGR` 85 | 86 | #### Serial 87 | 88 | ### Configuring via the command-line 89 | 90 | Supplying a device configuration via the command line is simple. You need to know three things: 91 | 92 | 1. What device you want to use 93 | 2. What port/pin(s) it's connected to 94 | 3. How many LEDs it's responsible for 95 | 96 | For example, to drive 20 APA102 LEDs connected to pins 14 and 15 you would run an example like so: 97 | 98 | ``` 99 | ./rainbow.py APA102:14:15:pixel_count=20 100 | ``` 101 | 102 | And to drive 20 WS281X LEDs connected to pin 18 you would use: 103 | 104 | ``` 105 | ./rainbow.py WS281X:18:WS2812:pixel_count=20 106 | ``` 107 | 108 | 109 | ### Configuring using a config file -------------------------------------------------------------------------------- /examples/example-config.yml: -------------------------------------------------------------------------------- 1 | pixels: 100 2 | devices: 3 | Simon: 4 | type: WS281X 5 | pixels: 30 6 | offset: 0 7 | gpio_pin: 1 8 | strip_type: WS2812 9 | Alvin: 10 | type: APA102 11 | pixels: 30 12 | offset: 30 13 | gpio_data: 10 14 | gpio_clock: 11 15 | Theodore: 16 | type: SERIAL 17 | pixels: 40 18 | offset: 60 19 | gpio_data: 10 20 | gpio_clock: 11 21 | -------------------------------------------------------------------------------- /examples/larson_hue.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import math 4 | import time 5 | import colorsys 6 | 7 | from plasma import auto 8 | 9 | 10 | NUM_PIXELS = 10 * 4 11 | FALLOFF = 1.9 12 | SCAN_SPEED = 4 13 | 14 | plasma = auto(default=f"GPIO:14:15:pixel_count={NUM_PIXELS}") 15 | 16 | if plasma.get_pixel_count() == 1: 17 | raise RuntimeError("Uh, you can't larson scan *one* pixel!?") 18 | 19 | plasma.set_clear_on_exit() 20 | 21 | start_time = time.time() 22 | 23 | while True: 24 | delta = (time.time() - start_time) 25 | 26 | # Offset is a sine wave derived from the time delta 27 | # we use this to animate both the hue and larson scan 28 | # so they are kept in sync with each other 29 | offset = (math.sin(delta * SCAN_SPEED) + 1) / 2 30 | 31 | # Use offset to pick the right colour from the hue wheel 32 | hue = int(round(offset * 360)) 33 | 34 | # Maximum number basex on NUM_PIXELS 35 | max_val = plasma.get_pixel_count() - 1 36 | 37 | # Now we generate a value from 0 to max_val 38 | offset = int(round(offset * max_val)) 39 | 40 | for x in range(plasma.get_pixel_count()): 41 | sat = 1.0 42 | 43 | val = max_val - (abs(offset - x) * FALLOFF) 44 | val /= float(max_val) # Convert to 0.0 to 1.0 45 | val = max(val, 0.0) # Ditch negative values 46 | 47 | xhue = hue # Grab hue for this pixel 48 | xhue += (1 - val) * 10 # Use the val offset to give a slight colour trail variation 49 | xhue %= 360 # Clamp to 0-359 50 | xhue /= 360.0 # Convert to 0.0 to 1.0 51 | 52 | r, g, b = [int(c * 255) for c in colorsys.hsv_to_rgb(xhue, sat, val)] 53 | 54 | plasma.set_pixel(x, r, g, b, val) 55 | 56 | plasma.show() 57 | 58 | time.sleep(0.001) 59 | -------------------------------------------------------------------------------- /examples/led-strip-mini-hat-apa102.yml: -------------------------------------------------------------------------------- 1 | pixels: 50 2 | devices: 3 | APA102_A: 4 | type: APA102 5 | gpio_data: 10 6 | gpio_clock: 11 7 | pixels: 25 8 | offset: 0 9 | APA102_B: 10 | type: APA102 11 | gpio_data: 20 12 | gpio_clock: 21 13 | pixels: 25 14 | offset: 25 15 | -------------------------------------------------------------------------------- /examples/led-strip-mini-hat-ws2812.yml: -------------------------------------------------------------------------------- 1 | pixels: 50 2 | devices: 3 | WS281X_B: 4 | type: WS281X 5 | strip_type: WS2812 6 | gpio_pin: 12 7 | pixels: 25 8 | offset: 0 9 | WS281X_A: 10 | type: WS281X 11 | strip_type: WS2812 12 | gpio_pin: 13 13 | pixels: 25 14 | offset: 25 15 | 16 | -------------------------------------------------------------------------------- /examples/rainbow.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import colorsys 4 | import time 5 | 6 | from plasma import auto 7 | 8 | 9 | NUM_PIXELS = 10 * 4 # Original Plasma light boards have 4 pixels per light 10 | 11 | 12 | print("""rainbow.py - Display a rainbow on Plasma lights 13 | 14 | Optionally supply a device descriptor to use Plasma USB Serial vs GPIO: 15 | 16 | ./rainbow.py SERIAL:/dev/ttyACM0:pixel_count=10 17 | 18 | Or your own choice of GPIO pins (Data/Clock): 19 | 20 | ./rainbow.py GPIO:14:15:pixel_count=10 21 | 22 | Press Ctrl+C to exit. 23 | 24 | """) 25 | 26 | plasma = auto(default=f"GPIO:14:15:pixel_count={NUM_PIXELS}") 27 | # plasma.set_clear_on_exit() 28 | 29 | spacing = 360.0 / 16.0 30 | hue = 0 31 | 32 | while True: 33 | hue = int(time.time() * 100) % 360 34 | for x in range(plasma.get_pixel_count()): 35 | offset = x * spacing 36 | h = ((hue + offset) % 360) / 360.0 37 | r, g, b = [int(c * 255) for c in colorsys.hsv_to_rgb(h, 1.0, 1.0)] 38 | plasma.set_pixel(x, r, g, b) 39 | 40 | plasma.show() 41 | time.sleep(0.001) 42 | -------------------------------------------------------------------------------- /examples/setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore= 3 | D100 # Ignore various docstring reqs 4 | D101 5 | D102 6 | D103 7 | E501 # Ignore long lines -------------------------------------------------------------------------------- /examples/solid-colour.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import time 5 | 6 | from plasma import auto 7 | 8 | 9 | NUM_PIXELS = 10 * 4 # Original Plasma light boards have 4 pixels per light 10 | 11 | 12 | print(f"""rainbow.py - Display a solid colour on Plasma lights 13 | 14 | Supply the colour values (0-255) on the command line: 15 | 16 | {sys.argv[0]} 255 0 255 17 | 18 | Optionally supply a device descriptor to use Plasma USB Serial vs GPIO: 19 | 20 | {sys.argv[0]} SERIAL:/dev/ttyACM0:pixel_count=10 255 0 255 21 | 22 | Or your own choice of GPIO pins (Data/Clock): 23 | 24 | {sys.argv[0]} GPIO:14:15:pixel_count=10 255 0 255 25 | 26 | Press Ctrl+C to exit. 27 | 28 | """) 29 | 30 | colour_offset = len(sys.argv) - 3 31 | 32 | 33 | plasma = auto(default=f"GPIO:14:15:pixel_count={NUM_PIXELS}") 34 | # plasma.set_clear_on_exit() 35 | 36 | spacing = 360.0 / 16.0 37 | hue = 0 38 | 39 | while True: 40 | hue = int(time.time() * 100) % 360 41 | for x in range(plasma.get_pixel_count()): 42 | r, g, b = [int(x) for x in sys.argv[colour_offset:]] 43 | plasma.set_pixel(x, r, g, b) 44 | 45 | plasma.show() 46 | time.sleep(0.001) 47 | -------------------------------------------------------------------------------- /fx/config.yml: -------------------------------------------------------------------------------- 1 | pixels: 31 2 | devices: 3 | serial: 4 | pixels: 28 5 | offset: 0 6 | port: /dev/ttyACM0 7 | apa102: 8 | pixels: 3 9 | offset: 28 10 | gpio_data: 10 11 | gpio_clock: 11 12 | gpio_cs: 8 13 | -------------------------------------------------------------------------------- /fx/plasmafx/CHANGELOG.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimoroni/plasma-python/7857c44255285aac061a9064dd033fd63bbbda29/fx/plasmafx/CHANGELOG.txt -------------------------------------------------------------------------------- /fx/plasmafx/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Pimoroni Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /fx/plasmafx/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGELOG.txt 2 | include LICENSE.txt 3 | include README.md 4 | include setup.py 5 | recursive-include plasmafx *.py 6 | -------------------------------------------------------------------------------- /fx/plasmafx/README.md: -------------------------------------------------------------------------------- 1 | # PlasmaFX - LED effects sequencing 2 | 3 | PlasmaFX allows you to split a chain of Plasma LEDs into chunks, each handled by its own plugin. 4 | 5 | For example, you can assign a plugin to a Plasma Light board so it can control the 4 onboard LEDs in interesting ways. 6 | 7 | Or, you could assign a plugin to an Adafruit NeoPixel ring wired in series with other NeoPixel boards. -------------------------------------------------------------------------------- /fx/plasmafx/plasmafx/__init__.py: -------------------------------------------------------------------------------- 1 | """Plasma: Light FX Sequencer.""" 2 | import time 3 | 4 | 5 | class Sequence(object): 6 | """PlasmaFX Sequence. 7 | 8 | A PlasmaFX sequence is responsible for a sequence of 9 | Plasma light groupings. 10 | 11 | LEDs can be grouped into "lights" such as the 4 on a Plasma PCB 12 | or the 12+ on an Adafruit NeoPixel ring. 13 | 14 | """ 15 | 16 | def __init__(self, pixel_count): 17 | """Initialise PlasmaFX Sequence. 18 | 19 | :param pixel_count: Number of individual pixels in sequence 20 | 21 | """ 22 | self._pixel_count = pixel_count 23 | self._plugins = {} 24 | self._pixels = [(0, 0, 0) for _ in range(self._pixel_count)] 25 | 26 | def __iter__(self): 27 | self.update_pixels() 28 | 29 | for index, pixel in enumerate(self._pixels): 30 | yield index, pixel 31 | 32 | def set_plugin(self, offset, plugin): 33 | """Set plugin for light at index.""" 34 | self._plugins[offset] = plugin 35 | 36 | def update_pixels(self, delta=None): 37 | if delta is None: 38 | delta = time.time() 39 | for offset, plugin in self._plugins.items(): 40 | self._pixels[offset:offset + plugin.get_pixel_count()] = plugin.get_pixels(delta) 41 | 42 | def get_pixels(self, delta=None): 43 | """Return RGB tuples for each LED in sequence at current time. 44 | 45 | :param delta: Time value to use, (default: time.time()) 46 | 47 | """ 48 | self.update_pixels(delta) 49 | 50 | for index, pixel in enumerate(self._pixels): 51 | yield index, pixel 52 | -------------------------------------------------------------------------------- /fx/plasmafx/plasmafx/core.py: -------------------------------------------------------------------------------- 1 | """Plasma: Light FX Sequencer - Plugin base class.""" 2 | 3 | 4 | class Plugin(object): 5 | """PlasmaFX Plugin. 6 | 7 | A PlasmaFX plugin is responsible for the 4 lights on 8 | a single Plasma light board. 9 | 10 | """ 11 | 12 | def __init__(self, pixel_count=1): 13 | """Initialise PlasmaFX: Base Plugin.""" 14 | self._pixel_count = pixel_count 15 | 16 | def get_pixels(self, delta): 17 | """Return RGB tuples for each LED in sequence at current time.""" 18 | for _ in range(self._pixel_count): 19 | yield (0, 0, 0) 20 | 21 | def get_pixel_count(self): 22 | """Return count of plugin managed pixels.""" 23 | return self._pixel_count 24 | -------------------------------------------------------------------------------- /fx/plasmafx/plasmafx/plugins.py: -------------------------------------------------------------------------------- 1 | """Plasma: Light FX Sequencer - Built-in plugins.""" 2 | 3 | import pkg_resources 4 | from .core import Plugin 5 | 6 | 7 | plasma_fx_plugins = {} 8 | 9 | for entry_point in pkg_resources.iter_entry_points("plasmafx.effect_plugins"): 10 | effect_handle = entry_point.name 11 | plasma_fx_plugins[effect_handle] = entry_point.load() 12 | globals()[f"FX{effect_handle}"] = entry_point.load() 13 | 14 | 15 | class Solid(Plugin): 16 | """PlasmaFX: Solid continuous light colour.""" 17 | 18 | def __init__(self, pixel_count, r, g, b): 19 | """Initialise PlasmaFX: Solid. 20 | 21 | :param r, g, b: Amount of red, green and blue 22 | 23 | """ 24 | Plugin.__init__(self, pixel_count) 25 | self.set_colour(r, g, b) 26 | 27 | def set_colour(self, r, g, b): 28 | """Set solid colour. 29 | 30 | :param r, g, b: Amount of red, green and blue 31 | 32 | """ 33 | self.r = r 34 | self.g = g 35 | self.b = b 36 | 37 | def get_pixels(self, delta): 38 | """Return colour values at time.""" 39 | for _ in range(self._pixel_count): 40 | yield (self.r, self.g, self.b) 41 | 42 | 43 | class Pulse(Plugin): 44 | """PlasmaFX: Pulsing light colour.""" 45 | 46 | def __init__(self, pixel_count, sequence, speed=1): 47 | """Initialise PlasmaFX: Pulse. 48 | 49 | :param sequence: List of colours to pulse through 50 | :param speed: Speed of effect 51 | 52 | """ 53 | Plugin.__init__(self, pixel_count) 54 | self.sequence = sequence 55 | self.speed = speed 56 | 57 | def _colour(self, index, channel): 58 | return self.sequence[index][channel] 59 | 60 | def _blend(self, channel, first, second, blend): 61 | result = (self._colour(second, channel) - self._colour(first, channel)) * float(blend) 62 | result += self._colour(first, channel) 63 | return int(result) 64 | 65 | def get_pixels(self, delta): 66 | """Return colour values at time.""" 67 | length = len(self.sequence) 68 | position = (delta % self.speed) / float(self.speed) 69 | colour = length * position 70 | blend = colour - int(colour) 71 | first_colour = int(colour) % length 72 | second_colour = (first_colour + 1) % length 73 | 74 | r = self._blend(0, first_colour, second_colour, blend) 75 | g = self._blend(1, first_colour, second_colour, blend) 76 | b = self._blend(2, first_colour, second_colour, blend) 77 | 78 | for _ in range(self._pixel_count): 79 | yield (r, g, b) 80 | 81 | 82 | class Spin(Plugin): 83 | """PlasmaFX: Spinning light effect.""" 84 | 85 | def __init__(self, pixel_count, sequence, speed=1): 86 | """Initialise PlasmaFX: Pulse. 87 | 88 | :param sequence: List of colours to spin through 89 | :param speed: Speed of effect 90 | 91 | """ 92 | if len(sequence) > pixel_count: 93 | raise ValueError(f"Sequence should contain at most {pixel_count} items.") 94 | Plugin.__init__(self, pixel_count) 95 | self.sequence = sequence 96 | self.speed = speed 97 | 98 | def get_pixels(self, delta): 99 | """Return colour values at time.""" 100 | offset = int(delta / self.speed) 101 | 102 | for x in range(self._pixel_count): 103 | x += offset 104 | x %= len(self.sequence) 105 | yield self.sequence[x] 106 | -------------------------------------------------------------------------------- /fx/plasmafx/setup.cfg: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | [metadata] 3 | name = plasmafx 4 | version = 1.0.0 5 | author = Philip Howard 6 | author_email = phil@pimoroni.com 7 | description = LED effects sequencer for Plasma LED Driver 8 | long_description = file: README.md 9 | long_description_content_type = text/markdown 10 | keywords = Rasperry Pi LED 11 | url = https://www.pimoroni.com 12 | project_urls = 13 | GitHub=https://www.github.com/pimoroni/plasma 14 | license = MIT 15 | license_files = LICENSE.txt 16 | classifiers = 17 | Development Status :: 4 - Beta 18 | Operating System :: POSIX :: Linux 19 | License :: OSI Approved :: MIT License 20 | Intended Audience :: Developers 21 | Programming Language :: Python :: 3.7 22 | Topic :: Software Development 23 | Topic :: Software Development :: Libraries 24 | Topic :: System :: Hardware 25 | 26 | [options] 27 | packages = plasmafx 28 | python_requires = >= 3.7 29 | install_requires = 30 | 31 | [flake8] 32 | exclude = 33 | test.py 34 | tests/ 35 | .tox, 36 | .eggs, 37 | .git, 38 | __pycache__, 39 | build, 40 | dist 41 | ignore = 42 | E501 43 | -------------------------------------------------------------------------------- /fx/plasmafx/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Copyright (c) 2020 Pimoroni. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 | of the Software, and to permit persons to whom the Software is furnished to do 11 | so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | 25 | from setuptools import setup 26 | 27 | setup() 28 | -------------------------------------------------------------------------------- /fx/plasmafx/tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py{37},qa 3 | skip_missing_interpreters = True 4 | 5 | [testenv] 6 | commands = 7 | python setup.py install 8 | coverage run -m py.test -v -r wsx 9 | coverage report 10 | deps = 11 | mock 12 | pytest>=3.1 13 | pytest-cov 14 | 15 | [testenv:qa] 16 | commands = 17 | flake8 18 | python setup.py sdist bdist_wheel 19 | twine check dist/* 20 | deps = 21 | flake8 22 | flake8-docstrings 23 | twine 24 | -------------------------------------------------------------------------------- /fx/plasmafx_plugin_cycle/CHANGELOG.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimoroni/plasma-python/7857c44255285aac061a9064dd033fd63bbbda29/fx/plasmafx_plugin_cycle/CHANGELOG.txt -------------------------------------------------------------------------------- /fx/plasmafx_plugin_cycle/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Pimoroni Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /fx/plasmafx_plugin_cycle/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGELOG.txt 2 | include LICENSE.txt 3 | include README.md 4 | include setup.py 5 | recursive-include plasmafx_plugin_cycle *.py 6 | -------------------------------------------------------------------------------- /fx/plasmafx_plugin_cycle/README.md: -------------------------------------------------------------------------------- 1 | # PlasmaFX - LED Colour Cycling Plugin -------------------------------------------------------------------------------- /fx/plasmafx_plugin_cycle/plasmafx_plugin_cycle/__init__.py: -------------------------------------------------------------------------------- 1 | """Plasma: Light FX Sequencer - Colour Cycling Plugin.""" 2 | from plasmafx.core import Plugin 3 | from colorsys import hsv_to_rgb 4 | 5 | 6 | class Cycle(Plugin): 7 | """Plasma: Light FX Sequencer - Colour Cycling Plugin.""" 8 | 9 | def __init__(self, pixel_count=1, speed=1.0, spread=360.0, offset=0.0, saturation=1.0, value=1.0): 10 | """Initialise PlasmaFX: Cycle. 11 | 12 | :param speed: Speed of cycling effect 13 | :param spread: Spread of effect around HSV circumference in degrees 14 | :param offset: Offset in degrees 15 | :param saturation: Colour saturation 16 | :param value: Colour value (effectively brightness) 17 | 18 | """ 19 | Plugin.__init__(self, pixel_count) 20 | self._speed = float(speed) 21 | self._saturation = float(saturation) 22 | self._value = float(value) 23 | self._spread = spread / 360.0 24 | self._offset = offset / 360.0 25 | self._pixel_separation = self._spread / self._pixel_count 26 | 27 | def get_pixels(self, delta): 28 | """Return colour values at time.""" 29 | delta /= 10.0 30 | delta *= self._speed 31 | for x in range(self._pixel_count): 32 | hue = delta + (self._pixel_separation * x) + self._offset 33 | r, g, b = [int(c * 255) for c in hsv_to_rgb(hue, self._saturation, self._value)] 34 | yield (r, g, b) 35 | -------------------------------------------------------------------------------- /fx/plasmafx_plugin_cycle/setup.cfg: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | [metadata] 3 | name = plasmafx-plugin-cycle 4 | version = 1.0.0 5 | author = Philip Howard 6 | author_email = phil@pimoroni.com 7 | description = Colour cycle plugin for plasmafx 8 | long_description = file: README.md 9 | long_description_content_type = text/markdown 10 | keywords = plugin, effects, lighting 11 | url = https://www.pimoroni.com 12 | project_urls = 13 | GitHub=https://www.github.com/pimoroni/plasma 14 | license = MIT 15 | license_files = LICENSE.txt 16 | classifiers = 17 | Development Status :: 4 - Beta 18 | Operating System :: POSIX :: Linux 19 | License :: OSI Approved :: MIT License 20 | Intended Audience :: Developers 21 | Programming Language :: Python :: 3.7 22 | Topic :: Software Development 23 | Topic :: Software Development :: Libraries 24 | Topic :: System :: Hardware 25 | 26 | [options] 27 | packages = plasmafx_plugin_cycle 28 | python_requires = >= 3.7 29 | install_requires = 30 | 31 | [options.entry_points] 32 | plasmafx.effect_plugins = 33 | Cycle = plasmafx_plugin_cycle:Cycle 34 | 35 | [flake8] 36 | exclude = 37 | test.py 38 | tests/ 39 | .tox, 40 | .eggs, 41 | .git, 42 | __pycache__, 43 | build, 44 | dist 45 | ignore = 46 | E501 47 | -------------------------------------------------------------------------------- /fx/plasmafx_plugin_cycle/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Copyright (c) 2020 Pimoroni. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 | of the Software, and to permit persons to whom the Software is furnished to do 11 | so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | 25 | from setuptools import setup 26 | 27 | setup() 28 | -------------------------------------------------------------------------------- /fx/plasmafx_plugin_cycle/tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py{37},qa 3 | skip_missing_interpreters = True 4 | 5 | [testenv] 6 | commands = 7 | python setup.py install 8 | coverage run -m py.test -v -r wsx 9 | coverage report 10 | deps = 11 | mock 12 | pytest>=3.1 13 | pytest-cov 14 | 15 | [testenv:qa] 16 | commands = 17 | flake8 18 | python setup.py sdist bdist_wheel 19 | twine check dist/* 20 | deps = 21 | flake8 22 | flake8-docstrings 23 | twine 24 | -------------------------------------------------------------------------------- /fx/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from plasma import auto 4 | import plasmafx 5 | from plasmafx import plugins 6 | import time 7 | 8 | 9 | FPS = 60 10 | PIXEL_COUNT = 10 11 | PIXELS_PER_LIGHT = 4 12 | 13 | 14 | print("""Plasma FX Plugin Test. 15 | 16 | This test is designed for the original Plasma Light boards. 17 | 18 | Each light has 4 pixels, so we set up a sequence of plugins that each handle 4 pixels. 19 | 20 | Now we can flash or cycle individual lights. 21 | 22 | """) 23 | 24 | plasma = auto(default=f"GPIO:14:15:pixel_count={PIXEL_COUNT}") 25 | 26 | sequence = plasmafx.Sequence(plasma.get_pixel_count()) 27 | 28 | light_count = plasma.get_pixel_count() // PIXELS_PER_LIGHT 29 | 30 | for x in range(light_count - 1): 31 | sequence.set_plugin(x * PIXELS_PER_LIGHT, plugins.FXCycle( 32 | light_count, 33 | speed=2, 34 | spread=360.0 / light_count, 35 | offset=360.0 / light_count * x 36 | )) 37 | 38 | sequence.set_plugin(0 * PIXELS_PER_LIGHT, plugins.Pulse( 39 | light_count, [ 40 | (0, 0, 0), 41 | (255, 0, 255)] 42 | )) 43 | 44 | sequence.set_plugin(1 * PIXELS_PER_LIGHT, plugins.Pulse( 45 | light_count, [ 46 | (255, 0, 0), 47 | (0, 0, 255), 48 | (0, 0, 0)], 49 | speed=0.5)) 50 | 51 | sequence.set_plugin(2 * PIXELS_PER_LIGHT, plugins.Spin( 52 | light_count, [ 53 | (255, 255, 0), 54 | (0, 0, 255) 55 | ], 56 | speed=0.1)) 57 | 58 | # Keybow hanging off the end 59 | if plasma.get_pixel_count() == 31: 60 | sequence.set_plugin(28, plugins.FXCycle(1, offset=0)) 61 | sequence.set_plugin(29, plugins.FXCycle(1, offset=120)) 62 | sequence.set_plugin(30, plugins.FXCycle(1, offset=240)) 63 | 64 | try: 65 | while True: 66 | plasma.set_sequence(sequence) 67 | plasma.show() 68 | time.sleep(1.0 / FPS) 69 | 70 | except KeyboardInterrupt: 71 | plasma.set_all(0, 0, 0) 72 | plasma.show() 73 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $(id -u) -ne 0 ]; then 4 | printf "Script must be run as root. Try 'sudo ./install.sh'\n" 5 | exit 1 6 | fi 7 | 8 | 9 | WORKING_DIR=`pwd` 10 | 11 | apt update 12 | apt install -y python-pip 13 | pip install pypng pyserial 14 | 15 | cd $WORKING_DIR/library 16 | sudo python setup.py install 17 | cd $WORKING_DIR 18 | 19 | cd $WORKING_DIR/daemon 20 | ./install.sh 21 | cd $WORKING_DIR 22 | -------------------------------------------------------------------------------- /library/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = plasma 3 | omit = 4 | .tox/* 5 | -------------------------------------------------------------------------------- /library/CHANGELOG.txt: -------------------------------------------------------------------------------- 1 | 2.0.2 2 | ----- 3 | 4 | * Pass brightness values through plasma Matrix to underlying LED drivers 5 | 6 | 2.0.1 7 | ----- 8 | 9 | * New `auto()` can accept a config file path directly (for plasma daemon) 10 | * New `Matrix` and `Core` now accept list and dict type sequences in `set_sequence` 11 | * New `Matrix` config supports an "enabled" option for easy config switching 12 | * Bugfix `Matrix` catches KeyError when trying to `del` non-required options 13 | 14 | 2.0.0 15 | ----- 16 | 17 | * Port to Python >=3.7, drop Python 2.7 support 18 | * Significant refactoring and restructuring 19 | * Config file support for configuring LED types/relationships 20 | * WS382X support 21 | * Chip-Select support for APA102 22 | * PlasmaMatrix to combine multiple output devices 23 | 24 | 1.0.0 25 | ----- 26 | 27 | * API refactor, use plasma.legacy for old API 28 | * USB support for Picade Player X 29 | 30 | 0.0.1 31 | ----- 32 | 33 | * Initial Release 34 | 35 | -------------------------------------------------------------------------------- /library/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Pimoroni Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /library/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGELOG.txt 2 | include LICENSE.txt 3 | include README.md 4 | include setup.py 5 | recursive-include plasma *.py 6 | -------------------------------------------------------------------------------- /library/README.md: -------------------------------------------------------------------------------- 1 | # Plasma: LED Sequencing 2 | 3 | Plasma is an LED/Light sequencing suite written to harmonise a variety of LED strand/board types and interfaces into a standard API for write-once-run-anyway lighting code. 4 | 5 | Plasma also includes plasmad, a system daemon for sequencing light strips using PNG images to provide animation frames. 6 | 7 | [![Build Status](https://travis-ci.com/pimoroni/plasma.svg?branch=master)](https://travis-ci.com/pimoroni/plasma) 8 | [![Coverage Status](https://coveralls.io/repos/github/pimoroni/plasma/badge.svg?branch=master)](https://coveralls.io/github/pimoroni/plasma?branch=master) 9 | [![PyPi Package](https://img.shields.io/pypi/v/plasmalights.svg)](https://pypi.python.org/pypi/plasmalights) 10 | [![Python Versions](https://img.shields.io/pypi/pyversions/plasmalights.svg)](https://pypi.python.org/pypi/plasmalights) 11 | 12 | ## Compatible Products 13 | 14 | Plasma was originally written to provide an easy way to sequence lights and swap out patterns for the Pimoroni Plasma kit. 15 | 16 | - https://shop.pimoroni.com/products/picade-plasma-kit-illuminated-arcade-buttons 17 | - https://shop.pimoroni.com/products/player-x-usb-games-controller-pcb 18 | - https://shop.pimoroni.com/products/blinkt 19 | - https://shop.pimoroni.com/products/unicorn-hat 20 | - https://shop.pimoroni.com/products/unicorn-phat 21 | 22 | ## Installing 23 | 24 | ### Full install (recommended): 25 | 26 | We've created an easy installation script that will install all pre-requisites and get your Plasma Arcade Button Lights 27 | up and running with minimal efforts. To run it, fire up Terminal which you'll find in Menu -> Accessories -> Terminal 28 | on your Raspberry Pi desktop, as illustrated below: 29 | 30 | ![Finding the terminal](http://get.pimoroni.com/resources/github-repo-terminal.png) 31 | 32 | In the new terminal window type the command exactly as it appears below (check for typos) and follow the on-screen instructions: 33 | 34 | ```bash 35 | curl https://get.pimoroni.com/plasma | bash 36 | ``` 37 | 38 | If you choose to download examples you'll find them in `/home/pi/Pimoroni/plasma/`. 39 | 40 | ### Manual install: 41 | 42 | ```bash 43 | sudo pip3 install plasmalights 44 | ``` 45 | 46 | ### Using Plasma Daemon 47 | 48 | To install the Plasma daemon you should clone this repository, navigate to the "daemon" directory and run the installer: 49 | 50 | ``` 51 | git clone https://github.com/pimoroni/plasma 52 | cd plasma/daemon 53 | sudo ./install 54 | ``` 55 | 56 | --- 57 | 58 | Note: If you're using Picade Player X you should edit daemon/etc/systemd/system/plasma.service and change the output device option from `-o GPIO:15:14` to `-o SERIAL:/dev/ttyACM0`. If you're using Unicorn HAT or pHAT you should use `-o WS281X:WS2812:18:0`. 59 | 60 | If you're using GPIO on a Picade HAT you can adjust the pins accordingly using `-o GPIO::` where data and clock are valid BCM pins. If you're using the old Plasma/Hack header you may need to swap from `-o GPIO:15:14` to `-o GPIO:14:15` depending on how your connections are wired. 61 | 62 | --- 63 | 64 | The Plasma daemon installer installs two programs onto your Raspberry Pi. `plasma` itself and a tool called `plasmactl` you can use to install and switch lighting effects. Plasma runs as a service on your system. 65 | 66 | `plasmactl` commands: 67 | 68 | * `plasmactl 255 0 0` - Set Plasma lights to R, G, B colour. Red in this case. 69 | * `plasmactl ` - Set Plasma lights to pattern image 70 | * `plasmactl fps ` - Change plasma effect framerate (default is 30, lower FPS = less CPU) 71 | * `plasmactl --list` - List all available patterns 72 | * `sudo plasmactl --install ` - Install a new pattern, where `` is the filename of a 24bit PNG image file 73 | 74 | ### Development: 75 | 76 | If you want to contribute, or like living on the edge of your seat by having the latest code, you should clone this repository, `cd` to the library directory, and run: 77 | 78 | ```bash 79 | sudo python3 setup.py install 80 | ``` 81 | 82 | ## Documentation & Support 83 | 84 | * Guides and tutorials - https://learn.pimoroni.com/plasma 85 | * Function reference - http://docs.pimoroni.com/plasma/ 86 | * Get help - http://forums.pimoroni.com/c/support 87 | 88 | ## Changelog 89 | 90 | 2.0.2 91 | ----- 92 | 93 | * Pass brightness values through plasma Matrix to underlying LED drivers 94 | 95 | 2.0.1 96 | ----- 97 | 98 | * New `auto()` can accept a config file path directly (for plasma daemon) 99 | * New `Matrix` and `Core` now accept list and dict type sequences in `set_sequence` 100 | * New `Matrix` config supports an "enabled" option for easy config switching 101 | * Bugfix `Matrix` catches KeyError when trying to `del` non-required options 102 | 103 | 2.0.0 104 | ----- 105 | 106 | * Port to Python >=3.7, drop Python 2.7 support 107 | * Significant refactoring and restructuring 108 | * Config file support for configuring LED types/relationships 109 | * WS382X support 110 | * Chip-Select support for APA102 111 | * PlasmaMatrix to combine multiple output devices 112 | 113 | 1.0.0 114 | ----- 115 | 116 | * API refactor, use plasma.legacy for old API 117 | * USB support for Picade Player X 118 | 119 | 0.0.1 120 | ----- 121 | 122 | * Initial Release 123 | 124 | -------------------------------------------------------------------------------- /library/plasma/__init__.py: -------------------------------------------------------------------------------- 1 | """Plasma multi device LED driver.""" 2 | import sys 3 | import pathlib 4 | 5 | __version__ = '2.0.2' 6 | 7 | 8 | def auto(default=None, descriptor=None): 9 | """Return a Plasma device instance. 10 | 11 | Will try to get arguments from command-line, 12 | otherwise falling back to supplied defaults. 13 | 14 | """ 15 | if descriptor is None: 16 | if len(sys.argv) > 1: 17 | descriptor = sys.argv[1] 18 | elif default is not None: 19 | descriptor = default 20 | else: 21 | raise ValueError("get_device requires a descriptor") 22 | 23 | plasma, options = get_device(descriptor) 24 | 25 | return plasma(**options) 26 | 27 | 28 | def get_device(descriptor=None): 29 | """Return a Plasma device class and arguments. 30 | 31 | :param descriptor: String describing device and arguments. 32 | 33 | This function accepts descriptors in the form: 34 | 35 | DEVICE:arg:arg 36 | 37 | IE: A serial connection would be described as: 38 | 39 | SERIAL:/dev/ttyACM0 40 | 41 | And GPIO as: 42 | 43 | GPIO:14:15 44 | 45 | And WS281X pixels as: 46 | 47 | WS281X:WS2812_RGB:13:1 48 | 49 | Additional optional arguments can be supplied by name, eg: 50 | 51 | WS281X:WS2812_RGB:13:1:freq_hz=800000 52 | 53 | """ 54 | path = pathlib.Path(descriptor) 55 | if path.is_file(): 56 | from .matrix import PlasmaMatrix 57 | return PlasmaMatrix, {'config_file': path} 58 | 59 | if type(descriptor) is str: 60 | dsc = descriptor.split(":") 61 | 62 | output_type = dsc[0] 63 | output_options = dsc[1:] 64 | 65 | if output_type in ["GPIO", "APA102"]: 66 | from .apa102 import PlasmaAPA102 67 | return PlasmaAPA102, PlasmaAPA102.parse_options(output_options) 68 | if output_type in ["USB", "SERIAL"]: 69 | from .serial import PlasmaSerial 70 | return PlasmaSerial, PlasmaSerial.parse_options(output_options) 71 | if output_type == "WS281X": 72 | from .ws281x import PlasmaWS281X 73 | return PlasmaWS281X, PlasmaWS281X.parse_options(output_options) 74 | 75 | raise ValueError(f"Invalid descriptor: {descriptor}") 76 | -------------------------------------------------------------------------------- /library/plasma/apa102.py: -------------------------------------------------------------------------------- 1 | """Plasma support for APA102 style pixels.""" 2 | import time 3 | from .core import Plasma 4 | 5 | 6 | class PlasmaAPA102(Plasma): 7 | """Plasma support for APA102 style pixels.""" 8 | 9 | name = "APA102" 10 | 11 | options = { 12 | 'pixel_count': int, 13 | "gpio_data": int, 14 | "gpio_clock": int, 15 | "gpio_cs": int 16 | } 17 | 18 | option_order = ("gpio_data", "gpio_clock", "gpio_cs") 19 | 20 | def __init__(self, pixel_count=1, gpio_data=14, gpio_clock=15, gpio_cs=None, gpio=None): 21 | """Initialise an APA102 device. 22 | 23 | :param pixel_count: Number of individual RGB LEDs 24 | :param gpio_data: BCM pin for data 25 | :param gpio_clock: BCM pin for clock 26 | :param gpio: Optional GPIO back-end, should be RPi.GPIO compatible 27 | 28 | """ 29 | self._gpio = gpio 30 | if self._gpio is None: 31 | import RPi.GPIO as GPIO 32 | self._gpio = GPIO 33 | 34 | self._gpio_data = gpio_data 35 | self._gpio_clock = gpio_clock 36 | self._gpio_cs = gpio_cs 37 | self._gpio_is_setup = False 38 | Plasma.__init__(self, pixel_count) 39 | 40 | def _write_byte(self, byte): 41 | for x in range(8): 42 | self._gpio.output(self._gpio_data, byte & 0b10000000) 43 | self._gpio.output(self._gpio_clock, 1) 44 | time.sleep(0) 45 | byte <<= 1 46 | self._gpio.output(self._gpio_clock, 0) 47 | time.sleep(0) 48 | 49 | def _eof(self): 50 | # Emit exactly enough clock pulses to latch the small dark die APA102s which are weird 51 | # for some reason it takes 36 clocks, the other IC takes just 4 (number of pixels/2) 52 | self._gpio.output(self._gpio_data, 0) 53 | for x in range(36): 54 | self._gpio.output(self._gpio_clock, 1) 55 | time.sleep(0) 56 | self._gpio.output(self._gpio_clock, 0) 57 | time.sleep(0) 58 | 59 | def _sof(self): 60 | self._gpio.output(self._gpio_data, 0) 61 | for x in range(32): 62 | self._gpio.output(self._gpio_clock, 1) 63 | time.sleep(0.) 64 | self._gpio.output(self._gpio_clock, 0) 65 | time.sleep(0) 66 | 67 | def show(self): 68 | """Output the buffer.""" 69 | if not self._gpio_is_setup: 70 | self._gpio.setmode(self._gpio.BCM) 71 | self._gpio.setwarnings(False) 72 | self._gpio.setup(self._gpio_data, self._gpio.OUT) 73 | self._gpio.setup(self._gpio_clock, self._gpio.OUT) 74 | if self._gpio_cs is not None: 75 | self._gpio.setup(self._gpio_cs, self._gpio.OUT) 76 | 77 | self._gpio_is_setup = True 78 | 79 | if self._gpio_cs is not None: 80 | self._gpio.output(self._gpio_cs, 0) 81 | 82 | self._sof() 83 | 84 | for pixel in self._pixels: 85 | r, g, b, brightness = pixel 86 | brightness = int(brightness * 31) & 0x1f 87 | self._write_byte(0b11100000 | brightness) 88 | self._write_byte(b) 89 | self._write_byte(g) 90 | self._write_byte(r) 91 | 92 | self._eof() 93 | 94 | if self._gpio_cs is not None: 95 | self._gpio.output(self._gpio_cs, 1) 96 | -------------------------------------------------------------------------------- /library/plasma/core.py: -------------------------------------------------------------------------------- 1 | """Base class for Plasma LED devices.""" 2 | import atexit 3 | 4 | 5 | class Plasma(): 6 | """Base class for Plasma LED devices.""" 7 | 8 | name = "" 9 | 10 | options = { 11 | 'pixel_count': int, 12 | } 13 | 14 | option_order = [] 15 | 16 | def __init__(self, pixel_count=1): 17 | """Initialise Plasma device. 18 | 19 | :param pixel_count: Number of individual RGB LEDs 20 | 21 | """ 22 | self._pixel_count = pixel_count 23 | self._pixels = [[0, 0, 0, 1.0]] * self._pixel_count 24 | self._clear_on_exit = False 25 | 26 | atexit.register(self.atexit) 27 | 28 | @classmethod 29 | def parse_options(self, options): 30 | """Parse option string into kwargs dict.""" 31 | named_options = {} 32 | for index, option in enumerate(options): 33 | if "=" in option: 34 | k, v = option.split("=") 35 | else: 36 | v = option 37 | k = self.option_order[index] 38 | named_options[k] = self.options[k](v) 39 | 40 | return named_options 41 | 42 | def get_pixel_count(self): 43 | """Get the count of pixels.""" 44 | return self._pixel_count 45 | 46 | def show(self): 47 | """Display changes.""" 48 | raise NotImplementedError 49 | 50 | def atexit(self): 51 | """Clear the display upon exit.""" 52 | if not self._clear_on_exit: 53 | return 54 | self.clear() 55 | self.show() 56 | 57 | def set_pixel_hsv(self, index, h, s, v): 58 | """Set the HSV colour of an individual pixel in your chain.""" 59 | raise NotImplementedError 60 | 61 | def set_clear_on_exit(self, status=True): 62 | """Set if the pixel strip should be cleared upon program exit.""" 63 | self._clear_on_exit = status 64 | 65 | def set_all(self, r, g, b, brightness=None): 66 | """Set the RGB value and optionally brightness of all pixels. 67 | 68 | If you don't supply a brightness value, the last value set for each pixel be kept. 69 | 70 | :param r: Amount of red: 0 to 255 71 | :param g: Amount of green: 0 to 255 72 | :param b: Amount of blue: 0 to 255 73 | :param brightness: Brightness: 0.0 to 1.0 (default is 1.0) 74 | 75 | """ 76 | for x in range(self._pixel_count): 77 | self.set_pixel(x, r, g, b, brightness) 78 | 79 | def set_sequence(self, sequence): 80 | """Set RGB values from a Plasma FX sequence.""" 81 | if type(sequence) is list: 82 | for index, led in enumerate(sequence): 83 | self.set_pixel(index, *led) 84 | elif type(sequence) is dict: 85 | for index, led in sequence.items(): 86 | self.set_pixel(index, *led) 87 | else: 88 | for index, led in sequence: 89 | self.set_pixel(index, *led) 90 | 91 | def get_pixel(self, x): 92 | """Get the RGB and brightness value of a specific pixel. 93 | 94 | :param x: The horizontal position of the pixel: 0 to 7 95 | 96 | """ 97 | r, g, b, brightness = self._pixels[x] 98 | 99 | return r, g, b, brightness 100 | 101 | def set_pixel(self, x, r, g, b, brightness=None): 102 | """Set the RGB value, and optionally brightness, of a single pixel. 103 | 104 | If you don't supply a brightness value, the last value will be kept. 105 | 106 | :param x: The horizontal position of the pixel: 0 to 7 107 | :param r: Amount of red: 0 to 255 108 | :param g: Amount of green: 0 to 255 109 | :param b: Amount of blue: 0 to 255 110 | :param brightness: Brightness: 0.0 to 1.0 (default around 0.2) 111 | 112 | """ 113 | r, g, b = [int(c) & 0xff for c in (r, g, b)] 114 | 115 | if brightness is None: 116 | brightness = self._pixels[x][3] 117 | 118 | self._pixels[x] = [r, g, b, brightness] 119 | 120 | def clear(self): 121 | """Clear the pixel buffer.""" 122 | for x in range(self._pixel_count): 123 | self._pixels[x][0:3] = [0, 0, 0] 124 | 125 | def set_brightness(self, brightness): 126 | """Set the brightness of all pixels. 127 | 128 | :param brightness: Brightness: 0.0 to 1.0 129 | 130 | """ 131 | if brightness < 0 or brightness > 1: 132 | raise ValueError('Brightness should be between 0.0 and 1.0') 133 | 134 | for x in range(self._pixel_count): 135 | self._pixels[x][3] = brightness 136 | -------------------------------------------------------------------------------- /library/plasma/gpio.py: -------------------------------------------------------------------------------- 1 | """Wrapper to provide deprecated PlasmaGPIO output device.""" 2 | from .apa102 import PlasmaAPA102 3 | 4 | 5 | class PlasmaGPIO(PlasmaAPA102): 6 | """Deprecated PlasmaGPIO output device.""" 7 | 8 | pass 9 | -------------------------------------------------------------------------------- /library/plasma/legacy.py: -------------------------------------------------------------------------------- 1 | """Library for the Pimoroni Blinkt! - 8-pixel APA102 LED display.""" 2 | import atexit 3 | import time 4 | 5 | import RPi.GPIO as GPIO 6 | 7 | 8 | __version__ = '0.0.1' 9 | 10 | DAT = 15 11 | CLK = 14 12 | PIXELS_PER_LIGHT = 4 13 | DEFAULT_BRIGHTNESS = 3 14 | MAX_BRIGHTNESS = 3 15 | NUM_PIXELS = 0 16 | 17 | 18 | pixels = [] 19 | 20 | _light_count = 0 21 | _gpio_setup = False 22 | _clear_on_exit = False 23 | 24 | 25 | def _exit(): 26 | if _clear_on_exit: 27 | clear() 28 | show() 29 | GPIO.cleanup() 30 | 31 | 32 | def use_pins(data, clock): 33 | """Set alternate data and clock pins for your Plasma chain.""" 34 | global DAT, CLK, _gpio_setup 35 | if DAT != data or CLK != clock: 36 | _gpio_setup = False 37 | DAT = data 38 | CLK = clock 39 | 40 | 41 | def set_light_count(light_count): 42 | """Set the number of light modules in your Plasma chain.""" 43 | global _light_count, pixels, NUM_PIXELS 44 | _light_count = light_count 45 | NUM_PIXELS = light_count * PIXELS_PER_LIGHT 46 | pixels = [[0, 0, 0, DEFAULT_BRIGHTNESS]] * light_count * PIXELS_PER_LIGHT 47 | 48 | 49 | def set_light(index, r, g, b): 50 | """Set the RGB colour of an individual light in your Plasma chain. 51 | 52 | This will set all four LEDs on the Plasma light to the same colour. 53 | 54 | :param index: Index of the light in your chain (starting at 0) 55 | :param r: Amount of red: 0 to 255 56 | :param g: Amount of green: 0 to 255 57 | :param b: Amount of blue: 0 to 255 58 | 59 | """ 60 | offset = index * 4 61 | for x in range(4): 62 | set_pixel(offset + x, r, g, b) 63 | 64 | 65 | def set_brightness(brightness): 66 | """Set the brightness of all pixels. 67 | 68 | :param brightness: Brightness: 0.0 to 1.0 69 | 70 | """ 71 | if brightness < 0 or brightness > 1: 72 | raise ValueError('Brightness should be between 0.0 and 1.0') 73 | 74 | for x in range(_light_count * PIXELS_PER_LIGHT): 75 | pixels[x][3] = int(float(MAX_BRIGHTNESS) * brightness) & 0b11111 76 | 77 | 78 | def clear(): 79 | """Clear the pixel buffer.""" 80 | for x in range(_light_count * PIXELS_PER_LIGHT): 81 | pixels[x][0:3] = [0, 0, 0] 82 | 83 | 84 | def _write_byte(byte): 85 | for x in range(8): 86 | GPIO.output(DAT, byte & 0b10000000) 87 | GPIO.output(CLK, 1) 88 | time.sleep(0.0000005) 89 | byte <<= 1 90 | GPIO.output(CLK, 0) 91 | time.sleep(0.0000005) 92 | 93 | 94 | # Emit exactly enough clock pulses to latch the small dark die APA102s which are weird 95 | # for some reason it takes 36 clocks, the other IC takes just 4 (number of pixels/2) 96 | def _eof(): 97 | GPIO.output(DAT, 0) 98 | for x in range(36): 99 | GPIO.output(CLK, 1) 100 | time.sleep(0.0000005) 101 | GPIO.output(CLK, 0) 102 | time.sleep(0.0000005) 103 | 104 | 105 | def _sof(): 106 | GPIO.output(DAT, 0) 107 | for x in range(32): 108 | GPIO.output(CLK, 1) 109 | time.sleep(0.0000005) 110 | GPIO.output(CLK, 0) 111 | time.sleep(0.0000005) 112 | 113 | 114 | def show(): 115 | """Output the buffer to Blinkt!.""" 116 | global _gpio_setup 117 | 118 | if not _gpio_setup: 119 | GPIO.setmode(GPIO.BCM) 120 | GPIO.setwarnings(False) 121 | GPIO.setup(DAT, GPIO.OUT) 122 | GPIO.setup(CLK, GPIO.OUT) 123 | atexit.register(_exit) 124 | _gpio_setup = True 125 | 126 | _sof() 127 | 128 | for pixel in pixels: 129 | r, g, b, brightness = pixel 130 | _write_byte(0b11100000 | brightness) 131 | _write_byte(b) 132 | _write_byte(g) 133 | _write_byte(r) 134 | 135 | _eof() 136 | 137 | 138 | def set_all(r, g, b, brightness=None): 139 | """Set the RGB value and optionally brightness of all pixels. 140 | 141 | If you don't supply a brightness value, the last value set for each pixel be kept. 142 | 143 | :param r: Amount of red: 0 to 255 144 | :param g: Amount of green: 0 to 255 145 | :param b: Amount of blue: 0 to 255 146 | :param brightness: Brightness: 0.0 to 1.0 (default is 1.0) 147 | 148 | """ 149 | for x in range(_light_count * PIXELS_PER_LIGHT): 150 | set_pixel(x, r, g, b, brightness) 151 | 152 | 153 | def get_pixel(x): 154 | """Get the RGB and brightness value of a specific pixel. 155 | 156 | :param x: The horizontal position of the pixel: 0 to 7 157 | 158 | """ 159 | r, g, b, brightness = pixels[x] 160 | brightness /= float(MAX_BRIGHTNESS) 161 | 162 | return r, g, b, round(brightness, 3) 163 | 164 | 165 | def set_pixel(x, r, g, b, brightness=None): 166 | """Set the RGB value, and optionally brightness, of a single pixel. 167 | 168 | If you don't supply a brightness value, the last value will be kept. 169 | 170 | :param x: The horizontal position of the pixel: 0 to 7 171 | :param r: Amount of red: 0 to 255 172 | :param g: Amount of green: 0 to 255 173 | :param b: Amount of blue: 0 to 255 174 | :param brightness: Brightness: 0.0 to 1.0 (default around 0.2) 175 | 176 | """ 177 | if brightness is None: 178 | brightness = pixels[x][3] 179 | else: 180 | brightness = int(float(MAX_BRIGHTNESS) * brightness) & 0b11111 181 | 182 | pixels[x] = [int(r) & 0xff, int(g) & 0xff, int(b) & 0xff, brightness] 183 | 184 | 185 | def set_clear_on_exit(value=True): 186 | """Set whether Plasma Lights should be cleared upon exit. 187 | 188 | By default Plasma will not turn off the pixels on exit, but calling:: 189 | 190 | plasma.set_clear_on_exit(True) 191 | 192 | Will ensure that it does. 193 | 194 | :param value: True or False (default True) 195 | 196 | """ 197 | global _clear_on_exit 198 | _clear_on_exit = value 199 | -------------------------------------------------------------------------------- /library/plasma/matrix.py: -------------------------------------------------------------------------------- 1 | """Combine multiple LED strip types into a single logical strip.""" 2 | import pathlib 3 | import yaml 4 | 5 | 6 | class PlasmaMatrix(): 7 | """Combine multiple LED strip types into a single logical strip.""" 8 | 9 | def __init__(self, config_file=None): 10 | """Initialise a matrix. 11 | 12 | :param config_file: Path to yml configuration file 13 | 14 | """ 15 | self._devices = {} 16 | 17 | if type(config_file) is str: 18 | config_file = pathlib.Path(config_file) 19 | 20 | if not config_file.is_file(): 21 | raise ValueError(f"Could not find {config_file}") 22 | 23 | self._config = yaml.safe_load(open(config_file, "r")) 24 | 25 | for required in ("pixels", "devices"): 26 | if required not in self._config: 27 | raise ValueError(f"Config missing required setting: '{required}'") 28 | 29 | self._pixel_count = int(self._config["pixels"]) 30 | 31 | for output_name, output_options in self._config["devices"].items(): 32 | enabled = output_options.get("enabled", True) 33 | if not enabled: 34 | continue 35 | 36 | output_type = output_options.get("type") 37 | pixels = output_options.get("pixels", self._pixel_count) 38 | offset = output_options.get("offset", 0) 39 | 40 | del output_options["type"] 41 | 42 | try: 43 | del output_options["enabled"] 44 | except KeyError: 45 | pass 46 | 47 | try: 48 | del output_options["pixels"] 49 | except KeyError: 50 | pass 51 | 52 | try: 53 | del output_options["offset"] 54 | except KeyError: 55 | pass 56 | 57 | output_device = self.get_output_device(output_type) 58 | 59 | self._devices[output_name] = { 60 | 'offset': offset, 61 | 'type': output_type, 62 | 'device': output_device(pixels, **output_options) 63 | } 64 | 65 | def get_pixel_count(self): 66 | """Get the count of pixels.""" 67 | return self._pixel_count 68 | 69 | def get_device_count(self): 70 | """Get the count of output devices.""" 71 | return len(self._devices) 72 | 73 | def get_device(self, index): 74 | """Get a device by name or type. 75 | 76 | If a string (type) is given, it will try to find a device with that name. 77 | 78 | Otherwise returns first device of that type. 79 | 80 | """ 81 | if index in self._devices.keys(): 82 | return self._devices[index].get('device') 83 | 84 | if type(index) == str: 85 | for n, d in self._devices.items(): 86 | if d.get('type') == index: 87 | return d.get('device') 88 | 89 | raise ValueError(f"Invalid device index {index}") 90 | 91 | def get_output_device(self, output_type): 92 | """Get output class by name.""" 93 | output_type = output_type.upper() 94 | if output_type in ["GPIO", "APA102"]: 95 | from .gpio import PlasmaGPIO 96 | return PlasmaGPIO 97 | if output_type in ["USB", "SERIAL"]: 98 | from .usb import PlasmaSerial 99 | return PlasmaSerial 100 | if output_type == "WS281X": 101 | from .ws281x import PlasmaWS281X 102 | return PlasmaWS281X 103 | raise ValueError(f"Invalid output type {output_type}!") 104 | 105 | def set_light(self, index, r, g, b, brightness=None): 106 | """Set the RGB colour of an individual light in your matrix.""" 107 | raise NotImplementedError("Use get_device(index).set_light()") 108 | 109 | def set_all(self, r, g, b, brightness=None): 110 | """Set the RGB value and optionally brightness of all pixels.""" 111 | for n, d in self._devices.items(): 112 | d.get('device').set_all(r, g, b, brightness=brightness) 113 | 114 | def set_sequence(self, sequence): 115 | """Set all LEDs from a buffer of individual colours.""" 116 | if type(sequence) is list: 117 | for index, led in enumerate(sequence): 118 | self.set_pixel(index, *led) 119 | elif type(sequence) is dict: 120 | for index, led in sequence.items(): 121 | self.set_pixel(index, *led) 122 | else: 123 | for index, led in sequence: 124 | self.set_pixel(index, *led) 125 | 126 | def get_pixel(self, x): 127 | """Get the RGB and brightness value of a specific pixel.""" 128 | for n, d in self._devices.items(): 129 | device = d.get('device') 130 | offset = d.get('offset') 131 | count = device.get_pixel_count() 132 | 133 | if x >= offset and x < offset + count: 134 | return device.get_pixel(x - offset) 135 | 136 | def set_pixel(self, x, r, g, b, brightness=None): 137 | """Set the RGB value, and optionally brightness, of a single pixel.""" 138 | for n, d in self._devices.items(): 139 | device = d.get('device') 140 | offset = d.get('offset') 141 | count = device.get_pixel_count() 142 | 143 | if x >= offset and x < offset + count: 144 | device.set_pixel(x - offset, r, g, b, brightness=brightness) 145 | 146 | def show(self): 147 | """Display lights across devices. 148 | 149 | Sets each device in turn, picking the pixels 150 | from the buffer starting from the specified offset. 151 | 152 | """ 153 | for n, d in self._devices.items(): 154 | d.get('device').show() 155 | -------------------------------------------------------------------------------- /library/plasma/serial.py: -------------------------------------------------------------------------------- 1 | """Serial class for Plasma light devices over USB Serial/UART.""" 2 | from .core import Plasma 3 | from serial import Serial 4 | 5 | 6 | class PlasmaSerial(Plasma): 7 | """Serial class for Plasma light devices over USB Serial/UART.""" 8 | 9 | name = "Serial" 10 | 11 | options = { 12 | 'pixel_count': int, 13 | "port": str 14 | } 15 | 16 | option_order = ("port", ) 17 | 18 | def __init__(self, pixel_count=1, port='/dev/ttyAMA0', baudrate=115200): 19 | """Initialise Serial device. 20 | 21 | :param pixel_count: Number of individual RGB LEDs 22 | :param port: Serial port name/path 23 | :param baudrate: Serial baud rate 24 | 25 | """ 26 | self._serial_port = port 27 | self._serial = Serial(port, baudrate=baudrate) 28 | Plasma.__init__(self, pixel_count) 29 | 30 | def show(self): 31 | """Display current buffer on LEDs.""" 32 | sof = b"LEDS" 33 | eof = b"\x00\x00\x00\xff" 34 | 35 | pixels = [] 36 | for pixel in self._pixels: 37 | r, g, b, brightness = pixel 38 | pixels += [r, g, b, int(brightness * 254)] 39 | 40 | self._serial.write(sof + bytearray(pixels) + eof) 41 | self._serial.flush() 42 | -------------------------------------------------------------------------------- /library/plasma/usb.py: -------------------------------------------------------------------------------- 1 | """Wrapper around PlasmaSerial to provide deprecated PlasmaUSB output device.""" 2 | from .serial import PlasmaSerial 3 | 4 | 5 | class PlasmaUSB(PlasmaSerial): 6 | """Deprecated PlasmaUSB output device.""" 7 | 8 | pass 9 | -------------------------------------------------------------------------------- /library/plasma/ws281x.py: -------------------------------------------------------------------------------- 1 | """Class for Plasma light devices in the WS281X/SK6812 family.""" 2 | from .core import Plasma 3 | 4 | 5 | class PlasmaWS281X(Plasma): 6 | """Class for Plasma light devices in the WS281X/SK6812 family.""" 7 | 8 | name = "WS281X" 9 | 10 | options = { 11 | 'pixel_count': int, 12 | "gpio_pin": int, 13 | "strip_type": str, 14 | "channel": int, 15 | "brightness": int, 16 | "freq_hz": int, 17 | "dma": int, 18 | "invert": bool 19 | } 20 | 21 | option_order = ("gpio_pin", "strip_type", "channel", "brightness", "freq_hz", "dma", "invert") 22 | 23 | def __init__(self, pixel_count=1, gpio_pin=13, strip_type='WS2812', channel=None, brightness=255, freq_hz=800000, dma=10, invert=False): 24 | """Initialise WS281X device. 25 | 26 | :param pixel_count: Number of individual RGB LEDs 27 | :param gpio_pin: BCM GPIO pin for output signal 28 | :param strip_type: Strip type: one of WS2812 or SK6812 29 | :param channel: LED channel (0 or 1, or None for automatic) 30 | :param brightness: Global WS281X LED brightness scale 31 | :param freq_hz: WS281X output signal frequency (usually 800khz) 32 | :param dma: DMA channel 33 | :param invert: Invert signals for NPN-transistor based level shifters 34 | 35 | """ 36 | from rpi_ws281x import PixelStrip, ws 37 | 38 | strip_types = {} 39 | for t in ws.__dict__: 40 | if '_STRIP' in t: 41 | k = t.replace('_STRIP', '') 42 | v = getattr(ws, t) 43 | strip_types[k] = v 44 | 45 | strip_type = strip_types[strip_type] 46 | 47 | if channel is None: 48 | if gpio_pin in [13]: 49 | channel = 1 50 | elif gpio_pin in [12, 18]: 51 | channel = 0 52 | 53 | self._strip = PixelStrip(pixel_count, gpio_pin, freq_hz, dma, invert, brightness, channel, strip_type) 54 | self._strip.begin() 55 | 56 | Plasma.__init__(self, pixel_count) 57 | 58 | def show(self): 59 | """Output the buffer.""" 60 | for i in range(self._strip.numPixels()): 61 | r, g, b, brightness = self._pixels[i] 62 | self._strip.setPixelColorRGB(i, r, g, b) 63 | 64 | self._strip.show() 65 | -------------------------------------------------------------------------------- /library/setup.cfg: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | [metadata] 3 | name = plasmalights 4 | version = 2.0.2 5 | author = Philip Howard 6 | author_email = phil@pimoroni.com 7 | description = Python library for driving Pimoroni Plasma and other LED devices 8 | long_description = file: README.md 9 | long_description_content_type = text/markdown 10 | keywords = Rasperry Pi LED 11 | url = https://www.pimoroni.com 12 | project_urls = 13 | GitHub=https://www.github.com/pimoroni/plasma 14 | license = MIT 15 | license_files = LICENSE.txt 16 | classifiers = 17 | Development Status :: 4 - Beta 18 | Operating System :: POSIX :: Linux 19 | License :: OSI Approved :: MIT License 20 | Intended Audience :: Developers 21 | Programming Language :: Python :: 3.7 22 | Topic :: Software Development 23 | Topic :: Software Development :: Libraries 24 | Topic :: System :: Hardware 25 | 26 | [options] 27 | packages = plasma 28 | python_requires = >= 3.7 29 | install_requires = 30 | pyyaml 31 | pyserial 32 | 33 | [flake8] 34 | exclude = 35 | test.py 36 | tests/ 37 | .tox, 38 | .eggs, 39 | .git, 40 | __pycache__, 41 | build, 42 | dist 43 | ignore = 44 | E501 45 | D100 46 | -------------------------------------------------------------------------------- /library/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Copyright (c) 2020 Pimoroni. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 | of the Software, and to permit persons to whom the Software is furnished to do 11 | so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | 25 | from setuptools import setup 26 | 27 | setup() 28 | -------------------------------------------------------------------------------- /library/tests/conftest.py: -------------------------------------------------------------------------------- 1 | """Test configuration. 2 | 3 | These allow the mocking of various Python modules 4 | that might otherwise have runtime side-effects. 5 | """ 6 | import os 7 | import sys 8 | import mock 9 | import pytest 10 | import pathlib 11 | import tempfile 12 | 13 | 14 | @pytest.fixture(scope='function', autouse=True) 15 | def cleanup_plasma(): 16 | """This fixture removes all plasma modules from sys.modules. 17 | 18 | This ensures that each module is fully re-imported, along with 19 | the fixtures for serial, etc, for each test function. 20 | 21 | """ 22 | 23 | yield None 24 | to_delete = [] 25 | for module in sys.modules: 26 | if module.startswith('plasma'): 27 | to_delete.append(module) 28 | 29 | for module in to_delete: 30 | del sys.modules[module] 31 | 32 | 33 | @pytest.fixture(scope='function', autouse=False) 34 | def GPIO(): 35 | """Mock RPi.GPIO module.""" 36 | 37 | GPIO = mock.MagicMock() 38 | # Fudge for Python < 37 (possibly earlier) 39 | sys.modules['RPi'] = mock.Mock() 40 | sys.modules['RPi'].GPIO = GPIO 41 | sys.modules['RPi.GPIO'] = GPIO 42 | yield GPIO 43 | del sys.modules['RPi'] 44 | del sys.modules['RPi.GPIO'] 45 | 46 | 47 | @pytest.fixture(scope='function', autouse=False) 48 | def serial(): 49 | """Mock serial (pyserial) module.""" 50 | 51 | serial = mock.MagicMock() 52 | sys.modules['serial'] = serial 53 | yield serial 54 | del sys.modules['serial'] 55 | 56 | 57 | @pytest.fixture(scope='function', autouse=False) 58 | def rpi_ws281x(): 59 | """Mock rpi_ws281x module.""" 60 | 61 | rpi_ws281x = mock.MagicMock() 62 | # Fake the WS2812_STRIP constant that's loaded into the strip_types dict 63 | rpi_ws281x.ws.WS2812_STRIP = 0 64 | sys.modules['rpi_ws281x'] = rpi_ws281x 65 | yield rpi_ws281x 66 | del sys.modules['rpi_ws281x'] 67 | 68 | 69 | @pytest.fixture(scope='function', autouse=False) 70 | def config_file(): 71 | """Temporary config file.""" 72 | file = tempfile.NamedTemporaryFile(delete=False) 73 | file.write(b"""pixels: 100 74 | devices: 75 | TABLE: 76 | type: WS281X 77 | pixels: 30 78 | offset: 0 79 | gpio_pin: 1 80 | strip_type: WS2812 81 | WALL: 82 | type: APA102 83 | pixels: 30 84 | offset: 30 85 | gpio_data: 10 86 | gpio_clock: 11 87 | BACKLIGHT: 88 | type: SERIAL 89 | pixels: 40 90 | offset: 60 91 | port: /dev/ttyAMA0 92 | """) 93 | file.flush() 94 | yield pathlib.Path(file.name) 95 | file.close() 96 | 97 | 98 | 99 | @pytest.fixture(scope='function', autouse=False) 100 | def config_file_default_pixels_and_offset(): 101 | """Temporary config file.""" 102 | file = tempfile.NamedTemporaryFile(delete=False) 103 | file.write(b"""pixels: 100 104 | devices: 105 | TABLE: 106 | type: APA102 107 | gpio_data: 10 108 | gpio_clock: 11 109 | """) 110 | file.flush() 111 | yield pathlib.Path(file.name) 112 | file.close() 113 | 114 | 115 | @pytest.fixture(scope='function', autouse=False) 116 | def argv(): 117 | """Replace sys.argv to avoid feeding Plasma auto the test args.""" 118 | argv = sys.argv 119 | sys.argv = [] 120 | yield 121 | sys.argv = argv 122 | 123 | 124 | @pytest.fixture(scope='function', autouse=False) 125 | def argv_valid(): 126 | """Replace sys.argv to avoid feeding Plasma auto the test args.""" 127 | argv = sys.argv 128 | sys.argv = ["", "APA102:14:15"] 129 | yield 130 | sys.argv = argv 131 | -------------------------------------------------------------------------------- /library/tests/test_apa102.py: -------------------------------------------------------------------------------- 1 | """Test Plasma APA102 initialisation.""" 2 | import mock 3 | 4 | 5 | def test_apa102_setup(GPIO): 6 | """Test init succeeds and GPIO pins are setup.""" 7 | from plasma.apa102 import PlasmaAPA102 8 | plasma = PlasmaAPA102(10, gpio_data=10, gpio_clock=11) 9 | plasma.show() 10 | 11 | GPIO.setmode.assert_called_once_with(GPIO.BCM) 12 | GPIO.setup.assert_has_calls([ 13 | mock.call(10, GPIO.OUT), 14 | mock.call(11, GPIO.OUT) 15 | ]) 16 | 17 | 18 | def test_apa102_set_pixel(GPIO): 19 | """Test a pixel can be set.""" 20 | from plasma.apa102 import PlasmaAPA102 21 | plasma = PlasmaAPA102(10, gpio_data=10, gpio_clock=11) 22 | plasma.set_pixel(0, 255, 0, 255) 23 | 24 | assert plasma.get_pixel(0) == (255, 0, 255, 1.0) 25 | 26 | 27 | def test_apa102_set_all(GPIO): 28 | """Test a pixel can be set.""" 29 | from plasma.apa102 import PlasmaAPA102 30 | plasma = PlasmaAPA102(10, gpio_data=10, gpio_clock=11) 31 | plasma.set_all(255, 0, 255) 32 | 33 | assert plasma.get_pixel(0) == (255, 0, 255, 1.0) 34 | 35 | 36 | def test_apa102_clear(GPIO): 37 | """Test all pixels are cleared.""" 38 | from plasma.apa102 import PlasmaAPA102 39 | plasma = PlasmaAPA102(10, gpio_data=10, gpio_clock=11) 40 | plasma.set_all(255, 0, 255) 41 | plasma.clear() 42 | 43 | assert plasma.get_pixel(0) == (0, 0, 0, 1.0) 44 | 45 | 46 | def test_apa102_set_brightness(GPIO): 47 | """Test brightness is set.""" 48 | from plasma.apa102 import PlasmaAPA102 49 | plasma = PlasmaAPA102(10, gpio_data=10, gpio_clock=11) 50 | plasma.set_all(255, 0, 255) 51 | plasma.set_brightness(0.5) 52 | 53 | assert plasma.get_pixel(0) == (255, 0, 255, 0.5) 54 | 55 | 56 | def test_matrix_set_sequence_dict(config_file, GPIO, rpi_ws281x, serial): 57 | from plasma.apa102 import PlasmaAPA102 58 | plasma = PlasmaAPA102(10, gpio_data=10, gpio_clock=11) 59 | plasma.set_sequence({ 60 | 0: (255, 0, 0), 61 | 2: (0, 255, 0), 62 | 4: (0, 0, 255) 63 | }) 64 | 65 | assert plasma.get_pixel(0) == (255, 0, 0, 1.0) 66 | assert plasma.get_pixel(2) == (0, 255, 0, 1.0) 67 | assert plasma.get_pixel(4) == (0, 0, 255, 1.0) 68 | 69 | 70 | def test_matrix_set_sequence_list(config_file, GPIO, rpi_ws281x, serial): 71 | from plasma.apa102 import PlasmaAPA102 72 | plasma = PlasmaAPA102(10, gpio_data=10, gpio_clock=11) 73 | plasma.set_sequence([ 74 | (255, 0, 0), 75 | (0, 255, 0), 76 | (0, 0, 255) 77 | ]) 78 | 79 | assert plasma.get_pixel(0) == (255, 0, 0, 1.0) 80 | assert plasma.get_pixel(1) == (0, 255, 0, 1.0) 81 | assert plasma.get_pixel(2) == (0, 0, 255, 1.0) 82 | 83 | 84 | def test_apa102_parse_options(): 85 | from plasma.apa102 import PlasmaAPA102 86 | 87 | options = PlasmaAPA102.parse_options(["10", "11"]) 88 | assert "gpio_data" in options 89 | assert "gpio_clock" in options 90 | 91 | options = PlasmaAPA102.parse_options(["gpio_data=10", "gpio_clock=11"]) 92 | assert "gpio_data" in options 93 | assert "gpio_clock" in options 94 | -------------------------------------------------------------------------------- /library/tests/test_auto.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_auto_apa102(argv, GPIO): 5 | from plasma import auto 6 | from plasma.apa102 import PlasmaAPA102 7 | 8 | plasma = auto("APA102:14:15") 9 | 10 | assert isinstance(plasma, PlasmaAPA102) 11 | 12 | 13 | def test_get_device_raises_valueerror(argv): 14 | from plasma import auto 15 | with pytest.raises(ValueError): 16 | auto() 17 | 18 | 19 | def test_get_device_from_argv(argv_valid, GPIO): 20 | from plasma import auto 21 | from plasma.apa102 import PlasmaAPA102 22 | 23 | plasma = auto("XXXXX") 24 | 25 | assert isinstance(plasma, PlasmaAPA102) 26 | 27 | 28 | def test_get_device_from_config(config_file, GPIO, rpi_ws281x, serial): 29 | from plasma import auto 30 | from plasma.matrix import PlasmaMatrix 31 | from plasma.apa102 import PlasmaAPA102 32 | 33 | plasma = auto("XXXXX", config_file) 34 | 35 | assert isinstance(plasma, PlasmaMatrix) 36 | assert isinstance(plasma.get_device("WALL"), PlasmaAPA102) 37 | 38 | -------------------------------------------------------------------------------- /library/tests/test_fx.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_fx_cycle(argv, GPIO): 5 | """Test that set_sequence supports the output of a PlasmaFX Sequence""" 6 | from plasma import auto 7 | from plasma.apa102 import PlasmaAPA102 8 | from plasmafx import Sequence 9 | from plasmafx.plugins import FXCycle 10 | 11 | sequence = Sequence(10) 12 | sequence.set_plugin(0, FXCycle()) 13 | 14 | plasma = auto("APA102:14:15:pixel_count=10") 15 | 16 | plasma.set_sequence(sequence.get_pixels()) 17 | 18 | assert isinstance(plasma, PlasmaAPA102) -------------------------------------------------------------------------------- /library/tests/test_get_device.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_get_device_matrix(config_file): 5 | from plasma import get_device 6 | 7 | device, args = get_device(config_file) 8 | 9 | 10 | def test_get_device_raises_valueerror(): 11 | from plasma import get_device 12 | with pytest.raises(ValueError): 13 | get_device("XXXXXX") 14 | 15 | 16 | def test_get_device_apa102(): 17 | from plasma import get_device 18 | from plasma.apa102 import PlasmaAPA102 19 | 20 | device, args = get_device("APA102:14:15") 21 | 22 | assert device == PlasmaAPA102 23 | 24 | 25 | def test_get_device_ws281x(): 26 | from plasma import get_device 27 | from plasma.ws281x import PlasmaWS281X 28 | 29 | device, args = get_device("WS281X:13:WS2812") 30 | 31 | assert device == PlasmaWS281X 32 | 33 | 34 | def test_get_device_serial(serial): 35 | from plasma import get_device 36 | from plasma.serial import PlasmaSerial 37 | 38 | device, args = get_device("SERIAL:/dev/ttyACM0") 39 | 40 | assert device == PlasmaSerial 41 | -------------------------------------------------------------------------------- /library/tests/test_legacy_gpio.py: -------------------------------------------------------------------------------- 1 | """Test Plasma GPIO (APA102 wrapper) initialisation.""" 2 | import mock 3 | 4 | 5 | def test_legacy_gpio_setup(GPIO): 6 | """Test init succeeds and GPIO pins are setup.""" 7 | from plasma.gpio import PlasmaGPIO 8 | plasma = PlasmaGPIO(10, gpio_data=10, gpio_clock=11) 9 | plasma.show() 10 | 11 | GPIO.setmode.assert_called_once_with(GPIO.BCM) 12 | GPIO.setup.assert_has_calls([ 13 | mock.call(10, GPIO.OUT), 14 | mock.call(11, GPIO.OUT) 15 | ]) 16 | 17 | 18 | def test_legacy_gpio_parse_options(): 19 | from plasma.gpio import PlasmaGPIO 20 | 21 | options = PlasmaGPIO.parse_options(["10", "11"]) 22 | assert "gpio_data" in options 23 | assert "gpio_clock" in options 24 | 25 | options = PlasmaGPIO.parse_options(["gpio_data=10", "gpio_clock=11"]) 26 | assert "gpio_data" in options 27 | assert "gpio_clock" in options 28 | -------------------------------------------------------------------------------- /library/tests/test_matrix.py: -------------------------------------------------------------------------------- 1 | def test_matrix_setup(config_file, GPIO, rpi_ws281x, serial): 2 | from plasma.matrix import PlasmaMatrix 3 | plasma = PlasmaMatrix(config_file) 4 | assert plasma.get_pixel_count() == 100 5 | assert plasma.get_device_count() == 3 6 | 7 | 8 | def test_matrix_set_pixel(config_file, GPIO, rpi_ws281x, serial): 9 | from plasma.matrix import PlasmaMatrix 10 | plasma = PlasmaMatrix(config_file) 11 | plasma.set_pixel(0, 255, 0, 0) # First pixel of the WS281X 12 | plasma.set_pixel(30, 0, 255, 0) # First pixel of the APA102 13 | plasma.set_pixel(60, 0, 0, 255) # First pixel of the SERIAL 14 | 15 | assert plasma.get_device("WS281X").get_pixel(0) == (255, 0, 0, 1.0) 16 | assert plasma.get_device("APA102").get_pixel(0) == (0, 255, 0, 1.0) 17 | assert plasma.get_device("SERIAL").get_pixel(0) == (0, 0, 255, 1.0) 18 | 19 | 20 | def test_matrix_set_all(config_file, GPIO, rpi_ws281x, serial): 21 | from plasma.matrix import PlasmaMatrix 22 | plasma = PlasmaMatrix(config_file) 23 | plasma.set_all(255, 0, 0) 24 | 25 | assert plasma.get_device("WS281X").get_pixel(0) == (255, 0, 0, 1.0) 26 | assert plasma.get_device("APA102").get_pixel(0) == (255, 0, 0, 1.0) 27 | assert plasma.get_device("SERIAL").get_pixel(0) == (255, 0, 0, 1.0) 28 | 29 | 30 | def test_matrix_set_sequence_dict(config_file, GPIO, rpi_ws281x, serial): 31 | from plasma.matrix import PlasmaMatrix 32 | plasma = PlasmaMatrix(config_file) 33 | plasma.set_sequence({ 34 | 0: (255, 0, 0), 35 | 2: (0, 255, 0), 36 | 4: (0, 0, 255) 37 | }) 38 | 39 | assert plasma.get_device("WS281X").get_pixel(0) == (255, 0, 0, 1.0) 40 | assert plasma.get_device("WS281X").get_pixel(2) == (0, 255, 0, 1.0) 41 | assert plasma.get_device("WS281X").get_pixel(4) == (0, 0, 255, 1.0) 42 | 43 | 44 | def test_matrix_set_sequence_list(config_file, GPIO, rpi_ws281x, serial): 45 | from plasma.matrix import PlasmaMatrix 46 | plasma = PlasmaMatrix(config_file) 47 | plasma.set_sequence([ 48 | (255, 0, 0), 49 | (0, 255, 0), 50 | (0, 0, 255) 51 | ]) 52 | 53 | assert plasma.get_device("WS281X").get_pixel(0) == (255, 0, 0, 1.0) 54 | assert plasma.get_device("WS281X").get_pixel(1) == (0, 255, 0, 1.0) 55 | assert plasma.get_device("WS281X").get_pixel(2) == (0, 0, 255, 1.0) 56 | 57 | 58 | def test_matrix_get_pixel(config_file, GPIO, rpi_ws281x, serial): 59 | from plasma.matrix import PlasmaMatrix 60 | plasma = PlasmaMatrix(config_file) 61 | plasma.set_pixel(0, 255, 0, 0) # First pixel of the WS281X 62 | plasma.set_pixel(30, 0, 255, 0) # First pixel of the APA102 63 | plasma.set_pixel(60, 0, 0, 255) # First pixel of the SERIAL 64 | 65 | assert plasma.get_pixel(0) == (255, 0, 0, 1.0) 66 | assert plasma.get_pixel(30) == (0, 255, 0, 1.0) 67 | assert plasma.get_pixel(60) == (0, 0, 255, 1.0) 68 | 69 | 70 | def test_matrix_get_device(config_file, GPIO, rpi_ws281x, serial): 71 | from plasma.matrix import PlasmaMatrix 72 | plasma = PlasmaMatrix(config_file) 73 | plasma.set_pixel(0, 255, 0, 0) # First pixel of the WS281X 74 | plasma.set_pixel(30, 0, 255, 0) # First pixel of the APA102 75 | plasma.set_pixel(60, 0, 0, 255) # First pixel of the SERIAL 76 | 77 | assert plasma.get_device("WS281X").get_pixel(0) == (255, 0, 0, 1.0) 78 | assert plasma.get_device("APA102").get_pixel(0) == (0, 255, 0, 1.0) 79 | assert plasma.get_device("SERIAL").get_pixel(0) == (0, 0, 255, 1.0) 80 | 81 | assert plasma.get_device("TABLE").get_pixel(0) == (255, 0, 0, 1.0) 82 | assert plasma.get_device("WALL").get_pixel(0) == (0, 255, 0, 1.0) 83 | assert plasma.get_device("BACKLIGHT").get_pixel(0) == (0, 0, 255, 1.0) 84 | 85 | 86 | def test_matrix_get_device_defaults(config_file_default_pixels_and_offset, GPIO, rpi_ws281x, serial): 87 | from plasma.matrix import PlasmaMatrix 88 | plasma = PlasmaMatrix(config_file_default_pixels_and_offset) 89 | plasma.set_pixel(0, 255, 0, 0) # First pixel of the WS281X 90 | plasma.set_pixel(30, 0, 255, 0) # First pixel of the APA102 91 | plasma.set_pixel(60, 0, 0, 255) # First pixel of the SERIAL 92 | 93 | 94 | def test_matrix_show(config_file, GPIO, rpi_ws281x, serial): 95 | from plasma.matrix import PlasmaMatrix 96 | plasma = PlasmaMatrix(config_file) 97 | plasma.show() 98 | 99 | rpi_ws281x.PixelStrip.assert_called_once() 100 | rpi_ws281x.PixelStrip().show.assert_called_once() 101 | 102 | serial.Serial().write.assert_called_once() 103 | -------------------------------------------------------------------------------- /library/tests/test_serial.py: -------------------------------------------------------------------------------- 1 | """Test Plasma Serial initialisation.""" 2 | import mock 3 | 4 | 5 | def test_serial_setup(serial): 6 | """Test init succeeds and GPIO pins are setup.""" 7 | from plasma.serial import PlasmaSerial 8 | plasma = PlasmaSerial(10, port="/dev/ttyAMA0", baudrate=8000) 9 | plasma.show() 10 | 11 | serial.Serial.assert_called_once_with("/dev/ttyAMA0", baudrate=8000) 12 | 13 | 14 | def test_serial_parse_options(serial): 15 | from plasma.serial import PlasmaSerial 16 | 17 | options = PlasmaSerial.parse_options(["/dev/ttyAMA0"]) 18 | assert "port" in options 19 | 20 | options = PlasmaSerial.parse_options(["port=/dev/ttyAMA0"]) 21 | assert "port" in options 22 | -------------------------------------------------------------------------------- /library/tests/test_setup.py: -------------------------------------------------------------------------------- 1 | """Test Plasma basic initialisation.""" 2 | import mock 3 | 4 | 5 | def test_legacy_setup(GPIO): 6 | """Test init succeeds and GPIO pins are setup.""" 7 | from plasma import legacy as plasma 8 | plasma.show() 9 | 10 | GPIO.setmode.assert_called_once_with(GPIO.BCM) 11 | GPIO.setup.assert_has_calls([ 12 | mock.call(plasma.DAT, GPIO.OUT), 13 | mock.call(plasma.CLK, GPIO.OUT) 14 | ]) 15 | -------------------------------------------------------------------------------- /library/tests/test_ws281x.py: -------------------------------------------------------------------------------- 1 | """Test Plasma WS281X initialisation.""" 2 | import mock 3 | 4 | 5 | def test_apa102_setup(rpi_ws281x): 6 | """Test init succeeds and GPIO pins are setup.""" 7 | from plasma.ws281x import PlasmaWS281X 8 | 9 | plasma = PlasmaWS281X(10) 10 | plasma.show() 11 | 12 | rpi_ws281x.PixelStrip.assert_called_once() 13 | 14 | 15 | def test_apa102_parse_options(rpi_ws281x): 16 | from plasma.ws281x import PlasmaWS281X 17 | 18 | options = PlasmaWS281X.parse_options(["13", "WS2812", "1"]) 19 | assert "gpio_pin" in options 20 | assert "strip_type" in options 21 | assert "channel" in options 22 | 23 | options = PlasmaWS281X.parse_options(["brightness=50", "freq_hz=800000"]) 24 | assert "brightness" in options 25 | assert "freq_hz" in options 26 | -------------------------------------------------------------------------------- /library/tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py{37,39},qa 3 | skip_missing_interpreters = True 4 | 5 | [testenv] 6 | commands = 7 | python setup.py install 8 | pip install ../fx/plasmafx/ 9 | pip install ../fx/plasmafx_plugin_cycle/ 10 | coverage run -m py.test -v -r wsx 11 | coverage report 12 | deps = 13 | mock 14 | pytest>=3.1 15 | pytest-cov 16 | 17 | [testenv:qa] 18 | commands = 19 | flake8 20 | flake8 ../examples/ 21 | python setup.py sdist bdist_wheel 22 | twine check dist/* 23 | deps = 24 | flake8 25 | flake8-docstrings 26 | twine 27 | -------------------------------------------------------------------------------- /packaging/CHANGELOG: -------------------------------------------------------------------------------- 1 | plasmalights (0.0.1) stable; urgency=low 2 | 3 | * Initial Release 4 | 5 | -- Phil Howard Wed, 07 Nov 2018 00:00:00 +0000 6 | -------------------------------------------------------------------------------- /packaging/debian/README: -------------------------------------------------------------------------------- 1 | README 2 | 3 | Plasma is an arcade button lighting system for your Raspberry Pi. 4 | 5 | Learn more: https://shop.pimoroni.com/products/plasma 6 | For examples run: `curl -sS get.pimoroni.com/plasma | bash` 7 | -------------------------------------------------------------------------------- /packaging/debian/changelog: -------------------------------------------------------------------------------- 1 | plasmalights (0.0.1) stable; urgency=low 2 | 3 | * Initial Release 4 | 5 | -- Phil Howard Wed, 07 Nov 2018 00:00:00 +0000 6 | -------------------------------------------------------------------------------- /packaging/debian/clean: -------------------------------------------------------------------------------- 1 | *.egg-info/* 2 | -------------------------------------------------------------------------------- /packaging/debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /packaging/debian/control: -------------------------------------------------------------------------------- 1 | Source: plasmalights 2 | Maintainer: Phil Howard 3 | Homepage: https://github.com/pimoroni/plasma 4 | Section: python 5 | Priority: extra 6 | Build-Depends: debhelper (>= 9.0.0), dh-python, python-all (>= 2.7), python-setuptools, python3-all (>= 3.4), python3-setuptools 7 | Standards-Version: 3.9.6 8 | X-Python-Version: >= 2.7 9 | X-Python3-Version: >= 3.4 10 | 11 | Package: python-plasmalights 12 | Architecture: all 13 | Section: python 14 | Depends: ${misc:Depends}, ${python:Depends}, python-rpi.gpio 15 | Suggests: 16 | Description: Python library for the Plasma Arcade Lights. 17 | Plasma allows you to control a chain of RGB LED lights. 18 | Ideal for adding illuminated buttons to your Raspberry Pi. 19 | . 20 | This is the Python 2 version of the package. 21 | 22 | Package: python3-plasmalights 23 | Architecture: all 24 | Section: python 25 | Depends: ${misc:Depends}, ${python3:Depends}, python3-rpi.gpio 26 | Suggests: 27 | Description: Python library for the Plasma Arcade Lights. 28 | Plasma allows you to control a chain of RGB LED lights. 29 | Ideal for adding illuminated buttons to your Raspberry Pi. 30 | . 31 | This is the Python 3 version of the package. 32 | -------------------------------------------------------------------------------- /packaging/debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: plasma 3 | Source: https://github.com/pimoroni/plasma 4 | 5 | Files: * 6 | Copyright: 2016 Pimoroni Ltd 7 | License: MIT 8 | 9 | License: MIT 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | . 17 | The above copyright notice and this permission notice shall be included in 18 | all copies or substantial portions of the Software. 19 | . 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /packaging/debian/docs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimoroni/plasma-python/7857c44255285aac061a9064dd033fd63bbbda29/packaging/debian/docs -------------------------------------------------------------------------------- /packaging/debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | 4 | #export DH_VERBOSE=1 5 | export DH_OPTIONS 6 | 7 | %: 8 | dh $@ --with python2,python3 --buildsystem=python_distutils 9 | 10 | override_dh_auto_install: 11 | python setup.py install --root debian/python-plasmalights --install-layout=deb 12 | python3 setup.py install --root debian/python3-plasmalights --install-layout=deb 13 | -------------------------------------------------------------------------------- /packaging/debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /packaging/debian/source/options: -------------------------------------------------------------------------------- 1 | extend-diff-ignore = "^[^/]+\.egg-info/" 2 | tar-ignore 3 | tar-ignore=.tox 4 | -------------------------------------------------------------------------------- /packaging/makeall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # script control variables 4 | 5 | reponame="" # leave this blank for auto-detection 6 | libname="plasma" # leave this blank for auto-detection 7 | packagename="plasmalights" # leave this blank for auto-selection 8 | 9 | debianlog="debian/changelog" 10 | debcontrol="debian/control" 11 | debcopyright="debian/copyright" 12 | debrules="debian/rules" 13 | debreadme="debian/README" 14 | 15 | debdir="$(pwd)" 16 | rootdir="$(dirname $debdir)" 17 | libdir="$rootdir/library" 18 | 19 | FLAG=false 20 | 21 | # function define 22 | 23 | success() { 24 | echo "$(tput setaf 2)$1$(tput sgr0)" 25 | } 26 | 27 | inform() { 28 | echo "$(tput setaf 6)$1$(tput sgr0)" 29 | } 30 | 31 | warning() { 32 | echo "$(tput setaf 1)$1$(tput sgr0)" 33 | } 34 | 35 | newline() { 36 | echo "" 37 | } 38 | 39 | # assessing repo and library variables 40 | 41 | if [ -z "$reponame" ] || [ -z "$libname" ]; then 42 | inform "detecting reponame and libname..." 43 | else 44 | inform "using reponame and libname overrides" 45 | fi 46 | 47 | if [ -z "$reponame" ]; then 48 | if [[ $rootdir == *"python"* ]]; then 49 | repodir="$(dirname $rootdir)" 50 | reponame="$(basename $repodir)" 51 | else 52 | repodir="$rootdir" 53 | reponame="$(basename $repodir)" 54 | fi 55 | reponame=$(echo "$reponame" | tr "[A-Z]" "[a-z]") 56 | fi 57 | 58 | if [ -z "$libname" ]; then 59 | cd "$libdir" 60 | libname=$(grep "name" setup.py | tr -d "[:space:]" | cut -c 7- | rev | cut -c 3- | rev) 61 | libname=$(echo "$libname" | tr "[A-Z]" "[a-z]") && cd "$debdir" 62 | fi 63 | 64 | if [ -z "$packagename" ]; then 65 | packagename="$libname" 66 | fi 67 | 68 | echo "reponame is $reponame and libname is $libname" 69 | echo "output packages will be python-$packagename and python3-$packagename" 70 | 71 | # checking generating changelog file 72 | 73 | ./makelog.sh 74 | version=$(head -n 1 "$libdir/CHANGELOG.txt") 75 | echo "building $libname version $version" 76 | 77 | # checking debian/changelog file 78 | 79 | inform "checking debian/changelog file..." 80 | 81 | if ! head -n 1 $debianlog | grep "$libname" &> /dev/null; then 82 | warning "library not mentioned in header!" && FLAG=true 83 | elif head -n 1 $debianlog | grep "UNRELEASED"; then 84 | warning "this changelog is not going to generate a release!" 85 | warning "change distribution to 'stable'" && FLAG=true 86 | fi 87 | 88 | # checking debian/copyright file 89 | 90 | inform "checking debian/copyright file..." 91 | 92 | if ! grep "^Source" $debcopyright | grep "$reponame" &> /dev/null; then 93 | warning "$(grep "^Source" $debcopyright)" && FLAG=true 94 | fi 95 | 96 | if ! grep "^Upstream-Name" $debcopyright | grep "$libname" &> /dev/null; then 97 | warning "$(grep "^Upstream-Name" $debcopyright)" && FLAG=true 98 | fi 99 | 100 | # checking debian/control file 101 | 102 | inform "checking debian/control file..." 103 | 104 | if ! grep "^Source" $debcontrol | grep "$libname" &> /dev/null; then 105 | warning "$(grep "^Source" $debcontrol)" && FLAG=true 106 | fi 107 | 108 | if ! grep "^Homepage" $debcontrol | grep "$reponame" &> /dev/null; then 109 | warning "$(grep "^Homepage" $debcontrol)" && FLAG=true 110 | fi 111 | 112 | if ! grep "^Package: python-$packagename" $debcontrol &> /dev/null; then 113 | warning "$(grep "^Package: python-" $debcontrol)" && FLAG=true 114 | fi 115 | 116 | if ! grep "^Package: python3-$packagename" $debcontrol &> /dev/null; then 117 | warning "$(grep "^Package: python3-" $debcontrol)" && FLAG=true 118 | fi 119 | 120 | if ! grep "^Priority: extra" $debcontrol &> /dev/null; then 121 | warning "$(grep "^Priority" $debcontrol)" && FLAG=true 122 | fi 123 | 124 | 125 | # checking debian/rules file 126 | 127 | inform "checking debian/rules file..." 128 | 129 | if ! grep "debian/python-$packagename" $debrules &> /dev/null; then 130 | warning "$(grep "debian/python-" $debrules)" && FLAG=true 131 | fi 132 | 133 | if ! grep "debian/python3-$packagename" $debrules &> /dev/null; then 134 | warning "$(grep "debian/python3-" $debrules)" && FLAG=true 135 | fi 136 | 137 | # checking debian/README file 138 | 139 | inform "checking debian/readme file..." 140 | 141 | if ! grep -e "$libname" -e "$reponame" $debreadme &> /dev/null; then 142 | warning "README does not seem to mention product, repo or lib!" && FLAG=true 143 | fi 144 | 145 | # summary of checks pre build 146 | 147 | if $FLAG; then 148 | warning "Check all of the above and correct!" && exit 1 149 | else 150 | inform "we're good to go... bulding!" 151 | fi 152 | 153 | # building deb and final checks 154 | 155 | ./makedeb.sh 156 | 157 | inform "running lintian..." 158 | lintian -v $(find -name "python*$version*.deb") 159 | lintian -v $(find -name "python3*$version*.deb") 160 | 161 | inform "checking signatures..." 162 | gpg --verify $(find -name "*$version*changes") 163 | gpg --verify $(find -name "*$version*dsc") 164 | 165 | exit 0 166 | -------------------------------------------------------------------------------- /packaging/makedeb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | gettools="no" # if set to yes downloads the tools required 4 | setup="yes" # if set to yes populates library folder 5 | buildeb="yes" # if set to yes builds the deb files 6 | cleanup="yes" # if set to yes cleans up build files 7 | pkgfiles=( "build" "changes" "deb" "dsc" "tar.xz" ) 8 | 9 | if [ $gettools == "yes" ]; then 10 | sudo apt-get update && sudo apt-get install build-essential debhelper devscripts dh-make dh-python dput gnupg 11 | sudo apt-get install python-all python-setuptools python3-all python3-setuptools 12 | sudo apt-get install python-mock python-sphinx python-sphinx-rtd-theme 13 | sudo pip install Sphinx --upgrade && sudo pip install sphinx_rtd_theme --upgrade 14 | fi 15 | 16 | if [ $setup == "yes" ]; then 17 | rm -R ../library/build ../library/debian &> /dev/null 18 | cp -R ./debian ../library/ && cp -R ../sphinx ../library/doc 19 | fi 20 | 21 | cd ../library 22 | 23 | if [ $buildeb == "yes" ]; then 24 | debuild -aarmhf 25 | for file in ${pkgfiles[@]}; do 26 | rm ../packaging/*.$file &> /dev/null 27 | mv ../*.$file ../packaging 28 | done 29 | rm -R ../documentation/html &> /dev/null 30 | cp -R ./build/sphinx/html ../documentation 31 | fi 32 | 33 | if [ $cleanup == "yes" ]; then 34 | debuild clean 35 | rm -R ./build ./debian ./doc &> /dev/null 36 | fi 37 | 38 | exit 0 39 | -------------------------------------------------------------------------------- /packaging/makedoc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | gettools="no" # if set to yes downloads the tools required 4 | setup="yes" # if set to yes populates library folder 5 | buildoc="yes" # if set to yes builds the deb files 6 | cleanup="yes" # if set to yes cleans up build files 7 | pkgfiles=( "build" "changes" "deb" "dsc" "tar.xz" ) 8 | 9 | if [ $gettools == "yes" ]; then 10 | sudo apt-get update && sudo apt-get install build-essential debhelper devscripts dh-make dh-python 11 | sudo apt-get install python-all python-setuptools python3-all python3-setuptools 12 | sudo apt-get install python-mock python-sphinx python-sphinx-rtd-theme 13 | sudo pip install Sphinx --upgrade && sudo pip install sphinx_rtd_theme --upgrade 14 | fi 15 | 16 | if [ $setup == "yes" ]; then 17 | rm -R ../library/build ../library/debian &> /dev/null 18 | cp -R ./debian ../library/ && cp -R ../sphinx ../library/doc 19 | fi 20 | 21 | cd ../library 22 | 23 | if [ $buildoc == "yes" ]; then 24 | debuild 25 | for file in ${pkgfiles[@]}; do 26 | rm ../*.$file &> /dev/null 27 | done 28 | rm -R ../documentation/html &> /dev/null 29 | cp -R ./build/sphinx/html ../documentation 30 | fi 31 | 32 | if [ $cleanup == "yes" ]; then 33 | debuild clean 34 | rm -R ./build ./debian ./doc &> /dev/null 35 | fi 36 | 37 | exit 0 38 | -------------------------------------------------------------------------------- /packaging/makelog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # script control variables 4 | 5 | libname="plasma" # leave this blank for auto-detection 6 | sibname=() # name of sibling in packages list 7 | versionwarn="yes" # set to anything but 'yes' to turn off warning 8 | 9 | debdir="$(pwd)" 10 | rootdir="$(dirname $debdir)" 11 | libdir="$rootdir/library" 12 | 13 | mainlog="CHANGELOG" 14 | debianlog="debian/changelog" 15 | pypilog="$libdir/CHANGELOG.txt" 16 | 17 | # function define 18 | 19 | success() { 20 | echo "$(tput setaf 2)$1$(tput sgr0)" 21 | } 22 | 23 | inform() { 24 | echo "$(tput setaf 6)$1$(tput sgr0)" 25 | } 26 | 27 | warning() { 28 | echo "$(tput setaf 1)$1$(tput sgr0)" 29 | } 30 | 31 | newline() { 32 | echo "" 33 | } 34 | 35 | # generate debian changelog 36 | 37 | cat $mainlog > $debianlog 38 | inform "seeded debian changelog" 39 | 40 | # generate pypi changelog 41 | 42 | sed -e "/--/d" -e "s/ \*/\*/" \ 43 | -e "s/.*\([0-9].[0-9].[0-9]\).*/\1/" \ 44 | -e '/[0-9].[0-9].[0-9]/ a\ 45 | -----' $mainlog | cat -s > $pypilog 46 | 47 | version=$(head -n 1 $pypilog) 48 | inform "pypi changelog generated" 49 | 50 | # touch up version in setup.py file 51 | 52 | if [ -n $(grep version "$libdir/setup.py" &> /dev/null) ]; then 53 | inform "touched up version in setup.py" 54 | sed -i "s/'[0-9].[0-9].[0-9]'/'$version'/" "$libdir/setup.py" 55 | else 56 | warning "couldn't touch up version in setup, no match found" 57 | fi 58 | 59 | # touch up version in lib or package siblings 60 | 61 | if [ -z "$libname" ]; then 62 | cd "$libdir" 63 | libname=$(grep "name" setup.py | tr -d "[:space:]" | cut -c 7- | rev | cut -c 3- | rev) 64 | libname=$(echo "$libname" | tr "[A-Z]" "[a-z]") && cd "$debdir" 65 | sibname+=( "$libname" ) 66 | elif [ "$libname" != "package" ]; then 67 | sibname+=( "$libname" ) 68 | fi 69 | 70 | for sibling in ${sibname[@]}; do 71 | if grep -e "__version__" "$libdir/$sibling.py" &> /dev/null; then 72 | sed -i "s/__version__ = '[0-9].[0-9].[0-9]'/__version__ = '$version'/" "$libdir/$sibling.py" 73 | inform "touched up version in $sibling.py" 74 | elif grep -e "__version__" "$libdir/$sibling/__init__.py" &> /dev/null; then 75 | sed -i "s/__version__ = '[0-9].[0-9].[0-9]'/__version__ = '$version'/" "$libdir/$sibling/__init__.py" 76 | inform "touched up version in $sibling/__init__.py" 77 | elif [ "$versionwarn" == "yes" ]; then 78 | warning "couldn't touch up __version__ in $sibling, no match found" 79 | fi 80 | done 81 | 82 | exit 0 83 | -------------------------------------------------------------------------------- /sphinx/_static/custom.css: -------------------------------------------------------------------------------- 1 | .rst-content a, .rst-content a:focus { 2 | color:#13c0d7; 3 | } 4 | .rst-content a:visited, .rst-content a:active { 5 | color:#87319a; 6 | } 7 | .rst-content .highlighted { 8 | background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAJElEQVQIW2P8//9/PSMjYyMDEmAEsdElwILoEnBBZAkUQZgEABMWE4Kzp1KUAAAAAElFTkSuQmCC),rgba(246,167,4,0.2); 9 | } 10 | .wy-side-nav-search { 11 | background:#333333; 12 | } 13 | .wy-nav-side { 14 | background:#444444; 15 | } 16 | .rst-content dl:not(.docutils) dt { 17 | background:#e7fafd; 18 | border-top:solid 3px #13c0d7; 19 | color:rgba(0,0,0,0.5); 20 | } 21 | .rst-content .viewcode-link, .rst-content .viewcode-back { 22 | color:#00b09b; 23 | } 24 | code.literal { 25 | color:#e63c2e; 26 | } 27 | .rst-content #module-plasma { 28 | margin-bottom:24px; 29 | } 30 | .rst-content #module-plasma dl:not(.docutils) dt { 31 | border:none; 32 | background:#f0f0f0; 33 | } 34 | .rst-content #module-plasma dl:not(.docutils) dd { 35 | display:none; 36 | } 37 | .rst-content #module-plasma dl:not(.docutils) { 38 | margin-bottom:0; 39 | } 40 | -------------------------------------------------------------------------------- /sphinx/_templates/breadcrumbs.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimoroni/plasma-python/7857c44255285aac061a9064dd033fd63bbbda29/sphinx/_templates/breadcrumbs.html -------------------------------------------------------------------------------- /sphinx/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | {% block extrahead %} 3 | 4 | {% endblock %} -------------------------------------------------------------------------------- /sphinx/conf.py: -------------------------------------------------------------------------------- 1 | #-*- coding: utf-8 -*- 2 | 3 | import sys 4 | import site 5 | 6 | import mock 7 | 8 | 9 | PACKAGE_NAME = u"Plasma" 10 | PACKAGE_HANDLE = "Plasma" 11 | PACKAGE_MODULE = "plasma" 12 | 13 | # Prompte /usr/local/lib to the front of sys.path 14 | #sys.path.insert(0,site.getsitepackages()[0]) 15 | 16 | import sphinx_rtd_theme 17 | 18 | sys.modules['RPi'] = mock.Mock() 19 | sys.modules['RPi.GPIO'] = mock.Mock() 20 | 21 | sys.path.insert(0, '../library/') 22 | 23 | import plasma 24 | 25 | 26 | from sphinx.ext import autodoc 27 | 28 | 29 | class OutlineMethodDocumenter(autodoc.MethodDocumenter): 30 | objtype = 'method' 31 | 32 | def add_content(self, more_content, no_docstring=False): 33 | return 34 | 35 | class OutlineFunctionDocumenter(autodoc.FunctionDocumenter): 36 | objtype = 'function' 37 | 38 | def add_content(self, more_content, no_docstring=False): 39 | return 40 | 41 | class ModuleOutlineDocumenter(autodoc.ModuleDocumenter): 42 | objtype = 'moduleoutline' 43 | 44 | def __init__(self, directive, name, indent=u''): 45 | # Monkey path the Method and Function documenters 46 | sphinx_app.add_autodocumenter(OutlineMethodDocumenter) 47 | sphinx_app.add_autodocumenter(OutlineFunctionDocumenter) 48 | autodoc.ModuleDocumenter.__init__(self, directive, name, indent) 49 | 50 | def __del__(self): 51 | # Return the Method and Function documenters to normal 52 | sphinx_app.add_autodocumenter(autodoc.MethodDocumenter) 53 | sphinx_app.add_autodocumenter(autodoc.FunctionDocumenter) 54 | 55 | 56 | def setup(app): 57 | global sphinx_app 58 | sphinx_app = app 59 | app.add_autodocumenter(ModuleOutlineDocumenter) 60 | 61 | ModuleOutlineDocumenter.objtype = 'module' 62 | 63 | # -- General configuration ------------------------------------------------ 64 | 65 | # If your documentation needs a minimal Sphinx version, state it here. 66 | # 67 | # needs_sphinx = '1.0' 68 | 69 | # Add any Sphinx extension module names here, as strings. They can be 70 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 71 | # ones. 72 | extensions = [ 73 | 'sphinx.ext.autodoc', 74 | 'sphinx.ext.autosummary', 75 | 'sphinx.ext.viewcode', 76 | ] 77 | 78 | # Add any paths that contain templates here, relative to this directory. 79 | templates_path = ['_templates'] 80 | 81 | # The suffix(es) of source filenames. 82 | # You can specify multiple suffix as a list of string: 83 | # 84 | # source_suffix = ['.rst', '.md'] 85 | source_suffix = '.rst' 86 | 87 | # The encoding of source files. 88 | # 89 | # source_encoding = 'utf-8-sig' 90 | 91 | # The master toctree document. 92 | master_doc = 'index' 93 | 94 | # General information about the project. 95 | project = PACKAGE_NAME 96 | copyright = u'2017, Pimoroni Ltd' 97 | author = u'Phil Howard' 98 | 99 | # The version info for the project you're documenting, acts as replacement for 100 | # |version| and |release|, also used in various other places throughout the 101 | # built documents. 102 | # 103 | # The short X.Y version. 104 | version = u'{}'.format(plasma.__version__) 105 | # The full version, including alpha/beta/rc tags. 106 | release = u'{}'.format(plasma.__version__) 107 | 108 | # The language for content autogenerated by Sphinx. Refer to documentation 109 | # for a list of supported languages. 110 | # 111 | # This is also used if you do content translation via gettext catalogs. 112 | # Usually you set "language" from the command line for these cases. 113 | language = None 114 | 115 | # There are two options for replacing |today|: either, you set today to some 116 | # non-false value, then it is used: 117 | # 118 | # today = '' 119 | # 120 | # Else, today_fmt is used as the format for a strftime call. 121 | # 122 | # today_fmt = '%B %d, %Y' 123 | 124 | # List of patterns, relative to source directory, that match files and 125 | # directories to ignore when looking for source files. 126 | # This patterns also effect to html_static_path and html_extra_path 127 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 128 | 129 | # The reST default role (used for this markup: `text`) to use for all 130 | # documents. 131 | # 132 | # default_role = None 133 | 134 | # If true, '()' will be appended to :func: etc. cross-reference text. 135 | # 136 | # add_function_parentheses = True 137 | 138 | # If true, the current module name will be prepended to all description 139 | # unit titles (such as .. function::). 140 | # 141 | # add_module_names = True 142 | 143 | # If true, sectionauthor and moduleauthor directives will be shown in the 144 | # output. They are ignored by default. 145 | # 146 | # show_authors = False 147 | 148 | # The name of the Pygments (syntax highlighting) style to use. 149 | pygments_style = 'sphinx' 150 | 151 | # A list of ignored prefixes for module index sorting. 152 | # modindex_common_prefix = [] 153 | 154 | # If true, keep warnings as "system message" paragraphs in the built documents. 155 | # keep_warnings = False 156 | 157 | # If true, `todo` and `todoList` produce output, else they produce nothing. 158 | todo_include_todos = False 159 | 160 | 161 | # -- Options for HTML output ---------------------------------------------- 162 | 163 | # The theme to use for HTML and HTML Help pages. See the documentation for 164 | # a list of builtin themes. 165 | # 166 | html_theme = 'sphinx_rtd_theme' 167 | #html_theme = 'alabaster' 168 | 169 | # Theme options are theme-specific and customize the look and feel of a theme 170 | # further. For a list of options available for each theme, see the 171 | # documentation. 172 | # 173 | html_theme_options = { 174 | 'collapse_navigation': False, 175 | 'display_version': True 176 | } 177 | 178 | # Add any paths that contain custom themes here, relative to this directory. 179 | html_theme_path = [ 180 | '_themes', 181 | sphinx_rtd_theme.get_html_theme_path() 182 | ] 183 | 184 | # The name for this set of Sphinx documents. 185 | # " v documentation" by default. 186 | # 187 | # html_title = PACKAGE_NAME + u' v0.1.2' 188 | 189 | # A shorter title for the navigation bar. Default is the same as html_title. 190 | # 191 | # html_short_title = None 192 | 193 | # The name of an image file (relative to this directory) to place at the top 194 | # of the sidebar. 195 | # 196 | html_logo = 'shop-logo.png' 197 | 198 | # The name of an image file (relative to this directory) to use as a favicon of 199 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 200 | # pixels large. 201 | # 202 | html_favicon = 'favicon.png' 203 | 204 | # Add any paths that contain custom static files (such as style sheets) here, 205 | # relative to this directory. They are copied after the builtin static files, 206 | # so a file named "default.css" will overwrite the builtin "default.css". 207 | html_static_path = ['_static'] 208 | 209 | # Add any extra paths that contain custom files (such as robots.txt or 210 | # .htaccess) here, relative to this directory. These files are copied 211 | # directly to the root of the documentation. 212 | # 213 | # html_extra_path = [] 214 | 215 | # If not None, a 'Last updated on:' timestamp is inserted at every page 216 | # bottom, using the given strftime format. 217 | # The empty string is equivalent to '%b %d, %Y'. 218 | # 219 | # html_last_updated_fmt = None 220 | 221 | # If true, SmartyPants will be used to convert quotes and dashes to 222 | # typographically correct entities. 223 | # 224 | # html_use_smartypants = True 225 | 226 | # Custom sidebar templates, maps document names to template names. 227 | # 228 | # html_sidebars = {} 229 | 230 | # Additional templates that should be rendered to pages, maps page names to 231 | # template names. 232 | # 233 | # html_additional_pages = {} 234 | 235 | # If false, no module index is generated. 236 | # 237 | # html_domain_indices = True 238 | 239 | # If false, no index is generated. 240 | # 241 | html_use_index = False 242 | 243 | # If true, the index is split into individual pages for each letter. 244 | # 245 | # html_split_index = False 246 | 247 | # If true, links to the reST sources are added to the pages. 248 | # 249 | html_show_sourcelink = False 250 | 251 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 252 | # 253 | html_show_sphinx = False 254 | 255 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 256 | # 257 | # html_show_copyright = True 258 | 259 | # If true, an OpenSearch description file will be output, and all pages will 260 | # contain a tag referring to it. The value of this option must be the 261 | # base URL from which the finished HTML is served. 262 | # 263 | # html_use_opensearch = '' 264 | 265 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 266 | # html_file_suffix = None 267 | 268 | # Language to be used for generating the HTML full-text search index. 269 | # Sphinx supports the following languages: 270 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 271 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' 272 | # 273 | # html_search_language = 'en' 274 | 275 | # A dictionary with options for the search language support, empty by default. 276 | # 'ja' uses this config value. 277 | # 'zh' user can custom change `jieba` dictionary path. 278 | # 279 | # html_search_options = {'type': 'default'} 280 | 281 | # The name of a javascript file (relative to the configuration directory) that 282 | # implements a search results scorer. If empty, the default will be used. 283 | # 284 | # html_search_scorer = 'scorer.js' 285 | 286 | # Output file base name for HTML help builder. 287 | htmlhelp_basename = PACKAGE_HANDLE + 'doc' 288 | 289 | # -- Options for LaTeX output --------------------------------------------- 290 | 291 | latex_elements = { 292 | # The paper size ('letterpaper' or 'a4paper'). 293 | # 294 | # 'papersize': 'letterpaper', 295 | 296 | # The font size ('10pt', '11pt' or '12pt'). 297 | # 298 | # 'pointsize': '10pt', 299 | 300 | # Additional stuff for the LaTeX preamble. 301 | # 302 | # 'preamble': '', 303 | 304 | # Latex figure (float) alignment 305 | # 306 | # 'figure_align': 'htbp', 307 | } 308 | 309 | # Grouping the document tree into LaTeX files. List of tuples 310 | # (source start file, target name, title, 311 | # author, documentclass [howto, manual, or own class]). 312 | latex_documents = [ 313 | (master_doc, PACKAGE_HANDLE + '.tex', PACKAGE_NAME + u' Documentation', 314 | u'Phil Howard', 'manual'), 315 | ] 316 | 317 | # The name of an image file (relative to this directory) to place at the top of 318 | # the title page. 319 | # 320 | # latex_logo = None 321 | 322 | # For "manual" documents, if this is true, then toplevel headings are parts, 323 | # not chapters. 324 | # 325 | # latex_use_parts = False 326 | 327 | # If true, show page references after internal links. 328 | # 329 | # latex_show_pagerefs = False 330 | 331 | # If true, show URL addresses after external links. 332 | # 333 | # latex_show_urls = False 334 | 335 | # Documents to append as an appendix to all manuals. 336 | # 337 | # latex_appendices = [] 338 | 339 | # It false, will not define \strong, \code, itleref, \crossref ... but only 340 | # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added 341 | # packages. 342 | # 343 | # latex_keep_old_macro_names = True 344 | 345 | # If false, no module index is generated. 346 | # 347 | # latex_domain_indices = True 348 | 349 | 350 | # -- Options for manual page output --------------------------------------- 351 | 352 | # One entry per manual page. List of tuples 353 | # (source start file, name, description, authors, manual section). 354 | man_pages = [ 355 | (master_doc, PACKAGE_MODULE, PACKAGE_NAME + u' Documentation', 356 | [author], 1) 357 | ] 358 | 359 | # If true, show URL addresses after external links. 360 | # 361 | # man_show_urls = False 362 | 363 | 364 | # -- Options for Texinfo output ------------------------------------------- 365 | 366 | # Grouping the document tree into Texinfo files. List of tuples 367 | # (source start file, target name, title, author, 368 | # dir menu entry, description, category) 369 | texinfo_documents = [ 370 | (master_doc, PACKAGE_HANDLE, PACKAGE_NAME + u' Documentation', 371 | author, PACKAGE_HANDLE, 'One line description of project.', 372 | 'Miscellaneous'), 373 | ] 374 | 375 | # Documents to append as an appendix to all manuals. 376 | # 377 | # texinfo_appendices = [] 378 | 379 | # If false, no module index is generated. 380 | # 381 | # texinfo_domain_indices = True 382 | 383 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 384 | # 385 | # texinfo_show_urls = 'footnote' 386 | 387 | # If true, do not generate a @detailmenu in the "Top" node's menu. 388 | # 389 | # texinfo_no_detailmenu = False 390 | -------------------------------------------------------------------------------- /sphinx/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimoroni/plasma-python/7857c44255285aac061a9064dd033fd63bbbda29/sphinx/favicon.png -------------------------------------------------------------------------------- /sphinx/index.rst: -------------------------------------------------------------------------------- 1 | .. role:: python(code) 2 | :language: python 3 | 4 | .. toctree:: 5 | :titlesonly: 6 | :maxdepth: 0 7 | 8 | Welcome 9 | ------- 10 | 11 | This documentation will guide you through the methods available in the Plasma Python library. 12 | 13 | * More information - https://shop.pimoroni.com/products/plasma 14 | * Get the code - https://github.com/pimoroni/plasma 15 | * Get started - https://learn.pimoroni.com/tutorial/sandyj/getting-started-with-plasma 16 | * Get help - http://forums.pimoroni.com/c/support 17 | 18 | At A Glance 19 | ----------- 20 | 21 | .. automoduleoutline:: plasma 22 | :members: 23 | 24 | Set A Single Pixel 25 | ------------------ 26 | 27 | The bread and butter of Plasma is setting pixels. Your Plasma LED string will always have 28 | 4x more pixels than buttons. You can set these to any one of around 16 million colours! 29 | 30 | The :python:`brightness` argument is completely optional. Omit it to keep the last 31 | brightness value set for that particular pixel. 32 | 33 | .. automodule:: plasma 34 | :noindex: 35 | :members: set_pixel 36 | 37 | Set All Pixels 38 | -------------- 39 | 40 | Sometimes you need to set all the pixels to the same colour. This convinience method 41 | does just that! 42 | 43 | .. automodule:: plasma 44 | :noindex: 45 | :members: set_all 46 | 47 | Show 48 | ---- 49 | 50 | None of your pixels will appear on Plasma until you :python:`show()` them. This method writes 51 | all the pixel data out to your device. 52 | 53 | .. automodule:: plasma 54 | :noindex: 55 | :members: show 56 | 57 | Clear 58 | ----- 59 | 60 | Exactly the same as calling :python:`set_all(0,0,0)`, clear sets all the pixels to black. 61 | 62 | You must also call :python:`show()` if you want to turn Plasma off. 63 | 64 | .. automodule:: plasma 65 | :noindex: 66 | :members: clear 67 | 68 | Enable/Disable Clear On Exit 69 | ---------------------------- 70 | 71 | Sometimes you want a script that runs and quits, leaving a pattern up on Plasma 72 | 73 | .. automodule:: plasma 74 | :noindex: 75 | :members: set_clear_on_exit 76 | 77 | Get A Single Pixel 78 | ------------------ 79 | 80 | .. automodule:: plasma 81 | :noindex: 82 | :members: get_pixel 83 | 84 | Constants 85 | --------- 86 | 87 | Plasma has 8 pixels. Simple. Use the constant :python:`plasma.NUM_PIXELS` when you're iterating over pixels, 88 | so you can avoid a *magic number* in your code. 89 | 90 | :python:`plasma.NUM_PIXELS = 8` 91 | -------------------------------------------------------------------------------- /sphinx/shop-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimoroni/plasma-python/7857c44255285aac061a9064dd033fd63bbbda29/sphinx/shop-logo.png -------------------------------------------------------------------------------- /terminal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimoroni/plasma-python/7857c44255285aac061a9064dd033fd63bbbda29/terminal.jpg --------------------------------------------------------------------------------