From 58d5036f208bf2cf4bb8bead38bc15506db1962c Mon Sep 17 00:00:00 2001 From: Arsen Musayelyan Date: Thu, 25 Nov 2021 12:39:43 -0800 Subject: [PATCH] Allow multiple call notification responses and improve error handling --- infinitime.go | 101 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 66 insertions(+), 35 deletions(-) diff --git a/infinitime.go b/infinitime.go index 63d3c96..f761d20 100644 --- a/infinitime.go +++ b/infinitime.go @@ -42,13 +42,19 @@ type Device struct { heartRateChar *gatt.GattCharacteristic1 fsVersionChar *gatt.GattCharacteristic1 fsTransferChar *gatt.GattCharacteristic1 + notifEventCh chan uint8 + notifEventDone bool onReconnect func() Music MusicCtrl DFU DFU } -var ErrNoDevices = errors.New("no InfiniTime devices found") -var ErrNotFound = errors.New("could not find any advertising InfiniTime devices") +var ( + ErrNoDevices = errors.New("no InfiniTime devices found") + ErrNotFound = errors.New("could not find any advertising InfiniTime devices") + ErrNotConnected = errors.New("not connected") + ErrCharNotAvail = errors.New("required characteristic is not available") +) type Options struct { AttemptReconnect bool @@ -332,8 +338,8 @@ func (i *Device) Address() string { // Version returns InfiniTime's reported firmware version string func (i *Device) Version() (string, error) { - if !i.device.Properties.Connected { - return "", nil + if err := i.checkStatus(i.fwVersionChar); err != nil { + return "", err } ver, err := i.fwVersionChar.ReadValue(nil) return string(ver), err @@ -341,8 +347,8 @@ func (i *Device) Version() (string, error) { // BatteryLevel gets the watch's battery level via the Battery Service func (i *Device) BatteryLevel() (uint8, error) { - if !i.device.Properties.Connected { - return 0, nil + if err := i.checkStatus(i.battLevelChar); err != nil { + return 0, err } battLevel, err := i.battLevelChar.ReadValue(nil) if err != nil { @@ -352,8 +358,8 @@ func (i *Device) BatteryLevel() (uint8, error) { } func (i *Device) StepCount() (uint32, error) { - if !i.device.Properties.Connected { - return 0, nil + if err := i.checkStatus(i.stepCountChar); err != nil { + return 0, err } stepCountData, err := i.stepCountChar.ReadValue(nil) if err != nil { @@ -370,8 +376,8 @@ type MotionValues struct { func (i *Device) Motion() (MotionValues, error) { out := MotionValues{} - if !i.device.Properties.Connected { - return out, nil + if err := i.checkStatus(i.motionValChar); err != nil { + return out, err } motionVals, err := i.motionValChar.ReadValue(nil) if err != nil { @@ -386,8 +392,8 @@ func (i *Device) Motion() (MotionValues, error) { } func (i *Device) HeartRate() (uint8, error) { - if !i.device.Properties.Connected { - return 0, nil + if err := i.checkStatus(i.heartRateChar); err != nil { + return 0, err } heartRate, err := i.heartRateChar.ReadValue(nil) if err != nil { @@ -397,8 +403,8 @@ func (i *Device) HeartRate() (uint8, error) { } func (i *Device) WatchHeartRate() (<-chan uint8, func(), error) { - if !i.device.Properties.Connected { - return make(<-chan uint8), nil, nil + if err := i.checkStatus(i.heartRateChar); err != nil { + return nil, nil, err } // Start notifications on heart rate characteristic err := i.heartRateChar.StartNotify() @@ -439,8 +445,8 @@ func (i *Device) WatchHeartRate() (<-chan uint8, func(), error) { } func (i *Device) WatchBatteryLevel() (<-chan uint8, func(), error) { - if !i.device.Properties.Connected { - return make(<-chan uint8), nil, nil + if err := i.checkStatus(i.battLevelChar); err != nil { + return nil, nil, err } // Start notifications on heart rate characteristic err := i.battLevelChar.StartNotify() @@ -481,8 +487,8 @@ func (i *Device) WatchBatteryLevel() (<-chan uint8, func(), error) { } func (i *Device) WatchStepCount() (<-chan uint32, func(), error) { - if !i.device.Properties.Connected { - return make(<-chan uint32), nil, nil + if err := i.checkStatus(i.stepCountChar); err != nil { + return nil, nil, err } // Start notifications on step count characteristic err := i.stepCountChar.StartNotify() @@ -523,8 +529,8 @@ func (i *Device) WatchStepCount() (<-chan uint32, func(), error) { } func (i *Device) WatchMotion() (<-chan MotionValues, func(), error) { - if !i.device.Properties.Connected { - return make(<-chan MotionValues), nil, nil + if err := i.checkStatus(i.motionValChar); err != nil { + return nil, nil, err } // Start notifications on motion characteristic err := i.motionValChar.StartNotify() @@ -576,8 +582,8 @@ func cancelFunc() (func(), chan struct{}) { // SetTime sets the watch's time using the Current Time Service func (i *Device) SetTime(t time.Time) error { - if !i.device.Properties.Connected { - return nil + if err := i.checkStatus(i.currentTimeChar); err != nil { + return err } buf := &bytes.Buffer{} binary.Write(buf, binary.LittleEndian, uint16(t.Year())) @@ -595,8 +601,8 @@ func (i *Device) SetTime(t time.Time) error { // Notify sends a notification to InfiniTime via // the Alert Notification Service (ANS) func (i *Device) Notify(title, body string) error { - if !i.device.Properties.Connected { - return nil + if err := i.checkStatus(i.newAlertChar); err != nil { + return err } return i.newAlertChar.WriteValue( append([]byte{0x00, 0x01, 0x00}, []byte(title+"\x00"+body)...), @@ -612,11 +618,10 @@ const ( ) // NotifyCall sends a call notification to the PineTime and returns a channel. -// This channel will contain the user's response to the call notification. It -// can only contain one value. +// This channel will contain the user's response to the call notification. func (i *Device) NotifyCall(from string) (<-chan uint8, error) { - if !i.device.Properties.Connected { - return make(<-chan uint8), nil + if err := i.checkStatus(i.newAlertChar); err != nil { + return nil, err } // Write call notification to new alert characteristic err := i.newAlertChar.WriteValue( @@ -626,33 +631,59 @@ func (i *Device) NotifyCall(from string) (<-chan uint8, error) { if err != nil { return nil, err } + + if !i.notifEventDone { + err = i.initNotifEvent() + if err != nil { + return nil, err + } + i.notifEventDone = true + } + + return i.notifEventCh, nil +} + +// initNotifEvent initializes the notification event channel +func (i *Device) initNotifEvent() error { // Start notifications on notification event characteristic - err = i.notifEventChar.StartNotify() + err := i.notifEventChar.StartNotify() if err != nil { - return nil, err + return err } // Watch properties of notification event characteristic ch, err := i.notifEventChar.WatchProperties() if err != nil { - return nil, err + return err } // Create new output channel for status - out := make(chan uint8, 1) + i.notifEventCh = make(chan uint8, 1) go func() { // For every event for event := range ch { // If value changed if event.Name == "Value" { // Send status to channel - out <- uint8(event.Value.([]byte)[0]) - return + i.notifEventCh <- uint8(event.Value.([]byte)[0]) } } }() - return out, nil + return nil } // FS creates and returns a new filesystem from the device func (i *Device) FS() (*blefs.FS, error) { + if err := i.checkStatus(i.currentTimeChar); err != nil { + return nil, err + } return blefs.New(i.fsTransferChar) } + +func (i *Device) checkStatus(char *gatt.GattCharacteristic1) error { + if !i.device.Properties.Connected { + return ErrNotConnected + } + if char == nil { + return ErrCharNotAvail + } + return nil +}