Browse Source
Added models and repos for Light and Bridge. Added basic light service and driver system that can currently be used to sync Hue lights with the database.
login_bugfix
Added models and repos for Light and Bridge. Added basic light service and driver system that can currently be used to sync Hue lights with the database.
login_bugfix
Gisle Aune
5 years ago
11 changed files with 717 additions and 1 deletions
-
16cmd/lucifer-server/main.go
-
58database/sqlite/bridge-repository.go
-
17database/sqlite/init.go
-
146database/sqlite/light-repository.go
-
4go.mod
-
8go.sum
-
29light/driver.go
-
169light/hue/driver.go
-
144light/service.go
-
24models/bridge.go
-
103models/light.go
@ -0,0 +1,58 @@ |
|||||
|
package sqlite |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
|
||||
|
"git.aiterp.net/lucifer/lucifer/models" |
||||
|
) |
||||
|
|
||||
|
type bridgeRepository struct{} |
||||
|
|
||||
|
// BridgeRepository is a sqlite datbase repository for the Bridge model.
|
||||
|
var BridgeRepository = &bridgeRepository{} |
||||
|
|
||||
|
func (b *bridgeRepository) FindByID(ctx context.Context, id int) (models.Bridge, error) { |
||||
|
bridge := models.Bridge{} |
||||
|
err := db.GetContext(ctx, &bridge, "SELECT * FROM bridge WHERE id=?", id) |
||||
|
|
||||
|
return bridge, err |
||||
|
} |
||||
|
|
||||
|
func (b *bridgeRepository) List(ctx context.Context) ([]models.Bridge, error) { |
||||
|
bridges := make([]models.Bridge, 0, 64) |
||||
|
err := db.SelectContext(ctx, &bridges, "SELECT * FROM bridge") |
||||
|
|
||||
|
return bridges, err |
||||
|
} |
||||
|
|
||||
|
func (b *bridgeRepository) ListByDriver(ctx context.Context, driver string) ([]models.Bridge, error) { |
||||
|
bridges := make([]models.Bridge, 0, 64) |
||||
|
err := db.SelectContext(ctx, &bridges, "SELECT * FROM bridge WHERE driver=?", driver) |
||||
|
|
||||
|
return bridges, err |
||||
|
} |
||||
|
|
||||
|
func (b *bridgeRepository) Insert(ctx context.Context, bridge models.Bridge) (models.Bridge, error) { |
||||
|
res, err := db.NamedExecContext(ctx, "INSERT INTO bridge (name, internal_id, driver, addr, key) VALUES(:name, :internal_id, :driver, :addr, :key)", bridge) |
||||
|
if err != nil { |
||||
|
return models.Bridge{}, err |
||||
|
} |
||||
|
|
||||
|
id, err := res.LastInsertId() |
||||
|
if err != nil { |
||||
|
return models.Bridge{}, err |
||||
|
} |
||||
|
|
||||
|
bridge.ID = int(id) |
||||
|
return bridge, nil |
||||
|
} |
||||
|
|
||||
|
func (b *bridgeRepository) Update(ctx context.Context, bridge models.Bridge) error { |
||||
|
_, err := db.NamedExecContext(ctx, "UPDATE bridge SET name=:name AND addr=:addr AND key=:key WHERE id=:id", bridge) |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
func (b *bridgeRepository) Remove(ctx context.Context, bridge models.Bridge) error { |
||||
|
_, err := db.NamedExecContext(ctx, "DELETE FROM bridge WHERE id=:id LIMIT 1", bridge) |
||||
|
return err |
||||
|
} |
@ -0,0 +1,146 @@ |
|||||
|
package sqlite |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
|
||||
|
"git.aiterp.net/lucifer/lucifer/models" |
||||
|
) |
||||
|
|
||||
|
type lightRepository struct{} |
||||
|
|
||||
|
// LightRepository is a sqlite datbase repository for the Light model.
|
||||
|
var LightRepository = &lightRepository{} |
||||
|
|
||||
|
type structScanner interface { |
||||
|
StructScan(v interface{}) error |
||||
|
} |
||||
|
|
||||
|
func scanLight(row structScanner, light *models.Light) error { |
||||
|
scannedLight := struct { |
||||
|
ID int `db:"id"` |
||||
|
BridgeID int `db:"bridge_id"` |
||||
|
InternalID string `db:"internal_id"` |
||||
|
Name string `db:"name"` |
||||
|
On bool `db:"on"` |
||||
|
Color string `db:"color"` |
||||
|
}{} |
||||
|
|
||||
|
if err := row.StructScan(&scannedLight); err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
light.ID = scannedLight.ID |
||||
|
light.BridgeID = scannedLight.BridgeID |
||||
|
light.InternalID = scannedLight.InternalID |
||||
|
light.Name = scannedLight.Name |
||||
|
light.On = scannedLight.On |
||||
|
|
||||
|
return light.Color.Parse(scannedLight.Color) |
||||
|
} |
||||
|
|
||||
|
func (r *lightRepository) FindByID(ctx context.Context, id int) (models.Light, error) { |
||||
|
row := db.QueryRowxContext(ctx, "SELECT * FROM light WHERE id=?", id) |
||||
|
if err := row.Err(); err != nil { |
||||
|
return models.Light{}, err |
||||
|
} |
||||
|
|
||||
|
light := models.Light{} |
||||
|
if err := scanLight(row, &light); err != nil { |
||||
|
return models.Light{}, err |
||||
|
} |
||||
|
|
||||
|
return light, nil |
||||
|
} |
||||
|
|
||||
|
func (r *lightRepository) FindByInternalID(ctx context.Context, internalID string) (models.Light, error) { |
||||
|
row := db.QueryRowxContext(ctx, "SELECT * FROM light WHERE internal_id=?", internalID) |
||||
|
if err := row.Err(); err != nil { |
||||
|
return models.Light{}, err |
||||
|
} |
||||
|
|
||||
|
light := models.Light{} |
||||
|
if err := scanLight(row, &light); err != nil { |
||||
|
return models.Light{}, err |
||||
|
} |
||||
|
|
||||
|
return light, nil |
||||
|
} |
||||
|
|
||||
|
func (r *lightRepository) List(ctx context.Context) ([]models.Light, error) { |
||||
|
res, err := db.QueryxContext(ctx, "SELECT * FROM light") |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} else if err := res.Err(); err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
lights := make([]models.Light, 0, 64) |
||||
|
for res.Next() { |
||||
|
light := models.Light{} |
||||
|
if err := scanLight(res, &light); err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
lights = append(lights, light) |
||||
|
} |
||||
|
|
||||
|
return lights, nil |
||||
|
} |
||||
|
|
||||
|
func (r *lightRepository) ListByBridge(ctx context.Context, bridge models.Bridge) ([]models.Light, error) { |
||||
|
res, err := db.QueryxContext(ctx, "SELECT * FROM light WHERE bridge_id=?", bridge.ID) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} else if err := res.Err(); err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
lights := make([]models.Light, 0, 64) |
||||
|
for res.Next() { |
||||
|
light := models.Light{} |
||||
|
if err := scanLight(res, &light); err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
lights = append(lights, light) |
||||
|
} |
||||
|
|
||||
|
return lights, nil |
||||
|
} |
||||
|
|
||||
|
func (r *lightRepository) Insert(ctx context.Context, light models.Light) (models.Light, error) { |
||||
|
data := struct { |
||||
|
BridgeID int `db:"bridge_id"` |
||||
|
InternalID string `db:"internal_id"` |
||||
|
Name string `db:"name"` |
||||
|
On bool `db:"on"` |
||||
|
Color string `db:"color"` |
||||
|
}{ |
||||
|
BridgeID: light.BridgeID, |
||||
|
InternalID: light.InternalID, |
||||
|
Name: light.Name, |
||||
|
On: light.On, |
||||
|
Color: light.Color.String(), |
||||
|
} |
||||
|
|
||||
|
res, err := db.NamedExecContext(ctx, "INSERT INTO light (bridge_id, internal_id, name, `on`, color) VALUES(:bridge_id, :internal_id, :name, :on, :color)", data) |
||||
|
if err != nil { |
||||
|
return models.Light{}, err |
||||
|
} |
||||
|
|
||||
|
id, err := res.LastInsertId() |
||||
|
if err != nil { |
||||
|
return models.Light{}, err |
||||
|
} |
||||
|
|
||||
|
light.ID = int(id) |
||||
|
return light, nil |
||||
|
} |
||||
|
|
||||
|
func (r *lightRepository) Update(ctx context.Context, light models.Light) error { |
||||
|
panic("not implemented") |
||||
|
} |
||||
|
|
||||
|
func (r *lightRepository) Remove(ctx context.Context, light models.Light) error { |
||||
|
panic("not implemented") |
||||
|
} |
@ -1,11 +1,15 @@ |
|||||
module git.aiterp.net/lucifer/lucifer |
module git.aiterp.net/lucifer/lucifer |
||||
|
|
||||
require ( |
require ( |
||||
|
github.com/collinux/GoHue v0.0.0-20181229002551-d259041d5eb8 // indirect |
||||
|
github.com/collinux/gohue v0.0.0-20181229002551-d259041d5eb8 |
||||
|
github.com/gerow/go-color v0.0.0-20140219113758-125d37f527f1 |
||||
github.com/gorilla/context v1.1.1 // indirect |
github.com/gorilla/context v1.1.1 // indirect |
||||
github.com/gorilla/mux v1.6.2 |
github.com/gorilla/mux v1.6.2 |
||||
github.com/jmoiron/sqlx v1.2.0 |
github.com/jmoiron/sqlx v1.2.0 |
||||
github.com/mattn/go-sqlite3 v1.10.0 |
github.com/mattn/go-sqlite3 v1.10.0 |
||||
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b |
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b |
||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 |
||||
google.golang.org/appengine v1.4.0 // indirect |
google.golang.org/appengine v1.4.0 // indirect |
||||
gopkg.in/yaml.v2 v2.2.2 |
gopkg.in/yaml.v2 v2.2.2 |
||||
) |
) |
@ -0,0 +1,29 @@ |
|||||
|
package light |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
|
||||
|
"git.aiterp.net/lucifer/lucifer/models" |
||||
|
) |
||||
|
|
||||
|
var drivers = make(map[string]Driver) |
||||
|
|
||||
|
// A Driver that communicates with an underlying lighting system.
|
||||
|
type Driver interface { |
||||
|
// Apply applies all changed lights, which are lights that differ from what DiscoverLights returned.
|
||||
|
Apply(ctx context.Context, bridge models.Bridge, lights ...models.Light) error |
||||
|
|
||||
|
// DiscoverLights lists all available lights. The `ID` field will the -1.
|
||||
|
DiscoverLights(ctx context.Context, bridge models.Bridge) ([]models.Light, error) |
||||
|
|
||||
|
// DiscoverBridges lists all available bridges.
|
||||
|
DiscoverBridges(ctx context.Context) ([]models.Bridge, error) |
||||
|
|
||||
|
// Connect connects to a bridge, returning the bridge with the API Key.
|
||||
|
Connect(ctx context.Context, bridge models.Bridge) (models.Bridge, error) |
||||
|
} |
||||
|
|
||||
|
// RegisterDriver registers a driver. This must happen in init() functions.
|
||||
|
func RegisterDriver(name string, driver Driver) { |
||||
|
drivers[name] = driver |
||||
|
} |
@ -0,0 +1,169 @@ |
|||||
|
package hue |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"errors" |
||||
|
"log" |
||||
|
"sync" |
||||
|
"time" |
||||
|
|
||||
|
"git.aiterp.net/lucifer/lucifer/light" |
||||
|
"git.aiterp.net/lucifer/lucifer/models" |
||||
|
gohue "github.com/collinux/gohue" |
||||
|
"golang.org/x/sync/errgroup" |
||||
|
) |
||||
|
|
||||
|
// A driver is a driver for Phillips Hue lights.
|
||||
|
type driver struct { |
||||
|
mutex sync.Mutex |
||||
|
bridges map[int]*gohue.Bridge |
||||
|
} |
||||
|
|
||||
|
func (d *driver) Apply(ctx context.Context, bridge models.Bridge, lights ...models.Light) error { |
||||
|
hueBridge, err := d.getBridge(bridge) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
hueLights, err := hueBridge.GetAllLights() |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
eg, egCtx := errgroup.WithContext(ctx) |
||||
|
|
||||
|
for _, hueLight := range hueLights { |
||||
|
if !hueLight.State.Reachable { |
||||
|
continue |
||||
|
} |
||||
|
|
||||
|
for _, light := range lights { |
||||
|
if hueLight.UniqueID != light.InternalID { |
||||
|
continue |
||||
|
} |
||||
|
|
||||
|
// Prevent race condition since `hueLight` changes per iteration.
|
||||
|
hl := hueLight |
||||
|
|
||||
|
eg.Go(func() error { |
||||
|
select { |
||||
|
case <-egCtx.Done(): |
||||
|
return egCtx.Err() |
||||
|
default: |
||||
|
} |
||||
|
|
||||
|
return hl.SetState(gohue.LightState{ |
||||
|
On: light.On, |
||||
|
Hue: light.Color.Hue, |
||||
|
Sat: light.Color.Sat, |
||||
|
Bri: light.Color.Bri, |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
break |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return eg.Wait() |
||||
|
} |
||||
|
|
||||
|
func (d *driver) DiscoverLights(ctx context.Context, bridge models.Bridge) ([]models.Light, error) { |
||||
|
hueBridge, err := d.getBridge(bridge) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
hueLights, err := hueBridge.GetAllLights() |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
lights := make([]models.Light, 0, len(hueLights)) |
||||
|
for _, hueLight := range hueLights { |
||||
|
lights = append(lights, models.Light{ |
||||
|
ID: -1, |
||||
|
Name: hueLight.Name, |
||||
|
BridgeID: bridge.ID, |
||||
|
InternalID: hueLight.UniqueID, |
||||
|
On: hueLight.State.On, |
||||
|
Color: models.LightColor{ |
||||
|
Hue: hueLight.State.Hue, |
||||
|
Sat: hueLight.State.Saturation, |
||||
|
Bri: hueLight.State.Bri, |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
return lights, nil |
||||
|
} |
||||
|
|
||||
|
func (d *driver) DiscoverBridges(ctx context.Context) ([]models.Bridge, error) { |
||||
|
panic("not implemented") |
||||
|
} |
||||
|
|
||||
|
func (d *driver) Connect(ctx context.Context, bridge models.Bridge) (models.Bridge, error) { |
||||
|
hueBridge, err := gohue.NewBridge(bridge.Addr) |
||||
|
if err != nil { |
||||
|
log.Fatalln(err) |
||||
|
} |
||||
|
|
||||
|
// Make 30 attempts (30 seconds)
|
||||
|
attempts := 30 |
||||
|
for attempts > 0 { |
||||
|
key, err := hueBridge.CreateUser("Lucifer (git.aiterp.net/lucifer/lucifer)") |
||||
|
if len(key) > 0 && err == nil { |
||||
|
bridge.Key = []byte(key) |
||||
|
bridge.InternalID = hueBridge.Info.Device.SerialNumber |
||||
|
|
||||
|
return bridge, nil |
||||
|
} |
||||
|
|
||||
|
select { |
||||
|
case <-time.After(time.Second): |
||||
|
attempts-- |
||||
|
case <-ctx.Done(): |
||||
|
return models.Bridge{}, ctx.Err() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return models.Bridge{}, errors.New("Failed to create bridge") |
||||
|
} |
||||
|
|
||||
|
func (d *driver) getBridge(bridge models.Bridge) (*gohue.Bridge, error) { |
||||
|
d.mutex.Lock() |
||||
|
defer d.mutex.Unlock() |
||||
|
|
||||
|
if hueBridge, ok := d.bridges[bridge.ID]; ok { |
||||
|
return hueBridge, nil |
||||
|
} |
||||
|
|
||||
|
hueBridge, err := gohue.NewBridge(bridge.Addr) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
if err := hueBridge.GetInfo(); err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
if hueBridge.Info.Device.SerialNumber != bridge.InternalID { |
||||
|
return nil, errors.New("Serial number does not match hardware") |
||||
|
} |
||||
|
|
||||
|
err = hueBridge.Login(string(bridge.Key)) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
d.bridges[bridge.ID] = hueBridge |
||||
|
|
||||
|
return hueBridge, nil |
||||
|
} |
||||
|
|
||||
|
func init() { |
||||
|
driver := &driver{ |
||||
|
bridges: make(map[int]*gohue.Bridge, 16), |
||||
|
} |
||||
|
|
||||
|
light.RegisterDriver("hue", driver) |
||||
|
} |
@ -0,0 +1,144 @@ |
|||||
|
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.DiscoverLights(ctx, bridge) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
dbLights, err := s.lights.ListByBridge(ctx, bridge) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
// Sync with matching db light if it exists.
|
||||
|
changedLights := make([]models.Light, 0, len(dbLights)) |
||||
|
|
||||
|
LightLoop: |
||||
|
for _, bridgeLight := range bridgeLights { |
||||
|
for _, dbLight := range dbLights { |
||||
|
if dbLight.InternalID == bridgeLight.InternalID { |
||||
|
if !dbLight.Color.Equals(bridgeLight.Color) || dbLight.On != bridgeLight.On { |
||||
|
changedLights = append(changedLights, dbLight) |
||||
|
} |
||||
|
|
||||
|
continue LightLoop |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Add unknown lights if it doesn't exist in the databse.
|
||||
|
log.Println("Adding unknown light", bridgeLight.InternalID) |
||||
|
_, err := s.lights.Insert(ctx, bridgeLight) |
||||
|
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.Second / 2) |
||||
|
|
||||
|
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 |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 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, |
||||
|
} |
||||
|
} |
@ -0,0 +1,24 @@ |
|||||
|
package models |
||||
|
|
||||
|
import "context" |
||||
|
|
||||
|
// A Bridge is a device or service that holds lights.
|
||||
|
type Bridge struct { |
||||
|
ID int `json:"id" db:"id"` |
||||
|
Name string `json:"name" db:"name"` |
||||
|
Driver string `json:"driver" db:"driver"` |
||||
|
Addr string `json:"addr" db:"addr"` |
||||
|
InternalID string `json:"-" db:"internal_id"` |
||||
|
Key []byte `json:"-" db:"key"` |
||||
|
} |
||||
|
|
||||
|
// BridgeRepository is an interface for all database operations
|
||||
|
// the Bridge model makes.
|
||||
|
type BridgeRepository interface { |
||||
|
FindByID(ctx context.Context, id int) (Bridge, error) |
||||
|
List(ctx context.Context) ([]Bridge, error) |
||||
|
ListByDriver(ctx context.Context, driver string) ([]Bridge, error) |
||||
|
Insert(ctx context.Context, bridge Bridge) (Bridge, error) |
||||
|
Update(ctx context.Context, bridge Bridge) error |
||||
|
Remove(ctx context.Context, bridge Bridge) error |
||||
|
} |
@ -0,0 +1,103 @@ |
|||||
|
package models |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"errors" |
||||
|
"fmt" |
||||
|
"strconv" |
||||
|
"strings" |
||||
|
|
||||
|
goColor "github.com/gerow/go-color" |
||||
|
) |
||||
|
|
||||
|
// A Light represents a bulb.
|
||||
|
type Light struct { |
||||
|
ID int `json:"id"` |
||||
|
BridgeID int `json:"bridgeId"` |
||||
|
InternalID string `json:"-"` |
||||
|
Name string `json:"name"` |
||||
|
On bool `json:"on"` |
||||
|
Color LightColor `json:"color"` |
||||
|
} |
||||
|
|
||||
|
// LightColor represent a HSB color.
|
||||
|
type LightColor struct { |
||||
|
Hue uint16 |
||||
|
Sat uint8 |
||||
|
Bri uint8 |
||||
|
} |
||||
|
|
||||
|
// SetRGB sets the light color's RGB.
|
||||
|
func (color *LightColor) SetRGB(r, g, b uint8) { |
||||
|
rgb := goColor.RGB{ |
||||
|
R: float64(r) / 255, |
||||
|
G: float64(g) / 255, |
||||
|
B: float64(b) / 255, |
||||
|
} |
||||
|
hsl := rgb.ToHSL() |
||||
|
|
||||
|
color.Hue = uint16(hsl.H * 65535) |
||||
|
color.Sat = uint8(hsl.S * 255) |
||||
|
color.Bri = uint8(hsl.L * 255) |
||||
|
} |
||||
|
|
||||
|
// String prints the color as HSB.
|
||||
|
func (color *LightColor) String() string { |
||||
|
return fmt.Sprintf("%d,%d,%d", color.Hue, color.Sat, color.Bri) |
||||
|
} |
||||
|
|
||||
|
// Equals returns true if all the light's values are the same.
|
||||
|
func (color *LightColor) Equals(other LightColor) bool { |
||||
|
return color.Hue == other.Hue && color.Sat == other.Sat && color.Bri == other.Bri |
||||
|
} |
||||
|
|
||||
|
// Parse parses three comma separated number.
|
||||
|
func (color *LightColor) Parse(src string) error { |
||||
|
split := strings.SplitN(src, ",", 3) |
||||
|
if len(split) < 3 { |
||||
|
return errors.New("H,S,V format is incomplete") |
||||
|
} |
||||
|
|
||||
|
hue, err := strconv.ParseUint(split[0], 10, 16) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
sat, err := strconv.ParseUint(split[1], 10, 16) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
bri, err := strconv.ParseUint(split[2], 10, 16) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
color.Hue = uint16(hue) |
||||
|
color.Sat = uint8(sat) |
||||
|
color.Bri = uint8(bri) |
||||
|
|
||||
|
// Hue doesn't like 0 and 255 for Sat and Bri.
|
||||
|
if color.Sat < 1 { |
||||
|
color.Sat = 1 |
||||
|
} else if color.Sat > 254 { |
||||
|
color.Sat = 254 |
||||
|
} |
||||
|
if color.Bri < 1 { |
||||
|
color.Bri = 1 |
||||
|
} else if color.Bri > 254 { |
||||
|
color.Bri = 254 |
||||
|
} |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// LightRepository is an interface for all database operations
|
||||
|
// the Light model makes.
|
||||
|
type LightRepository interface { |
||||
|
FindByID(ctx context.Context, id int) (Light, error) |
||||
|
FindByInternalID(ctx context.Context, internalID string) (Light, error) |
||||
|
List(ctx context.Context) ([]Light, error) |
||||
|
ListByBridge(ctx context.Context, bridge Bridge) ([]Light, error) |
||||
|
Insert(ctx context.Context, light Light) (Light, error) |
||||
|
Update(ctx context.Context, light Light) error |
||||
|
Remove(ctx context.Context, light Light) error |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue