Skip to content

Commit

Permalink
Initial proof of concept
Browse files Browse the repository at this point in the history
Contains a single command that can list the lights in the local bridge,
if the config file has been pre-populated with the bridge address and
user id.

https://github.com/skwair/huectl/ has been used for inspiration on how
to structure the code as well as commands.
  • Loading branch information
Carl-Magnus Björkell committed May 6, 2021
1 parent 34e37a6 commit e1c96aa
Show file tree
Hide file tree
Showing 8 changed files with 669 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.idea/
/huectl
62 changes: 62 additions & 0 deletions cmd/lights.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package cmd

import (
"fmt"
"github.com/sirupsen/logrus"
"os"
"text/tabwriter"

"github.com/spf13/cobra"
)

func newLightsCmd() *cobra.Command {
return &cobra.Command{
Use: "lights",
Aliases: []string{"light", "l"},
Short: "Manage Hue light bulbs",
Args: cobra.NoArgs,
// If called with no sub-command, list lights instead of printing help.
Run: func(*cobra.Command, []string) {
if err := runListLightsCmd(); err != nil {
logrus.Fatal(err)
}
},
}
}

func newListLightsCmd() *cobra.Command {
return &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "List available lights",
Args: cobra.NoArgs,
Run: func(*cobra.Command, []string) {
if err := runListLightsCmd(); err != nil {
logrus.Fatal(err)
}
},
}
}

func runListLightsCmd() error {
b, err := getBridge()
if err != nil {
return fmt.Errorf("unable to find hue brigde: %w", err)
}

lights, err := b.GetLights()
if err != nil {
return fmt.Errorf("unable to list lights: %w", err)
}

tw := tabwriter.NewWriter(os.Stdout, 0, 4, 4, ' ', 0)
defer tw.Flush()

fmt.Fprintln(tw, "ID\tNAME\tON\tREACHABLE\tBRIGHTNESS\tHUE\tSAT")

for _, light := range lights {
fmt.Fprintf(tw, "%d\t%s\t%t\t%t\t%d\t%d\t%d\n", light.ID, light.Name, light.State.On, light.State.Reachable, light.State.Bri, light.State.Hue, light.State.Sat)
}

return nil
}
40 changes: 40 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package cmd

import (
"errors"
"github.com/amimof/huego"
"github.com/callebjorkell/huectl/config"
"github.com/spf13/cobra"
"os"
)

func Huectl() *cobra.Command {
rootCmd := &cobra.Command{
Use: "huectl",
Short: "huectl controls a Philips Hue installation",
}

rootCmd.AddCommand(newVersionCmd())

lightsCmd := newLightsCmd()
rootCmd.AddCommand(lightsCmd)

lightsCmd.AddCommand(newListLightsCmd())

return rootCmd
}

func getBridge() (*huego.Bridge, error) {
cfg, err := config.Read()
if err != nil {
if os.IsNotExist(err) {
return nil, errors.New("config file does not exist, please create one")
}

return nil, err
}

bridge := huego.New(cfg.BridgeAddress, cfg.ClientID)

return bridge, nil
}
28 changes: 28 additions & 0 deletions cmd/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cmd

import (
"fmt"
"runtime"

"github.com/spf13/cobra"
)

var (
version = "dev"
commit = "none"
date = "unknown"
)

func newVersionCmd() *cobra.Command {
return &cobra.Command{
Use: "version",
Short: "Prints the version of huectl",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Version: %s\n", version)
fmt.Printf("Go Version: %s\n", runtime.Version())
fmt.Printf("Built on: %s\n", date)
fmt.Printf("Commit: %s\n", commit)
},
}
}
42 changes: 42 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package config

import (
"fmt"
log "github.com/sirupsen/logrus"
"os"
"path/filepath"

"gopkg.in/yaml.v2"
)

type Config struct {
BridgeAddress string `yaml:"bridgeAddress"`
ClientID string `yaml:"clientID"`
}

// Read the config from the file system
func Read() (*Config, error) {
cfgDir, err := os.UserConfigDir()
if err != nil {
return nil, fmt.Errorf("unable to find the configuration directory: %w", err)
}

cfg := filepath.Join(cfgDir, "huectl", "config.yml")
if _, err = os.Stat(cfg); os.IsNotExist(err) {
log.Warnf("Expected file at %v", cfg)
return nil, err
}

f, err := os.Open(cfg)
if err != nil {
return nil, fmt.Errorf("unable to open config file: %w", err)
}

var config Config
if err = yaml.NewDecoder(f).Decode(&config); err != nil {
return nil, fmt.Errorf("unable to decode config file: %w", err)
}
log.Debugf("Loaded config: %+v", config)

return &config, nil
}
10 changes: 10 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module github.com/callebjorkell/huectl

go 1.16

require (
github.com/amimof/huego v1.2.0
github.com/sirupsen/logrus v1.2.0 // indirect
github.com/spf13/cobra v1.1.3
gopkg.in/yaml.v2 v2.4.0 // indirect
)
450 changes: 450 additions & 0 deletions go.sum

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package main

import (
"fmt"
"github.com/callebjorkell/huectl/cmd"
log "github.com/sirupsen/logrus"
)

type colorFormatter struct {
log.TextFormatter
}

func (f *colorFormatter) Format(entry *log.Entry) ([]byte, error) {
var levelColor int
switch entry.Level {
case log.DebugLevel, log.TraceLevel:
levelColor = 90 // dark grey
case log.WarnLevel:
levelColor = 33 // yellow
case log.ErrorLevel, log.FatalLevel, log.PanicLevel:
levelColor = 91 // bright red
default:
levelColor = 39 // default
}
return []byte(fmt.Sprintf("\x1b[%dm%s\x1b[0m\n", levelColor, entry.Message)), nil
}

func main() {
log.SetFormatter(&colorFormatter{})
log.SetLevel(log.DebugLevel)

if err := cmd.Huectl().Execute(); err != nil {
log.Fatal(err)
}
}

0 comments on commit e1c96aa

Please sign in to comment.