2021-08-20 00:41:09 +00:00
package infinitime
import (
"bytes"
2022-05-11 20:22:57 +00:00
"context"
2021-08-20 00:41:09 +00:00
"encoding/binary"
"errors"
2021-12-12 20:43:43 +00:00
"reflect"
2021-10-27 04:42:41 +00:00
"strings"
2021-08-20 00:41:09 +00:00
"time"
2021-12-12 20:43:43 +00:00
"github.com/fxamacker/cbor/v2"
2021-08-20 00:41:09 +00:00
bt "github.com/muka/go-bluetooth/api"
2022-02-21 10:46:20 +00:00
"github.com/muka/go-bluetooth/bluez"
2021-08-20 00:41:09 +00:00
"github.com/muka/go-bluetooth/bluez/profile/adapter"
"github.com/muka/go-bluetooth/bluez/profile/device"
"github.com/muka/go-bluetooth/bluez/profile/gatt"
2022-04-16 11:24:21 +00:00
"github.com/rs/zerolog"
2021-11-23 05:19:30 +00:00
"go.arsenm.dev/infinitime/blefs"
2021-08-20 00:41:09 +00:00
)
2022-04-24 02:58:00 +00:00
// This global is used to store the logger.
// log.Logger is not used as it would interfere
// with the package importing the library
var log zerolog . Logger
2021-08-20 00:41:09 +00:00
const BTName = "InfiniTime"
const (
NewAlertChar = "00002a46-0000-1000-8000-00805f9b34fb"
2021-10-15 07:23:54 +00:00
NotifEventChar = "00020001-78fc-48fe-8e23-433b3a1942d0"
2021-10-22 19:59:51 +00:00
StepCountChar = "00030001-78fc-48fe-8e23-433b3a1942d0"
MotionValChar = "00030002-78fc-48fe-8e23-433b3a1942d0"
2021-08-20 00:41:09 +00:00
FirmwareVerChar = "00002a26-0000-1000-8000-00805f9b34fb"
CurrentTimeChar = "00002a2b-0000-1000-8000-00805f9b34fb"
BatteryLvlChar = "00002a19-0000-1000-8000-00805f9b34fb"
HeartRateChar = "00002a37-0000-1000-8000-00805f9b34fb"
2021-11-23 05:19:30 +00:00
FSTransferChar = "adaf0200-4669-6c65-5472-616e73666572"
FSVersionChar = "adaf0100-4669-6c65-5472-616e73666572"
2021-12-12 20:43:43 +00:00
WeatherDataChar = "00040001-78fc-48fe-8e23-433b3a1942d0"
2022-10-21 21:47:05 +00:00
NavFlagsChar = "00010001-78fc-48fe-8e23-433b3a1942d0"
NavNarrativeChar = "00010002-78fc-48fe-8e23-433b3a1942d0"
NavManDistChar = "00010003-78fc-48fe-8e23-433b3a1942d0"
NavProgressChar = "00010004-78fc-48fe-8e23-433b3a1942d0"
2021-08-20 00:41:09 +00:00
)
2022-05-11 20:22:57 +00:00
var charNames = map [ string ] string {
NewAlertChar : "New Alert" ,
NotifEventChar : "Notification Event" ,
StepCountChar : "Step Count" ,
MotionValChar : "Motion Values" ,
FirmwareVerChar : "Firmware Version" ,
CurrentTimeChar : "Current Time" ,
BatteryLvlChar : "Battery Level" ,
HeartRateChar : "Heart Rate" ,
FSTransferChar : "Filesystem Transfer" ,
FSVersionChar : "Filesystem Version" ,
WeatherDataChar : "Weather Data" ,
2022-10-21 21:47:05 +00:00
NavFlagsChar : "Navigation Icon" ,
NavNarrativeChar : "Navigation Instruction" ,
NavManDistChar : "Navigation Distance to next event" ,
NavProgressChar : "Navigation Progress" ,
2022-05-11 20:22:57 +00:00
}
2022-10-21 22:08:29 +00:00
var NavFlagNames = [ ] string {
"arrive" , "arrive-left" , "arrive-right" , "arrive-straight" ,
"close" ,
"continue" , "continue-left" , "continue-right" , "continue-slight-left" ,
"continue-slight-right" , "continue-straight" , "continue-uturn" ,
"depart" , "depart-left" , "depart-right" , "depart-straight" ,
"end-of-road-left" , "end-of-road-right" ,
"ferry" , "flag" ,
"fork" , "fork-left" , "fork-right" , "fork-straight" ,
"fork-slight-left" , "fork-slight-right" ,
"invalid" , "invalid-left" , "invalid-right" ,
"invalid-slight-left" , "invalid-slight-right" ,
"invalid-straight" , "invalid-uturn" ,
"merge-left" , "merge-right" , "merge-slight-left" ,
"merge-slight-right" , "merge-straight" ,
"new-name-left" , "new-name-right" , "new-name-sharp-left" , "new-name-sharp-right" ,
"new-name-slight-left" , "new-name-slight-right" , "new-name-straight" ,
"notification-left" , "notification-right" ,
"notification-sharp-left" , "notification-sharp-right" ,
"notification-slight-left" , "notification-slight-right" ,
"notification-straight" ,
"off-ramp-left" , "off-ramp-right" , "off-ramp-sharp-left" , "off-ramp-sharp-right" ,
"off-ramp-slight-left" , "off-ramp-slight-right" , "off-ramp-straight" ,
"on-ramp-left" , "on-ramp-right" , "on-ramp-sharp-left" , "on-ramp-sharp-right" ,
"on-ramp-slight-left" , "on-ramp-slight-right" , "on-ramp-straight" ,
"rotary" , "rotary-left" , "rotary-right" , "rotary-sharp-left" , "rotary-sharp-right" ,
"rotary-slight-left" , "rotary-slight-right" , "rotary-straight" ,
"roundabout" , "roundabout-left" , "roundabout-right" ,
"roundabout-sharp-left" , "roundabout-sharp-right" ,
"roundabout-slight-left" , "roundabout-slight-right" , "roundabout-straight" ,
"turn-left" , "turn-right" , "turn-sharp-left" , "turn-sharp-right" ,
"turn-slight-left" , "turn-slight-right" , "turn-stright" ,
"updown" , "uturn" ,
}
2022-10-21 21:47:28 +00:00
type NavigationEvent struct {
flag string
narrative string
dist string
progress uint8
}
2021-08-20 00:41:09 +00:00
type Device struct {
device * device . Device1
2022-10-21 21:47:05 +00:00
navflagsChar * gatt . GattCharacteristic1
navnarrativeChar * gatt . GattCharacteristic1
navmandistChar * gatt . GattCharacteristic1
navprogressChar * gatt . GattCharacteristic1
2021-08-20 00:41:09 +00:00
newAlertChar * gatt . GattCharacteristic1
2021-10-15 07:23:54 +00:00
notifEventChar * gatt . GattCharacteristic1
2021-10-22 19:59:51 +00:00
stepCountChar * gatt . GattCharacteristic1
motionValChar * gatt . GattCharacteristic1
2021-08-20 00:41:09 +00:00
fwVersionChar * gatt . GattCharacteristic1
currentTimeChar * gatt . GattCharacteristic1
battLevelChar * gatt . GattCharacteristic1
heartRateChar * gatt . GattCharacteristic1
2021-11-23 05:19:30 +00:00
fsVersionChar * gatt . GattCharacteristic1
fsTransferChar * gatt . GattCharacteristic1
2021-12-12 20:43:43 +00:00
weatherDataChar * gatt . GattCharacteristic1
2021-11-25 20:39:43 +00:00
notifEventCh chan uint8
notifEventDone bool
2021-08-20 00:41:09 +00:00
Music MusicCtrl
DFU DFU
2022-10-21 21:47:28 +00:00
navigationEv NavigationEvent
2021-08-20 00:41:09 +00:00
}
2021-11-25 20:39:43 +00:00
var (
2021-12-12 20:43:43 +00:00
ErrNoDevices = errors . New ( "no InfiniTime devices found" )
ErrNotFound = errors . New ( "could not find any advertising InfiniTime devices" )
ErrNotConnected = errors . New ( "not connected" )
ErrNoTimelineHeader = errors . New ( "events must contain the timeline header" )
2021-12-17 05:30:29 +00:00
ErrPairTimeout = errors . New ( "reached timeout while pairing" )
2022-10-21 22:08:29 +00:00
ErrNavProgress = errors . New ( "progress needs to between 0 and 100" )
ErrNavInvalidFlag = errors . New ( "this flag is invalid" )
2021-11-25 20:39:43 +00:00
)
2021-08-20 00:41:09 +00:00
2022-05-11 20:22:57 +00:00
type ErrCharNotAvail struct {
uuid string
}
func ( e ErrCharNotAvail ) Error ( ) string {
return "characteristic " + e . uuid + " (" + charNames [ e . uuid ] + ") not available"
}
2021-08-20 00:41:09 +00:00
type Options struct {
AttemptReconnect bool
2021-10-15 07:23:54 +00:00
WhitelistEnabled bool
Whitelist [ ] string
2021-12-17 05:30:29 +00:00
OnReqPasskey func ( ) ( uint32 , error )
2022-02-21 10:46:20 +00:00
OnReconnect func ( )
2022-04-16 11:24:21 +00:00
Logger zerolog . Logger
LogLevel zerolog . Level
2021-08-20 00:41:09 +00:00
}
var DefaultOptions = & Options {
AttemptReconnect : true ,
2021-10-15 07:23:54 +00:00
WhitelistEnabled : false ,
2022-04-16 11:24:21 +00:00
Logger : zerolog . Nop ( ) ,
LogLevel : zerolog . Disabled ,
2021-08-20 00:41:09 +00:00
}
// Connect will attempt to connect to a
// paired InfiniTime device. If none are paired,
// it will attempt to discover and pair one.
//
// It will also attempt to reconnect to the device
2022-02-21 10:46:20 +00:00
// if it disconnects and that is enabled in the options.
2022-05-11 20:22:57 +00:00
func Connect ( ctx context . Context , opts * Options ) ( * Device , error ) {
2021-08-20 00:41:09 +00:00
if opts == nil {
opts = DefaultOptions
}
2022-02-21 10:46:20 +00:00
2022-04-24 02:58:00 +00:00
log = opts . Logger . Level ( opts . LogLevel )
2022-04-24 02:07:41 +00:00
2022-02-21 10:46:20 +00:00
// Set passkey request callback
2021-12-17 05:30:29 +00:00
setOnPasskeyReq ( opts . OnReqPasskey )
2022-02-21 10:46:20 +00:00
// Connect to bluetooth device
2022-05-11 20:22:57 +00:00
btDev , err := connect ( ctx , opts , true )
2021-08-20 00:41:09 +00:00
if err != nil {
return nil , err
}
2022-02-21 10:46:20 +00:00
// Create new device
out := & Device { device : btDev }
// Resolve characteristics
err = out . resolveChars ( )
if err != nil {
return nil , err
2021-08-20 00:41:09 +00:00
}
2022-02-21 10:46:20 +00:00
return out , nil
2021-08-20 00:41:09 +00:00
}
2022-02-21 10:46:20 +00:00
// connect connects to the InfiniTime bluez device
2022-05-11 20:22:57 +00:00
func connect ( ctx context . Context , opts * Options , first bool ) ( dev * device . Device1 , err error ) {
2022-02-21 10:46:20 +00:00
// Get devices
2021-08-20 00:41:09 +00:00
devs , err := defaultAdapter . GetDevices ( )
if err != nil {
return nil , err
}
2022-02-21 10:46:20 +00:00
2021-08-20 00:41:09 +00:00
// For every device
2022-02-21 10:46:20 +00:00
for _ , listDev := range devs {
// If device name does not match, skip
if listDev . Properties . Name != BTName {
continue
}
// If whitelist enabled and doesn't contain
// device, skip
if opts . WhitelistEnabled &&
! contains ( opts . Whitelist , listDev . Properties . Address ) {
2022-04-24 02:58:00 +00:00
log . Debug ( ) .
Str ( "mac" , listDev . Properties . Address ) .
Msg ( "InfiniTime device skipped as it is not in whitelist" )
2022-02-21 10:46:20 +00:00
continue
}
// Set device
dev = listDev
2022-04-24 02:58:00 +00:00
log . Debug ( ) .
Str ( "mac" , dev . Properties . Address ) .
Msg ( "InfiniTime device found in list" )
2022-02-21 10:46:20 +00:00
break
}
// If device not set
if dev == nil {
2022-04-24 02:58:00 +00:00
log . Debug ( ) . Msg ( "No device found in list, attempting to discover" )
2022-02-21 10:46:20 +00:00
// Discover devices on adapter
discoverCh , cancel , err := bt . Discover ( defaultAdapter , & adapter . DiscoveryFilter { Transport : "le" } )
if err != nil {
return nil , err
}
2022-05-11 20:22:57 +00:00
discoverLoop :
for {
select {
case event := <- discoverCh :
// If event type is not device added, skip
if event . Type != adapter . DeviceAdded {
continue
}
2022-02-21 10:46:20 +00:00
2022-05-11 20:22:57 +00:00
// Create new device from event path
discovered , err := device . NewDevice1 ( event . Path )
if err != nil {
return nil , err
}
2022-02-21 10:46:20 +00:00
2022-05-11 20:22:57 +00:00
// If device name does not match, skip
if discovered . Properties . Name != BTName {
continue
}
// If whitelist enabled and doesn't contain
// device, skip
if opts . WhitelistEnabled &&
! contains ( opts . Whitelist , discovered . Properties . Address ) {
log . Debug ( ) .
Str ( "mac" , discovered . Properties . Address ) .
Msg ( "Discovered InfiniTime device skipped as it is not in whitelist" )
continue
}
2022-02-21 10:46:20 +00:00
2022-05-11 20:22:57 +00:00
// Set device
dev = discovered
2022-04-24 02:58:00 +00:00
2022-05-11 20:22:57 +00:00
log . Debug ( ) .
Str ( "mac" , dev . Properties . Address ) .
Msg ( "InfiniTime device discovered" )
break discoverLoop
case <- ctx . Done ( ) :
break discoverLoop
}
2021-08-20 00:41:09 +00:00
}
2022-05-11 20:22:57 +00:00
// Cancel discovery
2022-02-21 10:46:20 +00:00
cancel ( )
2021-08-20 00:41:09 +00:00
}
2022-02-21 10:46:20 +00:00
// If device is still not set, return error
if dev == nil {
2021-08-20 00:41:09 +00:00
return nil , ErrNoDevices
}
2022-02-21 10:46:20 +00:00
// Create variable to track if reconnect
// was required
reconnRequired := false
// If device is not connected
if ! dev . Properties . Connected {
2022-04-24 02:58:00 +00:00
log . Debug ( ) . Msg ( "Device not connected, connecting" )
2022-02-21 10:46:20 +00:00
// Connect to device
err = dev . Connect ( )
if err != nil {
return nil , err
}
// Set reconnect required to true
reconnRequired = true
2021-08-22 03:25:09 +00:00
}
2021-11-26 04:33:06 +00:00
2022-02-21 10:46:20 +00:00
// If device is not paired
if ! dev . Properties . Paired {
2022-04-24 02:58:00 +00:00
log . Debug ( ) . Msg ( "Device not paired, pairing" )
2022-02-21 10:46:20 +00:00
// Pair device
err = dev . Pair ( )
if err != nil {
return nil , err
}
}
2021-11-26 04:33:06 +00:00
2022-02-21 10:46:20 +00:00
// If this is the first connection and reconnect
// is enabled, start reconnect goroutine
if first && opts . AttemptReconnect {
2022-05-11 20:22:57 +00:00
go reconnect ( ctx , opts , dev )
2021-08-20 00:41:09 +00:00
}
2022-02-21 10:46:20 +00:00
// If this is not the first connection, a reonnect
// was required, and the OnReconnect callback exists,
// run it
if ! first && reconnRequired && opts . OnReconnect != nil {
2022-04-24 02:58:00 +00:00
log . Debug ( ) . Msg ( "Reconnected to device, running OnReconnect callback" )
2022-02-21 10:46:20 +00:00
opts . OnReconnect ( )
2021-10-15 07:23:54 +00:00
}
2022-02-21 10:46:20 +00:00
return dev , nil
2021-10-15 07:23:54 +00:00
}
2022-02-21 10:46:20 +00:00
// reconnect reconnects to a device if it disconnects
2022-05-11 20:22:57 +00:00
func reconnect ( ctx context . Context , opts * Options , dev * device . Device1 ) {
2022-02-21 10:46:20 +00:00
// Watch device properties
propCh := watchProps ( dev )
// Create variables to store time of last disconnect
// and amount of diconnects
lastDisconnect := time . Unix ( 0 , 0 )
amtDisconnects := 0
for event := range propCh {
// If event name is not Connected and value is not false, skip
if event . Name != "Connected" && event . Value != false {
2021-08-22 20:12:16 +00:00
continue
}
2022-02-21 10:46:20 +00:00
// Store seconds since last disconnect
secsSince := time . Since ( lastDisconnect ) . Seconds ( )
// If over 3 seconds have passed, reset disconnect count
if secsSince > 3 {
amtDisconnects = 0
2021-08-22 20:12:16 +00:00
}
2022-02-21 10:46:20 +00:00
2022-04-16 11:24:21 +00:00
// If less than 3 seconds have passed and more than 6
2022-02-21 10:46:20 +00:00
// disconnects have occurred, remove the device and reset
if secsSince <= 3 && amtDisconnects >= 6 {
2022-04-16 11:24:21 +00:00
opts . Logger . Warn ( ) . Msg ( "At least 6 disconnects have occurred in the last three seconds. If this continues, try removing the InfiniTime device from bluetooth." )
2022-02-21 10:46:20 +00:00
lastDisconnect = time . Unix ( 0 , 0 )
amtDisconnects = 0
2021-08-20 00:41:09 +00:00
}
2022-02-21 10:46:20 +00:00
// Set disconnect variables
lastDisconnect = time . Now ( )
amtDisconnects ++
2021-08-20 00:41:09 +00:00
2022-02-21 10:46:20 +00:00
for i := 0 ; i < 6 ; i ++ {
// If three tries failed, remove device
if i == 3 {
2022-04-16 11:24:21 +00:00
opts . Logger . Warn ( ) . Msg ( "Multiple connection attempts have failed. If this continues, try removing the InfiniTime device from bluetooth." )
2022-02-21 10:46:20 +00:00
}
// Connect to device
2022-05-11 20:22:57 +00:00
newDev , err := connect ( ctx , opts , false )
2022-02-21 10:46:20 +00:00
if err != nil {
time . Sleep ( time . Second )
continue
}
// Replace device with new device
* dev = * newDev
2021-08-20 00:41:09 +00:00
2022-02-21 10:46:20 +00:00
break
}
2021-12-17 05:30:29 +00:00
}
2022-02-21 10:46:20 +00:00
}
2021-11-26 04:33:06 +00:00
2022-02-21 10:46:20 +00:00
// bufferChannel writes all events on propCh to a new, buffered channel
func bufferChannel ( propCh chan * bluez . PropertyChanged ) <- chan * bluez . PropertyChanged {
out := make ( chan * bluez . PropertyChanged , 10 )
go func ( ) {
for event := range propCh {
out <- event
}
} ( )
return out
}
2021-08-20 00:41:09 +00:00
2022-02-21 10:46:20 +00:00
// watchProps returns a buffered channel for the device properties
func watchProps ( dev * device . Device1 ) <- chan * bluez . PropertyChanged {
uPropCh , err := dev . WatchProperties ( )
2021-08-20 00:41:09 +00:00
if err != nil {
2022-02-21 10:46:20 +00:00
panic ( err )
2021-08-20 00:41:09 +00:00
}
2022-02-21 10:46:20 +00:00
return bufferChannel ( uPropCh )
2021-08-20 00:41:09 +00:00
}
2021-12-17 05:30:29 +00:00
// setOnPasskeyReq sets the callback for a passkey request.
// It ensures the function will never be nil.
func setOnPasskeyReq ( onReqPasskey func ( ) ( uint32 , error ) ) {
itdAgent . ReqPasskey = onReqPasskey
if itdAgent . ReqPasskey == nil {
itdAgent . ReqPasskey = func ( ) ( uint32 , error ) {
return 0 , nil
}
2021-08-20 00:41:09 +00:00
}
2021-12-17 05:30:29 +00:00
}
2021-08-20 00:41:09 +00:00
2022-02-21 10:46:20 +00:00
// contains checks if s is contained within ss
func contains ( ss [ ] string , s string ) bool {
for _ , str := range ss {
if strings . EqualFold ( str , s ) {
return true
2021-12-17 05:30:29 +00:00
}
2021-08-20 00:41:09 +00:00
}
2022-02-21 10:46:20 +00:00
return false
2021-08-20 00:41:09 +00:00
}
// resolveChars attempts to set all required
// characteristics in an InfiniTime struct
func ( i * Device ) resolveChars ( ) error {
// Get device characteristics
chars , err := i . device . GetCharacteristics ( )
if err != nil {
return err
}
// While no characteristics found
for len ( chars ) == 0 {
// Sleep one second
time . Sleep ( time . Second )
// Attempt to retry getting characteristics
chars , err = i . device . GetCharacteristics ( )
if err != nil {
return err
}
}
// For every discovered characteristics
for _ , char := range chars {
2022-04-24 02:58:00 +00:00
charResolved := true
2021-08-20 00:41:09 +00:00
// Set correct characteristics
switch char . Properties . UUID {
2022-10-21 21:47:05 +00:00
case NavFlagsChar :
i . navflagsChar = char
case NavNarrativeChar :
i . navnarrativeChar = char
case NavManDistChar :
i . navmandistChar = char
case NavProgressChar :
i . navprogressChar = char
2021-08-20 00:41:09 +00:00
case NewAlertChar :
i . newAlertChar = char
2021-10-15 07:23:54 +00:00
case NotifEventChar :
i . notifEventChar = char
2021-10-22 19:59:51 +00:00
case StepCountChar :
i . stepCountChar = char
case MotionValChar :
i . motionValChar = char
2021-08-20 00:41:09 +00:00
case FirmwareVerChar :
i . fwVersionChar = char
case CurrentTimeChar :
i . currentTimeChar = char
case BatteryLvlChar :
i . battLevelChar = char
case HeartRateChar :
i . heartRateChar = char
case MusicEventChar :
i . Music . eventChar = char
case MusicStatusChar :
i . Music . statusChar = char
case MusicArtistChar :
i . Music . artistChar = char
case MusicTrackChar :
i . Music . trackChar = char
case MusicAlbumChar :
i . Music . albumChar = char
case DFUCtrlPointChar :
i . DFU . ctrlPointChar = char
case DFUPacketChar :
i . DFU . packetChar = char
2021-11-23 05:19:30 +00:00
case FSTransferChar :
i . fsTransferChar = char
case FSVersionChar :
i . fsVersionChar = char
2021-12-12 20:43:43 +00:00
case WeatherDataChar :
i . weatherDataChar = char
2022-04-24 02:58:00 +00:00
default :
charResolved = false
}
if charResolved {
log . Debug ( ) .
Str ( "uuid" , char . Properties . UUID ) .
2022-05-11 20:22:57 +00:00
Str ( "name" , charNames [ char . Properties . UUID ] ) .
2022-04-24 02:58:00 +00:00
Msg ( "Resolved characteristic" )
2021-08-20 00:41:09 +00:00
}
}
return nil
}
// Address returns the InfiniTime's bluetooth address
func ( i * Device ) Address ( ) string {
return i . device . Properties . Address
}
// Version returns InfiniTime's reported firmware version string
func ( i * Device ) Version ( ) ( string , error ) {
2022-05-11 20:22:57 +00:00
if err := i . checkStatus ( i . fwVersionChar , FirmwareVerChar ) ; err != nil {
2021-11-25 20:39:43 +00:00
return "" , err
2021-08-20 00:41:09 +00:00
}
ver , err := i . fwVersionChar . ReadValue ( nil )
return string ( ver ) , err
}
// BatteryLevel gets the watch's battery level via the Battery Service
func ( i * Device ) BatteryLevel ( ) ( uint8 , error ) {
2022-05-11 20:22:57 +00:00
if err := i . checkStatus ( i . battLevelChar , BatteryLvlChar ) ; err != nil {
2021-11-25 20:39:43 +00:00
return 0 , err
2021-08-20 00:41:09 +00:00
}
battLevel , err := i . battLevelChar . ReadValue ( nil )
if err != nil {
return 0 , err
}
return uint8 ( battLevel [ 0 ] ) , nil
}
2021-10-22 19:59:51 +00:00
func ( i * Device ) StepCount ( ) ( uint32 , error ) {
2022-05-11 20:22:57 +00:00
if err := i . checkStatus ( i . stepCountChar , StepCountChar ) ; err != nil {
2021-11-25 20:39:43 +00:00
return 0 , err
2021-10-22 19:59:51 +00:00
}
stepCountData , err := i . stepCountChar . ReadValue ( nil )
if err != nil {
return 0 , err
}
return binary . LittleEndian . Uint32 ( stepCountData ) , nil
}
type MotionValues struct {
X int16
Y int16
Z int16
}
func ( i * Device ) Motion ( ) ( MotionValues , error ) {
out := MotionValues { }
2022-05-11 20:22:57 +00:00
if err := i . checkStatus ( i . motionValChar , MotionValChar ) ; err != nil {
2021-11-25 20:39:43 +00:00
return out , err
2021-10-22 19:59:51 +00:00
}
motionVals , err := i . motionValChar . ReadValue ( nil )
if err != nil {
return out , nil
}
motionValReader := bytes . NewReader ( motionVals )
err = binary . Read ( motionValReader , binary . LittleEndian , & out )
if err != nil {
return out , err
}
return out , nil
}
2021-08-20 00:41:09 +00:00
func ( i * Device ) HeartRate ( ) ( uint8 , error ) {
2022-05-11 20:22:57 +00:00
if err := i . checkStatus ( i . heartRateChar , HeartRateChar ) ; err != nil {
2021-11-25 20:39:43 +00:00
return 0 , err
2021-08-20 00:41:09 +00:00
}
heartRate , err := i . heartRateChar . ReadValue ( nil )
if err != nil {
return 0 , err
}
return uint8 ( heartRate [ 1 ] ) , nil
}
2022-05-11 20:22:57 +00:00
func ( i * Device ) WatchHeartRate ( ctx context . Context ) ( <- chan uint8 , error ) {
if err := i . checkStatus ( i . heartRateChar , HeartRateChar ) ; err != nil {
return nil , err
2021-08-20 00:41:09 +00:00
}
// Start notifications on heart rate characteristic
err := i . heartRateChar . StartNotify ( )
if err != nil {
2022-05-11 20:22:57 +00:00
return nil , err
2021-08-20 00:41:09 +00:00
}
// Watch characteristics of heart rate characteristic
ch , err := i . heartRateChar . WatchProperties ( )
if err != nil {
2022-05-11 20:22:57 +00:00
return nil , err
2021-08-20 00:41:09 +00:00
}
out := make ( chan uint8 , 2 )
2021-08-25 05:17:34 +00:00
currentHeartRate , err := i . HeartRate ( )
if err != nil {
2022-05-11 20:22:57 +00:00
return nil , err
2021-08-25 05:17:34 +00:00
}
out <- currentHeartRate
2021-08-20 00:41:09 +00:00
go func ( ) {
// For every event
2022-04-24 01:40:25 +00:00
for {
2021-10-23 04:26:33 +00:00
select {
2022-05-11 20:22:57 +00:00
case <- ctx . Done ( ) :
2022-04-24 02:58:00 +00:00
log . Debug ( ) . Str ( "func" , "WatchMotion" ) . Msg ( "Received done signal" )
2021-10-23 04:26:33 +00:00
close ( out )
i . heartRateChar . StopNotify ( )
return
2022-04-24 01:40:25 +00:00
case event := <- ch :
2021-10-23 04:26:33 +00:00
// If value changed
if event . Name == "Value" {
// Send heart rate to channel
out <- uint8 ( event . Value . ( [ ] byte ) [ 1 ] )
2022-04-24 01:40:25 +00:00
} else if event . Name == "Notifying" && ! event . Value . ( bool ) {
2022-04-24 02:58:00 +00:00
log . Debug ( ) . Str ( "func" , "WatchMotion" ) . Msg ( "Notifications stopped, restarting" )
2022-04-24 01:40:25 +00:00
i . heartRateChar . StartNotify ( )
2021-10-23 04:26:33 +00:00
}
2021-08-20 00:41:09 +00:00
}
}
} ( )
2022-05-11 20:22:57 +00:00
return out , nil
2021-08-20 00:41:09 +00:00
}
2022-05-11 20:22:57 +00:00
func ( i * Device ) WatchBatteryLevel ( ctx context . Context ) ( <- chan uint8 , error ) {
if err := i . checkStatus ( i . battLevelChar , BatteryLvlChar ) ; err != nil {
return nil , err
2021-08-25 04:55:03 +00:00
}
// Start notifications on heart rate characteristic
err := i . battLevelChar . StartNotify ( )
if err != nil {
2022-05-11 20:22:57 +00:00
return nil , err
2021-08-25 04:55:03 +00:00
}
// Watch characteristics of heart rate characteristic
ch , err := i . battLevelChar . WatchProperties ( )
if err != nil {
2022-05-11 20:22:57 +00:00
return nil , err
2021-08-25 04:55:03 +00:00
}
out := make ( chan uint8 , 2 )
2021-08-25 05:17:34 +00:00
currentBattLevel , err := i . BatteryLevel ( )
if err != nil {
2022-05-11 20:22:57 +00:00
return nil , err
2021-08-25 05:17:34 +00:00
}
out <- currentBattLevel
2021-08-25 04:55:03 +00:00
go func ( ) {
// For every event
2022-04-24 01:40:25 +00:00
for {
2021-10-23 04:26:33 +00:00
select {
2022-05-11 20:22:57 +00:00
case <- ctx . Done ( ) :
2022-04-24 02:58:00 +00:00
log . Debug ( ) . Str ( "func" , "WatchMotion" ) . Msg ( "Received done signal" )
2021-10-23 04:26:33 +00:00
close ( out )
i . battLevelChar . StopNotify ( )
return
2022-04-24 01:40:25 +00:00
case event := <- ch :
2021-10-23 04:26:33 +00:00
// If value changed
if event . Name == "Value" {
// Send heart rate to channel
out <- uint8 ( event . Value . ( [ ] byte ) [ 0 ] )
2022-04-24 01:40:25 +00:00
} else if event . Name == "Notifying" && ! event . Value . ( bool ) {
2022-04-24 02:58:00 +00:00
log . Debug ( ) . Str ( "func" , "WatchMotion" ) . Msg ( "Notifications stopped, restarting" )
2022-04-24 01:40:25 +00:00
i . battLevelChar . StartNotify ( )
2021-10-23 04:26:33 +00:00
}
2021-08-25 04:55:03 +00:00
}
}
} ( )
2022-05-11 20:22:57 +00:00
return out , nil
2021-08-25 04:55:03 +00:00
}
2022-05-11 20:22:57 +00:00
func ( i * Device ) WatchStepCount ( ctx context . Context ) ( <- chan uint32 , error ) {
if err := i . checkStatus ( i . stepCountChar , StepCountChar ) ; err != nil {
return nil , err
2021-10-22 19:59:51 +00:00
}
// Start notifications on step count characteristic
err := i . stepCountChar . StartNotify ( )
if err != nil {
2022-05-11 20:22:57 +00:00
return nil , err
2021-10-22 19:59:51 +00:00
}
// Watch properties of step count characteristic
ch , err := i . stepCountChar . WatchProperties ( )
if err != nil {
2022-05-11 20:22:57 +00:00
return nil , err
2021-10-22 19:59:51 +00:00
}
out := make ( chan uint32 , 2 )
currentStepCount , err := i . StepCount ( )
if err != nil {
2022-05-11 20:22:57 +00:00
return nil , err
2021-10-22 19:59:51 +00:00
}
out <- currentStepCount
go func ( ) {
// For every event
2022-04-24 01:40:25 +00:00
for {
2021-10-22 19:59:51 +00:00
select {
2022-05-11 20:22:57 +00:00
case <- ctx . Done ( ) :
2022-04-24 02:58:00 +00:00
log . Debug ( ) . Str ( "func" , "WatchMotion" ) . Msg ( "Received done signal" )
2021-10-22 19:59:51 +00:00
close ( out )
i . stepCountChar . StopNotify ( )
return
2022-04-24 01:40:25 +00:00
case event := <- ch :
2021-10-22 19:59:51 +00:00
// If value changed
if event . Name == "Value" {
// Send step count to channel
out <- binary . LittleEndian . Uint32 ( event . Value . ( [ ] byte ) )
2022-04-24 01:40:25 +00:00
} else if event . Name == "Notifying" && ! event . Value . ( bool ) {
2022-04-24 02:58:00 +00:00
log . Debug ( ) . Str ( "func" , "WatchMotion" ) . Msg ( "Notifications stopped, restarting" )
2022-04-24 01:40:25 +00:00
i . stepCountChar . StartNotify ( )
2021-10-22 19:59:51 +00:00
}
}
}
} ( )
2022-05-11 20:22:57 +00:00
return out , nil
2021-10-22 19:59:51 +00:00
}
2022-05-11 20:22:57 +00:00
func ( i * Device ) WatchMotion ( ctx context . Context ) ( <- chan MotionValues , error ) {
if err := i . checkStatus ( i . motionValChar , MotionValChar ) ; err != nil {
return nil , err
2021-10-22 19:59:51 +00:00
}
// Start notifications on motion characteristic
err := i . motionValChar . StartNotify ( )
if err != nil {
2022-05-11 20:22:57 +00:00
return nil , err
2021-10-22 19:59:51 +00:00
}
// Watch properties of motion characteristic
ch , err := i . motionValChar . WatchProperties ( )
if err != nil {
2022-05-11 20:22:57 +00:00
return nil , err
2021-10-22 19:59:51 +00:00
}
out := make ( chan MotionValues , 2 )
motionVals , err := i . Motion ( )
if err != nil {
2022-05-11 20:22:57 +00:00
return nil , err
2021-10-22 19:59:51 +00:00
}
out <- motionVals
go func ( ) {
// For every event
2022-04-24 01:40:25 +00:00
for {
2021-10-22 19:59:51 +00:00
select {
2022-05-11 20:22:57 +00:00
case <- ctx . Done ( ) :
2022-04-24 02:58:00 +00:00
log . Debug ( ) . Str ( "func" , "WatchMotion" ) . Msg ( "Received done signal" )
2021-10-22 19:59:51 +00:00
close ( out )
i . motionValChar . StopNotify ( )
return
2022-04-24 01:40:25 +00:00
case event := <- ch :
2021-10-22 19:59:51 +00:00
// If value changed
if event . Name == "Value" {
// Read binary into MotionValues struct
binary . Read ( bytes . NewReader ( event . Value . ( [ ] byte ) ) , binary . LittleEndian , & motionVals )
// Send step count to channel
out <- motionVals
2022-04-24 01:40:25 +00:00
} else if event . Name == "Notifying" && ! event . Value . ( bool ) {
2022-04-24 02:58:00 +00:00
log . Debug ( ) . Str ( "func" , "WatchMotion" ) . Msg ( "Notifications stopped, restarting" )
2022-04-24 01:40:25 +00:00
i . motionValChar . StartNotify ( )
2021-10-22 19:59:51 +00:00
}
}
}
} ( )
2022-05-11 20:22:57 +00:00
return out , nil
2021-10-22 19:59:51 +00:00
}
2021-08-20 00:41:09 +00:00
// SetTime sets the watch's time using the Current Time Service
func ( i * Device ) SetTime ( t time . Time ) error {
2022-05-11 20:22:57 +00:00
if err := i . checkStatus ( i . currentTimeChar , CurrentTimeChar ) ; err != nil {
2021-11-25 20:39:43 +00:00
return err
2021-08-20 00:41:09 +00:00
}
buf := & bytes . Buffer { }
binary . Write ( buf , binary . LittleEndian , uint16 ( t . Year ( ) ) )
binary . Write ( buf , binary . LittleEndian , uint8 ( t . Month ( ) ) )
binary . Write ( buf , binary . LittleEndian , uint8 ( t . Day ( ) ) )
binary . Write ( buf , binary . LittleEndian , uint8 ( t . Hour ( ) ) )
binary . Write ( buf , binary . LittleEndian , uint8 ( t . Minute ( ) ) )
binary . Write ( buf , binary . LittleEndian , uint8 ( t . Second ( ) ) )
binary . Write ( buf , binary . LittleEndian , uint8 ( t . Weekday ( ) ) )
binary . Write ( buf , binary . LittleEndian , uint8 ( ( t . Nanosecond ( ) / 1000 ) / 1e6 * 256 ) )
binary . Write ( buf , binary . LittleEndian , uint8 ( 0b0001 ) )
return i . currentTimeChar . WriteValue ( buf . Bytes ( ) , nil )
}
// Notify sends a notification to InfiniTime via
// the Alert Notification Service (ANS)
func ( i * Device ) Notify ( title , body string ) error {
2022-05-11 20:22:57 +00:00
if err := i . checkStatus ( i . newAlertChar , NewAlertChar ) ; err != nil {
2021-11-25 20:39:43 +00:00
return err
2021-08-20 00:41:09 +00:00
}
return i . newAlertChar . WriteValue (
2021-10-15 07:23:54 +00:00
append ( [ ] byte { 0x00 , 0x01 , 0x00 } , [ ] byte ( title + "\x00" + body ) ... ) ,
2021-08-20 00:41:09 +00:00
nil ,
)
}
2021-10-15 07:23:54 +00:00
2022-10-21 22:08:29 +00:00
func CheckValidNavFlag ( flag string ) error {
for _ , v := range NavFlagNames {
if v == flag {
return nil
}
}
return ErrNavInvalidFlag
}
2022-10-21 21:48:00 +00:00
// Navigation sends a NavigationEvent to the watch
func ( i * Device ) Navigation ( flag string , narrative string , dist string , progress uint8 ) error {
2022-10-21 22:08:29 +00:00
if progress > 100 {
return ErrNavProgress
}
if err := CheckValidNavFlag ( flag ) ; err != nil {
return err
}
2022-10-21 22:08:15 +00:00
if flag != i . navigationEv . flag {
log . Debug ( ) . Str ( "func" , "Navigation" ) .
Msg ( "Sending flag" )
if err := i . checkStatus ( i . navflagsChar , NavFlagsChar ) ; err != nil {
return err
}
if err := i . navflagsChar . WriteValue ( [ ] byte ( flag ) , nil ) ; err != nil {
return err
}
i . navigationEv . flag = flag
}
2022-10-21 21:48:00 +00:00
if narrative != i . navigationEv . narrative {
log . Debug ( ) . Str ( "func" , "Navigation" ) .
Msg ( "Sending narrative" )
if err := i . checkStatus ( i . navnarrativeChar , NavNarrativeChar ) ; err != nil {
return err
}
if err := i . navnarrativeChar . WriteValue ( [ ] byte ( narrative ) , nil ) ; err != nil {
return err
}
i . navigationEv . narrative = narrative
}
2022-10-21 22:08:15 +00:00
if dist != i . navigationEv . dist {
log . Debug ( ) . Str ( "func" , "Navigation" ) .
Msg ( "Sending mandist" )
if err := i . checkStatus ( i . navmandistChar , NavManDistChar ) ; err != nil {
return err
}
if err := i . navmandistChar . WriteValue ( [ ] byte ( dist ) , nil ) ; err != nil {
return err
}
i . navigationEv . dist = dist
}
if progress != i . navigationEv . progress {
log . Debug ( ) . Str ( "func" , "Navigation" ) .
Msg ( "Sending progress" )
if err := i . checkStatus ( i . navprogressChar , NavProgressChar ) ; err != nil {
return err
}
buf := & bytes . Buffer { }
binary . Write ( buf , binary . LittleEndian , progress )
if err := i . navprogressChar . WriteValue ( buf . Bytes ( ) , nil ) ; err != nil {
return err
}
i . navigationEv . progress = progress
}
2022-10-21 21:48:00 +00:00
return nil
}
2021-10-15 07:23:54 +00:00
// 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.
2021-11-25 20:39:43 +00:00
// This channel will contain the user's response to the call notification.
2021-10-15 07:23:54 +00:00
func ( i * Device ) NotifyCall ( from string ) ( <- chan uint8 , error ) {
2022-05-11 20:22:57 +00:00
if err := i . checkStatus ( i . newAlertChar , NewAlertChar ) ; err != nil {
2021-11-25 20:39:43 +00:00
return nil , err
2021-10-15 07:23:54 +00:00
}
// 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
}
2021-11-25 20:39:43 +00:00
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 {
2021-10-15 07:23:54 +00:00
// Start notifications on notification event characteristic
2021-11-25 20:39:43 +00:00
err := i . notifEventChar . StartNotify ( )
2021-10-15 07:23:54 +00:00
if err != nil {
2021-11-25 20:39:43 +00:00
return err
2021-10-15 07:23:54 +00:00
}
// Watch properties of notification event characteristic
ch , err := i . notifEventChar . WatchProperties ( )
if err != nil {
2021-11-25 20:39:43 +00:00
return err
2021-10-15 07:23:54 +00:00
}
// Create new output channel for status
2021-11-25 20:39:43 +00:00
i . notifEventCh = make ( chan uint8 , 1 )
2021-10-15 07:23:54 +00:00
go func ( ) {
// For every event
for event := range ch {
// If value changed
if event . Name == "Value" {
// Send status to channel
2021-11-25 20:39:43 +00:00
i . notifEventCh <- uint8 ( event . Value . ( [ ] byte ) [ 0 ] )
2021-10-15 07:23:54 +00:00
}
}
} ( )
2021-11-25 20:39:43 +00:00
return nil
2021-10-15 07:23:54 +00:00
}
2021-11-23 05:19:30 +00:00
// FS creates and returns a new filesystem from the device
func ( i * Device ) FS ( ) ( * blefs . FS , error ) {
2022-05-11 20:22:57 +00:00
if err := i . checkStatus ( i . fsTransferChar , FSTransferChar ) ; err != nil {
2021-11-25 20:39:43 +00:00
return nil , err
}
2021-11-23 05:19:30 +00:00
return blefs . New ( i . fsTransferChar )
}
2021-11-25 20:39:43 +00:00
2021-12-12 20:43:43 +00:00
// AddWeatherEvent adds one of the event structs from
// the weather package to the timeline. Input must be
// a struct containing TimelineHeader.
func ( i * Device ) AddWeatherEvent ( event interface { } ) error {
2022-05-11 20:22:57 +00:00
if err := i . checkStatus ( i . weatherDataChar , WeatherDataChar ) ; err != nil {
2021-12-12 20:43:43 +00:00
return err
}
// Get type of input
inputType := reflect . TypeOf ( event )
// Check if input contains TimelineHeader
_ , hdrExists := inputType . FieldByName ( "TimelineHeader" )
// If header does not exist or input is not struct
if ! hdrExists || inputType . Kind ( ) != reflect . Struct {
return ErrNoTimelineHeader
}
// Encode event as CBOR
data , err := cbor . Marshal ( event )
if err != nil {
return err
}
2022-04-24 02:58:00 +00:00
log . Debug ( ) . Interface ( "event" , event ) . Msg ( "Adding weather event" )
2021-12-12 20:43:43 +00:00
// Write data to weather data characteristic
return i . weatherDataChar . WriteValue ( data , nil )
}
2022-05-11 20:22:57 +00:00
func ( i * Device ) checkStatus ( char * gatt . GattCharacteristic1 , uuid string ) error {
2022-04-24 02:58:00 +00:00
log . Debug ( ) . Msg ( "Checking characteristic status" )
2021-12-17 05:30:29 +00:00
connected , err := i . device . GetConnected ( )
if err != nil {
return err
}
if ! connected {
2021-11-25 20:39:43 +00:00
return ErrNotConnected
}
if char == nil {
2022-04-24 02:58:00 +00:00
log . Debug ( ) . Msg ( "Characteristic not available (nil)" )
2022-05-11 20:22:57 +00:00
return ErrCharNotAvail { uuid }
2021-11-25 20:39:43 +00:00
}
2022-05-11 20:22:57 +00:00
log . Debug ( ) .
Str ( "uuid" , char . Properties . UUID ) .
Str ( "name" , charNames [ char . Properties . UUID ] ) .
Msg ( "Characteristic available" )
2021-11-25 20:39:43 +00:00
return nil
}