forked from Elara6331/itd
		
	Switch from custom socket API to rpcx
This commit is contained in:
		
							
								
								
									
										117
									
								
								api/api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								api/api.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | ||||
| package api | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
|  | ||||
| 	"github.com/smallnest/rpcx/client" | ||||
| 	"github.com/smallnest/rpcx/protocol" | ||||
| 	"github.com/vmihailenco/msgpack/v5" | ||||
| 	"go.arsenm.dev/infinitime" | ||||
| ) | ||||
|  | ||||
| const DefaultAddr = "/tmp/itd/socket" | ||||
|  | ||||
| type Client struct { | ||||
| 	itdClient client.XClient | ||||
| 	itdCh     chan *protocol.Message | ||||
| 	fsClient  client.XClient | ||||
| 	fsCh      chan *protocol.Message | ||||
| 	srvVals   map[string]chan interface{} | ||||
| } | ||||
|  | ||||
| func New(sockPath string) (*Client, error) { | ||||
| 	d, err := client.NewPeer2PeerDiscovery("unix@"+sockPath, "") | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	out := &Client{} | ||||
|  | ||||
| 	out.itdCh = make(chan *protocol.Message, 5) | ||||
| 	out.itdClient = client.NewBidirectionalXClient( | ||||
| 		"ITD", | ||||
| 		client.Failtry, | ||||
| 		client.RandomSelect, | ||||
| 		d, | ||||
| 		client.DefaultOption, | ||||
| 		out.itdCh, | ||||
| 	) | ||||
|  | ||||
| 	out.fsCh = make(chan *protocol.Message, 5) | ||||
| 	out.fsClient = client.NewBidirectionalXClient( | ||||
| 		"FS", | ||||
| 		client.Failtry, | ||||
| 		client.RandomSelect, | ||||
| 		d, | ||||
| 		client.DefaultOption, | ||||
| 		out.fsCh, | ||||
| 	) | ||||
|  | ||||
| 	out.srvVals = map[string]chan interface{}{} | ||||
|  | ||||
| 	go out.handleMessages(out.itdCh) | ||||
| 	go out.handleMessages(out.fsCh) | ||||
|  | ||||
| 	return out, nil | ||||
| } | ||||
|  | ||||
| func (c *Client) handleMessages(msgCh chan *protocol.Message) { | ||||
| 	for msg := range msgCh { | ||||
| 		_, ok := c.srvVals[msg.ServicePath] | ||||
| 		if !ok { | ||||
| 			c.srvVals[msg.ServicePath] = make(chan interface{}, 5) | ||||
| 		} | ||||
|  | ||||
| 		//fmt.Printf("%+v\n", msg) | ||||
|  | ||||
| 		ch := c.srvVals[msg.ServicePath] | ||||
|  | ||||
| 		switch msg.ServiceMethod { | ||||
| 		case "FSProgress": | ||||
| 			var progress FSTransferProgress | ||||
| 			msgpack.Unmarshal(msg.Payload, &progress) | ||||
| 			ch <- progress | ||||
| 		case "DFUProgress": | ||||
| 			var progress infinitime.DFUProgress | ||||
| 			msgpack.Unmarshal(msg.Payload, &progress) | ||||
| 			ch <- progress | ||||
| 		case "MotionSample": | ||||
| 			var motionVals infinitime.MotionValues | ||||
| 			msgpack.Unmarshal(msg.Payload, &motionVals) | ||||
| 			ch <- motionVals | ||||
| 		case "Done": | ||||
| 			close(c.srvVals[msg.ServicePath]) | ||||
| 			delete(c.srvVals, msg.ServicePath) | ||||
| 		default: | ||||
| 			var value interface{} | ||||
| 			msgpack.Unmarshal(msg.Payload, &value) | ||||
| 			ch <- value | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) done(id string) error { | ||||
| 	return c.itdClient.Call( | ||||
| 		context.Background(), | ||||
| 		"Done", | ||||
| 		id, | ||||
| 		nil, | ||||
| 	) | ||||
| } | ||||
|  | ||||
| func (c *Client) Close() error { | ||||
| 	err := c.itdClient.Close() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	err = c.fsClient.Close() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	close(c.itdCh) | ||||
| 	close(c.fsCh) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										153
									
								
								api/client.go
									
									
									
									
									
								
							
							
						
						
									
										153
									
								
								api/client.go
									
									
									
									
									
								
							| @@ -1,153 +0,0 @@ | ||||
| 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 | ||||
| 	readProgressCh  chan types.FSTransferProgress | ||||
| 	writeProgressCh chan types.FSTransferProgress | ||||
| } | ||||
|  | ||||
| // 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 | ||||
| 	case types.ReqTypeFS: | ||||
| 		if res.Value == nil { | ||||
| 			c.respCh <- res | ||||
| 			break | ||||
| 		} | ||||
| 		var progress types.FSTransferProgress | ||||
| 		if err := mapstructure.Decode(res.Value, &progress); err != nil { | ||||
| 			c.respCh <- res | ||||
| 			break | ||||
| 		} | ||||
| 		switch progress.Type { | ||||
| 		case types.FSTypeRead: | ||||
| 			c.readProgressCh <- progress | ||||
| 		case types.FSTypeWrite: | ||||
| 			c.writeProgressCh <- progress | ||||
| 		default: | ||||
| 			c.respCh <- 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 | ||||
| } | ||||
							
								
								
									
										40
									
								
								api/firmware.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								api/firmware.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| package api | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"time" | ||||
|  | ||||
| 	"go.arsenm.dev/infinitime" | ||||
| ) | ||||
|  | ||||
| func (c *Client) FirmwareUpgrade(upgType UpgradeType, files ...string) (chan infinitime.DFUProgress, error) { | ||||
| 	var id string | ||||
| 	err := c.itdClient.Call( | ||||
| 		context.Background(), | ||||
| 		"FirmwareUpgrade", | ||||
| 		FwUpgradeData{ | ||||
| 			Type:  upgType, | ||||
| 			Files: files, | ||||
| 		}, | ||||
| 		&id, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	progressCh := make(chan infinitime.DFUProgress, 5) | ||||
| 	go func() { | ||||
| 		srvValCh, ok := c.srvVals[id] | ||||
| 		for !ok { | ||||
| 			time.Sleep(100 * time.Millisecond) | ||||
| 			srvValCh, ok = c.srvVals[id] | ||||
| 		} | ||||
|  | ||||
| 		for val := range srvValCh { | ||||
| 			progressCh <- val.(infinitime.DFUProgress) | ||||
| 		} | ||||
| 		close(progressCh) | ||||
| 	}() | ||||
|  | ||||
| 	return progressCh, nil | ||||
| } | ||||
							
								
								
									
										153
									
								
								api/fs.go
									
									
									
									
									
								
							
							
						
						
									
										153
									
								
								api/fs.go
									
									
									
									
									
								
							| @@ -1,102 +1,101 @@ | ||||
| package api | ||||
|  | ||||
| import ( | ||||
| 	"github.com/mitchellh/mapstructure" | ||||
| 	"go.arsenm.dev/itd/internal/types" | ||||
| 	"context" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| func (c *Client) Rename(old, new string) error { | ||||
| 	_, err := c.request(types.Request{ | ||||
| 		Type: types.ReqTypeFS, | ||||
| 		Data: types.ReqDataFS{ | ||||
| 			Type:  types.FSTypeMove, | ||||
| 			Files: []string{old, new}, | ||||
| 		}, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| func (c *Client) Remove(paths ...string) error { | ||||
| 	return c.fsClient.Call( | ||||
| 		context.Background(), | ||||
| 		"Remove", | ||||
| 		paths, | ||||
| 		nil, | ||||
| 	) | ||||
| } | ||||
|  | ||||
| func (c *Client) Remove(paths ...string) error { | ||||
| 	_, err := c.request(types.Request{ | ||||
| 		Type: types.ReqTypeFS, | ||||
| 		Data: types.ReqDataFS{ | ||||
| 			Type:  types.FSTypeDelete, | ||||
| 			Files: paths, | ||||
| 		}, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| func (c *Client) Rename(old, new string) error { | ||||
| 	return c.fsClient.Call( | ||||
| 		context.Background(), | ||||
| 		"Remove", | ||||
| 		[2]string{old, new}, | ||||
| 		nil, | ||||
| 	) | ||||
| } | ||||
|  | ||||
| func (c *Client) Mkdir(paths ...string) error { | ||||
| 	_, err := c.request(types.Request{ | ||||
| 		Type: types.ReqTypeFS, | ||||
| 		Data: types.ReqDataFS{ | ||||
| 			Type:  types.FSTypeMkdir, | ||||
| 			Files: paths, | ||||
| 		}, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| 	return c.fsClient.Call( | ||||
| 		context.Background(), | ||||
| 		"Mkdir", | ||||
| 		paths, | ||||
| 		nil, | ||||
| 	) | ||||
| } | ||||
|  | ||||
| func (c *Client) ReadDir(path string) ([]types.FileInfo, error) { | ||||
| 	res, err := c.request(types.Request{ | ||||
| 		Type: types.ReqTypeFS, | ||||
| 		Data: types.ReqDataFS{ | ||||
| 			Type:  types.FSTypeList, | ||||
| 			Files: []string{path}, | ||||
| 		}, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	var out []types.FileInfo | ||||
| 	err = mapstructure.Decode(res.Value, &out) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return out, nil | ||||
| func (c *Client) ReadDir(dir string) (out []FileInfo, err error) { | ||||
| 	err = c.fsClient.Call( | ||||
| 		context.Background(), | ||||
| 		"ReadDir", | ||||
| 		dir, | ||||
| 		&out, | ||||
| 	) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (c *Client) ReadFile(localPath, remotePath string) (<-chan types.FSTransferProgress, error) { | ||||
| 	c.readProgressCh = make(chan types.FSTransferProgress, 5) | ||||
|  | ||||
| 	_, err := c.request(types.Request{ | ||||
| 		Type: types.ReqTypeFS, | ||||
| 		Data: types.ReqDataFS{ | ||||
| 			Type:  types.FSTypeRead, | ||||
| 			Files: []string{localPath, remotePath}, | ||||
| 		}, | ||||
| 	}) | ||||
|  | ||||
| func (c *Client) Upload(dst, src string) (chan FSTransferProgress, error) { | ||||
| 	var id string | ||||
| 	err := c.fsClient.Call( | ||||
| 		context.Background(), | ||||
| 		"Upload", | ||||
| 		[2]string{dst, src}, | ||||
| 		&id, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return c.readProgressCh, nil | ||||
| 	progressCh := make(chan FSTransferProgress, 5) | ||||
| 	go func() { | ||||
| 		srvValCh, ok := c.srvVals[id] | ||||
| 		for !ok { | ||||
| 			time.Sleep(100 * time.Millisecond) | ||||
| 			srvValCh, ok = c.srvVals[id] | ||||
| 		} | ||||
|  | ||||
| 		for val := range srvValCh { | ||||
| 			progressCh <- val.(FSTransferProgress) | ||||
| 		} | ||||
| 		close(progressCh) | ||||
| 	}() | ||||
|  | ||||
| 	return progressCh, nil | ||||
| } | ||||
|  | ||||
| func (c *Client) WriteFile(localPath, remotePath string) (<-chan types.FSTransferProgress, error) { | ||||
| 	c.writeProgressCh = make(chan types.FSTransferProgress, 5) | ||||
|  | ||||
| 	_, err := c.request(types.Request{ | ||||
| 		Type: types.ReqTypeFS, | ||||
| 		Data: types.ReqDataFS{ | ||||
| 			Type:  types.FSTypeWrite, | ||||
| 			Files: []string{remotePath, localPath}, | ||||
| 		}, | ||||
| 	}) | ||||
| func (c *Client) Download(dst, src string) (chan FSTransferProgress, error) { | ||||
| 	var id string | ||||
| 	err := c.fsClient.Call( | ||||
| 		context.Background(), | ||||
| 		"Download", | ||||
| 		[2]string{dst, src}, | ||||
| 		&id, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return c.writeProgressCh, nil | ||||
| 	progressCh := make(chan FSTransferProgress, 5) | ||||
| 	go func() { | ||||
| 		srvValCh, ok := c.srvVals[id] | ||||
| 		for !ok { | ||||
| 			time.Sleep(100 * time.Millisecond) | ||||
| 			srvValCh, ok = c.srvVals[id] | ||||
| 		} | ||||
|  | ||||
| 		for val := range srvValCh { | ||||
| 			progressCh <- val.(FSTransferProgress) | ||||
| 		} | ||||
| 		close(progressCh) | ||||
| 	}() | ||||
|  | ||||
| 	return progressCh, nil | ||||
| } | ||||
|   | ||||
							
								
								
									
										67
									
								
								api/get.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								api/get.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| package api | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
|  | ||||
| 	"go.arsenm.dev/infinitime" | ||||
| ) | ||||
|  | ||||
| func (c *Client) HeartRate() (out uint8, err error) { | ||||
| 	err = c.itdClient.Call( | ||||
| 		context.Background(), | ||||
| 		"HeartRate", | ||||
| 		nil, | ||||
| 		&out, | ||||
| 	) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (c *Client) BatteryLevel() (out uint8, err error) { | ||||
| 	err = c.itdClient.Call( | ||||
| 		context.Background(), | ||||
| 		"BatteryLevel", | ||||
| 		nil, | ||||
| 		&out, | ||||
| 	) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (c *Client) Motion() (out infinitime.MotionValues, err error) { | ||||
| 	err = c.itdClient.Call( | ||||
| 		context.Background(), | ||||
| 		"Motion", | ||||
| 		nil, | ||||
| 		&out, | ||||
| 	) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (c *Client) StepCount() (out uint32, err error) { | ||||
| 	err = c.itdClient.Call( | ||||
| 		context.Background(), | ||||
| 		"StepCount", | ||||
| 		nil, | ||||
| 		&out, | ||||
| 	) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (c *Client) Version() (out string, err error) { | ||||
| 	err = c.itdClient.Call( | ||||
| 		context.Background(), | ||||
| 		"Version", | ||||
| 		nil, | ||||
| 		&out, | ||||
| 	) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (c *Client) Address() (out string, err error) { | ||||
| 	err = c.itdClient.Call( | ||||
| 		context.Background(), | ||||
| 		"Address", | ||||
| 		nil, | ||||
| 		&out, | ||||
| 	) | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										209
									
								
								api/info.go
									
									
									
									
									
								
							
							
						
						
									
										209
									
								
								api/info.go
									
									
									
									
									
								
							| @@ -1,209 +0,0 @@ | ||||
| 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 | ||||
| } | ||||
| @@ -1,14 +1,17 @@ | ||||
| package api | ||||
|  | ||||
| import "go.arsenm.dev/itd/internal/types" | ||||
| import ( | ||||
| 	"context" | ||||
| ) | ||||
|  | ||||
| func (c *Client) Notify(title string, body string) error { | ||||
| 	_, err := c.request(types.Request{ | ||||
| 		Type: types.ReqTypeNotify, | ||||
| 		Data: types.ReqDataNotify{ | ||||
| func (c *Client) Notify(title, body string) error { | ||||
| 	return c.itdClient.Call( | ||||
| 		context.Background(), | ||||
| 		"Notify", | ||||
| 		NotifyData{ | ||||
| 			Title: title, | ||||
| 			Body: body, | ||||
| 		}, | ||||
| 	}) | ||||
| 	return err | ||||
| 		nil, | ||||
| 	) | ||||
| } | ||||
							
								
								
									
										15
									
								
								api/set.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								api/set.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| package api | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| func (c *Client) SetTime(t time.Time) error { | ||||
| 	return c.itdClient.Call( | ||||
| 		context.Background(), | ||||
| 		"SetTime", | ||||
| 		t, | ||||
| 		nil, | ||||
| 	) | ||||
| } | ||||
							
								
								
									
										33
									
								
								api/time.go
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								api/time.go
									
									
									
									
									
								
							| @@ -1,33 +0,0 @@ | ||||
| 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 | ||||
| } | ||||
							
								
								
									
										96
									
								
								api/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								api/types.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | ||||
| package api | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| ) | ||||
|  | ||||
| type UpgradeType uint8 | ||||
|  | ||||
| const ( | ||||
| 	UpgradeTypeArchive UpgradeType = iota | ||||
| 	UpgradeTypeFiles | ||||
| ) | ||||
|  | ||||
| type FSData struct { | ||||
| 	Files []string | ||||
| 	Data  string | ||||
| } | ||||
|  | ||||
| type FwUpgradeData struct { | ||||
| 	Type  UpgradeType | ||||
| 	Files []string | ||||
| } | ||||
|  | ||||
| type NotifyData struct { | ||||
| 	Title string | ||||
| 	Body  string | ||||
| } | ||||
|  | ||||
| type FSTransferProgress struct { | ||||
| 	Total uint32 | ||||
| 	Sent  uint32 | ||||
| } | ||||
|  | ||||
| type FileInfo struct { | ||||
| 	Name  string | ||||
| 	Size  int64 | ||||
| 	IsDir bool | ||||
| } | ||||
|  | ||||
| func (fi FileInfo) String() string { | ||||
| 	var isDirChar rune | ||||
| 	if fi.IsDir { | ||||
| 		isDirChar = 'd' | ||||
| 	} else { | ||||
| 		isDirChar = '-' | ||||
| 	} | ||||
|  | ||||
| 	// Get human-readable value for file size | ||||
| 	val, unit := bytesHuman(fi.Size) | ||||
| 	prec := 0 | ||||
| 	// If value is less than 10, set precision to 1 | ||||
| 	if val < 10 { | ||||
| 		prec = 1 | ||||
| 	} | ||||
| 	// Convert float to string | ||||
| 	valStr := strconv.FormatFloat(val, 'f', prec, 64) | ||||
|  | ||||
| 	// Return string formatted like so: | ||||
| 	// -  10 kB file | ||||
| 	// or: | ||||
| 	// d   0 B  . | ||||
| 	return fmt.Sprintf( | ||||
| 		"%c %3s %-2s %s", | ||||
| 		isDirChar, | ||||
| 		valStr, | ||||
| 		unit, | ||||
| 		fi.Name, | ||||
| 	) | ||||
| } | ||||
|  | ||||
| // bytesHuman returns a human-readable string for | ||||
| // the amount of bytes inputted. | ||||
| func bytesHuman(b int64) (float64, string) { | ||||
| 	const unit = 1000 | ||||
| 	// Set possible units prefixes (PineTime flash is 4MB) | ||||
| 	units := [2]rune{'k', 'M'} | ||||
| 	// If amount of bytes is less than smallest unit | ||||
| 	if b < unit { | ||||
| 		// Return unchanged with unit "B" | ||||
| 		return float64(b), "B" | ||||
| 	} | ||||
|  | ||||
| 	div, exp := int64(unit), 0 | ||||
| 	// Get decimal values and unit prefix index | ||||
| 	for n := b / unit; n >= unit; n /= unit { | ||||
| 		div *= unit | ||||
| 		exp++ | ||||
| 	} | ||||
|  | ||||
| 	// Create string for full unit | ||||
| 	unitStr := string([]rune{units[exp], 'B'}) | ||||
|  | ||||
| 	// Return decimal with unit string | ||||
| 	return float64(b) / float64(div), unitStr | ||||
| } | ||||
							
								
								
									
										12
									
								
								api/update.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								api/update.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| package api | ||||
|  | ||||
| import "context" | ||||
|  | ||||
| func (c *Client) WeatherUpdate() error { | ||||
| 	return c.itdClient.Call( | ||||
| 		context.Background(), | ||||
| 		"WeatherUpdate", | ||||
| 		nil, | ||||
| 		nil, | ||||
| 	) | ||||
| } | ||||
| @@ -1,48 +0,0 @@ | ||||
| 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 | ||||
| } | ||||
							
								
								
									
										144
									
								
								api/watch.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								api/watch.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,144 @@ | ||||
| package api | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"time" | ||||
|  | ||||
| 	"go.arsenm.dev/infinitime" | ||||
| ) | ||||
|  | ||||
| func (c *Client) WatchHeartRate() (<-chan uint8, func(), error) { | ||||
| 	var id string | ||||
| 	err := c.itdClient.Call( | ||||
| 		context.Background(), | ||||
| 		"WatchHeartRate", | ||||
| 		nil, | ||||
| 		&id, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	outCh := make(chan uint8, 2) | ||||
| 	go func() { | ||||
| 		srvValCh, ok := c.srvVals[id] | ||||
| 		for !ok { | ||||
| 			time.Sleep(100 * time.Millisecond) | ||||
| 			srvValCh, ok = c.srvVals[id] | ||||
| 		} | ||||
|  | ||||
| 		for val := range srvValCh { | ||||
| 			outCh <- val.(uint8) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	doneFn := func() { | ||||
| 		c.done(id) | ||||
| 		close(c.srvVals[id]) | ||||
| 		delete(c.srvVals, id) | ||||
| 	} | ||||
|  | ||||
| 	return outCh, doneFn, nil | ||||
| } | ||||
|  | ||||
| func (c *Client) WatchBatteryLevel() (<-chan uint8, func(), error) { | ||||
| 	var id string | ||||
| 	err := c.itdClient.Call( | ||||
| 		context.Background(), | ||||
| 		"WatchBatteryLevel", | ||||
| 		nil, | ||||
| 		&id, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	outCh := make(chan uint8, 2) | ||||
| 	go func() { | ||||
| 		srvValCh, ok := c.srvVals[id] | ||||
| 		for !ok { | ||||
| 			time.Sleep(100 * time.Millisecond) | ||||
| 			srvValCh, ok = c.srvVals[id] | ||||
| 		} | ||||
|  | ||||
| 		for val := range srvValCh { | ||||
| 			outCh <- val.(uint8) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	doneFn := func() { | ||||
| 		c.done(id) | ||||
| 		close(c.srvVals[id]) | ||||
| 		delete(c.srvVals, id) | ||||
| 	} | ||||
|  | ||||
| 	return outCh, doneFn, nil | ||||
| } | ||||
|  | ||||
| func (c *Client) WatchStepCount() (<-chan uint32, func(), error) { | ||||
| 	var id string | ||||
| 	err := c.itdClient.Call( | ||||
| 		context.Background(), | ||||
| 		"WatchStepCount", | ||||
| 		nil, | ||||
| 		&id, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	outCh := make(chan uint32, 2) | ||||
| 	go func() { | ||||
| 		srvValCh, ok := c.srvVals[id] | ||||
| 		for !ok { | ||||
| 			time.Sleep(100 * time.Millisecond) | ||||
| 			srvValCh, ok = c.srvVals[id] | ||||
| 		} | ||||
|  | ||||
| 		for val := range srvValCh { | ||||
| 			outCh <- val.(uint32) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	doneFn := func() { | ||||
| 		c.done(id) | ||||
| 		close(c.srvVals[id]) | ||||
| 		delete(c.srvVals, id) | ||||
| 	} | ||||
|  | ||||
| 	return outCh, doneFn, nil | ||||
| } | ||||
|  | ||||
| func (c *Client) WatchMotion() (<-chan infinitime.MotionValues, func(), error) { | ||||
| 	var id string | ||||
| 	err := c.itdClient.Call( | ||||
| 		context.Background(), | ||||
| 		"WatchMotion", | ||||
| 		nil, | ||||
| 		&id, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	outCh := make(chan infinitime.MotionValues, 2) | ||||
| 	go func() { | ||||
| 		srvValCh, ok := c.srvVals[id] | ||||
| 		for !ok { | ||||
| 			time.Sleep(100 * time.Millisecond) | ||||
| 			srvValCh, ok = c.srvVals[id] | ||||
| 		} | ||||
|  | ||||
| 		for val := range srvValCh { | ||||
| 			outCh <- val.(infinitime.MotionValues) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	doneFn := func() { | ||||
| 		c.done(id) | ||||
| 		close(c.srvVals[id]) | ||||
| 		delete(c.srvVals, id) | ||||
| 	} | ||||
|  | ||||
| 	return outCh, doneFn, nil | ||||
| } | ||||
| @@ -1,17 +0,0 @@ | ||||
| package api | ||||
|  | ||||
| import ( | ||||
| 	"go.arsenm.dev/itd/internal/types" | ||||
| ) | ||||
|  | ||||
| // UpdateWeather sends the update weather signal, | ||||
| // immediately sending current weather data | ||||
| func (c *Client) UpdateWeather() error { | ||||
| 	_, err := c.request(types.Request{ | ||||
| 		Type: types.ReqTypeWeatherUpdate, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
		Reference in New Issue
	
	Block a user