Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6947fc3461 | ||
|
|
600c90a614 |
@@ -7,6 +7,7 @@ import (
|
||||
"git.aiterp.net/lucifer/new-server/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -265,6 +266,46 @@ func Devices(r gin.IRoutes) {
|
||||
|
||||
return withSceneState(devices), nil
|
||||
}))
|
||||
|
||||
r.DELETE("/:fetch", handler(func(c *gin.Context) (interface{}, error) {
|
||||
id, err := strconv.Atoi(c.Param("fetch"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
device, err := config.DeviceRepository().Find(ctxOf(c), id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.Query("forgetmenot") != "true" {
|
||||
bridge, err := config.BridgeRepository().Find(ctxOf(c), device.BridgeID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
driver, err := config.DriverProvider().Provide(bridge.Driver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
forgettableDriver, ok := driver.(models.ForgettableDriver)
|
||||
if !ok {
|
||||
return nil, models.ErrCannotForget
|
||||
}
|
||||
err = forgettableDriver.ForgetDevice(ctxOf(c), bridge, *device)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = config.DeviceRepository().Delete(ctxOf(c), device)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return device, nil
|
||||
}))
|
||||
}
|
||||
|
||||
func withSceneState(devices []models.Device) []models.Device {
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.aiterp.net/lucifer/new-server/models"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@@ -23,6 +23,7 @@ type Bridge struct {
|
||||
externalID int
|
||||
lightStates []*hueLightState
|
||||
sensorStates []*hueSensorState
|
||||
quarantine map[string]time.Time
|
||||
syncingPublish uint32
|
||||
}
|
||||
|
||||
@@ -34,6 +35,10 @@ func (b *Bridge) Refresh(ctx context.Context) error {
|
||||
|
||||
b.mu.Lock()
|
||||
for index, light := range lightMap {
|
||||
if time.Now().Before(b.quarantine[light.Uniqueid]) {
|
||||
continue
|
||||
}
|
||||
|
||||
var state *hueLightState
|
||||
for _, existingState := range b.lightStates {
|
||||
if existingState.index == index {
|
||||
@@ -68,6 +73,10 @@ func (b *Bridge) Refresh(ctx context.Context) error {
|
||||
|
||||
b.mu.Lock()
|
||||
for index, sensor := range sensorMap {
|
||||
if time.Now().Before(b.quarantine[sensor.UniqueID]) {
|
||||
continue
|
||||
}
|
||||
|
||||
var state *hueSensorState
|
||||
for _, existingState := range b.sensorStates {
|
||||
if existingState.index == index {
|
||||
@@ -98,17 +107,17 @@ func (b *Bridge) Refresh(ctx context.Context) error {
|
||||
|
||||
func (b *Bridge) SyncStale(ctx context.Context) error {
|
||||
indices := make([]int, 0, 4)
|
||||
arrayIndices := make([]int, 0, 4)
|
||||
inputs := make([]LightStateInput, 0, 4)
|
||||
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
b.mu.Lock()
|
||||
for i, state := range b.lightStates {
|
||||
for _, state := range b.lightStates {
|
||||
if !state.stale {
|
||||
continue
|
||||
}
|
||||
|
||||
indices = append(indices, state.index)
|
||||
arrayIndices = append(arrayIndices, i)
|
||||
inputs = append(inputs, state.input)
|
||||
}
|
||||
b.mu.Unlock()
|
||||
@@ -117,83 +126,60 @@ func (b *Bridge) SyncStale(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
groups := make([]*syncGroup, 0, 4)
|
||||
for i := range inputs {
|
||||
input := inputs[i]
|
||||
for i, input := range inputs {
|
||||
iCopy := i
|
||||
index := indices[i]
|
||||
inputCopy := input
|
||||
|
||||
found := false
|
||||
for _, group := range groups {
|
||||
if group.State.Equal(input) {
|
||||
group.Indexes = append(group.Indexes, indices[i])
|
||||
group.ArrayIndexes = append(group.ArrayIndexes, arrayIndices[i])
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
groups = append(groups, &syncGroup{
|
||||
GroupIndex: -1,
|
||||
State: input,
|
||||
Indexes: []int{indices[i]},
|
||||
ArrayIndexes: []int{arrayIndices[i]},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
groupMap, err := b.getGroups(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for id, data := range groupMap {
|
||||
for _, group := range groups {
|
||||
if group.Matches(&data) {
|
||||
group.GroupIndex = id
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, group := range groups {
|
||||
if group.GroupIndex == -1 {
|
||||
data := GroupData{
|
||||
Name: "lucifer_auto_group",
|
||||
Lights: []string{},
|
||||
}
|
||||
|
||||
for _, idx := range group.Indexes {
|
||||
data.Lights = append(data.Lights, strconv.Itoa(idx))
|
||||
}
|
||||
|
||||
id, err := b.postGroup(ctx, data)
|
||||
eg.Go(func() error {
|
||||
err := b.putLightState(ctx, index, inputCopy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
group.GroupIndex = id
|
||||
}
|
||||
b.lightStates[iCopy].stale = false
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
log.Println("Updating", len(inputs), "lights on Hue bridge", b.externalID, "in", len(groups), "groups")
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
for _, group := range groups {
|
||||
err := b.putGroupLightState(ctx, group.GroupIndex, group.State)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.mu.Lock()
|
||||
for _, arrayIndex := range group.ArrayIndexes {
|
||||
b.lightStates[arrayIndex].stale = false
|
||||
}
|
||||
b.mu.Unlock()
|
||||
|
||||
if groupMap[group.GroupIndex].Name == "lucifer_auto_group" {
|
||||
err := b.deleteGroup(ctx, group.GroupIndex)
|
||||
if err != nil {
|
||||
log.Println("Could not delete temporary group", err)
|
||||
}
|
||||
func (b *Bridge) ForgetDevice(ctx context.Context, device models.Device) error {
|
||||
// Find index
|
||||
b.mu.Lock()
|
||||
found := false
|
||||
index := -1
|
||||
for i, ls := range b.lightStates {
|
||||
if ls.uniqueID == device.InternalID {
|
||||
found = true
|
||||
index = i
|
||||
}
|
||||
}
|
||||
b.mu.Unlock()
|
||||
if !found {
|
||||
return models.ErrNotFound
|
||||
}
|
||||
|
||||
// Delete light from bridge
|
||||
err := b.deleteLight(ctx, index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove light state from local list. I don't know if the quarantine is necessary, but let's have it anyway.
|
||||
b.mu.Lock()
|
||||
for i, ls := range b.lightStates {
|
||||
if ls.uniqueID == device.InternalID {
|
||||
b.lightStates = append(b.lightStates[:i], b.lightStates[i+1:]...)
|
||||
}
|
||||
}
|
||||
if b.quarantine == nil {
|
||||
b.quarantine = make(map[string]time.Time, 1)
|
||||
}
|
||||
b.quarantine[device.InternalID] = time.Now().Add(time.Second * 30)
|
||||
b.mu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -236,6 +222,10 @@ func (b *Bridge) putGroupLightState(ctx context.Context, index int, input LightS
|
||||
return b.put(ctx, fmt.Sprintf("groups/%d/action", index), input, nil)
|
||||
}
|
||||
|
||||
func (b *Bridge) deleteLight(ctx context.Context, index int) error {
|
||||
return b.delete(ctx, fmt.Sprintf("lights/%d", index), nil)
|
||||
}
|
||||
|
||||
func (b *Bridge) getToken(ctx context.Context) (string, error) {
|
||||
result := make([]CreateUserResponse, 0, 1)
|
||||
err := b.post(ctx, "", CreateUserInput{DeviceType: "git.aiterp.net/lucifer"}, &result)
|
||||
@@ -296,7 +286,6 @@ func (b *Bridge) postGroup(ctx context.Context, input GroupData) (int, error) {
|
||||
return id, err
|
||||
}
|
||||
|
||||
|
||||
func (b *Bridge) deleteGroup(ctx context.Context, index int) error {
|
||||
return b.delete(ctx, "groups/"+strconv.Itoa(index), nil)
|
||||
}
|
||||
@@ -343,7 +332,6 @@ func (b *Bridge) delete(ctx context.Context, resource string, target interface{}
|
||||
return json.NewDecoder(res.Body).Decode(target)
|
||||
}
|
||||
|
||||
|
||||
func (b *Bridge) post(ctx context.Context, resource string, body interface{}, target interface{}) error {
|
||||
rb, err := reqBody(body)
|
||||
if err != nil {
|
||||
|
||||
@@ -372,6 +372,15 @@ func (d *Driver) Run(ctx context.Context, bridge models.Bridge, ch chan<- models
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Driver) ForgetDevice(ctx context.Context, bridge models.Bridge, device models.Device) error {
|
||||
b, err := d.ensureBridge(ctx, bridge)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.ForgetDevice(ctx, device)
|
||||
}
|
||||
|
||||
func (d *Driver) ensureBridge(ctx context.Context, info models.Bridge) (*Bridge, error) {
|
||||
d.mu.Lock()
|
||||
for _, bridge := range d.bridges {
|
||||
|
||||
@@ -16,3 +16,7 @@ type Driver interface {
|
||||
Publish(ctx context.Context, bridge Bridge, devices []Device) error
|
||||
Run(ctx context.Context, bridge Bridge, ch chan<- Event) error
|
||||
}
|
||||
|
||||
type ForgettableDriver interface {
|
||||
ForgetDevice(ctx context.Context, bridge Bridge, device Device) error
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ var ErrUnexpectedResponse = errors.New("driver api returned unexpected response
|
||||
var ErrBridgeSearchFailed = errors.New("bridge search failed")
|
||||
var ErrAddressOnlyDryRunnable = errors.New("this address may only be used for a dry run")
|
||||
var ErrCannotForwardRequest = errors.New("driver is not able to forward requests")
|
||||
var ErrCannotForget = errors.New("forget not supported on this bridge type")
|
||||
|
||||
var ErrInvalidAddress = errors.New("invalid mac address")
|
||||
var ErrPayloadTooShort = errors.New("payload too short")
|
||||
|
||||
Reference in New Issue
Block a user