ring

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 12, 2020 License: MIT Imports: 5 Imported by: 0

README

Ring

Version PkgGoDev License Go version

Ring is a wrapper of rpi-ws281x-go specialized in controlling ring-shaped LEDs.

The library adds the ability to use layers to do complex animations. Each layer supports color transparency and blending is handled automatically.

Compilation

Compiling directly on a Raspberry Pi might take too long. The recommended way to compile this library is to cross-compile using a Docker container.

Follow the guide from rpi-ws281x-go to learn how.

Running

Because rpi-ws281x needs to access /dev/mem to create correct pwm timings, you will need to run the compiled binary with root permissions.

Example

Boss: "Can you make a pulsating white background with colorful rotating lights and a blinking cyan light?"

You:

ring.gif

To create the animation above, try the following code:

package main

import (
	"bufio"
	"fmt"
	"image/color"
	"log"
	"math"
	"os"
	"sync"
	"time"

	"github.com/cgxeiji/ring"
)

func main() {
	// Initialize the ring.
	r, err := ring.New(&ring.Options{
		LedCount:      12,  // adjust this to the number of LEDs you have
		MaxBrightness: 180, // value from 0 to 255
	})
	r.Offset(-math.Pi / 3) // you can set a rotation offset for the ring
	if err != nil {
		log.Fatal(err)
	}
	// Make sure to properly close the ring.
	defer r.Close()

	// Create a new layer.  This will be a static white background.
	bg, err := ring.NewLayer(&ring.LayerOptions{
		Resolution:  1,                 // set to 1 pixel because it is a uniform color background
		ContentMode: ring.ContentScale, // this scales the pixel to the whole ring
	})
	if err != nil {
		log.Fatal(err)
	}
	// Set all pixels of the layer to white.
	bg.SetAll(color.White)
	// Add the layer to the ring.
	r.AddLayer(bg)

	// Create a mask layer.  This will fade the background.
	bgMask, err := ring.NewLayer(&ring.LayerOptions{
		Resolution: 1, // set to 1 pixel because it is a uniform mask
	})
	if err != nil {
		log.Fatal(err)
	}
	r.AddLayer(bgMask)

	// Render the ring.
	if err := r.Render(); err != nil {
		log.Fatal(err)
	}

	// Wait for 1 second to see the beauty of the freshly rendered layer.
	time.Sleep(1 * time.Second)

	// Create another layer.  This will set 3 pixels to red, green and blue,
	// and a hidden purple pixel with transparency of 200, that rotate
	// counter-clockwise.
	triRotate, err := ring.NewLayer(&ring.LayerOptions{
		Resolution: 48,
	})
	if err != nil {
		log.Fatal(err)
	}
	// We can immediately add the layer to the ring.  By default, new layers
	// are initialized with transparent pixels.  The new layer is added on top
	// of the previous layers.
	r.AddLayer(triRotate)

	// Set the colors.
	triRotate.SetPixel(0, color.NRGBA{128, 0, 0, 200})    // dark red
	triRotate.SetPixel(3, color.NRGBA{0, 128, 0, 200})    // dark green
	triRotate.SetPixel(6, color.NRGBA{0, 0, 128, 200})    // dark blue
	triRotate.SetPixel(24, color.NRGBA{128, 0, 255, 200}) // purple
	// Render the ring.
	if err := r.Render(); err != nil {
		log.Fatal(err)
	}

	// Wait for 1 second to see the beauty of both layers.
	time.Sleep(1 * time.Second)

	// Create another layer. This will set a pixel that will blink every 500ms.
	blink, err := ring.NewLayer(&ring.LayerOptions{
		Resolution:  3,                // we are going to set the 3rd pixel (index: 2) to blink
		ContentMode: ring.ContentCrop, // this crops the layer to avoid repetition
	})
	if err != nil {
		log.Fatal(err)
	}
	// Add the layer to the ring. This will be on top of the previous two
	// layers.
	r.AddLayer(blink)

	// Set the color. We can use any variable that implements the color.Color
	// interface.
	blink.SetPixel(2, color.CMYK{255, 0, 0, 0})
	// Render the ring.
	if err := r.Render(); err != nil {
		log.Fatal(err)
	}

	// Wait for 1 second and enjoy the view.
	time.Sleep(1 * time.Second)

	/* ANIMATION SETUP */
	done := make(chan struct{})   // this will cancel all animations
	render := make(chan struct{}) // this will request a concurrent-safe render
	var ws sync.WaitGroup         // this makes sure we close all goroutines

	/* render goroutine */
	ws.Add(1)
	go func() {
		defer ws.Done()
		for {
			select {
			case <-done:
				return
			case <-render:
				if err := r.Render(); err != nil {
					log.Fatal(err)
				}
			}
		}
	}()

	/* fading goroutine */
	ws.Add(1)
	go func() {
		defer ws.Done()
		c := color.NRGBA{0, 0, 0, 0}
		step := uint8(5)
		for {
			for a := uint8(0); a < 255; a += step {
				c.A = a
				bgMask.SetAll(c)
				select {
				case <-done:
					return
				case render <- struct{}{}:
				}
				time.Sleep(20 * time.Millisecond)
			}
			for a := uint8(255); a > 0; a -= step {
				c.A = a
				bgMask.SetAll(c)
				select {
				case <-done:
					return
				case render <- struct{}{}:
				}
				time.Sleep(20 * time.Millisecond)
			}
		}
	}()

	/* rotation goroutine */
	ws.Add(1)
	go func() {
		defer ws.Done()
		for {
			for a := 0.0; a < math.Pi*2; a += 0.01 {
				triRotate.Rotate(a)
				select {
				case <-done:
					return
				case render <- struct{}{}:
				}
				time.Sleep(20 * time.Millisecond)
			}
		}
	}()

	/* blinking goroutine */
	ws.Add(1)
	go func() {
		defer ws.Done()
		c := color.CMYK{255, 0, 0, 0}
		timer := time.NewTicker(500 * time.Millisecond)
		on := true
		for {
			select {
			case <-done:
				return
			case <-timer.C:
				if on {
					blink.SetPixel(2, color.Transparent)
					on = false
				} else {
					blink.SetPixel(2, c)
					on = true
				}
				select {
				case <-done:
					return
				case render <- struct{}{}:
				}
			}
		}
	}()

	fmt.Println("Press [ENTER] to exit")
	stdin := bufio.NewReader(os.Stdin)
	stdin.ReadString('\n')

	// Stop all animations
	close(done)
	// Wait for goroutines to exit
	ws.Wait()

	// Remember that we called a defer `r.Close()` at the beginning of the
	// code. This will turn off the LEDs and clean up the resources used by the
	// ring before exiting. Otherwise, the ring will stay on with the latest
	// render.
}

