mystreamdeck/cmd/root.go
2020-10-04 15:28:04 -05:00

653 lines
17 KiB
Go

/*Package cmd - root command
Copyright © 2020 Jeffrey C. Ollie <jeff@ocjtech.us>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cmd
import (
"fmt"
"image/color"
"log"
"os"
"sync"
"git.ocjtech.us/jeff/mystreamdeck/internal"
"github.com/go-fonts/dejavu/dejavusans"
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
"github.com/jcollie/go-homeassistant"
"github.com/jcollie/go-streamdeck"
homedir "github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var cfgFile string
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "mystreamdeck",
Short: "",
Long: ``,
Run: root,
}
// Execute .
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.mystreamdeck.yaml)")
}
// initConfig reads in config file and ENV variables if set.
func initConfig() {
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Find home directory.
home, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// Search config in home directory with name ".mystreamdeck" (without extension).
viper.AddConfigPath(home)
viper.SetConfigName(".mystreamdeck")
}
viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
log.Println("Using config file:", viper.ConfigFileUsed())
}
}
// RemoteSendCommand .
type RemoteSendCommand struct {
EntityID string `json:"entity_id"`
Device string `json:"device"`
Commands []string `json:"command"`
NumRepeats int `json:"num_repeats,omitempty"`
DelaySecs float64 `json:"delay_secs,omitempty"`
}
func root(cmd *cobra.Command, args []string) {
var wg sync.WaitGroup
var err error
sd, err := streamdeck.New()
if err != nil {
log.Printf("unable to get new streamdeck: %+v", err)
return
}
sd.ResetToLogo()
// sd.ResetKeyStream()
ha, err := homeassistant.NewConnection(
viper.GetString("homeassistant.hostname"),
viper.GetInt("homeassistant.port"),
viper.GetString("homeassistant.access_token"),
viper.GetBool("homeassistant.secure"),
)
if err != nil {
log.Printf("unable to get connection to home assistant: %+v", err)
return
}
b := internal.NewDynamicIconButton(
ha,
sd,
"switch.living_room_tv",
internal.ButtonInfo{X: 2, Y: 2},
internal.DynamicIconInfo{
ForState: func(pressed bool, state string) streamdeck.IconButton {
log.Println(pressed, state)
switch {
case pressed && state == "off":
return streamdeck.IconButton{
Icon: "television-off",
IconColor: color.RGBA{255, 0, 0, 255},
BackgroundColor: color.RGBA{0, 0, 0, 255},
}
case pressed && state == "on":
return streamdeck.IconButton{
Icon: "television",
IconColor: color.RGBA{255, 0, 0, 255},
BackgroundColor: color.RGBA{0, 0, 0, 255},
}
case !pressed && state == "off":
return streamdeck.IconButton{
Icon: "television-off",
IconColor: color.RGBA{150, 150, 150, 255},
BackgroundColor: color.RGBA{0, 0, 0, 255},
}
case !pressed && state == "on":
return streamdeck.IconButton{
Icon: "television",
IconColor: color.RGBA{255, 255, 0, 255},
BackgroundColor: color.RGBA{0, 0, 0, 255},
}
default:
return streamdeck.IconButton{
Icon: "television-off",
IconColor: color.RGBA{150, 150, 150, 255},
BackgroundColor: color.RGBA{0, 0, 0, 255},
}
}
},
},
internal.ServiceInfo{
Domain: "homeassistant",
Service: "toggle",
Data: internal.HomeAssistantToggle{EntityID: "switch.living_room_tv"},
},
)
b.InitializeButton()
s := internal.NewStaticIconButton(
ha,
sd,
internal.ButtonInfo{X: 0, Y: 2},
internal.StaticIconInfo{
Icon: "volume-plus",
FgColor: color.RGBA{150, 150, 150, 255},
BgColor: color.RGBA{0, 0, 0, 255},
PressedFgColor: color.RGBA{255, 0, 0, 255},
PressedBgColor: color.RGBA{0, 0, 0, 255},
},
internal.ServiceInfo{
Domain: "remote",
Service: "send_command",
Data: RemoteSendCommand{
EntityID: "remote.living_room",
Device: "Pioneer AV Receiver",
Commands: []string{
"VolumeUp",
},
},
},
)
s.InitializeButton()
s = internal.NewStaticIconButton(
ha,
sd,
internal.ButtonInfo{X: 0, Y: 1},
internal.StaticIconInfo{
Icon: "volume-minus",
FgColor: color.RGBA{150, 150, 150, 255},
BgColor: color.RGBA{0, 0, 0, 255},
PressedFgColor: color.RGBA{255, 0, 0, 255},
PressedBgColor: color.RGBA{0, 0, 0, 255},
},
internal.ServiceInfo{
Domain: "remote",
Service: "send_command",
Data: RemoteSendCommand{
EntityID: "remote.living_room",
Device: "Pioneer AV Receiver",
Commands: []string{
"VolumeDown",
},
},
},
)
s.InitializeButton()
s = internal.NewStaticIconButton(
ha,
sd,
internal.ButtonInfo{X: 0, Y: 0},
internal.StaticIconInfo{
Icon: "volume-mute",
FgColor: color.RGBA{150, 150, 150, 255},
BgColor: color.RGBA{0, 0, 0, 255},
PressedFgColor: color.RGBA{255, 0, 0, 255},
PressedBgColor: color.RGBA{0, 0, 0, 255},
},
internal.ServiceInfo{
Domain: "remote",
Service: "send_command",
Data: RemoteSendCommand{
EntityID: "remote.living_room",
Device: "Pioneer AV Receiver",
Commands: []string{
"Mute",
},
},
},
)
s.InitializeButton()
s = internal.NewStaticIconButton(
ha,
sd,
internal.ButtonInfo{X: 1, Y: 2},
internal.StaticIconInfo{
Icon: "pause",
FgColor: color.RGBA{150, 150, 150, 255},
BgColor: color.RGBA{0, 0, 0, 255},
PressedFgColor: color.RGBA{255, 0, 0, 255},
PressedBgColor: color.RGBA{0, 0, 0, 255},
},
internal.ServiceInfo{
Domain: "remote",
Service: "send_command",
Data: RemoteSendCommand{
EntityID: "remote.living_room",
Device: "NVIDIA Shield TV",
Commands: []string{
"Pause",
},
},
},
)
s.InitializeButton()
s = internal.NewStaticIconButton(
ha,
sd,
internal.ButtonInfo{X: 1, Y: 1},
internal.StaticIconInfo{
Icon: "skip-forward",
FgColor: color.RGBA{150, 150, 150, 255},
BgColor: color.RGBA{0, 0, 0, 255},
PressedFgColor: color.RGBA{255, 0, 0, 255},
PressedBgColor: color.RGBA{0, 0, 0, 255},
},
internal.ServiceInfo{
Domain: "remote",
Service: "send_command",
Data: RemoteSendCommand{
EntityID: "remote.living_room",
Device: "NVIDIA Shield TV",
Commands: []string{
"NextTrack",
},
},
},
)
s.InitializeButton()
s = internal.NewStaticIconButton(
ha,
sd,
internal.ButtonInfo{X: 1, Y: 0},
internal.StaticIconInfo{
Icon: "skip-backward",
FgColor: color.RGBA{150, 150, 150, 255},
BgColor: color.RGBA{0, 0, 0, 255},
PressedFgColor: color.RGBA{255, 0, 0, 255},
PressedBgColor: color.RGBA{0, 0, 0, 255},
},
internal.ServiceInfo{
Domain: "remote",
Service: "send_command",
Data: RemoteSendCommand{
EntityID: "remote.living_room",
Device: "NVIDIA Shield TV",
Commands: []string{
"PreviousTrack",
},
},
},
)
s.InitializeButton()
font, err := readFont("/usr/share/fonts/google-droid-sans-mono-fonts/DroidSansMono.ttf")
// font, err := readFont("/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf")
if err != nil {
log.Printf("unable to load font: %+v", err)
return
}
t := internal.NewDynamicIconTextButton(
ha,
sd,
"switch.lamp_near_tv",
internal.ButtonInfo{
X: 4,
Y: 2,
},
internal.DynamicIconTextInfo{
ForState: func(pressed bool, state string) streamdeck.IconTextButton {
log.Println(pressed, state)
switch {
case pressed && state == "off":
return streamdeck.IconTextButton{
Icon: "floor-lamp",
IconColor: color.RGBA{255, 0, 0, 255},
Text: "Near TV",
FontSize: 14,
FontColor: color.White,
Font: font,
BackgroundColor: color.Black,
}
case pressed && state == "on":
return streamdeck.IconTextButton{
Icon: "floor-lamp",
IconColor: color.RGBA{255, 0, 0, 255},
Text: "Near TV",
FontSize: 14,
FontColor: color.White,
Font: font,
BackgroundColor: color.Black,
}
case !pressed && state == "off":
return streamdeck.IconTextButton{
Icon: "floor-lamp",
IconColor: color.Gray{150},
Text: "Near TV",
FontSize: 14,
FontColor: color.White,
Font: font,
BackgroundColor: color.Black,
}
case !pressed && state == "on":
return streamdeck.IconTextButton{
Icon: "floor-lamp",
IconColor: color.RGBA{255, 255, 0, 255},
Text: "Near TV",
FontSize: 14,
FontColor: color.White,
Font: font,
BackgroundColor: color.RGBA{0, 0, 0, 255},
}
default:
return streamdeck.IconTextButton{
Icon: "floor-lamp",
IconColor: color.Gray{150},
Text: "Near TV",
FontSize: 14,
FontColor: color.White,
Font: font,
BackgroundColor: color.RGBA{0, 0, 0, 255},
}
}
},
},
internal.ServiceInfo{
Domain: "homeassistant",
Service: "toggle",
Data: internal.HomeAssistantToggle{EntityID: "switch.lamp_near_tv"},
},
)
t.InitializeButton()
t = internal.NewDynamicIconTextButton(
ha,
sd,
"light.lamp_near_recliner",
internal.ButtonInfo{
X: 4,
Y: 1,
},
internal.DynamicIconTextInfo{
ForState: func(pressed bool, state string) streamdeck.IconTextButton {
log.Println(pressed, state)
switch {
case pressed && state == "off":
return streamdeck.IconTextButton{
Icon: "floor-lamp",
IconColor: color.RGBA{255, 0, 0, 255},
Text: "Recliner",
FontSize: 14,
FontColor: color.White,
Font: font,
BackgroundColor: color.Black,
}
case pressed && state == "on":
return streamdeck.IconTextButton{
Icon: "floor-lamp",
IconColor: color.RGBA{255, 0, 0, 255},
Text: "Recliner",
FontSize: 14,
FontColor: color.White,
Font: font,
BackgroundColor: color.Black,
}
case !pressed && state == "off":
return streamdeck.IconTextButton{
Icon: "floor-lamp",
IconColor: color.Gray{150},
Text: "Recliner",
FontSize: 14,
FontColor: color.White,
Font: font,
BackgroundColor: color.Black,
}
case !pressed && state == "on":
return streamdeck.IconTextButton{
Icon: "floor-lamp",
IconColor: color.RGBA{255, 255, 0, 255},
Text: "Recliner",
FontSize: 14,
FontColor: color.White,
Font: font,
BackgroundColor: color.RGBA{0, 0, 0, 255},
}
default:
return streamdeck.IconTextButton{
Icon: "floor-lamp",
IconColor: color.Gray{150},
Text: "Recliner",
FontSize: 14,
FontColor: color.White,
Font: font,
BackgroundColor: color.RGBA{0, 0, 0, 255},
}
}
},
},
internal.ServiceInfo{
Domain: "homeassistant",
Service: "toggle",
Data: internal.HomeAssistantToggle{EntityID: "light.lamp_near_recliner"},
},
)
t.InitializeButton()
t = internal.NewDynamicIconTextButton(
ha,
sd,
"switch.lamp_near_couch",
internal.ButtonInfo{
X: 4,
Y: 0,
},
internal.DynamicIconTextInfo{
ForState: func(pressed bool, state string) streamdeck.IconTextButton {
log.Println(pressed, state)
switch {
case pressed && state == "off":
return streamdeck.IconTextButton{
Icon: "floor-lamp",
IconColor: color.RGBA{255, 0, 0, 255},
Text: "Couch",
FontSize: 14,
FontColor: color.White,
Font: font,
BackgroundColor: color.Black,
}
case pressed && state == "on":
return streamdeck.IconTextButton{
Icon: "floor-lamp",
IconColor: color.RGBA{255, 0, 0, 255},
Text: "Couch",
FontSize: 14,
FontColor: color.White,
Font: font,
BackgroundColor: color.Black,
}
case !pressed && state == "off":
return streamdeck.IconTextButton{
Icon: "floor-lamp",
IconColor: color.Gray{150},
Text: "Couch",
FontSize: 14,
FontColor: color.White,
Font: font,
BackgroundColor: color.Black,
}
case !pressed && state == "on":
return streamdeck.IconTextButton{
Icon: "floor-lamp",
IconColor: color.RGBA{255, 255, 0, 255},
Text: "Couch",
FontSize: 14,
FontColor: color.White,
Font: font,
BackgroundColor: color.RGBA{0, 0, 0, 255},
}
default:
return streamdeck.IconTextButton{
Icon: "floor-lamp",
IconColor: color.Gray{150},
Text: "Couch",
FontSize: 14,
FontColor: color.White,
Font: font,
BackgroundColor: color.RGBA{0, 0, 0, 255},
}
}
},
},
internal.ServiceInfo{
Domain: "homeassistant",
Service: "toggle",
Data: internal.HomeAssistantToggle{EntityID: "switch.lamp_near_couch"},
},
)
t.InitializeButton()
t = internal.NewDynamicIconTextButton(
ha,
sd,
"switch.lava_lamp",
internal.ButtonInfo{
X: 3,
Y: 2,
},
internal.DynamicIconTextInfo{
ForState: func(pressed bool, state string) streamdeck.IconTextButton {
log.Println(pressed, state)
switch {
case pressed:
return streamdeck.IconTextButton{
Icon: "lava-lamp",
IconColor: color.RGBA{255, 0, 0, 255},
Text: "Lava Lamp",
FontSize: 14,
FontColor: color.White,
Font: font,
BackgroundColor: color.Black,
}
case !pressed && state == "off":
return streamdeck.IconTextButton{
Icon: "lava-lamp",
IconColor: color.Gray{150},
Text: "Lava Lamp",
FontSize: 14,
FontColor: color.White,
Font: font,
BackgroundColor: color.Black,
}
case !pressed && state == "on":
return streamdeck.IconTextButton{
Icon: "lava-lamp",
IconColor: color.RGBA{0, 0, 255, 255},
Text: "Lava Lamp",
FontSize: 14,
FontColor: color.White,
Font: font,
BackgroundColor: color.RGBA{0, 0, 0, 255},
}
default:
return streamdeck.IconTextButton{
Icon: "lava-lamp",
IconColor: color.Gray{150},
Text: "Lava Lamp",
FontSize: 14,
FontColor: color.White,
Font: font,
BackgroundColor: color.RGBA{0, 0, 0, 255},
}
}
},
},
internal.ServiceInfo{
Domain: "homeassistant",
Service: "toggle",
Data: internal.HomeAssistantToggle{EntityID: "switch.lava_lamp"},
},
)
t.InitializeButton()
wg.Add(1)
go func() {
defer wg.Done()
err = internal.InitializeState(ha)
if err != nil {
log.Printf("unable to initalize states: %+v", err)
return
}
err = internal.StartWatchingEvents(ha)
if err != nil {
log.Printf("unable to start watching states: %+v", err)
}
}()
wg.Add(1)
go func() {
defer wg.Done()
sd.Read()
}()
wg.Wait()
}
func readFont(path string) (*truetype.Font, error) {
font, err := freetype.ParseFont(dejavusans.TTF)
if err != nil {
return nil, errors.Wrap(err, "unable to parse font")
}
// fontFile, err := os.Open(path)
// if err != nil {
// return nil, errors.Wrapf(err, "unable to open font '%s'", path)
// }
// defer fontFile.Close()
// fontData, err := ioutil.ReadAll(fontFile)
// if err != nil {
// return nil, errors.Wrapf(err, "unable to read font '%s'", path)
// }
// font, err := freetype.ParseFont(fontData)
// if err != nil {
// return nil, errors.Wrapf(err, "unable to parse font '%s'", path)
// }
return font, nil
}