Compare commits
14 Commits
8cf2b47733
...
v0.0.4
| Author | SHA1 | Date | |
|---|---|---|---|
| 552f19676b | |||
| 9d58ea0ae7 | |||
| 76875db7ea | |||
| be5bdc625b | |||
| 0d0db949af | |||
| 0d164aef3d | |||
| 28610d9ebb | |||
| dff34b484d | |||
| 2ea9f99db6 | |||
| 44dc5f8e47 | |||
| 4d35912466 | |||
| ef29b9bee4 | |||
| e198b769f9 | |||
| ef4bad94b5 |
@@ -18,11 +18,11 @@ const DefaultAddr = "/tmp/itd/socket"
|
||||
type Client struct {
|
||||
conn net.Conn
|
||||
respCh chan types.Response
|
||||
heartRateCh chan uint8
|
||||
battLevelCh chan uint8
|
||||
stepCountCh chan uint32
|
||||
motionCh chan infinitime.MotionValues
|
||||
dfuProgressCh chan DFUProgress
|
||||
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
|
||||
@@ -90,28 +90,44 @@ func (c *Client) requestNoRes(req types.Request) error {
|
||||
// handleResp handles the received response as needed
|
||||
func (c *Client) handleResp(res types.Response) error {
|
||||
switch res.Type {
|
||||
case types.ResTypeWatchHeartRate:
|
||||
c.heartRateCh <- uint8(res.Value.(float64))
|
||||
case types.ResTypeWatchBattLevel:
|
||||
c.battLevelCh <- uint8(res.Value.(float64))
|
||||
case types.ResTypeWatchStepCount:
|
||||
c.stepCountCh <- uint32(res.Value.(float64))
|
||||
case types.ResTypeWatchMotion:
|
||||
out := infinitime.MotionValues{}
|
||||
err := mapstructure.Decode(res.Value, &out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.motionCh <- out
|
||||
case types.ResTypeDFUProgress:
|
||||
out := DFUProgress{}
|
||||
err := mapstructure.Decode(res.Value, &out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.dfuProgressCh <- out
|
||||
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
|
||||
}
|
||||
|
||||
95
api/info.go
95
api/info.go
@@ -1,8 +1,6 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"go.arsenm.dev/infinitime"
|
||||
"go.arsenm.dev/itd/internal/types"
|
||||
@@ -48,15 +46,27 @@ func (c *Client) BatteryLevel() (uint8, error) {
|
||||
// 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 uint8, 2)
|
||||
c.battLevelCh = make(chan types.Response, 2)
|
||||
err := c.requestNoRes(types.Request{
|
||||
Type: types.ReqTypeBattLevel,
|
||||
Type: types.ReqTypeWatchBattLevel,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cancel := c.cancelFn(types.ReqTypeCancelBattLevel, c.battLevelCh)
|
||||
return c.battLevelCh, cancel, nil
|
||||
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
|
||||
@@ -68,33 +78,46 @@ func (c *Client) HeartRate() (uint8, error) {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return uint8(res.Value.(float64)), nil
|
||||
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 uint8, 2)
|
||||
c.heartRateCh = make(chan types.Response, 2)
|
||||
err := c.requestNoRes(types.Request{
|
||||
Type: types.ReqTypeWatchHeartRate,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cancel := c.cancelFn(types.ReqTypeCancelHeartRate, c.heartRateCh)
|
||||
return c.heartRateCh, cancel, nil
|
||||
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(reqType int, ch interface{}) func() {
|
||||
return func() {
|
||||
reflectCh := reflect.ValueOf(ch)
|
||||
reflectCh.Close()
|
||||
reflectCh.Set(reflect.Zero(reflectCh.Type()))
|
||||
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: reqType,
|
||||
Type: types.ReqTypeCancel,
|
||||
Data: reqID,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -115,15 +138,27 @@ func (c *Client) StepCount() (uint32, error) {
|
||||
// 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 uint32, 2)
|
||||
c.stepCountCh = make(chan types.Response, 2)
|
||||
err := c.requestNoRes(types.Request{
|
||||
Type: types.ReqTypeWatchStepCount,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cancel := c.cancelFn(types.ReqTypeCancelStepCount, c.stepCountCh)
|
||||
return c.stepCountCh, cancel, nil
|
||||
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
|
||||
@@ -146,13 +181,29 @@ func (c *Client) Motion() (infinitime.MotionValues, error) {
|
||||
// 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 infinitime.MotionValues, 2)
|
||||
c.motionCh = make(chan types.Response, 5)
|
||||
err := c.requestNoRes(types.Request{
|
||||
Type: types.ReqTypeWatchMotion,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cancel := c.cancelFn(types.ReqTypeCancelMotion, c.motionCh)
|
||||
return c.motionCh, cancel, nil
|
||||
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
|
||||
}
|
||||
@@ -31,7 +31,18 @@ func (c *Client) FirmwareUpgrade(upgType UpgradeType, files ...string) (<-chan D
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.dfuProgressCh = make(chan DFUProgress, 5)
|
||||
c.dfuProgressCh = make(chan types.Response, 5)
|
||||
|
||||
return c.dfuProgressCh, nil
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
1
go.mod
1
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
|
||||
|
||||
1
go.sum
1
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=
|
||||
|
||||
@@ -9,35 +9,12 @@ const (
|
||||
ReqTypeNotify
|
||||
ReqTypeSetTime
|
||||
ReqTypeWatchHeartRate
|
||||
ReqTypeCancelHeartRate
|
||||
ReqTypeWatchBattLevel
|
||||
ReqTypeCancelBattLevel
|
||||
ReqTypeMotion
|
||||
ReqTypeWatchMotion
|
||||
ReqTypeCancelMotion
|
||||
ReqTypeStepCount
|
||||
ReqTypeWatchStepCount
|
||||
ReqTypeCancelStepCount
|
||||
)
|
||||
|
||||
const (
|
||||
ResTypeHeartRate = iota
|
||||
ResTypeBattLevel
|
||||
ResTypeFwVersion
|
||||
ResTypeDFUProgress
|
||||
ResTypeBtAddress
|
||||
ResTypeNotify
|
||||
ResTypeSetTime
|
||||
ResTypeWatchHeartRate
|
||||
ResTypeCancelHeartRate
|
||||
ResTypeWatchBattLevel
|
||||
ResTypeCancelBattLevel
|
||||
ResTypeMotion
|
||||
ResTypeWatchMotion
|
||||
ResTypeCancelMotion
|
||||
ResTypeStepCount
|
||||
ResTypeWatchStepCount
|
||||
ResTypeCancelStepCount
|
||||
ReqTypeCancel
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -54,6 +31,7 @@ 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"`
|
||||
}
|
||||
|
||||
@@ -70,6 +48,7 @@ type ReqDataNotify struct {
|
||||
type DFUProgress struct {
|
||||
Received int64 `mapstructure:"recvd"`
|
||||
Total int64 `mapstructure:"total"`
|
||||
Sent int64 `mapstructure:"sent"`
|
||||
}
|
||||
|
||||
type MotionValues struct {
|
||||
|
||||
180
socket.go
180
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,16 +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
|
||||
}
|
||||
|
||||
heartRateDone := make(chan struct{})
|
||||
battLevelDone := make(chan struct{})
|
||||
stepCountDone := make(chan struct{})
|
||||
motionDone := make(chan struct{})
|
||||
|
||||
// Create new scanner on connection
|
||||
scanner := bufio.NewScanner(conn)
|
||||
@@ -93,196 +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: types.ResTypeHeartRate,
|
||||
Type: req.Type,
|
||||
Value: heartRate,
|
||||
})
|
||||
case types.ReqTypeWatchHeartRate:
|
||||
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 {
|
||||
select {
|
||||
case <-heartRateDone:
|
||||
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: types.ResTypeWatchHeartRate,
|
||||
Type: req.Type,
|
||||
ID: reqID,
|
||||
Value: heartRate,
|
||||
})
|
||||
}
|
||||
}
|
||||
}()
|
||||
case types.ReqTypeCancelHeartRate:
|
||||
// Stop heart rate notifications
|
||||
heartRateDone <- struct{}{}
|
||||
json.NewEncoder(conn).Encode(types.Response{})
|
||||
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: types.ResTypeBattLevel,
|
||||
Type: req.Type,
|
||||
Value: battLevel,
|
||||
})
|
||||
case types.ReqTypeWatchBattLevel:
|
||||
battLevelCh, cancel, err := dev.WatchBatteryLevel()
|
||||
if err != nil {
|
||||
connErr(conn, err, "Error getting battery level 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 {
|
||||
select {
|
||||
case <-battLevelDone:
|
||||
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: types.ResTypeWatchBattLevel,
|
||||
Type: req.Type,
|
||||
ID: reqID,
|
||||
Value: battLevel,
|
||||
})
|
||||
}
|
||||
}
|
||||
}()
|
||||
case types.ReqTypeCancelBattLevel:
|
||||
// Stop battery level notifications
|
||||
battLevelDone <- struct{}{}
|
||||
json.NewEncoder(conn).Encode(types.Response{})
|
||||
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: types.ResTypeMotion,
|
||||
Type: req.Type,
|
||||
Value: motionVals,
|
||||
})
|
||||
case types.ReqTypeWatchMotion:
|
||||
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 {
|
||||
select {
|
||||
case <-motionDone:
|
||||
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: types.ResTypeWatchMotion,
|
||||
Type: req.Type,
|
||||
ID: reqID,
|
||||
Value: motionVals,
|
||||
})
|
||||
}
|
||||
}
|
||||
}()
|
||||
case types.ReqTypeCancelMotion:
|
||||
// Stop motion notifications
|
||||
motionDone <- struct{}{}
|
||||
json.NewEncoder(conn).Encode(types.Response{})
|
||||
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: types.ResTypeStepCount,
|
||||
Type: req.Type,
|
||||
Value: stepCount,
|
||||
})
|
||||
case types.ReqTypeWatchStepCount:
|
||||
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 {
|
||||
select {
|
||||
case <-stepCountDone:
|
||||
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: types.ResTypeWatchStepCount,
|
||||
Type: req.Type,
|
||||
ID: reqID,
|
||||
Value: stepCount,
|
||||
})
|
||||
}
|
||||
}
|
||||
}()
|
||||
case types.ReqTypeCancelStepCount:
|
||||
// Stop step count notifications
|
||||
stepCountDone <- struct{}{}
|
||||
json.NewEncoder(conn).Encode(types.Response{})
|
||||
case types.ReqTypeFwVersion:
|
||||
// Get firmware version from watch
|
||||
version, err := dev.Version()
|
||||
if err != nil {
|
||||
connErr(conn, err, "Error getting firmware version")
|
||||
connErr(conn, req.Type, err, "Error getting firmware version")
|
||||
break
|
||||
}
|
||||
// Encode version to connection
|
||||
json.NewEncoder(conn).Encode(types.Response{
|
||||
Type: types.ResTypeFwVersion,
|
||||
Type: req.Type,
|
||||
Value: version,
|
||||
})
|
||||
case types.ReqTypeBtAddress:
|
||||
// Encode bluetooth address to connection
|
||||
json.NewEncoder(conn).Encode(types.Response{
|
||||
Type: types.ResTypeBtAddress,
|
||||
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")
|
||||
@@ -292,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{Type: types.ResTypeNotify})
|
||||
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
|
||||
}
|
||||
|
||||
@@ -317,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{Type: types.ResTypeSetTime})
|
||||
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
|
||||
@@ -348,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
|
||||
}
|
||||
}
|
||||
@@ -393,10 +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: types.ResTypeDFUProgress,
|
||||
Type: req.Type,
|
||||
Value: event,
|
||||
})
|
||||
}
|
||||
firmwareUpdating = false
|
||||
}()
|
||||
|
||||
// Set firmwareUpdating
|
||||
@@ -404,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 {
|
||||
@@ -421,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