loupedeck

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Dec 10, 2023 License: Apache-2.0 Imports: 19 Imported by: 2

README

loupedeck

This provides somewhat minimal support for talking to a Loupedeck Live from Go. Supported features:

  • Reacting to button, knob, and touchscreen events.
  • Displaying images on any of the 3 displays.

In addition, widgets and convienence functions are provided for a couple higher-level input abstractions.

This code talks directly to the Loupedeck hardware, and doesn't go through Loupedeck's Windows/Mac software. It's largely intended for using the Loupedeck as a controller for semi-embedded devices, like a DMX lighting controller. This code should work under Linux/Windows/Mac/etc, although only Linux on a Raspberry Pi has been tested. It talks to the Loupedeck via the go.bug.st/serial library ; any platform with working USB serial support in the library will likely work just fine.

This is only tested with a Loupedeck Live; other Loupedeck models use the same protocol but have different numbers of displays and controls, and will need minor updates to work correctly.

Sample code

	l, err := loupedeck.ConnectAuto()
	if err != nil { ... }

	// Create 3 variables for holding dial positions, and add a callback for whenever they change.
	light1 := loupedeck.NewWatchedInt(0)
	light1.AddWatcher(func (i int) { fmt.Printf("DMX 1->%d\n", i) })
	light2 := loupedeck.NewWatchedInt(0)
	light2.AddWatcher(func (i int) { fmt.Printf("DMX 3->%d\n", i) })
	light3 := loupedeck.NewWatchedInt(0)
	light3.AddWatcher(func (i int) { fmt.Printf("DMX 5->%d\n", i) })

        // Use the left display and the 3 left knobs to adjust 3 independent lights between 0 and 100.
	// Whenever these change, the callbacks from 'AddWatcher' (above) will be called.
	l.NewTouchDial(loupedeck.DisplayLeft, light1, light2, light3, 0, 100)
	
	// Define the 'Circle' button (bottom left) to function as an "off" button for lights 1-3.
	// Similar to NewTouchDial, the callbacks from `AddWatcher` will be called.  This
	// includes an implicit call to the TouchDial's Draw() function, so just calling 'Set'
	// will update the values, the lights (if the callbacks above actually did anything useful),
	// and the Loupedeck.
	
	l.BindButton(loupedeck.Circle, func (b loupedeck.Button, s loupedeck.ButtonStatus){
		light1.Set(0)
		light2.Set(0)
		light3.Set(0)
	})
		
	l.Listen()

Disclaimer

This is not an official Google project.

Documentation

Overview

Package loupedeck provides a Go interface for talking to a Loupedeck Live control surface.

The Loupedeck Live with firmware 1.x appeared as a USB network device that we talked to via HTTP+websockets, but newer firmware looks like a serial device that talks a mutant version of the Websocket protocol.

See https://github.com/foxxyz/loupedeck for Javascript code for talking to the Loupedeck Live; it supports more of the Loupedeck's functionality.

Example
package main

import (
	"fmt"
	"github.com/scottlaird/loupedeck"
)

func main() {
	l, err := loupedeck.ConnectAuto()
	if err != nil {
		panic(err)
	}

	light1 := loupedeck.NewWatchedInt(0)
	light1.AddWatcher(func(i int) { fmt.Printf("DMX 1->%d\n", i) })
	light2 := loupedeck.NewWatchedInt(0)
	light2.AddWatcher(func(i int) { fmt.Printf("DMX 3->%d\n", i) })
	light3 := loupedeck.NewWatchedInt(0)
	light3.AddWatcher(func(i int) { fmt.Printf("DMX 5->%d\n", i) })

	l.NewTouchDial(l.GetDisplay("left"), light1, light2, light3, 0, 100)

	// Define the 'Circle' button (bottom left) to function as an "off" button.
	l.BindButton(loupedeck.Circle, func(b loupedeck.Button, s loupedeck.ButtonStatus) {
		light1.Set(0)
		light2.Set(0)
		light3.Set(0)
	})

	l.Listen()
}
Output:

Index

Examples

Constants

