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.

246 lines
5.4 KiB

  1. package tradfri
  2. import (
  3. "fmt"
  4. lucifer3 "git.aiterp.net/lucifer3/server"
  5. "git.aiterp.net/lucifer3/server/device"
  6. "git.aiterp.net/lucifer3/server/events"
  7. "git.aiterp.net/lucifer3/server/internal/color"
  8. "git.aiterp.net/lucifer3/server/internal/gentools"
  9. "github.com/eriklupander/tradfri-go/tradfri"
  10. "math"
  11. "math/rand"
  12. "strings"
  13. "sync"
  14. "time"
  15. )
  16. type Bridge struct {
  17. mx sync.Mutex
  18. client *tradfri.Client
  19. id string
  20. newIP string
  21. newCredentials string
  22. internalMap map[string]int
  23. nameMap map[string]string
  24. stateMap map[string]device.State
  25. }
  26. func connect(ip, credentials string) (bridge *Bridge, retErr error) {
  27. bridge = &Bridge{}
  28. defer func() {
  29. retErr = catchPanic()
  30. }()
  31. if !strings.Contains(ip, ":") {
  32. ip = ip + ":5684"
  33. }
  34. parts := strings.Split(credentials, ":")
  35. if len(parts) == 1 {
  36. bridge.client = tradfri.NewTradfriClient(ip, "Client_identity", parts[0])
  37. clientID := fmt.Sprintf("Lucifer4_%d", rand.Intn(10000))
  38. token, err := bridge.client.AuthExchange(clientID)
  39. if err != nil {
  40. return nil, err
  41. }
  42. bridge.newCredentials = clientID + ":" + token.Token
  43. } else {
  44. bridge.client = tradfri.NewTradfriClient(ip, parts[0], parts[1])
  45. bridge.newCredentials = parts[0] + ":" + parts[1]
  46. }
  47. bridge.newIP = ip
  48. bridge.id = fmt.Sprintf("tradfri:%s", ip)
  49. return
  50. }
  51. func (b *Bridge) listen(bus *lucifer3.EventBus) {
  52. b.internalMap = make(map[string]int, 16)
  53. b.nameMap = make(map[string]string, 16)
  54. b.stateMap = make(map[string]device.State, 16)
  55. go func() {
  56. defer b.mx.Unlock()
  57. for {
  58. b.mx.Lock()
  59. devices, err := b.client.ListDevices()
  60. if err != nil {
  61. bus.RunEvent(events.DeviceFailed{
  62. ID: b.id,
  63. Error: "Unable to fetch IKEA devices",
  64. })
  65. return
  66. }
  67. for _, ikeaDevice := range devices {
  68. id := fmt.Sprintf("%s:%d", b.id, ikeaDevice.DeviceId)
  69. lc := ikeaDevice.LightControl
  70. if len(lc) == 0 {
  71. continue
  72. }
  73. currState := device.State{
  74. Power: gentools.Ptr(lc[0].Power > 0),
  75. Intensity: gentools.Ptr(float64(lc[0].Dimmer) / 254),
  76. Color: &color.Color{
  77. XY: &color.XY{
  78. X: float64(lc[0].CIE_1931_X) / 65535,
  79. Y: float64(lc[0].CIE_1931_Y) / 65535,
  80. },
  81. },
  82. }
  83. if len(b.nameMap[id]) == 0 {
  84. b.internalMap[id] = ikeaDevice.DeviceId
  85. b.nameMap[id] = ikeaDevice.Name
  86. b.stateMap[id] = currState
  87. bus.RunEvent(events.DeviceReady{ID: id})
  88. bus.RunEvent(events.HardwareMetadata{ID: id})
  89. bus.RunEvent(events.HardwareState{
  90. ID: id,
  91. InternalName: ikeaDevice.Name,
  92. SupportFlags: defaultSF,
  93. ColorFlags: defaultCF,
  94. State: b.stateMap[id],
  95. })
  96. }
  97. b.refreshDevice(id, bus)
  98. }
  99. b.mx.Unlock()
  100. time.Sleep(10 * time.Second)
  101. }
  102. }()
  103. }
  104. func (b *Bridge) writeState(id string, state device.State, bus *lucifer3.EventBus) {
  105. b.stateMap[id] = state
  106. b.refreshDevice(id, bus)
  107. }
  108. func (b *Bridge) refreshDevice(id string, bus *lucifer3.EventBus) {
  109. ikeaDevice, err := b.client.GetDevice(b.internalMap[id])
  110. if err != nil {
  111. bus.RunEvent(events.DeviceFailed{
  112. ID: id,
  113. Error: "Unable to fetch IKEA device",
  114. })
  115. return
  116. }
  117. changed := false
  118. if len(ikeaDevice.LightControl) > 0 {
  119. ikeaState := ikeaDevice.LightControl[0]
  120. luciferState := b.stateMap[id]
  121. currPower := ikeaState.Power > 0
  122. currIntensity := float64(ikeaState.Dimmer) / 254
  123. currX := float64(ikeaState.CIE_1931_X) / 65535
  124. currY := float64(ikeaState.CIE_1931_Y) / 65535
  125. if luciferState.Intensity != nil {
  126. newIntensity := *luciferState.Intensity
  127. diffIntensity := math.Abs(newIntensity - currIntensity)
  128. if diffIntensity >= 0.01 {
  129. changed = true
  130. intensityInt := int(math.Round(newIntensity * 254))
  131. _, err := b.client.PutDeviceDimming(ikeaDevice.DeviceId, intensityInt)
  132. if err != nil {
  133. bus.RunEvent(events.DeviceFailed{
  134. ID: id,
  135. Error: "Failed to update intensity state",
  136. })
  137. return
  138. }
  139. }
  140. }
  141. if luciferState.Color != nil && luciferState.Color.XY != nil {
  142. newX := luciferState.Color.XY.X
  143. newY := luciferState.Color.XY.Y
  144. diffX := math.Abs(newX - currX)
  145. diffY := math.Abs(newY - currY)
  146. if diffX >= 0.0001 || diffY >= 0.0001 {
  147. changed = true
  148. xInt := int(math.Round(newX * 65535))
  149. yInt := int(math.Round(newY * 65535))
  150. _, err := b.client.PutDeviceColor(ikeaDevice.DeviceId, xInt, yInt)
  151. if err != nil {
  152. bus.RunEvent(events.DeviceFailed{
  153. ID: id,
  154. Error: "Failed to update color state",
  155. })
  156. return
  157. }
  158. }
  159. }
  160. if luciferState.Power != nil {
  161. newPower := *luciferState.Power
  162. diffPower := newPower != currPower
  163. if diffPower {
  164. changed = true
  165. powerInt := 0
  166. if newPower {
  167. powerInt = 1
  168. }
  169. _, err := b.client.PutDevicePower(ikeaDevice.DeviceId, powerInt)
  170. if err != nil {
  171. bus.RunEvent(events.DeviceFailed{
  172. ID: id,
  173. Error: "Failed to update power state",
  174. })
  175. return
  176. }
  177. }
  178. }
  179. if changed {
  180. bus.RunEvent(events.HardwareState{
  181. ID: id,
  182. InternalName: ikeaDevice.Name,
  183. SupportFlags: defaultSF,
  184. ColorFlags: defaultCF,
  185. State: luciferState,
  186. })
  187. }
  188. }
  189. }
  190. const defaultSF = device.SFlagPower | device.SFlagIntensity | device.SFlagColor
  191. const defaultCF = device.CFlagXY
  192. func catchPanic(mutexes ...sync.Locker) (retErr error) {
  193. if err, ok := recover().(error); ok {
  194. retErr = err
  195. }
  196. for _, mx := range mutexes {
  197. mx.Unlock()
  198. }
  199. return
  200. }