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

Test Your Internet Speed Using a Raspberry Pi + Ubidots
Easy
  • 413
  • 6

Protip

Use your Raspberry Pi to measure any internet connection speed with Ubidots.

Chicken Coop Livestream
Easy
  • 2,773
  • 11

Full instructions

We have two chickens, and their coop is located at the back of the garden. I decided to build a Pi-based streaming device to check on them.

Dataplicity “Custom Actions" for controlling RPi GPIOs
Easy
  • 46
  • 1

Protip

With this example you will control GPIOs on your Raspberry Pi from the Dataplicity Mobile app using "Custom Actions" feature.

Raspberry Pi Shutdown / Restart Button
Easy
  • 5,353
  • 20

Full instructions

Building an installation project? Shut down or reboot your project safely, without a keyboard or SSH!

DIY Raspberry Pi Indoor Outdoor Webcam
Easy
  • 1,626
  • 6

Full instructions

Ever want to monitor your home but don't want to buy a $100 1080p webcam? Well I did, and you can too!

Single Device to Control Many Arduinos with NRF24L01+ Sensor
Easy
  • 1,541
  • 9

Turns your fan on and off by sensing the temperature.

Sign up / LoginProjectsPlatformsTopicsContestsLiveAppsBetaFree StoreBlog