Add call notification support

This commit is contained in:
Elara 2021-10-15 00:23:54 -07:00
parent a1e08ed862
commit c101249d3e
1 changed files with 70 additions and 4 deletions

View File

@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt"
"time" "time"
bt "github.com/muka/go-bluetooth/api" bt "github.com/muka/go-bluetooth/api"
@ -17,6 +16,7 @@ const BTName = "InfiniTime"
const ( const (
NewAlertChar = "00002a46-0000-1000-8000-00805f9b34fb" NewAlertChar = "00002a46-0000-1000-8000-00805f9b34fb"
NotifEventChar = "00020001-78fc-48fe-8e23-433b3a1942d0"
FirmwareVerChar = "00002a26-0000-1000-8000-00805f9b34fb" FirmwareVerChar = "00002a26-0000-1000-8000-00805f9b34fb"
CurrentTimeChar = "00002a2b-0000-1000-8000-00805f9b34fb" CurrentTimeChar = "00002a2b-0000-1000-8000-00805f9b34fb"
BatteryLvlChar = "00002a19-0000-1000-8000-00805f9b34fb" BatteryLvlChar = "00002a19-0000-1000-8000-00805f9b34fb"
@ -27,6 +27,7 @@ type Device struct {
opts *Options opts *Options
device *device.Device1 device *device.Device1
newAlertChar *gatt.GattCharacteristic1 newAlertChar *gatt.GattCharacteristic1
notifEventChar *gatt.GattCharacteristic1
fwVersionChar *gatt.GattCharacteristic1 fwVersionChar *gatt.GattCharacteristic1
currentTimeChar *gatt.GattCharacteristic1 currentTimeChar *gatt.GattCharacteristic1
battLevelChar *gatt.GattCharacteristic1 battLevelChar *gatt.GattCharacteristic1
@ -41,10 +42,13 @@ var ErrNotFound = errors.New("could not find any advertising InfiniTime devices"
type Options struct { type Options struct {
AttemptReconnect bool AttemptReconnect bool
WhitelistEnabled bool
Whitelist []string
} }
var DefaultOptions = &Options{ var DefaultOptions = &Options{
AttemptReconnect: true, AttemptReconnect: true,
WhitelistEnabled: false,
} }
// Connect will attempt to connect to a // Connect will attempt to connect to a
@ -58,7 +62,7 @@ func Connect(opts *Options) (*Device, error) {
opts = DefaultOptions opts = DefaultOptions
} }
// Attempt to connect to paired device by name // Attempt to connect to paired device by name
dev, err := connectByName() dev, err := connectByName(opts)
// If such device does not exist // If such device does not exist
if errors.Is(err, ErrNoDevices) { if errors.Is(err, ErrNoDevices) {
// Attempt to pair device // Attempt to pair device
@ -128,7 +132,7 @@ func (i *Device) OnReconnect(f func()) {
} }
// Connect connects to a paired InfiniTime device // Connect connects to a paired InfiniTime device
func connectByName() (*Device, error) { func connectByName(opts *Options) (*Device, error) {
// Create new device // Create new device
out := &Device{} out := &Device{}
// Get devices from default adapter // Get devices from default adapter
@ -140,6 +144,9 @@ func connectByName() (*Device, error) {
for _, dev := range devs { for _, dev := range devs {
// If device name is InfiniTime // If device name is InfiniTime
if dev.Properties.Name == BTName { if dev.Properties.Name == BTName {
if opts.WhitelistEnabled && !contains(opts.Whitelist, dev.Properties.Address) {
continue
}
// Set outout device to discovered device // Set outout device to discovered device
out.device = dev out.device = dev
break break
@ -161,6 +168,15 @@ func connectByName() (*Device, error) {
return out, nil 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 // Pair attempts to discover and pair an InfiniTime device
func pair() (*Device, error) { func pair() (*Device, error) {
// Create new device // Create new device
@ -259,6 +275,8 @@ func (i *Device) resolveChars() error {
switch char.Properties.UUID { switch char.Properties.UUID {
case NewAlertChar: case NewAlertChar:
i.newAlertChar = char i.newAlertChar = char
case NotifEventChar:
i.notifEventChar = char
case FirmwareVerChar: case FirmwareVerChar:
i.fwVersionChar = char i.fwVersionChar = char
case CurrentTimeChar: case CurrentTimeChar:
@ -414,7 +432,55 @@ func (i *Device) Notify(title, body string) error {
return nil return nil
} }
return i.newAlertChar.WriteValue( return i.newAlertChar.WriteValue(
[]byte(fmt.Sprintf("00\x00%s\x00%s", title, body)), append([]byte{0x00, 0x01, 0x00}, []byte(title+"\x00"+body)...),
nil, 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
}