add beginning of CLI app and some backend fixes.
This commit is contained in:
+42
-1
@@ -6,6 +6,7 @@ import (
|
||||
"git.aiterp.net/lucifer/new-server/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -14,9 +15,18 @@ func fetchDevices(ctx context.Context, fetchStr string) ([]models.Device, error)
|
||||
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 strings.HasPrefix(fetchStr, "id:") {
|
||||
return config.DeviceRepository().FetchByReference(ctx, models.RKDeviceID, fetchStr[7:])
|
||||
} else if strings.HasPrefix(fetchStr, "name:") {
|
||||
return config.DeviceRepository().FetchByReference(ctx, models.RKName, fetchStr[7:])
|
||||
}else if fetchStr == "all" {
|
||||
return config.DeviceRepository().FetchByReference(ctx, models.RKAll, "")
|
||||
} else {
|
||||
_, err := strconv.Atoi(fetchStr)
|
||||
if err != nil {
|
||||
return config.DeviceRepository().FetchByReference(ctx, models.RKName, fetchStr)
|
||||
}
|
||||
|
||||
return config.DeviceRepository().FetchByReference(ctx, models.RKDeviceID, fetchStr)
|
||||
}
|
||||
}
|
||||
@@ -30,7 +40,7 @@ func Devices(r gin.IRoutes) {
|
||||
return fetchDevices(ctxOf(c), c.Param("fetch"))
|
||||
}))
|
||||
|
||||
r.PUT("/batch", handler(func(c *gin.Context) (interface{}, error) {
|
||||
r.PUT("", handler(func(c *gin.Context) (interface{}, error) {
|
||||
var body []struct {
|
||||
Fetch string `json:"fetch"`
|
||||
SetState models.NewDeviceState `json:"setState"`
|
||||
@@ -81,6 +91,34 @@ func Devices(r gin.IRoutes) {
|
||||
return changed, nil
|
||||
}))
|
||||
|
||||
r.PUT("/:fetch", handler(func(c *gin.Context) (interface{}, error) {
|
||||
update := models.DeviceUpdate{}
|
||||
err := parseBody(c, &update)
|
||||
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 {
|
||||
devices[i].ApplyUpdate(update)
|
||||
|
||||
err := config.DeviceRepository().Save(context.Background(), &devices[i])
|
||||
if err != nil {
|
||||
log.Println("Failed to save device for state:", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return devices, nil
|
||||
}))
|
||||
|
||||
r.PUT("/:fetch/state", handler(func(c *gin.Context) (interface{}, error) {
|
||||
state := models.NewDeviceState{}
|
||||
err := parseBody(c, &state)
|
||||
@@ -159,6 +197,9 @@ func Devices(r gin.IRoutes) {
|
||||
index = i
|
||||
}
|
||||
}
|
||||
if index == -1 {
|
||||
continue
|
||||
}
|
||||
|
||||
device.Tags = append(device.Tags[:index], device.Tags[index+1:]...)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.aiterp.net/lucifer/new-server/models"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
APIRoot string
|
||||
}
|
||||
|
||||
func (client *Client) GetDevices(ctx context.Context, fetchStr string) ([]models.Device, error) {
|
||||
devices := make([]models.Device, 0, 16)
|
||||
err := client.Fetch(ctx, "GET", "/api/devices/"+fetchStr, &devices, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return devices, nil
|
||||
}
|
||||
|
||||
func (client *Client) PutDevice(ctx context.Context, fetchStr string, update models.DeviceUpdate) ([]models.Device, error) {
|
||||
devices := make([]models.Device, 0, 16)
|
||||
err := client.Fetch(ctx, "PUT", "/api/devices/"+fetchStr, &devices, update)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return devices, nil
|
||||
}
|
||||
|
||||
func (client *Client) PutDeviceState(ctx context.Context, fetchStr string, update models.NewDeviceState) ([]models.Device, error) {
|
||||
devices := make([]models.Device, 0, 16)
|
||||
err := client.Fetch(ctx, "PUT", "/api/devices/"+fetchStr+"/state", &devices, update)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return devices, nil
|
||||
}
|
||||
|
||||
func (client *Client) PutDeviceTags(ctx context.Context, fetchStr string, addTags []string, removeTags []string) ([]models.Device, error) {
|
||||
devices := make([]models.Device, 0, 16)
|
||||
err := client.Fetch(ctx, "PUT", "/api/devices/"+fetchStr+"/tags", &devices, map[string][]string{
|
||||
"add": addTags,
|
||||
"remove": removeTags,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return devices, nil
|
||||
}
|
||||
|
||||
func (client *Client) FireEvent(ctx context.Context, event models.Event) error {
|
||||
err := client.Fetch(ctx, "POST", "/api/events", nil, event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (client *Client) Fetch(ctx context.Context, method string, path string, dst interface{}, body interface{}) error {
|
||||
var reqBody io.ReadWriter
|
||||
if body != nil && method != "GET" {
|
||||
reqBody = bytes.NewBuffer(make([]byte, 0, 512))
|
||||
|
||||
err := json.NewEncoder(reqBody).Encode(body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, client.APIRoot+path, reqBody)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := httpClient.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if !strings.HasPrefix(res.Header.Get("Content-Type"), "application/json") {
|
||||
return fmt.Errorf("%s: %s", path, res.Status)
|
||||
}
|
||||
|
||||
var resJson struct {
|
||||
Code int `json:"code"`
|
||||
Message *string `json:"message"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
err = json.NewDecoder(res.Body).Decode(&resJson)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resJson.Code != 200 {
|
||||
msg := ""
|
||||
if resJson.Message != nil {
|
||||
msg = *resJson.Message
|
||||
}
|
||||
|
||||
return fmt.Errorf("%d: %s", resJson.Code, msg)
|
||||
}
|
||||
|
||||
if dst == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return json.Unmarshal(resJson.Data, dst)
|
||||
}
|
||||
|
||||
var httpClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
MaxIdleConns: 16,
|
||||
MaxIdleConnsPerHost: 16,
|
||||
IdleConnTimeout: time.Minute,
|
||||
},
|
||||
Timeout: time.Minute,
|
||||
}
|
||||
@@ -46,6 +46,8 @@ var loc, _ = time.LoadLocation("Europe/Oslo")
|
||||
var ctx = context.Background()
|
||||
|
||||
func handleEvent(event models.Event) (responses []models.Event) {
|
||||
var deadHandlers []models.EventHandler
|
||||
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
duration := time.Since(startTime)
|
||||
@@ -102,6 +104,10 @@ func handleEvent(event models.Event) (responses []models.Event) {
|
||||
continue
|
||||
}
|
||||
|
||||
if handler.OneShot {
|
||||
deadHandlers = append(deadHandlers, handler)
|
||||
}
|
||||
|
||||
if handler.Priority > highestPriority {
|
||||
highestPriority = handler.Priority
|
||||
prioritizedEvent = handler.Actions.FireEvent
|
||||
@@ -159,6 +165,18 @@ func handleEvent(event models.Event) (responses []models.Event) {
|
||||
wg.Done()
|
||||
}(device)
|
||||
}
|
||||
for _, handler := range deadHandlers {
|
||||
wg.Add(1)
|
||||
|
||||
go func(handler models.EventHandler) {
|
||||
err := config.EventHandlerRepository().Delete(context.Background(), &handler)
|
||||
if err != nil {
|
||||
log.Println("Failed to delete spent one-shot event handler:", err)
|
||||
}
|
||||
|
||||
wg.Done()
|
||||
}(handler)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Param struct {
|
||||
Index int
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
func (p *Param) String() *string {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &p.Value
|
||||
}
|
||||
|
||||
func (p *Param) StringOr(fallback string) string {
|
||||
if p == nil {
|
||||
return fallback
|
||||
}
|
||||
|
||||
return p.Value
|
||||
}
|
||||
|
||||
|
||||
func (p *Param) Int() *int {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
n, err := strconv.Atoi(p.Value)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &n
|
||||
}
|
||||
|
||||
func (p *Param) Float() *float64 {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
n, err := strconv.ParseFloat(p.Value, 64)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &n
|
||||
}
|
||||
|
||||
func (p *Param) Bool() *bool {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
v := strings.ToLower(p.Value)
|
||||
if v == "yes" || v == "true" || v == "on" {
|
||||
r := true
|
||||
return &r
|
||||
} else if v == "no" || v == "false" || v == "off" {
|
||||
r := false
|
||||
return &r
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Params []Param
|
||||
|
||||
func (p Params) Get(key interface{}) *Param {
|
||||
switch key := key.(type) {
|
||||
case string:
|
||||
for _, p := range p {
|
||||
if p.Key == key {
|
||||
return &p
|
||||
}
|
||||
}
|
||||
case int:
|
||||
for _, p := range p {
|
||||
if p.Index == key {
|
||||
return &p
|
||||
}
|
||||
}
|
||||
default:
|
||||
log.Panicf("Incorrect key type %T", key)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p Params) Subset(prefix string) Params {
|
||||
if len(p) == 0 {
|
||||
return Params{}
|
||||
}
|
||||
|
||||
if len(prefix) > 0 && !strings.HasSuffix(prefix, ".") {
|
||||
prefix += "."
|
||||
}
|
||||
|
||||
res := make(Params, 0, len(p))
|
||||
|
||||
for _, param := range p {
|
||||
if param.Index == -1 && strings.HasPrefix(param.Key, prefix) {
|
||||
res = append(res, Param{Index: -1, Key: param.Key[len(prefix):], Value: param.Value})
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (p Params) StringMap() map[string]string {
|
||||
res := make(map[string]string)
|
||||
for _, param := range p {
|
||||
if param.Index == -1 {
|
||||
res[param.Key] = param.Value
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (p Params) StringPtrMap() map[string]*string {
|
||||
res := make(map[string]*string)
|
||||
for _, param := range p {
|
||||
if param.Index == -1 {
|
||||
if param.Value == "NULL" {
|
||||
res[param.Key] = nil
|
||||
} else {
|
||||
res[param.Key] = ¶m.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (p Params) Strings(minIndex int) []string {
|
||||
res := make([]string, 0, len(p))
|
||||
for _, param := range p {
|
||||
if param.Index >= minIndex {
|
||||
res = append(res, param.Value)
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
type Command struct {
|
||||
Name string
|
||||
Params Params
|
||||
}
|
||||
|
||||
func parseCommand(args []string) Command {
|
||||
if len(args) == 0 {
|
||||
return Command{Name: "help"}
|
||||
}
|
||||
|
||||
cmd := Command{
|
||||
Name: args[0],
|
||||
Params: make(Params, 0, len(args)-1),
|
||||
}
|
||||
|
||||
nextIndex := 0
|
||||
for _, arg := range args[1:] {
|
||||
kv := strings.SplitN(arg, "=", 2)
|
||||
|
||||
if len(kv) == 2 {
|
||||
cmd.Params = append(cmd.Params, Param{Index: -1, Key: kv[0], Value: kv[1]})
|
||||
} else {
|
||||
cmd.Params = append(cmd.Params, Param{Index: nextIndex, Value: arg})
|
||||
nextIndex += 1
|
||||
}
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
var helpString = `
|
||||
lucy – lucifer CLI
|
||||
|
||||
EXAMPLES
|
||||
lucy set tag:Hexagon color=hs:35,1 intensity=0.3
|
||||
lucy run SetProfile name=evening
|
||||
|
||||
DEVICE COMMANDS
|
||||
list <search>
|
||||
set <search> <power=B> <color=C> <intensity=F> <temperature=N>
|
||||
tag <search> <[+/-]tag-name> –-Tag
|
||||
update <search> <name=S> <icon=S> <prop.*=S/NULL>
|
||||
|
||||
EVENT COMMANDS
|
||||
run <event name> <*=S>
|
||||
`[1:]
|
||||
@@ -0,0 +1,115 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.aiterp.net/lucifer/new-server/app/client"
|
||||
"git.aiterp.net/lucifer/new-server/models"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
c := client.Client{APIRoot: "http://10.24.4.1:8000"}
|
||||
cmd := parseCommand(os.Args[1:])
|
||||
|
||||
switch cmd.Name {
|
||||
// DEVICE
|
||||
case "list":
|
||||
{
|
||||
fetchStr := cmd.Params.Get(0).String()
|
||||
if fetchStr == nil {
|
||||
s := "all"
|
||||
fetchStr = &s
|
||||
}
|
||||
|
||||
devices, err := c.GetDevices(context.Background(), *fetchStr)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
WriteDeviceStateTable(os.Stdout, devices)
|
||||
}
|
||||
case "set":
|
||||
{
|
||||
devices, err := c.PutDeviceState(context.Background(), cmd.Params.Get(0).StringOr("all"), models.NewDeviceState{
|
||||
Power: cmd.Params.Get("power").Bool(),
|
||||
Color: cmd.Params.Get("color").String(),
|
||||
Intensity: cmd.Params.Get("intensity").Float(),
|
||||
Temperature: cmd.Params.Get("temperature").Int(),
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
WriteDeviceStateTable(os.Stdout, devices)
|
||||
}
|
||||
case "tag":
|
||||
{
|
||||
var addTags []string
|
||||
var removeTags []string
|
||||
|
||||
for _, tag := range cmd.Params.Strings(1) {
|
||||
if strings.HasPrefix(tag, "+") {
|
||||
addTags = append(addTags, tag[1:])
|
||||
} else if strings.HasPrefix(tag, "-") {
|
||||
removeTags = append(removeTags, tag[1:])
|
||||
} else {
|
||||
addTags = append(addTags, tag)
|
||||
}
|
||||
}
|
||||
|
||||
devices, err := c.PutDeviceTags(
|
||||
context.Background(),
|
||||
cmd.Params.Get(0).StringOr("all"),
|
||||
addTags,
|
||||
removeTags,
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
WriteDeviceInfoTable(os.Stdout, devices)
|
||||
}
|
||||
case "update":
|
||||
{
|
||||
devices, err := c.PutDevice(context.Background(), cmd.Params.Get(0).StringOr("all"), models.DeviceUpdate{
|
||||
Name: cmd.Params.Get("name").String(),
|
||||
Icon: cmd.Params.Get("icon").String(),
|
||||
UserProperties: cmd.Params.Subset("prop.").StringPtrMap(),
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
WriteDeviceInfoTable(os.Stdout, devices)
|
||||
}
|
||||
|
||||
// EVENT
|
||||
case "run":
|
||||
{
|
||||
name := cmd.Params.Get(0).String()
|
||||
if name == nil {
|
||||
return
|
||||
}
|
||||
|
||||
event := models.Event{
|
||||
Name: *name,
|
||||
Payload: cmd.Params.StringMap(),
|
||||
}
|
||||
|
||||
err := c.FireEvent(context.Background(), event)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
if cmd.Name != "help" {
|
||||
log.Println("Unknown command:", cmd.Name)
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(os.Stderr, helpString[1:])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"git.aiterp.net/lucifer/new-server/models"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func WriteDeviceStateTable(w io.Writer, devices []models.Device) {
|
||||
table := tablewriter.NewWriter(w)
|
||||
table.SetHeader([]string{"ID", "NAME", "POWER", "COLOR", "INTENSITY", "TEMPERATURE"})
|
||||
|
||||
table.SetReflowDuringAutoWrap(true)
|
||||
|
||||
for _, v := range devices {
|
||||
powerStr := ""
|
||||
if v.HasCapability(models.DCPower) {
|
||||
powerStr = strconv.FormatBool(v.State.Power)
|
||||
}
|
||||
|
||||
colorStr := ""
|
||||
if v.HasCapability(models.DCColorHSK, models.DCColorHS, models.DCColorKelvin) {
|
||||
colorStr = v.State.Color.String()
|
||||
}
|
||||
|
||||
temperatureString := ""
|
||||
if v.HasCapability(models.DCTemperatureControl, models.DCTemperatureSensor) {
|
||||
temperatureString = strconv.FormatFloat(v.State.Temperature, 'f', -1, 64)
|
||||
}
|
||||
|
||||
intensityString := ""
|
||||
if v.HasCapability(models.DCIntensity) {
|
||||
intensityString = strconv.FormatFloat(v.State.Intensity, 'f', -1, 64)
|
||||
}
|
||||
|
||||
table.Append([]string{
|
||||
strconv.Itoa(v.ID),
|
||||
v.Name,
|
||||
powerStr,
|
||||
colorStr,
|
||||
intensityString,
|
||||
temperatureString,
|
||||
})
|
||||
}
|
||||
|
||||
table.Render()
|
||||
}
|
||||
|
||||
func WriteDeviceInfoTable(w io.Writer, devices []models.Device) {
|
||||
table := tablewriter.NewWriter(w)
|
||||
table.SetHeader([]string{"ID", "NAME", "ICON", "BUTTONS", "TAGS", "USER PROPERTIES"})
|
||||
|
||||
table.SetReflowDuringAutoWrap(false)
|
||||
|
||||
for _, v := range devices {
|
||||
propStr := ""
|
||||
for key, value := range v.UserProperties {
|
||||
propStr += key + "=" + value + " "
|
||||
}
|
||||
|
||||
table.Append([]string{
|
||||
strconv.Itoa(v.ID),
|
||||
v.Name,
|
||||
v.Icon,
|
||||
strings.Join(v.ButtonNames, ","),
|
||||
strings.Join(v.Tags, ","),
|
||||
propStr,
|
||||
})
|
||||
}
|
||||
|
||||
table.Render()
|
||||
}
|
||||
@@ -10,6 +10,7 @@ require (
|
||||
github.com/lib/pq v1.10.3 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pressly/goose v2.7.0+incompatible
|
||||
golang.org/x/crypto v0.0.0-20210915214749-c084706c2272 // indirect
|
||||
|
||||
@@ -38,6 +38,8 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
@@ -45,6 +47,8 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OH
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
||||
@@ -2,6 +2,7 @@ package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"git.aiterp.net/lucifer/new-server/models"
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
@@ -66,6 +67,8 @@ func (r *DeviceRepo) FetchByReference(ctx context.Context, kind models.Reference
|
||||
q = q.Where(sq.Eq{"id": strings.Split(value, ",")})
|
||||
case models.RKBridgeID:
|
||||
q = q.Where(sq.Eq{"bridge_id": strings.Split(value, ",")})
|
||||
case models.RKName:
|
||||
q = q.Where(sq.Like{"name": value})
|
||||
case models.RKTag:
|
||||
allTags := strings.Split(strings.ReplaceAll(strings.ReplaceAll(value, "-", ",-"), "+", ",+"), ",")
|
||||
optionalTags := make([]string, 0, len(allTags))
|
||||
@@ -91,8 +94,12 @@ func (r *DeviceRepo) FetchByReference(ctx context.Context, kind models.Reference
|
||||
return []models.Device{}, nil
|
||||
}
|
||||
|
||||
query, args, err := q.ToSql()
|
||||
query, args, err := q.OrderBy("name", "id").ToSql()
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return []models.Device{}, nil
|
||||
}
|
||||
|
||||
return nil, dbErr(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,10 +23,10 @@ func (c *ColorValue) IsKelvin() bool {
|
||||
|
||||
func (c *ColorValue) String() string {
|
||||
if c.Kelvin > 0 {
|
||||
return fmt.Sprintf("kelvin:%d", c.Kelvin)
|
||||
return fmt.Sprintf("k:%d", c.Kelvin)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("hsv:%f,%f", c.Hue, c.Saturation)
|
||||
return fmt.Sprintf("hs:%g,%g", c.Hue, c.Saturation)
|
||||
}
|
||||
|
||||
func ParseColorValue(raw string) (ColorValue, error) {
|
||||
|
||||
+26
-1
@@ -19,6 +19,12 @@ type Device struct {
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
||||
type DeviceUpdate struct {
|
||||
Icon *string `json:"icon"`
|
||||
Name *string `json:"name"`
|
||||
UserProperties map[string]*string `json:"userProperties"`
|
||||
}
|
||||
|
||||
// DeviceState contains optional state values that
|
||||
// - Power: Whether the device is powered on
|
||||
// - Color: Color value, if a color setting can be set on the device
|
||||
@@ -35,7 +41,7 @@ type NewDeviceState struct {
|
||||
Power *bool `json:"power"`
|
||||
Color *string `json:"color"`
|
||||
Intensity *float64 `json:"intensity"`
|
||||
Temperature int `json:"temperature"`
|
||||
Temperature *int `json:"temperature"`
|
||||
}
|
||||
|
||||
type DeviceCapability string
|
||||
@@ -79,6 +85,25 @@ var Capabilities = []DeviceCapability{
|
||||
DCTemperatureSensor,
|
||||
}
|
||||
|
||||
func (d *Device) ApplyUpdate(update DeviceUpdate) {
|
||||
if update.Name != nil {
|
||||
d.Name = *update.Name
|
||||
}
|
||||
if update.Icon != nil {
|
||||
d.Icon = *update.Icon
|
||||
}
|
||||
if d.UserProperties == nil {
|
||||
d.UserProperties = make(map[string]string)
|
||||
}
|
||||
for key, value := range update.UserProperties {
|
||||
if value != nil {
|
||||
d.UserProperties[key] = *value
|
||||
} else {
|
||||
delete(d.UserProperties, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) Validate() error {
|
||||
d.Name = strings.Trim(d.Name, " \t\n ")
|
||||
if d.Name == "" {
|
||||
|
||||
+1
-2
@@ -7,6 +7,5 @@ var (
|
||||
RKBridgeID ReferenceKind = "BridgeID"
|
||||
RKTag ReferenceKind = "Tag"
|
||||
RKAll ReferenceKind = "All"
|
||||
RKName ReferenceKind = "Name"
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user