Things used in this project

Hardware components:
475267 240424 01 front zoom kankcmvqdh
Raspberry Pi Raspberry Pi Zero Wireless
×1
Unicorn pHAT
×1
2A Micro-USB Power Adapter
×1
Software apps and online services:
PHPUnit
Hand tools and fabrication machines:
09507 01
Soldering iron (generic)

Code

PHPUnicornListener.phpPHP
Custom listener for PHPUnit to collect test stats and send them along to the Pi
<?php

namespace ColinODell\PHPUnicorn;

use Exception;
use PHPUnit_Framework_AssertionFailedError;
use PHPUnit_Framework_Test;
use PHPUnit_Framework_TestSuite;
use PHPUnit_Framework_Warning;

class PHPUnicornListener extends \PHPUnit_Framework_BaseTestListener
{
    const NO_RESULT = 'N';
    const ERROR = 'E';
    const FAILURE = 'F';
    const INCOMPLETE = 'I';
    const RISKY = 'R';
    const SKIPPED = 'S';
    const PASSED = 'P';
    const WARNING = 'W';
    const TOTAL = 'T';
    const COMPLETED = 'C';

    private $currentTestPassed = false;
    private $counts = [];

    /**
     * @var string
     */
    private $host;

    /**
     * @var int
     */
    private $port;

    /**
     * @var resource
     */
    private $socket;

    /**
     * @param string $host
     * @param int    $port
     */
    public function __construct($host, $port)
    {
        $this->host = $host;
        $this->port = $port;
        $this->socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
        $this->resetCounts();
        $this->broadcast();
    }

    /**
     * Ensure socket is closed
     */
    public function __destruct()
    {
        socket_close($this->socket);
        $this->socket = null;
    }

    /**
     * {@inheritdoc}
     */
    public function addError(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        $this->counts[self::ERROR]++;
        $this->currentTestPassed = false;
    }

    /**
     * {@inheritdoc}
     */
    public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) {
        $this->counts[self::FAILURE]++;
        $this->currentTestPassed = false;
    }

    /**
     * {@inheritdoc}
     */
    public function addWarning(PHPUnit_Framework_Test $test, PHPUnit_Framework_Warning $e, $time)
    {
        $this->counts[self::WARNING]++;
        $this->currentTestPassed = false;
    }


    /**
     * {@inheritdoc}
     */
    public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) {
        $this->counts[self::INCOMPLETE]++;
        $this->currentTestPassed = false;
    }

    /**
     * {@inheritdoc}
     */
    public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) {
        $this->counts[self::RISKY]++;
        $this->currentTestPassed = false;
    }

    /**
     * {@inheritdoc}
     */
    public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        $this->counts[self::SKIPPED]++;
        $this->currentTestPassed = false;
    }

    /**
     * A test suite has started.
     *
     * {@inheritdoc}
     */
    public function startTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
        // Test suites can contain child test suites.  This function is always
        // called with the top-most parent, so use its count method to determine
        // how many tests there are (it'll count all sub-children recursively).
        if ($this->counts[self::TOTAL] == 0) {
            $this->counts[self::TOTAL] = $suite->count();
        }
    }

    /**
     * A test suite ended.
     *
     * {@inheritdoc}
     */
    public function endTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
        // Send the results over to the Pi
        $this->broadcast();
    }

    /**
     * A single test started.
     *
     * {@inheritdoc}
     */
    public function startTest(PHPUnit_Framework_Test $test)
    {
        // There's no method like addPassed(), so we'll assume the test passes
        // unless one of the other add___() methods are called.
        $this->currentTestPassed = true;
    }

    /**
     * A test ended.
     *
     * {@inheritdoc}
     */
    public function endTest(PHPUnit_Framework_Test $test, $time)
    {
        if ($this->currentTestPassed) {
            $this->counts[self::PASSED]++;
        }

        $this->counts[self::COMPLETED]++;

        $this->broadcast();
    }

    private function resetCounts()
    {
        $this->counts = [
            self::NO_RESULT => 0,
            self::ERROR => 0,
            self::FAILURE => 0,
            self::WARNING => 0,
            self::INCOMPLETE => 0,
            self::RISKY => 0,
            self::SKIPPED => 0,
            self::PASSED => 0,
            self::TOTAL => 0,
            self::COMPLETED => 0,
        ];
    }

    private function broadcast()
    {
        if ($this->counts[self::TOTAL] == 0) {
            // Ask the Pi to clear the screen when we first start
            $message = 'reset';
        } else {
            $message = json_encode($this->counts);
        }

        socket_sendto($this->socket, $message, strlen($message), 0, $this->host, $this->port);
    }
}
display.pyPython
Python 3 script to receive the test stats and render them on a Unicorn pHAT display
#!/usr/bin/env python

