forked from Elara6331/infinitime
		
	Implement BLE filesystem (experimental and will change in the future)
This commit is contained in:
		
							
								
								
									
										54
									
								
								blefs/basic.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								blefs/basic.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					package blefs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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 == FSStatusError {
 | 
				
			||||||
 | 
						// If status is not ok, return error
 | 
				
			||||||
 | 
							return FSError{status}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										156
									
								
								blefs/dir.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								blefs/dir.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,156 @@
 | 
				
			|||||||
 | 
					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,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										61
									
								
								blefs/error.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								blefs/error.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
				
			|||||||
 | 
					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")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										350
									
								
								blefs/file.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										350
									
								
								blefs/file.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,350 @@
 | 
				
			|||||||
 | 
					package blefs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"io/fs"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// File represents a file on the BLE filesystem
 | 
				
			||||||
 | 
					type File struct {
 | 
				
			||||||
 | 
						fs            *FS
 | 
				
			||||||
 | 
						path          string
 | 
				
			||||||
 | 
						offset        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) (fs.File, error) {
 | 
				
			||||||
 | 
						// 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,
 | 
				
			||||||
 | 
							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) {
 | 
				
			||||||
 | 
						// 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,
 | 
				
			||||||
 | 
							isReadOnly:  false,
 | 
				
			||||||
 | 
							isWriteOnly: true,
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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.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,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// 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.amtTferd += chunkLen
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return int(fl.offset), 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 implements the fs.File interface.
 | 
				
			||||||
 | 
					// It just returns nil.
 | 
				
			||||||
 | 
					func (fl *File) Close() error {
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Stat does a RedDir() and finds the current file in the output
 | 
				
			||||||
 | 
					func (fl *File) Stat() (fs.FileInfo, error) {
 | 
				
			||||||
 | 
						// Get directory in filepath
 | 
				
			||||||
 | 
						dir := filepath.Dir(fl.path)
 | 
				
			||||||
 | 
						// Read directory
 | 
				
			||||||
 | 
						dirEntries, err := fl.fs.ReadDir(dir)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, entry := range dirEntries {
 | 
				
			||||||
 | 
							// If file name is base name of path
 | 
				
			||||||
 | 
							if entry.Name() == filepath.Base(fl.path) {
 | 
				
			||||||
 | 
								// Return file info
 | 
				
			||||||
 | 
								return entry.Info()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil, ErrFileNotExists
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										47
									
								
								blefs/fileinfo.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								blefs/fileinfo.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										232
									
								
								blefs/fs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										232
									
								
								blefs/fs.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,232 @@
 | 
				
			|||||||
 | 
					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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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 {
 | 
				
			||||||
 | 
						return blefs.transferChar.Properties.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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -11,6 +11,7 @@ import (
 | 
				
			|||||||
	"github.com/muka/go-bluetooth/bluez/profile/adapter"
 | 
						"github.com/muka/go-bluetooth/bluez/profile/adapter"
 | 
				
			||||||
	"github.com/muka/go-bluetooth/bluez/profile/device"
 | 
						"github.com/muka/go-bluetooth/bluez/profile/device"
 | 
				
			||||||
	"github.com/muka/go-bluetooth/bluez/profile/gatt"
 | 
						"github.com/muka/go-bluetooth/bluez/profile/gatt"
 | 
				
			||||||
 | 
						"go.arsenm.dev/infinitime/blefs"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const BTName = "InfiniTime"
 | 
					const BTName = "InfiniTime"
 | 
				
			||||||
@@ -24,6 +25,8 @@ const (
 | 
				
			|||||||
	CurrentTimeChar = "00002a2b-0000-1000-8000-00805f9b34fb"
 | 
						CurrentTimeChar = "00002a2b-0000-1000-8000-00805f9b34fb"
 | 
				
			||||||
	BatteryLvlChar  = "00002a19-0000-1000-8000-00805f9b34fb"
 | 
						BatteryLvlChar  = "00002a19-0000-1000-8000-00805f9b34fb"
 | 
				
			||||||
	HeartRateChar   = "00002a37-0000-1000-8000-00805f9b34fb"
 | 
						HeartRateChar   = "00002a37-0000-1000-8000-00805f9b34fb"
 | 
				
			||||||
 | 
						FSTransferChar  = "adaf0200-4669-6c65-5472-616e73666572"
 | 
				
			||||||
 | 
						FSVersionChar   = "adaf0100-4669-6c65-5472-616e73666572"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Device struct {
 | 
					type Device struct {
 | 
				
			||||||
@@ -37,6 +40,8 @@ type Device struct {
 | 
				
			|||||||
	currentTimeChar *gatt.GattCharacteristic1
 | 
						currentTimeChar *gatt.GattCharacteristic1
 | 
				
			||||||
	battLevelChar   *gatt.GattCharacteristic1
 | 
						battLevelChar   *gatt.GattCharacteristic1
 | 
				
			||||||
	heartRateChar   *gatt.GattCharacteristic1
 | 
						heartRateChar   *gatt.GattCharacteristic1
 | 
				
			||||||
 | 
						fsVersionChar   *gatt.GattCharacteristic1
 | 
				
			||||||
 | 
						fsTransferChar  *gatt.GattCharacteristic1
 | 
				
			||||||
	onReconnect     func()
 | 
						onReconnect     func()
 | 
				
			||||||
	Music           MusicCtrl
 | 
						Music           MusicCtrl
 | 
				
			||||||
	DFU             DFU
 | 
						DFU             DFU
 | 
				
			||||||
@@ -311,6 +316,10 @@ func (i *Device) resolveChars() error {
 | 
				
			|||||||
			i.DFU.ctrlPointChar = char
 | 
								i.DFU.ctrlPointChar = char
 | 
				
			||||||
		case DFUPacketChar:
 | 
							case DFUPacketChar:
 | 
				
			||||||
			i.DFU.packetChar = char
 | 
								i.DFU.packetChar = char
 | 
				
			||||||
 | 
							case FSTransferChar:
 | 
				
			||||||
 | 
								i.fsTransferChar = char
 | 
				
			||||||
 | 
							case FSVersionChar:
 | 
				
			||||||
 | 
								i.fsVersionChar = char
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
@@ -642,3 +651,8 @@ func (i *Device) NotifyCall(from string) (<-chan uint8, error) {
 | 
				
			|||||||
	}()
 | 
						}()
 | 
				
			||||||
	return out, nil
 | 
						return out, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FS creates and returns a new filesystem from the device
 | 
				
			||||||
 | 
					func (i *Device) FS() (*blefs.FS, error) {
 | 
				
			||||||
 | 
						return blefs.New(i.fsTransferChar)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user