forked from Elara6331/infinitime
		
	Compare commits
	
		
			25 Commits
		
	
	
		
			9ed74726c4
			...
			gatt_local
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ce90459491 | |||
| 0c369dc5df | |||
|  | 5fb5cf4b92 | ||
|  | 76809726d5 | ||
| 72b558707e | |||
| 31f4c51a61 | |||
| 8e5bbafba8 | |||
| e6fb402c0c | |||
| 1f5301f5de | |||
| 0ad671d3f5 | |||
| 54fdd19bec | |||
| a01f1b9d92 | |||
| bb017b471e | |||
| 01970b2bb7 | |||
| b476853dc0 | |||
| 3e9957d419 | |||
| b5d345cdec | |||
| 0eead333b7 | |||
| e4c12d32a1 | |||
| 5af53d1dc6 | |||
| d199fba93c | |||
| f56be08106 | |||
| 49cde2b3c2 | |||
| ee06b34281 | |||
| e10697448c | 
							
								
								
									
										11
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								README.md
									
									
									
									
									
								
							| @@ -15,24 +15,24 @@ This library's import path is `go.arsenm.dev/infinitime`. | ||||
|  | ||||
| ### Dependencies | ||||
|  | ||||
| This library requires `dbus`, `bluez`, and `pactl` to function. These allow the library to use bluetooth, control media, control volume, etc. | ||||
| This library requires `dbus`, and `bluez` to function. These allow the library to use bluetooth, control media, control volume, etc. | ||||
|  | ||||
| #### Arch | ||||
|  | ||||
| ```shell | ||||
| sudo pacman -S dbus bluez libpulse --needed | ||||
| sudo pacman -S dbus bluez --needed | ||||
| ``` | ||||
|  | ||||
| #### Debian/Ubuntu | ||||
|  | ||||
| ```shell | ||||
| sudo apt install dbus bluez pulseaudio-utils | ||||
| sudo apt install dbus bluez | ||||
| ``` | ||||
|  | ||||
| #### Fedora | ||||
|  | ||||
| ```shell | ||||
| sudo dnf install dbus bluez pulseaudio-utils | ||||
| sudo dnf install dbus bluez | ||||
| ``` | ||||
|  | ||||
| --- | ||||
| @@ -47,9 +47,10 @@ This library currently supports the following features: | ||||
| - Battery level | ||||
| - Music control | ||||
| - OTA firmware upgrades | ||||
| - Navigation | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### Mentions | ||||
|  | ||||
| The DFU process used in this library was created with the help of [siglo](https://github.com/alexr4535/siglo)'s source code. Specifically, this file: [ble_dfu.py](https://github.com/alexr4535/siglo/blob/main/src/ble_dfu.py) | ||||
| The DFU process used in this library was created with the help of [siglo](https://github.com/alexr4535/siglo)'s source code. Specifically, this file: [ble_dfu.py](https://github.com/alexr4535/siglo/blob/main/src/ble_dfu.py) | ||||
|   | ||||
							
								
								
									
										95
									
								
								blefs/all.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								blefs/all.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | ||||
| package blefs | ||||
|  | ||||
| import ( | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| func (blefs *FS) RemoveAll(path string) error { | ||||
| 	if path == "" { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	if filepath.Clean(path) == "/" { | ||||
| 		return ErrNoRemoveRoot | ||||
| 	} | ||||
|  | ||||
| 	fi, err := blefs.Stat(path) | ||||
| 	if err != nil { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	if fi.IsDir() { | ||||
| 		return blefs.removeAllChildren(path) | ||||
| 	} else { | ||||
| 		err = blefs.Remove(path) | ||||
|  | ||||
| 		var code int8 | ||||
| 		if err, ok := err.(FSError); ok { | ||||
| 			code = err.Code | ||||
| 		} | ||||
|  | ||||
| 		if err != nil && code != -2 { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (blefs *FS) removeAllChildren(path string) error { | ||||
| 	list, err := blefs.ReadDir(path) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, entry := range list { | ||||
| 		name := entry.Name() | ||||
|  | ||||
| 		if name == "." || name == ".." { | ||||
| 			continue | ||||
| 		} | ||||
| 		entryPath := filepath.Join(path, name) | ||||
|  | ||||
| 		if entry.IsDir() { | ||||
| 			err = blefs.removeAllChildren(entryPath) | ||||
| 		} else { | ||||
| 			err = blefs.Remove(entryPath) | ||||
| 		} | ||||
|  | ||||
| 		var code int8 | ||||
| 		if err, ok := err.(FSError); ok { | ||||
| 			code = err.Code | ||||
| 		} | ||||
|  | ||||
| 		if err != nil && code != -2 { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (blefs *FS) MkdirAll(path string) error { | ||||
| 	if path == "" || path == "/" { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	splitPath := strings.Split(path, "/") | ||||
| 	for i := 1; i < len(splitPath); i++ { | ||||
| 		curPath := strings.Join(splitPath[0:i+1], "/") | ||||
|  | ||||
| 		err := blefs.Mkdir(curPath) | ||||
|  | ||||
| 		var code int8 | ||||
| 		if err, ok := err.(FSError); ok { | ||||
| 			code = err.Code | ||||
| 		} | ||||
|  | ||||
| 		if err != nil && code != -17 { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
| @@ -1,5 +1,29 @@ | ||||
| package blefs | ||||
|  | ||||
| import ( | ||||
| 	"io/fs" | ||||
| 	"path/filepath" | ||||
| ) | ||||
|  | ||||
| // Stat does a ReadDir() and finds the current file in the output | ||||
| func (blefs *FS) Stat(path string) (fs.FileInfo, error) { | ||||
| 	// Get directory in filepath | ||||
| 	dir := filepath.Dir(path) | ||||
| 	// Read directory | ||||
| 	dirEntries, err := blefs.ReadDir(dir) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	for _, entry := range dirEntries { | ||||
| 		// If file name is base name of path | ||||
| 		if entry.Name() == filepath.Base(path) { | ||||
| 			// Return file info | ||||
| 			return entry.Info() | ||||
| 		} | ||||
| 	} | ||||
| 	return nil, ErrFileNotExists | ||||
| } | ||||
|  | ||||
| // Rename moves or renames a file or directory | ||||
| func (blefs *FS) Rename(old, new string) error { | ||||
| 	// Create move request | ||||
|   | ||||
| @@ -13,6 +13,7 @@ var ( | ||||
| 	ErrOffsetChanged = errors.New("offset has already been changed") | ||||
| 	ErrReadOpen      = errors.New("only one file can be opened for reading at a time") | ||||
| 	ErrWriteOpen     = errors.New("only one file can be opened for writing at a time") | ||||
| 	ErrNoRemoveRoot  = errors.New("refusing to remove root directory") | ||||
| ) | ||||
|  | ||||
| // FSError represents an error returned by BLE FS | ||||
|   | ||||
| @@ -3,7 +3,6 @@ package blefs | ||||
| import ( | ||||
| 	"io" | ||||
| 	"io/fs" | ||||
| 	"path/filepath" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| @@ -291,7 +290,7 @@ func (fl *File) Write(b []byte) (n int, err error) { | ||||
| 	} | ||||
|  | ||||
| 	close(fl.offsetCh) | ||||
| 	return int(fl.offset), nil | ||||
| 	return int(fl.amtTferd), nil | ||||
| } | ||||
|  | ||||
| // WriteString converts the string to []byte and calls Write() | ||||
| @@ -335,23 +334,9 @@ func (fl *File) Close() error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Stat does a RedDir() and finds the current file in the output | ||||
| // Stat does a ReadDir() and finds the current file in the output | ||||
| func (fl *File) Stat() (fs.FileInfo, error) { | ||||
| 	// Get directory in filepath | ||||
| 	dir := filepath.Dir(fl.path) | ||||
| 	// Read directory | ||||
| 	dirEntries, err := fl.fs.ReadDir(dir) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	for _, entry := range dirEntries { | ||||
| 		// If file name is base name of path | ||||
| 		if entry.Name() == filepath.Base(fl.path) { | ||||
| 			// Return file info | ||||
| 			return entry.Info() | ||||
| 		} | ||||
| 	} | ||||
| 	return nil, ErrFileNotExists | ||||
| 	return fl.fs.Stat(fl.path) | ||||
| } | ||||
|  | ||||
| // fileReadResponse represents a response for a read request | ||||
|   | ||||
| @@ -199,7 +199,13 @@ func decode(data []byte, vals ...interface{}) error { | ||||
| // to send in a packet. Subtracting 20 ensures that the MTU | ||||
| // is never exceeded. | ||||
| func (blefs *FS) maxData() uint16 { | ||||
| 	return blefs.transferChar.Properties.MTU - 20 | ||||
| 	mtu := blefs.transferChar.Properties.MTU | ||||
| 	// If MTU is zero, the current version of BlueZ likely | ||||
| 	// doesn't support the MTU property, so assume 256. | ||||
| 	if mtu == 0 { | ||||
| 		mtu = 256 | ||||
| 	} | ||||
| 	return mtu - 20 | ||||
| } | ||||
|  | ||||
| // padding returns a slice of len amount of 0x00. | ||||
|   | ||||
| @@ -12,4 +12,4 @@ func (iofs goFS) Open(path string) (fs.File, error) { | ||||
|  | ||||
| func (blefs *FS) GoFS() fs.FS { | ||||
| 	return goFS{blefs} | ||||
| } | ||||
| } | ||||
|   | ||||
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							| @@ -5,7 +5,7 @@ go 1.16 | ||||
| require ( | ||||
| 	github.com/fxamacker/cbor/v2 v2.4.0 | ||||
| 	github.com/godbus/dbus/v5 v5.0.6 | ||||
| 	github.com/muka/go-bluetooth v0.0.0-20220219050759-674a63b8741a | ||||
| 	github.com/muka/go-bluetooth v0.0.0-20220819140550-1d8857e3b268 | ||||
| 	github.com/rs/zerolog v1.26.1 | ||||
| 	golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect | ||||
| ) | ||||
|   | ||||
							
								
								
									
										4
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.sum
									
									
									
									
									
								
							| @@ -15,8 +15,8 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJ | ||||
| github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= | ||||
| github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | ||||
| github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | ||||
| github.com/muka/go-bluetooth v0.0.0-20220219050759-674a63b8741a h1:fnzS9RRQW8B5AgNCxkN0vJ/AoX+Xfqk3sAYon3iVrzA= | ||||
| github.com/muka/go-bluetooth v0.0.0-20220219050759-674a63b8741a/go.mod h1:dMCjicU6vRBk34dqOmIZm0aod6gUwZXOXzBROqGous0= | ||||
| github.com/muka/go-bluetooth v0.0.0-20220819140550-1d8857e3b268 h1:kOnq7TfaAO2Vc/MHxPqFIXe00y1qBxJAvhctXdko6vo= | ||||
| github.com/muka/go-bluetooth v0.0.0-20220819140550-1d8857e3b268/go.mod h1:dMCjicU6vRBk34dqOmIZm0aod6gUwZXOXzBROqGous0= | ||||
| github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= | ||||
| github.com/paypal/gatt v0.0.0-20151011220935-4ae819d591cf/go.mod h1:+AwQL2mK3Pd3S+TUwg0tYQjid0q1txyNUJuuSmz8Kdk= | ||||
| github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
|   | ||||
| @@ -33,6 +33,7 @@ const ( | ||||
| 	MotionValChar   = "00030002-78fc-48fe-8e23-433b3a1942d0" | ||||
| 	FirmwareVerChar = "00002a26-0000-1000-8000-00805f9b34fb" | ||||
| 	CurrentTimeChar = "00002a2b-0000-1000-8000-00805f9b34fb" | ||||
| 	LocalTimeChar   = "00002a0f-0000-1000-8000-00805f9b34fb" | ||||
| 	BatteryLvlChar  = "00002a19-0000-1000-8000-00805f9b34fb" | ||||
| 	HeartRateChar   = "00002a37-0000-1000-8000-00805f9b34fb" | ||||
| 	FSTransferChar  = "adaf0200-4669-6c65-5472-616e73666572" | ||||
| @@ -41,17 +42,22 @@ const ( | ||||
| ) | ||||
|  | ||||
| var charNames = map[string]string{ | ||||
| 	NewAlertChar:    "New Alert", | ||||
| 	NotifEventChar:  "Notification Event", | ||||
| 	StepCountChar:   "Step Count", | ||||
| 	MotionValChar:   "Motion Values", | ||||
| 	FirmwareVerChar: "Firmware Version", | ||||
| 	CurrentTimeChar: "Current Time", | ||||
| 	BatteryLvlChar:  "Battery Level", | ||||
| 	HeartRateChar:   "Heart Rate", | ||||
| 	FSTransferChar:  "Filesystem Transfer", | ||||
| 	FSVersionChar:   "Filesystem Version", | ||||
| 	WeatherDataChar: "Weather Data", | ||||
| 	NewAlertChar:     "New Alert", | ||||
| 	NotifEventChar:   "Notification Event", | ||||
| 	StepCountChar:    "Step Count", | ||||
| 	MotionValChar:    "Motion Values", | ||||
| 	FirmwareVerChar:  "Firmware Version", | ||||
| 	CurrentTimeChar:  "Current Time", | ||||
| 	LocalTimeChar:    "Local Time", | ||||
| 	BatteryLvlChar:   "Battery Level", | ||||
| 	HeartRateChar:    "Heart Rate", | ||||
| 	FSTransferChar:   "Filesystem Transfer", | ||||
| 	FSVersionChar:    "Filesystem Version", | ||||
| 	WeatherDataChar:  "Weather Data", | ||||
| 	NavFlagsChar:     "Navigation Icon", | ||||
| 	NavNarrativeChar: "Navigation Instruction", | ||||
| 	NavManDistChar:   "Navigation Distance to next event", | ||||
| 	NavProgressChar:  "Navigation Progress", | ||||
| } | ||||
|  | ||||
| type Device struct { | ||||
| @@ -62,14 +68,17 @@ type Device struct { | ||||
| 	motionValChar   *gatt.GattCharacteristic1 | ||||
| 	fwVersionChar   *gatt.GattCharacteristic1 | ||||
| 	currentTimeChar *gatt.GattCharacteristic1 | ||||
| 	localTimeChar   *gatt.GattCharacteristic1 | ||||
| 	battLevelChar   *gatt.GattCharacteristic1 | ||||
| 	heartRateChar   *gatt.GattCharacteristic1 | ||||
| 	fsVersionChar   *gatt.GattCharacteristic1 | ||||
| 	fsTransferChar  *gatt.GattCharacteristic1 | ||||
| 	weatherDataChar *gatt.GattCharacteristic1 | ||||
| 	weatherdataChar *gatt.GattCharacteristic1 | ||||
| 	notifEventCh    chan uint8 | ||||
| 	notifEventDone  bool | ||||
| 	Music           MusicCtrl | ||||
| 	Navigation      NavigationService | ||||
| 	DFU             DFU | ||||
| } | ||||
|  | ||||
| @@ -130,6 +139,7 @@ func Connect(ctx context.Context, opts *Options) (*Device, error) { | ||||
|  | ||||
| 	// Create new device | ||||
| 	out := &Device{device: btDev} | ||||
| 	out.Navigation = NavigationService{dev: out} | ||||
|  | ||||
| 	// Resolve characteristics | ||||
| 	err = out.resolveChars() | ||||
| @@ -394,6 +404,14 @@ func (i *Device) resolveChars() error { | ||||
| 		charResolved := true | ||||
| 		// Set correct characteristics | ||||
| 		switch char.Properties.UUID { | ||||
| 		case NavFlagsChar: | ||||
| 			i.Navigation.flagsChar = char | ||||
| 		case NavNarrativeChar: | ||||
| 			i.Navigation.narrativeChar = char | ||||
| 		case NavManDistChar: | ||||
| 			i.Navigation.mandistChar = char | ||||
| 		case NavProgressChar: | ||||
| 			i.Navigation.progressChar = char | ||||
| 		case NewAlertChar: | ||||
| 			i.newAlertChar = char | ||||
| 		case NotifEventChar: | ||||
| @@ -406,6 +424,8 @@ func (i *Device) resolveChars() error { | ||||
| 			i.fwVersionChar = char | ||||
| 		case CurrentTimeChar: | ||||
| 			i.currentTimeChar = char | ||||
| 		case LocalTimeChar: | ||||
| 			i.localTimeChar = char | ||||
| 		case BatteryLvlChar: | ||||
| 			i.battLevelChar = char | ||||
| 		case HeartRateChar: | ||||
| @@ -693,7 +713,9 @@ func (i *Device) WatchMotion(ctx context.Context) (<-chan MotionValues, error) { | ||||
| 	return out, nil | ||||
| } | ||||
|  | ||||
| // SetTime sets the watch's time using the Current Time Service | ||||
| // SetTime sets the watch's | ||||
| // * time using the Current Time Service's current time characteristic | ||||
| // * timezone information using the CTS's local time characteristic | ||||
| func (i *Device) SetTime(t time.Time) error { | ||||
| 	if err := i.checkStatus(i.currentTimeChar, CurrentTimeChar); err != nil { | ||||
| 		return err | ||||
| @@ -708,7 +730,38 @@ func (i *Device) SetTime(t time.Time) error { | ||||
| 	binary.Write(buf, binary.LittleEndian, uint8(t.Weekday())) | ||||
| 	binary.Write(buf, binary.LittleEndian, uint8((t.Nanosecond()/1000)/1e6*256)) | ||||
| 	binary.Write(buf, binary.LittleEndian, uint8(0b0001)) | ||||
| 	return i.currentTimeChar.WriteValue(buf.Bytes(), nil) | ||||
| 	if err := i.currentTimeChar.WriteValue(buf.Bytes(), nil); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if err := i.checkStatus(i.localTimeChar, LocalTimeChar); err != nil { | ||||
| 		// If the characteristic is unavailable, | ||||
| 		// fail silently, as many people may be on | ||||
| 		// older InfiniTime versions. A warning | ||||
| 		// may be added later. | ||||
| 		if _, ok := err.(ErrCharNotAvail); ok { | ||||
| 			return nil | ||||
| 		} else { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	_, offset := t.Zone() | ||||
| 	dst := 0 | ||||
|  | ||||
| 	// Local time expects two values: the timezone offset and the dst offset, both | ||||
| 	// expressed in quarters of an hour. | ||||
| 	// Timezone offset is to be constant over DST, with dst offset holding the offset != 0 | ||||
| 	// when DST is in effect. | ||||
| 	// As there is no standard way in go to get the actual dst offset, we assume it to be 1h | ||||
| 	// when DST is in effect | ||||
| 	if t.IsDST() { | ||||
| 		dst = 3600 | ||||
| 		offset -= 3600 | ||||
| 	} | ||||
| 	bufTz := &bytes.Buffer{} | ||||
| 	binary.Write(bufTz, binary.LittleEndian, uint8(offset / 3600 * 4)) | ||||
| 	binary.Write(bufTz, binary.LittleEndian, uint8(dst / 3600 * 4)) | ||||
| 	return i.localTimeChar.WriteValue(bufTz.Bytes(), nil) | ||||
| } | ||||
|  | ||||
| // Notify sends a notification to InfiniTime via | ||||
|   | ||||
							
								
								
									
										151
									
								
								navigation.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								navigation.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | ||||
| package infinitime | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"github.com/muka/go-bluetooth/bluez/profile/gatt" | ||||
| ) | ||||
|  | ||||
| var ErrNavProgress = errors.New("progress needs to be between 0 and 100") | ||||
|  | ||||
| const ( | ||||
| 	NavFlagsChar     = "00010001-78fc-48fe-8e23-433b3a1942d0" | ||||
| 	NavNarrativeChar = "00010002-78fc-48fe-8e23-433b3a1942d0" | ||||
| 	NavManDistChar   = "00010003-78fc-48fe-8e23-433b3a1942d0" | ||||
| 	NavProgressChar  = "00010004-78fc-48fe-8e23-433b3a1942d0" | ||||
| ) | ||||
|  | ||||
| type NavigationService struct { | ||||
| 	dev           *Device | ||||
| 	flagsChar     *gatt.GattCharacteristic1 | ||||
| 	narrativeChar *gatt.GattCharacteristic1 | ||||
| 	mandistChar   *gatt.GattCharacteristic1 | ||||
| 	progressChar  *gatt.GattCharacteristic1 | ||||
| } | ||||
|  | ||||
| type NavFlag string | ||||
|  | ||||
| const ( | ||||
| 	NavFlagArrive                  NavFlag = "arrive" | ||||
| 	NavFlagArriveLeft              NavFlag = "arrive-left" | ||||
| 	NavFlagArriveRight             NavFlag = "arrive-right" | ||||
| 	NavFlagArriveStraight          NavFlag = "arrive-straight" | ||||
| 	NavFlagClose                   NavFlag = "close" | ||||
| 	NavFlagContinue                NavFlag = "continue" | ||||
| 	NavFlagContinueLeft            NavFlag = "continue-left" | ||||
| 	NavFlagContinueRight           NavFlag = "continue-right" | ||||
| 	NavFlagContinueSlightLeft      NavFlag = "continue-slight-left" | ||||
| 	NavFlagContinueSlightRight     NavFlag = "continue-slight-right" | ||||
| 	NavFlagContinueStraight        NavFlag = "continue-straight" | ||||
| 	NavFlagContinueUturn           NavFlag = "continue-uturn" | ||||
| 	NavFlagDepart                  NavFlag = "depart" | ||||
| 	NavFlagDepartLeft              NavFlag = "depart-left" | ||||
| 	NavFlagDepartRight             NavFlag = "depart-right" | ||||
| 	NavFlagDepartStraight          NavFlag = "depart-straight" | ||||
| 	NavFlagEndOfRoadLeft           NavFlag = "end-of-road-left" | ||||
| 	NavFlagEndOfRoadRight          NavFlag = "end-of-road-right" | ||||
| 	NavFlagFerry                   NavFlag = "ferry" | ||||
| 	NavFlagFlag                    NavFlag = "flag" | ||||
| 	NavFlagFork                    NavFlag = "fork" | ||||
| 	NavFlagForkLeft                NavFlag = "fork-left" | ||||
| 	NavFlagForkRight               NavFlag = "fork-right" | ||||
| 	NavFlagForkSlightLeft          NavFlag = "fork-slight-left" | ||||
| 	NavFlagForkSlightRight         NavFlag = "fork-slight-right" | ||||
| 	NavFlagForkStraight            NavFlag = "fork-straight" | ||||
| 	NavFlagInvalid                 NavFlag = "invalid" | ||||
| 	NavFlagInvalidLeft             NavFlag = "invalid-left" | ||||
| 	NavFlagInvalidRight            NavFlag = "invalid-right" | ||||
| 	NavFlagInvalidSlightLeft       NavFlag = "invalid-slight-left" | ||||
| 	NavFlagInvalidSlightRight      NavFlag = "invalid-slight-right" | ||||
| 	NavFlagInvalidStraight         NavFlag = "invalid-straight" | ||||
| 	NavFlagInvalidUturn            NavFlag = "invalid-uturn" | ||||
| 	NavFlagMergeLeft               NavFlag = "merge-left" | ||||
| 	NavFlagMergeRight              NavFlag = "merge-right" | ||||
| 	NavFlagMergeSlightLeft         NavFlag = "merge-slight-left" | ||||
| 	NavFlagMergeSlightRight        NavFlag = "merge-slight-right" | ||||
| 	NavFlagMergeStraight           NavFlag = "merge-straight" | ||||
| 	NavFlagNewNameLeft             NavFlag = "new-name-left" | ||||
| 	NavFlagNewNameRight            NavFlag = "new-name-right" | ||||
| 	NavFlagNewNameSharpLeft        NavFlag = "new-name-sharp-left" | ||||
| 	NavFlagNewNameSharpRight       NavFlag = "new-name-sharp-right" | ||||
| 	NavFlagNewNameSlightLeft       NavFlag = "new-name-slight-left" | ||||
| 	NavFlagNewNameSlightRight      NavFlag = "new-name-slight-right" | ||||
| 	NavFlagNewNameStraight         NavFlag = "new-name-straight" | ||||
| 	NavFlagNotificationLeft        NavFlag = "notification-left" | ||||
| 	NavFlagNotificationRight       NavFlag = "notification-right" | ||||
| 	NavFlagNotificationSharpLeft   NavFlag = "notification-sharp-left" | ||||
| 	NavFlagNotificationSharpRight  NavFlag = "notification-sharp-right" | ||||
| 	NavFlagNotificationSlightLeft  NavFlag = "notification-slight-left" | ||||
| 	NavFlagNotificationSlightRight NavFlag = "notification-slight-right" | ||||
| 	NavFlagNotificationStraight    NavFlag = "notification-straight" | ||||
| 	NavFlagOffRampLeft             NavFlag = "off-ramp-left" | ||||
| 	NavFlagOffRampRight            NavFlag = "off-ramp-right" | ||||
| 	NavFlagOffRampSharpLeft        NavFlag = "off-ramp-sharp-left" | ||||
| 	NavFlagOffRampSharpRight       NavFlag = "off-ramp-sharp-right" | ||||
| 	NavFlagOffRampSlightLeft       NavFlag = "off-ramp-slight-left" | ||||
| 	NavFlagOffRampSlightRight      NavFlag = "off-ramp-slight-right" | ||||
| 	NavFlagOffRampStraight         NavFlag = "off-ramp-straight" | ||||
| 	NavFlagOnRampLeft              NavFlag = "on-ramp-left" | ||||
| 	NavFlagOnRampRight             NavFlag = "on-ramp-right" | ||||
| 	NavFlagOnRampSharpLeft         NavFlag = "on-ramp-sharp-left" | ||||
| 	NavFlagOnRampSharpRight        NavFlag = "on-ramp-sharp-right" | ||||
| 	NavFlagOnRampSlightLeft        NavFlag = "on-ramp-slight-left" | ||||
| 	NavFlagOnRampSlightRight       NavFlag = "on-ramp-slight-right" | ||||
| 	NavFlagOnRampStraight          NavFlag = "on-ramp-straight" | ||||
| 	NavFlagRotary                  NavFlag = "rotary" | ||||
| 	NavFlagRotaryLeft              NavFlag = "rotary-left" | ||||
| 	NavFlagRotaryRight             NavFlag = "rotary-right" | ||||
| 	NavFlagRotarySharpLeft         NavFlag = "rotary-sharp-left" | ||||
| 	NavFlagRotarySharpRight        NavFlag = "rotary-sharp-right" | ||||
| 	NavFlagRotarySlightLeft        NavFlag = "rotary-slight-left" | ||||
| 	NavFlagRotarySlightRight       NavFlag = "rotary-slight-right" | ||||
| 	NavFlagRotaryStraight          NavFlag = "rotary-straight" | ||||
| 	NavFlagRoundabout              NavFlag = "roundabout" | ||||
| 	NavFlagRoundaboutLeft          NavFlag = "roundabout-left" | ||||
| 	NavFlagRoundaboutRight         NavFlag = "roundabout-right" | ||||
| 	NavFlagRoundaboutSharpLeft     NavFlag = "roundabout-sharp-left" | ||||
| 	NavFlagRoundaboutSharpRight    NavFlag = "roundabout-sharp-right" | ||||
| 	NavFlagRoundaboutSlightLeft    NavFlag = "roundabout-slight-left" | ||||
| 	NavFlagRoundaboutSlightRight   NavFlag = "roundabout-slight-right" | ||||
| 	NavFlagRoundaboutStraight      NavFlag = "roundabout-straight" | ||||
| 	NavFlagTurnLeft                NavFlag = "turn-left" | ||||
| 	NavFlagTurnRight               NavFlag = "turn-right" | ||||
| 	NavFlagTurnSharpLeft           NavFlag = "turn-sharp-left" | ||||
| 	NavFlagTurnSharpRight          NavFlag = "turn-sharp-right" | ||||
| 	NavFlagTurnSlightLeft          NavFlag = "turn-slight-left" | ||||
| 	NavFlagTurnSlightRight         NavFlag = "turn-slight-right" | ||||
| 	NavFlagTurnStright             NavFlag = "turn-stright" | ||||
| 	NavFlagUpdown                  NavFlag = "updown" | ||||
| 	NavFlagUturn                   NavFlag = "uturn" | ||||
| ) | ||||
|  | ||||
| func (n *NavigationService) SetFlag(flag NavFlag) error { | ||||
| 	log.Debug().Str("func", "SetFlag").Msg("Sending flag") | ||||
| 	if err := n.dev.checkStatus(n.flagsChar, NavFlagsChar); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return n.flagsChar.WriteValue([]byte(flag), nil) | ||||
| } | ||||
|  | ||||
| func (n *NavigationService) SetNarrative(narrative string) error { | ||||
| 	log.Debug().Str("func", "SetNarrative").Msg("Sending narrative") | ||||
| 	if err := n.dev.checkStatus(n.narrativeChar, NavNarrativeChar); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return n.narrativeChar.WriteValue([]byte(narrative), nil) | ||||
| } | ||||
|  | ||||
| func (n *NavigationService) SetManDist(manDist string) error { | ||||
| 	log.Debug().Str("func", "SetNarrative").Msg("Sending maneuver distance") | ||||
| 	if err := n.dev.checkStatus(n.mandistChar, NavManDistChar); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return n.mandistChar.WriteValue([]byte(manDist), nil) | ||||
| } | ||||
|  | ||||
| func (n *NavigationService) SetProgress(progress uint8) error { | ||||
| 	log.Debug().Str("func", "SetNarrative").Msg("Sending progress") | ||||
| 	if err := n.dev.checkStatus(n.progressChar, NavProgressChar); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return n.progressChar.WriteValue([]byte{progress}, nil) | ||||
| } | ||||
| @@ -1,16 +0,0 @@ | ||||
| package player | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os/exec" | ||||
| ) | ||||
|  | ||||
| // VolUp uses pactl to increase the volume of the default sink | ||||
| func VolUp(percent uint) error { | ||||
| 	return exec.Command("pactl", "set-sink-volume", "@DEFAULT_SINK@", fmt.Sprintf("+%d%%", percent)).Run() | ||||
| } | ||||
|  | ||||
| // VolDown uses pactl to decrease the volume of the default sink | ||||
| func VolDown(percent uint) error { | ||||
| 	return exec.Command("pactl", "set-sink-volume", "@DEFAULT_SINK@", fmt.Sprintf("-%d%%", percent)).Run() | ||||
| } | ||||
| @@ -107,6 +107,46 @@ func Prev() error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func VolUp(percent uint) error { | ||||
|  | ||||
| 	player, err := getPlayerObj() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if player != nil { | ||||
|                 currentVal, err := player.GetProperty("org.mpris.MediaPlayer2.Player.Volume") | ||||
|                 if err != nil { | ||||
|                     return err | ||||
|                 } | ||||
|                 newVal := currentVal.Value().(float64) + (float64(percent) / 100) | ||||
|                 err = player.SetProperty("org.mpris.MediaPlayer2.Player.Volume", newVal) | ||||
|                 if err != nil { | ||||
|                     return err | ||||
|                 } | ||||
|         } | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func VolDown(percent uint) error { | ||||
|  | ||||
| 	player, err := getPlayerObj() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if player != nil { | ||||
|                 currentVal, err := player.GetProperty("org.mpris.MediaPlayer2.Player.Volume") | ||||
|                 if err != nil { | ||||
|                     return err | ||||
|                 } | ||||
|                 newVal := currentVal.Value().(float64) - (float64(percent) / 100) | ||||
|                 err = player.SetProperty("org.mpris.MediaPlayer2.Player.Volume", newVal) | ||||
|                 if err != nil { | ||||
|                     return err | ||||
|                 } | ||||
|         } | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type ChangeType int | ||||
|  | ||||
| const ( | ||||
|   | ||||
							
								
								
									
										209
									
								
								resources.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								resources.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,209 @@ | ||||
| package infinitime | ||||
|  | ||||
| import ( | ||||
| 	"archive/zip" | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
|  | ||||
| 	"go.arsenm.dev/infinitime/blefs" | ||||
| ) | ||||
|  | ||||
| // ResourceOperation represents an operation performed during | ||||
| // resource loading | ||||
| type ResourceOperation uint8 | ||||
|  | ||||
| const ( | ||||
| 	// ResourceOperationUpload represents the upload phase | ||||
| 	// of resource loading | ||||
| 	ResourceOperationUpload = iota | ||||
| 	// ResourceOperationRemoveObsolete represents the obsolete | ||||
| 	// file removal phase of resource loading | ||||
| 	ResourceOperationRemoveObsolete | ||||
| ) | ||||
|  | ||||
| // ResourceManifest is the structure of the resource manifest file | ||||
| type ResourceManifest struct { | ||||
| 	Resources []Resource         `json:"resources"` | ||||
| 	Obsolete  []ObsoleteResource `json:"obsolete_files"` | ||||
| } | ||||
|  | ||||
| // Resource represents a resource entry in the manifest | ||||
| type Resource struct { | ||||
| 	Name string `json:"filename"` | ||||
| 	Path string `json:"path"` | ||||
| } | ||||
|  | ||||
| // ObsoleteResource represents an obsolete file entry in the manifest | ||||
| type ObsoleteResource struct { | ||||
| 	Path  string `json:"path"` | ||||
| 	Since string `json:"since"` | ||||
| } | ||||
|  | ||||
| // ResourceLoadProgress contains information on the progress of | ||||
| // a resource load | ||||
| type ResourceLoadProgress struct { | ||||
| 	Operation ResourceOperation | ||||
| 	Name      string | ||||
| 	Total     int64 | ||||
| 	Sent      int64 | ||||
| 	Err       error | ||||
| } | ||||
|  | ||||
| // LoadResources accepts a resources zip file and a BLE FS. | ||||
| // It loads the resources from the zip onto the FS. | ||||
| func LoadResources(file *os.File, fs *blefs.FS) (<-chan ResourceLoadProgress, error) { | ||||
| 	out := make(chan ResourceLoadProgress, 10) | ||||
|  | ||||
| 	fi, err := file.Stat() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	r, err := zip.NewReader(file, fi.Size()) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	m, err := r.Open("resources.json") | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer m.Close() | ||||
|  | ||||
| 	var manifest ResourceManifest | ||||
| 	err = json.NewDecoder(m).Decode(&manifest) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	m.Close() | ||||
|  | ||||
| 	log.Debug().Msg("Decoded manifest file") | ||||
|  | ||||
| 	go func() { | ||||
| 		defer close(out) | ||||
|  | ||||
| 		for _, file := range manifest.Obsolete { | ||||
| 			name := filepath.Base(file.Path) | ||||
|  | ||||
| 			log.Debug().Str("file", file.Path).Msg("Removing file") | ||||
|  | ||||
| 			err := fs.RemoveAll(file.Path) | ||||
| 			if err != nil { | ||||
| 				out <- ResourceLoadProgress{ | ||||
| 					Name:      name, | ||||
| 					Operation: ResourceOperationRemoveObsolete, | ||||
| 					Err:       err, | ||||
| 				} | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			log.Debug().Str("file", file.Path).Msg("Removed file") | ||||
|  | ||||
| 			out <- ResourceLoadProgress{ | ||||
| 				Name:      name, | ||||
| 				Operation: ResourceOperationRemoveObsolete, | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		for _, file := range manifest.Resources { | ||||
| 			src, err := r.Open(file.Name) | ||||
| 			if err != nil { | ||||
| 				out <- ResourceLoadProgress{ | ||||
| 					Name:      file.Name, | ||||
| 					Operation: ResourceOperationUpload, | ||||
| 					Err:       err, | ||||
| 				} | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			srcFi, err := src.Stat() | ||||
| 			if err != nil { | ||||
| 				out <- ResourceLoadProgress{ | ||||
| 					Name:      file.Name, | ||||
| 					Operation: ResourceOperationUpload, | ||||
| 					Total:     srcFi.Size(), | ||||
| 					Err:       err, | ||||
| 				} | ||||
| 				src.Close() | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			log.Debug().Str("file", file.Path).Msg("Making directories") | ||||
|  | ||||
| 			err = fs.MkdirAll(filepath.Dir(file.Path)) | ||||
| 			if err != nil { | ||||
| 				log.Debug().Err(err).Msg("Error making directories") | ||||
| 				out <- ResourceLoadProgress{ | ||||
| 					Name:      file.Name, | ||||
| 					Operation: ResourceOperationUpload, | ||||
| 					Total:     srcFi.Size(), | ||||
| 					Err:       err, | ||||
| 				} | ||||
| 				src.Close() | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			log.Debug(). | ||||
| 				Str("file", file.Path). | ||||
| 				Int64("size", srcFi.Size()). | ||||
| 				Msg("Creating file") | ||||
|  | ||||
| 			dst, err := fs.Create(file.Path, uint32(srcFi.Size())) | ||||
| 			if err != nil { | ||||
| 				log.Debug().Err(err).Msg("Error creating file") | ||||
| 				out <- ResourceLoadProgress{ | ||||
| 					Name:      file.Name, | ||||
| 					Operation: ResourceOperationUpload, | ||||
| 					Total:     srcFi.Size(), | ||||
| 					Err:       err, | ||||
| 				} | ||||
| 				src.Close() | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			progCh := dst.Progress() | ||||
| 			go func() { | ||||
| 				for sent := range progCh { | ||||
| 					log.Debug(). | ||||
| 						Int64("total", srcFi.Size()). | ||||
| 						Uint32("sent", sent). | ||||
| 						Msg("Progress event sent") | ||||
|  | ||||
| 					out <- ResourceLoadProgress{ | ||||
| 						Name:      file.Name, | ||||
| 						Operation: ResourceOperationUpload, | ||||
| 						Total:     srcFi.Size(), | ||||
| 						Sent:      int64(sent), | ||||
| 					} | ||||
|  | ||||
| 					if sent == uint32(srcFi.Size()) { | ||||
| 						return | ||||
| 					} | ||||
| 				} | ||||
| 			}() | ||||
|  | ||||
| 			n, err := io.Copy(dst, src) | ||||
| 			if err != nil { | ||||
| 				log.Debug().Err(err).Msg("Error writing to file") | ||||
| 				out <- ResourceLoadProgress{ | ||||
| 					Name:      file.Name, | ||||
| 					Operation: ResourceOperationUpload, | ||||
| 					Total:     srcFi.Size(), | ||||
| 					Sent:      n, | ||||
| 					Err:       err, | ||||
| 				} | ||||
| 				src.Close() | ||||
| 				dst.Close() | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			src.Close() | ||||
| 			dst.Close() | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	return out, nil | ||||
| } | ||||
| @@ -82,8 +82,12 @@ type TimelineHeader struct { | ||||
| // NewHeader creates and populates a new timeline header | ||||
| // and returns it | ||||
| func NewHeader(evtType EventType, expires time.Duration) TimelineHeader { | ||||
| 	now := time.Now() | ||||
| 	_, offset := now.Zone() | ||||
| 	now = now.Add(time.Duration(offset) * time.Second) | ||||
|  | ||||
| 	return TimelineHeader{ | ||||
| 		Timestamp: uint64(time.Now().Unix()), | ||||
| 		Timestamp: uint64(now.Unix()), | ||||
| 		Expires:   uint32(expires.Seconds()), | ||||
| 		EventType: evtType, | ||||
| 	} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user