import json
import socket
import time
import unicornhat as unicorn

UDP_IP = "0.0.0.0"
UDP_PORT = 5005

# Define constants for each JSON key
NO_RESULT = 'N'
ERROR = 'E'
FAILURE = 'F'
WARNING = 'W'
INCOMPLETE = 'I'
RISKY = 'R'
SKIPPED = 'S'
PASSED = 'P'
TOTAL = 'T'
COMPLETED = 'C'

TOTAL_PIXELS = 4*8


unicorn.set_layout(unicorn.PHAT)
unicorn.rotation(0)
unicorn.brightness(0.5)

sock = socket.socket(socket.AF_INET, # Internet
                     socket.SOCK_DGRAM) # UDP
sock.bind((UDP_IP, UDP_PORT))

time.sleep(1)

def set_pixel(i, r, g, b):
    # Don't render more pixels than we have
    if i > TOTAL_PIXELS - 1:
        return

    # Determine the X/Y coordinates based on pixel index
    x = int(i % 8)
    y = int((i - x) / 8)

    unicorn.set_pixel(x, y, r, g, b)


while True:
    data, addr = sock.recvfrom(1024)

    unicorn.set_all(0, 0, 0)

    data = data.decode("utf-8")
    if data == "reset":
        unicorn.show()
        continue

    data = json.loads(data)
    original_data = data.copy()
    print(data)

    test_count = data[TOTAL]
    # how many tests count towards a pixel?
    tests_per_pixel = test_count / TOTAL_PIXELS

    for i in range(0, TOTAL_PIXELS):
        # Render test results in order of severity
        if data[ERROR] > 0:
            data[ERROR] -= tests_per_pixel
            set_pixel(i, 255, 0, 0)
            continue
        elif data[FAILURE] > 0:
            data[FAILURE] -= tests_per_pixel
            set_pixel(i, 200, 0, 0)
            continue
        elif data[WARNING] > 0:
            data[WARNING] -= tests_per_pixel
            set_pixel(i, 255, 255, 0)
            continue
        elif data[INCOMPLETE] > 0:
            data[INCOMPLETE] -= tests_per_pixel
            set_pixel(i, 170, 170, 0)
            continue
        elif data[RISKY] > 0:
            data[RISKY] -= tests_per_pixel
            set_pixel(i, 255, 255, 0)
            continue
        elif data[SKIPPED] > 0:
            data[SKIPPED] -= tests_per_pixel
            set_pixel(i, 64, 64, 64)
            continue
        elif data[PASSED] > 0:
            data[PASSED] -= tests_per_pixel
            set_pixel(i, 0, 255, 0)
            continue

    # Update the display once the pixel buffers are set
    unicorn.show()

Credits

202034
Colin O'Dell

Lead Web Developer at Unleashed Technologies. Author of league/commonmark. Conference speaker. Arduino enthusiast.

Contact

Replications

Did you replicate this project? Share it!

I made one

Love this project? Think it could be improved? Tell us what you think!

Give feedback

Comments

Similar projects you might like

Secure Simple Remote Access for Camera Viewing
Easy
  • 2,685
  • 11

Full instructions

Turn your RPi into a full featured camera with remote access, end2end encryption, and easy user enrollment.

Smartphone Connected Home Door Lock
Easy
  • 309
  • 3

Full instructions

An internet-connected, servo-driven deadbolt actuator that can be operated remotely using a smartphone.

Cortana Skills and Bot Framework on Raspberry Pi
Easy
  • 289
  • 2

Protip

Using Bot Framework on Raspberry Pi, using Cortana Skills to Echo sent message.

PiCorder
Easy
  • 4,582
  • 14

Full instructions

A simple Pi-based camcorder using the very nice HyperPixel touchscreen from Pimoroni.

Setting Up Your Pi Cap on the Raspberry Pi 1, 2, or 3
Easy
  • 151
  • 2

Protip

Follow this tutorial to set up your Pi Cap with a Raspberry Pi 1 A+/B+, Raspberry Pi 2, or Raspberry Pi 3.

Setting Up Your Pi Cap on the Raspberry Pi Zero
Easy
  • 165
  • 2

Protip

Follow this tutorial to set up your Pi Cap with a Raspberry Pi Zero.

ProjectsCommunitiesTopicsContestsLiveAppsBetaFree StoreBlogAdd projectSign up / Login