mirror of
https://github.com/Luzifer/streamdeck.git
synced 2024-12-20 09:41:19 +00:00
Add support for StreamDeck Mini (#12)
This commit is contained in:
parent
2f3cc8dd8c
commit
06e83ac866
5 changed files with 177 additions and 1 deletions
|
@ -1,3 +1,7 @@
|
||||||
|
# 1.6.0 / 2022-02-05
|
||||||
|
|
||||||
|
* Add support for StreamDeck Mini
|
||||||
|
|
||||||
# 1.5.0 / 2021-05-27
|
# 1.5.0 / 2021-05-27
|
||||||
|
|
||||||
* Add caption support for image buttons
|
* Add caption support for image buttons
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
- Elgato StreamDeck Original V2 (15 keys, ID `0fd9:006d`)
|
- Elgato StreamDeck Original V2 (15 keys, ID `0fd9:006d`)
|
||||||
- Elgato StreamDeck XL (32 keys, ID `0fd9:006c`)
|
- Elgato StreamDeck XL (32 keys, ID `0fd9:006c`)
|
||||||
|
- Elgato StreamDeck Mini (6 keys, ID `0fd9:0063`)
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
|
167
deck_mini.go
Normal file
167
deck_mini.go
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
package streamdeck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/disintegration/imaging"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sstallion/go-hid"
|
||||||
|
"golang.org/x/image/bmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
deckMiniMaxPacketSize = 1024
|
||||||
|
deckMiniHeaderSize = 16
|
||||||
|
)
|
||||||
|
|
||||||
|
type deckConfigMini struct {
|
||||||
|
dev *hid.Device
|
||||||
|
writeLock sync.Mutex
|
||||||
|
|
||||||
|
keyState []EventType
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDeckConfigMini() *deckConfigMini {
|
||||||
|
d := &deckConfigMini{}
|
||||||
|
d.keyState = make([]EventType, d.NumKeys())
|
||||||
|
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *deckConfigMini) SetDevice(dev *hid.Device) { d.dev = dev }
|
||||||
|
func (d *deckConfigMini) NumKeys() int { return 6 }
|
||||||
|
func (d *deckConfigMini) KeyColumns() int { return 3 }
|
||||||
|
func (d *deckConfigMini) KeyRows() int { return 2 }
|
||||||
|
func (d *deckConfigMini) KeyDirection() keyDirection { return keyDirectionLTR }
|
||||||
|
func (d *deckConfigMini) KeyDataOffset() int { return 1 }
|
||||||
|
func (d *deckConfigMini) TransformKeyIndex(keyIdx int) int { return keyIdx }
|
||||||
|
func (d *deckConfigMini) IconSize() int { return 80 }
|
||||||
|
func (d *deckConfigMini) IconBytes() int { return d.IconSize() * d.IconSize() * 3 }
|
||||||
|
func (d *deckConfigMini) Model() uint16 { return StreamDeckMini }
|
||||||
|
|
||||||
|
func (d *deckConfigMini) 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 *deckConfigMini) FillImage(keyIdx int, img image.Image) error {
|
||||||
|
d.writeLock.Lock()
|
||||||
|
defer d.writeLock.Unlock()
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
rimg := imaging.Transpose(img)
|
||||||
|
if err := bmp.Encode(buf, rimg); err != nil {
|
||||||
|
return errors.Wrap(err, "Unable to encode bmp")
|
||||||
|
}
|
||||||
|
|
||||||
|
var partIndex int16
|
||||||
|
for buf.Len() > 0 {
|
||||||
|
chunk := make([]byte, deckMiniMaxPacketSize-deckMiniHeaderSize)
|
||||||
|
n, err := buf.Read(chunk)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "Unable to read image chunk")
|
||||||
|
}
|
||||||
|
|
||||||
|
var last uint8
|
||||||
|
if n < deckMiniMaxPacketSize-deckMiniHeaderSize || buf.Len() == 0 {
|
||||||
|
last = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
header := make([]byte, deckMiniHeaderSize)
|
||||||
|
header[0] = 0x02
|
||||||
|
header[1] = 0x01
|
||||||
|
header[2] = byte(partIndex)
|
||||||
|
header[4] = last
|
||||||
|
header[5] = byte(keyIdx + 1)
|
||||||
|
|
||||||
|
if _, err = d.dev.Write(append(header, chunk...)); err != nil {
|
||||||
|
return errors.Wrap(err, "Unable to send image chunk")
|
||||||
|
}
|
||||||
|
|
||||||
|
partIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *deckConfigMini) 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 *deckConfigMini) ClearKey(keyIdx int) error {
|
||||||
|
return d.FillColor(keyIdx, color.RGBA{0x0, 0x0, 0x0, 0xff})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *deckConfigMini) 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 *deckConfigMini) SetBrightness(pct int) error {
|
||||||
|
if pct < 0 || pct > 100 {
|
||||||
|
return errors.New("Percentage out of bounds")
|
||||||
|
}
|
||||||
|
|
||||||
|
r := make([]byte, 17)
|
||||||
|
r[0] = 0x05
|
||||||
|
r[1] = 0x55
|
||||||
|
r[2] = 0xaa
|
||||||
|
r[3] = 0xd1
|
||||||
|
r[4] = 0x01
|
||||||
|
r[5] = byte(pct)
|
||||||
|
|
||||||
|
_, err := d.dev.SendFeatureReport(r)
|
||||||
|
|
||||||
|
return errors.Wrap(err, "Unable to send feature report")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *deckConfigMini) ResetToLogo() error {
|
||||||
|
r := make([]byte, 17)
|
||||||
|
r[0] = 0x0b
|
||||||
|
r[1] = 0x63
|
||||||
|
|
||||||
|
_, err := d.dev.SendFeatureReport(r)
|
||||||
|
|
||||||
|
return errors.Wrap(err, "Unable to send feature report")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *deckConfigMini) GetFimwareVersion() (string, error) {
|
||||||
|
fw := make([]byte, 32)
|
||||||
|
fw[0] = 4
|
||||||
|
|
||||||
|
_, err := d.dev.GetFeatureReport(fw)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "Unable to get feature report")
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(fw[5:13]), nil
|
||||||
|
}
|
|
@ -47,4 +47,5 @@ type deckConfig interface {
|
||||||
var decks = map[uint16]deckConfig{
|
var decks = map[uint16]deckConfig{
|
||||||
StreamDeckOriginalV2: &deckConfigOriginalV2{},
|
StreamDeckOriginalV2: &deckConfigOriginalV2{},
|
||||||
StreamDeckXL: &deckConfigXL{},
|
StreamDeckXL: &deckConfigXL{},
|
||||||
|
StreamDeckMini: &deckConfigMini{},
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,14 @@ const (
|
||||||
StreamDeckOriginalV2 uint16 = 0x006d
|
StreamDeckOriginalV2 uint16 = 0x006d
|
||||||
// Stremdeck XL (0fd9:006c) 32 keys
|
// Stremdeck XL (0fd9:006c) 32 keys
|
||||||
StreamDeckXL uint16 = 0x006c
|
StreamDeckXL uint16 = 0x006c
|
||||||
|
// StreamDeck Mini (0fd9:0063) 6 keys
|
||||||
|
StreamDeckMini uint16 = 0x0063
|
||||||
)
|
)
|
||||||
|
|
||||||
var DeckToName = map[uint16]string{
|
var DeckToName = map[uint16]string{
|
||||||
StreamDeckOriginalV2: "StreamDeck Original V2",
|
StreamDeckOriginalV2: "StreamDeck Original V2",
|
||||||
StreamDeckXL: "StreamDeck XL",
|
StreamDeckXL: "StreamDeck XL",
|
||||||
|
StreamDeckMini: "StreamDeck Mini",
|
||||||
}
|
}
|
||||||
|
|
||||||
// EventType represents the state of a button (Up / Down)
|
// EventType represents the state of a button (Up / Down)
|
||||||
|
|
Loading…
Reference in a new issue