1
0
Fork 0
mirror of https://github.com/Luzifer/streamdeck.git synced 2024-10-18 05:04:18 +00:00

Initial library version

This commit is contained in:
Knut Ahlers 2019-11-20 02:31:01 +01:00
commit 4eb30f3a4a
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
5 changed files with 320 additions and 0 deletions

163
deck_original_v2.go Normal file
View file

@ -0,0 +1,163 @@
package streamdeck
import (
"bytes"
"encoding/binary"
"image"
"image/color"
"image/jpeg"
"strings"
"github.com/pkg/errors"
"github.com/sstallion/go-hid"
)
const (
deckOriginalV2MaxPacketSize = 1024
deckOriginalV2HeaderSize = 8
)
type deckConfigOriginalV2 struct {
dev *hid.Device
keyState []EventType
}
func newDeckConfigOriginalV2() *deckConfigOriginalV2 {
d := &deckConfigOriginalV2{}
d.keyState = make([]EventType, d.NumKeys())
return d
}
func (d *deckConfigOriginalV2) SetDevice(dev *hid.Device) { d.dev = dev }
func (d deckConfigOriginalV2) NumKeys() int { return 15 }
func (d deckConfigOriginalV2) KeyColumns() int { return 5 }
func (d deckConfigOriginalV2) KeyRows() int { return 3 }
func (d deckConfigOriginalV2) KeyDirection() keyDirection { return keyDirectionLTR }
func (d deckConfigOriginalV2) KeyDataOffset() int { return 4 }
func (d deckConfigOriginalV2) TransformKeyIndex(keyIdx int) int { return keyIdx }
func (d deckConfigOriginalV2) IconSize() int { return 72 }
func (d deckConfigOriginalV2) IconBytes() int { return d.IconSize() * d.IconSize() * 3 }
func (d deckConfigOriginalV2) Model() uint16 { return StreamDeckOriginalV2 }
func (d deckConfigOriginalV2) FillColor(keyIdx int, col color.RGBA) error {
img := image.NewRGBA(image.Rect(0, 0, d.IconSize(), d.IconSize()))
for x := 0; x < d.IconSize(); x++ {
for y := 0; y < d.IconSize(); y++ {
img.Set(x, y, col)
}
}
return d.FillImage(keyIdx, img)
}
func (d deckConfigOriginalV2) FillImage(keyIdx int, img image.Image) error {
buf := new(bytes.Buffer)
if err := jpeg.Encode(buf, img, &jpeg.Options{Quality: 95}); err != nil {
return errors.Wrap(err, "Unable to encode jpeg")
}
var partIndex int16
for buf.Len() > 0 {
chunk := make([]byte, deckOriginalV2MaxPacketSize-deckOriginalV2HeaderSize)
n, err := buf.Read(chunk)
if err != nil {
return errors.Wrap(err, "Unable to read image chunk")
}
var last uint8
if n < deckOriginalV2MaxPacketSize-deckOriginalV2HeaderSize {
last = 1
}
tbuf := new(bytes.Buffer)
tbuf.Write([]byte{0x02, 0x07, byte(keyIdx), last})
binary.Write(tbuf, binary.LittleEndian, int16(n))
binary.Write(tbuf, binary.LittleEndian, partIndex)
tbuf.Write(chunk)
if _, err = d.dev.Write(tbuf.Bytes()); err != nil {
return errors.Wrap(err, "Unable to send image chunk")
}
partIndex++
}
return nil
}
func (d deckConfigOriginalV2) FillPanel(img image.RGBA) error {
if img.Bounds().Size().X < d.KeyColumns()*d.IconSize() || img.Bounds().Size().Y < d.KeyRows()*d.IconSize() {
return errors.New("Image is too small")
}
for k := 0; k < d.NumKeys(); k++ {
var (
ky = k / d.KeyColumns()
kx = k % d.KeyColumns()
)
if err := d.FillImage(k, img.SubImage(image.Rect(kx*d.IconSize(), ky*d.IconSize(), (kx+1)*d.IconSize(), (ky+1)*d.IconSize()))); err != nil {
return errors.Wrap(err, "Unable to set key image")
}
}
return nil
}
func (d deckConfigOriginalV2) ClearKey(keyIdx int) error {
return d.FillColor(keyIdx, color.RGBA{0x0, 0x0, 0x0, 0xff})
}
func (d deckConfigOriginalV2) ClearAllKeys() error {
for i := 0; i < d.NumKeys(); i++ {
if err := d.ClearKey(i); err != nil {
return errors.Wrap(err, "Unable to clear key")
}
}
return nil
}
func (d deckConfigOriginalV2) SetBrightness(pct int) error {
if pct < 0 || pct > 100 {
return errors.New("Percentage out of bounds")
}
_, err := d.dev.SendFeatureReport([]byte{
0x03, 0x08, byte(pct), 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
})
return errors.Wrap(err, "Unable to send feature report")
}
func (d deckConfigOriginalV2) ResetToLogo() error {
_, err := d.dev.SendFeatureReport([]byte{
0x03,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
})
return errors.Wrap(err, "Unable to send feature report")
}
func (d deckConfigOriginalV2) GetFimwareVersion() (string, error) {
fw := make([]byte, 32)
fw[0] = 5
_, err := d.dev.GetFeatureReport(fw)
if err != nil {
return "", errors.Wrap(err, "Unable to get feature report")
}
return strings.TrimRight(string(fw[6:]), "\x00"), nil
}

