added local time characteristic #4

Merged
Elara6331 merged 5 commits from cybuzuma/infinitime:gatt_localtime into master 2022-11-21 16:51:50 +00:00
15 changed files with 572 additions and 57 deletions
Showing only changes of commit ce90459491 - Show all commits

View File

@ -15,24 +15,24 @@ This library's import path is `go.arsenm.dev/infinitime`.
### Dependencies
This library requires `dbus`, `bluez`, and `pactl` to function. These allow the library to use bluetooth, control media, control volume, etc.
This library requires `dbus`, and `bluez` to function. These allow the library to use bluetooth, control media, control volume, etc.
#### Arch
```shell
sudo pacman -S dbus bluez libpulse --needed
sudo pacman -S dbus bluez --needed
```
#### Debian/Ubuntu
```shell
sudo apt install dbus bluez pulseaudio-utils
sudo apt install dbus bluez
```
#### Fedora
```shell
sudo dnf install dbus bluez pulseaudio-utils
sudo dnf install dbus bluez
```
---
@ -47,6 +47,7 @@ This library currently supports the following features:
- Battery level
- Music control
- OTA firmware upgrades
- Navigation
---

95
blefs/all.go Normal file
View File

@ -0,0 +1,95 @@
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
}

View File

@ -1,5 +1,29 @@
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
func (blefs *FS) Rename(old, new string) error {
// Create move request

View File

@ -13,6 +13,7 @@ var (
ErrOffsetChanged = errors.New("offset has already been changed")
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")
ErrNoRemoveRoot = errors.New("refusing to remove root directory")
)
// FSError represents an error returned by BLE FS

View File

@ -3,7 +3,6 @@ package blefs
import (
"io"
"io/fs"
"path/filepath"
"time"
)
@ -291,7 +290,7 @@ func (fl *File) Write(b []byte) (n int, err error) {
}
close(fl.offsetCh)
return int(fl.offset), nil
return int(fl.amtTferd), nil
}
// WriteString converts the string to []byte and calls Write()
@ -335,23 +334,9 @@ func (fl *File) Close() error {
return nil
}
// Stat does a RedDir() and finds the current file in the output
// Stat does a ReadDir() and finds the current file in the output
func (fl *File) Stat() (fs.FileInfo, error) {
// 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
return fl.fs.Stat(fl.path)
}
// fileReadResponse represents a response for a read request

View File

