You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
271 lines
5.9 KiB
271 lines
5.9 KiB
package nanoleaf
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"git.aiterp.net/lucifer3/server/device"
|
|
"git.aiterp.net/lucifer3/server/internal/color"
|
|
"time"
|
|
)
|
|
|
|
type EffectInfo struct {
|
|
EffectsList []string `json:"effectsList"`
|
|
Select string `json:"select"`
|
|
}
|
|
|
|
type PanelLayout struct {
|
|
GlobalOrientation GlobalOrientation `json:"globalOrientation"`
|
|
Data PanelLayoutData `json:"layout"`
|
|
}
|
|
|
|
type GlobalOrientation struct {
|
|
Value int `json:"value"`
|
|
Max int `json:"max"`
|
|
Min int `json:"min"`
|
|
}
|
|
|
|
type PanelLayoutData struct {
|
|
NumPanels int `json:"numPanels"`
|
|
SideLength int `json:"sideLength"`
|
|
PositionData []PositionData `json:"positionData"`
|
|
}
|
|
|
|
type PositionData struct {
|
|
PanelID uint16 `json:"panelId"`
|
|
X int `json:"x"`
|
|
Y int `json:"y"`
|
|
O int `json:"o"`
|
|
ShapeType int `json:"shapeType"`
|
|
}
|
|
|
|
type StateBool struct {
|
|
Value bool `json:"value"`
|
|
}
|
|
|
|
type StateInt struct {
|
|
Value int `json:"value"`
|
|
Max int `json:"max"`
|
|
Min int `json:"min"`
|
|
}
|
|
|
|
type State struct {
|
|
Brightness StateInt `json:"brightness"`
|
|
ColorMode string `json:"colorMode"`
|
|
Ct StateInt `json:"ct"`
|
|
Hue StateInt `json:"hue"`
|
|
On StateBool `json:"on"`
|
|
Sat StateInt `json:"sat"`
|
|
}
|
|
|
|
type Overview struct {
|
|
Name string `json:"name"`
|
|
SerialNumber string `json:"serialNo"`
|
|
Manufacturer string `json:"manufacturer"`
|
|
FirmwareVersion string `json:"firmwareVersion"`
|
|
HardwareVersion string `json:"hardwareVersion"`
|
|
Model string `json:"model"`
|
|
Effects EffectInfo `json:"effects"`
|
|
PanelLayout PanelLayout `json:"panelLayout"`
|
|
State State `json:"state"`
|
|
}
|
|
|
|
type DeviceInfo struct {
|
|
SerialNumber string `json:"serialNumber"`
|
|
HardwareVersion string `json:"hardwareVersion"`
|
|
FirmwareVersion string `json:"firmwareVersion"`
|
|
BootloaderVersion string `json:"bootloaderVersion"`
|
|
ModelNumber string `json:"modelNumber"`
|
|
}
|
|
|
|
type TokenResponse struct {
|
|
Token string `json:"auth_token"`
|
|
}
|
|
|
|
type PanelUpdate []byte
|
|
|
|
func (u *PanelUpdate) Add(message [8]byte) {
|
|
if len(*u) < 2 {
|
|
*u = make([]byte, 2, 10)
|
|
}
|
|
|
|
binary.BigEndian.PutUint16(*u, binary.BigEndian.Uint16(*u)+1)
|
|
|
|
*u = append(*u, message[:]...)
|
|
}
|
|
|
|
func (u *PanelUpdate) Len() int {
|
|
if len(*u) < 2 {
|
|
return 0
|
|
}
|
|
|
|
return int(binary.BigEndian.Uint16(*u))
|
|
}
|
|
|
|
type PanelEventMessage []byte
|
|
|
|
func (remote PanelEventMessage) Count() int {
|
|
return int(binary.BigEndian.Uint16(remote[0:]))
|
|
}
|
|
|
|
func (remote PanelEventMessage) ValidateLength() bool {
|
|
return len(remote) >= (2 + remote.Count()*5)
|
|
}
|
|
|
|
func (remote PanelEventMessage) PanelID(idx int) uint16 {
|
|
return binary.BigEndian.Uint16(remote[2+(idx*5):])
|
|
}
|
|
|
|
func (remote PanelEventMessage) TouchType(idx int) int {
|
|
value := int(remote[2+(idx*5)])
|
|
return (value & 0b11100000) >> 5
|
|
}
|
|
|
|
func (remote PanelEventMessage) TouchStrength(idx int) int {
|
|
value := int(remote[2+(idx*5)])
|
|
return (value & 0b00011110) >> 1
|
|
}
|
|
|
|
func (remote PanelEventMessage) SwipedFromPanelID(idx int) uint16 {
|
|
return binary.BigEndian.Uint16(remote[2+(idx*5)+3:])
|
|
}
|
|
|
|
var shapeTypeMap = map[int]string{
|
|
0: "Legacy Triangle",
|
|
1: "Rhythm",
|
|
2: "Square",
|
|
3: "Control Square Master",
|
|
4: "Control Square Passive",
|
|
7: "Hexagon",
|
|
8: "Triangle",
|
|
9: "Mini Triangle",
|
|
12: "Shapes Controller",
|
|
}
|
|
|
|
var shapeIconMap = map[int]string{
|
|
0: "triangle",
|
|
1: "triangle",
|
|
2: "square",
|
|
3: "square",
|
|
4: "square",
|
|
7: "hexagon",
|
|
8: "triangle",
|
|
9: "triangle",
|
|
12: "hexagon",
|
|
}
|
|
|
|
var shapeWidthMap = map[int]int{
|
|
0: 150,
|
|
1: -1,
|
|
2: 100,
|
|
3: 100,
|
|
4: 100,
|
|
7: 67,
|
|
8: 134,
|
|
9: 67,
|
|
12: -1,
|
|
}
|
|
|
|
var httpMessage = []byte(`{ "write": { "command": "display", "animType": "extControl", "extControlVersion": "v2" }}`)
|
|
|
|
type panel struct {
|
|
ID uint16
|
|
FullID string
|
|
|
|
On bool
|
|
Intensity float64
|
|
ColorRGBA [4]byte
|
|
TransitionAt time.Time
|
|
|
|
O int
|
|
X int
|
|
Y int
|
|
ShapeType int
|
|
|
|
Stale bool
|
|
SlowUpdates int
|
|
TicksUntilSlowUpdate int
|
|
}
|
|
|
|
func (p *panel) message() (message [8]byte) {
|
|
transitionTime := p.TransitionAt.Sub(time.Now()).Round(time.Millisecond * 100)
|
|
if transitionTime > maxTransitionTime {
|
|
transitionTime = maxTransitionTime
|
|
} else if transitionTime < 0 {
|
|
transitionTime = 0
|
|
}
|
|
|
|
binary.BigEndian.PutUint16(message[0:], p.ID)
|
|
copy(message[2:], p.ColorRGBA[:])
|
|
binary.BigEndian.PutUint16(message[6:], uint16(transitionTime/(time.Millisecond*100)))
|
|
|
|
return
|
|
}
|
|
|
|
func (p *panel) update(colorRGBA [4]byte, transitionAt time.Time) {
|
|
if p.ColorRGBA != colorRGBA {
|
|
p.ColorRGBA = colorRGBA
|
|
p.Stale = true
|
|
p.SlowUpdates = 3
|
|
p.TicksUntilSlowUpdate = 10
|
|
}
|
|
p.TransitionAt = transitionAt
|
|
}
|
|
|
|
func (p *panel) apply(change device.State, transitionTime time.Time) {
|
|
if change.Power != nil {
|
|
p.On = *change.Power
|
|
}
|
|
|
|
if change.Intensity != nil {
|
|
p.Intensity = *change.Intensity
|
|
}
|
|
|
|
if change.Color == nil {
|
|
change.Color = &color.Color{RGB: &color.RGB{Red: 255, Green: 255, Blue: 255}}
|
|
}
|
|
|
|
if !p.On {
|
|
newColor := [4]byte{0, 0, 0, 0}
|
|
if newColor != p.ColorRGBA {
|
|
p.update(newColor, transitionTime)
|
|
}
|
|
} else {
|
|
rgbColor, ok := change.Color.ToRGB()
|
|
if !ok {
|
|
newColor := [4]byte{255, 255, 255, 255}
|
|
if newColor != p.ColorRGBA {
|
|
p.update(newColor, transitionTime)
|
|
}
|
|
}
|
|
|
|
rgb := rgbColor.RGB.AtIntensity(p.Intensity)
|
|
red := byte(rgb.Red * 255.0001)
|
|
green := byte(rgb.Green * 255.0001)
|
|
blue := byte(rgb.Blue * 255.0001)
|
|
newColor := [4]byte{red, green, blue, 255}
|
|
if newColor != p.ColorRGBA {
|
|
p.update(newColor, transitionTime)
|
|
}
|
|
}
|
|
}
|
|
|
|
type panelUpdate []byte
|
|
|
|
func (u *panelUpdate) Add(message [8]byte) {
|
|
if len(*u) < 2 {
|
|
*u = make([]byte, 2, 10)
|
|
}
|
|
|
|
binary.BigEndian.PutUint16(*u, binary.BigEndian.Uint16(*u)+1)
|
|
|
|
*u = append(*u, message[:]...)
|
|
}
|
|
|
|
func (u *panelUpdate) Len() int {
|
|
if len(*u) < 2 {
|
|
return 0
|
|
}
|
|
|
|
return int(binary.BigEndian.Uint16(*u))
|
|
}
|
|
|
|
const maxTransitionTime = time.Minute * 109
|