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.

249 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{
  89. ID: id,
  90. Icon: "lightbulb",
  91. })
  92. bus.RunEvent(events.HardwareState{
  93. ID: id,
  94. InternalName: ikeaDevice.Name,
  95. SupportFlags: defaultSF,
  96. ColorFlags: defaultCF,
  97. State: b.stateMap[id],
  98. })
  99. }
  100. b.refreshDevice(id, bus)
  101. }
  102. b.mx.Unlock()
  103. time.Sleep(10 * time.Second)
  104. }
  105. }()
  106. }
  107. func (b *Bridge) writeState(id string, state device.State, bus *lucifer3.EventBus) {
  108. b.stateMap[id] = state
  109. b.refreshDevice(id, bus)
  110. }
  111. func (b *Bridge) refreshDevice(id string, bus *lucifer3.EventBus) {
  112. ikeaDevice, err := b.client.GetDevice(b.internalMap[id])
  113. if err != nil {
  114. bus.RunEvent(events.DeviceFailed{
  115. ID: id,
  116. Error: "Unable to fetch IKEA device",
  117. })
  118. return
  119. }
  120. changed := false
  121. if len(ikeaDevice.LightControl) > 0 {
  122. ikeaState := ikeaDevice.LightControl[0]
  123. luciferState := b.stateMap[id]
  124. currPower := ikeaState.Power > 0
  125. currIntensity := float64(ikeaState.Dimmer) / 254
  126. currX := float64(ikeaState.CIE_1931_X) / 65535
  127. currY := float64(ikeaState.CIE_1931_Y) / 65535
  128. if luciferState.Intensity != nil {
  129. newIntensity := *luciferState.Intensity
  130. diffIntensity := math.Abs(newIntensity - currIntensity)
  131. if diffIntensity >= 0.01 {
  132. changed = true
  133. intensityInt := int(math.Round(newIntensity * 254))
  134. _, err := b.client.PutDeviceDimming(ikeaDevice.DeviceId, intensityInt)
  135. if err != nil {
  136. bus.RunEvent(events.DeviceFailed{
  137. ID: id,
  138. Error: "Failed to update intensity state",
  139. })
  140. return
  141. }
  142. }
  143. }
  144. if luciferState.Color != nil && luciferState.Color.XY != nil {
  145. newX := luciferState.Color.XY.X
  146. newY := luciferState.Color.XY.Y
  147. diffX := math.Abs(newX - currX)
  148. diffY := math.Abs(newY - currY)
  149. if diffX >= 0.0001 || diffY >= 0.0001 {
  150. changed = true
  151. xInt := int(math.Round(newX * 65535))
  152. yInt := int(math.Round(newY * 65535))
  153. _, err := b.client.PutDeviceColor(ikeaDevice.DeviceId, xInt, yInt)
  154. if err != nil {
  155. bus.RunEvent(events.DeviceFailed{
  156. ID: id,
  157. Error: "Failed to update color state",
  158. })
  159. return
  160. }
  161. }
  162. }
  163. if luciferState.Power != nil {
  164. newPower := *luciferState.Power
  165. diffPower := newPower != currPower
  166. if diffPower {
  167. changed = true
  168. powerInt := 0
  169. if newPower {
  170. powerInt = 1
  171. }
  172. _, err := b.client.PutDevicePower(ikeaDevice.DeviceId, powerInt)
  173. if err != nil {
  174. bus.RunEvent(events.DeviceFailed{
  175. ID: id,
  176. Error: "Failed to update power state",
  177. })
  178. return
  179. }
  180. }
  181. }
  182. if changed {
  183. bus.RunEvent(events.HardwareState{
  184. ID: id,
  185. InternalName: ikeaDevice.Name,
  186. SupportFlags: defaultSF,
  187. ColorFlags: defaultCF,
  188. State: luciferState,
  189. })
  190. }
  191. }
  192. }
  193. const defaultSF = device.SFlagPower | device.SFlagIntensity | device.SFlagColor
  194. const defaultCF = device.CFlagXY
  195. func catchPanic(mutexes ...sync.Locker) (retErr error) {
  196. if err, ok := recover().(error); ok {
  197. retErr = err
  198. }
  199. for _, mx := range mutexes {
  200. mx.Unlock()
  201. }
  202. return
  203. }