Compare commits
6 Commits
master
..
91fbe718e5
| Author | SHA1 | Date | |
|---|---|---|---|
| 91fbe718e5 | |||
| 7f19dfb354 | |||
| ea488067fb | |||
| 08076e6ba2 | |||
| 3eadabe975 | |||
| 21078996c1 |
@@ -1,42 +1,42 @@
|
|||||||
# 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.
|
||||||
|
|
||||||
[](https://pkg.go.dev/go.elara.ws/infinitime)
|
[](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`, `playerctl`, and `pactl` to function. The first two are for bluetooth, and the last two for music control.
|
||||||
|
|
||||||
#### Arch
|
#### Arch
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo pacman -S dbus bluez --needed
|
sudo pacman -S dbus bluez playerctl --needed
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Debian/Ubuntu
|
#### Debian/Ubuntu
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo apt install dbus bluez
|
sudo apt install dbus bluez playerctl
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Fedora
|
#### Fedora
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo dnf install dbus bluez
|
sudo dnf install dbus bluez playerctl
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`pactl` comes with `pulseaudio` or `pipewire-pulse` and should therefore be installed on most systems already.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
@@ -49,7 +49,6 @@ This library currently supports the following features:
|
|||||||
- Battery level
|
- Battery level
|
||||||
- Music control
|
- Music control
|
||||||
- OTA firmware upgrades
|
- OTA firmware upgrades
|
||||||
- Navigation
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
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
|
|
||||||
err := blefs.request(
|
|
||||||
FSCmdMove,
|
|
||||||
true,
|
|
||||||
uint16(len(old)),
|
|
||||||
uint16(len(new)),
|
|
||||||
old,
|
|
||||||
byte(0x00),
|
|
||||||
new,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var status int8
|
|
||||||
// Upon receiving 0x61 (FSResponseMove)
|
|
||||||
blefs.on(FSResponseMove, func(data []byte) error {
|
|
||||||
// Read status byte
|
|
||||||
return decode(data, &status)
|
|
||||||
})
|
|
||||||
// If status is not ok, return error
|
|
||||||
if status != FSStatusOk {
|
|
||||||
return FSError{status}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove removes a file or directory
|
|
||||||
func (blefs *FS) Remove(path string) error {
|
|
||||||
// Create delete request
|
|
||||||
err := blefs.request(
|
|
||||||
FSCmdDelete,
|
|
||||||
true,
|
|
||||||
uint16(len(path)),
|
|
||||||
path,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var status int8
|
|
||||||
// Upon receiving 0x31 (FSResponseDelete)
|
|
||||||
blefs.on(FSResponseDelete, func(data []byte) error {
|
|
||||||
// Read status byte
|
|
||||||
return decode(data, &status)
|
|
||||||
})
|
|
||||||
if status != FSStatusOk {
|
|
||||||
// If status is not ok, return error
|
|
||||||
return FSError{status}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-156
@@ -1,156 +0,0 @@
|
|||||||
package blefs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Mkdir creates a directory at the given path
|
|
||||||
func (blefs *FS) Mkdir(path string) error {
|
|
||||||
// Create make directory request
|
|
||||||
err := blefs.request(
|
|
||||||
FSCmdMkdir,
|
|
||||||
true,
|
|
||||||
uint16(len(path)),
|
|
||||||
padding(4),
|
|
||||||
uint64(time.Now().UnixNano()),
|
|
||||||
path,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var status int8
|
|
||||||
// Upon receiving 0x41 (FSResponseMkdir), read status byte
|
|
||||||
blefs.on(FSResponseMkdir, func(data []byte) error {
|
|
||||||
return decode(data, &status)
|
|
||||||
})
|
|
||||||
// If status not ok, return error
|
|
||||||
if status != FSStatusOk {
|
|
||||||
return FSError{status}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadDir returns a list of directory entries from the given path
|
|
||||||
func (blefs *FS) ReadDir(path string) ([]fs.DirEntry, error) {
|
|
||||||
// Create list directory request
|
|
||||||
err := blefs.request(
|
|
||||||
FSCmdListDir,
|
|
||||||
true,
|
|
||||||
uint16(len(path)),
|
|
||||||
path,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var out []fs.DirEntry
|
|
||||||
for {
|
|
||||||
// Create new directory entry
|
|
||||||
listing := DirEntry{}
|
|
||||||
// Upon receiving 0x50 (FSResponseListDir)
|
|
||||||
blefs.on(FSResponseListDir, func(data []byte) error {
|
|
||||||
// Read data into listing
|
|
||||||
err := decode(
|
|
||||||
data,
|
|
||||||
&listing.status,
|
|
||||||
&listing.pathLen,
|
|
||||||
&listing.entryNum,
|
|
||||||
&listing.entries,
|
|
||||||
&listing.flags,
|
|
||||||
&listing.modtime,
|
|
||||||
&listing.size,
|
|
||||||
&listing.path,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
// If status is not ok, return error
|
|
||||||
if listing.status != FSStatusOk {
|
|
||||||
return nil, FSError{listing.status}
|
|
||||||
}
|
|
||||||
// Stop once entry number equals total entries
|
|
||||||
if listing.entryNum == listing.entries {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// Append listing to slice
|
|
||||||
out = append(out, listing)
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DirEntry represents an entry from a directory listing
|
|
||||||
type DirEntry struct {
|
|
||||||
status int8
|
|
||||||
pathLen uint16
|
|
||||||
entryNum uint32
|
|
||||||
entries uint32
|
|
||||||
flags uint32
|
|
||||||
modtime uint64
|
|
||||||
size uint32
|
|
||||||
path string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the name of the file described by the entry
|
|
||||||
func (de DirEntry) Name() string {
|
|
||||||
return de.path
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDir reports whether the entry describes a directory.
|
|
||||||
func (de DirEntry) IsDir() bool {
|
|
||||||
return de.flags&0b1 == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type returns the type bits for the entry.
|
|
||||||
func (de DirEntry) Type() fs.FileMode {
|
|
||||||
if de.IsDir() {
|
|
||||||
return fs.ModeDir
|
|
||||||
} else {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info returns the FileInfo for the file or subdirectory described by the entry.
|
|
||||||
func (de DirEntry) Info() (fs.FileInfo, error) {
|
|
||||||
return FileInfo{
|
|
||||||
name: de.path,
|
|
||||||
size: de.size,
|
|
||||||
modtime: de.modtime,
|
|
||||||
mode: de.Type(),
|
|
||||||
isDir: de.IsDir(),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (de DirEntry) String() string {
|
|
||||||
var isDirChar rune
|
|
||||||
if de.IsDir() {
|
|
||||||
isDirChar = 'd'
|
|
||||||
} else {
|
|
||||||
isDirChar = '-'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get human-readable value for file size
|
|
||||||
val, unit := bytesHuman(de.size)
|
|
||||||
prec := 0
|
|
||||||
// If value is less than 10, set precision to 1
|
|
||||||
if val < 10 {
|
|
||||||
prec = 1
|
|
||||||
}
|
|
||||||
// Convert float to string
|
|
||||||
valStr := strconv.FormatFloat(val, 'f', prec, 64)
|
|
||||||
|
|
||||||
// Return string formatted like so:
|
|
||||||
// - 10 kB file
|
|
||||||
// or:
|
|
||||||
// d 0 B .
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"%c %3s %-2s %s",
|
|
||||||
isDirChar,
|
|
||||||
valStr,
|
|
||||||
unit,
|
|
||||||
de.path,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
package blefs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrFileNotExists = errors.New("file does not exist")
|
|
||||||
ErrFileReadOnly = errors.New("file is read only")
|
|
||||||
ErrFileWriteOnly = errors.New("file is write only")
|
|
||||||
ErrInvalidOffset = errors.New("invalid file offset")
|
|
||||||
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
|
|
||||||
type FSError struct {
|
|
||||||
Code int8
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error returns the string associated with the error code
|
|
||||||
func (err FSError) Error() string {
|
|
||||||
switch err.Code {
|
|
||||||
case 0x02:
|
|
||||||
return "filesystem error"
|
|
||||||
case 0x05:
|
|
||||||
return "read-only filesystem"
|
|
||||||
case 0x03:
|
|
||||||
return "no such file"
|
|
||||||
case 0x04:
|
|
||||||
return "protocol error"
|
|
||||||
case -5:
|
|
||||||
return "input/output error"
|
|
||||||
case -84:
|
|
||||||
return "filesystem is corrupted"
|
|
||||||
case -2:
|
|
||||||
return "no such directory entry"
|
|
||||||
case -17:
|
|
||||||
return "entry already exists"
|
|
||||||
case -20:
|
|
||||||
return "entry is not a directory"
|
|
||||||
case -39:
|
|
||||||
return "directory is not empty"
|
|
||||||
case -9:
|
|
||||||
return "bad file number"
|
|
||||||
case -27:
|
|
||||||
return "file is too large"
|
|
||||||
case -22:
|
|
||||||
return "invalid parameter"
|
|
||||||
case -28:
|
|
||||||
return "no space left on device"
|
|
||||||
case -12:
|
|
||||||
return "no more memory available"
|
|
||||||
case -61:
|
|
||||||
return "no attr available"
|
|
||||||
case -36:
|
|
||||||
return "file name is too long"
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("unknown error (code %d)", err.Code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-367
@@ -1,367 +0,0 @@
|
|||||||
package blefs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// File represents a file on the BLE filesystem
|
|
||||||
type File struct {
|
|
||||||
fs *FS
|
|
||||||
path string
|
|
||||||
offset uint32
|
|
||||||
offsetCh chan uint32
|
|
||||||
length uint32
|
|
||||||
amtLeft uint32
|
|
||||||
amtTferd uint32
|
|
||||||
isReadOnly bool
|
|
||||||
isWriteOnly bool
|
|
||||||
offsetChanged bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open opens a file and returns it as an fs.File to
|
|
||||||
// satisfy the fs.FS interface
|
|
||||||
func (blefs *FS) Open(path string) (*File, error) {
|
|
||||||
if blefs.readOpen {
|
|
||||||
return nil, ErrReadOpen
|
|
||||||
}
|
|
||||||
blefs.readOpen = true
|
|
||||||
// Make a read file request. This opens the file for reading.
|
|
||||||
err := blefs.request(
|
|
||||||
FSCmdReadFile,
|
|
||||||
true,
|
|
||||||
uint16(len(path)),
|
|
||||||
uint32(0),
|
|
||||||
uint32(blefs.maxData()),
|
|
||||||
path,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &File{
|
|
||||||
fs: blefs,
|
|
||||||
path: path,
|
|
||||||
length: 0,
|
|
||||||
offset: 0,
|
|
||||||
offsetCh: make(chan uint32, 5),
|
|
||||||
isReadOnly: true,
|
|
||||||
isWriteOnly: false,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create makes a new file on the BLE file system and returns it.
|
|
||||||
func (blefs *FS) Create(path string, size uint32) (*File, error) {
|
|
||||||
if blefs.writeOpen {
|
|
||||||
return nil, ErrWriteOpen
|
|
||||||
}
|
|
||||||
blefs.writeOpen = true
|
|
||||||
// Make a write file request. This will create and open a file for writing.
|
|
||||||
err := blefs.request(
|
|
||||||
FSCmdWriteFile,
|
|
||||||
true,
|
|
||||||
uint16(len(path)),
|
|
||||||
uint32(0),
|
|
||||||
uint64(time.Now().UnixNano()),
|
|
||||||
size,
|
|
||||||
path,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &File{
|
|
||||||
fs: blefs,
|
|
||||||
path: path,
|
|
||||||
length: size,
|
|
||||||
amtLeft: size,
|
|
||||||
offset: 0,
|
|
||||||
offsetCh: make(chan uint32, 5),
|
|
||||||
isReadOnly: false,
|
|
||||||
isWriteOnly: true,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns the total size of the opened file
|
|
||||||
func (file *File) Size() uint32 {
|
|
||||||
return file.length
|
|
||||||
}
|
|
||||||
|
|
||||||
// Progress returns a channel that receives the amount
|
|
||||||
// of bytes sent as they are sent
|
|
||||||
func (file *File) Progress() <-chan uint32 {
|
|
||||||
return file.offsetCh
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read reads data from a file into b
|
|
||||||
func (fl *File) Read(b []byte) (n int, err error) {
|
|
||||||
// If file is write only (opened by FS.Create())
|
|
||||||
if fl.isWriteOnly {
|
|
||||||
return 0, ErrFileWriteOnly
|
|
||||||
}
|
|
||||||
|
|
||||||
// If offset has been changed (Seek() called)
|
|
||||||
if fl.offsetChanged {
|
|
||||||
// Create new read file request with the specified offset to restart reading
|
|
||||||
err := fl.fs.request(
|
|
||||||
FSCmdReadFile,
|
|
||||||
true,
|
|
||||||
uint16(len(fl.path)),
|
|
||||||
fl.offset,
|
|
||||||
uint32(fl.fs.maxData()),
|
|
||||||
fl.path,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
// Reset offsetChanged
|
|
||||||
fl.offsetChanged = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get length of b. This will be the maximum amount that can be read.
|
|
||||||
maxLen := uint32(len(b))
|
|
||||||
if maxLen == 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
var buf []byte
|
|
||||||
for {
|
|
||||||
// If amount transfered equals max length
|
|
||||||
if fl.amtTferd == maxLen {
|
|
||||||
// Reset amount transfered
|
|
||||||
fl.amtTferd = 0
|
|
||||||
// Copy buffer contents to b
|
|
||||||
copy(b, buf)
|
|
||||||
// Return max length with no error
|
|
||||||
return int(maxLen), nil
|
|
||||||
}
|
|
||||||
// Create new empty fileReadResponse
|
|
||||||
resp := fileReadResponse{}
|
|
||||||
// Upon receiving 0x11 (FSResponseReadFile)
|
|
||||||
err := fl.fs.on(FSResponseReadFile, func(data []byte) error {
|
|
||||||
// Read binary data into struct
|
|
||||||
err := decode(
|
|
||||||
data,
|
|
||||||
&resp.status,
|
|
||||||
&resp.padding,
|
|
||||||
&resp.offset,
|
|
||||||
&resp.length,
|
|
||||||
&resp.chunkLen,
|
|
||||||
&resp.data,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// If status is not ok
|
|
||||||
if resp.status != FSStatusOk {
|
|
||||||
return FSError{resp.status}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
// If entire file transferred, break
|
|
||||||
if fl.offset == resp.length {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append data returned in response to buffer
|
|
||||||
buf = append(buf, resp.data...)
|
|
||||||
// Set file length
|
|
||||||
fl.length = resp.length
|
|
||||||
// Add returned chunk length to offset and amount transferred
|
|
||||||
fl.offset += resp.chunkLen
|
|
||||||
fl.offsetCh <- fl.offset
|
|
||||||
fl.amtTferd += resp.chunkLen
|
|
||||||
|
|
||||||
// Calculate amount of bytes to be sent in next request
|
|
||||||
chunkLen := min(fl.length-fl.offset, uint32(fl.fs.maxData()))
|
|
||||||
// If after transferring, there will be more data than max length
|
|
||||||
if fl.amtTferd+chunkLen > maxLen {
|
|
||||||
// Set chunk length to amount left to fill max length
|
|
||||||
chunkLen = maxLen - fl.amtTferd
|
|
||||||
}
|
|
||||||
// Make data request. This will return more data from the file.
|
|
||||||
fl.fs.request(
|
|
||||||
FSCmdDataReq,
|
|
||||||
false,
|
|
||||||
byte(FSStatusOk),
|
|
||||||
padding(2),
|
|
||||||
fl.offset,
|
|
||||||
chunkLen,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
close(fl.offsetCh)
|
|
||||||
// Copy buffer contents to b
|
|
||||||
copied := copy(b, buf)
|
|
||||||
// Return amount of bytes copied with EOF error
|
|
||||||
return copied, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes data from b into a file on the BLE filesysyem
|
|
||||||
func (fl *File) Write(b []byte) (n int, err error) {
|
|
||||||
maxLen := uint32(cap(b))
|
|
||||||
// If file is read only (opened by FS.Open())
|
|
||||||
if fl.isReadOnly {
|
|
||||||
return 0, ErrFileReadOnly
|
|
||||||
}
|
|
||||||
|
|
||||||
// If offset has been changed (Seek() called)
|
|
||||||
if fl.offsetChanged {
|
|
||||||
// Create new write file request with the specified offset to restart writing
|
|
||||||
err := fl.fs.request(
|
|
||||||
FSCmdWriteFile,
|
|
||||||
true,
|
|
||||||
uint16(len(fl.path)),
|
|
||||||
fl.offset,
|
|
||||||
uint64(time.Now().UnixNano()),
|
|
||||||
fl.length,
|
|
||||||
fl.path,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
// Reset offsetChanged
|
|
||||||
fl.offsetChanged = false
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
// If amount transfered equals max length
|
|
||||||
if fl.amtTferd == maxLen {
|
|
||||||
// Reset amount transfered
|
|
||||||
fl.amtTferd = 0
|
|
||||||
// Return max length with no error
|
|
||||||
return int(maxLen), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new empty fileWriteResponse
|
|
||||||
resp := fileWriteResponse{}
|
|
||||||
// Upon receiving 0x21 (FSResponseWriteFile)
|
|
||||||
err := fl.fs.on(FSResponseWriteFile, func(data []byte) error {
|
|
||||||
// Read binary data into struct
|
|
||||||
err := decode(
|
|
||||||
data,
|
|
||||||
&resp.status,
|
|
||||||
&resp.padding,
|
|
||||||
&resp.offset,
|
|
||||||
&resp.modtime,
|
|
||||||
&resp.free,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// If status is not ok
|
|
||||||
if resp.status != FSStatusOk {
|
|
||||||
return FSError{resp.status}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
// If no free space left in current file, break
|
|
||||||
if resp.free == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate amount of bytes to be transferred in next request
|
|
||||||
chunkLen := min(fl.length-fl.offset, uint32(fl.fs.maxData()))
|
|
||||||
// If after transferring, there will be more data than max length
|
|
||||||
if fl.amtTferd+chunkLen > maxLen {
|
|
||||||
// Set chunk length to amount left to fill max length
|
|
||||||
chunkLen = maxLen - fl.amtTferd
|
|
||||||
}
|
|
||||||
// Get data from b
|
|
||||||
chunk := b[fl.amtTferd : fl.amtTferd+chunkLen]
|
|
||||||
// Create transfer request. This will transfer the chunk to the file.
|
|
||||||
fl.fs.request(
|
|
||||||
FSCmdTransfer,
|
|
||||||
false,
|
|
||||||
byte(FSStatusOk),
|
|
||||||
padding(2),
|
|
||||||
fl.offset,
|
|
||||||
chunkLen,
|
|
||||||
chunk,
|
|
||||||
)
|
|
||||||
// Add chunk length to offset and amount transferred
|
|
||||||
fl.offset += chunkLen
|
|
||||||
fl.offsetCh <- fl.offset
|
|
||||||
fl.amtTferd += chunkLen
|
|
||||||
}
|
|
||||||
|
|
||||||
close(fl.offsetCh)
|
|
||||||
return int(fl.amtTferd), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteString converts the string to []byte and calls Write()
|
|
||||||
func (fl *File) WriteString(s string) (n int, err error) {
|
|
||||||
return fl.Write([]byte(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seek changes the offset of the file being read/written.
|
|
||||||
// This can only be done once in between reads/writes.
|
|
||||||
func (fl *File) Seek(offset int64, whence int) (int64, error) {
|
|
||||||
if fl.offsetChanged {
|
|
||||||
return int64(fl.offset), ErrOffsetChanged
|
|
||||||
}
|
|
||||||
var newOffset int64
|
|
||||||
switch whence {
|
|
||||||
case io.SeekCurrent:
|
|
||||||
newOffset = int64(fl.offset) + offset
|
|
||||||
case io.SeekStart:
|
|
||||||
newOffset = offset
|
|
||||||
case io.SeekEnd:
|
|
||||||
newOffset = int64(fl.length) + offset
|
|
||||||
default:
|
|
||||||
newOffset = int64(fl.offset)
|
|
||||||
}
|
|
||||||
if newOffset < 0 || uint32(newOffset) > fl.length {
|
|
||||||
return int64(fl.offset), ErrInvalidOffset
|
|
||||||
}
|
|
||||||
fl.offset = uint32(newOffset)
|
|
||||||
fl.offsetChanged = true
|
|
||||||
return int64(newOffset), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close must be called before opening another file
|
|
||||||
func (fl *File) Close() error {
|
|
||||||
if fl.isReadOnly {
|
|
||||||
fl.fs.readOpen = false
|
|
||||||
} else if fl.isWriteOnly {
|
|
||||||
fl.fs.writeOpen = false
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stat does a ReadDir() and finds the current file in the output
|
|
||||||
func (fl *File) Stat() (fs.FileInfo, error) {
|
|
||||||
return fl.fs.Stat(fl.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fileReadResponse represents a response for a read request
|
|
||||||
type fileReadResponse struct {
|
|
||||||
status int8
|
|
||||||
padding [2]byte
|
|
||||||
offset uint32
|
|
||||||
length uint32
|
|
||||||
chunkLen uint32
|
|
||||||
data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// fileWriteResponse represents a response for a write request
|
|
||||||
type fileWriteResponse struct {
|
|
||||||
status int8
|
|
||||||
padding [2]byte
|
|
||||||
offset uint32
|
|
||||||
modtime uint64
|
|
||||||
free uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// min returns the smaller uint32 out of two given
|
|
||||||
func min(o, t uint32) uint32 {
|
|
||||||
if t < o {
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package blefs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/fs"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FileInfo implements fs.FileInfo
|
|
||||||
type FileInfo struct {
|
|
||||||
name string
|
|
||||||
size uint32
|
|
||||||
modtime uint64
|
|
||||||
mode fs.FileMode
|
|
||||||
isDir bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the base name of the file
|
|
||||||
func (fi FileInfo) Name() string {
|
|
||||||
return fi.name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns the total size of the file
|
|
||||||
func (fi FileInfo) Size() int64 {
|
|
||||||
return int64(fi.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mode returns the mode of the file
|
|
||||||
func (fi FileInfo) Mode() fs.FileMode {
|
|
||||||
return fi.mode
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModTime returns the modification time of the file
|
|
||||||
// As of now, this is unimplemented in InfiniTime, and
|
|
||||||
// will always return 0.
|
|
||||||
func (fi FileInfo) ModTime() time.Time {
|
|
||||||
return time.Unix(0, int64(fi.modtime))
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDir returns whether the file is a directory
|
|
||||||
func (fi FileInfo) IsDir() bool {
|
|
||||||
return fi.isDir
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sys is unimplemented and returns nil
|
|
||||||
func (fi FileInfo) Sys() interface{} {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-240
@@ -1,240 +0,0 @@
|
|||||||
package blefs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/muka/go-bluetooth/bluez"
|
|
||||||
"github.com/muka/go-bluetooth/bluez/profile/gatt"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrFSUnexpectedResponse = errors.New("unexpected response returned by filesystem")
|
|
||||||
ErrFSResponseTimeout = errors.New("timed out waiting for response")
|
|
||||||
ErrFSError = errors.New("error reported by filesystem")
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
FSStatusOk = 0x01
|
|
||||||
FSStatusError = 0x02
|
|
||||||
)
|
|
||||||
|
|
||||||
// Filesystem command
|
|
||||||
const (
|
|
||||||
FSCmdReadFile = 0x10
|
|
||||||
FSCmdDataReq = 0x12
|
|
||||||
FSCmdWriteFile = 0x20
|
|
||||||
FSCmdTransfer = 0x22
|
|
||||||
FSCmdDelete = 0x30
|
|
||||||
FSCmdMkdir = 0x40
|
|
||||||
FSCmdListDir = 0x50
|
|
||||||
FSCmdMove = 0x60
|
|
||||||
)
|
|
||||||
|
|
||||||
// Filesystem response
|
|
||||||
const (
|
|
||||||
FSResponseReadFile = 0x11
|
|
||||||
FSResponseWriteFile = 0x21
|
|
||||||
FSResponseDelete = 0x31
|
|
||||||
FSResponseMkdir = 0x41
|
|
||||||
FSResponseListDir = 0x51
|
|
||||||
FSResponseMove = 0x61
|
|
||||||
)
|
|
||||||
|
|
||||||
// btOptsCmd cause a write command rather than a wrire request
|
|
||||||
var btOptsCmd = map[string]interface{}{"type": "command"}
|
|
||||||
|
|
||||||
// FS implements the fs.FS interface for the Adafruit BLE FS protocol
|
|
||||||
type FS struct {
|
|
||||||
transferChar *gatt.GattCharacteristic1
|
|
||||||
transferRespCh <-chan *bluez.PropertyChanged
|
|
||||||
readOpen bool
|
|
||||||
writeOpen bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new fs given the transfer characteristic
|
|
||||||
func New(transfer *gatt.GattCharacteristic1) (*FS, error) {
|
|
||||||
// Create new FS instance
|
|
||||||
out := &FS{transferChar: transfer}
|
|
||||||
|
|
||||||
// Start notifications on transfer characteristic
|
|
||||||
err := out.transferChar.StartNotify()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Watch properties of transfer characteristic
|
|
||||||
ch, err := out.transferChar.WatchProperties()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Create buffered channel for propery change events
|
|
||||||
bufCh := make(chan *bluez.PropertyChanged, 10)
|
|
||||||
go func() {
|
|
||||||
// Relay all messages from original channel to buffered
|
|
||||||
for val := range ch {
|
|
||||||
bufCh <- val
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
// Set transfer response channel to buffered channel
|
|
||||||
out.transferRespCh = bufCh
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (blefs *FS) Close() error {
|
|
||||||
return blefs.transferChar.StopNotify()
|
|
||||||
}
|
|
||||||
|
|
||||||
// request makes a request on the transfer characteristic
|
|
||||||
func (blefs *FS) request(cmd byte, padding bool, data ...interface{}) error {
|
|
||||||
// Encode data as binary
|
|
||||||
dataBin, err := encode(data...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
bin := []byte{cmd}
|
|
||||||
if padding {
|
|
||||||
bin = append(bin, 0x00)
|
|
||||||
}
|
|
||||||
// Append encoded data to command with one byte of padding
|
|
||||||
bin = append(bin, dataBin...)
|
|
||||||
// Write value to characteristic
|
|
||||||
err = blefs.transferChar.WriteValue(bin, btOptsCmd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// on waits for the given command to be received on
|
|
||||||
// the control point characteristic, then runs the callback.
|
|
||||||
func (blefs *FS) on(resp byte, onCmdCb func(data []byte) error) error {
|
|
||||||
// Use for loop in case of invalid property
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case propChanged := <-blefs.transferRespCh:
|
|
||||||
// If property was invalid
|
|
||||||
if propChanged.Name != "Value" {
|
|
||||||
// Keep waiting
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Assert propery value as byte slice
|
|
||||||
data := propChanged.Value.([]byte)
|
|
||||||
// If command has prefix of given command
|
|
||||||
if data[0] == resp {
|
|
||||||
// Return callback with data after command
|
|
||||||
return onCmdCb(data[1:])
|
|
||||||
}
|
|
||||||
return ErrFSUnexpectedResponse
|
|
||||||
case <-time.After(time.Minute):
|
|
||||||
return ErrFSResponseTimeout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// encode encodes go values to binary
|
|
||||||
func encode(data ...interface{}) ([]byte, error) {
|
|
||||||
// Create new buffer
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
// For every data element
|
|
||||||
for _, elem := range data {
|
|
||||||
switch val := elem.(type) {
|
|
||||||
case string:
|
|
||||||
// Write string to buffer
|
|
||||||
if _, err := buf.WriteString(val); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case []byte:
|
|
||||||
// Write bytes to buffer
|
|
||||||
if _, err := buf.Write(val); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// Encode and write value as little endian binary
|
|
||||||
if err := binary.Write(buf, binary.LittleEndian, val); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Return bytes from buffer
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// decode reads binary into pointers given in vals
|
|
||||||
func decode(data []byte, vals ...interface{}) error {
|
|
||||||
offset := 0
|
|
||||||
for _, elem := range vals {
|
|
||||||
// If at end of data, stop
|
|
||||||
if offset == len(data) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
switch val := elem.(type) {
|
|
||||||
case *string:
|
|
||||||
// Set val to string starting from offset
|
|
||||||
*val = string(data[offset:])
|
|
||||||
// Add string length to offset
|
|
||||||
offset += len(data) - offset
|
|
||||||
case *[]byte:
|
|
||||||
// Set val to byte slice starting from offset
|
|
||||||
*val = data[offset:]
|
|
||||||
// Add slice length to offset
|
|
||||||
offset += len(data) - offset
|
|
||||||
default:
|
|
||||||
// Create new reader for data starting from offset
|
|
||||||
reader := bytes.NewReader(data[offset:])
|
|
||||||
// Read binary into value pointer
|
|
||||||
err := binary.Read(reader, binary.LittleEndian, val)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Add size of value to offset
|
|
||||||
offset += binary.Size(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// maxData returns MTU-20. This is the maximum amount of data
|
|
||||||
// to send in a packet. Subtracting 20 ensures that the MTU
|
|
||||||
// is never exceeded.
|
|
||||||
func (blefs *FS) maxData() uint16 {
|
|
||||||
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.
|
|
||||||
func padding(len int) []byte {
|
|
||||||
return make([]byte, len)
|
|
||||||
}
|
|
||||||
|
|
||||||
// bytesHuman returns a human-readable string for
|
|
||||||
// the amount of bytes inputted.
|
|
||||||
func bytesHuman(b uint32) (float64, string) {
|
|
||||||
const unit = 1000
|
|
||||||
// Set possible units prefixes (PineTime flash is 4MB)
|
|
||||||
units := [2]rune{'k', 'M'}
|
|
||||||
// If amount of bytes is less than smallest unit
|
|
||||||
if b < unit {
|
|
||||||
// Return unchanged with unit "B"
|
|
||||||
return float64(b), "B"
|
|
||||||
}
|
|
||||||
|
|
||||||
div, exp := uint32(unit), 0
|
|
||||||
// Get decimal values and unit prefix index
|
|
||||||
for n := b / unit; n >= unit; n /= unit {
|
|
||||||
div *= unit
|
|
||||||
exp++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create string for full unit
|
|
||||||
unitStr := string([]rune{units[exp], 'B'})
|
|
||||||
|
|
||||||
// Return decimal with unit string
|
|
||||||
return float64(b) / float64(div), unitStr
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package blefs
|
|
||||||
|
|
||||||
import "io/fs"
|
|
||||||
|
|
||||||
type goFS struct {
|
|
||||||
*FS
|
|
||||||
}
|
|
||||||
|
|
||||||
func (iofs goFS) Open(path string) (fs.File, error) {
|
|
||||||
return iofs.FS.Open(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (blefs *FS) GoFS() fs.FS {
|
|
||||||
return goFS{blefs}
|
|
||||||
}
|
|
||||||
+3
-97
@@ -1,117 +1,23 @@
|
|||||||
package infinitime
|
package infinitime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/godbus/dbus/v5"
|
|
||||||
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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultAdapter *adapter.Adapter1
|
var defaultAdapter *adapter.Adapter1
|
||||||
var itdAgent *Agent
|
|
||||||
|
|
||||||
func Init(adapterID string) {
|
|
||||||
conn, err := dbus.SystemBus()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ag := &Agent{}
|
|
||||||
err = agent.ExposeAgent(conn, ag, agent.CapKeyboardDisplay, true)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if adapterID == "" {
|
|
||||||
adapterID = bt.GetDefaultAdapterID()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
func init() {
|
||||||
// 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultAdapter = da
|
defaultAdapter = da
|
||||||
itdAgent = ag
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func Exit() error {
|
func Exit() error {
|
||||||
if defaultAdapter != nil {
|
|
||||||
defaultAdapter.Close()
|
|
||||||
}
|
|
||||||
agent.RemoveAgent(itdAgent)
|
|
||||||
return bt.Exit()
|
return bt.Exit()
|
||||||
}
|
}
|
||||||
|
|
||||||
var errAuthFailed = dbus.NewError("org.bluez.Error.AuthenticationFailed", nil)
|
|
||||||
|
|
||||||
// Agent implements the agent.Agent1Client interface.
|
|
||||||
// It only requires RequestPasskey as that is all InfiniTime
|
|
||||||
// will use.
|
|
||||||
type Agent struct {
|
|
||||||
ReqPasskey func() (uint32, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release returns nil
|
|
||||||
func (*Agent) Release() *dbus.Error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestPinCode returns an empty string and nil
|
|
||||||
func (*Agent) RequestPinCode(device dbus.ObjectPath) (pincode string, err *dbus.Error) {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisplayPinCode returns nil
|
|
||||||
func (*Agent) DisplayPinCode(device dbus.ObjectPath, pincode string) *dbus.Error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestPasskey runs Agent.ReqPasskey and returns the result
|
|
||||||
func (a *Agent) RequestPasskey(device dbus.ObjectPath) (uint32, *dbus.Error) {
|
|
||||||
if a.ReqPasskey == nil {
|
|
||||||
return 0, errAuthFailed
|
|
||||||
}
|
|
||||||
log.Debug("Passkey requested, calling onReqPasskey callback").Send()
|
|
||||||
passkey, err := a.ReqPasskey()
|
|
||||||
if err != nil {
|
|
||||||
return 0, errAuthFailed
|
|
||||||
}
|
|
||||||
return passkey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisplayPasskey returns nil
|
|
||||||
func (*Agent) DisplayPasskey(device dbus.ObjectPath, passkey uint32, entered uint16) *dbus.Error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestConfirmation returns nil
|
|
||||||
func (*Agent) RequestConfirmation(device dbus.ObjectPath, passkey uint32) *dbus.Error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestAuthorization returns nil
|
|
||||||
func (*Agent) RequestAuthorization(device dbus.ObjectPath) *dbus.Error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthorizeService returns nil
|
|
||||||
func (*Agent) AuthorizeService(device dbus.ObjectPath, uuid string) *dbus.Error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cancel returns nil
|
|
||||||
func (*Agent) Cancel() *dbus.Error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path returns "/ws/elara/infinitime/Agent"
|
|
||||||
func (*Agent) Path() dbus.ObjectPath {
|
|
||||||
return "/ws/elara/infinitime/Agent"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface returns "org.bluez.Agent1"
|
|
||||||
func (*Agent) Interface() string {
|
|
||||||
return "org.bluez.Agent1"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@@ -190,23 +191,6 @@ func (dfu *DFU) sendProgress() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// bufferPropsCh resends all messages on in to a new channel that is buffered with 5 elements
|
|
||||||
func bufferPropsCh(in chan *bluez.PropertyChanged) chan *bluez.PropertyChanged {
|
|
||||||
// Create new buffered channel
|
|
||||||
out := make(chan *bluez.PropertyChanged, 5)
|
|
||||||
go func() {
|
|
||||||
// Close channel when underlying channel closed
|
|
||||||
defer close(out)
|
|
||||||
// For every property change on in
|
|
||||||
for prop := range in {
|
|
||||||
// Write to out
|
|
||||||
out <- prop
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
// Return new buffered channel
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start DFU process
|
// Start DFU process
|
||||||
func (dfu *DFU) Start() error {
|
func (dfu *DFU) Start() error {
|
||||||
if dfu.progress == nil {
|
if dfu.progress == nil {
|
||||||
@@ -225,12 +209,10 @@ func (dfu *DFU) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Watch for property changes on control point
|
// Watch for property changes on control point
|
||||||
unbufferedCh, err := dfu.ctrlPointChar.WatchProperties()
|
dfu.ctrlRespCh, err = dfu.ctrlPointChar.WatchProperties()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Buffer properties channel so no changes are missed
|
|
||||||
dfu.ctrlRespCh = bufferPropsCh(unbufferedCh)
|
|
||||||
|
|
||||||
// Run step one
|
// Run step one
|
||||||
err = dfu.stepOne()
|
err = dfu.stepOne()
|
||||||
@@ -297,38 +279,16 @@ func (dfu *DFU) Start() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset reverts all values back to default to prepare for
|
|
||||||
// the next DFU.
|
|
||||||
func (dfu *DFU) Reset() {
|
|
||||||
dfu.bytesRecvd = 0
|
|
||||||
dfu.bytesSent = 0
|
|
||||||
dfu.fwSize = 0
|
|
||||||
dfu.fwSendDone = false
|
|
||||||
dfu.fwImage = nil
|
|
||||||
dfu.initPacket = nil
|
|
||||||
dfu.ctrlRespCh = nil
|
|
||||||
dfu.progress = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
for {
|
|
||||||
select {
|
select {
|
||||||
case propChanged := <-dfu.ctrlRespCh:
|
case propChanged := <-dfu.ctrlRespCh:
|
||||||
// If property was invalid
|
|
||||||
if propChanged.Name != "Value" {
|
if propChanged.Name != "Value" {
|
||||||
// Keep waiting
|
return ErrDFUInvalidResponse
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
// 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
|
||||||
@@ -339,7 +299,6 @@ func (dfu *DFU) on(cmd []byte, onCmdCb func(data []byte) error) error {
|
|||||||
return ErrDFUTimeout
|
return ErrDFUTimeout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func (dfu *DFU) stepOne() error {
|
func (dfu *DFU) stepOne() error {
|
||||||
return dfu.ctrlPointChar.WriteValue(DFUCmdStart, nil)
|
return dfu.ctrlPointChar.WriteValue(DFUCmdStart, nil)
|
||||||
@@ -387,25 +346,16 @@ func (dfu *DFU) stepSeven() error {
|
|||||||
// While send is not done
|
// While send is not done
|
||||||
for !dfu.fwSendDone {
|
for !dfu.fwSendDone {
|
||||||
for i := 0; i < DFUPktRecvInterval; i++ {
|
for i := 0; i < DFUPktRecvInterval; i++ {
|
||||||
amtLeft := dfu.fwSize - int64(dfu.bytesSent)
|
// Create byte slice with segment size
|
||||||
// If no bytes left to send, end transfer
|
segment := make([]byte, DFUSegmentSize)
|
||||||
if amtLeft == 0 {
|
// Write firmware image into slice
|
||||||
|
n, err := dfu.fwImage.Read(segment)
|
||||||
|
// If EOF, send is done
|
||||||
|
if err == io.EOF {
|
||||||
dfu.sendProgress()
|
dfu.sendProgress()
|
||||||
dfu.fwSendDone = true
|
dfu.fwSendDone = true
|
||||||
return nil
|
return nil
|
||||||
}
|
} else if err != nil {
|
||||||
var segment []byte
|
|
||||||
// If amount left is less than segment size
|
|
||||||
if amtLeft < DFUSegmentSize {
|
|
||||||
// Create byte slice with amount left
|
|
||||||
segment = make([]byte, amtLeft)
|
|
||||||
} else {
|
|
||||||
// Create byte slice with segment size
|
|
||||||
segment = make([]byte, DFUSegmentSize)
|
|
||||||
}
|
|
||||||
// Write firmware image into slice
|
|
||||||
_, err := dfu.fwImage.Read(segment)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Write segment to packet characteristic
|
// Write segment to packet characteristic
|
||||||
@@ -413,18 +363,13 @@ 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 += n
|
||||||
}
|
}
|
||||||
// On 0x11, verify packet receipt size
|
// On 0x11, verify packet receipt size
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
module go.elara.ws/infinitime
|
module go.arsenm.dev/infinitime
|
||||||
|
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require github.com/muka/go-bluetooth v0.0.0-20210812063148-b6c83362e27d
|
||||||
github.com/fxamacker/cbor/v2 v2.4.0
|
|
||||||
github.com/godbus/dbus/v5 v5.0.6
|
|
||||||
github.com/muka/go-bluetooth v0.0.0-20220819140550-1d8857e3b268
|
|
||||||
go.elara.ws/logger v0.0.0-20230928062203-85e135cf02ae
|
|
||||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -3,22 +3,15 @@ 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=
|
||||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||||
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
|
github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
|
||||||
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.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro=
|
|
||||||
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-20210812063148-b6c83362e27d h1:EG/xyWjHT19rkUpwsWSkyiCCmyqNwFovr9m10rhyOxU=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/muka/go-bluetooth v0.0.0-20210812063148-b6c83362e27d/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=
|
||||||
@@ -27,21 +20,11 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
|||||||
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/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=
|
|
||||||
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=
|
||||||
@@ -55,12 +38,8 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||||||
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 h1:sIky/MyNRSHTrdxfsiUSS4WIAMvInbeXljJz+jDjeYE=
|
||||||
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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
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/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=
|
||||||
@@ -69,6 +48,5 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
|
|||||||
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=
|
|
||||||
|
|||||||
+182
-683
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package player
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Play uses playerctl to play media
|
||||||
|
func Play() error {
|
||||||
|
return exec.Command("playerctl", "play").Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pause uses playerctl to pause media
|
||||||
|
func Pause() error {
|
||||||
|
return exec.Command("playerctl", "pause").Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next uses playerctl to skip to next media
|
||||||
|
func Next() error {
|
||||||
|
return exec.Command("playerctl", "next").Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prev uses playerctl to skip to previous media
|
||||||
|
func Prev() error {
|
||||||
|
return exec.Command("playerctl", "previous").Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadata uses playerctl to detect music metadata changes
|
||||||
|
func Metadata(key string, onChange func(string)) error {
|
||||||
|
// Execute playerctl command with key and follow flag
|
||||||
|
cmd := exec.Command("playerctl", "metadata", key, "-F")
|
||||||
|
// Get stdout pipe
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
// Read line from command stdout
|
||||||
|
line, _, err := bufio.NewReader(stdout).ReadLine()
|
||||||
|
if err == io.EOF {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Convert line to string
|
||||||
|
data := string(line)
|
||||||
|
// If key unknown, return suitable default
|
||||||
|
if data == "No player could handle this command" || data == "" {
|
||||||
|
data = "Unknown " + strings.Title(key)
|
||||||
|
}
|
||||||
|
// Run the onChange callback
|
||||||
|
onChange(data)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// Start command asynchronously
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Status(onChange func(bool)) error {
|
||||||
|
// Execute playerctl status with follow flag
|
||||||
|
cmd := exec.Command("playerctl", "status", "-F")
|
||||||
|
// Get stdout pipe
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
// Read line from command stdout
|
||||||
|
line, _, err := bufio.NewReader(stdout).ReadLine()
|
||||||
|
if err == io.EOF {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Convert line to string
|
||||||
|
data := string(line)
|
||||||
|
// Run the onChange callback
|
||||||
|
onChange(data == "Playing")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// Start command asynchronously
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
-209
@@ -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
|
|
||||||
}
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
package weather
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Type of weather event
|
|
||||||
type EventType uint8
|
|
||||||
|
|
||||||
// These event types correspond to structs
|
|
||||||
// that can be used to add an event to the
|
|
||||||
// weather timeline
|
|
||||||
const (
|
|
||||||
EventTypeObscuration EventType = iota
|
|
||||||
EventTypePrecipitation
|
|
||||||
EventTypeWind
|
|
||||||
EventTypeTemperature
|
|
||||||
EventTypeAirQuality
|
|
||||||
EventTypeSpecial
|
|
||||||
EventTypePressure
|
|
||||||
EventTypeLocation
|
|
||||||
EventTypeClouds
|
|
||||||
EventTypeHumidity
|
|
||||||
)
|
|
||||||
|
|
||||||
// Special event type
|
|
||||||
type SpecialType uint8
|
|
||||||
|
|
||||||
// See https://git.io/JM7Oe for the meaning of each type
|
|
||||||
const (
|
|
||||||
SpecialTypeSquall SpecialType = iota
|
|
||||||
SpecialTypeTsunami
|
|
||||||
SpecialTypeTornado
|
|
||||||
SpecialTypeFire
|
|
||||||
SpecialTypeThunder
|
|
||||||
)
|
|
||||||
|
|
||||||
// Precipitation type
|
|
||||||
type PrecipitationType uint8
|
|
||||||
|
|
||||||
// See https://git.io/JM7YM for the meaning of each type
|
|
||||||
const (
|
|
||||||
PrecipitationTypeNone PrecipitationType = iota
|
|
||||||
PrecipitationTypeRain
|
|
||||||
PrecipitationTypeDrizzle
|
|
||||||
PrecipitationTypeFreezingRain
|
|
||||||
PrecipitationTypeSleet
|
|
||||||
PrecipitationTypeHail
|
|
||||||
PrecipitationTypeSmallHail
|
|
||||||
PrecipitationTypeSnow
|
|
||||||
PrecipitationTypeSnowGrains
|
|
||||||
PrecipitationTypeIceCrystals
|
|
||||||
PrecipitationTypeAsh
|
|
||||||
)
|
|
||||||
|
|
||||||
// Visibility obscuration type
|
|
||||||
type ObscurationType uint8
|
|
||||||
|
|
||||||
// See https://git.io/JM7Yd for the meaning of each type
|
|
||||||
const (
|
|
||||||
ObscurationTypeNone ObscurationType = iota
|
|
||||||
ObscurationTypeFog
|
|
||||||
ObscurationTypeHaze
|
|
||||||
ObscurationTypeSmoke
|
|
||||||
ObscurationTypeAsh
|
|
||||||
ObscurationTypeDust
|
|
||||||
ObscurationTypeSand
|
|
||||||
ObscurationTypeMist
|
|
||||||
ObscurationTypePrecipitation
|
|
||||||
)
|
|
||||||
|
|
||||||
// TimelineHeader contains the header for a timeline envent
|
|
||||||
type TimelineHeader struct {
|
|
||||||
// UNIX timestamp with timezone offset
|
|
||||||
Timestamp uint64
|
|
||||||
// Seconds until the event expires
|
|
||||||
Expires uint32
|
|
||||||
// Type of weather event
|
|
||||||
EventType EventType
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHeader creates and populates a new timeline header
|
|
||||||
// and returns it
|
|
||||||
func NewHeader(t time.Time, evtType EventType, expires time.Duration) TimelineHeader {
|
|
||||||
_, offset := t.Zone()
|
|
||||||
t = t.Add(time.Duration(offset) * time.Second)
|
|
||||||
|
|
||||||
return TimelineHeader{
|
|
||||||
Timestamp: uint64(t.Unix()),
|
|
||||||
Expires: uint32(expires.Seconds()),
|
|
||||||
EventType: evtType,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloudsEvent corresponds to EventTypeClouds
|
|
||||||
type CloudsEvent struct {
|
|
||||||
TimelineHeader
|
|
||||||
// Cloud coverage percentage
|
|
||||||
Amount uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
// ObscurationEvent corresponds to EventTypeObscuration
|
|
||||||
type ObscurationEvent struct {
|
|
||||||
TimelineHeader
|
|
||||||
// Type of obscuration
|
|
||||||
Type ObscurationType
|
|
||||||
// Visibility in meters. 65535 is unspecified.
|
|
||||||
Amount uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrecipitationEvent corresponds to EventTypePrecipitation
|
|
||||||
type PrecipitationEvent struct {
|
|
||||||
TimelineHeader
|
|
||||||
// Type of precipitation
|
|
||||||
Type PrecipitationType
|
|
||||||
// Amount of rain in millimeters. 255 is unspecified.
|
|
||||||
Amount uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
// WindEvent corresponds to EventTypeWind
|
|
||||||
type WindEvent struct {
|
|
||||||
TimelineHeader
|
|
||||||
// Minimum speed in meters per second
|
|
||||||
SpeedMin uint8
|
|
||||||
// Maximum speed in meters per second
|
|
||||||
SpeedMax uint8
|
|
||||||
// Unitless direction, about 1 unit per 0.71 degrees.
|
|
||||||
DirectionMin uint8
|
|
||||||
// Unitless direction, about 1 unit per 0.71 degrees
|
|
||||||
DirectionMax uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
// TemperatureEvent corresponds to EventTypeTemperature
|
|
||||||
type TemperatureEvent struct {
|
|
||||||
TimelineHeader
|
|
||||||
// Temperature in celcius multiplied by 100.
|
|
||||||
// -32768 is "no data"
|
|
||||||
Temperature int16
|
|
||||||
// Dew point in celcius multiplied by 100.
|
|
||||||
// -32768 is "no data"
|
|
||||||
DewPoint int16
|
|
||||||
}
|
|
||||||
|
|
||||||
// LocationEvent corresponds to EventTypeLocation
|
|
||||||
type LocationEvent struct {
|
|
||||||
TimelineHeader
|
|
||||||
// Location name
|
|
||||||
Location string
|
|
||||||
// Altitude from sea level in meters
|
|
||||||
Altitude int16
|
|
||||||
// EPSG:3857 latitude (Google Maps, Openstreetmaps)
|
|
||||||
Latitude int32
|
|
||||||
// EPSG:3857 longitude (Google Maps, Openstreetmaps)
|
|
||||||
Longitude int32
|
|
||||||
}
|
|
||||||
|
|
||||||
// HumidityEvent corresponds to EventTypeHumidity
|
|
||||||
type HumidityEvent struct {
|
|
||||||
TimelineHeader
|
|
||||||
// Relative humidity percentage
|
|
||||||
Humidity uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
// PressureEvent corresponds to EventTypePressure
|
|
||||||
type PressureEvent struct {
|
|
||||||
TimelineHeader
|
|
||||||
// Air pressure in hectopascals (hPa)
|
|
||||||
Pressure int16
|
|
||||||
}
|
|
||||||
|
|
||||||
// SpecialEvent corresponds to EventTypeSpecial
|
|
||||||
type SpecialEvent struct {
|
|
||||||
TimelineHeader
|
|
||||||
// Type of special event
|
|
||||||
Type SpecialType
|
|
||||||
}
|
|
||||||
|
|
||||||
// AirQualityEvent corresponds to EventTypeAirQuality
|
|
||||||
type AirQualityEvent struct {
|
|
||||||
TimelineHeader
|
|
||||||
// Name of the polluting item
|
|
||||||
//
|
|
||||||
// Do not localize the name. That should be handled by the watch.
|
|
||||||
//
|
|
||||||
// For particulate matter, use "PM0.1"`, "PM5", or "PM10".
|
|
||||||
//
|
|
||||||
// For chemicals, use the molecular formula ("NO2", "CO2", or "O3").
|
|
||||||
//
|
|
||||||
// For pollen, use the genus of the plant.
|
|
||||||
Polluter string
|
|
||||||
// Amount of pollution in SI units
|
|
||||||
//
|
|
||||||
// https://ec.europa.eu/environment/air/quality/standards.htm
|
|
||||||
// http://www.ourair.org/wp-content/uploads/2012-aaqs2.pdf
|
|
||||||
Amount uint32
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user