Documentation

Overview

Example
package main

import (
	"bufio"
	"fmt"
	"image/color"
	"log"
	"math"
	"os"
	"sync"
	"time"

	"github.com/cgxeiji/ring"
)

func main() {
	// Initialize the ring.
	r, err := ring.New(&ring.Options{
		LedCount:      12,  // adjust this to the number of LEDs you have
		MaxBrightness: 180, // value from 0 to 255
	})
	r.Offset(-math.Pi / 3) // you can set a rotation offset for the ring
	if err != nil {
		log.Fatal(err)
	}
	// Make sure to properly close the ring.
	defer r.Close()

	// Create a new layer.  This will be a static white background.
	bg, err := ring.NewLayer(&ring.LayerOptions{
		Resolution:  1,                 // set to 1 pixel because it is a uniform color background
		ContentMode: ring.ContentScale, // this scales the pixel to the whole ring
	})
	if err != nil {
		log.Fatal(err)
	}
	// Set all pixels of the layer to white.
	bg.SetAll(color.White)
	// Add the layer to the ring.
	r.AddLayer(bg)

	// Create a mask layer.  This will fade the background.
	bgMask, err := ring.NewLayer(&ring.LayerOptions{
		Resolution: 1, // set to 1 pixel because it is a uniform mask
	})
	if err != nil {
		log.Fatal(err)
	}
	r.AddLayer(bgMask)

	// Render the ring.
	if err := r.Render(); err != nil {
		log.Fatal(err)
	}

	// Wait for 1 second to see the beauty of the freshly rendered layer.
	time.Sleep(1 * time.Second)

	// Create another layer.  This will set 3 pixels to red, green and blue,
	// and a hidden purple pixel with transparency of 200, that rotate
	// counter-clockwise.
	triRotate, err := ring.NewLayer(&ring.LayerOptions{
		Resolution: 48,
	})
	if err != nil {
		log.Fatal(err)
	}
	// We can immediately add the layer to the ring.  By default, new layers
	// are initialized with transparent pixels.  The new layer is added on top
	// of the previous layers.
	r.AddLayer(triRotate)

	// Set the colors.
	triRotate.SetPixel(0, color.NRGBA{128, 0, 0, 200})    // dark red
	triRotate.SetPixel(3, color.NRGBA{0, 128, 0, 200})    // dark green
	triRotate.SetPixel(6, color.NRGBA{0, 0, 128, 200})    // dark blue
	triRotate.SetPixel(24, color.NRGBA{128, 0, 255, 200}) // purple
	// Render the ring.
	if err := r.Render(); err != nil {
		log.Fatal(err)
	}

	// Wait for 1 second to see the beauty of both layers.
	time.Sleep(1 * time.Second)

	// Create another layer. This will set a pixel that will blink every 500ms.
	blink, err := ring.NewLayer(&ring.LayerOptions{
		Resolution:  3,                // we are going to set the 3rd pixel (index: 2) to blink
		ContentMode: ring.ContentCrop, // this crops the layer to avoid repetition
	})
	if err != nil {
		log.Fatal(err)
	}
	// Add the layer to the ring. This will be on top of the previous two
	// layers.
	r.AddLayer(blink)

	// Set the color. We can use any variable that implements the color.Color
	// interface.
	blink.SetPixel(2, color.CMYK{255, 0, 0, 0})
	// Render the ring.
	if err := r.Render(); err != nil {
		log.Fatal(err)
	}

	// Wait for 1 second and enjoy the view.
	time.Sleep(1 * time.Second)

	/* ANIMATION SETUP */
	done := make(chan struct{})   // this will cancel all animations
	render := make(chan struct{}) // this will request a concurrent-safe render
	var ws sync.WaitGroup         // this makes sure we close all goroutines

	/* render goroutine */
	ws.Add(1)
	go func() {
		defer ws.Done()
		for {
			select {
			case <-done:
				return
			case <-render:
				if err := r.Render(); err != nil {
					log.Fatal(err)
				}
			}
		}
	}()

	/* fading goroutine */
	ws.Add(1)
	go func() {
		defer ws.Done()
		c := color.NRGBA{0, 0, 0, 0}
		step := uint8(5)
		for {
			for a := uint8(0); a < 255; a += step {
				c.A = a
				bgMask.SetAll(c)
				select {
				case <-done:
					return
				case render <- struct{}{}:
				}
				time.Sleep(20 * time.Millisecond)
			}
			for a := uint8(255); a > 0; a -= step {
				c.A = a
				bgMask.SetAll(c)
				select {
				case <-done:
					return
				case render <- struct{}{}:
				}
				time.Sleep(20 * time.Millisecond)
			}
		}
	}()

	/* rotation goroutine */
	ws.Add(1)
	go func() {
		defer ws.Done()
		for {
			for a := 0.0; a < math.Pi*2; a += 0.01 {
				triRotate.Rotate(a)
				select {
				case <-done:
					return
				case render <- struct{}{}:
				}
				time.Sleep(20 * time.Millisecond)
			}
		}
	}()

	/* blinking goroutine */
	ws.Add(1)
	go func() {
		defer ws.Done()
		c := color.CMYK{255, 0, 0, 0}
		timer := time.NewTicker(500 * time.Millisecond)
		on := true
		for {
			select {
			case <-done:
				return
			case <-timer.C:
				if on {
					blink.SetPixel(2, color.Transparent)
					on = false
				} else {
					blink.SetPixel(2, c)
					on = true
				}
				select {
				case <-done:
					return
				case render <- struct{}{}:
				}
			}
		}
	}()

	fmt.Println("Press [ENTER] to exit")
	stdin := bufio.NewReader(os.Stdin)
	stdin.ReadString('\n')

	// Stop all animations
	close(done)
	// Wait for goroutines to exit
	ws.Wait()

	// Remember that we called a defer `r.Close()` at the beginning of the
	// code. This will turn off the LEDs and clean up the resources used by the
	// ring before exiting. Otherwise, the ring will stay on with the latest
	// render.
}
Output:

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ContentMode

