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.

353 lines
8.2 KiB

1 year ago
2 years ago
2 years ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
  1. package httpapiv1
  2. import (
  3. lucifer3 "git.aiterp.net/lucifer3/server"
  4. "git.aiterp.net/lucifer3/server/commands"
  5. "git.aiterp.net/lucifer3/server/effects"
  6. "git.aiterp.net/lucifer3/server/events"
  7. "git.aiterp.net/lucifer3/server/internal/color"
  8. "git.aiterp.net/lucifer3/server/internal/gentools"
  9. "git.aiterp.net/lucifer3/server/services/script"
  10. "git.aiterp.net/lucifer3/server/services/uistate"
  11. "github.com/google/uuid"
  12. "github.com/gorilla/websocket"
  13. "github.com/labstack/echo/v4"
  14. "github.com/labstack/echo/v4/middleware"
  15. "log"
  16. "net"
  17. "net/http"
  18. "sync"
  19. "time"
  20. )
  21. var zeroUUID = uuid.UUID{
  22. 0, 0, 0, 0,
  23. 0, 0, 0, 0,
  24. 0, 0, 0, 0,
  25. 0, 0, 0, 0,
  26. }
  27. func New(addr string) (lucifer3.Service, error) {
  28. svc := &service{}
  29. upgrader := websocket.Upgrader{
  30. CheckOrigin: func(r *http.Request) bool {
  31. return true
  32. },
  33. }
  34. e := echo.New()
  35. e.HideBanner = true
  36. e.HidePort = true
  37. e.Use(middleware.CORS())
  38. e.GET("/color/:value", func(c echo.Context) error {
  39. col, err := color.Parse(c.Param("value"))
  40. if err != nil {
  41. return c.String(400, err.Error())
  42. }
  43. rgb, _ := col.ToRGB()
  44. xy, _ := col.ToXY()
  45. hs, _ := col.ToHS()
  46. return c.JSON(200, color.Color{
  47. K: col.K,
  48. RGB: rgb.RGB,
  49. HS: hs.HS,
  50. XY: xy.XY,
  51. })
  52. })
  53. e.GET("/state", func(c echo.Context) error {
  54. svc.mu.Lock()
  55. data := svc.data
  56. svc.mu.Unlock()
  57. return c.JSON(200, data)
  58. })
  59. e.GET("/subscribe", func(c echo.Context) error {
  60. ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil)
  61. if err != nil {
  62. return err
  63. }
  64. sub := make(chan uistate.Patch, 64)
  65. svc.addSub(sub)
  66. defer svc.removeSub(sub)
  67. defer ws.Close()
  68. lastSent := time.Now()
  69. patches := make([]uistate.Patch, 0, 128)
  70. for {
  71. patch := <-sub
  72. since := time.Now().Sub(lastSent)
  73. patches = append(patches[:0], patch)
  74. if since < time.Millisecond*950 {
  75. waitCh := time.NewTimer(time.Millisecond*1000 - since)
  76. waiting := true
  77. for waiting {
  78. select {
  79. case patch, ok := <-sub:
  80. patches = append(patches, patch)
  81. waiting = ok
  82. case <-waitCh.C:
  83. waiting = false
  84. }
  85. }
  86. }
  87. err := ws.WriteJSON(patches)
  88. if err != nil {
  89. break
  90. }
  91. lastSent = time.Now()
  92. }
  93. return nil
  94. })
  95. e.GET("/subscribe-simple", func(c echo.Context) error {
  96. type ChangedPatch struct {
  97. Devices map[string]*uistate.Device `json:"devices"`
  98. Assignments map[uuid.UUID]*uistate.Assignment `json:"assignments"`
  99. Scripts map[string][]script.Line `json:"scripts"`
  100. Triggers map[uuid.UUID]*script.Trigger `json:"triggers"`
  101. Full *uistate.Data `json:"full,omitempty"`
  102. }
  103. ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil)
  104. if err != nil {
  105. return err
  106. }
  107. sub := make(chan uistate.Patch, 64)
  108. svc.addSub(sub)
  109. defer svc.removeSub(sub)
  110. defer ws.Close()
  111. svc.mu.Lock()
  112. patch := ChangedPatch{Full: gentools.Ptr(svc.data.Copy())}
  113. svc.mu.Unlock()
  114. err = ws.WriteJSON(patch)
  115. if err != nil {
  116. return err
  117. }
  118. patches := make([]uistate.Patch, 0, 128)
  119. for {
  120. patch := <-sub
  121. patches = append(patches[:0], patch)
  122. waitCh := time.After(time.Millisecond * 330)
  123. waiting := true
  124. for waiting {
  125. select {
  126. case patch, ok := <-sub:
  127. patches = append(patches, patch)
  128. waiting = ok
  129. case <-waitCh:
  130. waiting = false
  131. }
  132. }
  133. statePatch := ChangedPatch{
  134. Devices: make(map[string]*uistate.Device),
  135. Assignments: make(map[uuid.UUID]*uistate.Assignment),
  136. Scripts: make(map[string][]script.Line),
  137. Triggers: make(map[uuid.UUID]*script.Trigger),
  138. Full: nil,
  139. }
  140. svc.mu.Lock()
  141. data := svc.data.Copy()
  142. svc.mu.Unlock()
  143. for _, patch := range patches {
  144. if patch.Device != nil {
  145. if patch.Device.Delete {
  146. statePatch.Devices[patch.Device.ID] = nil
  147. } else {
  148. statePatch.Devices[patch.Device.ID] = gentools.Ptr(data.Devices[patch.Device.ID])
  149. }
  150. }
  151. if patch.Assignment != nil {
  152. if patch.Assignment.Delete {
  153. statePatch.Assignments[patch.Assignment.ID] = nil
  154. } else {
  155. statePatch.Assignments[patch.Assignment.ID] = gentools.Ptr(data.Assignments[patch.Assignment.ID])
  156. }
  157. }
  158. if patch.Script != nil {
  159. if len(patch.Script.Lines) > 0 {
  160. statePatch.Scripts[patch.Script.Name] = data.Scripts[patch.Script.Name]
  161. } else {
  162. statePatch.Scripts[patch.Script.Name] = nil
  163. }
  164. }
  165. if patch.Trigger != nil {
  166. if patch.Trigger.Delete {
  167. statePatch.Triggers[patch.Trigger.ID] = nil
  168. } else {
  169. statePatch.Triggers[patch.Trigger.ID] = gentools.Ptr(data.Triggers[patch.Trigger.ID])
  170. }
  171. }
  172. }
  173. err := ws.WriteJSON(statePatch)
  174. if err != nil {
  175. break
  176. }
  177. }
  178. return nil
  179. })
  180. e.POST("/command", func(c echo.Context) error {
  181. var input commandInput
  182. err := c.Bind(&input)
  183. if err != nil {
  184. return err
  185. }
  186. svc.mu.Lock()
  187. bus := svc.bus
  188. svc.mu.Unlock()
  189. if bus == nil {
  190. return c.String(413, "Waiting for bus")
  191. }
  192. switch {
  193. case input.Assign != nil:
  194. bus.RunCommand(commands.Assign{
  195. ID: nil,
  196. Match: input.Assign.Match,
  197. Effect: input.Assign.Effect.Effect,
  198. })
  199. case input.PairDevice != nil:
  200. bus.RunCommand(*input.PairDevice)
  201. case input.ConnectDevice != nil:
  202. bus.RunCommand(*input.ConnectDevice)
  203. case input.ForgetDevice != nil:
  204. bus.RunCommand(*input.ForgetDevice)
  205. case input.SearchDevices != nil:
  206. bus.RunCommand(*input.SearchDevices)
  207. case input.AddAlias != nil:
  208. bus.RunCommand(*input.AddAlias)
  209. case input.RemoveAlias != nil:
  210. bus.RunCommand(*input.RemoveAlias)
  211. case input.UpdateScript != nil:
  212. bus.RunCommand(*input.UpdateScript)
  213. case input.ExecuteScript != nil:
  214. bus.RunCommand(*input.ExecuteScript)
  215. case input.UpdateTrigger != nil:
  216. if input.UpdateTrigger.ID == zeroUUID {
  217. input.UpdateTrigger.ID = uuid.New()
  218. }
  219. if input.UpdateTrigger.ScriptPre == nil {
  220. input.UpdateTrigger.ScriptPre = make([]script.Line, 0)
  221. }
  222. if input.UpdateTrigger.ScriptPost == nil {
  223. input.UpdateTrigger.ScriptPost = make([]script.Line, 0)
  224. }
  225. bus.RunCommand(*input.UpdateTrigger)
  226. case input.DeleteTrigger != nil:
  227. bus.RunCommand(*input.DeleteTrigger)
  228. default:
  229. return c.String(400, "No supported command found in input")
  230. }
  231. return c.JSON(200, input)
  232. })
  233. listener, err := net.Listen("tcp", addr)
  234. if err != nil {
  235. return nil, err
  236. }
  237. e.Listener = listener
  238. go func() {
  239. err := e.Start(addr)
  240. if err != nil {
  241. log.Fatalln("Failed to listen to webserver")
  242. }
  243. }()
  244. return svc, nil
  245. }
  246. type service struct {
  247. mu sync.Mutex
  248. data uistate.Data
  249. bus *lucifer3.EventBus
  250. subs []chan uistate.Patch
  251. }
  252. func (s *service) Active() bool {
  253. return true
  254. }
  255. func (s *service) HandleEvent(bus *lucifer3.EventBus, event lucifer3.Event) {
  256. switch event := event.(type) {
  257. case events.Started:
  258. s.mu.Lock()
  259. s.bus = bus
  260. s.mu.Unlock()
  261. case uistate.Patch:
  262. s.mu.Lock()
  263. s.data = s.data.WithPatch(event)
  264. subs := s.subs
  265. s.mu.Unlock()
  266. for _, sub := range subs {
  267. select {
  268. case sub <- event:
  269. default:
  270. }
  271. }
  272. }
  273. }
  274. func (s *service) addSub(ch chan uistate.Patch) {
  275. s.mu.Lock()
  276. defer s.mu.Unlock()
  277. s.subs = append(s.subs, ch)
  278. }
  279. func (s *service) removeSub(ch chan uistate.Patch) {
  280. s.mu.Lock()
  281. defer s.mu.Unlock()
  282. s.subs = append([]chan uistate.Patch{}, s.subs...)
  283. for i, sub := range s.subs {
  284. if sub == ch {
  285. s.subs = append(s.subs[:i], s.subs[i+1:]...)
  286. break
  287. }
  288. }
  289. }
  290. type commandInput struct {
  291. Assign *assignInput `json:"assign,omitempty"`
  292. AddAlias *commands.AddAlias `json:"addAlias,omitempty"`
  293. RemoveAlias *commands.RemoveAlias `json:"removeAlias,omitempty"`
  294. PairDevice *commands.PairDevice `json:"pairDevice,omitempty"`
  295. ConnectDevice *commands.ConnectDevice `json:"connectDevice,omitempty"`
  296. SearchDevices *commands.SearchDevices `json:"searchDevices,omitempty"`
  297. ForgetDevice *commands.ForgetDevice `json:"forgetDevice,omitempty"`
  298. UpdateScript *script.Update `json:"updateScript,omitempty"`
  299. ExecuteScript *script.Execute `json:"executeScript,omitempty"`
  300. UpdateTrigger *script.UpdateTrigger `json:"updateTrigger,omitempty"`
  301. DeleteTrigger *script.DeleteTrigger `json:"deleteTrigger,omitempty"`
  302. }
  303. type assignInput struct {
  304. Match string `json:"match"`
  305. Effect effects.Serializable `json:"effect"`
  306. }