infinitime/blefs/fs.go
2022-10-25 12:36:34 -07:00

241 lines
5.9 KiB
Go

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
}