├── LICENSE ├── README.md ├── flask_listener.py ├── listener.js ├── listener_pitft.sh ├── machine.png ├── machine_stars.py ├── penguin_thinking.png ├── raspipe.py ├── raspipe_pitft.sh ├── raspipe_tee ├── star.png ├── templates └── index.html └── tick.wav /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Adafruit 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Adafruit-RasPipe 2 | ================ 3 | 4 | A small-display Raspberry Pi pipeline viewer using Python and Pygame. 5 | 6 | See [RasPipe: A Raspberry Pi Pipeline Viewer, Part 1][1] for context and 7 | detailed documentation. 8 | 9 | contents 10 | -------- 11 | 12 | * `raspipe.py` - scroll lines of standard input or display stars proportionally 13 | sized to each line. Both a standalone script and a class, RasPipe. 14 | * `raspipe_pitft.sh` 15 | * `raspipe_tee` - a pipeline utility for sending things to a Pi running a RasPipe 16 | listener 17 | * `listener.js` - a very brief node.js wrapper for listening on a network socket and 18 | sending things to stdout 19 | * `listener_pitft.sh` - a shell script for using `netcat` to listen on a socket and pass 20 | things to `raspipe_pitft.sh` 21 | * `flask_listener.py` - a simple web app using Flask to talk to the RasPipe class. 22 | * `machine_stars.py` - standard input goes into a little machine and comes out 23 | as stars. 24 | 25 | [1]: https://learn.adafruit.com/raspipe-a-raspberry-pi-pipeline-viewer 26 | -------------------------------------------------------------------------------- /flask_listener.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from flask import Flask 4 | from flask import request 5 | from flask import render_template 6 | from flask import redirect, url_for 7 | 8 | from raspipe import RasPipe 9 | 10 | app = Flask(__name__) 11 | 12 | rp = RasPipe(None) 13 | rp.input_lines.append('starting up...') 14 | rp.render_frame() 15 | 16 | @app.route('/') 17 | def index(): 18 | return render_template('index.html', rp=rp) 19 | 20 | @app.route('/display', methods=['POST']) 21 | def display(): 22 | rp.input_lines.append(request.form['line']) 23 | rp.render_frame() 24 | return redirect(url_for('index')) 25 | 26 | @app.route('/quit') 27 | def quit(): 28 | func = request.environ.get('werkzeug.server.shutdown') 29 | func() 30 | return "Quitting..." 31 | 32 | if __name__ == '__main__': 33 | app.debug = True 34 | app.run(host='0.0.0.0') 35 | -------------------------------------------------------------------------------- /listener.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var net = require('net'); 4 | 5 | var onConnection = function (socket) { 6 | socket.pipe(process.stdout); 7 | }; 8 | 9 | net.createServer(onConnection).listen(5280); 10 | console.log('Server running at 127.0.0.1:5280'); 11 | -------------------------------------------------------------------------------- /listener_pitft.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "Listening on port 5280" 3 | netcat -l 5280 -k | ./raspipe_pitft.sh 4 | -------------------------------------------------------------------------------- /machine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adafruit/Adafruit-RasPipe/bedc53e0f97bdc11dc2ea8188df925c7903604d9/machine.png -------------------------------------------------------------------------------- /machine_stars.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import random 5 | 6 | import pygame 7 | 8 | text_color = pygame.Color(0, 0, 0) 9 | bg_color = pygame.Color(255, 255, 255) 10 | 11 | pygame.init() 12 | screen = pygame.display.set_mode([320, 240]) 13 | screen.fill(bg_color) 14 | 15 | # Set up the picture of our little machine: 16 | machine_img = pygame.image.load('machine.png').convert_alpha() 17 | machine_img = pygame.transform.smoothscale(machine_img, (100, 112)) 18 | machine_rect = machine_img.get_rect() 19 | machine_rect.left = 10 20 | machine_rect.top = 120 21 | screen.blit(machine_img, machine_rect) 22 | 23 | # Set up the picture of a star: 24 | orig_star_img = pygame.image.load('star.png').convert_alpha() 25 | 26 | # This will hold some input lines: 27 | stars_length = 0 28 | offset = 0 29 | 30 | # Start building a list of things to display from stdin: 31 | display_lines = [sys.stdin.readline()] 32 | 33 | while len(display_lines) > 0: 34 | 35 | # Get the next available line from stdin: 36 | line = sys.stdin.readline() 37 | 38 | if line: 39 | display_lines.insert(0, line) 40 | 41 | # If there're more than 6 lines to display, or we're not getting 42 | # any more input, pop the last line off the list and turn it into 43 | # a number of stars to show: 44 | if (len(display_lines) > 6) or (not line): 45 | stars_length = len(display_lines.pop()) 46 | 47 | # If there's no more input, start offsetting display from the top 48 | # of the screen so it seems to fall downwards: 49 | if not line: 50 | offset = offset + 20 51 | 52 | # Blank the areas above and right of the machine image: 53 | screen.fill(bg_color, [0, 0, 320, 120]) 54 | screen.fill(bg_color, [machine_rect.right, machine_rect.top, 320, 240]) 55 | 56 | # Display the most recent lines of stdin falling into the machine, 57 | # in a font that gets smaller as it falls: 58 | font_size = 22 59 | y = 0 + offset 60 | for render_me in display_lines: 61 | font_size = font_size - 2 62 | font = pygame.font.Font(None, font_size) 63 | input_text_surface = font.render(render_me.rstrip(), True, text_color) 64 | input_text_rect = input_text_surface.get_rect(center=(64, y)) 65 | screen.blit(input_text_surface, input_text_rect) 66 | y += 20 67 | 68 | pygame.display.update() 69 | 70 | # Display stars leaving machine's output. Stars are scaled to a random 71 | # height & width between 8 and 30 pixels, then displayed at a random 72 | # vertical location on the screen +/- 8 pixels from 185: 73 | if stars_length > 0: 74 | star_x = machine_rect.right 75 | for i in range(0, stars_length): 76 | star_w = random.randrange(8, 30) 77 | star_h = random.randrange(8, 30) 78 | star_img = pygame.transform.smoothscale(orig_star_img, (star_w, star_h)) 79 | 80 | star_rect = star_img.get_rect() 81 | star_rect.left = star_x 82 | star_rect.top = 185 + random.randrange(-8, 8) 83 | screen.blit(star_img, star_rect) 84 | 85 | pygame.display.update() 86 | 87 | # Chill out for 15 milliseconds: 88 | pygame.time.wait(15) 89 | 90 | # Move start of next star to end of the current one, and quit 91 | # drawing stars if we've run off the edge of the screen: 92 | star_x += star_w 93 | if star_x > 320: 94 | break 95 | 96 | pygame.time.wait(100) 97 | -------------------------------------------------------------------------------- /penguin_thinking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adafruit/Adafruit-RasPipe/bedc53e0f97bdc11dc2ea8188df925c7903604d9/penguin_thinking.png -------------------------------------------------------------------------------- /raspipe.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import getopt 4 | import re 5 | import sys 6 | import time 7 | 8 | import pygame 9 | 10 | class RasPipe: 11 | size = width, height = 320, 240 12 | 13 | grep_for = None 14 | delay = 70 # ms 15 | display_lines = 7 16 | font_size = 26 17 | input_lines = [] 18 | tick = 0 19 | stars = False 20 | matched_line = None # for regex matches 21 | 22 | def __init__(self, infile): 23 | """Create a RasPipe object for a given input file.""" 24 | self.infile = infile 25 | 26 | pygame.init() 27 | pygame.mouse.set_visible(False) 28 | self.screen = pygame.display.set_mode(self.size) 29 | self.set_font(self.font_size) 30 | 31 | self.fgcolor = pygame.Color(0, 0, 0) 32 | self.bgcolor = pygame.Color(255, 255, 255) 33 | 34 | # A little bit of sound. 35 | pygame.mixer.init() 36 | self.click = pygame.mixer.Sound('./tick.wav') 37 | 38 | # Set up the picture of a star: 39 | self.orig_star_img = pygame.image.load('star.png').convert_alpha() 40 | self.penguin = pygame.image.load('penguin_thinking.png').convert_alpha() 41 | 42 | def set_font(self, size): 43 | """Set a display font of a given size.""" 44 | self.font = pygame.font.Font(None, size) 45 | 46 | def toggle_stars(self): 47 | self.stars = not self.stars 48 | 49 | def wait_for_click(self): 50 | event = pygame.event.wait() 51 | if event.type == pygame.MOUSEBUTTONDOWN: 52 | return 53 | else: 54 | self.wait_for_click() 55 | 56 | def scale_display(self): 57 | """Set the current font size and delay based on amount of input.""" 58 | original_font_size = self.font_size 59 | 60 | # How big should our font be, and how fast should text scroll? 61 | if len(self.input_lines) > 150: 62 | self.input_lines.pop(0) 63 | self.font_size = 18 64 | self.delay = 5 65 | elif len(self.input_lines) > 60: 66 | self.font_size = 20 67 | self.delay = 10 68 | elif len(self.input_lines) > 30: 69 | self.font_size = 24 70 | self.delay = 20 71 | 72 | if self.font_size != original_font_size: 73 | self.set_font(self.font_size) 74 | 75 | # How many lines of text to display? 76 | self.display_lines = int(self.size[1] / self.font_size) 77 | 78 | def penguin_think(self, thought): 79 | r = self.penguin.get_rect(left=50, bottom=self.height) 80 | self.screen.fill(self.fgcolor, [0, 10, self.width, r.top]) 81 | self.screen.blit(self.penguin, r) 82 | thought_surface = self.font.render(thought, True, self.bgcolor) 83 | thought_r = thought_surface.get_rect(left=5, top=30) 84 | self.screen.blit(thought_surface, thought_r) 85 | pygame.display.update() 86 | 87 | def run(self): 88 | """Process standard input.""" 89 | line = self.infile.readline() 90 | while line: 91 | self.input_lines.append(line) 92 | 93 | for event in pygame.event.get(): 94 | if event.type == pygame.KEYDOWN and event.key in (pygame.K_q, pygame.K_x): 95 | sys.exit() 96 | if event.type == pygame.QUIT: 97 | sys.exit() 98 | if event.type == pygame.MOUSEBUTTONDOWN: 99 | self.toggle_stars() 100 | 101 | self.render_frame() 102 | 103 | line = self.infile.readline() 104 | 105 | # Wait until input from user before quitting. 106 | self.set_font(48) 107 | msg = self.font.render("click to exit", True, self.fgcolor) 108 | self.screen.blit(msg, msg.get_rect(center=(self.width / 2, self.height / 2))) 109 | pygame.display.update() 110 | self.wait_for_click() 111 | 112 | def render_frame(self): 113 | """Render an individual frame of animation.""" 114 | self.tick += 1 115 | self.scale_display() 116 | self.screen.fill(self.bgcolor) 117 | 118 | # Get last display_lines of input and scroll them up display: 119 | to_render = self.input_lines[-self.display_lines:] 120 | 121 | y = 0 122 | for render_line in to_render: 123 | # Show a penguin thinking if we have a regular expression to 124 | # search for and found a match: 125 | if self.grep_for is not None: 126 | if re.match(self.grep_for, render_line): 127 | self.matched_line = render_line 128 | 129 | render_line = render_line.rstrip() 130 | 131 | if self.stars: 132 | star_w = star_h = len(render_line) 133 | star_img = pygame.transform.scale(self.orig_star_img, (star_w, star_h)) 134 | r = star_img.get_rect(center=(self.width / 2, y)) 135 | self.screen.blit(star_img, r) 136 | y += star_h 137 | if y > self.height: 138 | break 139 | 140 | else: 141 | text_surface = self.font.render(render_line, True, self.fgcolor) 142 | r = text_surface.get_rect(left=2, top=y) 143 | self.screen.blit(text_surface, r) 144 | for pixel_x in range(0, len(render_line)): 145 | if render_line[pixel_x] != ' ': 146 | pygame.draw.line(self.screen, self.fgcolor, [pixel_x, r.bottom], [pixel_x, r.bottom], 1) 147 | y += self.font_size 148 | 149 | if self.matched_line is not None: 150 | self.penguin_think(self.matched_line) 151 | 152 | if self.tick % self.display_lines == 0: 153 | self.click.play() 154 | 155 | # Actually display the display: 156 | pygame.display.flip() 157 | pygame.time.wait(self.delay); 158 | 159 | # Handle running this file as a standalone script. 160 | if __name__ == '__main__': 161 | rp = RasPipe(sys.stdin) 162 | 163 | opts, args = getopt.getopt(sys.argv[1:], 'sr:x:y:') 164 | for opt, arg in opts: 165 | if opt == '-x': 166 | rp.size[0] = (int(arg)) 167 | if opt == '-y': 168 | rp.size[1] = (int(arg)) 169 | if opt == '-r': 170 | rp.grep_for = (arg) 171 | if opt == '-s': 172 | rp.toggle_stars() 173 | 174 | rp.run() 175 | -------------------------------------------------------------------------------- /raspipe_pitft.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # A wrapper for displaying raspipe.py output on /dev/fb1 4 | 5 | export SDL_FBDEV=/dev/fb1 6 | cat /dev/stdin | ./raspipe.py $@ 7 | -------------------------------------------------------------------------------- /raspipe_tee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | RASPIPE_ADDY=192.168.1.4 4 | cat /dev/stdin | tee >(netcat $RASPIPE_ADDY 5280) 5 | -------------------------------------------------------------------------------- /star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adafruit/Adafruit-RasPipe/bedc53e0f97bdc11dc2ea8188df925c7903604d9/star.png -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |