Compare commits
12 Commits
v0.0.1
...
6e16aa7a7a
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e16aa7a7a | |||
| 91f7132d5e | |||
| b186f77bea | |||
| adb297c6dd | |||
| b4992cb393 | |||
| a5490b8364 | |||
| 44d1f5552b | |||
| 5e34f656b3 | |||
| 560d19860e | |||
| ea1a7fa9f4 | |||
| 81fe634ed8 | |||
| 281e1dcbac |
14
Makefile
14
Makefile
@@ -7,14 +7,20 @@ all:
|
||||
go build $(GOFLAGS)
|
||||
go build ./cmd/itctl $(GOFLAGS)
|
||||
|
||||
clean:
|
||||
rm -f itctl
|
||||
rm -f itd
|
||||
|
||||
install:
|
||||
install -Dm755 ./itd $(BIN_PREFIX)/itd
|
||||
install -Dm755 ./itctl $(BIN_PREFIX)/itctl
|
||||
install -Dm644 ./itd.service $(SERVICE_PREFIX)/itd.service
|
||||
install -Dm644 ./itd.toml $(CFG_PREFIX)/itd.toml
|
||||
|
||||
clean:
|
||||
rm -f itctl
|
||||
rm -f itd
|
||||
uninstall:
|
||||
rm $(BIN_PREFIX)/itd
|
||||
rm $(BIN_PREFIX)/itctl
|
||||
rm $(SERVICE_PREFIX)/itd.service
|
||||
rm $(CFG_PREFIX)/itd.toml
|
||||
|
||||
.PHONY: all install clean
|
||||
.PHONY: all clean install uninstall
|
||||
22
README.md
22
README.md
@@ -3,6 +3,10 @@
|
||||
|
||||
`itd` is a daemon that uses my infinitime [library](https://go.arsenm.dev/infinitime) to interact with the [PineTime](https://www.pine64.org/pinetime/) running [InfiniTime](https://infinitime.io).
|
||||
|
||||
[](https://ci.appveyor.com/project/moussaelianarsen/itd)
|
||||
[](https://minio.arsenm.dev/minio/itd/)
|
||||
[](https://aur.archlinux.org/packages/itd-git/)
|
||||
|
||||
---
|
||||
|
||||
### Features
|
||||
@@ -56,6 +60,24 @@ Flags:
|
||||
|
||||
Use "itctl [command] --help" for more information about a command.
|
||||
```
|
||||
|
||||
#### Interactive mode
|
||||
|
||||
Running `itctl` by itself will open interactive mode. It's essentially a shell where you can enter commands. For example:
|
||||
|
||||
```
|
||||
$ itctl
|
||||
itctl> fw ver
|
||||
1.3.0
|
||||
itctl> get batt
|
||||
81%
|
||||
itctl> get heart
|
||||
92 BPM
|
||||
itctl> set time 2021-08-22T00:06:18-07:00
|
||||
itctl> set time now
|
||||
itctl> exit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Installation
|
||||
|
||||
@@ -60,5 +60,6 @@ var rootCmd = &cobra.Command{
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
rootCmd.CompletionOptions.DisableDefaultCmd = true
|
||||
cobra.CheckErr(rootCmd.Execute())
|
||||
}
|
||||
|
||||
47
config.go
Normal file
47
config.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Set up logger
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||
|
||||
// Set config settings
|
||||
setCfgDefaults()
|
||||
viper.AddConfigPath("$HOME/.cmonfig")
|
||||
viper.AddConfigPath("/etoc")
|
||||
viper.SetConfigName("itd")
|
||||
viper.SetConfigType("toml")
|
||||
viper.WatchConfig()
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
viper.SetEnvPrefix("itd")
|
||||
// Ignore error because defaults set
|
||||
viper.ReadInConfig()
|
||||
viper.AutomaticEnv()
|
||||
}
|
||||
|
||||
func setCfgDefaults() {
|
||||
viper.SetDefault("cfg.version", 2)
|
||||
|
||||
viper.SetDefault("socket.path", "/tmp/itd/socket")
|
||||
|
||||
viper.SetDefault("conn.reconnect", true)
|
||||
|
||||
viper.SetDefault("on.connect.notify", true)
|
||||
|
||||
viper.SetDefault("on.reconnect.notify", true)
|
||||
viper.SetDefault("on.reconnect.setTime", true)
|
||||
|
||||
viper.SetDefault("notifs.ignore.sender", []string{})
|
||||
viper.SetDefault("notifs.ignore.summary", []string{"InfiniTime"})
|
||||
viper.SetDefault("notifs.ignore.body", []string{})
|
||||
|
||||
viper.SetDefault("music.vol.interval", 5)
|
||||
}
|
||||
4
go.mod
4
go.mod
@@ -19,7 +19,7 @@ require (
|
||||
github.com/spf13/cast v1.4.1 // indirect
|
||||
github.com/spf13/cobra v1.2.1
|
||||
github.com/spf13/viper v1.8.1
|
||||
go.arsenm.dev/infinitime v0.0.0-20210821070429-ea488067fb9b
|
||||
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55 // indirect
|
||||
go.arsenm.dev/infinitime v0.0.0-20210823171603-8648afeebf08
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
)
|
||||
|
||||
8
go.sum
8
go.sum
@@ -287,8 +287,10 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.arsenm.dev/infinitime v0.0.0-20210821070429-ea488067fb9b h1:Wwj7F0gqYHUx+9H8fCCIy5JZTlCusJRpPuzeSFM0EoU=
|
||||
go.arsenm.dev/infinitime v0.0.0-20210821070429-ea488067fb9b/go.mod h1:gaepaueUz4J5FfxuV19B4w5pi+V3mD0LTef50ryxr/Q=
|
||||
go.arsenm.dev/infinitime v0.0.0-20210822201216-955384489609 h1:QH7hsVjulEs1OP8lcQ7EfVy2UO/rtwRsxUo3ylde83E=
|
||||
go.arsenm.dev/infinitime v0.0.0-20210822201216-955384489609/go.mod h1:gaepaueUz4J5FfxuV19B4w5pi+V3mD0LTef50ryxr/Q=
|
||||
go.arsenm.dev/infinitime v0.0.0-20210823171603-8648afeebf08 h1:eh/ZfShWAYhi3UR6nrX+5mORDvN58A1T+NHtYoQeFC4=
|
||||
go.arsenm.dev/infinitime v0.0.0-20210823171603-8648afeebf08/go.mod h1:gaepaueUz4J5FfxuV19B4w5pi+V3mD0LTef50ryxr/Q=
|
||||
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
|
||||
@@ -451,6 +453,8 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55 h1:rw6UNGRMfarCepjI8qOepea/SXwIBVfTKjztZ5gBbq4=
|
||||
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k=
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
||||
21
itd.toml
21
itd.toml
@@ -1,14 +1,25 @@
|
||||
# This is temporary, it is to show a notice
|
||||
# to people still using the old config
|
||||
cfg.version = 2
|
||||
|
||||
[socket]
|
||||
path = "/tmp/itd/socket"
|
||||
|
||||
[conn]
|
||||
reconnect = true
|
||||
|
||||
[notify]
|
||||
onConnect = true
|
||||
onReconnect = true
|
||||
[on.connect]
|
||||
notify = true
|
||||
|
||||
[notifications.ignore]
|
||||
[on.reconnect]
|
||||
notify = true
|
||||
setTime = true
|
||||
|
||||
[notifs.ignore]
|
||||
sender = []
|
||||
summary = ["InfiniTime"]
|
||||
body = []
|
||||
|
||||
[music]
|
||||
volInterval = 5
|
||||
vol.interval = 5
|
||||
|
||||
|
||||
39
main.go
39
main.go
@@ -19,11 +19,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
"go.arsenm.dev/infinitime"
|
||||
@@ -31,25 +28,11 @@ import (
|
||||
|
||||
var firmwareUpdating = false
|
||||
|
||||
func init() {
|
||||
// Set up logger
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||
|
||||
// Set config settings
|
||||
viper.AddConfigPath("$HOME/.config")
|
||||
viper.AddConfigPath("/etc")
|
||||
viper.SetConfigName("itd")
|
||||
viper.SetConfigType("toml")
|
||||
viper.WatchConfig()
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
viper.SetEnvPrefix("itd")
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
log.Warn().Err(err).Msg("Could not read in config")
|
||||
}
|
||||
viper.AutomaticEnv()
|
||||
}
|
||||
|
||||
func main() {
|
||||
if viper.GetInt("cfg.version") != 2 {
|
||||
log.Fatal().Msg("Please update your config to the newest format, only v2 configs supported.")
|
||||
}
|
||||
|
||||
// Cleanly exit after function
|
||||
defer infinitime.Exit()
|
||||
|
||||
@@ -63,14 +46,16 @@ func main() {
|
||||
|
||||
// When InfiniTime reconnects
|
||||
dev.OnReconnect(func() {
|
||||
// Set time to current time
|
||||
err = dev.SetTime(time.Now())
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error setting current time on connected InfiniTime")
|
||||
if viper.GetBool("on.reconnect.setTime") {
|
||||
// Set time to current time
|
||||
err = dev.SetTime(time.Now())
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error setting current time on connected InfiniTime")
|
||||
}
|
||||
}
|
||||
|
||||
// If config specifies to notify on reconnect
|
||||
if viper.GetBool("notify.onReconnect") {
|
||||
if viper.GetBool("on.reconnect.notify") {
|
||||
// Send notification to InfiniTime
|
||||
err = dev.Notify("itd", "Successfully reconnected")
|
||||
if err != nil {
|
||||
@@ -89,7 +74,7 @@ func main() {
|
||||
log.Info().Str("version", ver).Msg("Connected to InfiniTime")
|
||||
|
||||
// If config specifies to notify on connect
|
||||
if viper.GetBool("notify.onConnect") {
|
||||
if viper.GetBool("on.connect.notify") {
|
||||
// Send notification to InfiniTime
|
||||
err = dev.Notify("itd", "Successfully connected")
|
||||
if err != nil {
|
||||
|
||||
8
music.go
8
music.go
@@ -19,10 +19,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"go.arsenm.dev/infinitime"
|
||||
"go.arsenm.dev/infinitime/pkg/player"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
"go.arsenm.dev/infinitime"
|
||||
"go.arsenm.dev/infinitime/pkg/player"
|
||||
)
|
||||
|
||||
func initMusicCtrl(dev *infinitime.Device) error {
|
||||
@@ -85,9 +85,9 @@ func initMusicCtrl(dev *infinitime.Device) error {
|
||||
case infinitime.MusicEventPrev:
|
||||
player.Prev()
|
||||
case infinitime.MusicEventVolUp:
|
||||
player.VolUp(viper.GetUint("music.volInterval"))
|
||||
player.VolUp(viper.GetUint("music.vol.interval"))
|
||||
case infinitime.MusicEventVolDown:
|
||||
player.VolDown(viper.GetUint("music.volInterval"))
|
||||
player.VolDown(viper.GetUint("music.vol.interval"))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -90,9 +90,9 @@ func initNotifRelay(dev *infinitime.Device) error {
|
||||
|
||||
// ignored checks whether any fields were ignored in the config
|
||||
func ignored(sender, summary, body string) bool {
|
||||
ignoreSender := viper.GetStringSlice("notifications.ignore.sender")
|
||||
ignoreSummary := viper.GetStringSlice("notifications.ignore.summary")
|
||||
ignoreBody := viper.GetStringSlice("notifications.ignore.body")
|
||||
ignoreSender := viper.GetStringSlice("notifs.ignore.sender")
|
||||
ignoreSummary := viper.GetStringSlice("notifs.ignore.summary")
|
||||
ignoreBody := viper.GetStringSlice("notifs.ignore.body")
|
||||
return strSlcContains(ignoreSender, sender) ||
|
||||
strSlcContains(ignoreSummary, summary) ||
|
||||
strSlcContains(ignoreBody, body)
|
||||
|
||||
32
socket.go
32
socket.go
@@ -29,12 +29,11 @@ import (
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
"go.arsenm.dev/infinitime"
|
||||
"go.arsenm.dev/itd/internal/types"
|
||||
)
|
||||
|
||||
const SockPath = "/tmp/itd/socket"
|
||||
|
||||
const (
|
||||
ReqTypeHeartRate = "hrt"
|
||||
ReqTypeBattLevel = "battlvl"
|
||||
@@ -51,20 +50,20 @@ const (
|
||||
)
|
||||
|
||||
func startSocket(dev *infinitime.Device) error {
|
||||
// Make socket directory if non existant
|
||||
err := os.MkdirAll(filepath.Dir(SockPath), 0755)
|
||||
// Make socket directory if non-existent
|
||||
err := os.MkdirAll(filepath.Dir(viper.GetString("socket.path")), 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove old socket if it exists
|
||||
err = os.RemoveAll(SockPath)
|
||||
err = os.RemoveAll(viper.GetString("socket.path"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Listen on socket path
|
||||
ln, err := net.Listen("unix", SockPath)
|
||||
ln, err := net.Listen("unix", viper.GetString("socket.path"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -83,7 +82,7 @@ func startSocket(dev *infinitime.Device) error {
|
||||
}()
|
||||
|
||||
// Log socket start
|
||||
log.Info().Str("path", SockPath).Msg("Started control socket")
|
||||
log.Info().Str("path", viper.GetString("socket.path")).Msg("Started control socket")
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -149,14 +148,14 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
|
||||
case ReqTypeNotify:
|
||||
// If no data, return error
|
||||
if req.Data == nil {
|
||||
connErr(conn, nil, "Data required for notify types.Request")
|
||||
connErr(conn, nil, "Data required for notify request")
|
||||
break
|
||||
}
|
||||
var reqData types.ReqDataNotify
|
||||
// Decode data map to notify types.Request data
|
||||
// Decode data map to notify request data
|
||||
err = mapstructure.Decode(req.Data, &reqData)
|
||||
if err != nil {
|
||||
connErr(conn, err, "Error decoding types.Request data")
|
||||
connErr(conn, err, "Error decoding request data")
|
||||
break
|
||||
}
|
||||
// Send notification to watch
|
||||
@@ -170,13 +169,13 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
|
||||
case ReqTypeSetTime:
|
||||
// If no data, return error
|
||||
if req.Data == nil {
|
||||
connErr(conn, nil, "Data required for settime types.Request")
|
||||
connErr(conn, nil, "Data required for settime request")
|
||||
break
|
||||
}
|
||||
// Get string from data or return error
|
||||
reqTimeStr, ok := req.Data.(string)
|
||||
if !ok {
|
||||
connErr(conn, nil, "Data for settime types.Request must be RFC3339 formatted time string")
|
||||
connErr(conn, nil, "Data for settime request must be RFC3339 formatted time string")
|
||||
break
|
||||
}
|
||||
|
||||
@@ -184,7 +183,7 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
|
||||
if reqTimeStr == "now" {
|
||||
reqTime = time.Now()
|
||||
} else {
|
||||
// Parse time as RFC3339/ISO9601
|
||||
// Parse time as RFC3339/ISO8601
|
||||
reqTime, err = time.Parse(time.RFC3339, reqTimeStr)
|
||||
if err != nil {
|
||||
connErr(conn, err, "Invalid time format. Time string must be formatted as ISO8601 or the word `now`")
|
||||
@@ -202,14 +201,14 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
|
||||
case ReqTypeFwUpgrade:
|
||||
// If no data, return error
|
||||
if req.Data == nil {
|
||||
connErr(conn, nil, "Data required for firmware upgrade types.Request")
|
||||
connErr(conn, nil, "Data required for firmware upgrade request")
|
||||
break
|
||||
}
|
||||
var reqData types.ReqDataFwUpgrade
|
||||
// Decode data map to firmware upgrade types.Request data
|
||||
// Decode data map to firmware upgrade request data
|
||||
err = mapstructure.Decode(req.Data, &reqData)
|
||||
if err != nil {
|
||||
connErr(conn, err, "Error decoding types.Request data")
|
||||
connErr(conn, err, "Error decoding request data")
|
||||
break
|
||||
}
|
||||
switch reqData.Type {
|
||||
@@ -272,6 +271,7 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
|
||||
err = dev.DFU.Start()
|
||||
if err != nil {
|
||||
connErr(conn, err, "Error performing upgrade")
|
||||
firmwareUpdating = false
|
||||
break
|
||||
}
|
||||
firmwareUpdating = false
|
||||
|
||||
Reference in New Issue
Block a user