small fixed, device repo and some device api

This commit is contained in:
2021-09-18 16:08:57 +02:00
parent 4e734c86a5
commit 26fcc1f314
19 changed files with 708 additions and 50 deletions
+22 -3
View File
@@ -1,6 +1,7 @@
package api
import (
"fmt"
"git.aiterp.net/lucifer/new-server/app/config"
"git.aiterp.net/lucifer/new-server/models"
"github.com/gin-gonic/gin"
@@ -18,9 +19,11 @@ func Bridges(r gin.IRoutes) {
r.POST("", handler(func(c *gin.Context) (interface{}, error) {
var body struct {
Driver models.DriverKind `json:"driver"`
Address string `json:"address"`
DryRun bool `json:"dryRun"`
Driver models.DriverKind `json:"driver"`
Address string `json:"address"`
Token string `json:"token"`
ForceSave bool `json:"forceSave"`
DryRun bool `json:"dryRun"`
}
err := parseBody(c, &body)
if err != nil {
@@ -32,6 +35,22 @@ func Bridges(r gin.IRoutes) {
return nil, err
}
if body.ForceSave {
bridge := models.Bridge{
Name: fmt.Sprintf("Manually saved %s bridge (%s)", body.Driver, body.Address),
Driver: body.Driver,
Address: body.Address,
Token: body.Token,
}
err := config.BridgeRepository().Save(ctxOf(c), &bridge)
if err != nil {
return nil, err
}
return []models.Bridge{bridge}, nil
}
bridges, err := driver.SearchBridge(ctxOf(c), body.Address, body.DryRun)
if err != nil {
return nil, err
+123
View File
@@ -0,0 +1,123 @@
package api
import (
"context"
"git.aiterp.net/lucifer/new-server/app/config"
"git.aiterp.net/lucifer/new-server/models"
"github.com/gin-gonic/gin"
"log"
"strings"
)
func fetchDevices(ctx context.Context, fetchStr string) ([]models.Device, error) {
if strings.HasPrefix(fetchStr, "tag:") {
return config.DeviceRepository().FetchByReference(ctx, models.RKTag, fetchStr[4:])
} else if strings.HasPrefix(fetchStr, "bridge:") {
return config.DeviceRepository().FetchByReference(ctx, models.RKBridgeID, fetchStr[7:])
} else if fetchStr == "all" {
return config.DeviceRepository().FetchByReference(ctx, models.RKAll, "")
} else {
return config.DeviceRepository().FetchByReference(ctx, models.RKDeviceID, fetchStr)
}
}
func Devices(r gin.IRoutes) {
r.GET("", handler(func(c *gin.Context) (interface{}, error) {
return config.DeviceRepository().FetchByReference(ctxOf(c), models.RKAll, "")
}))
r.GET("/:fetch", handler(func(c *gin.Context) (interface{}, error) {
return fetchDevices(ctxOf(c), c.Param("fetch"))
}))
r.PUT("/:fetch/state", handler(func(c *gin.Context) (interface{}, error) {
state := models.NewDeviceState{}
err := parseBody(c, &state)
if err != nil {
return nil, err
}
devices, err := fetchDevices(ctxOf(c), c.Param("fetch"))
if err != nil {
return nil, err
}
if len(devices) == 0 {
return []models.Device{}, nil
}
for i := range devices {
err := devices[i].SetState(state)
if err != nil {
return nil, err
}
}
config.PublishChannel <- devices
go func() {
for _, device := range devices {
err := config.DeviceRepository().Save(context.Background(), &device)
if err != nil {
log.Println("Failed to save device for state:", err)
continue
}
}
}()
return devices, nil
}))
r.PUT("/:fetch/tags", handler(func(c *gin.Context) (interface{}, error) {
var body struct {
Add []string `json:"add"`
Remove []string `json:"remove"`
}
err := parseBody(c, &body)
if err != nil {
return nil, err
}
devices, err := fetchDevices(ctxOf(c), c.Param("fetch"))
if err != nil {
return nil, err
}
if len(devices) == 0 {
return []models.Device{}, nil
}
for i := range devices {
device := &devices[i]
for _, tag := range body.Add {
found := false
for _, tag2 := range device.Tags {
if tag == tag2 {
found = true
break
}
}
if !found {
device.Tags = append(device.Tags, tag)
}
}
for _, tag := range body.Remove {
index := -1
for i, tag2 := range device.Tags {
if tag == tag2 {
index = i
}
}
device.Tags = append(device.Tags[:index], device.Tags[index+1:]...)
}
err = config.DeviceRepository().Save(ctxOf(c), device)
if err != nil {
return nil, err
}
}
return devices, nil
}))
}
+3 -6
View File
@@ -10,19 +10,16 @@ import (
)
var dp models.DriverProvider
var dpMutex sync.Mutex
var dpOnce sync.Once
func DriverProvider() models.DriverProvider {
dpMutex.Lock()
defer dpMutex.Unlock()
if dp == nil {
dpOnce.Do(func() {
dp = drivers.DriverMap{
models.DTNanoLeaf: &nanoleaf.Driver{},
models.DTHue: &hue.Driver{},
models.DTLIFX: &lifx.Driver{},
}
}
})
return dp
}
+1 -1
View File
@@ -30,7 +30,7 @@ func ColorPresetRepository() models.ColorPresetRepository {
func DeviceRepository() models.DeviceRepository {
if dRepo == nil {
panic("panik")
dRepo = &mysql.DeviceRepo{DBX: DBX()}
}
return dRepo
+4
View File
@@ -11,12 +11,16 @@ import (
func StartServer() {
services.StartEventHandler()
services.StartPublisher()
services.ConnectToBridges()
services.CheckNewDevices()
gin.SetMode(gin.ReleaseMode)
ginny := gin.New()
apiGin := ginny.Group("/api")
api.Bridges(apiGin.Group("/bridges"))
api.Devices(apiGin.Group("/devices"))
api.ColorPresets(apiGin.Group("/color-presets"))
api.DriverKinds(apiGin.Group("/driver-kinds"))
api.Events(apiGin.Group("/events"))
+30 -4
View File
@@ -3,11 +3,15 @@ package services
import (
"context"
"git.aiterp.net/lucifer/new-server/app/config"
"git.aiterp.net/lucifer/new-server/models"
"log"
"strconv"
"sync"
"time"
)
var cancelMap = make(map[int]context.CancelFunc, 8)
var cancelMutex sync.Mutex
func ConnectToBridges() {
go func() {
@@ -29,7 +33,10 @@ func runConnectToBridges() error {
}
for _, bridge := range bridges {
if cancelMap[bridge.ID] != nil {
cancelMutex.Lock()
isRunning := cancelMap[bridge.ID] != nil
cancelMutex.Unlock()
if isRunning {
continue
}
@@ -39,11 +46,30 @@ func runConnectToBridges() error {
}
ctx, cancel := context.WithCancel(context.Background())
err = driver.Run(ctx, bridge, config.EventChannel)
cancelMutex.Lock()
cancelMap[bridge.ID] = cancel
cancelMutex.Unlock()
log.Printf("Connected to bridge \"%s\" (%d)", bridge.Name, bridge.ID)
log.Printf("Running bridge \"%s\" (%d)", bridge.Name, bridge.ID)
go func(bridge models.Bridge, cancel func()) {
savedDevices, err := config.DeviceRepository().FetchByReference(ctx, models.RKBridgeID, strconv.Itoa(bridge.ID))
if err != nil {
log.Println("Failed to fetch devices from db for refresh:", err)
}
err = driver.Publish(ctx, bridge, savedDevices)
if err != nil {
log.Println("Failed to publish devices from db before run:", err)
}
err = driver.Run(ctx, bridge, config.EventChannel)
log.Printf("Bridge \"%s\" (%d) stopped: %s", bridge.Name, bridge.ID, err)
cancelMutex.Lock()
cancel()
cancelMap[bridge.ID] = nil
cancelMutex.Unlock()
}(bridge, cancel)
}
return nil
+9 -3
View File
@@ -2,10 +2,12 @@ package services
import (
"context"
"fmt"
"git.aiterp.net/lucifer/new-server/app/config"
"git.aiterp.net/lucifer/new-server/models"
"log"
"strconv"
"strings"
"time"
)
@@ -18,7 +20,7 @@ func StartEventHandler() {
}
}()
// Dispatch an HourChanged event at every hour
// Generate TimeChanged event
go func() {
drift := time.Now().Add(time.Minute).Truncate(time.Minute).Sub(time.Now())
time.Sleep(drift + time.Millisecond * 5)
@@ -48,7 +50,12 @@ func handleEvent(event models.Event) {
}
if !X {
log.Println("Unhandled event: " + event.Name)
paramStrings := make([]string, 0, 8)
for key, value := range event.Payload {
paramStrings = append(paramStrings, fmt.Sprintf("%s=%s", key, value))
}
log.Printf("Unhandled event %s(%s)", event.Name, strings.Join(paramStrings, ", "))
return
}
@@ -68,7 +75,6 @@ func handleEvent(event models.Event) {
if !handler.MatchesEvent(event, devices) {
continue
}
}
}
+44 -16
View File
@@ -5,6 +5,8 @@ import (
"git.aiterp.net/lucifer/new-server/app/config"
"git.aiterp.net/lucifer/new-server/models"
"log"
"sync"
"time"
)
func StartPublisher() {
@@ -16,32 +18,58 @@ func StartPublisher() {
continue
}
// Emergency solution! Please avoid!
// Send devices not belonging to the first channel separately
bridgeID := devices[0].BridgeID
lists := make(map[int][]models.Device, 4)
for _, device := range devices {
if device.BridgeID != bridgeID {
config.PublishChannel<-[]models.Device{device}
}
lists[device.BridgeID] = append(lists[device.BridgeID], device)
}
bridge, err := config.BridgeRepository().Find(ctx, devices[0].BridgeID)
ctx, cancel := context.WithTimeout(ctx, time.Second * 30)
bridges, err := config.BridgeRepository().FetchAll(ctx)
if err != nil {
log.Println("Publishing error (1): " + err.Error())
continue
}
driver, err := config.DriverProvider().Provide(bridge.Driver)
if err != nil {
log.Println("Publishing error (2): " + err.Error())
continue
wg := sync.WaitGroup{}
for _, devices := range lists {
wg.Add(1)
go func(devices []models.Device) {
defer wg.Done()
var bridge models.Bridge
for _, bridge2 := range bridges {
if bridge2.ID == devices[0].BridgeID {
bridge = bridge2
}
}
if bridge.ID == 0 {
log.Println("Unknown bridge")
}
bridge, err := config.BridgeRepository().Find(ctx, devices[0].BridgeID)
if err != nil {
log.Println("Publishing error (1): " + err.Error())
return
}
driver, err := config.DriverProvider().Provide(bridge.Driver)
if err != nil {
log.Println("Publishing error (2): " + err.Error())
return
}
err = driver.Publish(ctx, bridge, devices)
if err != nil {
log.Println("Publishing error (3): " + err.Error())
return
}
}(devices)
}
err = driver.Publish(ctx, bridge, devices)
if err != nil {
log.Println("Publishing error (3): " + err.Error())
continue
}
wg.Wait()
cancel()
}
}()
}
+95
View File
@@ -0,0 +1,95 @@
package services
import (
"context"
"git.aiterp.net/lucifer/new-server/app/config"
"git.aiterp.net/lucifer/new-server/models"
"log"
"strconv"
"time"
)
func CheckNewDevices() {
go func() {
// Wait a bit before the first to let bridges connect.
time.Sleep(time.Second * 5)
err := checkNewDevices()
if err != nil {
log.Println("Failed to sync lights:", err)
}
for range time.NewTicker(time.Second * 30).C {
err := checkNewDevices()
if err != nil {
log.Println("Failed to sync lights:", err)
}
}
}()
}
func checkNewDevices() error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*27)
defer cancel()
bridges, err := config.BridgeRepository().FetchAll(ctx)
if err != nil {
return err
}
for _, bridge := range bridges {
driver, err := config.DriverProvider().Provide(bridge.Driver)
if err != nil {
log.Println("Unknown/unsupported driver:", bridge.Driver)
continue
}
savedDevices, err := config.DeviceRepository().FetchByReference(ctx, models.RKBridgeID, strconv.Itoa(bridge.ID))
if err != nil {
log.Println("Failed to list devices from db:", err)
continue
}
driverDevices, err := driver.ListDevices(ctx, bridge)
if err != nil {
log.Println("Failed to list devices from driver:", err)
continue
}
foundNewDevices := false
SaveLoop:
for _, driverDevice := range driverDevices {
for _, savedDevice := range savedDevices {
if savedDevice.InternalID == driverDevice.InternalID {
continue SaveLoop
}
}
log.Println("Saving new device", driverDevice.InternalID)
err := config.DeviceRepository().Save(ctx, &driverDevice)
if err != nil {
log.Println("Failed to save device:", err)
continue
}
foundNewDevices = true
}
// If new devices were found, publish them so that the driver can be set up.
if foundNewDevices {
savedDevices, err := config.DeviceRepository().FetchByReference(ctx, models.RKBridgeID, strconv.Itoa(bridge.ID))
if err != nil {
log.Println("Failed to fetch devices from db second time:", err)
continue
}
err = driver.Publish(ctx, bridge, savedDevices)
if err != nil {
log.Println("Failed to list devices from db:", err)
continue
}
}
}
return nil
}