add beginning of CLI app and some backend fixes.
This commit is contained in:
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user