@ -199,7 +199,13 @@ func decode(data []byte, vals ...interface{}) error {
// to send in a packet. Subtracting 20 ensures that the MTU
// is never exceeded.
func (blefs *FS) maxData() uint16 {
return blefs.transferChar.Properties.MTU - 20
mtu := blefs.transferChar.Properties.MTU
// 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.

2
go.mod
View File

@ -5,7 +5,7 @@ go 1.16
require (
github.com/fxamacker/cbor/v2 v2.4.0
github.com/godbus/dbus/v5 v5.0.6
github.com/muka/go-bluetooth v0.0.0-20220219050759-674a63b8741a
github.com/muka/go-bluetooth v0.0.0-20220819140550-1d8857e3b268
github.com/rs/zerolog v1.26.1
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
)

4
go.sum
View File

@ -15,8 +15,8 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJ
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/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/muka/go-bluetooth v0.0.0-20220219050759-674a63b8741a h1:fnzS9RRQW8B5AgNCxkN0vJ/AoX+Xfqk3sAYon3iVrzA=
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/paypal/gatt v0.0.0-20151011220935-4ae819d591cf/go.mod h1:+AwQL2mK3Pd3S+TUwg0tYQjid0q1txyNUJuuSmz8Kdk=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

View File

@ -54,6 +54,10 @@ var charNames = map[string]string{
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 {
@ -70,9 +74,11 @@ type Device struct {
fsVersionChar *gatt.GattCharacteristic1
fsTransferChar *gatt.GattCharacteristic1
weatherDataChar *gatt.GattCharacteristic1
weatherdataChar *gatt.GattCharacteristic1
notifEventCh chan uint8
notifEventDone bool
Music MusicCtrl
Navigation NavigationService
DFU DFU
}
@ -133,6 +139,7 @@ func Connect(ctx context.Context, opts *Options) (*Device, error) {
// Create new device
out := &Device{device: btDev}
out.Navigation = NavigationService{dev: out}
// Resolve characteristics
err = out.resolveChars()
@ -397,6 +404,14 @@ func (i *Device) resolveChars() error {
charResolved := true
// Set correct characteristics
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:
i.newAlertChar = char
case NotifEventChar:

151
navigation.go Normal file
View File

@ -0,0 +1,151 @@
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().Str("func", "SetFlag").Msg("Sending flag")
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().Str("func", "SetNarrative").Msg("Sending narrative")
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().Str("func", "SetNarrative").Msg("Sending maneuver distance")
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().Str("func", "SetNarrative").Msg("Sending progress")
if err := n.dev.checkStatus(n.progressChar, NavProgressChar); err != nil {
return err
}
return n.progressChar.WriteValue([]byte{progress}, nil)
}

View File

@ -1,16 +0,0 @@
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()
}

View File

@ -107,6 +107,46 @@ func Prev() error {
return nil
}
func VolUp(percent uint) error {
player, err := getPlayerObj()
if err != nil {
return err
}
if player != nil {
currentVal, err := player.GetProperty("org.mpris.MediaPlayer2.Player.Volume")
if err != nil {
return err
}
newVal := currentVal.Value().(float64) + (float64(percent) / 100)
err = player.SetProperty("org.mpris.MediaPlayer2.Player.Volume", newVal)
if err != nil {
return err
}
}
return nil
}
func VolDown(percent uint) error {
player, err := getPlayerObj()
if err != nil {
return err
}
if player != nil {
currentVal, err := player.GetProperty("org.mpris.MediaPlayer2.Player.Volume")
if err != nil {
return err
}
newVal := currentVal.Value().(float64) - (float64(percent) / 100)
err = player.SetProperty("org.mpris.MediaPlayer2.Player.Volume", newVal)
if err != nil {
return err
}
}
return nil
}
type ChangeType int
const (

209
resources.go Normal file
View File

@ -0,0 +1,209 @@
package infinitime
import (
"archive/zip"
"encoding/json"
"io"
"os"
"path/filepath"
"go.arsenm.dev/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().Msg("Decoded manifest file")
go func() {
defer close(out)
for _, file := range manifest.Obsolete {
name := filepath.Base(file.Path)
log.Debug().Str("file", file.Path).Msg("Removing file")
err := fs.RemoveAll(file.Path)
if err != nil {
out <- ResourceLoadProgress{
Name: name,
Operation: ResourceOperationRemoveObsolete,
Err: err,
}
return
}
log.Debug().Str("file", file.Path).Msg("Removed file")
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().Str("file", file.Path).Msg("Making directories")
err = fs.MkdirAll(filepath.Dir(file.Path))
if err != nil {
log.Debug().Err(err).Msg("Error making directories")
out <- ResourceLoadProgress{
Name: file.Name,
Operation: ResourceOperationUpload,
Total: srcFi.Size(),
Err: err,
}
src.Close()
return
}
log.Debug().
Str("file", file.Path).
Int64("size", srcFi.Size()).
Msg("Creating file")
dst, err := fs.Create(file.Path, uint32(srcFi.Size()))
if err != nil {
log.Debug().Err(err).Msg("Error creating file")
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().
Int64("total", srcFi.Size()).
Uint32("sent", sent).
Msg("Progress event sent")
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().Err(err).Msg("Error writing to file")
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
}

View File

@ -82,8 +82,12 @@ type TimelineHeader struct {
// NewHeader creates and populates a new timeline header
// and returns it
func NewHeader(evtType EventType, expires time.Duration) TimelineHeader {
now := time.Now()
_, offset := now.Zone()
now = now.Add(time.Duration(offset) * time.Second)
return TimelineHeader{
Timestamp: uint64(time.Now().Unix()),
Timestamp: uint64(now.Unix()),
Expires: uint32(expires.Seconds()),
EventType: evtType,
}