type ContentMode uint8

ContentMode defines how the layer will be rendered.

const (
	// ContentTile sets the layer to crop its content if it is larger that the ring
	// and to repeat the content.
	ContentTile ContentMode = iota
	// ContentCrop sets the layer to crop its content if it is larger than the ring
	// and does not repeat the content.
	ContentCrop
	// ContentScale sets the layer to scale up or down its content to fit the ring.
	ContentScale
)

type Layer

type Layer struct {
	// contains filtered or unexported fields
}

Layer represents a drawable layer of the LED ring.

func NewLayer

func NewLayer(options *LayerOptions) (*Layer, error)

NewLayer creates a new drawable layer.

func (*Layer) Rotate

func (l *Layer) Rotate(angle float64)

Rotate sets the rotation of the layer. A positive angle makes a counter-clockwise rotation.

func (*Layer) SetAll

func (l *Layer) SetAll(c color.Color)

SetAll sets all the pixels of a layer to an uniform color.

func (*Layer) SetPixel

func (l *Layer) SetPixel(i int, c color.Color)

SetPixel sets the color of a single pixel in the layer.

type LayerOptions

type LayerOptions struct {
	// Resolution sets the number of pixels a layer has. Usually, this is set
	// to the same number of LEDs the ring has.
	Resolution int
	// ContentMode sets how the layer will be rendered (default: Tile).
	ContentMode ContentMode
}

