苹果自2019年在iPhone中引入UWB技术,伴随着的就是其应用软件框架Nearby Interaction框架的升级。Nearby Interaction框架,是一个功能强大且易于使用的iOS空间感知能力。通过NI框架,支持开发者和配件制造商将对象检测和设备激活等功能整合到应用程序,更好的支持移动设备之间的互动。
开发者基于Apple U1芯片的UWB技术(最新一代的iPhone 15/15 Pro已经升级到U2芯片),基于NI功能实现“支持用户指向其他设备、查找朋友,并根据控件的距离和方向显示控制或其他UI”。
另外,从2021年1月苹果也宣布了基于UWB的数字汽车钥匙功能,该功能使车主可以更佳智能的实现车辆的管理。
在本文中,将对NI框架的基础内容,部分实现进行简要的介绍。
框架概述
在应用程序中使用Nearby Interaction来获取具有U1或超宽带(UWB)芯片的设备的位置,如iPhone11或更高版本的iPhone,Apple Watch和第三方配件。为了能够参与交互,物理接近的设备需要运行app,共享其位置和设备token以便能够唯一识别。当app在后台运行,Nearby Interaction通过报告等来通知交互会话对方的位置,基于方向和距离(米)。
Apple设备使用U1芯片的高频功能来共享其在物理环境中的位置,并实现流式传输的交互式会话。例如:
- AR体验,放置虚拟水球放在参与者的手中;
- 出租车或拼车应用程序,实时使用同行用户的方向来识别司机和乘客的相对位置;
- 游戏应用程序,使用户能够使用他们的设备,控制球拍并响应等用户屏幕上的移动球。
Nearby Interaction为应用程序提供两种主要类型的输出:测量设备之间的距离,以及测量从一个设备到另一个设备的相对方向。因此,应用程序运行时,它能够获得包含距离和方向信息的连续更新流,该更新是双向的,会话的双方都在同时了解彼此的相对位置。
使用流程:
1)首先创建一个Nearby Interaction的会话对象,所有的Nearby Interaction都被封装在会话中。与其他的Apple框架类似,如ARKit,你可以为你的会话对象提供希望它运行的配置。因此,假设有两个或多个用户在运行您的应用程序,并且他们希望以某种空间方式相互交互。在此发生之前,您的应用程序需要让系统双方都知道如何识别附近的其他设备。
对等发现是一个关键概念。设备可以通过使用我们称为发现令牌的东西以保护隐私的方式相互发现。
发现令牌是在特定Nearby Interaction会话中为给定设备随机生成的标识符。
发现令牌有一个可以使用的有限时间段,并且该时间段与会话本身一样长,这意味着如果使会话无效或用户退出应用程序,任何会话机及其关联的令牌都会无效。
令牌由系统生成,通过会话对象在应用程序中接收它。创建的每个会话都有其自己唯一的关联发现令牌。最后,你的应用需要从会话对象中复制发现令牌,然后在想要交互的用户之间共享它。
发现令牌是符合NSSecureCoding协议,意味着可以轻松对其进行编码,然后使用应用程序可用的传输技术进行传输。
会话代码示例:
// Per-session Discovery Token
@available(iOS 14.0, *)
open class NIDiscoveryToken : NSObject, NSCopying, NSSecureCoding {
}
// Encode discovery token
if let encodeData = try ? NSKeyedArchiver.archivedData(
withRootObject: myDiscoveryToken, requiringSevureCoding: true){
//Share encoded token using your app's networking layer,通过网络层共享token
// EXAMPLE : using MultipeerConnectivity.framework
mpcSession.send(encodeData, toPeers: [myPeer], with: .reliable)
}
// 1.Session的创建示例
let session = NISession()
// 2.set the session's delegate
session.delegate = self
// 3. Share the discovery token with the peer device
let myDiscoveryToken = session.discoveryToken
// your app's method,应用中根据实际的情况,将token发送到相应的peer
sendDiscoveryTokenToMyPeer(peer, myDiscoveryToken)
// Receive a discovery token from the peer device
let peerDiscoveryToken = ...
// 4. Create a session configuration,基于peer的DiscoveryToken
let config = NINearbyPeerConfiguration(peerToken: peerDiscoveryToken)
// 5. Run your session
session.run(config)
// 可以暂停会话
session.pause()
// 可以恢复会话
session.run(session.configuration)
基本流程:
1、创建一个session,交换DiscoveryTokens。(测距双方交换Token)
2、基于Peer的Token创建一个NINearPeerConfiguration。
3、基于配置运行session(会话)。
会话只有当应用在前台运行时才处于活跃状态,恢复前等待sessionSuspensionEnded回调。
挂起的会话不会自动恢复。重新运行通过调用run(…)函数。
4、会话需要一个delegate(委托)来提供更新。运行之后,委托将开始获取有关附近对象的更新。
无效的会话将不能再被运行,这种情况下,需要创建新的会话,重新交换token。
外设实现
step.1 检测设备是否支持Apple U1芯片
var isSupported : Bool
if #available(iOS 16.0, watchOS 9.0, *) {
isSupported = NISession.deviceCapabilities.supportsPreciseDistanceMeasurement
} else {
isSupported = NISession.isSupported
}
if isSupported {
// Initiate an interaction session.
}
注:在iOS16平台框架会调用不同的方法实现设备的支持检测,但是在实际的NI例程中框架在iOS16平台会报错提示没有deviceCapabilities对象,可能是官方文档已经更新但Xcode里框架的源代码还是旧版本,因此为避免报错可以将iOS16平台内的deviceCapabilities.supportsPreciseDistanceMeasurement方法也改用isSupported方法。(官方文档认为isSupported方法为弃用方法,但仍可用)
step.2 生成NIConfiguration类
配对流程
Nearby Interaction框架与配件设备进行配对连接的过程分为三步,
- 第一步,通过双方设备共同接入的某种网络进行连接(与第三方配件设备框架推荐的方式是Bluetooth LE)。
- 第二步,由配件设备通过选定的网络发送设备信息给框架。
- 第三步,Nearby Interaction发送配置信息给配件设备。之后将开启UWB的测距会话。
具体流程图如下图所示。
step.3 处理会话委托的回调函数
Nearby Interaction框架获取配对设备的距离、角度和设备信息通过NISessionDelegate协议与用户交互
NISessionDelegate协议
NISessionDelegate协议是Nearby Interaction框架与用户进行数据交互的的协议对象, 该协议要求委托类实现以下接口。
public protocol NISessionDelegate : NSObjectProtocol {
/**
This is called when new updates about nearby objects are available.
@param session The nearby interaction session being run.
@param nearbyObjects The nearby objects that have been updated.
*/
optional func session(_ session: NISession, didUpdate nearbyObjects: [NINearbyObject])
/**
This is called when the system is no longer attempting to interact with some nearby objects.
@discussion The system was unable to interact with a peer device for some time, or the peer device signaled that it is leaving the session. After this callback is received, the session with the peer is no longer active. To retry interacting with the peer, issue a new call to -runWithConfiguration:.
@param session The nearby interaction session that removed the nearby object(s).
@param nearbyObjects The nearby objects that were removed.
@param reason The reason the nearby object(s) were removed. All objects in nearbyObjects are removed for the same reason. If multiple nearby objects are removed for different reasons, -didRemoveNearbyObjects:reason: will be called multiple times.
*/
optional func session(_ session: NISession, didRemove nearbyObjects: [NINearbyObject], reason: NINearbyObject.RemovalReason)
/**
This is called when a session is suspended.
@discussion A session will be suspended in various app and system scenarios.
A suspended session may be run again only after -sessionSuspensionEnded: has been called. Restart a session by issuing a new call to -runWithConfiguration:.
@param session The nearby interaction session that was suspended.
*/
optional func sessionWasSuspended(_ session: NISession)
/**
This is called when a session may be resumed.
@param session The nearby interaction session that was suspended.
*/
optional func sessionSuspensionEnded(_ session: NISession)
/**
This is called when a session is invalidated.
@param session The session that has become invalid. Your app should discard any references it has to this session.
@param error The error indicating the reason for invalidation of the session (see NIError.h).
*/
optional func session(_ session: NISession, didInvalidateWith error: Error)
/**
Provides configuration data that needs to be shared with the accessory.
@note Shareable configuration data is only provided when running an NINearbyAccessoryConfiguration.
@discussion After invoking this callback, the session will go into a special preparedness state for a limited amount of time.
The interaction on the accessory must start within this time window. If activity is not detected from the accessory, the session will call
the -[session:didRemoveNearbyObjects:reason:] delegate callback. To restart the session, coordinate with the accessory and call -[runWithConfiguration] again.
@param session The session which produced the configuration data.
@param shareableConfigurationData The configuration data that needs to be shared with the accessory.
@param object A representation of the accessory as a NINearbyObject. The discoveryToken property will be equal to the one in the configuration used to run the session.
*/
@available(iOS 15.0, *)
optional func session(_ session: NISession, didGenerateShareableConfigurationData shareableConfigurationData: Data, for object: NINearbyObject)
}