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 }