Compare commits

..

45 Commits

Author SHA1 Message Date
Elara6331 bebd1017c5 Ensure that done signals for Watch functions are handled properly and restart notifications if they stop unexpectedly 2022-04-23 18:40:25 -07:00
Elara6331 b7a50271be Log when too many disconnects occur rather than removing the device (Arsen6331/itd#10) 2022-04-16 04:24:21 -07:00
Elara6331 7026da3f6f Handle case where artists value is a single string rather than a slice. Fixes Arsen6331/itd#9. 2022-03-04 12:04:37 -08:00
Elara6331 e82981e3fc Rewrite connect/reconnect code 2022-02-21 02:46:20 -08:00
Elara6331 738e140bfb Create custom BlueZ agent 2021-12-16 21:30:29 -08:00
Elara6331 9250d26fdc Restrict to one open file of each type at a time 2021-12-13 09:56:34 -08:00
Elara6331 382b1d9556 Add file transfer progress 2021-12-12 13:25:40 -08:00
Elara6331 a2ff29ce84 Add weather service 2021-12-12 12:43:43 -08:00
Elara6331 522c10a9c0 Set connected property to true in all connecting functions 2021-11-25 20:33:06 -08:00
Elara6331 bd057815cf Fix checkStatus call in (*Device).FS() 2021-11-25 13:35:58 -08:00
Elara6331 58d5036f20 Allow multiple call notification responses and improve error handling 2021-11-25 12:39:43 -08:00
Elara6331 9966880bc8 Only allow OnChange() to be called once 2021-11-24 17:21:12 -08:00
Elara6331 e12ccb2ff5 Fix README errors 2021-11-24 16:51:42 -08:00
Elara6331 4b8b2a3b22 Remove playerctl from dependencies and add pactl providers 2021-11-24 16:50:35 -08:00
Elara6331 e9a611aec8 Buffer message channel for music 2021-11-24 16:41:56 -08:00
Elara6331 d9823bf0c8 Switch player to MPRIS interface 2021-11-24 16:31:18 -08:00
Elara6331 ec1548ec0f Remove BLE docs as they have been merged into InfiniTime 2021-11-22 21:20:01 -08:00
Elara6331 e9a92bac46 Implement BLE filesystem (experimental and will change in the future) 2021-11-22 21:19:30 -08:00
Elara6331 ec43bad466 Update go-bluetooth to fix BlueZ 5.62 compatibility 2021-11-22 01:14:16 -08:00
Elara6331 504a64da93 Upgrade go-bluetooth version 2021-11-01 11:20:10 -07:00
Elara6331 7fb1fcce68 Create Init() rather than using init() 2021-11-01 09:19:12 -07:00
Elara6331 75942bdb0c Add whitelist to pair() and make case insensitive 2021-10-26 21:42:41 -07:00
Elara6331 53aa6f8a0c Add cancel functions to battery level and heart rate 2021-10-22 21:26:33 -07:00
Elara6331 45baea1048 Implement Motion Service 2021-10-22 12:59:51 -07:00
Elara6331 1dde7f9b07 Fix issue where DFU responses are missed causing DFU to time out intermittently 2021-10-21 20:17:44 -07:00
Elara6331 d1a75f1c67 Fix Alert Notification Service Client link 2021-10-15 20:13:09 -07:00
Elara6331 937a3298c8 Fix typo 2021-10-15 20:11:34 -07:00
Elara6331 bf99350e48 Add name to Notification Event UUID 2021-10-15 20:10:59 -07:00
Elara6331 c4c0c46d71 Add docs for InfiniTime BLE 2021-10-15 19:58:57 -07:00
Elara6331 c101249d3e Add call notification support 2021-10-15 00:23:54 -07:00
Elara6331 a1e08ed862 Prevent sending of extra zero bytes during DFU 2021-10-06 17:08:21 -07:00
Elara6331 dbfe8bb8c4 Power on bluetooth as part of setup 2021-09-09 08:34:56 -07:00
Elara6331 745b4bd37c Return intial values in watch functions 2021-08-24 22:17:34 -07:00
Elara6331 0430ddcd30 Add watch function for battery level 2021-08-24 21:55:03 -07:00
Elara6331 8648afeebf Improve current music status functions 2021-08-23 10:16:03 -07:00
Elara6331 75121b709c Add functions for current status and metadata 2021-08-23 10:07:11 -07:00
Elara6331 9553844896 Remove pair timeout entirely 2021-08-22 13:12:16 -07:00
Elara6331 47293f04bc Fix bug where Connect blocked forever after 10 seconds 2021-08-22 13:09:56 -07:00
Elara6331 d228b6cf60 Fix segfault due to using wrong opts struct 2021-08-21 20:27:31 -07:00
Elara6331 91fbe718e5 Add error handling to connect call 2021-08-21 20:25:09 -07:00
Elara6331 7f19dfb354 Allow configuration of pair timeout 2021-08-21 20:24:56 -07:00
Elara6331 ea488067fb Add progress to DFU 2021-08-21 00:04:29 -07:00
Elara6331 08076e6ba2 Mention import path in README.md 2021-08-19 18:04:23 -07:00
Elara6331 3eadabe975 Add go reference badge 2021-08-19 17:59:38 -07:00
Elara6331 21078996c1 Initial Commit 2021-08-19 17:41:09 -07:00
19 changed files with 452 additions and 772 deletions
+7 -10
View File
@@ -1,40 +1,38 @@
# InfiniTime # InfiniTime
> **Warning** This library is no longer maintained. A rewrite has been merged into the ITD repo in [the infinitime subpackage](https://gitea.elara.ws/Elara6331/itd/src/branch/master/infinitime)
This is a go library for interfacing with InfiniTime firmware This is a go library for interfacing with InfiniTime firmware
over BLE on Linux. over BLE on Linux.
[![Go Reference](https://pkg.go.dev/badge/go.elara.ws/infinitime.svg)](https://pkg.go.dev/go.elara.ws/infinitime) [![Go Reference](https://pkg.go.dev/badge/go.arsenm.dev/infinitime.svg)](https://pkg.go.dev/go.arsenm.dev/infinitime)
--- ---
### Importing ### Importing
This library's import path is `go.elara.ws/infinitime`. This library's import path is `go.arsenm.dev/infinitime`.
--- ---
### Dependencies ### Dependencies
This library requires `dbus`, and `bluez` to function. These allow the library to use bluetooth, control media, control volume, etc. This library requires `dbus`, `bluez`, and `pactl` to function. These allow the library to use bluetooth, control media, control volume, etc.
#### Arch #### Arch
```shell ```shell
sudo pacman -S dbus bluez --needed sudo pacman -S dbus bluez libpulse --needed
``` ```
#### Debian/Ubuntu #### Debian/Ubuntu
```shell ```shell
sudo apt install dbus bluez sudo apt install dbus bluez pulseaudio-utils
``` ```
#### Fedora #### Fedora
```shell ```shell
sudo dnf install dbus bluez sudo dnf install dbus bluez pulseaudio-utils
``` ```
--- ---
@@ -49,10 +47,9 @@ This library currently supports the following features:
- Battery level - Battery level
- Music control - Music control
- OTA firmware upgrades - OTA firmware upgrades
- Navigation
--- ---
### Mentions ### Mentions
The DFU process used in this library was created with the help of [siglo](https://github.com/alexr4535/siglo)'s source code. Specifically, this file: [ble_dfu.py](https://github.com/alexr4535/siglo/blob/main/src/ble_dfu.py) The DFU process used in this library was created with the help of [siglo](https://github.com/alexr4535/siglo)'s source code. Specifically, this file: [ble_dfu.py](https://github.com/alexr4535/siglo/blob/main/src/ble_dfu.py)
-95
View File
@@ -1,95 +0,0 @@
package blefs
import (
"path/filepath"
"strings"
)
func (blefs *FS) RemoveAll(path string) error {
if path == "" {
return nil
}
if filepath.Clean(path) == "/" {
return ErrNoRemoveRoot
}
fi, err := blefs.Stat(path)
if err != nil {
return nil
}
if fi.IsDir() {
return blefs.removeAllChildren(path)
} else {
err = blefs.Remove(path)
var code int8
if err, ok := err.(FSError); ok {
code = err.Code
}
if err != nil && code != -2 {
return err
}
}
return nil
}
func (blefs *FS) removeAllChildren(path string) error {
list, err := blefs.ReadDir(path)
if err != nil {
return err
}
for _, entry := range list {
name := entry.Name()
if name == "." || name == ".." {
continue
}
entryPath := filepath.Join(path, name)
if entry.IsDir() {
err = blefs.removeAllChildren(entryPath)
} else {
err = blefs.Remove(entryPath)
}
var code int8
if err, ok := err.(FSError); ok {
code = err.Code
}
if err != nil && code != -2 {
return err
}
}
return nil
}
func (blefs *FS) MkdirAll(path string) error {
if path == "" || path == "/" {
return nil
}
splitPath := strings.Split(path, "/")
for i := 1; i < len(splitPath); i++ {
curPath := strings.Join(splitPath[0:i+1], "/")
err := blefs.Mkdir(curPath)
var code int8
if err, ok := err.(FSError); ok {
code = err.Code
}
if err != nil && code != -17 {
return err
}
}
return nil
}
+2 -26
View File
@@ -1,29 +1,5 @@
package blefs package blefs
import (
"io/fs"
"path/filepath"
)
// Stat does a ReadDir() and finds the current file in the output
func (blefs *FS) Stat(path string) (fs.FileInfo, error) {
// Get directory in filepath
dir := filepath.Dir(path)
// Read directory
dirEntries, err := blefs.ReadDir(dir)
if err != nil {
return nil, err
}
for _, entry := range dirEntries {
// If file name is base name of path
if entry.Name() == filepath.Base(path) {
// Return file info
return entry.Info()
}
}
return nil, ErrFileNotExists
}
// Rename moves or renames a file or directory // Rename moves or renames a file or directory
func (blefs *FS) Rename(old, new string) error { func (blefs *FS) Rename(old, new string) error {
// Create move request // Create move request
@@ -70,8 +46,8 @@ func (blefs *FS) Remove(path string) error {
// Read status byte // Read status byte
return decode(data, &status) return decode(data, &status)
}) })
if status != FSStatusOk { if status == FSStatusError {
// If status is not ok, return error // If status is not ok, return error
return FSError{status} return FSError{status}
} }
return nil return nil
-1
View File
@@ -13,7 +13,6 @@ var (
ErrOffsetChanged = errors.New("offset has already been changed") ErrOffsetChanged = errors.New("offset has already been changed")
ErrReadOpen = errors.New("only one file can be opened for reading at a time") ErrReadOpen = errors.New("only one file can be opened for reading at a time")
ErrWriteOpen = errors.New("only one file can be opened for writing at a time") ErrWriteOpen = errors.New("only one file can be opened for writing at a time")
ErrNoRemoveRoot = errors.New("refusing to remove root directory")
) )
// FSError represents an error returned by BLE FS // FSError represents an error returned by BLE FS
+18 -3
View File
@@ -3,6 +3,7 @@ package blefs
import ( import (
"io" "io"
"io/fs" "io/fs"
"path/filepath"
"time" "time"
) )
@@ -290,7 +291,7 @@ func (fl *File) Write(b []byte) (n int, err error) {
} }
close(fl.offsetCh) close(fl.offsetCh)
return int(fl.amtTferd), nil return int(fl.offset), nil
} }
// WriteString converts the string to []byte and calls Write() // WriteString converts the string to []byte and calls Write()
@@ -334,9 +335,23 @@ func (fl *File) Close() error {
return nil return nil
} }
// Stat does a ReadDir() and finds the current file in the output // Stat does a RedDir() and finds the current file in the output
func (fl *File) Stat() (fs.FileInfo, error) { func (fl *File) Stat() (fs.FileInfo, error) {
return fl.fs.Stat(fl.path) // Get directory in filepath
dir := filepath.Dir(fl.path)
// Read directory
dirEntries, err := fl.fs.ReadDir(dir)
if err != nil {
return nil, err
}
for _, entry := range dirEntries {
// If file name is base name of path
if entry.Name() == filepath.Base(fl.path) {
// Return file info
return entry.Info()
}
}
return nil, ErrFileNotExists
} }
// fileReadResponse represents a response for a read request // fileReadResponse represents a response for a read request
+1 -7
View File
@@ -199,13 +199,7 @@ func decode(data []byte, vals ...interface{}) error {
// to send in a packet. Subtracting 20 ensures that the MTU // to send in a packet. Subtracting 20 ensures that the MTU
// is never exceeded. // is never exceeded.
func (blefs *FS) maxData() uint16 { func (blefs *FS) maxData() uint16 {
mtu := blefs.transferChar.Properties.MTU return blefs.transferChar.Properties.MTU - 20
// If MTU is zero, the current version of BlueZ likely
// doesn't support the MTU property, so assume 256.
if mtu == 0 {
mtu = 256
}
return mtu - 20
} }
// padding returns a slice of len amount of 0x00. // padding returns a slice of len amount of 0x00.
+1 -1
View File
@@ -12,4 +12,4 @@ func (iofs goFS) Open(path string) (fs.File, error) {
func (blefs *FS) GoFS() fs.FS { func (blefs *FS) GoFS() fs.FS {
return goFS{blefs} return goFS{blefs}
} }
+8 -9
View File
@@ -5,12 +5,13 @@ import (
bt "github.com/muka/go-bluetooth/api" bt "github.com/muka/go-bluetooth/api"
"github.com/muka/go-bluetooth/bluez/profile/adapter" "github.com/muka/go-bluetooth/bluez/profile/adapter"
"github.com/muka/go-bluetooth/bluez/profile/agent" "github.com/muka/go-bluetooth/bluez/profile/agent"
"github.com/muka/go-bluetooth/hw/linux/btmgmt"
) )
var defaultAdapter *adapter.Adapter1 var defaultAdapter *adapter.Adapter1
var itdAgent *Agent var itdAgent *Agent
func Init(adapterID string) { func Init() {
conn, err := dbus.SystemBus() conn, err := dbus.SystemBus()
if err != nil { if err != nil {
panic(err) panic(err)
@@ -22,16 +23,15 @@ func Init(adapterID string) {
panic(err) panic(err)
} }
if adapterID == "" {
adapterID = bt.GetDefaultAdapterID()
}
// Get bluez default adapter // Get bluez default adapter
da, err := bt.GetAdapter(adapterID) da, err := bt.GetDefaultAdapter()
if err != nil { if err != nil {
panic(err) panic(err)
} }
daMgmt := btmgmt.NewBtMgmt(bt.GetDefaultAdapterID())
daMgmt.SetPowered(true)
defaultAdapter = da defaultAdapter = da
itdAgent = ag itdAgent = ag
} }
@@ -73,7 +73,6 @@ func (a *Agent) RequestPasskey(device dbus.ObjectPath) (uint32, *dbus.Error) {
if a.ReqPasskey == nil { if a.ReqPasskey == nil {
return 0, errAuthFailed return 0, errAuthFailed
} }
log.Debug("Passkey requested, calling onReqPasskey callback").Send()
passkey, err := a.ReqPasskey() passkey, err := a.ReqPasskey()
if err != nil { if err != nil {
return 0, errAuthFailed return 0, errAuthFailed
@@ -106,9 +105,9 @@ func (*Agent) Cancel() *dbus.Error {
return nil return nil
} }
// Path returns "/ws/elara/infinitime/Agent" // Path returns "/dev/arsenm/infinitime/Agent"
func (*Agent) Path() dbus.ObjectPath { func (*Agent) Path() dbus.ObjectPath {
return "/ws/elara/infinitime/Agent" return "/dev/arsenm/infinitime/Agent"
} }
// Interface returns "org.bluez.Agent1" // Interface returns "org.bluez.Agent1"
-10
View File
@@ -313,7 +313,6 @@ func (dfu *DFU) Reset() {
// on waits for the given command to be received on // on waits for the given command to be received on
// the control point characteristic, then runs the callback. // the control point characteristic, then runs the callback.
func (dfu *DFU) on(cmd []byte, onCmdCb func(data []byte) error) error { func (dfu *DFU) on(cmd []byte, onCmdCb func(data []byte) error) error {
log.Debug("Waiting for DFU command").Bytes("expecting", cmd).Send()
// Use for loop in case of invalid property // Use for loop in case of invalid property
for { for {
select { select {
@@ -325,10 +324,6 @@ func (dfu *DFU) on(cmd []byte, onCmdCb func(data []byte) error) error {
} }
// Assert propery value as byte slice // Assert propery value as byte slice
data := propChanged.Value.([]byte) data := propChanged.Value.([]byte)
log.Debug("Received DFU command").
Bytes("expecting", cmd).
Bytes("received", data).
Send()
// If command has prefix of given command // If command has prefix of given command
if bytes.HasPrefix(data, cmd) { if bytes.HasPrefix(data, cmd) {
// Return callback with data after command // Return callback with data after command
@@ -413,7 +408,6 @@ func (dfu *DFU) stepSeven() error {
if err != nil { if err != nil {
return err return err
} }
log.Debug("Sent firmware image segment").Send()
// Increment bytes sent by amount read // Increment bytes sent by amount read
dfu.bytesSent += len(segment) dfu.bytesSent += len(segment)
} }
@@ -421,10 +415,6 @@ func (dfu *DFU) stepSeven() error {
err := dfu.on(DFUNotifPktRecvd, func(data []byte) error { err := dfu.on(DFUNotifPktRecvd, func(data []byte) error {
// Set bytes received to data returned by InfiniTime // Set bytes received to data returned by InfiniTime
dfu.bytesRecvd = int(binary.LittleEndian.Uint32(data)) dfu.bytesRecvd = int(binary.LittleEndian.Uint32(data))
log.Debug("Received packet receipt notification").
Int("sent", dfu.bytesSent).
Int("rcvd", dfu.bytesRecvd).
Send()
if dfu.bytesRecvd != dfu.bytesSent { if dfu.bytesRecvd != dfu.bytesSent {
return ErrDFUSizeMismatch return ErrDFUSizeMismatch
} }
+3 -3
View File
@@ -1,11 +1,11 @@
module go.elara.ws/infinitime module go.arsenm.dev/infinitime
go 1.16 go 1.16
require ( require (
github.com/fxamacker/cbor/v2 v2.4.0 github.com/fxamacker/cbor/v2 v2.4.0
github.com/godbus/dbus/v5 v5.0.6 github.com/godbus/dbus/v5 v5.0.6
github.com/muka/go-bluetooth v0.0.0-20220819140550-1d8857e3b268 github.com/muka/go-bluetooth v0.0.0-20220219050759-674a63b8741a
go.elara.ws/logger v0.0.0-20230928062203-85e135cf02ae github.com/rs/zerolog v1.26.1
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
) )
+23 -20
View File
@@ -1,3 +1,4 @@
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -6,69 +7,71 @@ github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro= github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ=
github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/muka/go-bluetooth v0.0.0-20220219050759-674a63b8741a h1:fnzS9RRQW8B5AgNCxkN0vJ/AoX+Xfqk3sAYon3iVrzA=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/muka/go-bluetooth v0.0.0-20220219050759-674a63b8741a/go.mod h1:dMCjicU6vRBk34dqOmIZm0aod6gUwZXOXzBROqGous0=
github.com/muka/go-bluetooth v0.0.0-20220819140550-1d8857e3b268 h1:kOnq7TfaAO2Vc/MHxPqFIXe00y1qBxJAvhctXdko6vo=
github.com/muka/go-bluetooth v0.0.0-20220819140550-1d8857e3b268/go.mod h1:dMCjicU6vRBk34dqOmIZm0aod6gUwZXOXzBROqGous0=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/paypal/gatt v0.0.0-20151011220935-4ae819d591cf/go.mod h1:+AwQL2mK3Pd3S+TUwg0tYQjid0q1txyNUJuuSmz8Kdk= github.com/paypal/gatt v0.0.0-20151011220935-4ae819d591cf/go.mod h1:+AwQL2mK3Pd3S+TUwg0tYQjid0q1txyNUJuuSmz8Kdk=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc=
github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc=
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/suapapa/go_eddystone v1.3.1/go.mod h1:bXC11TfJOS+3g3q/Uzd7FKd5g62STQEfeEIhcKe4Qy8= github.com/suapapa/go_eddystone v1.3.1/go.mod h1:bXC11TfJOS+3g3q/Uzd7FKd5g62STQEfeEIhcKe4Qy8=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.elara.ws/logger v0.0.0-20230928062203-85e135cf02ae h1:d+gJUhEWSrOjrrfgeydYWEr8TTnx0DLvcVhghaOsFeE= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.elara.ws/logger v0.0.0-20230928062203-85e135cf02ae/go.mod h1:qng49owViqsW5Aey93lwBXONw20oGbJIoLVscB16mPM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+103 -220
View File
@@ -2,7 +2,6 @@ package infinitime
import ( import (
"bytes" "bytes"
"context"
"encoding/binary" "encoding/binary"
"errors" "errors"
"reflect" "reflect"
@@ -15,15 +14,10 @@ import (
"github.com/muka/go-bluetooth/bluez/profile/adapter" "github.com/muka/go-bluetooth/bluez/profile/adapter"
"github.com/muka/go-bluetooth/bluez/profile/device" "github.com/muka/go-bluetooth/bluez/profile/device"
"github.com/muka/go-bluetooth/bluez/profile/gatt" "github.com/muka/go-bluetooth/bluez/profile/gatt"
"go.elara.ws/infinitime/blefs" "github.com/rs/zerolog"
"go.elara.ws/logger" "go.arsenm.dev/infinitime/blefs"
) )
// 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 logger.Logger
const BTName = "InfiniTime" const BTName = "InfiniTime"
const ( const (
@@ -33,7 +27,6 @@ const (
MotionValChar = "00030002-78fc-48fe-8e23-433b3a1942d0" MotionValChar = "00030002-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"
LocalTimeChar = "00002a0f-0000-1000-8000-00805f9b34fb"
BatteryLvlChar = "00002a19-0000-1000-8000-00805f9b34fb" BatteryLvlChar = "00002a19-0000-1000-8000-00805f9b34fb"
HeartRateChar = "00002a37-0000-1000-8000-00805f9b34fb" HeartRateChar = "00002a37-0000-1000-8000-00805f9b34fb"
FSTransferChar = "adaf0200-4669-6c65-5472-616e73666572" FSTransferChar = "adaf0200-4669-6c65-5472-616e73666572"
@@ -41,25 +34,6 @@ const (
WeatherDataChar = "00040001-78fc-48fe-8e23-433b3a1942d0" WeatherDataChar = "00040001-78fc-48fe-8e23-433b3a1942d0"
) )
var charNames = map[string]string{
NewAlertChar: "New Alert",
NotifEventChar: "Notification Event",
StepCountChar: "Step Count",
MotionValChar: "Motion Values",
FirmwareVerChar: "Firmware Version",
CurrentTimeChar: "Current Time",
LocalTimeChar: "Local Time",
BatteryLvlChar: "Battery Level",
HeartRateChar: "Heart Rate",
FSTransferChar: "Filesystem Transfer",
FSVersionChar: "Filesystem Version",
WeatherDataChar: "Weather Data",
NavFlagsChar: "Navigation Icon",
NavNarrativeChar: "Navigation Instruction",
NavManDistChar: "Navigation Distance to next event",
NavProgressChar: "Navigation Progress",
}
type Device struct { type Device struct {
device *device.Device1 device *device.Device1
newAlertChar *gatt.GattCharacteristic1 newAlertChar *gatt.GattCharacteristic1
@@ -68,17 +42,14 @@ type Device struct {
motionValChar *gatt.GattCharacteristic1 motionValChar *gatt.GattCharacteristic1
fwVersionChar *gatt.GattCharacteristic1 fwVersionChar *gatt.GattCharacteristic1
currentTimeChar *gatt.GattCharacteristic1 currentTimeChar *gatt.GattCharacteristic1
localTimeChar *gatt.GattCharacteristic1
battLevelChar *gatt.GattCharacteristic1 battLevelChar *gatt.GattCharacteristic1
heartRateChar *gatt.GattCharacteristic1 heartRateChar *gatt.GattCharacteristic1
fsVersionChar *gatt.GattCharacteristic1 fsVersionChar *gatt.GattCharacteristic1
fsTransferChar *gatt.GattCharacteristic1 fsTransferChar *gatt.GattCharacteristic1
weatherDataChar *gatt.GattCharacteristic1 weatherDataChar *gatt.GattCharacteristic1
weatherdataChar *gatt.GattCharacteristic1
notifEventCh chan uint8 notifEventCh chan uint8
notifEventDone bool notifEventDone bool
Music MusicCtrl Music MusicCtrl
Navigation NavigationService
DFU DFU DFU DFU
} }
@@ -86,32 +57,26 @@ var (
ErrNoDevices = errors.New("no InfiniTime devices found") ErrNoDevices = errors.New("no InfiniTime devices found")
ErrNotFound = errors.New("could not find any advertising InfiniTime devices") ErrNotFound = errors.New("could not find any advertising InfiniTime devices")
ErrNotConnected = errors.New("not connected") ErrNotConnected = errors.New("not connected")
ErrCharNotAvail = errors.New("required characteristic is not available")
ErrNoTimelineHeader = errors.New("events must contain the timeline header") ErrNoTimelineHeader = errors.New("events must contain the timeline header")
ErrPairTimeout = errors.New("reached timeout while pairing") ErrPairTimeout = errors.New("reached timeout while pairing")
) )
type ErrCharNotAvail struct {
uuid string
}
func (e ErrCharNotAvail) Error() string {
return "characteristic " + e.uuid + " (" + charNames[e.uuid] + ") not available"
}
type Options struct { type Options struct {
AttemptReconnect bool AttemptReconnect bool
WhitelistEnabled bool WhitelistEnabled bool
Whitelist []string Whitelist []string
OnReqPasskey func() (uint32, error) OnReqPasskey func() (uint32, error)
OnReconnect func() OnReconnect func()
Logger logger.Logger Logger zerolog.Logger
LogLevel logger.LogLevel LogLevel zerolog.Level
} }
var DefaultOptions = &Options{ var DefaultOptions = &Options{
AttemptReconnect: true, AttemptReconnect: true,
WhitelistEnabled: false, WhitelistEnabled: false,
Logger: logger.NewNop(), Logger: zerolog.Nop(),
LogLevel: zerolog.Disabled,
} }
// Connect will attempt to connect to a // Connect will attempt to connect to a
@@ -120,26 +85,22 @@ var DefaultOptions = &Options{
// //
// It will also attempt to reconnect to the device // It will also attempt to reconnect to the device
// if it disconnects and that is enabled in the options. // if it disconnects and that is enabled in the options.
func Connect(ctx context.Context, opts *Options) (*Device, error) { func Connect(opts *Options) (*Device, error) {
if opts == nil { if opts == nil {
opts = DefaultOptions opts = DefaultOptions
} }
log = opts.Logger
opts.Logger.SetLevel(opts.LogLevel)
// Set passkey request callback // Set passkey request callback
setOnPasskeyReq(opts.OnReqPasskey) setOnPasskeyReq(opts.OnReqPasskey)
// Connect to bluetooth device // Connect to bluetooth device
btDev, err := connect(ctx, opts, true) btDev, err := connect(opts, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Create new device // Create new device
out := &Device{device: btDev} out := &Device{device: btDev}
out.Navigation = NavigationService{dev: out}
// Resolve characteristics // Resolve characteristics
err = out.resolveChars() err = out.resolveChars()
@@ -151,7 +112,7 @@ func Connect(ctx context.Context, opts *Options) (*Device, error) {
} }
// connect connects to the InfiniTime bluez device // connect connects to the InfiniTime bluez device
func connect(ctx context.Context, opts *Options, first bool) (dev *device.Device1, err error) { func connect(opts *Options, first bool) (dev *device.Device1, err error) {
// Get devices // Get devices
devs, err := defaultAdapter.GetDevices() devs, err := defaultAdapter.GetDevices()
if err != nil { if err != nil {
@@ -168,74 +129,51 @@ func connect(ctx context.Context, opts *Options, first bool) (dev *device.Device
// device, skip // device, skip
if opts.WhitelistEnabled && if opts.WhitelistEnabled &&
!contains(opts.Whitelist, listDev.Properties.Address) { !contains(opts.Whitelist, listDev.Properties.Address) {
log.Debug("InfiniTime device skipped as it is not in whitelist").
Str("mac", listDev.Properties.Address).
Send()
continue continue
} }
// Set device // Set device
dev = listDev dev = listDev
log.Debug("InfiniTime device found in list").
Str("mac", dev.Properties.Address).
Send()
break break
} }
// If device not set // If device not set
if dev == nil { if dev == nil {
log.Debug("No device found in list, attempting to discover").Send()
// Discover devices on adapter // Discover devices on adapter
discoverCh, cancel, err := bt.Discover(defaultAdapter, &adapter.DiscoveryFilter{Transport: "le"}) discoverCh, cancel, err := bt.Discover(defaultAdapter, &adapter.DiscoveryFilter{Transport: "le"})
if err != nil { if err != nil {
return nil, err return nil, err
} }
discoverLoop: // For every discovery event
for { for event := range discoverCh {
select { // If event type is not device added, skip
case event := <-discoverCh: if event.Type != adapter.DeviceAdded {
// If event type is not device added, skip continue
if event.Type != adapter.DeviceAdded {
continue
}
// Create new device from event path
discovered, err := device.NewDevice1(event.Path)
if err != nil {
return nil, err
}
// 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("Discovered InfiniTime device skipped as it is not in whitelist").
Str("mac", discovered.Properties.Address).
Send()
continue
}
// Set device
dev = discovered
log.Debug("InfiniTime device discovered").
Str("mac", dev.Properties.Address).
Send()
break discoverLoop
case <-ctx.Done():
break discoverLoop
} }
}
// Cancel discovery // Create new device from event path
discovered, err := device.NewDevice1(event.Path)
if err != nil {
return nil, err
}
// 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) {
continue
}
// Set device
dev = discovered
break
}
// Stop discovery
cancel() cancel()
} }
@@ -249,7 +187,6 @@ func connect(ctx context.Context, opts *Options, first bool) (dev *device.Device
reconnRequired := false reconnRequired := false
// If device is not connected // If device is not connected
if !dev.Properties.Connected { if !dev.Properties.Connected {
log.Debug("Device not connected, connecting").Send()
// Connect to device // Connect to device
err = dev.Connect() err = dev.Connect()
if err != nil { if err != nil {
@@ -261,7 +198,6 @@ func connect(ctx context.Context, opts *Options, first bool) (dev *device.Device
// If device is not paired // If device is not paired
if !dev.Properties.Paired { if !dev.Properties.Paired {
log.Debug("Device not paired, pairing").Send()
// Pair device // Pair device
err = dev.Pair() err = dev.Pair()
if err != nil { if err != nil {
@@ -272,14 +208,13 @@ func connect(ctx context.Context, opts *Options, first bool) (dev *device.Device
// If this is the first connection and reconnect // If this is the first connection and reconnect
// is enabled, start reconnect goroutine // is enabled, start reconnect goroutine
if first && opts.AttemptReconnect { if first && opts.AttemptReconnect {
go reconnect(ctx, opts, dev) go reconnect(opts, dev)
} }
// If this is not the first connection, a reonnect // If this is not the first connection, a reonnect
// was required, and the OnReconnect callback exists, // was required, and the OnReconnect callback exists,
// run it // run it
if !first && reconnRequired && opts.OnReconnect != nil { if !first && reconnRequired && opts.OnReconnect != nil {
log.Debug("Reconnected to device, running OnReconnect callback").Send()
opts.OnReconnect() opts.OnReconnect()
} }
@@ -287,7 +222,7 @@ func connect(ctx context.Context, opts *Options, first bool) (dev *device.Device
} }
// reconnect reconnects to a device if it disconnects // reconnect reconnects to a device if it disconnects
func reconnect(ctx context.Context, opts *Options, dev *device.Device1) { func reconnect(opts *Options, dev *device.Device1) {
// Watch device properties // Watch device properties
propCh := watchProps(dev) propCh := watchProps(dev)
@@ -312,7 +247,7 @@ func reconnect(ctx context.Context, opts *Options, dev *device.Device1) {
// If less than 3 seconds have passed and more than 6 // If less than 3 seconds have passed and more than 6
// disconnects have occurred, remove the device and reset // disconnects have occurred, remove the device and reset
if secsSince <= 3 && amtDisconnects >= 6 { if secsSince <= 3 && amtDisconnects >= 6 {
opts.Logger.Warn("At least 6 disconnects have occurred in the last three seconds. If this continues, try removing the InfiniTime device from bluetooth.").Send() 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.")
lastDisconnect = time.Unix(0, 0) lastDisconnect = time.Unix(0, 0)
amtDisconnects = 0 amtDisconnects = 0
} }
@@ -324,10 +259,10 @@ func reconnect(ctx context.Context, opts *Options, dev *device.Device1) {
for i := 0; i < 6; i++ { for i := 0; i < 6; i++ {
// If three tries failed, remove device // If three tries failed, remove device
if i == 3 { if i == 3 {
opts.Logger.Warn("Multiple connection attempts have failed. If this continues, try removing the InfiniTime device from bluetooth.").Send() opts.Logger.Warn().Msg("Multiple connection attempts have failed. If this continues, try removing the InfiniTime device from bluetooth.")
} }
// Connect to device // Connect to device
newDev, err := connect(ctx, opts, false) newDev, err := connect(opts, false)
if err != nil { if err != nil {
time.Sleep(time.Second) time.Sleep(time.Second)
continue continue
@@ -401,17 +336,8 @@ func (i *Device) resolveChars() error {
} }
// For every discovered characteristics // For every discovered characteristics
for _, char := range chars { for _, char := range chars {
charResolved := true
// Set correct characteristics // Set correct characteristics
switch char.Properties.UUID { switch char.Properties.UUID {
case NavFlagsChar:
i.Navigation.flagsChar = char
case NavNarrativeChar:
i.Navigation.narrativeChar = char
case NavManDistChar:
i.Navigation.mandistChar = char
case NavProgressChar:
i.Navigation.progressChar = char
case NewAlertChar: case NewAlertChar:
i.newAlertChar = char i.newAlertChar = char
case NotifEventChar: case NotifEventChar:
@@ -424,8 +350,6 @@ func (i *Device) resolveChars() error {
i.fwVersionChar = char i.fwVersionChar = char
case CurrentTimeChar: case CurrentTimeChar:
i.currentTimeChar = char i.currentTimeChar = char
case LocalTimeChar:
i.localTimeChar = char
case BatteryLvlChar: case BatteryLvlChar:
i.battLevelChar = char i.battLevelChar = char
case HeartRateChar: case HeartRateChar:
@@ -450,14 +374,6 @@ func (i *Device) resolveChars() error {
i.fsVersionChar = char i.fsVersionChar = char
case WeatherDataChar: case WeatherDataChar:
i.weatherDataChar = char i.weatherDataChar = char
default:
charResolved = false
}
if charResolved {
log.Debug("Resolved characteristic").
Str("uuid", char.Properties.UUID).
Str("name", charNames[char.Properties.UUID]).
Send()
} }
} }
return nil return nil
@@ -470,7 +386,7 @@ func (i *Device) Address() string {
// Version returns InfiniTime's reported firmware version string // Version returns InfiniTime's reported firmware version string
func (i *Device) Version() (string, error) { func (i *Device) Version() (string, error) {
if err := i.checkStatus(i.fwVersionChar, FirmwareVerChar); err != nil { if err := i.checkStatus(i.fwVersionChar); err != nil {
return "", err return "", err
} }
ver, err := i.fwVersionChar.ReadValue(nil) ver, err := i.fwVersionChar.ReadValue(nil)
@@ -479,7 +395,7 @@ func (i *Device) Version() (string, error) {
// BatteryLevel gets the watch's battery level via the Battery Service // BatteryLevel gets the watch's battery level via the Battery Service
func (i *Device) BatteryLevel() (uint8, error) { func (i *Device) BatteryLevel() (uint8, error) {
if err := i.checkStatus(i.battLevelChar, BatteryLvlChar); err != nil { if err := i.checkStatus(i.battLevelChar); err != nil {
return 0, err return 0, err
} }
battLevel, err := i.battLevelChar.ReadValue(nil) battLevel, err := i.battLevelChar.ReadValue(nil)
@@ -490,7 +406,7 @@ func (i *Device) BatteryLevel() (uint8, error) {
} }
func (i *Device) StepCount() (uint32, error) { func (i *Device) StepCount() (uint32, error) {
if err := i.checkStatus(i.stepCountChar, StepCountChar); err != nil { if err := i.checkStatus(i.stepCountChar); err != nil {
return 0, err return 0, err
} }
stepCountData, err := i.stepCountChar.ReadValue(nil) stepCountData, err := i.stepCountChar.ReadValue(nil)
@@ -508,7 +424,7 @@ type MotionValues struct {
func (i *Device) Motion() (MotionValues, error) { func (i *Device) Motion() (MotionValues, error) {
out := MotionValues{} out := MotionValues{}
if err := i.checkStatus(i.motionValChar, MotionValChar); err != nil { if err := i.checkStatus(i.motionValChar); err != nil {
return out, err return out, err
} }
motionVals, err := i.motionValChar.ReadValue(nil) motionVals, err := i.motionValChar.ReadValue(nil)
@@ -524,7 +440,7 @@ func (i *Device) Motion() (MotionValues, error) {
} }
func (i *Device) HeartRate() (uint8, error) { func (i *Device) HeartRate() (uint8, error) {
if err := i.checkStatus(i.heartRateChar, HeartRateChar); err != nil { if err := i.checkStatus(i.heartRateChar); err != nil {
return 0, err return 0, err
} }
heartRate, err := i.heartRateChar.ReadValue(nil) heartRate, err := i.heartRateChar.ReadValue(nil)
@@ -534,33 +450,34 @@ func (i *Device) HeartRate() (uint8, error) {
return uint8(heartRate[1]), nil return uint8(heartRate[1]), nil
} }
func (i *Device) WatchHeartRate(ctx context.Context) (<-chan uint8, error) { func (i *Device) WatchHeartRate() (<-chan uint8, func(), error) {
if err := i.checkStatus(i.heartRateChar, HeartRateChar); err != nil { if err := i.checkStatus(i.heartRateChar); err != nil {
return nil, err return nil, nil, err
} }
// Start notifications on heart rate characteristic // Start notifications on heart rate characteristic
err := i.heartRateChar.StartNotify() err := i.heartRateChar.StartNotify()
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
// Watch characteristics of heart rate characteristic // Watch characteristics of heart rate characteristic
ch, err := i.heartRateChar.WatchProperties() ch, err := i.heartRateChar.WatchProperties()
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
out := make(chan uint8, 2) out := make(chan uint8, 2)
currentHeartRate, err := i.HeartRate() currentHeartRate, err := i.HeartRate()
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
out <- currentHeartRate out <- currentHeartRate
cancel, done := cancelFunc()
go func() { go func() {
// For every event // For every event
for { for {
select { select {
case <-ctx.Done(): case <-done:
log.Debug("Received done signal").Str("func", "WatchMotion").Send()
close(out) close(out)
close(done)
i.heartRateChar.StopNotify() i.heartRateChar.StopNotify()
return return
case event := <-ch: case event := <-ch:
@@ -569,42 +486,42 @@ func (i *Device) WatchHeartRate(ctx context.Context) (<-chan uint8, error) {
// Send heart rate to channel // Send heart rate to channel
out <- uint8(event.Value.([]byte)[1]) out <- uint8(event.Value.([]byte)[1])
} else if event.Name == "Notifying" && !event.Value.(bool) { } else if event.Name == "Notifying" && !event.Value.(bool) {
log.Debug("Notifications stopped, restarting").Str("func", "WatchMotion").Send()
i.heartRateChar.StartNotify() i.heartRateChar.StartNotify()
} }
} }
} }
}() }()
return out, nil return out, cancel, nil
} }
func (i *Device) WatchBatteryLevel(ctx context.Context) (<-chan uint8, error) { func (i *Device) WatchBatteryLevel() (<-chan uint8, func(), error) {
if err := i.checkStatus(i.battLevelChar, BatteryLvlChar); err != nil { if err := i.checkStatus(i.battLevelChar); err != nil {
return nil, err return nil, nil, err
} }
// Start notifications on heart rate characteristic // Start notifications on heart rate characteristic
err := i.battLevelChar.StartNotify() err := i.battLevelChar.StartNotify()
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
// Watch characteristics of heart rate characteristic // Watch characteristics of heart rate characteristic
ch, err := i.battLevelChar.WatchProperties() ch, err := i.battLevelChar.WatchProperties()
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
out := make(chan uint8, 2) out := make(chan uint8, 2)
currentBattLevel, err := i.BatteryLevel() currentBattLevel, err := i.BatteryLevel()
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
out <- currentBattLevel out <- currentBattLevel
cancel, done := cancelFunc()
go func() { go func() {
// For every event // For every event
for { for {
select { select {
case <-ctx.Done(): case <-done:
log.Debug("Received done signal").Str("func", "WatchMotion").Send()
close(out) close(out)
close(done)
i.battLevelChar.StopNotify() i.battLevelChar.StopNotify()
return return
case event := <-ch: case event := <-ch:
@@ -613,42 +530,42 @@ func (i *Device) WatchBatteryLevel(ctx context.Context) (<-chan uint8, error) {
// Send heart rate to channel // Send heart rate to channel
out <- uint8(event.Value.([]byte)[0]) out <- uint8(event.Value.([]byte)[0])
} else if event.Name == "Notifying" && !event.Value.(bool) { } else if event.Name == "Notifying" && !event.Value.(bool) {
log.Debug("Notifications stopped, restarting").Str("func", "WatchMotion").Send()
i.battLevelChar.StartNotify() i.battLevelChar.StartNotify()
} }
} }
} }
}() }()
return out, nil return out, cancel, nil
} }
func (i *Device) WatchStepCount(ctx context.Context) (<-chan uint32, error) { func (i *Device) WatchStepCount() (<-chan uint32, func(), error) {
if err := i.checkStatus(i.stepCountChar, StepCountChar); err != nil { if err := i.checkStatus(i.stepCountChar); err != nil {
return nil, err return nil, nil, err
} }
// Start notifications on step count characteristic // Start notifications on step count characteristic
err := i.stepCountChar.StartNotify() err := i.stepCountChar.StartNotify()
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
// Watch properties of step count characteristic // Watch properties of step count characteristic
ch, err := i.stepCountChar.WatchProperties() ch, err := i.stepCountChar.WatchProperties()
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
out := make(chan uint32, 2) out := make(chan uint32, 2)
currentStepCount, err := i.StepCount() currentStepCount, err := i.StepCount()
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
out <- currentStepCount out <- currentStepCount
cancel, done := cancelFunc()
go func() { go func() {
// For every event // For every event
for { for {
select { select {
case <-ctx.Done(): case <-done:
log.Debug("Received done signal").Str("func", "WatchMotion").Send()
close(out) close(out)
close(done)
i.stepCountChar.StopNotify() i.stepCountChar.StopNotify()
return return
case event := <-ch: case event := <-ch:
@@ -657,42 +574,42 @@ func (i *Device) WatchStepCount(ctx context.Context) (<-chan uint32, error) {
// Send step count to channel // Send step count to channel
out <- binary.LittleEndian.Uint32(event.Value.([]byte)) out <- binary.LittleEndian.Uint32(event.Value.([]byte))
} else if event.Name == "Notifying" && !event.Value.(bool) { } else if event.Name == "Notifying" && !event.Value.(bool) {
log.Debug("Notifications stopped, restarting").Str("func", "WatchMotion").Send()
i.stepCountChar.StartNotify() i.stepCountChar.StartNotify()
} }
} }
} }
}() }()
return out, nil return out, cancel, nil
} }
func (i *Device) WatchMotion(ctx context.Context) (<-chan MotionValues, error) { func (i *Device) WatchMotion() (<-chan MotionValues, func(), error) {
if err := i.checkStatus(i.motionValChar, MotionValChar); err != nil { if err := i.checkStatus(i.motionValChar); err != nil {
return nil, err return nil, nil, err
} }
// Start notifications on motion characteristic // Start notifications on motion characteristic
err := i.motionValChar.StartNotify() err := i.motionValChar.StartNotify()
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
// Watch properties of motion characteristic // Watch properties of motion characteristic
ch, err := i.motionValChar.WatchProperties() ch, err := i.motionValChar.WatchProperties()
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
out := make(chan MotionValues, 2) out := make(chan MotionValues, 2)
motionVals, err := i.Motion() motionVals, err := i.Motion()
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
out <- motionVals out <- motionVals
cancel, done := cancelFunc()
go func() { go func() {
// For every event // For every event
for { for {
select { select {
case <-ctx.Done(): case <-done:
log.Debug("Received done signal").Str("func", "WatchMotion").Send()
close(out) close(out)
close(done)
i.motionValChar.StopNotify() i.motionValChar.StopNotify()
return return
case event := <-ch: case event := <-ch:
@@ -703,21 +620,25 @@ func (i *Device) WatchMotion(ctx context.Context) (<-chan MotionValues, error) {
// Send step count to channel // Send step count to channel
out <- motionVals out <- motionVals
} else if event.Name == "Notifying" && !event.Value.(bool) { } else if event.Name == "Notifying" && !event.Value.(bool) {
log.Debug("Notifications stopped, restarting").Str("func", "WatchMotion").Send()
i.motionValChar.StartNotify() i.motionValChar.StartNotify()
} }
} }
} }
}() }()
return out, nil return out, cancel, nil
} }
// SetTime sets the watch's func cancelFunc() (func(), chan struct{}) {
// * time using the Current Time Service's current time characteristic done := make(chan struct{}, 1)
// * timezone information using the CTS's local time characteristic return func() {
done <- struct{}{}
}, done
}
// SetTime sets the watch's time using the Current Time Service
func (i *Device) SetTime(t time.Time) error { func (i *Device) SetTime(t time.Time) error {
if err := i.checkStatus(i.currentTimeChar, CurrentTimeChar); err != nil { if err := i.checkStatus(i.currentTimeChar); err != nil {
return err return err
} }
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
@@ -730,44 +651,13 @@ func (i *Device) SetTime(t time.Time) error {
binary.Write(buf, binary.LittleEndian, uint8(t.Weekday())) 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((t.Nanosecond()/1000)/1e6*256))
binary.Write(buf, binary.LittleEndian, uint8(0b0001)) binary.Write(buf, binary.LittleEndian, uint8(0b0001))
if err := i.currentTimeChar.WriteValue(buf.Bytes(), nil); err != nil { return i.currentTimeChar.WriteValue(buf.Bytes(), nil)
return err
}
if err := i.checkStatus(i.localTimeChar, LocalTimeChar); err != nil {
// If the characteristic is unavailable,
// fail silently, as many people may be on
// older InfiniTime versions. A warning
// may be added later.
if _, ok := err.(ErrCharNotAvail); ok {
return nil
} else {
return err
}
}
_, offset := t.Zone()
dst := 0
// Local time expects two values: the timezone offset and the dst offset, both
// expressed in quarters of an hour.
// Timezone offset is to be constant over DST, with dst offset holding the offset != 0
// when DST is in effect.
// As there is no standard way in go to get the actual dst offset, we assume it to be 1h
// when DST is in effect
if t.IsDST() {
dst = 3600
offset -= 3600
}
bufTz := &bytes.Buffer{}
binary.Write(bufTz, binary.LittleEndian, uint8(offset/3600*4))
binary.Write(bufTz, binary.LittleEndian, uint8(dst/3600*4))
return i.localTimeChar.WriteValue(bufTz.Bytes(), nil)
} }
// Notify sends a notification to InfiniTime via // Notify sends a notification to InfiniTime via
// the Alert Notification Service (ANS) // the Alert Notification Service (ANS)
func (i *Device) Notify(title, body string) error { func (i *Device) Notify(title, body string) error {
if err := i.checkStatus(i.newAlertChar, NewAlertChar); err != nil { if err := i.checkStatus(i.newAlertChar); err != nil {
return err return err
} }
return i.newAlertChar.WriteValue( return i.newAlertChar.WriteValue(
@@ -786,7 +676,7 @@ const (
// NotifyCall sends a call notification to the PineTime and returns a channel. // NotifyCall sends a call notification to the PineTime and returns a channel.
// This channel will contain the user's response to the call notification. // This channel will contain the user's response to the call notification.
func (i *Device) NotifyCall(from string) (<-chan uint8, error) { func (i *Device) NotifyCall(from string) (<-chan uint8, error) {
if err := i.checkStatus(i.newAlertChar, NewAlertChar); err != nil { if err := i.checkStatus(i.newAlertChar); err != nil {
return nil, err return nil, err
} }
// Write call notification to new alert characteristic // Write call notification to new alert characteristic
@@ -838,7 +728,7 @@ func (i *Device) initNotifEvent() error {
// FS creates and returns a new filesystem from the device // FS creates and returns a new filesystem from the device
func (i *Device) FS() (*blefs.FS, error) { func (i *Device) FS() (*blefs.FS, error) {
if err := i.checkStatus(i.fsTransferChar, FSTransferChar); err != nil { if err := i.checkStatus(i.fsTransferChar); err != nil {
return nil, err return nil, err
} }
return blefs.New(i.fsTransferChar) return blefs.New(i.fsTransferChar)
@@ -848,7 +738,7 @@ func (i *Device) FS() (*blefs.FS, error) {
// the weather package to the timeline. Input must be // the weather package to the timeline. Input must be
// a struct containing TimelineHeader. // a struct containing TimelineHeader.
func (i *Device) AddWeatherEvent(event interface{}) error { func (i *Device) AddWeatherEvent(event interface{}) error {
if err := i.checkStatus(i.weatherDataChar, WeatherDataChar); err != nil { if err := i.checkStatus(i.weatherDataChar); err != nil {
return err return err
} }
// Get type of input // Get type of input
@@ -866,13 +756,11 @@ func (i *Device) AddWeatherEvent(event interface{}) error {
return err return err
} }
log.Debug("Adding weather event").Any("event", event).Send()
// Write data to weather data characteristic // Write data to weather data characteristic
return i.weatherDataChar.WriteValue(data, nil) return i.weatherDataChar.WriteValue(data, nil)
} }
func (i *Device) checkStatus(char *gatt.GattCharacteristic1, uuid string) error { func (i *Device) checkStatus(char *gatt.GattCharacteristic1) error {
log.Debug("Checking characteristic status").Send()
connected, err := i.device.GetConnected() connected, err := i.device.GetConnected()
if err != nil { if err != nil {
return err return err
@@ -881,12 +769,7 @@ func (i *Device) checkStatus(char *gatt.GattCharacteristic1, uuid string) error
return ErrNotConnected return ErrNotConnected
} }
if char == nil { if char == nil {
log.Debug("Characteristic not available (nil)").Send() return ErrCharNotAvail
return ErrCharNotAvail{uuid}
} }
log.Debug("Characteristic available").
Str("uuid", char.Properties.UUID).
Str("name", charNames[char.Properties.UUID]).
Send()
return nil return nil
} }
+37
View File
@@ -0,0 +1,37 @@
package utils
import "github.com/godbus/dbus/v5"
func NewSystemBusConn() (*dbus.Conn, error) {
// Connect to dbus session bus
conn, err := dbus.SystemBusPrivate()
if err != nil {
return nil, err
}
err = conn.Auth(nil)
if err != nil {
return nil, err
}
err = conn.Hello()
if err != nil {
return nil, err
}
return conn, nil
}
func NewSessionBusConn() (*dbus.Conn, error) {
// Connect to dbus session bus
conn, err := dbus.SessionBusPrivate()
if err != nil {
return nil, err
}
err = conn.Auth(nil)
if err != nil {
return nil, err
}
err = conn.Hello()
if err != nil {
return nil, err
}
return conn, nil
}
-1
View File
@@ -72,7 +72,6 @@ func (mc MusicCtrl) WatchEvents() (<-chan MusicEvent, error) {
for event := range ch { for event := range ch {
// If value changes // If value changes
if event.Name == "Value" { if event.Name == "Value" {
log.Debug("Received music event from watch").Bytes("value", event.Value.([]byte)).Send()
// Send music event to channel // Send music event to channel
musicEventCh <- MusicEvent(event.Value.([]byte)[0]) musicEventCh <- MusicEvent(event.Value.([]byte)[0])
} }
-152
View File
@@ -1,152 +0,0 @@
package infinitime
import (
"errors"
"github.com/muka/go-bluetooth/bluez/profile/gatt"
)
var ErrNavProgress = errors.New("progress needs to be between 0 and 100")
const (
NavFlagsChar = "00010001-78fc-48fe-8e23-433b3a1942d0"
NavNarrativeChar = "00010002-78fc-48fe-8e23-433b3a1942d0"
NavManDistChar = "00010003-78fc-48fe-8e23-433b3a1942d0"
NavProgressChar = "00010004-78fc-48fe-8e23-433b3a1942d0"
)
type NavigationService struct {
dev *Device
flagsChar *gatt.GattCharacteristic1
narrativeChar *gatt.GattCharacteristic1
mandistChar *gatt.GattCharacteristic1
progressChar *gatt.GattCharacteristic1
}
type NavFlag string
const (
NavFlagArrive NavFlag = "arrive"
NavFlagArriveLeft NavFlag = "arrive-left"
NavFlagArriveRight NavFlag = "arrive-right"
NavFlagArriveStraight NavFlag = "arrive-straight"
NavFlagClose NavFlag = "close"
NavFlagContinue NavFlag = "continue"
NavFlagContinueLeft NavFlag = "continue-left"
NavFlagContinueRight NavFlag = "continue-right"
NavFlagContinueSlightLeft NavFlag = "continue-slight-left"
NavFlagContinueSlightRight NavFlag = "continue-slight-right"
NavFlagContinueStraight NavFlag = "continue-straight"
NavFlagContinueUturn NavFlag = "continue-uturn"
NavFlagDepart NavFlag = "depart"
NavFlagDepartLeft NavFlag = "depart-left"
NavFlagDepartRight NavFlag = "depart-right"
NavFlagDepartStraight NavFlag = "depart-straight"
NavFlagEndOfRoadLeft NavFlag = "end-of-road-left"
NavFlagEndOfRoadRight NavFlag = "end-of-road-right"
NavFlagFerry NavFlag = "ferry"
NavFlagFlag NavFlag = "flag"
NavFlagFork NavFlag = "fork"
NavFlagForkLeft NavFlag = "fork-left"
NavFlagForkRight NavFlag = "fork-right"
NavFlagForkSlightLeft NavFlag = "fork-slight-left"
NavFlagForkSlightRight NavFlag = "fork-slight-right"
NavFlagForkStraight NavFlag = "fork-straight"
NavFlagInvalid NavFlag = "invalid"
NavFlagInvalidLeft NavFlag = "invalid-left"
NavFlagInvalidRight NavFlag = "invalid-right"
NavFlagInvalidSlightLeft NavFlag = "invalid-slight-left"
NavFlagInvalidSlightRight NavFlag = "invalid-slight-right"
NavFlagInvalidStraight NavFlag = "invalid-straight"
NavFlagInvalidUturn NavFlag = "invalid-uturn"
NavFlagMergeLeft NavFlag = "merge-left"
NavFlagMergeRight NavFlag = "merge-right"
NavFlagMergeSlightLeft NavFlag = "merge-slight-left"
NavFlagMergeSlightRight NavFlag = "merge-slight-right"
NavFlagMergeStraight NavFlag = "merge-straight"
NavFlagNewNameLeft NavFlag = "new-name-left"
NavFlagNewNameRight NavFlag = "new-name-right"
NavFlagNewNameSharpLeft NavFlag = "new-name-sharp-left"
NavFlagNewNameSharpRight NavFlag = "new-name-sharp-right"
NavFlagNewNameSlightLeft NavFlag = "new-name-slight-left"
NavFlagNewNameSlightRight NavFlag = "new-name-slight-right"
NavFlagNewNameStraight NavFlag = "new-name-straight"
NavFlagNotificationLeft NavFlag = "notification-left"
NavFlagNotificationRight NavFlag = "notification-right"
NavFlagNotificationSharpLeft NavFlag = "notification-sharp-left"
NavFlagNotificationSharpRight NavFlag = "notification-sharp-right"
NavFlagNotificationSlightLeft NavFlag = "notification-slight-left"
NavFlagNotificationSlightRight NavFlag = "notification-slight-right"
NavFlagNotificationStraight NavFlag = "notification-straight"
NavFlagOffRampLeft NavFlag = "off-ramp-left"
NavFlagOffRampRight NavFlag = "off-ramp-right"
NavFlagOffRampSharpLeft NavFlag = "off-ramp-sharp-left"
NavFlagOffRampSharpRight NavFlag = "off-ramp-sharp-right"
NavFlagOffRampSlightLeft NavFlag = "off-ramp-slight-left"
NavFlagOffRampSlightRight NavFlag = "off-ramp-slight-right"
NavFlagOffRampStraight NavFlag = "off-ramp-straight"
NavFlagOnRampLeft NavFlag = "on-ramp-left"
NavFlagOnRampRight NavFlag = "on-ramp-right"
NavFlagOnRampSharpLeft NavFlag = "on-ramp-sharp-left"
NavFlagOnRampSharpRight NavFlag = "on-ramp-sharp-right"
NavFlagOnRampSlightLeft NavFlag = "on-ramp-slight-left"
NavFlagOnRampSlightRight NavFlag = "on-ramp-slight-right"
NavFlagOnRampStraight NavFlag = "on-ramp-straight"
NavFlagRotary NavFlag = "rotary"
NavFlagRotaryLeft NavFlag = "rotary-left"
NavFlagRotaryRight NavFlag = "rotary-right"
NavFlagRotarySharpLeft NavFlag = "rotary-sharp-left"
NavFlagRotarySharpRight NavFlag = "rotary-sharp-right"
NavFlagRotarySlightLeft NavFlag = "rotary-slight-left"
NavFlagRotarySlightRight NavFlag = "rotary-slight-right"
NavFlagRotaryStraight NavFlag = "rotary-straight"
NavFlagRoundabout NavFlag = "roundabout"
NavFlagRoundaboutLeft NavFlag = "roundabout-left"
NavFlagRoundaboutRight NavFlag = "roundabout-right"
NavFlagRoundaboutSharpLeft NavFlag = "roundabout-sharp-left"
NavFlagRoundaboutSharpRight NavFlag = "roundabout-sharp-right"
NavFlagRoundaboutSlightLeft NavFlag = "roundabout-slight-left"
NavFlagRoundaboutSlightRight NavFlag = "roundabout-slight-right"
NavFlagRoundaboutStraight NavFlag = "roundabout-straight"
NavFlagTurnLeft NavFlag = "turn-left"
NavFlagTurnRight NavFlag = "turn-right"
NavFlagTurnSharpLeft NavFlag = "turn-sharp-left"
NavFlagTurnSharpRight NavFlag = "turn-sharp-right"
NavFlagTurnSlightLeft NavFlag = "turn-slight-left"
NavFlagTurnSlightRight NavFlag = "turn-slight-right"
NavFlagTurnStright NavFlag = "turn-stright"
NavFlagUpdown NavFlag = "updown"
NavFlagUturn NavFlag = "uturn"
)
func (n *NavigationService) SetFlag(flag NavFlag) error {
log.Debug("Sending flag").Str("func", "SetFlag").Send()
if err := n.dev.checkStatus(n.flagsChar, NavFlagsChar); err != nil {
return err
}
return n.flagsChar.WriteValue([]byte(flag), nil)
}
func (n *NavigationService) SetNarrative(narrative string) error {
log.Debug("Sending narrative").Str("func", "SetNarrative").Send()
if err := n.dev.checkStatus(n.narrativeChar, NavNarrativeChar); err != nil {
return err
}
return n.narrativeChar.WriteValue([]byte(narrative), nil)
}
func (n *NavigationService) SetManDist(manDist string) error {
log.Debug("Sending maneuver distance").Str("func", "SetNarrative").Send()
if err := n.dev.checkStatus(n.mandistChar, NavManDistChar); err != nil {
return err
}
return n.mandistChar.WriteValue([]byte(manDist), nil)
}
func (n *NavigationService) SetProgress(progress uint8) error {
log.Debug("Sending progress").Str("func", "SetNarrative").Send()
if err := n.dev.checkStatus(n.progressChar, NavProgressChar); err != nil {
return err
}
return n.progressChar.WriteValue([]byte{progress}, nil)
}
+16
View File
@@ -0,0 +1,16 @@
package player
import (
"fmt"
"os/exec"
)
// VolUp uses pactl to increase the volume of the default sink
func VolUp(percent uint) error {
return exec.Command("pactl", "set-sink-volume", "@DEFAULT_SINK@", fmt.Sprintf("+%d%%", percent)).Run()
}
// VolDown uses pactl to decrease the volume of the default sink
func VolDown(percent uint) error {
return exec.Command("pactl", "set-sink-volume", "@DEFAULT_SINK@", fmt.Sprintf("-%d%%", percent)).Run()
}
+231
View File
@@ -0,0 +1,231 @@
package player
import (
"strings"
"sync"
"github.com/godbus/dbus/v5"
"go.arsenm.dev/infinitime/internal/utils"
)
var (
method, monitor *dbus.Conn
monitorCh chan *dbus.Message
onChangeOnce sync.Once
)
// Init makes required connections to DBis and
// initializes change monitoring channel
func Init() error {
// Connect to session bus for monitoring
monitorConn, err := utils.NewSessionBusConn()
if err != nil {
return err
}
// Add match rule for PropertiesChanged on media player
monitorConn.AddMatchSignal(
dbus.WithMatchObjectPath("/org/mpris/MediaPlayer2"),
dbus.WithMatchInterface("org.freedesktop.DBus.Properties"),
dbus.WithMatchMember("PropertiesChanged"),
)
monitorCh = make(chan *dbus.Message, 10)
monitorConn.Eavesdrop(monitorCh)
// Connect to session bus for method calls
methodConn, err := utils.NewSessionBusConn()
if err != nil {
return err
}
method, monitor = methodConn, monitorConn
return nil
}
// Exit closes all connections and channels
func Exit() {
close(monitorCh)
method.Close()
monitor.Close()
}
// Play uses MPRIS to play media
func Play() error {
player, err := getPlayerObj()
if err != nil {
return err
}
if player != nil {
call := player.Call("org.mpris.MediaPlayer2.Player.Play", 0)
if call.Err != nil {
return call.Err
}
}
return nil
}
// Pause uses MPRIS to pause media
func Pause() error {
player, err := getPlayerObj()
if err != nil {
return err
}
if player != nil {
call := player.Call("org.mpris.MediaPlayer2.Player.Pause", 0)
if call.Err != nil {
return call.Err
}
}
return nil
}
// Next uses MPRIS to skip to next media
func Next() error {
player, err := getPlayerObj()
if err != nil {
return err
}
if player != nil {
call := player.Call("org.mpris.MediaPlayer2.Player.Next", 0)
if call.Err != nil {
return call.Err
}
}
return nil
}
// Prev uses MPRIS to skip to previous media
func Prev() error {
player, err := getPlayerObj()
if err != nil {
return err
}
if player != nil {
call := player.Call("org.mpris.MediaPlayer2.Player.Previous", 0)
if call.Err != nil {
return call.Err
}
}
return nil
}
type ChangeType int
const (
ChangeTypeTitle ChangeType = iota
ChangeTypeArtist
ChangeTypeAlbum
ChangeTypeStatus
)
func (ct ChangeType) String() string {
switch ct {
case ChangeTypeTitle:
return "Title"
case ChangeTypeAlbum:
return "Album"
case ChangeTypeArtist:
return "Artist"
case ChangeTypeStatus:
return "Status"
}
return ""
}
// OnChange runs cb when a value changes
func OnChange(cb func(ChangeType, string)) {
go onChangeOnce.Do(func() {
// For every message on channel
for msg := range monitorCh {
// Parse PropertiesChanged
iface, changed, ok := parsePropertiesChanged(msg)
if !ok || iface != "org.mpris.MediaPlayer2.Player" {
continue
}
// For every property changed
for name, val := range changed {
// If metadata changed
if name == "Metadata" {
// Get fields
fields := val.Value().(map[string]dbus.Variant)
// For every field
for name, val := range fields {
// Handle each field appropriately
if strings.HasSuffix(name, "title") {
title := val.Value().(string)
if title == "" {
title = "Unknown " + ChangeTypeTitle.String()
}
cb(ChangeTypeTitle, title)
} else if strings.HasSuffix(name, "album") {
album := val.Value().(string)
if album == "" {
album = "Unknown " + ChangeTypeAlbum.String()
}
cb(ChangeTypeAlbum, album)
} else if strings.HasSuffix(name, "artist") {
var artists string
switch artistVal := val.Value().(type) {
case string:
artists = artistVal
case []string:
artists = strings.Join(artistVal, ", ")
}
if artists == "" {
artists = "Unknown " + ChangeTypeArtist.String()
}
cb(ChangeTypeArtist, artists)
}
}
} else if name == "PlaybackStatus" {
// Handle status change
cb(ChangeTypeStatus, val.Value().(string))
}
}
}
})
}
// getPlayerNames gets all DBus MPRIS player bus names
func getPlayerNames(conn *dbus.Conn) ([]string, error) {
var names []string
err := conn.BusObject().Call("org.freedesktop.DBus.ListNames", 0).Store(&names)
if err != nil {
return nil, err
}
var players []string
for _, name := range names {
if strings.HasPrefix(name, "org.mpris.MediaPlayer2") {
players = append(players, name)
}
}
return players, nil
}
// GetPlayerObj gets the object corresponding to the first
// bus name found in DBus
func getPlayerObj() (dbus.BusObject, error) {
players, err := getPlayerNames(method)
if err != nil {
return nil, err
}
if len(players) == 0 {
return nil, nil
}
return method.Object(players[0], "/org/mpris/MediaPlayer2"), nil
}
// parsePropertiesChanged parses a DBus PropertiesChanged signal
func parsePropertiesChanged(msg *dbus.Message) (iface string, changed map[string]dbus.Variant, ok bool) {
if len(msg.Body) != 3 {
return "", nil, false
}
iface, ok = msg.Body[0].(string)
if !ok {
return
}
changed, ok = msg.Body[1].(map[string]dbus.Variant)
if !ok {
return
}
return
}
-209
View File
@@ -1,209 +0,0 @@
package infinitime
import (
"archive/zip"
"encoding/json"
"io"
"os"
"path/filepath"
"go.elara.ws/infinitime/blefs"
)
// ResourceOperation represents an operation performed during
// resource loading
type ResourceOperation uint8
const (
// ResourceOperationUpload represents the upload phase
// of resource loading
ResourceOperationUpload = iota
// ResourceOperationRemoveObsolete represents the obsolete
// file removal phase of resource loading
ResourceOperationRemoveObsolete
)
// ResourceManifest is the structure of the resource manifest file
type ResourceManifest struct {
Resources []Resource `json:"resources"`
Obsolete []ObsoleteResource `json:"obsolete_files"`
}
// Resource represents a resource entry in the manifest
type Resource struct {
Name string `json:"filename"`
Path string `json:"path"`
}
// ObsoleteResource represents an obsolete file entry in the manifest
type ObsoleteResource struct {
Path string `json:"path"`
Since string `json:"since"`
}
// ResourceLoadProgress contains information on the progress of
// a resource load
type ResourceLoadProgress struct {
Operation ResourceOperation
Name string
Total int64
Sent int64
Err error
}
// LoadResources accepts a resources zip file and a BLE FS.
// It loads the resources from the zip onto the FS.
func LoadResources(file *os.File, fs *blefs.FS) (<-chan ResourceLoadProgress, error) {
out := make(chan ResourceLoadProgress, 10)
fi, err := file.Stat()
if err != nil {
return nil, err
}
r, err := zip.NewReader(file, fi.Size())
if err != nil {
return nil, err
}
m, err := r.Open("resources.json")
if err != nil {
return nil, err
}
defer m.Close()
var manifest ResourceManifest
err = json.NewDecoder(m).Decode(&manifest)
if err != nil {
return nil, err
}
m.Close()
log.Debug("Decoded manifest file").Send()
go func() {
defer close(out)
for _, file := range manifest.Obsolete {
name := filepath.Base(file.Path)
log.Debug("Removing file").Str("file", file.Path).Send()
err := fs.RemoveAll(file.Path)
if err != nil {
out <- ResourceLoadProgress{
Name: name,
Operation: ResourceOperationRemoveObsolete,
Err: err,
}
return
}
log.Debug("Removed file").Str("file", file.Path).Send()
out <- ResourceLoadProgress{
Name: name,
Operation: ResourceOperationRemoveObsolete,
}
}
for _, file := range manifest.Resources {
src, err := r.Open(file.Name)
if err != nil {
out <- ResourceLoadProgress{
Name: file.Name,
Operation: ResourceOperationUpload,
Err: err,
}
return
}
srcFi, err := src.Stat()
if err != nil {
out <- ResourceLoadProgress{
Name: file.Name,
Operation: ResourceOperationUpload,
Total: srcFi.Size(),
Err: err,
}
src.Close()
return
}
log.Debug("Making directories").Str("file", file.Path).Send()
err = fs.MkdirAll(filepath.Dir(file.Path))
if err != nil {
log.Debug("Error making directories").Err(err).Send()
out <- ResourceLoadProgress{
Name: file.Name,
Operation: ResourceOperationUpload,
Total: srcFi.Size(),
Err: err,
}
src.Close()
return
}
log.Debug("Creating file").
Str("file", file.Path).
Int64("size", srcFi.Size()).
Send()
dst, err := fs.Create(file.Path, uint32(srcFi.Size()))
if err != nil {
log.Debug("Error creating file").Err(err).Send()
out <- ResourceLoadProgress{
Name: file.Name,
Operation: ResourceOperationUpload,
Total: srcFi.Size(),
Err: err,
}
src.Close()
return
}
progCh := dst.Progress()
go func() {
for sent := range progCh {
log.Debug("Progress event sent").
Int64("total", srcFi.Size()).
Uint32("sent", sent).
Send()
out <- ResourceLoadProgress{
Name: file.Name,
Operation: ResourceOperationUpload,
Total: srcFi.Size(),
Sent: int64(sent),
}
if sent == uint32(srcFi.Size()) {
return
}
}
}()
n, err := io.Copy(dst, src)
if err != nil {
log.Debug("Error writing to file").Err(err).Send()
out <- ResourceLoadProgress{
Name: file.Name,
Operation: ResourceOperationUpload,
Total: srcFi.Size(),
Sent: n,
Err: err,
}
src.Close()
dst.Close()
return
}
src.Close()
dst.Close()
}
}()
return out, nil
}
+2 -5
View File
@@ -81,12 +81,9 @@ type TimelineHeader struct {
// NewHeader creates and populates a new timeline header // NewHeader creates and populates a new timeline header
// and returns it // and returns it
func NewHeader(t time.Time, evtType EventType, expires time.Duration) TimelineHeader { func NewHeader(evtType EventType, expires time.Duration) TimelineHeader {
_, offset := t.Zone()
t = t.Add(time.Duration(offset) * time.Second)
return TimelineHeader{ return TimelineHeader{
Timestamp: uint64(t.Unix()), Timestamp: uint64(time.Now().Unix()),
Expires: uint32(expires.Seconds()), Expires: uint32(expires.Seconds()),
EventType: evtType, EventType: evtType,
} }