The main server, and probably only repository in this org.
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.

507 lines
11 KiB

  1. package light
  2. import (
  3. "context"
  4. "database/sql"
  5. "log"
  6. "net/http"
  7. "sync"
  8. "time"
  9. "git.aiterp.net/lucifer/lucifer/internal/httperr"
  10. "git.aiterp.net/lucifer/lucifer/models"
  11. "golang.org/x/sync/errgroup"
  12. )
  13. // ErrUnknownDriver is returned by any function asking for a driver name if the driver specified doesn't exist.
  14. var ErrUnknownDriver = httperr.Error{Status: http.StatusNotImplemented, Kind: "unknown_driver", Message: "Unknown light driver"}
  15. // A Service wraps the repos for lights and bridges and takes care of the business logic.
  16. type Service struct {
  17. mutex sync.Mutex
  18. bridges models.BridgeRepository
  19. lights models.LightRepository
  20. buttons models.ButtonRepository
  21. groups models.GroupRepository
  22. buttonActive map[int]bool
  23. buttonTargets map[int]int
  24. }
  25. // DirectConnect connects to a bridge directly, without going through the discovery process to find them..
  26. func (s *Service) DirectConnect(ctx context.Context, driver string, addr string, name string) (models.Bridge, error) {
  27. d, ok := drivers[driver]
  28. if !ok {
  29. return models.Bridge{}, ErrUnknownDriver
  30. }
  31. bridge := models.Bridge{
  32. ID: -1,
  33. Name: name,
  34. Addr: addr,
  35. Driver: driver,
  36. }
  37. bridge, err := d.Connect(ctx, bridge)
  38. if err != nil {
  39. return models.Bridge{}, httperr.Error{Status: http.StatusPreconditionFailed, Kind: "connect_failed", Message: err.Error()}
  40. }
  41. bridge, err = s.bridges.Insert(ctx, bridge)
  42. if err != nil {
  43. return models.Bridge{}, err
  44. }
  45. return bridge, nil
  46. }
  47. // SyncLights syncs all lights in a bridge with the state in the database.
  48. func (s *Service) SyncLights(ctx context.Context, bridge models.Bridge) error {
  49. d, ok := drivers[bridge.Driver]
  50. if !ok {
  51. return ErrUnknownDriver
  52. }
  53. bridgeLights, err := d.Lights(ctx, bridge)
  54. if err != nil {
  55. return err
  56. }
  57. dbLights, err := s.lights.ListByBridge(ctx, bridge)
  58. if err != nil {
  59. return err
  60. }
  61. // Add unknown lights
  62. for _, bridgeLight := range bridgeLights {
  63. found := false
  64. for _, dbLight := range dbLights {
  65. if dbLight.InternalID == bridgeLight.InternalID {
  66. found = true
  67. break
  68. }
  69. }
  70. // Add unknown lights if it doesn't exist in the databse.
  71. if !found {
  72. log.Println("Adding unknown light", bridgeLight.InternalID)
  73. bridgeLight.SetColor("FFFFFF")
  74. bridgeLight.Brightness = 254
  75. bridgeLight.On = true
  76. _, err := s.lights.Insert(ctx, bridgeLight)
  77. if err != nil {
  78. return err
  79. }
  80. }
  81. }
  82. changedLights, err := d.ChangedLights(ctx, bridge, dbLights...)
  83. if err != nil {
  84. return err
  85. }
  86. if len(changedLights) > 0 {
  87. err := d.Apply(ctx, bridge, changedLights...)
  88. if err != nil {
  89. log.Printf("Failed to apply one or more of the changes on bridge %d (%s): %s", bridge.ID, bridge.Name, err)
  90. }
  91. }
  92. return nil
  93. }
  94. // SyncButtons syncs all buttons in a bridge with the state in the database.
  95. func (s *Service) SyncButtons(ctx context.Context, bridge models.Bridge) error {
  96. d, ok := drivers[bridge.Driver]
  97. if !ok {
  98. return ErrUnknownDriver
  99. }
  100. bridgeButtons, err := d.Buttons(ctx, bridge)
  101. if err != nil {
  102. return err
  103. }
  104. dbButtons, err := s.buttons.ListByBridge(ctx, bridge)
  105. if err != nil {
  106. return err
  107. }
  108. changedButtons := make([]models.Button, 0, len(bridgeButtons))
  109. newButtons := make([]models.Button, 0, len(dbButtons))
  110. exists := make(map[string]bool, len(dbButtons))
  111. // Check for new or changed
  112. for _, bridgeButton := range bridgeButtons {
  113. found := false
  114. exists[bridgeButton.InternalID] = true
  115. for _, dbButton := range dbButtons {
  116. if bridgeButton.InternalID == dbButton.InternalID {
  117. if dbButton.InternalIndex != bridgeButton.InternalIndex || dbButton.Missing {
  118. dbButton.InternalIndex = bridgeButton.InternalIndex
  119. dbButton.Missing = false
  120. changedButtons = append(changedButtons, dbButton)
  121. log.Println("Updating button", dbButton.ID)
  122. }
  123. found = true
  124. break
  125. }
  126. }
  127. if !found {
  128. newButtons = append(newButtons, bridgeButton)
  129. }
  130. }
  131. // Check for missing buttons
  132. for _, dbButton := range dbButtons {
  133. if !exists[dbButton.InternalID] {
  134. dbButton.Missing = true
  135. changedButtons = append(changedButtons, dbButton)
  136. log.Println("Marking button", dbButton.ID, "as missing")
  137. }
  138. }
  139. // Insert new buttons
  140. for _, newButton := range newButtons {
  141. button, err := s.buttons.Insert(ctx, newButton)
  142. if err != nil {
  143. log.Printf("Failed to insert button %s (%s): %s", button.Name, button.InternalID, err)
  144. continue
  145. }
  146. dbButtons = append(dbButtons, button)
  147. }
  148. // Update changed buttons
  149. for _, changedButton := range changedButtons {
  150. err := s.buttons.Update(ctx, changedButton)
  151. if err != nil {
  152. log.Printf("Failed to change button %s (%d): %s", changedButton.Name, changedButton.ID, err)
  153. continue
  154. }
  155. for i := range dbButtons {
  156. if dbButtons[i].ID == changedButton.ID {
  157. dbButtons[i] = changedButton
  158. break
  159. }
  160. }
  161. }
  162. // Start polling buttons
  163. for _, dbButton := range dbButtons {
  164. s.mutex.Lock()
  165. if exists[dbButton.InternalID] && !s.buttonActive[dbButton.ID] {
  166. s.buttonActive[dbButton.ID] = true
  167. go s.pollButton(ctx, bridge, dbButton)
  168. log.Printf("Polling button %s (%d)", dbButton.Name, dbButton.ID)
  169. }
  170. s.buttonTargets[dbButton.ID] = dbButton.TargetGroupID
  171. s.mutex.Unlock()
  172. }
  173. return nil
  174. }
  175. // UpdateLight updates the light immediately.
  176. func (s *Service) UpdateLight(ctx context.Context, light models.Light) error {
  177. bridge, err := s.bridges.FindByID(ctx, light.BridgeID)
  178. if err != nil {
  179. return httperr.NotFound("Bridge")
  180. }
  181. d, ok := drivers[bridge.Driver]
  182. if !ok {
  183. return ErrUnknownDriver
  184. }
  185. err = s.lights.Update(ctx, light)
  186. if err != nil {
  187. return err
  188. }
  189. return d.Apply(ctx, bridge, light)
  190. }
  191. // SyncLoop runs synclight on all bridges twice every second until the context is
  192. // done.
  193. func (s *Service) SyncLoop(ctx context.Context) {
  194. interval := time.NewTicker(time.Millisecond * 2500)
  195. for {
  196. select {
  197. case <-interval.C:
  198. {
  199. bridges, err := s.Bridges(context.Background())
  200. if err != nil {
  201. log.Println("Could not get bridges:", err)
  202. }
  203. for _, bridge := range bridges {
  204. err = s.SyncLights(ctx, bridge)
  205. if err != nil {
  206. log.Printf("Light sync failed for bridge %s (%d): %s", bridge.Name, bridge.ID, err)
  207. break
  208. }
  209. err = s.SyncButtons(ctx, bridge)
  210. if err != nil {
  211. log.Printf("Button sync failed for bridge %s (%d): %s", bridge.Name, bridge.ID, err)
  212. break
  213. }
  214. }
  215. }
  216. case <-ctx.Done():
  217. {
  218. log.Println("Sync loop stopped.")
  219. return
  220. }
  221. }
  222. }
  223. }
  224. // Bridge gets a bridge by ID.
  225. func (s *Service) Bridge(ctx context.Context, id int) (models.Bridge, error) {
  226. return s.bridges.FindByID(ctx, id)
  227. }
  228. // Bridges gets all known bridges.
  229. func (s *Service) Bridges(ctx context.Context) ([]models.Bridge, error) {
  230. return s.bridges.List(ctx)
  231. }
  232. // Light gets all known bridges.
  233. func (s *Service) Light(ctx context.Context, id int) (models.Light, error) {
  234. return s.lights.FindByID(ctx, id)
  235. }
  236. // DeleteBridge deletes the bridge.
  237. func (s *Service) DeleteBridge(ctx context.Context, bridge models.Bridge) error {
  238. s.mutex.Lock()
  239. defer s.mutex.Unlock()
  240. err := s.bridges.Remove(ctx, bridge)
  241. if err != nil {
  242. return err
  243. }
  244. lights, err := s.lights.ListByBridge(ctx, bridge)
  245. if err != nil {
  246. return err
  247. }
  248. for _, light := range lights {
  249. err := s.lights.Remove(ctx, light)
  250. if err != nil {
  251. return err
  252. }
  253. }
  254. return nil
  255. }
  256. // DeleteBridgeLight deletes the light in a bridge.
  257. func (s *Service) DeleteBridgeLight(ctx context.Context, bridge models.Bridge, light models.Light) error {
  258. s.mutex.Lock()
  259. defer s.mutex.Unlock()
  260. d, ok := drivers[bridge.Driver]
  261. if !ok {
  262. return ErrUnknownDriver
  263. }
  264. err := d.ForgetLight(ctx, bridge, light)
  265. if err != nil {
  266. return err
  267. }
  268. err = s.lights.Remove(ctx, light)
  269. if err != nil {
  270. return err
  271. }
  272. return nil
  273. }
  274. // UpdateBridge updates a bridge.
  275. func (s *Service) UpdateBridge(ctx context.Context, bridge models.Bridge) error {
  276. s.mutex.Lock()
  277. defer s.mutex.Unlock()
  278. err := s.bridges.Update(ctx, bridge)
  279. if err != nil {
  280. return err
  281. }
  282. return nil
  283. }
  284. // DiscoverLights discovers new lights.
  285. func (s *Service) DiscoverLights(ctx context.Context, bridge models.Bridge) error {
  286. d, ok := drivers[bridge.Driver]
  287. if !ok {
  288. return ErrUnknownDriver
  289. }
  290. return d.DiscoverLights(ctx, bridge)
  291. }
  292. // DiscoverBridges discovers new lights.
  293. func (s *Service) DiscoverBridges(ctx context.Context, driver string) ([]models.Bridge, error) {
  294. d, ok := drivers[driver]
  295. if !ok {
  296. return nil, ErrUnknownDriver
  297. }
  298. existingBridges, err := s.Bridges(ctx)
  299. if err != nil {
  300. return nil, err
  301. }
  302. newBridges, err := d.Bridges(ctx)
  303. if err != nil {
  304. return nil, err
  305. }
  306. for _, existingBridge := range existingBridges {
  307. for j, newBridge := range newBridges {
  308. if newBridge.InternalID == existingBridge.InternalID {
  309. newBridges = append(newBridges[:j], newBridges[j+1:]...)
  310. break
  311. }
  312. }
  313. }
  314. return newBridges, nil
  315. }
  316. func (s *Service) pollButton(ctx context.Context, bridge models.Bridge, button models.Button) {
  317. defer func() {
  318. s.mutex.Lock()
  319. s.buttonActive[button.ID] = false
  320. s.mutex.Unlock()
  321. }()
  322. d, ok := drivers[bridge.Driver]
  323. if !ok {
  324. log.Printf("Could not listen on button %s (%d) because the driver %s is unknwon", button.Name, button.ID, bridge.Driver)
  325. return
  326. }
  327. events, err := d.PollButton(ctx, bridge, button)
  328. if err != nil {
  329. log.Printf("Could not listen on button %s (%d) because the driver %s is unknwon", button.Name, button.ID, bridge.Driver)
  330. return
  331. }
  332. for event := range events {
  333. s.mutex.Lock()
  334. targetGroupID := s.buttonTargets[button.ID]
  335. s.mutex.Unlock()
  336. if targetGroupID < 0 {
  337. continue
  338. }
  339. if event.Kind != models.ButtonEventKindPress && event.Kind != models.ButtonEventKindRepeat {
  340. continue
  341. }
  342. group, err := s.groups.FindByID(ctx, targetGroupID)
  343. if err != nil {
  344. if err != sql.ErrNoRows {
  345. log.Println("Group not found:", err, targetGroupID)
  346. }
  347. continue
  348. }
  349. lights, err := s.lights.ListByGroup(ctx, group)
  350. log.Printf("Got button input for group %s (%d) (id: %d, idx: %d, kind: %s)", group.Name, group.ID, button.ID, event.Index, event.Kind)
  351. eg, _ := errgroup.WithContext(ctx)
  352. for i := range lights {
  353. light := &lights[i]
  354. switch event.Index {
  355. case 1:
  356. {
  357. if !light.On {
  358. light.On = true
  359. eg.Go(func() error {
  360. return s.UpdateLight(ctx, *light)
  361. })
  362. }
  363. }
  364. case 2:
  365. {
  366. if light.Brightness < 254 {
  367. if light.Brightness >= (254 - 64) {
  368. light.Brightness = 254
  369. } else {
  370. light.Brightness += 64
  371. }
  372. eg.Go(func() error {
  373. return s.UpdateLight(ctx, *light)
  374. })
  375. }
  376. }
  377. case 3:
  378. {
  379. if light.Brightness > 0 {
  380. if light.Brightness < 64 {
  381. light.Brightness = 0
  382. } else {
  383. light.Brightness -= 64
  384. }
  385. eg.Go(func() error {
  386. return s.UpdateLight(ctx, *light)
  387. })
  388. }
  389. }
  390. case 4:
  391. {
  392. if light.On {
  393. light.On = false
  394. eg.Go(func() error {
  395. return s.UpdateLight(ctx, *light)
  396. })
  397. }
  398. }
  399. }
  400. }
  401. err = eg.Wait()
  402. if err != nil {
  403. log.Println("Failed to update one or more lights:", err)
  404. }
  405. }
  406. }
  407. // NewService creates a new light.Service.
  408. func NewService(bridges models.BridgeRepository, lights models.LightRepository, groups models.GroupRepository, buttons models.ButtonRepository) *Service {
  409. return &Service{
  410. bridges: bridges,
  411. lights: lights,
  412. buttons: buttons,
  413. groups: groups,
  414. buttonActive: make(map[int]bool, 64),
  415. buttonTargets: make(map[int]int, 64),
  416. }
  417. }