forked from Elara6331/infinitime
		
	Create custom BlueZ agent
This commit is contained in:
		
							
								
								
									
										91
									
								
								btsetup.go
									
									
									
									
									
								
							
							
						
						
									
										91
									
								
								btsetup.go
									
									
									
									
									
								
							| @@ -1,27 +1,116 @@ | ||||
| package infinitime | ||||
|  | ||||
| import ( | ||||
| 	"github.com/godbus/dbus/v5" | ||||
| 	bt "github.com/muka/go-bluetooth/api" | ||||
| 	"github.com/muka/go-bluetooth/bluez/profile/adapter" | ||||
| 	"github.com/muka/go-bluetooth/bluez/profile/agent" | ||||
| 	"github.com/muka/go-bluetooth/hw/linux/btmgmt" | ||||
| ) | ||||
|  | ||||
| var defaultAdapter *adapter.Adapter1 | ||||
| var itdAgent *Agent | ||||
|  | ||||
| func Init() { | ||||
| 	conn, err := dbus.SystemBus() | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
|  | ||||
| 	ag := &Agent{} | ||||
| 	err = agent.ExposeAgent(conn, ag, agent.CapKeyboardDisplay, true) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
|  | ||||
| 	// Get bluez default adapter | ||||
| 	da, err := bt.GetDefaultAdapter() | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
|  | ||||
| 	da.SetPowered(true) | ||||
| 	daMgmt := btmgmt.NewBtMgmt(bt.GetDefaultAdapterID()) | ||||
| 	daMgmt.SetPowered(true) | ||||
|  | ||||
| 	defaultAdapter = da | ||||
| 	itdAgent = ag | ||||
| } | ||||
|  | ||||
| func Exit() error { | ||||
| 	if defaultAdapter != nil { | ||||
| 		defaultAdapter.Close() | ||||
| 	} | ||||
| 	agent.RemoveAgent(itdAgent) | ||||
| 	return bt.Exit() | ||||
| } | ||||
|  | ||||
| var errAuthFailed = dbus.NewError("org.bluez.Error.AuthenticationFailed", nil) | ||||
|  | ||||
| // Agent implements the agent.Agent1Client interface. | ||||
| // It only requires RequestPasskey as that is all InfiniTime | ||||
| // will use. | ||||
| type Agent struct { | ||||
| 	ReqPasskey func() (uint32, error) | ||||
| } | ||||
|  | ||||
| // Release returns nil | ||||
| func (*Agent) Release() *dbus.Error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // RequestPinCode returns an empty string and nil | ||||
| func (*Agent) RequestPinCode(device dbus.ObjectPath) (pincode string, err *dbus.Error) { | ||||
| 	return "", nil | ||||
| } | ||||
|  | ||||
| // DisplayPinCode returns nil | ||||
| func (*Agent) DisplayPinCode(device dbus.ObjectPath, pincode string) *dbus.Error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // RequestPasskey runs Agent.ReqPasskey and returns the result | ||||
| func (a *Agent) RequestPasskey(device dbus.ObjectPath) (uint32, *dbus.Error) { | ||||
| 	if a.ReqPasskey == nil { | ||||
| 		return 0, errAuthFailed | ||||
| 	} | ||||
| 	passkey, err := a.ReqPasskey() | ||||
| 	if err != nil { | ||||
| 		return 0, errAuthFailed | ||||
| 	} | ||||
| 	return passkey, nil | ||||
| } | ||||
|  | ||||
| // DisplayPasskey returns nil | ||||
| func (*Agent) DisplayPasskey(device dbus.ObjectPath, passkey uint32, entered uint16) *dbus.Error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // RequestConfirmation returns nil | ||||
| func (*Agent) RequestConfirmation(device dbus.ObjectPath, passkey uint32) *dbus.Error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // RequestAuthorization returns nil | ||||
| func (*Agent) RequestAuthorization(device dbus.ObjectPath) *dbus.Error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // AuthorizeService returns nil | ||||
| func (*Agent) AuthorizeService(device dbus.ObjectPath, uuid string) *dbus.Error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Cancel returns nil | ||||
| func (*Agent) Cancel() *dbus.Error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Path returns "/dev/arsenm/infinitime/Agent" | ||||
| func (*Agent) Path() dbus.ObjectPath { | ||||
| 	return "/dev/arsenm/infinitime/Agent" | ||||
| } | ||||
|  | ||||
| // Interface returns "org.bluez.Agent1" | ||||
| func (*Agent) Interface() string { | ||||
| 	return "org.bluez.Agent1" | ||||
| } | ||||
|   | ||||
| @@ -59,12 +59,14 @@ var ( | ||||
| 	ErrNotConnected     = errors.New("not connected") | ||||
| 	ErrCharNotAvail     = errors.New("required characteristic is not available") | ||||
| 	ErrNoTimelineHeader = errors.New("events must contain the timeline header") | ||||
| 	ErrPairTimeout      = errors.New("reached timeout while pairing") | ||||
| ) | ||||
|  | ||||
| type Options struct { | ||||
| 	AttemptReconnect bool | ||||
| 	WhitelistEnabled bool | ||||
| 	Whitelist        []string | ||||
| 	OnReqPasskey     func() (uint32, error) | ||||
| } | ||||
|  | ||||
| var DefaultOptions = &Options{ | ||||
| @@ -94,6 +96,8 @@ func Connect(opts *Options) (*Device, error) { | ||||
| 	} | ||||
| 	dev.opts = opts | ||||
| 	dev.onReconnect = func() {} | ||||
| 	setOnPasskeyReq(opts.OnReqPasskey) | ||||
|  | ||||
| 	// Watch device properties | ||||
| 	devEvtCh, err := dev.device.WatchProperties() | ||||
| 	if err != nil { | ||||
| @@ -121,14 +125,29 @@ func Connect(opts *Options) (*Device, error) { | ||||
| 					dev.device.Properties.Connected = false | ||||
| 					// While not connected | ||||
| 					for !dev.device.Properties.Connected { | ||||
| 						// Attempt to connect via bluetooth address | ||||
| 						reConnDev, err := ConnectByAddress(dev.device.Properties.Address) | ||||
| 						reConnDev := dev | ||||
|  | ||||
| 						paired, err := reConnDev.device.GetPaired() | ||||
| 						if err != nil { | ||||
| 							// Decrement disconnect event number | ||||
| 							disconnEvtNum-- | ||||
| 							// Skip rest of loop | ||||
| 							continue | ||||
| 						} | ||||
|  | ||||
| 						if !paired { | ||||
| 							err = reConnDev.pairTimeout() | ||||
| 							if err != nil { | ||||
| 								continue | ||||
| 							} | ||||
| 						} else { | ||||
| 							// Attempt to connect via bluetooth address | ||||
| 							reConnDev, err = connectByName(opts) | ||||
| 							if err != nil { | ||||
| 								// Decrement disconnect event number | ||||
| 								disconnEvtNum-- | ||||
| 								// Skip rest of loop | ||||
| 								continue | ||||
| 							} | ||||
| 						} | ||||
|  | ||||
| 						// Store onReconn callback | ||||
| 						onReconn := dev.onReconnect | ||||
| 						// Set device to new device | ||||
| @@ -154,6 +173,7 @@ func (i *Device) OnReconnect(f func()) { | ||||
|  | ||||
| // Connect connects to a paired InfiniTime device | ||||
| func connectByName(opts *Options) (*Device, error) { | ||||
| 	setOnPasskeyReq(opts.OnReqPasskey) | ||||
| 	// Create new device | ||||
| 	out := &Device{} | ||||
| 	// Get devices from default adapter | ||||
| @@ -203,6 +223,7 @@ func contains(ss []string, s string) bool { | ||||
|  | ||||
| // Pair attempts to discover and pair an InfiniTime device | ||||
| func pair(opts *Options) (*Device, error) { | ||||
| 	setOnPasskeyReq(opts.OnReqPasskey) | ||||
| 	// Create new device | ||||
| 	out := &Device{} | ||||
| 	// Start bluetooth discovery | ||||
| @@ -237,10 +258,17 @@ func pair(opts *Options) (*Device, error) { | ||||
| 		return nil, ErrNotFound | ||||
| 	} | ||||
|  | ||||
| 	// Pair device | ||||
| 	out.device.Pair() | ||||
| 	// Connect to device | ||||
| 	err = out.device.Connect() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	out.device.Properties.Connected = true | ||||
| 	// Pair device | ||||
| 	err = out.pairTimeout() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	// Set connected to true | ||||
| 	out.device.Properties.Connected = true | ||||
| @@ -254,32 +282,33 @@ func pair(opts *Options) (*Device, error) { | ||||
| 	return out, nil | ||||
| } | ||||
|  | ||||
| // ConnectByAddress tries to connect to an InifiniTime at | ||||
| // the specified InfiniTime address | ||||
| func ConnectByAddress(addr string) (*Device, error) { | ||||
| 	var err error | ||||
| 	// Create new device | ||||
| 	out := &Device{} | ||||
| 	// Get device from bluetooth address | ||||
| 	out.device, err = defaultAdapter.GetDeviceByAddress(addr) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| // setOnPasskeyReq sets the callback for a passkey request. | ||||
| // It ensures the function will never be nil. | ||||
| func setOnPasskeyReq(onReqPasskey func() (uint32, error)) { | ||||
| 	itdAgent.ReqPasskey = onReqPasskey | ||||
| 	if itdAgent.ReqPasskey == nil { | ||||
| 		itdAgent.ReqPasskey = func() (uint32, error) { | ||||
| 			return 0, nil | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| 	// Connect to device | ||||
| 	err = out.device.Connect() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| // pairTimeout tries to pair with the device. | ||||
| // It will time out after 20 seconds. | ||||
| func (i *Device) pairTimeout() error { | ||||
| 	errCh := make(chan error) | ||||
| 	go func() { | ||||
| 		errCh <- i.device.Pair() | ||||
| 	}() | ||||
| 	select { | ||||
| 	case err := <-errCh: | ||||
| 		return err | ||||
| 	case <-time.After(20 * time.Second): | ||||
| 		if err := i.device.CancelPairing(); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		return ErrPairTimeout | ||||
| 	} | ||||
|  | ||||
| 	out.device.Properties.Connected = true | ||||
|  | ||||
| 	// Resolve characteristics | ||||
| 	err = out.resolveChars() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return out, nil | ||||
| } | ||||
|  | ||||
| // resolveChars attempts to set all required | ||||
| @@ -719,7 +748,11 @@ func (i *Device) AddWeatherEvent(event interface{}) error { | ||||
| } | ||||
|  | ||||
| func (i *Device) checkStatus(char *gatt.GattCharacteristic1) error { | ||||
| 	if !i.device.Properties.Connected { | ||||
| 	connected, err := i.device.GetConnected() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if !connected { | ||||
| 		return ErrNotConnected | ||||
| 	} | ||||
| 	if char == nil { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user