View Source
const (
	// CTKnob is the large knob in the center of the Loupedeck CT
	CTKnob Knob = 0
	// Knob1 is the upper left knob.
	Knob1 = 1
	// Knob2 is the middle left knob.
	Knob2 = 2
	// Knob3 is the bottom left knob.
	Knob3 = 3
	// Knob4 is the upper right knob.
	Knob4 = 4
	// Knob5 is the middle right knob.
	Knob5 = 5
	// Knob6 is the bottom right knob.
	Knob6 = 6
)
View Source
const (
	// TouchLeft indicates that the left touchscreen area, near the leftmost knobs has been touched.
	TouchLeft TouchButton = 1
	// TouchRight indicates that hte right touchscreen area, near the rightmost knobs has been touched.
	TouchRight = 2
	Touch1     = 3
	Touch2     = 4
	Touch3     = 5
	Touch4     = 6
	Touch5     = 7
	Touch6     = 8
	Touch7     = 9
	Touch8     = 10
	Touch9     = 11
	Touch10    = 12
	Touch11    = 13
	Touch12    = 14
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Button

type Button uint16

Button represents a physical button on the Loupedeck Live. This includes the 8 buttons at the bottom of the device as well as the 'click' function of the 6 dials.

const (
	KnobPress1 Button = 1
	KnobPress2 Button = 2
	KnobPress3 Button = 3
	KnobPress4 Button = 4
	KnobPress5 Button = 5
	KnobPress6 Button = 6

	// Circle is sent when the left-most hardware button under the
	// display is clicked.  This has a circle icon on the
	// Loupedeck Live, but is unfortunately labeled "1" on the
	// Loupedeck CT.
	Circle  Button = 7
	Button1 Button = 8
	Button2 Button = 9
	Button3 Button = 10
	Button4 Button = 11
	Button5 Button = 12
	Button6 Button = 13
	Button7 Button = 14

	// CT-specific buttons.
	CTCircle Button = 15
	Undo     Button = 16
	Keyboard Button = 17
	Enter    Button = 18
	Save     Button = 19
	LeftFn   Button = 20
	Up       Button = 21
	A        Button = 21
	Left     Button = 22
	C        Button = 22
	RightFn  Button = 23
	Down     Button = 24
	B        Button = 24
	Right    Button = 25
	D        Button = 25
	E        Button = 26
)

type ButtonFunc

type ButtonFunc func(Button, ButtonStatus)

ButtonFunc is a function signature used for callbacks on Button events. When a specified event happens, the ButtonFunc is called with parameters specifying which button was pushed and what its current state is.

type ButtonStatus

type ButtonStatus uint8

ButtonStatus represents the state of Buttons.

const (
	// ButtonDown indicates that a button has just been pressed.
	ButtonDown ButtonStatus = 0
	// ButtonUp indicates that a button was just released.
	ButtonUp = 1
)

type Display

type Display struct {
	Name string
	// contains filtered or unexported fields
}

Display is part of the Loupedeck protocol, used to identify which of the displays on the Loupedeck to write to.

func (*Display) Draw added in v1.0.0

func (d *Display) Draw(im image.Image, xoff, yoff int)

Draw draws an image onto a specific display of the Loupedeck Live. The device has 3 seperate displays, the left segment (by knobs 1-3), the right segment (by knobs 4-6) and the main/center segment (underneath the 4x3 array of touch buttons). Drawing subsets of a display is explicitly allowed; writing a 90x90 block of pixels to the main display will only overwrite one button's worth of image, and will not touch other pixels.

Most Loupedeck screens are little-endian, except for the knob screen on the Loupedeck CT, which is big-endian. This does not deal with this case correctly yet.

func (*Display) Height added in v1.0.0

func (d *Display) Height() int

Height returns the height (in pixels) of the Loupedeck's displays.

func (*Display) Width added in v1.0.0

func (d *Display) Width() int

Width returns the width (in pixels) of the Loupedeck's displays.

type IntKnob

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

IntKnob is an abstraction over the Loupedeck Live's Knobs. The IntKnob turns left/right dial actions into incrememnting and decrementing an integer within a specified range. In addition, the 'click' action of the knob resets the IntKnob's value to 0.

func (*IntKnob) Get

func (k *IntKnob) Get() int

Get returns the current value of the IntKnob.

func (*IntKnob) Inc

func (k *IntKnob) Inc(v int)

Inc incremements (or decrements) the current value of the IntKnob by a specified amount. This triggers a callback on the WatchedInt that underlies the IntKnob.

func (*IntKnob) Set

func (k *IntKnob) Set(v int)

Set sets the current value of the IntKnob, triggering any callbacks set on the WatchedInt that underlies the IntKnob.

type Knob

type Knob uint16

Knob represents the 6 knobs on the Loupedeck Live.

type KnobFunc

type KnobFunc func(Knob, int)

KnobFunc is a function signature used for callbacks on Knob events, similar to ButtonFunc's use with Button events. The exact use of the second parameter depends on the use; in some cases it's simply +1/-1 (for right/left button turns) and in other cases it's the current value of the dial.

type Loupedeck

type Loupedeck struct {
	Vendor   string
	Product  string
	Model    string
	Version  string
	SerialNo string
	// contains filtered or unexported fields
}

Loupedeck describes a Loupedeck device.

func ConnectAuto

func ConnectAuto() (*Loupedeck, error)

ConnectAuto connects to a Loupedeck Live by automatically locating the first USB Loupedeck device in the system. If you have more than one device and want to connect to a specific one, then use ConnectPath().

func ConnectPath

func ConnectPath(serialPath string) (*Loupedeck, error)

ConnectPath connects to a Loupedeck Live via a specified serial device. If successful it returns a new Loupedeck.

func (*Loupedeck) BindButton

func (l *Loupedeck) BindButton(b Button, f ButtonFunc)

BindButton sets a callback for actions on a specific button. When the Button is pushed down, then the provided ButtonFunc is called.

func (*Loupedeck) BindButtonUp

func (l *Loupedeck) BindButtonUp(b Button, f ButtonFunc)

BindButtonUp sets a callback for actions on a specific button. When the Button is released, then the provided ButtonFunc is called.

func (*Loupedeck) BindKnob

func (l *Loupedeck) BindKnob(k Knob, f KnobFunc)

BindKnob sets a callback for actions on a specific knob. When the Knob is turned then the provided KnobFunc is called.

func (*Loupedeck) BindTouch

func (l *Loupedeck) BindTouch(b TouchButton, f TouchFunc)

BindTouch sets a callback for actions on a specific TouchButton. When the TouchButton is pushed down, then the provided TouchFunc is called.

func (*Loupedeck) BindTouchUp

func (l *Loupedeck) BindTouchUp(b TouchButton, f TouchFunc)

BindTouchUp sets a callback for actions on a specific TouchButton. When the TouchButton is released, then the provided TouchFunc is called.

func (*Loupedeck) Close

func (l *Loupedeck) Close()

Close closes the connection to the Loupedeck.

func (*Loupedeck) Face

func (l *Loupedeck) Face() font.Face

Face returns the current font.Face in use for writing text onto the Loupedeck's graphical buttons.

func (*Loupedeck) FontDrawer

func (l *Loupedeck) FontDrawer() font.Drawer

FontDrawer returns a font.Drawer object configured to writing text onto the Loupedeck's graphical buttons.

func (*Loupedeck) GetDisplay added in v1.0.0

func (l *Loupedeck) GetDisplay(name string) *Display

GetDisplay returns a Display object with a given name if it exists, otherwise it returns nil.

Traditional Loupedeck devices had 3 displays, Left, Center, and Right. Newer devices make all 3 look like a single display, and it's impossible to know at compile-time what any given device will support, so we need to create them dynamically and then look them up.

In addition, some devices (like the Loupedeck CT) have additional displays.

For now, common display names are:

  • left (on all devices, emulated on newer hardware)
  • right (on all devices, emulated on newer hardware)
  • main (on all devices, emulated on newer hardware)
  • dial (Loupedeck CT only)
  • main (only on newer hardware)

func (*Loupedeck) IntKnob

func (l *Loupedeck) IntKnob(k Knob, min int, max int, watchedint *WatchedInt) *IntKnob

IntKnob implements a generic dial knob using the specified Loupedeck Knob. It binds the dial function of the knob to increase/decrease the IntKnob's value and binds the button function of the knob to reset the value to 0. Basically, spin the dial and it changes, and click and it resets.

func (*Loupedeck) Listen

func (l *Loupedeck) Listen()

Listen waits for events from the Loupedeck and calls callbacks as configured.

func (*Loupedeck) NewMessage added in v1.0.0

func (l *Loupedeck) NewMessage(messageType MessageType, data []byte) *Message

NewMessage creates a new low-level Loupedeck message with a specified type and data. This isn't generally needed for end-use.

func (*Loupedeck) NewMultiButton

func (l *Loupedeck) NewMultiButton(watchedint *WatchedInt, b TouchButton, im image.Image, val int) *MultiButton

NewMultiButton creates a new MultiButton, bound to an existing WatchedInt. One image.Image and value must be provided; this is the first image (and default value) for the MultiButton. Additional images and values can be added via the Add function.

func (*Loupedeck) NewTouchDial

func (l *Loupedeck) NewTouchDial(display *Display, w1, w2, w3 *WatchedInt, min, max int) *TouchDial

NewTouchDial creates a TouchDial.

func (*Loupedeck) ParseMessage added in v1.0.0

func (l *Loupedeck) ParseMessage(b []byte) (*Message, error)

ParseMessage creates a Loupedeck Message from a block of bytes. This is used to decode incoming messages from a Loupedeck, and shouldn't generally be needed outside of this library.

func (*Loupedeck) Send added in v1.0.0

func (l *Loupedeck) Send(m *Message) error

Send sends a message to the specified device.

func (*Loupedeck) SendAndWait added in v1.0.0

func (l *Loupedeck) SendAndWait(m *Message, timeout time.Duration) (*Message, error)

SendAndWait sends a message and then waits for a response, returning the response message.

func (*Loupedeck) SendWithCallback added in v1.0.0

func (l *Loupedeck) SendWithCallback(m *Message, c transactionCallback) error

SendWithCallback sends a message to the specified device and registers a callback. When (or if) the Loupedeck sends a response to the message, the callback function will be called and provided with the response message.

func (*Loupedeck) SetBrightness

func (l *Loupedeck) SetBrightness(b int) error

SetBrightness sets the overall brightness of the Loupedeck display.

func (*Loupedeck) SetButtonColor

func (l *Loupedeck) SetButtonColor(b Button, c color.RGBA) error

SetButtonColor sets the color of a specific Button. The Loupedeck Live allows the 8 buttons below the display to be set to specific colors, however the 'Circle' button's colors may be overridden to show the status of the Loupedeck Live's connection to the host.

func (*Loupedeck) SetDefaultFont

func (l *Loupedeck) SetDefaultFont() error

SetDefaultFont sets the default font for drawing onto buttons.

TODO(laird): Actually make it easy to override this default.

func (*Loupedeck) SetDisplays added in v1.0.0

func (l *Loupedeck) SetDisplays()

SetDisplays configures the Loupdeck's displays based on the hardware ID of the conencted device.

func (*Loupedeck) TextInBox

func (l *Loupedeck) TextInBox(x, y int, s string, fg, bg color.Color) (image.Image, error)

TextInBox writes a specified string into a x,y pixel image.Image, using the specified foreground and background colors. The font size used will be chosen to maximize the size of the text.

type Message added in v1.0.0

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

Message defines a message for communicating with the Loupedeck over USB. All communication with the Loupedeck occurs via Messages, but most application software can use higher-level functions in this library and never touch messages directly.

The exception would be wanting to use a feature like vibration that isn't currently supported in this library.

func (*Message) String added in v1.0.0

func (m *Message) String() string

function String() returns a human-readable form of the message for debugging use.

type MessageType added in v1.0.0

type MessageType byte

MessageType is a uint16 used to identify various commands and actions needed for the Loupedeck protocol.

const (
	ButtonPress    MessageType = 0x00
	KnobRotate     MessageType = 0x01
	SetColor       MessageType = 0x02
	Serial         MessageType = 0x03
	Reset          MessageType = 0x06
	Version        MessageType = 0x07
	SetBrightness  MessageType = 0x09
	MCU            MessageType = 0x0d
	Draw           MessageType = 0x0f
	WriteFramebuff MessageType = 0x10
	SetVibration   MessageType = 0x1b
	Touch          MessageType = 0x4d
	TouchCT        MessageType = 0x52
	TouchEnd       MessageType = 0x6d
	TouchEndCT     MessageType = 0x72
)

See 'COMMANDS' in https://github.com/foxxyz/loupedeck/blob/master/constants.js

type MultiButton

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

MultiButton implements a multi-image touch button for the Loupedeck Live that rotates between a set of images for each touch, changing its value (and image) for each touch. Once the last image is touched, it loops back to the first image in the set.

func (*MultiButton) Add

func (m *MultiButton) Add(im image.Image, value int)

Add adds an additional image+value to a MultiButton.

func (*MultiButton) Advance

func (m *MultiButton) Advance()

Advance moves to the next value of the MultiButton, updating the display and underlying WatchedInt.

func (*MultiButton) Draw

func (m *MultiButton) Draw()

Draw redraws the MultiButton on the Loupedeck live.

func (*MultiButton) GetCur

func (m *MultiButton) GetCur() int

GetCur gets the current value of the MultiButton. The value returned will match one of the values from either NewMultiButton or multibutton.Add, depending on which image is currently displayed.

type SerialWebSockConn

type SerialWebSockConn struct {
	Name            string
	Port            serial.Port
	Vendor, Product string
}

SerialWebSockConn implements an external dialer interface for the Gorilla that allows it to talk to Loupedeck's weird websockets-over-serial-over-USB setup.

The Gorilla websockets library can use an external dialer interface, which means that we can use it *mostly* unmodified to talk to a serial device instead of a network device. We just need to provide something that matches the net.Conn interface. Here's a minimal implementation.

func ConnectSerialAuto

func ConnectSerialAuto() (*SerialWebSockConn, error)

ConnectSerialAuto connects to the first compatible Loupedeck in the system. To connect to a specific Loupedeck, use ConnectSerialPath.

func ConnectSerialPath

func ConnectSerialPath(serialPath string) (*SerialWebSockConn, error)

ConnectSerialPath connects to a specific Loupedeck, using the path to the USB serial device as a key.

func (*SerialWebSockConn) Close

func (l *SerialWebSockConn) Close() error

Close closed the connection.

func (*SerialWebSockConn) LocalAddr

func (l *SerialWebSockConn) LocalAddr() net.Addr

LocalAddr is needed for Gorilla compatibility, but doesn't actually make sense with serial ports.

func (*SerialWebSockConn) Read

func (l *SerialWebSockConn) Read(b []byte) (n int, err error)

Read reads bytes from the connected serial port.

func (*SerialWebSockConn) RemoteAddr

func (l *SerialWebSockConn) RemoteAddr() net.Addr

RemoteAddr is needed for Gorilla compatibility, but doesn't actually make sense with serial ports.

func (*SerialWebSockConn) SetDeadline

func (l *SerialWebSockConn) SetDeadline(t time.Time) error

SetDeadline is needed for Gorilla compatibility, but doesn't actually make sense with serial ports.

func (*SerialWebSockConn) SetReadDeadline

func (l *SerialWebSockConn) SetReadDeadline(t time.Time) error

SetReadDeadline is needed for Gorilla compatibility, but doesn't actually make sense with serial ports.

func (*SerialWebSockConn) SetWriteDeadline

func (l *SerialWebSockConn) SetWriteDeadline(t time.Time) error

SetWriteDeadline is needed for Gorilla compatibility, but doesn't actually make sense with serial ports.

func (*SerialWebSockConn) Write

func (l *SerialWebSockConn) Write(b []byte) (n int, err error)

Write sends bytes to the connected serial port.

type TouchButton

type TouchButton uint16

TouchButton represents the regions of the touchpad on the Loupedeck Live.

type TouchDial

type TouchDial struct {
	Knob1, Knob2, Knob3 *IntKnob
	// contains filtered or unexported fields
}

TouchDial implements a "smart" bank of dials for the Loupedeck Live. If displayid is DisplayLeft then the TouchDial will display knobs 1-3 on the left display, otherwise it will show knobs 4-6 on the right display.

The display will show the current value of the WatchedInt for each knob. Turning the knob will increment/decrement each value as expected. Clicking the knob will zero the value. Sliding up or down on the LCD display will increase or decrease all 3 knob values at once.

func (*TouchDial) Draw

func (t *TouchDial) Draw()

Draw updates the display for a TouchDial.

type TouchFunc

type TouchFunc func(TouchButton, ButtonStatus, uint16, uint16)

TouchFunc is a function signature used for callbacks on TouchButton events, similar to ButtonFunc and KnobFunc. The parameters are:

  • The TouchButton touched
  • The ButtonStatus (down/up)
  • The X location touched (relative to the whole display)
  • The Y location touched (relative to the whole display)

type WatchFunc

type WatchFunc func(int)

WatchFunc is used for callbacks for changes to a WatchedInt.

type WatchedInt

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

WatchedInt wraps an int with zero or more callback watchers; whenever the value of the int changes (via Set), all of the callbacks will be called. This is used to implement a sane model for Loupedeck Live knobs, etc. Calling 'myknob.Set(3)' will update any impacted displays and should trigger any required underlying behaviour.

func NewWatchedInt

func NewWatchedInt(value int) *WatchedInt

NewWatchedInt creates a new WatchedInt with the specified initial value.

func (*WatchedInt) AddWatcher

func (w *WatchedInt) AddWatcher(f WatchFunc)

AddWatcher adds a callback function for this WatchedInt. The callback will be called whenever Set is called.

func (*WatchedInt) Get

func (w *WatchedInt) Get() int

Get returns the current value of the WatchedInt.

func (*WatchedInt) Set

func (w *WatchedInt) Set(value int)

Set updates the current value of the WatchedInt and calls all callback functions added via AddWatcher.

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

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