|
|
package tradfri
import ( "fmt" lucifer3 "git.aiterp.net/lucifer3/server" "git.aiterp.net/lucifer3/server/device" "git.aiterp.net/lucifer3/server/events" "git.aiterp.net/lucifer3/server/internal/color" "git.aiterp.net/lucifer3/server/internal/gentools" "github.com/eriklupander/tradfri-go/tradfri" "math" "math/rand" "strings" "sync" "time" )
type Bridge struct { mx sync.Mutex client *tradfri.Client
id string newIP string newCredentials string
internalMap map[string]int nameMap map[string]string stateMap map[string]device.State }
func connect(ip, credentials string) (bridge *Bridge, retErr error) { bridge = &Bridge{} defer func() { retErr = catchPanic() }()
if !strings.Contains(ip, ":") { ip = ip + ":5684" }
parts := strings.Split(credentials, ":") if len(parts) == 1 { bridge.client = tradfri.NewTradfriClient(ip, "Client_identity", parts[0])
clientID := fmt.Sprintf("Lucifer4_%d", rand.Intn(10000))
token, err := bridge.client.AuthExchange(clientID) if err != nil { return nil, err }
bridge.newCredentials = clientID + ":" + token.Token } else { bridge.client = tradfri.NewTradfriClient(ip, parts[0], parts[1])
bridge.newCredentials = parts[0] + ":" + parts[1] }
bridge.newIP = ip bridge.id = fmt.Sprintf("tradfri:%s", ip)
return }
func (b *Bridge) listen(bus *lucifer3.EventBus) { b.internalMap = make(map[string]int, 16) b.nameMap = make(map[string]string, 16) b.stateMap = make(map[string]device.State, 16)
go func() { defer b.mx.Unlock()
for { b.mx.Lock()
devices, err := b.client.ListDevices() if err != nil { bus.RunEvent(events.DeviceFailed{ ID: b.id, Error: "Unable to fetch IKEA devices", }) return }
for _, ikeaDevice := range devices { id := fmt.Sprintf("%s:%d", b.id, ikeaDevice.DeviceId)
lc := ikeaDevice.LightControl if len(lc) == 0 { continue }
currState := device.State{ Power: gentools.Ptr(lc[0].Power > 0), Intensity: gentools.Ptr(float64(lc[0].Dimmer) / 254), Color: &color.Color{ XY: &color.XY{ X: float64(lc[0].CIE_1931_X) / 65535, Y: float64(lc[0].CIE_1931_Y) / 65535, }, }, }
if len(b.nameMap[id]) == 0 { b.internalMap[id] = ikeaDevice.DeviceId b.nameMap[id] = ikeaDevice.Name b.stateMap[id] = currState
bus.RunEvent(events.DeviceReady{ID: id}) bus.RunEvent(events.HardwareMetadata{ID: id}) bus.RunEvent(events.HardwareState{ ID: id, InternalName: ikeaDevice.Name, SupportFlags: defaultSF, ColorFlags: defaultCF, State: b.stateMap[id], }) }
b.refreshDevice(id, bus) }
b.mx.Unlock() time.Sleep(10 * time.Second) } }() }
func (b *Bridge) writeState(id string, state device.State, bus *lucifer3.EventBus) { b.stateMap[id] = state b.refreshDevice(id, bus) }
func (b *Bridge) refreshDevice(id string, bus *lucifer3.EventBus) { ikeaDevice, err := b.client.GetDevice(b.internalMap[id]) if err != nil { bus.RunEvent(events.DeviceFailed{ ID: id, Error: "Unable to fetch IKEA device", }) return }
changed := false
if len(ikeaDevice.LightControl) > 0 { ikeaState := ikeaDevice.LightControl[0] luciferState := b.stateMap[id]
currPower := ikeaState.Power > 0 currIntensity := float64(ikeaState.Dimmer) / 254 currX := float64(ikeaState.CIE_1931_X) / 65535 currY := float64(ikeaState.CIE_1931_Y) / 65535
if luciferState.Intensity != nil { newIntensity := *luciferState.Intensity diffIntensity := math.Abs(newIntensity - currIntensity)
if diffIntensity >= 0.01 { changed = true
intensityInt := int(math.Round(newIntensity * 254))
_, err := b.client.PutDeviceDimming(ikeaDevice.DeviceId, intensityInt) if err != nil { bus.RunEvent(events.DeviceFailed{ ID: id, Error: "Failed to update intensity state", }) return } } }
if luciferState.Color != nil && luciferState.Color.XY != nil { newX := luciferState.Color.XY.X newY := luciferState.Color.XY.Y diffX := math.Abs(newX - currX) diffY := math.Abs(newY - currY)
if diffX >= 0.0001 || diffY >= 0.0001 { changed = true
xInt := int(math.Round(newX * 65535)) yInt := int(math.Round(newY * 65535))
_, err := b.client.PutDeviceColor(ikeaDevice.DeviceId, xInt, yInt) if err != nil { bus.RunEvent(events.DeviceFailed{ ID: id, Error: "Failed to update color state", }) return } } }
if luciferState.Power != nil { newPower := *luciferState.Power diffPower := newPower != currPower
if diffPower { changed = true
powerInt := 0 if newPower { powerInt = 1 }
_, err := b.client.PutDevicePower(ikeaDevice.DeviceId, powerInt) if err != nil { bus.RunEvent(events.DeviceFailed{ ID: id, Error: "Failed to update power state", }) return } } }
if changed { bus.RunEvent(events.HardwareState{ ID: id, InternalName: ikeaDevice.Name, SupportFlags: defaultSF, ColorFlags: defaultCF, State: luciferState, }) } } }
const defaultSF = device.SFlagPower | device.SFlagIntensity | device.SFlagColor const defaultCF = device.CFlagXY
func catchPanic(mutexes ...sync.Locker) (retErr error) { if err, ok := recover().(error); ok { retErr = err }
for _, mx := range mutexes { mx.Unlock() }
return }
|