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.

174 lines
3.8 KiB

package light
import (
"context"
"errors"
"log"
"time"
"git.aiterp.net/lucifer/lucifer/models"
)
// ErrUnknownDriver is returned by any function asking for a driver name if the driver specified doesn't exist.
var ErrUnknownDriver = errors.New("Unknown driver specified")
// A Service wraps the repos for lights and bridges and takes care of the business logic.
type Service struct {
bridges models.BridgeRepository
lights models.LightRepository
}
// DirectConnect connects to a bridge directly, without going through the discovery process to find them..
func (s *Service) DirectConnect(ctx context.Context, driver string, addr string, name string) (models.Bridge, error) {
d, ok := drivers[driver]
if !ok {
return models.Bridge{}, ErrUnknownDriver
}
bridge := models.Bridge{
ID: -1,
Name: name,
Addr: addr,
Driver: driver,
}
bridge, err := d.Connect(ctx, bridge)
if err != nil {
return models.Bridge{}, err
}
bridge, err = s.bridges.Insert(ctx, bridge)
if err != nil {
return models.Bridge{}, err
}
return bridge, nil
}
// SyncLights syncs all lights in a bridge with the state in the database.
func (s *Service) SyncLights(ctx context.Context, bridge models.Bridge) error {
d, ok := drivers[bridge.Driver]
if !ok {
return ErrUnknownDriver
}
bridgeLights, err := d.Lights(ctx, bridge)
if err != nil {
return err
}
dbLights, err := s.lights.ListByBridge(ctx, bridge)
if err != nil {
return err
}
// Add unknown lights
for _, bridgeLight := range bridgeLights {
found := false
for _, dbLight := range dbLights {
if dbLight.InternalID == bridgeLight.InternalID {
found = true
break
}
}
// Add unknown lights if it doesn't exist in the databse.
if !found {
log.Println("Adding unknown light", bridgeLight.InternalID)
_, err := s.lights.Insert(ctx, bridgeLight)
if err != nil {
return err
}
}
}
changedLights, err := d.ChangedLights(ctx, bridge, dbLights...)
if err != nil {
return err
}
if len(changedLights) > 0 {
err := d.Apply(ctx, bridge, changedLights...)
if err != nil {
log.Printf("Failed to apply one or more of the changes on bridge %d (%s): %s", bridge.ID, bridge.Name, err)
}
}
return nil
}
// SyncLoop runs synclight on all bridges twice every second until the context is
// done.
func (s *Service) SyncLoop(ctx context.Context) {
interval := time.NewTicker(time.Millisecond * 2500)
for {
select {
case <-interval.C:
{
bridges, err := s.Bridges(context.Background())
if err != nil {
log.Println("Could not get bridges:", err)
}
for _, bridge := range bridges {
err = s.SyncLights(ctx, bridge)
if err != nil {
log.Println("Sync failed:", err)
}
}
}
case <-ctx.Done():
{
log.Println("Sync loop stopped.")
return
}
}
}
}
// DiscoverLoop discovers stuff.
func (s *Service) DiscoverLoop(ctx context.Context) {
for {
bridges, err := s.Bridges(ctx)
if err != nil {
log.Println("Failed to get bridges:", err)
continue
}
for _, bridge := range bridges {
d, ok := drivers[bridge.Driver]
if !ok {
continue
}
log.Println("Searcing on bridge", bridge.Name)
err := d.DiscoverLights(ctx, bridge)
if err != nil {
log.Println("Failed to discover lights:", err)
continue
}
time.Sleep(time.Second * 60)
}
}
}
// Bridge gets a bridge by ID.
func (s *Service) Bridge(ctx context.Context, id int) (models.Bridge, error) {
return s.bridges.FindByID(ctx, id)
}
// Bridges gets all known bridges.
func (s *Service) Bridges(ctx context.Context) ([]models.Bridge, error) {
return s.bridges.List(ctx)
}
// NewService creates a new light.Service.
func NewService(bridges models.BridgeRepository, lights models.LightRepository) *Service {
return &Service{
bridges: bridges,
lights: lights,
}
}