8
go.mod Normal file
View file

@ -0,0 +1,8 @@
module github.com/Luzifer/streamdeck
go 1.13
require (
github.com/pkg/errors v0.8.1
github.com/sstallion/go-hid v0.0.0-20190621001400-1cf4630be9f4
)

4
go.sum Normal file
View file

@ -0,0 +1,4 @@
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/sstallion/go-hid v0.0.0-20190621001400-1cf4630be9f4 h1:hczfXYN39SDj4FcN5J7sgHBtJm4U7ef2nvlonn6NvVU=
github.com/sstallion/go-hid v0.0.0-20190621001400-1cf4630be9f4/go.mod h1:JwBz6izP5UGcbYDU5VGeLkqpRIpSBDPtCB5/XnVXz9Q=

49
interface.go Normal file
View file

@ -0,0 +1,49 @@
package streamdeck
import (
"image"
"image/color"
"github.com/sstallion/go-hid"
)
type keyDirection uint
const (
keyDirectionLTR keyDirection = iota
keyDirectionRTL
)
type deckConfig interface {
SetDevice(dev *hid.Device)
NumKeys() int
KeyColumns() int
KeyRows() int
KeyDirection() keyDirection
KeyDataOffset() int
TransformKeyIndex(keyIdx int) int
IconSize() int
IconBytes() int
Model() uint16
FillColor(keyIdx int, col color.RGBA) error
FillImage(keyIdx int, img image.Image) error
FillPanel(img image.RGBA) error
ClearKey(keyIdx int) error
ClearAllKeys() error
SetBrightness(pct int) error
ResetToLogo() error
GetFimwareVersion() (string, error)
}
var decks = map[uint16]deckConfig{
StreamDeckOriginalV2: &deckConfigOriginalV2{},
}

96
streamdeck.go Normal file
View file

@ -0,0 +1,96 @@
package streamdeck
import (
"image"
"image/color"
"github.com/pkg/errors"
hid "github.com/sstallion/go-hid"
)
const vendorElgato = 0x0fd9
const (
StreamDeckOriginalV2 uint16 = 0x006d
)
type EventType uint8
const (
EventTypeUp EventType = iota
EventTypeDown
)
type Event struct {
Key int
Type EventType
}
type Client struct {
cfg deckConfig
dev *hid.Device
devType uint16
keyStates []EventType
evts chan Event
}
func New(devicePID uint16) (*Client, error) {
dev, err := hid.OpenFirst(vendorElgato, devicePID)
if err != nil {
return nil, errors.Wrap(err, "Unable to open device")
}
cfg := decks[devicePID]
cfg.SetDevice(dev)
client := &Client{
cfg: cfg,
dev: dev,
devType: devicePID,
keyStates: make([]EventType, cfg.NumKeys()),
evts: make(chan Event, 100),
}
go client.read()
return client, nil
}
func (c Client) Close() error { return c.dev.Close() }
func (c Client) Serial() (string, error) { return c.dev.GetSerialNbr() }
func (c Client) FillColor(keyIdx int, col color.RGBA) error { return c.cfg.FillColor(keyIdx, col) }
func (c Client) FillImage(keyIdx int, img image.Image) error { return c.cfg.FillImage(keyIdx, img) }
func (c Client) FillPanel(img image.RGBA) error { return c.cfg.FillPanel(img) }
func (c Client) ClearKey(keyIdx int) error { return c.cfg.ClearKey(keyIdx) }
func (c Client) ClearAllKeys() error { return c.cfg.ClearAllKeys() }
func (c Client) SetBrightness(pct int) error { return c.cfg.SetBrightness(pct) }
func (c Client) ResetToLogo() error { return c.cfg.ResetToLogo() }
func (c Client) GetFimwareVersion() (string, error) { return c.cfg.GetFimwareVersion() }
func (c Client) Subscribe() <-chan Event { return c.evts }
func (c Client) emit(key int, t EventType) { c.evts <- Event{Key: key, Type: t} }
func (c *Client) read() {
for {
buf := make([]byte, 1024)
_, err := c.dev.Read(buf)
if err != nil {
continue
}
for k := 0; k < c.cfg.NumKeys(); k++ {
newState := EventType(buf[k+c.cfg.KeyDataOffset()])
if c.keyStates[k] != newState {
c.emit(k, newState)
c.keyStates[k] = newState
}
}
}
}