Compare commits
20 Commits
b87586ef15
...
v0.0.4
| Author | SHA1 | Date | |
|---|---|---|---|
| 552f19676b | |||
| 9d58ea0ae7 | |||
| 76875db7ea | |||
| be5bdc625b | |||
| 0d0db949af | |||
| 0d164aef3d | |||
| 28610d9ebb | |||
| dff34b484d | |||
| 2ea9f99db6 | |||
| 44dc5f8e47 | |||
| 4d35912466 | |||
| ef29b9bee4 | |||
| e198b769f9 | |||
| ef4bad94b5 | |||
| 8cf2b47733 | |||
| f20fdcb161 | |||
| eeba9b2964 | |||
| d7057e3f9c | |||
| 80a5867d6b | |||
| f001dd6079 |
@@ -15,7 +15,7 @@
|
||||
- Notification transliteration
|
||||
- Call Notifications (ModemManager)
|
||||
- Music control
|
||||
- Get info from watch (HRM, Battery level, Firmware version)
|
||||
- Get info from watch (HRM, Battery level, Firmware version, Motion)
|
||||
- Set current time
|
||||
- Control socket
|
||||
- Firmware upgrades
|
||||
@@ -29,7 +29,7 @@ This daemon creates a UNIX socket at `/tmp/itd/socket`. It allows you to directl
|
||||
The socket accepts JSON requests. For example, sending a notification looks like this:
|
||||
|
||||
```json
|
||||
{"type": "notify", "data": {"title": "title1", "body": "body1"}}
|
||||
{"type": 5, "data": {"title": "title1", "body": "body1"}}
|
||||
```
|
||||
|
||||
It will return a JSON response. A response can have 3 fields: `error`, `msg`, and `value`. Error is a boolean that signals whether an error was returned. If error is true, the msg field will contain the error. Value can contain any data and depends on what the request was.
|
||||
@@ -83,10 +83,10 @@ This is the `itctl` usage screen:
|
||||
Control the itd daemon for InfiniTime smartwatches
|
||||
|
||||
Usage:
|
||||
itctl [flags]
|
||||
itctl [command]
|
||||
|
||||
Available Commands:
|
||||
completion generate the autocompletion script for the specified shell
|
||||
firmware Manage InfiniTime firmware
|
||||
get Get information from InfiniTime
|
||||
help Help about any command
|
||||
@@ -94,7 +94,8 @@ Available Commands:
|
||||
set Set information on InfiniTime
|
||||
|
||||
Flags:
|
||||
-h, --help help for itctl
|
||||
-h, --help help for itctl
|
||||
-s, --socket-path string Path to itd socket
|
||||
|
||||
Use "itctl [command] --help" for more information about a command.
|
||||
```
|
||||
|
||||
133
api/client.go
Normal file
133
api/client.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"go.arsenm.dev/infinitime"
|
||||
"go.arsenm.dev/itd/internal/types"
|
||||
)
|
||||
|
||||
// Default socket address
|
||||
const DefaultAddr = "/tmp/itd/socket"
|
||||
|
||||
// Client is the socket API client
|
||||
type Client struct {
|
||||
conn net.Conn
|
||||
respCh chan types.Response
|
||||
heartRateCh chan types.Response
|
||||
battLevelCh chan types.Response
|
||||
stepCountCh chan types.Response
|
||||
motionCh chan types.Response
|
||||
dfuProgressCh chan types.Response
|
||||
}
|
||||
|
||||
// New creates a new client and sets it up
|
||||
func New(addr string) (*Client, error) {
|
||||
conn, err := net.Dial("unix", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := &Client{
|
||||
conn: conn,
|
||||
respCh: make(chan types.Response, 5),
|
||||
}
|
||||
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(conn)
|
||||
for scanner.Scan() {
|
||||
var res types.Response
|
||||
err = json.Unmarshal(scanner.Bytes(), &res)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
out.handleResp(res)
|
||||
}
|
||||
}()
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
err := c.conn.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
close(c.respCh)
|
||||
return nil
|
||||
}
|
||||
|
||||
// request sends a request to itd and waits for and returns the response
|
||||
func (c *Client) request(req types.Request) (types.Response, error) {
|
||||
// Encode request into connection
|
||||
err := json.NewEncoder(c.conn).Encode(req)
|
||||
if err != nil {
|
||||
return types.Response{}, err
|
||||
}
|
||||
|
||||
res := <-c.respCh
|
||||
|
||||
if res.Error {
|
||||
return res, errors.New(res.Message)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// requestNoRes sends a request to itd and does not wait for the response
|
||||
func (c *Client) requestNoRes(req types.Request) error {
|
||||
// Encode request into connection
|
||||
err := json.NewEncoder(c.conn).Encode(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleResp handles the received response as needed
|
||||
func (c *Client) handleResp(res types.Response) error {
|
||||
switch res.Type {
|
||||
case types.ReqTypeWatchHeartRate:
|
||||
c.heartRateCh <- res
|
||||
case types.ReqTypeWatchBattLevel:
|
||||
c.battLevelCh <- res
|
||||
case types.ReqTypeWatchStepCount:
|
||||
c.stepCountCh <- res
|
||||
case types.ReqTypeWatchMotion:
|
||||
c.motionCh <- res
|
||||
case types.ReqTypeFwUpgrade:
|
||||
c.dfuProgressCh <- res
|
||||
default:
|
||||
c.respCh <- res
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeUint8(val interface{}) uint8 {
|
||||
return uint8(val.(float64))
|
||||
}
|
||||
|
||||
func decodeUint32(val interface{}) uint32 {
|
||||
return uint32(val.(float64))
|
||||
}
|
||||
|
||||
func decodeMotion(val interface{}) (infinitime.MotionValues, error) {
|
||||
out := infinitime.MotionValues{}
|
||||
err := mapstructure.Decode(val, &out)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func decodeDFUProgress(val interface{}) (DFUProgress, error) {
|
||||
out := DFUProgress{}
|
||||
err := mapstructure.Decode(val, &out)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
209
api/info.go
Normal file
209
api/info.go
Normal file
@@ -0,0 +1,209 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"go.arsenm.dev/infinitime"
|
||||
"go.arsenm.dev/itd/internal/types"
|
||||
)
|
||||
|
||||
// Address gets the bluetooth address of the connected device
|
||||
func (c *Client) Address() (string, error) {
|
||||
res, err := c.request(types.Request{
|
||||
Type: types.ReqTypeBtAddress,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return res.Value.(string), nil
|
||||
}
|
||||
|
||||
// Version gets the firmware version of the connected device
|
||||
func (c *Client) Version() (string, error) {
|
||||
res, err := c.request(types.Request{
|
||||
Type: types.ReqTypeFwVersion,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return res.Value.(string), nil
|
||||
}
|
||||
|
||||
// BatteryLevel gets the battery level of the connected device
|
||||
func (c *Client) BatteryLevel() (uint8, error) {
|
||||
res, err := c.request(types.Request{
|
||||
Type: types.ReqTypeBattLevel,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return uint8(res.Value.(float64)), nil
|
||||
}
|
||||
|
||||
// WatchBatteryLevel returns a channel which will contain
|
||||
// new battery level values as they update. Do not use after
|
||||
// calling cancellation function
|
||||
func (c *Client) WatchBatteryLevel() (<-chan uint8, func(), error) {
|
||||
c.battLevelCh = make(chan types.Response, 2)
|
||||
err := c.requestNoRes(types.Request{
|
||||
Type: types.ReqTypeWatchBattLevel,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
res := <-c.battLevelCh
|
||||
done, cancel := c.cancelFn(res.ID, c.battLevelCh)
|
||||
out := make(chan uint8, 2)
|
||||
go func() {
|
||||
for res := range c.battLevelCh {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
out <- decodeUint8(res.Value)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return out, cancel, nil
|
||||
}
|
||||
|
||||
// HeartRate gets the heart rate from the connected device
|
||||
func (c *Client) HeartRate() (uint8, error) {
|
||||
res, err := c.request(types.Request{
|
||||
Type: types.ReqTypeHeartRate,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return decodeUint8(res.Value), nil
|
||||
}
|
||||
|
||||
// WatchHeartRate returns a channel which will contain
|
||||
// new heart rate values as they update. Do not use after
|
||||
// calling cancellation function
|
||||
func (c *Client) WatchHeartRate() (<-chan uint8, func(), error) {
|
||||
c.heartRateCh = make(chan types.Response, 2)
|
||||
err := c.requestNoRes(types.Request{
|
||||
Type: types.ReqTypeWatchHeartRate,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
res := <-c.heartRateCh
|
||||
done, cancel := c.cancelFn(res.ID, c.heartRateCh)
|
||||
out := make(chan uint8, 2)
|
||||
go func() {
|
||||
for res := range c.heartRateCh {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
out <- decodeUint8(res.Value)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return out, cancel, nil
|
||||
}
|
||||
|
||||
// cancelFn generates a cancellation function for the given
|
||||
// request type and channel
|
||||
func (c *Client) cancelFn(reqID string, ch chan types.Response) (chan struct{}, func()) {
|
||||
done := make(chan struct{}, 1)
|
||||
return done, func() {
|
||||
done <- struct{}{}
|
||||
close(ch)
|
||||
c.requestNoRes(types.Request{
|
||||
Type: types.ReqTypeCancel,
|
||||
Data: reqID,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// StepCount gets the step count from the connected device
|
||||
func (c *Client) StepCount() (uint32, error) {
|
||||
res, err := c.request(types.Request{
|
||||
Type: types.ReqTypeStepCount,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return uint32(res.Value.(float64)), nil
|
||||
}
|
||||
|
||||
// WatchStepCount returns a channel which will contain
|
||||
// new step count values as they update. Do not use after
|
||||
// calling cancellation function
|
||||
func (c *Client) WatchStepCount() (<-chan uint32, func(), error) {
|
||||
c.stepCountCh = make(chan types.Response, 2)
|
||||
err := c.requestNoRes(types.Request{
|
||||
Type: types.ReqTypeWatchStepCount,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
res := <-c.stepCountCh
|
||||
done, cancel := c.cancelFn(res.ID, c.stepCountCh)
|
||||
out := make(chan uint32, 2)
|
||||
go func() {
|
||||
for res := range c.stepCountCh {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
out <- decodeUint32(res.Value)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return out, cancel, nil
|
||||
}
|
||||
|
||||
// Motion gets the motion values from the connected device
|
||||
func (c *Client) Motion() (infinitime.MotionValues, error) {
|
||||
out := infinitime.MotionValues{}
|
||||
res, err := c.request(types.Request{
|
||||
Type: types.ReqTypeMotion,
|
||||
})
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
err = mapstructure.Decode(res.Value, &out)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// WatchMotion returns a channel which will contain
|
||||
// new motion values as they update. Do not use after
|
||||
// calling cancellation function
|
||||
func (c *Client) WatchMotion() (<-chan infinitime.MotionValues, func(), error) {
|
||||
c.motionCh = make(chan types.Response, 5)
|
||||
err := c.requestNoRes(types.Request{
|
||||
Type: types.ReqTypeWatchMotion,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
res := <-c.motionCh
|
||||
done, cancel := c.cancelFn(res.ID, c.motionCh)
|
||||
out := make(chan infinitime.MotionValues, 5)
|
||||
go func() {
|
||||
for res := range c.motionCh {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
motion, err := decodeMotion(res.Value)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
out <- motion
|
||||
}
|
||||
}
|
||||
}()
|
||||
return out, cancel, nil
|
||||
}
|
||||
14
api/notify.go
Normal file
14
api/notify.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package api
|
||||
|
||||
import "go.arsenm.dev/itd/internal/types"
|
||||
|
||||
func (c *Client) Notify(title string, body string) error {
|
||||
_, err := c.request(types.Request{
|
||||
Type: types.ReqTypeNotify,
|
||||
Data: types.ReqDataNotify{
|
||||
Title: title,
|
||||
Body: body,
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
33
api/time.go
Normal file
33
api/time.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"go.arsenm.dev/itd/internal/types"
|
||||
)
|
||||
|
||||
// SetTime sets the given time on the connected device
|
||||
func (c *Client) SetTime(t time.Time) error {
|
||||
_, err := c.request(types.Request{
|
||||
Type: types.ReqTypeSetTime,
|
||||
Data: t.Format(time.RFC3339),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTimeNow sets the time on the connected device to
|
||||
// the current time. This is more accurate than
|
||||
// SetTime(time.Now()) due to RFC3339 formatting
|
||||
func (c *Client) SetTimeNow() error {
|
||||
_, err := c.request(types.Request{
|
||||
Type: types.ReqTypeSetTime,
|
||||
Data: "now",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
48
api/upgrade.go
Normal file
48
api/upgrade.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"go.arsenm.dev/itd/internal/types"
|
||||
)
|
||||
|
||||
// DFUProgress stores the progress of a DFU upfate
|
||||
type DFUProgress types.DFUProgress
|
||||
|
||||
// UpgradeType indicates the type of upgrade to be performed
|
||||
type UpgradeType uint8
|
||||
|
||||
// Type of DFU upgrade
|
||||
const (
|
||||
UpgradeTypeArchive UpgradeType = iota
|
||||
UpgradeTypeFiles
|
||||
)
|
||||
|
||||
// FirmwareUpgrade initiates a DFU update and returns the progress channel
|
||||
func (c *Client) FirmwareUpgrade(upgType UpgradeType, files ...string) (<-chan DFUProgress, error) {
|
||||
err := json.NewEncoder(c.conn).Encode(types.Request{
|
||||
Type: types.ReqTypeFwUpgrade,
|
||||
Data: types.ReqDataFwUpgrade{
|
||||
Type: int(upgType),
|
||||
Files: files,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.dfuProgressCh = make(chan types.Response, 5)
|
||||
|
||||
out := make(chan DFUProgress, 5)
|
||||
go func() {
|
||||
for res := range c.dfuProgressCh {
|
||||
progress, err := decodeDFUProgress(res.Value)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
out <- progress
|
||||
}
|
||||
}()
|
||||
|
||||
return out, nil
|
||||
}
|
||||
@@ -16,10 +16,11 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package firmware
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"go.arsenm.dev/itd/cmd/itctl/root"
|
||||
)
|
||||
|
||||
// firmwareCmd represents the firmware command
|
||||
@@ -30,5 +31,5 @@ var firmwareCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(firmwareCmd)
|
||||
root.RootCmd.AddCommand(firmwareCmd)
|
||||
}
|
||||
@@ -16,61 +16,50 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package firmware
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"net"
|
||||
|
||||
"github.com/cheggaaa/pb/v3"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"go.arsenm.dev/itd/api"
|
||||
"go.arsenm.dev/itd/internal/types"
|
||||
)
|
||||
|
||||
type DFUProgress struct {
|
||||
Received int64 `mapstructure:"recvd"`
|
||||
Total int64 `mapstructure:"total"`
|
||||
}
|
||||
|
||||
// upgradeCmd represents the upgrade command
|
||||
var upgradeCmd = &cobra.Command{
|
||||
Use: "upgrade",
|
||||
Short: "Upgrade InfiniTime firmware using files or archive",
|
||||
Aliases: []string{"upg"},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Connect to itd UNIX socket
|
||||
conn, err := net.Dial("unix", viper.GetString("sockPath"))
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
|
||||
}
|
||||
defer conn.Close()
|
||||
client := viper.Get("client").(*api.Client)
|
||||
|
||||
var data types.ReqDataFwUpgrade
|
||||
var upgType api.UpgradeType
|
||||
var files []string
|
||||
// Get relevant data struct
|
||||
if viper.GetString("archive") != "" {
|
||||
// Get archive data struct
|
||||
data = types.ReqDataFwUpgrade{
|
||||
Type: types.UpgradeTypeArchive,
|
||||
Files: []string{viper.GetString("archive")},
|
||||
}
|
||||
upgType = types.UpgradeTypeArchive
|
||||
files = []string{viper.GetString("archive")}
|
||||
} else if viper.GetString("initPkt") != "" && viper.GetString("firmware") != "" {
|
||||
// Get files data struct
|
||||
data = types.ReqDataFwUpgrade{
|
||||
Type: types.UpgradeTypeFiles,
|
||||
Files: []string{viper.GetString("initPkt"), viper.GetString("firmware")},
|
||||
}
|
||||
upgType = types.UpgradeTypeFiles
|
||||
files = []string{viper.GetString("initPkt"), viper.GetString("firmware")}
|
||||
} else {
|
||||
cmd.Usage()
|
||||
log.Warn().Msg("Upgrade command requires either archive or init packet and firmware.")
|
||||
return
|
||||
}
|
||||
|
||||
// Encode response into connection
|
||||
err = json.NewEncoder(conn).Encode(types.Request{
|
||||
Type: types.ReqTypeFwUpgrade,
|
||||
Data: data,
|
||||
})
|
||||
progress, err := client.FirmwareUpgrade(upgType, files...)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error making request")
|
||||
log.Fatal().Err(err).Msg("Error initiating DFU")
|
||||
}
|
||||
|
||||
// Create progress bar template
|
||||
@@ -78,37 +67,18 @@ var upgradeCmd = &cobra.Command{
|
||||
// Start full bar at 0 total
|
||||
bar := pb.ProgressBarTemplate(barTmpl).Start(0)
|
||||
// Create new scanner of connection
|
||||
scanner := bufio.NewScanner(conn)
|
||||
for scanner.Scan() {
|
||||
var res types.Response
|
||||
// Decode scanned line into response struct
|
||||
err = json.Unmarshal(scanner.Bytes(), &res)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error decoding JSON response")
|
||||
}
|
||||
if res.Error {
|
||||
log.Fatal().Msg(res.Message)
|
||||
}
|
||||
var event DFUProgress
|
||||
// Decode response data into progress struct
|
||||
err = mapstructure.Decode(res.Value, &event)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error decoding response data")
|
||||
}
|
||||
for event := range progress {
|
||||
// Set total bytes in progress bar
|
||||
bar.SetTotal(event.Total)
|
||||
// Set amount of bytes received in progress bar
|
||||
bar.SetCurrent(event.Received)
|
||||
// If transfer finished, break
|
||||
if event.Received == event.Total {
|
||||
if event.Sent == event.Total {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Finish progress bar
|
||||
bar.Finish()
|
||||
if scanner.Err() != nil {
|
||||
log.Fatal().Err(scanner.Err()).Msg("Error while scanning output")
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -16,18 +16,15 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package firmware
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"go.arsenm.dev/itd/internal/types"
|
||||
"go.arsenm.dev/itd/api"
|
||||
)
|
||||
|
||||
// versionCmd represents the version command
|
||||
@@ -36,40 +33,14 @@ var versionCmd = &cobra.Command{
|
||||
Aliases: []string{"ver"},
|
||||
Short: "Get firmware version of InfiniTime",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Connect to itd UNIX socket
|
||||
conn, err := net.Dial("unix", viper.GetString("sockPath"))
|
||||
client := viper.Get("client").(*api.Client)
|
||||
|
||||
version, err := client.Version()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Encode request into connection
|
||||
err = json.NewEncoder(conn).Encode(types.Request{
|
||||
Type: types.ReqTypeFwVersion,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error making request")
|
||||
log.Fatal().Err(err).Msg("Error getting firmware version")
|
||||
}
|
||||
|
||||
// Read one line from connection
|
||||
line, _, err := bufio.NewReader(conn).ReadLine()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error reading line from connection")
|
||||
}
|
||||
|
||||
var res types.Response
|
||||
// Decode line into response
|
||||
err = json.Unmarshal(line, &res)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error decoding JSON data")
|
||||
}
|
||||
|
||||
if res.Error {
|
||||
log.Fatal().Msg(res.Message)
|
||||
}
|
||||
|
||||
// Print returned value
|
||||
fmt.Println(res.Value)
|
||||
fmt.Println(version)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -16,18 +16,15 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package get
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"go.arsenm.dev/itd/internal/types"
|
||||
"go.arsenm.dev/itd/api"
|
||||
)
|
||||
|
||||
// addressCmd represents the address command
|
||||
@@ -36,40 +33,14 @@ var addressCmd = &cobra.Command{
|
||||
Aliases: []string{"addr"},
|
||||
Short: "Get InfiniTime's bluetooth address",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Connect to itd UNIX socket
|
||||
conn, err := net.Dial("unix", viper.GetString("sockPath"))
|
||||
client := viper.Get("client").(*api.Client)
|
||||
|
||||
address, err := client.Address()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Encode request into connection
|
||||
err = json.NewEncoder(conn).Encode(types.Request{
|
||||
Type: types.ReqTypeBtAddress,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error making request")
|
||||
log.Fatal().Err(err).Msg("Error getting bluetooth address")
|
||||
}
|
||||
|
||||
// Read one line from connection
|
||||
line, _, err := bufio.NewReader(conn).ReadLine()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error reading line from connection")
|
||||
}
|
||||
|
||||
var res types.Response
|
||||
// Decode line into response
|
||||
err = json.Unmarshal(line, &res)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error decoding JSON data")
|
||||
}
|
||||
|
||||
if res.Error {
|
||||
log.Fatal().Msg(res.Message)
|
||||
}
|
||||
|
||||
// Print returned value
|
||||
fmt.Println(res.Value)
|
||||
fmt.Println(address)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -16,18 +16,15 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package get
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"go.arsenm.dev/itd/internal/types"
|
||||
"go.arsenm.dev/itd/api"
|
||||
)
|
||||
|
||||
// batteryCmd represents the batt command
|
||||
@@ -36,40 +33,15 @@ var batteryCmd = &cobra.Command{
|
||||
Aliases: []string{"batt"},
|
||||
Short: "Get battery level from InfiniTime",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Connect to itd UNIX socket
|
||||
conn, err := net.Dial("unix", viper.GetString("sockPath"))
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
|
||||
}
|
||||
defer conn.Close()
|
||||
client := viper.Get("client").(*api.Client)
|
||||
|
||||
// Encode request into connection
|
||||
err = json.NewEncoder(conn).Encode(types.Request{
|
||||
Type: types.ReqTypeBattLevel,
|
||||
})
|
||||
battLevel, err := client.BatteryLevel()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error making request")
|
||||
}
|
||||
|
||||
// Read one line from connection
|
||||
line, _, err := bufio.NewReader(conn).ReadLine()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error reading line from connection")
|
||||
}
|
||||
|
||||
var res types.Response
|
||||
// Deocde line into response
|
||||
err = json.Unmarshal(line, &res)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error decoding JSON data")
|
||||
}
|
||||
|
||||
if res.Error {
|
||||
log.Fatal().Msg(res.Message)
|
||||
log.Fatal().Err(err).Msg("Error getting battery level")
|
||||
}
|
||||
|
||||
// Print returned percentage
|
||||
fmt.Printf("%d%%\n", int(res.Value.(float64)))
|
||||
fmt.Printf("%d%%\n", battLevel)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -16,10 +16,11 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package get
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"go.arsenm.dev/itd/cmd/itctl/root"
|
||||
)
|
||||
|
||||
// getCmd represents the get command
|
||||
@@ -29,5 +30,5 @@ var getCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(getCmd)
|
||||
root.RootCmd.AddCommand(getCmd)
|
||||
}
|
||||
@@ -16,18 +16,15 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package get
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"go.arsenm.dev/itd/internal/types"
|
||||
"go.arsenm.dev/itd/api"
|
||||
)
|
||||
|
||||
// heartCmd represents the heart command
|
||||
@@ -35,40 +32,15 @@ var heartCmd = &cobra.Command{
|
||||
Use: "heart",
|
||||
Short: "Get heart rate from InfiniTime",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Connect to itd UNIX socket
|
||||
conn, err := net.Dial("unix", viper.GetString("sockPath"))
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
|
||||
}
|
||||
defer conn.Close()
|
||||
client := viper.Get("client").(*api.Client)
|
||||
|
||||
// Encode request into connection
|
||||
err = json.NewEncoder(conn).Encode(types.Request{
|
||||
Type: types.ReqTypeHeartRate,
|
||||
})
|
||||
heartRate, err := client.HeartRate()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error making request")
|
||||
}
|
||||
|
||||
// Read one line from connection
|
||||
line, _, err := bufio.NewReader(conn).ReadLine()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error reading line from connection")
|
||||
}
|
||||
|
||||
var res types.Response
|
||||
// Decode line into response
|
||||
err = json.Unmarshal(line, &res)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error decoding JSON data")
|
||||
}
|
||||
|
||||
if res.Error {
|
||||
log.Fatal().Msg(res.Message)
|
||||
log.Fatal().Err(err).Msg("Error getting heart rate")
|
||||
}
|
||||
|
||||
// Print returned BPM
|
||||
fmt.Printf("%d BPM\n", int(res.Value.(float64)))
|
||||
fmt.Printf("%d BPM\n", heartRate)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -16,19 +16,15 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package get
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"go.arsenm.dev/itd/internal/types"
|
||||
"go.arsenm.dev/itd/api"
|
||||
)
|
||||
|
||||
// steps.goCmd represents the steps.go command
|
||||
@@ -36,42 +32,11 @@ var motionCmd = &cobra.Command{
|
||||
Use: "motion",
|
||||
Short: "Get motion values from InfiniTime",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Connect to itd UNIX socket
|
||||
conn, err := net.Dial("unix", viper.GetString("sockPath"))
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
|
||||
}
|
||||
defer conn.Close()
|
||||
client := viper.Get("client").(*api.Client)
|
||||
|
||||
// Encode request into connection
|
||||
err = json.NewEncoder(conn).Encode(types.Request{
|
||||
Type: types.ReqTypeMotion,
|
||||
})
|
||||
motionVals, err := client.Motion()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error making request")
|
||||
}
|
||||
|
||||
// Read one line from connection
|
||||
line, _, err := bufio.NewReader(conn).ReadLine()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error reading line from connection")
|
||||
}
|
||||
|
||||
var res types.Response
|
||||
// Decode line into response
|
||||
err = json.Unmarshal(line, &res)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error decoding JSON data")
|
||||
}
|
||||
|
||||
var motionVals types.MotionValues
|
||||
err = mapstructure.Decode(res.Value, &motionVals)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error decoding motion values")
|
||||
}
|
||||
|
||||
if res.Error {
|
||||
log.Fatal().Msg(res.Message)
|
||||
log.Fatal().Err(err).Msg("Error getting motion values")
|
||||
}
|
||||
|
||||
if viper.GetBool("shell") {
|
||||
@@ -16,18 +16,15 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package get
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"go.arsenm.dev/itd/internal/types"
|
||||
"go.arsenm.dev/itd/api"
|
||||
)
|
||||
|
||||
// steps.goCmd represents the steps.go command
|
||||
@@ -35,40 +32,15 @@ var stepsCmd = &cobra.Command{
|
||||
Use: "steps",
|
||||
Short: "Get step count from InfiniTime",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Connect to itd UNIX socket
|
||||
conn, err := net.Dial("unix", viper.GetString("sockPath"))
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
|
||||
}
|
||||
defer conn.Close()
|
||||
client := viper.Get("client").(*api.Client)
|
||||
|
||||
// Encode request into connection
|
||||
err = json.NewEncoder(conn).Encode(types.Request{
|
||||
Type: types.ReqTypeStepCount,
|
||||
})
|
||||
stepCount, err := client.StepCount()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error making request")
|
||||
}
|
||||
|
||||
// Read one line from connection
|
||||
line, _, err := bufio.NewReader(conn).ReadLine()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error reading line from connection")
|
||||
}
|
||||
|
||||
var res types.Response
|
||||
// Decode line into response
|
||||
err = json.Unmarshal(line, &res)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error decoding JSON data")
|
||||
}
|
||||
|
||||
if res.Error {
|
||||
log.Fatal().Msg(res.Message)
|
||||
log.Fatal().Err(err).Msg("Error getting step count")
|
||||
}
|
||||
|
||||
// Print returned BPM
|
||||
fmt.Printf("%d Steps\n", int(res.Value.(float64)))
|
||||
fmt.Printf("%d Steps\n", stepCount)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -19,9 +19,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
_ "go.arsenm.dev/itd/cmd/itctl/firmware"
|
||||
_ "go.arsenm.dev/itd/cmd/itctl/get"
|
||||
_ "go.arsenm.dev/itd/cmd/itctl/notify"
|
||||
"go.arsenm.dev/itd/cmd/itctl/root"
|
||||
_ "go.arsenm.dev/itd/cmd/itctl/set"
|
||||
_ "go.arsenm.dev/itd/cmd/itctl/watch"
|
||||
|
||||
"go.arsenm.dev/itd/cmd/itctl/cmd"
|
||||
"os"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
@@ -32,5 +37,5 @@ func init() {
|
||||
}
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
root.Execute()
|
||||
}
|
||||
|
||||
@@ -16,17 +16,14 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package notify
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"net"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"go.arsenm.dev/itd/internal/types"
|
||||
"go.arsenm.dev/itd/api"
|
||||
"go.arsenm.dev/itd/cmd/itctl/root"
|
||||
)
|
||||
|
||||
// notifyCmd represents the notify command
|
||||
@@ -40,44 +37,15 @@ var notifyCmd = &cobra.Command{
|
||||
log.Fatal().Msg("Command notify requires two arguments")
|
||||
}
|
||||
|
||||
// Connect to itd UNIX socket
|
||||
conn, err := net.Dial("unix", viper.GetString("sockPath"))
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
|
||||
}
|
||||
defer conn.Close()
|
||||
client := viper.Get("client").(*api.Client)
|
||||
|
||||
// Encode request into connection
|
||||
err = json.NewEncoder(conn).Encode(types.Request{
|
||||
Type: types.ReqTypeNotify,
|
||||
Data: types.ReqDataNotify{
|
||||
Title: args[0],
|
||||
Body: args[1],
|
||||
},
|
||||
})
|
||||
err := client.Notify(args[0], args[1])
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error making request")
|
||||
}
|
||||
|
||||
// Read one line from connection
|
||||
line, _, err := bufio.NewReader(conn).ReadLine()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error reading line from connection")
|
||||
}
|
||||
|
||||
var res types.Response
|
||||
// Decode line into response
|
||||
err = json.Unmarshal(line, &res)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error decoding JSON data")
|
||||
}
|
||||
|
||||
if res.Error {
|
||||
log.Fatal().Msg(res.Message)
|
||||
log.Fatal().Err(err).Msg("Error sending notification")
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(notifyCmd)
|
||||
root.RootCmd.AddCommand(notifyCmd)
|
||||
}
|
||||
@@ -16,16 +16,18 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package root
|
||||
|
||||
import (
|
||||
"github.com/abiosoft/ishell"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"go.arsenm.dev/itd/api"
|
||||
)
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
var rootCmd = &cobra.Command{
|
||||
// RootCmd represents the base command when called without any subcommands
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "itctl",
|
||||
Short: "Control the itd daemon for InfiniTime smartwatches",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
@@ -61,18 +63,25 @@ 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())
|
||||
client, err := api.New(viper.GetString("sockPath"))
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error connecting to socket. Is itd running?")
|
||||
}
|
||||
defer client.Close()
|
||||
viper.Set("client", client)
|
||||
RootCmd.CompletionOptions.DisableDefaultCmd = true
|
||||
cobra.CheckErr(RootCmd.Execute())
|
||||
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Register flag for socket path
|
||||
rootCmd.Flags().StringP("socket-path", "s", "", "Path to itd socket")
|
||||
RootCmd.Flags().StringP("socket-path", "s", api.DefaultAddr, "Path to itd socket")
|
||||
|
||||
// Bind flag and environment variable to viper key
|
||||
viper.BindPFlag("sockPath", rootCmd.Flags().Lookup("socket-path"))
|
||||
viper.BindPFlag("sockPath", RootCmd.Flags().Lookup("socket-path"))
|
||||
viper.BindEnv("sockPath", "ITCTL_SOCKET_PATH")
|
||||
|
||||
// Set default value for socket path
|
||||
viper.SetDefault("sockPath", "/tmp/itd/socket")
|
||||
viper.SetDefault("sockPath", api.DefaultAddr)
|
||||
}
|
||||
@@ -16,10 +16,11 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package set
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"go.arsenm.dev/itd/cmd/itctl/root"
|
||||
)
|
||||
|
||||
// setCmd represents the set command
|
||||
@@ -29,5 +30,5 @@ var setCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(setCmd)
|
||||
root.RootCmd.AddCommand(setCmd)
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package set
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
76
cmd/itctl/watch/battery.go
Normal file
76
cmd/itctl/watch/battery.go
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||
* Copyright (C) 2021 Arsen Musayelyan
|
||||
*
|
||||
* 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 watch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"go.arsenm.dev/itd/api"
|
||||
)
|
||||
|
||||
// heartCmd represents the address command
|
||||
var batteryCmd = &cobra.Command{
|
||||
Use: "battery",
|
||||
Aliases: []string{"batt"},
|
||||
Short: "Watch InfiniTime's battery level for changes",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
client := viper.Get("client").(*api.Client)
|
||||
|
||||
battLevelCh, cancel, err := client.WatchBatteryLevel()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error getting battery level channel")
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
signalCh := make(chan os.Signal, 1)
|
||||
go func() {
|
||||
<-signalCh
|
||||
cancel()
|
||||
os.Exit(0)
|
||||
}()
|
||||
signal.Notify(signalCh,
|
||||
syscall.SIGINT,
|
||||
syscall.SIGTERM,
|
||||
)
|
||||
|
||||
for battlevel := range battLevelCh {
|
||||
fmt.Printf("%d%%\n", battlevel)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
watchCmd.AddCommand(batteryCmd)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
|
||||
// Cobra supports Persistent Flags which will work for this command
|
||||
// and all subcommands, e.g.:
|
||||
// addressCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||
|
||||
// Cobra supports local flags which will only run when this command
|
||||
// is called directly, e.g.:
|
||||
// addressCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
}
|
||||
75
cmd/itctl/watch/heart.go
Normal file
75
cmd/itctl/watch/heart.go
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||
* Copyright (C) 2021 Arsen Musayelyan
|
||||
*
|
||||
* 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 watch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"go.arsenm.dev/itd/api"
|
||||
)
|
||||
|
||||
// heartCmd represents the address command
|
||||
var heartCmd = &cobra.Command{
|
||||
Use: "heart",
|
||||
Short: "Watch InfiniTime's heart rate for changes",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
client := viper.Get("client").(*api.Client)
|
||||
|
||||
heartRateCh, cancel, err := client.WatchHeartRate()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error getting heart rate channel")
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
signalCh := make(chan os.Signal, 1)
|
||||
go func() {
|
||||
<-signalCh
|
||||
cancel()
|
||||
os.Exit(0)
|
||||
}()
|
||||
signal.Notify(signalCh,
|
||||
syscall.SIGINT,
|
||||
syscall.SIGTERM,
|
||||
)
|
||||
|
||||
for heartRate := range heartRateCh {
|
||||
fmt.Println(heartRate, "BPM")
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
watchCmd.AddCommand(heartCmd)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
|
||||
// Cobra supports Persistent Flags which will work for this command
|
||||
// and all subcommands, e.g.:
|
||||
// addressCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||
|
||||
// Cobra supports local flags which will only run when this command
|
||||
// is called directly, e.g.:
|
||||
// addressCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
}
|
||||
86
cmd/itctl/watch/motion.go
Normal file
86
cmd/itctl/watch/motion.go
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||
* Copyright (C) 2021 Arsen Musayelyan
|
||||
*
|
||||
* 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 watch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"go.arsenm.dev/itd/api"
|
||||
)
|
||||
|
||||
// heartCmd represents the address command
|
||||
var motionCmd = &cobra.Command{
|
||||
Use: "motion",
|
||||
Short: "Watch InfiniTime's motion values for changes",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
client := viper.Get("client").(*api.Client)
|
||||
|
||||
motionValCh, cancel, err := client.WatchMotion()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error getting motion value channel")
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
signalCh := make(chan os.Signal, 1)
|
||||
go func() {
|
||||
<-signalCh
|
||||
cancel()
|
||||
os.Exit(0)
|
||||
}()
|
||||
signal.Notify(signalCh,
|
||||
syscall.SIGINT,
|
||||
syscall.SIGTERM,
|
||||
)
|
||||
|
||||
for motionVals := range motionValCh {
|
||||
if viper.GetBool("shell") {
|
||||
fmt.Printf(
|
||||
"X=%d\nY=%d\nZ=%d\n",
|
||||
motionVals.X,
|
||||
motionVals.Y,
|
||||
motionVals.Z,
|
||||
)
|
||||
} else {
|
||||
fmt.Printf("%+v\n", motionVals)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
watchCmd.AddCommand(motionCmd)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
|
||||
// Cobra supports Persistent Flags which will work for this command
|
||||
// and all subcommands, e.g.:
|
||||
// addressCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||
|
||||
// Cobra supports local flags which will only run when this command
|
||||
// is called directly, e.g.:
|
||||
// addressCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
motionCmd.Flags().BoolP("shell", "s", false, "Output data in shell-compatible format")
|
||||
viper.BindPFlag("shell", motionCmd.Flags().Lookup("shell"))
|
||||
}
|
||||
75
cmd/itctl/watch/steps.go
Normal file
75
cmd/itctl/watch/steps.go
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||
* Copyright (C) 2021 Arsen Musayelyan
|
||||
*
|
||||
* 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 watch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"go.arsenm.dev/itd/api"
|
||||
)
|
||||
|
||||
// heartCmd represents the address command
|
||||
var stepsCmd = &cobra.Command{
|
||||
Use: "steps",
|
||||
Short: "Watch InfiniTime's step count for changes",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
client := viper.Get("client").(*api.Client)
|
||||
|
||||
stepCountCh, cancel, err := client.WatchStepCount()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error getting step count channel")
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
signalCh := make(chan os.Signal, 1)
|
||||
go func() {
|
||||
<-signalCh
|
||||
cancel()
|
||||
os.Exit(0)
|
||||
}()
|
||||
signal.Notify(signalCh,
|
||||
syscall.SIGINT,
|
||||
syscall.SIGTERM,
|
||||
)
|
||||
|
||||
for stepCount := range stepCountCh {
|
||||
fmt.Println(stepCount, "Steps")
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
watchCmd.AddCommand(stepsCmd)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
|
||||
// Cobra supports Persistent Flags which will work for this command
|
||||
// and all subcommands, e.g.:
|
||||
// addressCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||
|
||||
// Cobra supports local flags which will only run when this command
|
||||
// is called directly, e.g.:
|
||||
// addressCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
}
|
||||
@@ -16,9 +16,19 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package watch
|
||||
|
||||
type DFUProgress struct {
|
||||
Received int64 `mapstructure:"recvd"`
|
||||
Total int64 `mapstructure:"total"`
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"go.arsenm.dev/itd/cmd/itctl/root"
|
||||
)
|
||||
|
||||
// watchCmd represents the watch command
|
||||
var watchCmd = &cobra.Command{
|
||||
Use: "watch",
|
||||
Short: "Watch values from InfiniTime for changes",
|
||||
}
|
||||
|
||||
func init() {
|
||||
root.RootCmd.AddCommand(watchCmd)
|
||||
}
|
||||
@@ -1,22 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image/color"
|
||||
"net"
|
||||
|
||||
"encoding/json"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"go.arsenm.dev/itd/internal/types"
|
||||
"go.arsenm.dev/itd/api"
|
||||
)
|
||||
|
||||
func infoTab(parent fyne.Window) *fyne.Container {
|
||||
func infoTab(parent fyne.Window, client *api.Client) *fyne.Container {
|
||||
infoLayout := container.NewVBox(
|
||||
// Add rectangle for a bit of padding
|
||||
canvas.NewRectangle(color.Transparent),
|
||||
@@ -25,20 +20,50 @@ func infoTab(parent fyne.Window) *fyne.Container {
|
||||
// Create label for heart rate
|
||||
heartRateLbl := newText("0 BPM", 24)
|
||||
// Creae container to store heart rate section
|
||||
heartRate := container.NewVBox(
|
||||
heartRateSect := container.NewVBox(
|
||||
newText("Heart Rate", 12),
|
||||
heartRateLbl,
|
||||
canvas.NewLine(theme.ShadowColor()),
|
||||
)
|
||||
infoLayout.Add(heartRate)
|
||||
infoLayout.Add(heartRateSect)
|
||||
|
||||
// Watch for heart rate updates
|
||||
go watch(types.ReqTypeWatchHeartRate, func(data interface{}) {
|
||||
// Change text of heart rate label
|
||||
heartRateLbl.Text = fmt.Sprintf("%d BPM", int(data.(float64)))
|
||||
// Refresh label
|
||||
heartRateLbl.Refresh()
|
||||
}, parent)
|
||||
heartRateCh, cancel, err := client.WatchHeartRate()
|
||||
if err != nil {
|
||||
guiErr(err, "Error getting heart rate channel", true, parent)
|
||||
}
|
||||
onClose = append(onClose, cancel)
|
||||
go func() {
|
||||
for heartRate := range heartRateCh {
|
||||
// Change text of heart rate label
|
||||
heartRateLbl.Text = fmt.Sprintf("%d BPM", heartRate)
|
||||
// Refresh label
|
||||
heartRateLbl.Refresh()
|
||||
}
|
||||
}()
|
||||
|
||||
// Create label for heart rate
|
||||
stepCountLbl := newText("0 Steps", 24)
|
||||
// Creae container to store heart rate section
|
||||
stepCountSect := container.NewVBox(
|
||||
newText("Step Count", 12),
|
||||
stepCountLbl,
|
||||
canvas.NewLine(theme.ShadowColor()),
|
||||
)
|
||||
infoLayout.Add(stepCountSect)
|
||||
|
||||
stepCountCh, cancel, err := client.WatchStepCount()
|
||||
if err != nil {
|
||||
guiErr(err, "Error getting step count channel", true, parent)
|
||||
}
|
||||
onClose = append(onClose, cancel)
|
||||
go func() {
|
||||
for stepCount := range stepCountCh {
|
||||
// Change text of heart rate label
|
||||
stepCountLbl.Text = fmt.Sprintf("%d Steps", stepCount)
|
||||
// Refresh label
|
||||
stepCountLbl.Refresh()
|
||||
}
|
||||
}()
|
||||
|
||||
// Create label for battery level
|
||||
battLevelLbl := newText("0%", 24)
|
||||
@@ -50,32 +75,40 @@ func infoTab(parent fyne.Window) *fyne.Container {
|
||||
)
|
||||
infoLayout.Add(battLevel)
|
||||
|
||||
// Watch for changes in battery level
|
||||
go watch(types.ReqTypeWatchBattLevel, func(data interface{}) {
|
||||
battLevelLbl.Text = fmt.Sprintf("%d%%", int(data.(float64)))
|
||||
battLevelLbl.Refresh()
|
||||
}, parent)
|
||||
battLevelCh, cancel, err := client.WatchBatteryLevel()
|
||||
if err != nil {
|
||||
guiErr(err, "Error getting battery level channel", true, parent)
|
||||
}
|
||||
onClose = append(onClose, cancel)
|
||||
go func() {
|
||||
for battLevel := range battLevelCh {
|
||||
// Change text of battery level label
|
||||
battLevelLbl.Text = fmt.Sprintf("%d%%", battLevel)
|
||||
// Refresh label
|
||||
battLevelLbl.Refresh()
|
||||
}
|
||||
}()
|
||||
|
||||
fwVerString, err := get(types.ReqTypeFwVersion)
|
||||
fwVerString, err := client.Version()
|
||||
if err != nil {
|
||||
guiErr(err, "Error getting firmware string", true, parent)
|
||||
}
|
||||
|
||||
fwVer := container.NewVBox(
|
||||
newText("Firmware Version", 12),
|
||||
newText(fwVerString.(string), 24),
|
||||
newText(fwVerString, 24),
|
||||
canvas.NewLine(theme.ShadowColor()),
|
||||
)
|
||||
infoLayout.Add(fwVer)
|
||||
|
||||
btAddrString, err := get(types.ReqTypeBtAddress)
|
||||
btAddrString, err := client.Address()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
btAddr := container.NewVBox(
|
||||
newText("Bluetooth Address", 12),
|
||||
newText(btAddrString.(string), 24),
|
||||
newText(btAddrString, 24),
|
||||
canvas.NewLine(theme.ShadowColor()),
|
||||
)
|
||||
infoLayout.Add(btAddr)
|
||||
@@ -83,65 +116,6 @@ func infoTab(parent fyne.Window) *fyne.Container {
|
||||
return infoLayout
|
||||
}
|
||||
|
||||
func watch(req int, onRecv func(data interface{}), parent fyne.Window) error {
|
||||
conn, err := net.Dial("unix", SockPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
err = json.NewEncoder(conn).Encode(types.Request{
|
||||
Type: req,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
scanner := bufio.NewScanner(conn)
|
||||
for scanner.Scan() {
|
||||
res, err := getResp(scanner.Bytes())
|
||||
if err != nil {
|
||||
guiErr(err, "Error getting response from connection", false, parent)
|
||||
continue
|
||||
}
|
||||
onRecv(res.Value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func get(req int) (interface{}, error) {
|
||||
conn, err := net.Dial("unix", SockPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
err = json.NewEncoder(conn).Encode(types.Request{
|
||||
Type: req,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
line, _, err := bufio.NewReader(conn).ReadLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := getResp(line)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.Value, nil
|
||||
}
|
||||
|
||||
func getResp(line []byte) (*types.Response, error) {
|
||||
var res types.Response
|
||||
err := json.Unmarshal(line, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.Error {
|
||||
return nil, errors.New(res.Message)
|
||||
}
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func newText(t string, size float32) *canvas.Text {
|
||||
text := canvas.NewText(t, theme.ForegroundColor())
|
||||
text.TextSize = size
|
||||
|
||||
@@ -1,31 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"fyne.io/fyne/v2/app"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"go.arsenm.dev/itd/api"
|
||||
)
|
||||
|
||||
var SockPath = "/tmp/itd/socket"
|
||||
var onClose []func()
|
||||
|
||||
func main() {
|
||||
// Create new app
|
||||
a := app.New()
|
||||
// Create new window with title "itgui"
|
||||
window := a.NewWindow("itgui")
|
||||
window.SetOnClosed(func() {
|
||||
for _, closeFn := range onClose {
|
||||
closeFn()
|
||||
}
|
||||
})
|
||||
|
||||
_, err := net.Dial("unix", SockPath)
|
||||
client, err := api.New(api.DefaultAddr)
|
||||
if err != nil {
|
||||
guiErr(err, "Error dialing itd socket", true, window)
|
||||
guiErr(err, "Error connecting to itd", true, window)
|
||||
}
|
||||
onClose = append(onClose, func() {
|
||||
client.Close()
|
||||
})
|
||||
|
||||
// Create new app tabs container
|
||||
tabs := container.NewAppTabs(
|
||||
container.NewTabItem("Info", infoTab(window)),
|
||||
container.NewTabItem("Notify", notifyTab(window)),
|
||||
container.NewTabItem("Set Time", timeTab(window)),
|
||||
container.NewTabItem("Upgrade", upgradeTab(window)),
|
||||
container.NewTabItem("Info", infoTab(window, client)),
|
||||
container.NewTabItem("Motion", motionTab(window, client)),
|
||||
container.NewTabItem("Notify", notifyTab(window, client)),
|
||||
container.NewTabItem("Set Time", timeTab(window, client)),
|
||||
container.NewTabItem("Upgrade", upgradeTab(window, client)),
|
||||
)
|
||||
|
||||
// Set tabs as window content
|
||||
|
||||
105
cmd/itgui/motion.go
Normal file
105
cmd/itgui/motion.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"strconv"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"go.arsenm.dev/itd/api"
|
||||
)
|
||||
|
||||
func motionTab(parent fyne.Window, client *api.Client) *fyne.Container {
|
||||
// Create label for heart rate
|
||||
xCoordLbl := newText("0", 24)
|
||||
// Creae container to store heart rate section
|
||||
xCoordSect := container.NewVBox(
|
||||
newText("X Coordinate", 12),
|
||||
xCoordLbl,
|
||||
canvas.NewLine(theme.ShadowColor()),
|
||||
)
|
||||
|
||||
// Create label for heart rate
|
||||
yCoordLbl := newText("0", 24)
|
||||
// Creae container to store heart rate section
|
||||
yCoordSect := container.NewVBox(
|
||||
newText("Y Coordinate", 12),
|
||||
yCoordLbl,
|
||||
canvas.NewLine(theme.ShadowColor()),
|
||||
)
|
||||
// Create label for heart rate
|
||||
zCoordLbl := newText("0", 24)
|
||||
// Creae container to store heart rate section
|
||||
zCoordSect := container.NewVBox(
|
||||
newText("Z Coordinate", 12),
|
||||
zCoordLbl,
|
||||
canvas.NewLine(theme.ShadowColor()),
|
||||
)
|
||||
|
||||
// Create variable to keep track of whether motion started
|
||||
started := false
|
||||
|
||||
// Create button to stop motion
|
||||
stopBtn := widget.NewButton("Stop", nil)
|
||||
// Create button to start motion
|
||||
startBtn := widget.NewButton("Start", func() {
|
||||
// if motion is started
|
||||
if started {
|
||||
// Do nothing
|
||||
return
|
||||
}
|
||||
// Set motion started
|
||||
started = true
|
||||
// Watch motion values
|
||||
motionCh, cancel, err := client.WatchMotion()
|
||||
if err != nil {
|
||||
guiErr(err, "Error getting heart rate channel", true, parent)
|
||||
}
|
||||
// Create done channel
|
||||
done := make(chan struct{}, 1)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case motion := <-motionCh:
|
||||
// Set labels to new values
|
||||
xCoordLbl.Text = strconv.Itoa(int(motion.X))
|
||||
yCoordLbl.Text = strconv.Itoa(int(motion.Y))
|
||||
zCoordLbl.Text = strconv.Itoa(int(motion.Z))
|
||||
// Refresh labels to display new values
|
||||
xCoordLbl.Refresh()
|
||||
yCoordLbl.Refresh()
|
||||
zCoordLbl.Refresh()
|
||||
}
|
||||
}
|
||||
}()
|
||||
// Create stop function
|
||||
stopBtn.OnTapped = func() {
|
||||
done <- struct{}{}
|
||||
started = false
|
||||
cancel()
|
||||
}
|
||||
|
||||
})
|
||||
// Run stop button function on close if possible
|
||||
onClose = append(onClose, func() {
|
||||
if stopBtn.OnTapped != nil {
|
||||
stopBtn.OnTapped()
|
||||
}
|
||||
})
|
||||
|
||||
// Return new container containing all elements
|
||||
return container.NewVBox(
|
||||
// Add rectangle for a bit of padding
|
||||
canvas.NewRectangle(color.Transparent),
|
||||
startBtn,
|
||||
stopBtn,
|
||||
xCoordSect,
|
||||
yCoordSect,
|
||||
zCoordSect,
|
||||
)
|
||||
}
|
||||
@@ -1,17 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"go.arsenm.dev/itd/internal/types"
|
||||
"go.arsenm.dev/itd/api"
|
||||
)
|
||||
|
||||
func notifyTab(parent fyne.Window) *fyne.Container {
|
||||
func notifyTab(parent fyne.Window, client *api.Client) *fyne.Container {
|
||||
// Create new entry for notification title
|
||||
titleEntry := widget.NewEntry()
|
||||
titleEntry.SetPlaceHolder("Title")
|
||||
@@ -22,20 +19,11 @@ func notifyTab(parent fyne.Window) *fyne.Container {
|
||||
|
||||
// Create new button to send notification
|
||||
sendBtn := widget.NewButton("Send", func() {
|
||||
// Dial itd UNIX socket
|
||||
conn, err := net.Dial("unix", SockPath)
|
||||
err := client.Notify(titleEntry.Text, bodyEntry.Text)
|
||||
if err != nil {
|
||||
guiErr(err, "Error dialing socket", false, parent)
|
||||
guiErr(err, "Error sending notification", false, parent)
|
||||
return
|
||||
}
|
||||
// Encode notify request on connection
|
||||
json.NewEncoder(conn).Encode(types.Request{
|
||||
Type: types.ReqTypeNotify,
|
||||
Data: types.ReqDataNotify{
|
||||
Title: titleEntry.Text,
|
||||
Body: bodyEntry.Text,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
// Return new container containing all elements
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"go.arsenm.dev/itd/internal/types"
|
||||
"go.arsenm.dev/itd/api"
|
||||
)
|
||||
|
||||
func timeTab(parent fyne.Window) *fyne.Container {
|
||||
func timeTab(parent fyne.Window, client *api.Client) *fyne.Container {
|
||||
// Create new entry for time string
|
||||
timeEntry := widget.NewEntry()
|
||||
// Set text to current time formatter properly
|
||||
@@ -21,7 +19,7 @@ func timeTab(parent fyne.Window) *fyne.Container {
|
||||
// Create button to set current time
|
||||
currentBtn := widget.NewButton("Set Current", func() {
|
||||
timeEntry.SetText(time.Now().Format(time.RFC1123))
|
||||
setTime(true)
|
||||
setTime(client, true)
|
||||
})
|
||||
|
||||
// Create button to set time inside entry
|
||||
@@ -33,7 +31,7 @@ func timeTab(parent fyne.Window) *fyne.Container {
|
||||
return
|
||||
}
|
||||
// Set time to parsed time
|
||||
setTime(false, parsedTime)
|
||||
setTime(client, false, parsedTime)
|
||||
})
|
||||
|
||||
// Return new container with all elements centered
|
||||
@@ -48,30 +46,13 @@ func timeTab(parent fyne.Window) *fyne.Container {
|
||||
|
||||
// setTime sets the first element in the variadic parameter
|
||||
// if current is false, otherwise, it sets the current time.
|
||||
func setTime(current bool, t ...time.Time) error {
|
||||
// Dial UNIX socket
|
||||
conn, err := net.Dial("unix", SockPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
var data string
|
||||
// If current is true, use the string "now"
|
||||
// otherwise, use the formatted time from the
|
||||
// first element in the variadic parameter.
|
||||
// "now" is more accurate than formatting
|
||||
// current time as only seconds are preserved
|
||||
// in that case.
|
||||
func setTime(client *api.Client, current bool, t ...time.Time) error {
|
||||
var err error
|
||||
if current {
|
||||
data = "now"
|
||||
err = client.SetTimeNow()
|
||||
} else {
|
||||
data = t[0].Format(time.RFC3339)
|
||||
err = client.SetTime(t[0])
|
||||
}
|
||||
// Encode SetTime request with above data
|
||||
err = json.NewEncoder(conn).Encode(types.Request{
|
||||
Type: types.ReqTypeSetTime,
|
||||
Data: data,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"path/filepath"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
@@ -13,11 +10,11 @@ import (
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/storage"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"go.arsenm.dev/itd/api"
|
||||
"go.arsenm.dev/itd/internal/types"
|
||||
)
|
||||
|
||||
func upgradeTab(parent fyne.Window) *fyne.Container {
|
||||
func upgradeTab(parent fyne.Window, client *api.Client) *fyne.Container {
|
||||
var (
|
||||
archivePath string
|
||||
firmwarePath string
|
||||
@@ -117,7 +114,7 @@ func upgradeTab(parent fyne.Window) *fyne.Container {
|
||||
// Resize modal to 300x100
|
||||
progressDlg.Resize(fyne.NewSize(300, 100))
|
||||
|
||||
var fwUpgType int
|
||||
var fwUpgType api.UpgradeType
|
||||
var files []string
|
||||
// Get appropriate upgrade type and file paths
|
||||
switch upgradeTypeSelect.Selected {
|
||||
@@ -129,48 +126,18 @@ func upgradeTab(parent fyne.Window) *fyne.Container {
|
||||
files = append(files, initPktPath, firmwarePath)
|
||||
}
|
||||
|
||||
// Dial itd UNIX socket
|
||||
conn, err := net.Dial("unix", SockPath)
|
||||
progress, err := client.FirmwareUpgrade(fwUpgType, files...)
|
||||
if err != nil {
|
||||
guiErr(err, "Error dialing socket", false, parent)
|
||||
guiErr(err, "Error initiating DFU", false, parent)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Encode firmware upgrade request to connection
|
||||
json.NewEncoder(conn).Encode(types.Request{
|
||||
Type: types.ReqTypeFwUpgrade,
|
||||
Data: types.ReqDataFwUpgrade{
|
||||
Type: fwUpgType,
|
||||
Files: files,
|
||||
},
|
||||
})
|
||||
|
||||
// Show progress dialog
|
||||
progressDlg.Show()
|
||||
// Hide progress dialog after completion
|
||||
defer progressDlg.Hide()
|
||||
|
||||
scanner := bufio.NewScanner(conn)
|
||||
for scanner.Scan() {
|
||||
var res types.Response
|
||||
// Decode scanned line into response struct
|
||||
err = json.Unmarshal(scanner.Bytes(), &res)
|
||||
if err != nil {
|
||||
guiErr(err, "Error decoding response", false, parent)
|
||||
return
|
||||
}
|
||||
if res.Error {
|
||||
guiErr(err, "Error returned in response", false, parent)
|
||||
return
|
||||
}
|
||||
var event types.DFUProgress
|
||||
// Decode response data into progress struct
|
||||
err = mapstructure.Decode(res.Value, &event)
|
||||
if err != nil {
|
||||
guiErr(err, "Error decoding response value", false, parent)
|
||||
return
|
||||
}
|
||||
for event := range progress {
|
||||
// Set label text to received / total B
|
||||
progressLbl.SetText(fmt.Sprintf("%d / %d B", event.Received, event.Total))
|
||||
// Set progress bar values
|
||||
@@ -179,7 +146,7 @@ func upgradeTab(parent fyne.Window) *fyne.Container {
|
||||
// Refresh progress bar
|
||||
progressBar.Refresh()
|
||||
// If transfer finished, break
|
||||
if event.Received == event.Total {
|
||||
if event.Sent == event.Total {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
3
go.mod
3
go.mod
@@ -13,6 +13,7 @@ require (
|
||||
github.com/go-gl/gl v0.0.0-20210905235341-f7a045908259 // indirect
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be // indirect
|
||||
github.com/godbus/dbus/v5 v5.0.5
|
||||
github.com/google/uuid v1.1.2
|
||||
github.com/mattn/go-colorable v0.1.11 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.2
|
||||
@@ -24,7 +25,7 @@ require (
|
||||
github.com/srwiley/oksvg v0.0.0-20210519022825-9fc0c575d5fe // indirect
|
||||
github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 // indirect
|
||||
github.com/yuin/goldmark v1.4.1 // indirect
|
||||
go.arsenm.dev/infinitime v0.0.0-20211022195951-45baea10486b
|
||||
go.arsenm.dev/infinitime v0.0.0-20211023042633-53aa6f8a0c72
|
||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect
|
||||
golang.org/x/net v0.0.0-20211011170408-caeb26a5c8c0 // indirect
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac // indirect
|
||||
|
||||
5
go.sum
5
go.sum
@@ -193,6 +193,7 @@ github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLe
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
@@ -366,8 +367,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
|
||||
github.com/yuin/goldmark v1.3.8/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.1 h1:/vn0k+RBvwlxEmP5E7SZMqNxPhfMVFEJiykr15/0XKM=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.arsenm.dev/infinitime v0.0.0-20211022195951-45baea10486b h1:2VitKPwSYSWXmL5BH88nfTPLSIYPCt4yubpEJHhcQBc=
|
||||
go.arsenm.dev/infinitime v0.0.0-20211022195951-45baea10486b/go.mod h1:gaepaueUz4J5FfxuV19B4w5pi+V3mD0LTef50ryxr/Q=
|
||||
go.arsenm.dev/infinitime v0.0.0-20211023042633-53aa6f8a0c72 h1:e8kOuL6Jj8ZjJzkGwJ3xqpGG9EhUzfvZk9AlSsm3X1U=
|
||||
go.arsenm.dev/infinitime v0.0.0-20211023042633-53aa6f8a0c72/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=
|
||||
|
||||
@@ -14,6 +14,7 @@ const (
|
||||
ReqTypeWatchMotion
|
||||
ReqTypeStepCount
|
||||
ReqTypeWatchStepCount
|
||||
ReqTypeCancel
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -27,8 +28,10 @@ type ReqDataFwUpgrade struct {
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Type int `json:"type"`
|
||||
Value interface{} `json:"value,omitempty"`
|
||||
Message string `json:"msg,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Error bool `json:"error"`
|
||||
}
|
||||
|
||||
@@ -45,6 +48,7 @@ type ReqDataNotify struct {
|
||||
type DFUProgress struct {
|
||||
Received int64 `mapstructure:"recvd"`
|
||||
Total int64 `mapstructure:"total"`
|
||||
Sent int64 `mapstructure:"sent"`
|
||||
}
|
||||
|
||||
type MotionValues struct {
|
||||
|
||||
208
socket.go
208
socket.go
@@ -27,6 +27,7 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
@@ -35,6 +36,29 @@ import (
|
||||
"go.arsenm.dev/itd/translit"
|
||||
)
|
||||
|
||||
type DoneMap map[string]chan struct{}
|
||||
|
||||
func (dm DoneMap) Exists(key string) bool {
|
||||
_, ok := dm[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (dm DoneMap) Done(key string) {
|
||||
ch := dm[key]
|
||||
ch <- struct{}{}
|
||||
}
|
||||
|
||||
func (dm DoneMap) Create(key string) {
|
||||
dm[key] = make(chan struct{}, 1)
|
||||
}
|
||||
|
||||
func (dm DoneMap) Remove(key string) {
|
||||
close(dm[key])
|
||||
delete(dm, key)
|
||||
}
|
||||
|
||||
var done = DoneMap{}
|
||||
|
||||
func startSocket(dev *infinitime.Device) error {
|
||||
// Make socket directory if non-existant
|
||||
err := os.MkdirAll(filepath.Dir(viper.GetString("socket.path")), 0755)
|
||||
@@ -75,11 +99,6 @@ func startSocket(dev *infinitime.Device) error {
|
||||
|
||||
func handleConnection(conn net.Conn, dev *infinitime.Device) {
|
||||
defer conn.Close()
|
||||
// If firmware is updating, return error
|
||||
if firmwareUpdating {
|
||||
connErr(conn, nil, "Firmware update in progress")
|
||||
return
|
||||
}
|
||||
|
||||
// Create new scanner on connection
|
||||
scanner := bufio.NewScanner(conn)
|
||||
@@ -88,134 +107,203 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
|
||||
// Decode scanned message into types.Request
|
||||
err := json.Unmarshal(scanner.Bytes(), &req)
|
||||
if err != nil {
|
||||
connErr(conn, err, "Error decoding JSON input")
|
||||
connErr(conn, req.Type, err, "Error decoding JSON input")
|
||||
continue
|
||||
}
|
||||
|
||||
// If firmware is updating, return error
|
||||
if firmwareUpdating {
|
||||
connErr(conn, req.Type, nil, "Firmware update in progress")
|
||||
return
|
||||
}
|
||||
|
||||
switch req.Type {
|
||||
case types.ReqTypeHeartRate:
|
||||
// Get heart rate from watch
|
||||
heartRate, err := dev.HeartRate()
|
||||
if err != nil {
|
||||
connErr(conn, err, "Error getting heart rate")
|
||||
connErr(conn, req.Type, err, "Error getting heart rate")
|
||||
break
|
||||
}
|
||||
// Encode heart rate to connection
|
||||
json.NewEncoder(conn).Encode(types.Response{
|
||||
Type: req.Type,
|
||||
Value: heartRate,
|
||||
})
|
||||
case types.ReqTypeWatchHeartRate:
|
||||
heartRateCh, err := dev.WatchHeartRate()
|
||||
heartRateCh, cancel, err := dev.WatchHeartRate()
|
||||
if err != nil {
|
||||
connErr(conn, err, "Error getting heart rate channel")
|
||||
connErr(conn, req.Type, err, "Error getting heart rate channel")
|
||||
break
|
||||
}
|
||||
reqID := uuid.New().String()
|
||||
go func() {
|
||||
done.Create(reqID)
|
||||
// For every heart rate value
|
||||
for heartRate := range heartRateCh {
|
||||
json.NewEncoder(conn).Encode(types.Response{
|
||||
Value: heartRate,
|
||||
})
|
||||
select {
|
||||
case <-done[reqID]:
|
||||
// Stop notifications if done signal received
|
||||
cancel()
|
||||
done.Remove(reqID)
|
||||
return
|
||||
default:
|
||||
// Encode response to connection if no done signal received
|
||||
json.NewEncoder(conn).Encode(types.Response{
|
||||
Type: req.Type,
|
||||
ID: reqID,
|
||||
Value: heartRate,
|
||||
})
|
||||
}
|
||||
}
|
||||
}()
|
||||
case types.ReqTypeBattLevel:
|
||||
// Get battery level from watch
|
||||
battLevel, err := dev.BatteryLevel()
|
||||
if err != nil {
|
||||
connErr(conn, err, "Error getting battery level")
|
||||
connErr(conn, req.Type, err, "Error getting battery level")
|
||||
break
|
||||
}
|
||||
// Encode battery level to connection
|
||||
json.NewEncoder(conn).Encode(types.Response{
|
||||
Type: req.Type,
|
||||
Value: battLevel,
|
||||
})
|
||||
case types.ReqTypeWatchBattLevel:
|
||||
battLevelCh, err := dev.WatchBatteryLevel()
|
||||
battLevelCh, cancel, err := dev.WatchBatteryLevel()
|
||||
if err != nil {
|
||||
connErr(conn, err, "Error getting heart rate channel")
|
||||
connErr(conn, req.Type, err, "Error getting battery level channel")
|
||||
break
|
||||
}
|
||||
reqID := uuid.New().String()
|
||||
go func() {
|
||||
done.Create(reqID)
|
||||
// For every battery level value
|
||||
for battLevel := range battLevelCh {
|
||||
json.NewEncoder(conn).Encode(types.Response{
|
||||
Value: battLevel,
|
||||
})
|
||||
select {
|
||||
case <-done[reqID]:
|
||||
// Stop notifications if done signal received
|
||||
cancel()
|
||||
done.Remove(reqID)
|
||||
return
|
||||
default:
|
||||
// Encode response to connection if no done signal received
|
||||
json.NewEncoder(conn).Encode(types.Response{
|
||||
Type: req.Type,
|
||||
ID: reqID,
|
||||
Value: battLevel,
|
||||
})
|
||||
}
|
||||
}
|
||||
}()
|
||||
case types.ReqTypeMotion:
|
||||
// Get battery level from watch
|
||||
motionVals, err := dev.Motion()
|
||||
if err != nil {
|
||||
connErr(conn, err, "Error getting motion values")
|
||||
connErr(conn, req.Type, err, "Error getting motion values")
|
||||
break
|
||||
}
|
||||
// Encode battery level to connection
|
||||
json.NewEncoder(conn).Encode(types.Response{
|
||||
Type: req.Type,
|
||||
Value: motionVals,
|
||||
})
|
||||
case types.ReqTypeWatchMotion:
|
||||
motionValCh, _, err := dev.WatchMotion()
|
||||
motionValCh, cancel, err := dev.WatchMotion()
|
||||
if err != nil {
|
||||
connErr(conn, err, "Error getting heart rate channel")
|
||||
connErr(conn, req.Type, err, "Error getting heart rate channel")
|
||||
break
|
||||
}
|
||||
reqID := uuid.New().String()
|
||||
go func() {
|
||||
done.Create(reqID)
|
||||
// For every motion event
|
||||
for motionVals := range motionValCh {
|
||||
json.NewEncoder(conn).Encode(types.Response{
|
||||
Value: motionVals,
|
||||
})
|
||||
select {
|
||||
case <-done[reqID]:
|
||||
// Stop notifications if done signal received
|
||||
cancel()
|
||||
done.Remove(reqID)
|
||||
|
||||
return
|
||||
default:
|
||||
// Encode response to connection if no done signal received
|
||||
json.NewEncoder(conn).Encode(types.Response{
|
||||
Type: req.Type,
|
||||
ID: reqID,
|
||||
Value: motionVals,
|
||||
})
|
||||
}
|
||||
}
|
||||
}()
|
||||
case types.ReqTypeStepCount:
|
||||
// Get battery level from watch
|
||||
stepCount, err := dev.StepCount()
|
||||
if err != nil {
|
||||
connErr(conn, err, "Error getting step count")
|
||||
connErr(conn, req.Type, err, "Error getting step count")
|
||||
break
|
||||
}
|
||||
// Encode battery level to connection
|
||||
json.NewEncoder(conn).Encode(types.Response{
|
||||
Type: req.Type,
|
||||
Value: stepCount,
|
||||
})
|
||||
case types.ReqTypeWatchStepCount:
|
||||
stepCountCh, _, err := dev.WatchStepCount()
|
||||
stepCountCh, cancel, err := dev.WatchStepCount()
|
||||
if err != nil {
|
||||
connErr(conn, err, "Error getting heart rate channel")
|
||||
connErr(conn, req.Type, err, "Error getting heart rate channel")
|
||||
break
|
||||
}
|
||||
reqID := uuid.New().String()
|
||||
go func() {
|
||||
done.Create(reqID)
|
||||
// For every step count value
|
||||
for stepCount := range stepCountCh {
|
||||
json.NewEncoder(conn).Encode(types.Response{
|
||||
Value: stepCount,
|
||||
})
|
||||
select {
|
||||
case <-done[reqID]:
|
||||
// Stop notifications if done signal received
|
||||
cancel()
|
||||
done.Remove(reqID)
|
||||
return
|
||||
default:
|
||||
// Encode response to connection if no done signal received
|
||||
json.NewEncoder(conn).Encode(types.Response{
|
||||
Type: req.Type,
|
||||
ID: reqID,
|
||||
Value: stepCount,
|
||||
})
|
||||
}
|
||||
}
|
||||
}()
|
||||
case types.ReqTypeFwVersion:
|
||||
// Get firmware version from watch
|
||||
version, err := dev.Version()
|
||||
if err != nil {
|
||||
connErr(conn, err, "Error getting battery level")
|
||||
connErr(conn, req.Type, err, "Error getting firmware version")
|
||||
break
|
||||
}
|
||||
// Encode version to connection
|
||||
json.NewEncoder(conn).Encode(types.Response{
|
||||
Type: req.Type,
|
||||
Value: version,
|
||||
})
|
||||
case types.ReqTypeBtAddress:
|
||||
// Encode bluetooth address to connection
|
||||
json.NewEncoder(conn).Encode(types.Response{
|
||||
Type: req.Type,
|
||||
Value: dev.Address(),
|
||||
})
|
||||
case types.ReqTypeNotify:
|
||||
// If no data, return error
|
||||
if req.Data == nil {
|
||||
connErr(conn, nil, "Data required for notify request")
|
||||
connErr(conn, req.Type, nil, "Data required for notify request")
|
||||
break
|
||||
}
|
||||
var reqData types.ReqDataNotify
|
||||
// Decode data map to notify request data
|
||||
err = mapstructure.Decode(req.Data, &reqData)
|
||||
if err != nil {
|
||||
connErr(conn, err, "Error decoding request data")
|
||||
connErr(conn, req.Type, err, "Error decoding request data")
|
||||
break
|
||||
}
|
||||
maps := viper.GetStringSlice("notifs.translit.use")
|
||||
@@ -225,21 +313,21 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
|
||||
// Send notification to watch
|
||||
err = dev.Notify(title, body)
|
||||
if err != nil {
|
||||
connErr(conn, err, "Error sending notification")
|
||||
connErr(conn, req.Type, err, "Error sending notification")
|
||||
break
|
||||
}
|
||||
// Encode empty types.Response to connection
|
||||
json.NewEncoder(conn).Encode(types.Response{})
|
||||
json.NewEncoder(conn).Encode(types.Response{Type: req.Type})
|
||||
case types.ReqTypeSetTime:
|
||||
// If no data, return error
|
||||
if req.Data == nil {
|
||||
connErr(conn, nil, "Data required for settime request")
|
||||
connErr(conn, req.Type, 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 request must be RFC3339 formatted time string")
|
||||
connErr(conn, req.Type, nil, "Data for settime request must be RFC3339 formatted time string")
|
||||
break
|
||||
}
|
||||
|
||||
@@ -250,29 +338,29 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
|
||||
// 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`")
|
||||
connErr(conn, req.Type, err, "Invalid time format. Time string must be formatted as ISO8601 or the word `now`")
|
||||
break
|
||||
}
|
||||
}
|
||||
// Set time on watch
|
||||
err = dev.SetTime(reqTime)
|
||||
if err != nil {
|
||||
connErr(conn, err, "Error setting device time")
|
||||
connErr(conn, req.Type, err, "Error setting device time")
|
||||
break
|
||||
}
|
||||
// Encode empty types.Response to connection
|
||||
json.NewEncoder(conn).Encode(types.Response{})
|
||||
json.NewEncoder(conn).Encode(types.Response{Type: req.Type})
|
||||
case types.ReqTypeFwUpgrade:
|
||||
// If no data, return error
|
||||
if req.Data == nil {
|
||||
connErr(conn, nil, "Data required for firmware upgrade request")
|
||||
connErr(conn, req.Type, nil, "Data required for firmware upgrade request")
|
||||
break
|
||||
}
|
||||
var reqData types.ReqDataFwUpgrade
|
||||
// Decode data map to firmware upgrade request data
|
||||
err = mapstructure.Decode(req.Data, &reqData)
|
||||
if err != nil {
|
||||
connErr(conn, err, "Error decoding request data")
|
||||
connErr(conn, req.Type, err, "Error decoding request data")
|
||||
break
|
||||
}
|
||||
// Reset DFU to prepare for next update
|
||||
@@ -281,40 +369,40 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
|
||||
case types.UpgradeTypeArchive:
|
||||
// If less than one file, return error
|
||||
if len(reqData.Files) < 1 {
|
||||
connErr(conn, nil, "Archive upgrade requires one file with .zip extension")
|
||||
connErr(conn, req.Type, nil, "Archive upgrade requires one file with .zip extension")
|
||||
break
|
||||
}
|
||||
// If file is not zip archive, return error
|
||||
if filepath.Ext(reqData.Files[0]) != ".zip" {
|
||||
connErr(conn, nil, "Archive upgrade file must be a zip archive")
|
||||
connErr(conn, req.Type, nil, "Archive upgrade file must be a zip archive")
|
||||
break
|
||||
}
|
||||
// Load DFU archive
|
||||
err := dev.DFU.LoadArchive(reqData.Files[0])
|
||||
if err != nil {
|
||||
connErr(conn, err, "Error loading archive file")
|
||||
connErr(conn, req.Type, err, "Error loading archive file")
|
||||
break
|
||||
}
|
||||
case types.UpgradeTypeFiles:
|
||||
// If less than two files, return error
|
||||
if len(reqData.Files) < 2 {
|
||||
connErr(conn, nil, "Files upgrade requires two files. First with .dat and second with .bin extension.")
|
||||
connErr(conn, req.Type, nil, "Files upgrade requires two files. First with .dat and second with .bin extension.")
|
||||
break
|
||||
}
|
||||
// If first file is not init packet, return error
|
||||
if filepath.Ext(reqData.Files[0]) != ".dat" {
|
||||
connErr(conn, nil, "First file must be a .dat file")
|
||||
connErr(conn, req.Type, nil, "First file must be a .dat file")
|
||||
break
|
||||
}
|
||||
// If second file is not firmware image, return error
|
||||
if filepath.Ext(reqData.Files[1]) != ".bin" {
|
||||
connErr(conn, nil, "Second file must be a .bin file")
|
||||
connErr(conn, req.Type, nil, "Second file must be a .bin file")
|
||||
break
|
||||
}
|
||||
// Load individual DFU files
|
||||
err := dev.DFU.LoadFiles(reqData.Files[0], reqData.Files[1])
|
||||
if err != nil {
|
||||
connErr(conn, err, "Error loading firmware files")
|
||||
connErr(conn, req.Type, err, "Error loading firmware files")
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -326,9 +414,11 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
|
||||
for event := range progress {
|
||||
// Encode event on connection
|
||||
json.NewEncoder(conn).Encode(types.Response{
|
||||
Type: req.Type,
|
||||
Value: event,
|
||||
})
|
||||
}
|
||||
firmwareUpdating = false
|
||||
}()
|
||||
|
||||
// Set firmwareUpdating
|
||||
@@ -336,16 +426,30 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
|
||||
// Start DFU
|
||||
err = dev.DFU.Start()
|
||||
if err != nil {
|
||||
connErr(conn, err, "Error performing upgrade")
|
||||
connErr(conn, req.Type, err, "Error performing upgrade")
|
||||
firmwareUpdating = false
|
||||
break
|
||||
}
|
||||
firmwareUpdating = false
|
||||
case types.ReqTypeCancel:
|
||||
if req.Data == nil {
|
||||
connErr(conn, req.Type, nil, "No data provided. Cancel request requires request ID string as data.")
|
||||
continue
|
||||
}
|
||||
reqID, ok := req.Data.(string)
|
||||
if !ok {
|
||||
connErr(conn, req.Type, nil, "Invalid data. Cancel request required request ID string as data.")
|
||||
}
|
||||
// Stop notifications
|
||||
done.Done(reqID)
|
||||
json.NewEncoder(conn).Encode(types.Response{Type: req.Type})
|
||||
default:
|
||||
connErr(conn, req.Type, nil, fmt.Sprintf("Unknown request type %d", req.Type))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func connErr(conn net.Conn, err error, msg string) {
|
||||
func connErr(conn net.Conn, resType int, err error, msg string) {
|
||||
var res types.Response
|
||||
// If error exists, add to types.Response, otherwise don't
|
||||
if err != nil {
|
||||
@@ -353,7 +457,7 @@ func connErr(conn net.Conn, err error, msg string) {
|
||||
res = types.Response{Message: fmt.Sprintf("%s: %s", msg, err)}
|
||||
} else {
|
||||
log.Error().Msg(msg)
|
||||
res = types.Response{Message: msg}
|
||||
res = types.Response{Message: msg, Type: resType}
|
||||
}
|
||||
res.Error = true
|
||||
|
||||
|
||||
Reference in New Issue
Block a user