From a235903583e0d4aea16ce19e4034284db2b7e6ca Mon Sep 17 00:00:00 2001 From: Elara Musayelyan Date: Fri, 22 Oct 2021 20:47:57 -0700 Subject: [PATCH] Send response types in socket responses and create api package --- api/client.go | 102 +++++++++++++++++++++++++++++++++ api/info.go | 121 ++++++++++++++++++++++++++++++++++++++++ api/time.go | 29 ++++++++++ api/upgrade.go | 33 +++++++++++ internal/types/types.go | 17 ++++++ socket.go | 19 +++++-- 6 files changed, 317 insertions(+), 4 deletions(-) create mode 100644 api/client.go create mode 100644 api/info.go create mode 100644 api/time.go create mode 100644 api/upgrade.go diff --git a/api/client.go b/api/client.go new file mode 100644 index 0000000..20e2fbd --- /dev/null +++ b/api/client.go @@ -0,0 +1,102 @@ +package api + +import ( + "bufio" + "encoding/json" + "errors" + "net" + + "github.com/mitchellh/mapstructure" + "go.arsenm.dev/infinitime" + "go.arsenm.dev/itd/internal/types" +) + +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 +} + +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) 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 +} + +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 +} + +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 + default: + c.respCh <- res + } + return nil +} diff --git a/api/info.go b/api/info.go new file mode 100644 index 0000000..409d631 --- /dev/null +++ b/api/info.go @@ -0,0 +1,121 @@ +package api + +import ( + "github.com/mitchellh/mapstructure" + "go.arsenm.dev/infinitime" + "go.arsenm.dev/itd/internal/types" +) + +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 +} + +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 +} + +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 +} + +func (c *Client) WatchBatteryLevel() (<-chan uint8, error) { + c.battLevelCh = make(chan uint8, 2) + err := c.requestNoRes(types.Request{ + Type: types.ReqTypeBattLevel, + }) + if err != nil { + return nil, err + } + return c.battLevelCh, nil +} + +func (c *Client) HeartRate() (uint8, error) { + res, err := c.request(types.Request{ + Type: types.ReqTypeHeartRate, + }) + if err != nil { + return 0, err + } + + return uint8(res.Value.(float64)), nil +} + +func (c *Client) WatchHeartRate() (<-chan uint8, error) { + c.heartRateCh = make(chan uint8, 2) + err := c.requestNoRes(types.Request{ + Type: types.ReqTypeWatchHeartRate, + }) + if err != nil { + return nil, err + } + return c.heartRateCh, nil +} + +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 +} + +func (c *Client) WatchStepCount() (<-chan uint32, error) { + c.stepCountCh = make(chan uint32, 2) + err := c.requestNoRes(types.Request{ + Type: types.ReqTypeWatchStepCount, + }) + if err != nil { + return nil, err + } + return c.stepCountCh, nil +} + +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 +} + +func (c *Client) WatchMotion() (<-chan infinitime.MotionValues, error) { + c.motionCh = make(chan infinitime.MotionValues, 2) + err := c.requestNoRes(types.Request{ + Type: types.ReqTypeWatchMotion, + }) + if err != nil { + return nil, err + } + return c.motionCh, nil +} diff --git a/api/time.go b/api/time.go new file mode 100644 index 0000000..5230008 --- /dev/null +++ b/api/time.go @@ -0,0 +1,29 @@ +package api + +import ( + "time" + + "go.arsenm.dev/itd/internal/types" +) + +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 +} + +func (c *Client) SetTimeNow() error { + _, err := c.request(types.Request{ + Type: types.ReqTypeSetTime, + Data: "now", + }) + if err != nil { + return err + } + return nil +} diff --git a/api/upgrade.go b/api/upgrade.go new file mode 100644 index 0000000..f074072 --- /dev/null +++ b/api/upgrade.go @@ -0,0 +1,33 @@ +package api + +import ( + "encoding/json" + + "go.arsenm.dev/itd/internal/types" +) + +type DFUProgress types.DFUProgress + +type UpgradeType uint8 + +const ( + UpgradeTypeArchive UpgradeType = iota + UpgradeTypeFiles +) + +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 DFUProgress, 5) + + return c.dfuProgressCh, nil +} diff --git a/internal/types/types.go b/internal/types/types.go index 1ceec8b..b9ccfcf 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -16,6 +16,22 @@ const ( ReqTypeWatchStepCount ) +const ( + ResTypeHeartRate = iota + ResTypeBattLevel + ResTypeFwVersion + ResTypeDFUProgress + ResTypeBtAddress + ResTypeNotify + ResTypeSetTime + ResTypeWatchHeartRate + ResTypeWatchBattLevel + ResTypeMotion + ResTypeWatchMotion + ResTypeStepCount + ResTypeWatchStepCount +) + const ( UpgradeTypeArchive = iota UpgradeTypeFiles @@ -27,6 +43,7 @@ type ReqDataFwUpgrade struct { } type Response struct { + Type int Value interface{} `json:"value,omitempty"` Message string `json:"msg,omitempty"` Error bool `json:"error"` diff --git a/socket.go b/socket.go index 92fef74..ea80f2b 100644 --- a/socket.go +++ b/socket.go @@ -102,6 +102,7 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) { } // Encode heart rate to connection json.NewEncoder(conn).Encode(types.Response{ + Type: types.ResTypeHeartRate, Value: heartRate, }) case types.ReqTypeWatchHeartRate: @@ -113,6 +114,7 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) { go func() { for heartRate := range heartRateCh { json.NewEncoder(conn).Encode(types.Response{ + Type: types.ResTypeWatchHeartRate, Value: heartRate, }) } @@ -126,17 +128,19 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) { } // Encode battery level to connection json.NewEncoder(conn).Encode(types.Response{ + Type: types.ResTypeBattLevel, Value: battLevel, }) case types.ReqTypeWatchBattLevel: battLevelCh, err := dev.WatchBatteryLevel() if err != nil { - connErr(conn, err, "Error getting heart rate channel") + connErr(conn, err, "Error getting battery level channel") break } go func() { for battLevel := range battLevelCh { json.NewEncoder(conn).Encode(types.Response{ + Type: types.ResTypeWatchBattLevel, Value: battLevel, }) } @@ -150,6 +154,7 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) { } // Encode battery level to connection json.NewEncoder(conn).Encode(types.Response{ + Type: types.ResTypeMotion, Value: motionVals, }) case types.ReqTypeWatchMotion: @@ -161,6 +166,7 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) { go func() { for motionVals := range motionValCh { json.NewEncoder(conn).Encode(types.Response{ + Type: types.ResTypeWatchMotion, Value: motionVals, }) } @@ -174,6 +180,7 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) { } // Encode battery level to connection json.NewEncoder(conn).Encode(types.Response{ + Type: types.ResTypeStepCount, Value: stepCount, }) case types.ReqTypeWatchStepCount: @@ -185,6 +192,7 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) { go func() { for stepCount := range stepCountCh { json.NewEncoder(conn).Encode(types.Response{ + Type: types.ResTypeWatchStepCount, Value: stepCount, }) } @@ -193,16 +201,18 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) { // Get firmware version from watch version, err := dev.Version() if err != nil { - connErr(conn, err, "Error getting battery level") + connErr(conn, err, "Error getting firmware version") break } // Encode version to connection json.NewEncoder(conn).Encode(types.Response{ + Type: types.ResTypeFwVersion, Value: version, }) case types.ReqTypeBtAddress: // Encode bluetooth address to connection json.NewEncoder(conn).Encode(types.Response{ + Type: types.ResTypeBtAddress, Value: dev.Address(), }) case types.ReqTypeNotify: @@ -229,7 +239,7 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) { break } // Encode empty types.Response to connection - json.NewEncoder(conn).Encode(types.Response{}) + json.NewEncoder(conn).Encode(types.Response{Type: types.ResTypeNotify}) case types.ReqTypeSetTime: // If no data, return error if req.Data == nil { @@ -261,7 +271,7 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) { break } // Encode empty types.Response to connection - json.NewEncoder(conn).Encode(types.Response{}) + json.NewEncoder(conn).Encode(types.Response{Type: types.ResTypeSetTime}) case types.ReqTypeFwUpgrade: // If no data, return error if req.Data == nil { @@ -326,6 +336,7 @@ 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, Value: event, }) }