LayerOptions is the list of options of a layer.

type Options

type Options struct {
	// LedCount is the number of LEDs in the ring.
	LedCount int
	// MinBrightness is the minimum output of the LED> Goes from 0 to 255
	// (default: 0).
	// MaxBrightness is the maximum output of the LED. Goes from 0 to 255
	// (default: 64).
	//
	// The color will be scaled to these values. For example, color.RGBA{255,
	// 255, 255, 255} will output led(R: 128, G: 128, B: 128) if MaxBrightness
	// is set to 128, and color.RGBA(0, 0, 0, 0) will output led(R: 10, G: 10,
	// B: 10) if MinBrightness is set to 10.
	MinBrightness, MaxBrightness int
	// GpioPin is the GPIO pin on the Raspberry Pi with PWM output (default:
	// GPIO 18). *Do not confuse with the physical pin number*
	GpioPin int
}

Options is the list of ring options.

type Ring

type Ring struct {
	// contains filtered or unexported fields
}

Ring represents the WS2811 LED device.

func New

func New(options *Options) (*Ring, error)

New creates a new LED ring with given options.

func (*Ring) AddLayer

func (r *Ring) AddLayer(l *Layer)

AddLayer adds a drawable layer to the ring.

func (*Ring) Close

func (r *Ring) Close()

Close turns off the LED ring and closes the device.

func (*Ring) Offset

func (r *Ring) Offset(rotation float64)

Offset sets an angular offset (in radians) to render the layers. A positive angle rotates counter-clockwise.

func (*Ring) Render

func (r *Ring) Render() error

Render updates the LED ring.

func (*Ring) Size

func (r *Ring) Size() int

Size returns the total number of LEDs of the ring.

func (*Ring) TurnOff added in v0.1.1

func (r *Ring) TurnOff()

TurnOff tuns off the LED ring without closing the device.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL