From c101249d3e610805bde12d236db61affcd6ab829 Mon Sep 17 00:00:00 2001 From: Arsen Musayelyan Date: Fri, 15 Oct 2021 00:23:54 -0700 Subject: [PATCH] Add call notification support --- infinitime.go | 74 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 4 deletions(-) diff --git a/infinitime.go b/infinitime.go index b6c5f56..a59afad 100644 --- a/infinitime.go +++ b/infinitime.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/binary" "errors" - "fmt" "time" bt "github.com/muka/go-bluetooth/api" @@ -17,6 +16,7 @@ const BTName = "InfiniTime" const ( NewAlertChar = "00002a46-0000-1000-8000-00805f9b34fb" + NotifEventChar = "00020001-78fc-48fe-8e23-433b3a1942d0" FirmwareVerChar = "00002a26-0000-1000-8000-00805f9b34fb" CurrentTimeChar = "00002a2b-0000-1000-8000-00805f9b34fb" BatteryLvlChar = "00002a19-0000-1000-8000-00805f9b34fb" @@ -27,6 +27,7 @@ type Device struct { opts *Options device *device.Device1 newAlertChar *gatt.GattCharacteristic1 + notifEventChar *gatt.GattCharacteristic1 fwVersionChar *gatt.GattCharacteristic1 currentTimeChar *gatt.GattCharacteristic1 battLevelChar *gatt.GattCharacteristic1 @@ -41,10 +42,13 @@ var ErrNotFound = errors.New("could not find any advertising InfiniTime devices" type Options struct { AttemptReconnect bool + WhitelistEnabled bool + Whitelist []string } var DefaultOptions = &Options{ AttemptReconnect: true, + WhitelistEnabled: false, } // Connect will attempt to connect to a @@ -58,7 +62,7 @@ func Connect(opts *Options) (*Device, error) { opts = DefaultOptions } // Attempt to connect to paired device by name - dev, err := connectByName() + dev, err := connectByName(opts) // If such device does not exist if errors.Is(err, ErrNoDevices) { // Attempt to pair device @@ -128,7 +132,7 @@ func (i *Device) OnReconnect(f func()) { } // Connect connects to a paired InfiniTime device -func connectByName() (*Device, error) { +func connectByName(opts *Options) (*Device, error) { // Create new device out := &Device{} // Get devices from default adapter @@ -140,6 +144,9 @@ func connectByName() (*Device, error) { for _, dev := range devs { // If device name is InfiniTime if dev.Properties.Name == BTName { + if opts.WhitelistEnabled && !contains(opts.Whitelist, dev.Properties.Address) { + continue + } // Set outout device to discovered device out.device = dev break @@ -161,6 +168,15 @@ func connectByName() (*Device, error) { return out, nil } +func contains(ss []string, s string) bool { + for _, str := range ss { + if str == s { + return true + } + } + return false +} + // Pair attempts to discover and pair an InfiniTime device func pair() (*Device, error) { // Create new device @@ -259,6 +275,8 @@ func (i *Device) resolveChars() error { switch char.Properties.UUID { case NewAlertChar: i.newAlertChar = char + case NotifEventChar: + i.notifEventChar = char case FirmwareVerChar: i.fwVersionChar = char case CurrentTimeChar: @@ -414,7 +432,55 @@ func (i *Device) Notify(title, body string) error { return nil } return i.newAlertChar.WriteValue( - []byte(fmt.Sprintf("00\x00%s\x00%s", title, body)), + append([]byte{0x00, 0x01, 0x00}, []byte(title+"\x00"+body)...), nil, ) } + +// These constants represent the possible call statuses selected by the user +const ( + CallStatusDeclined uint8 = iota + CallStatusAccepted + CallStatusMuted +) + +// 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. +func (i *Device) NotifyCall(from string) (<-chan uint8, error) { + if !i.device.Properties.Connected { + return make(<-chan uint8), nil + } + // Write call notification to new alert characteristic + err := i.newAlertChar.WriteValue( + append([]byte{0x03, 0x01, 0x00}, []byte(from)...), + nil, + ) + if err != nil { + return nil, err + } + // Start notifications on notification event characteristic + err = i.notifEventChar.StartNotify() + if err != nil { + return nil, err + } + // Watch properties of notification event characteristic + ch, err := i.notifEventChar.WatchProperties() + if err != nil { + return nil, err + } + // Create new output channel for status + out := 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 + } + } + }() + return out, nil +}