iOS 移动端设备网关 SDK
更新时间:2018-11-07 14:23:18
概述
移动端设备网关 SDK,运行于 APP 上的子设备网关,对于无法直连网络的子设备,如蓝牙设备,提供子设备的管理功能,如子设备添加拓扑,删除拓扑,上线,下线以及数据上下行等
依赖 SDK | 概述 |
---|---|
长连接通道 | 提供了 App 生命周期内的,适用于 IoT 业务场景的长连接通道 |
初始化
设备网关 SDK 的初始化依赖长连接通道的初始化,请参见:长连接通道 SDK - 初始化 在长连接通道初始化成功后,再初始化设备网关 SDK。
#import <AlinkAppExpress/AlinkAppExpress.h>
#import <AlinkMobileGateway/AlinkMobileGateway.h>
NSString *clientId = [[LKAppExpress sharedInstance] getClientId];
if (clientId.length > 0) {
[[LKIoTMobileGateway sharedGateway] startConnectWithId:clientId connectListener:self];
}
使用说明
前期准备
蓝牙连接已经建立
子设备接入网关
以下流程都是基于子设备跟 APP 已经建立好蓝牙连接通路的前提下。
开发者需要实现以下 protocol,传给网关使用,网关可以因之获取子设备的相关信息。
/**
子设备的连接通道,如Ble通道,由应用层实现后传入。
*/
@protocol LKIoTSubdeviceConn <NSObject>
/**
打开子设备连接通道
@param completionHandler 结果通知
*/
- (void)open:(void (^)(NSError *error))completionHandler;
/**
关闭子设备连接通道
@param completionHandler 结果通知
*/
- (void)close:(void (^)(NSError *error))completionHandler;
/**
判断跟子设备之间的连接是否准备就绪,如果没有就绪,SDK会调用open接口打开
*/
- (BOOL)isReady;
/**
获取子设备的信息,
字典key包括:
"clientId"
"productKey"
"deviceName"
"sign",
"signMethod"
对于三元组设备 子设备签名规则同网关相同
clientId客户端标识,可以取mac,也可以是随机字串,由设备端确定.
对于蓝牙设备,signMethod指定为sha256
假定:clientId=123, deviceName=test, productKey=123,deviceSecret=secret. signMethod:"sha256",
则sign计算规则如下
sign = sha256(clientId123deviceNametestdeviceSecretsecretproductKey123)
*/
- (NSDictionary *)getSubDeviceInfo;
@end
- 创建子设备虚拟线路
子设备虚拟线路:网关给每个子设备分配,用于子设备的相关操作。
代码示例
[[LKIoTMobileGateway sharedGateway]setupGatewayLine:_bleSubConn complete:^(LKIoTGatewayLine * _Nullable line, NSError * _Nullable err) {
_subdeviceLine = line;
}];
/**
_bleSubConn 为实现了protocol LKIoTSubdeviceConn的类的对象。
子设备虚拟线路会在 complete block中返回,即LKIoTGatewayLine对象。
*/
子设备跟网关建立/删除 topo 关系
所谓 topo 关系,即在 IoT 云端的上下级丛属关系。子设备要通过网关实现数据上下云必须首先跟
网关建立 topo 关系。topo 关系建立后一直存在,直到删除 topo 关系接口或者子设备
跟另外的网关建立了 topo 关系。
在建立 topo 关系的流程中,IoT 云端会校验子设备生成的 sign 值来认证子设备身份。
sign 生成规则参见上边 protocol 里的注释。
- 建立 topo 示例
[_subdeviceLine addTopo:^(BOOL success, NSError *err, id dataObj) {
NSLog(@"add topo success : %@", success?@"YES":@"NO");
}];
/**
_subdeviceLine 为上节中建立的虚拟线路
*/
- 删除 topo 示例
[_subdeviceLine deleteTopo:^(BOOL success, NSError *err, id dataObj) {
NSLog(@"deleteTopo success : %@", success?@"YES":@"NO");
}];
/**
_subdeviceLine 为上节中建立的虚拟线路
*/
子设备上线/下线
所谓上线即子设备复用网关跟 IoT云端 已经建立好的长连接通路。子设备只有上线成功,才能复用网关
的长连接通路。上线的前提条件是:子设备跟网关已经建立了 topo 关系。
上线成功后,子设备的数据可以上行到云,而云也可下推数据到子设备。
- 上线示例:
[_subdeviceLine online:^(BOOL success, NSError *err, id dataObj) {
NSLog(@"sub on line success : %@", success?@"YES":@"NO");
}];
/**
_subdeviceLine 为上节中建立的虚拟线路
*/
- 下线示例:
[_subdeviceLine offline:^(BOOL success, NSError *err, id dataObj) {
NSLog(@"sub on line success : %@", success?@"YES":@"NO");
}];
/**
_subdeviceLine 为上节中建立的虚拟线路
*/
子设备上行请求
- 子设备上报设备属性
这是一个 RPC 流程,设备上报属性到云后,云会下发一个业务回执,即上云请求的 reply。
上报属性示例:
NSString * subPk = [_subdeviceLine subDeviceProductKey];
NSString * subDn = [_subdeviceLine subDeviceDeviceName];
NSString * completeTopic = [NSString stringWithFormat:@"/sys/%@/%@/thing/event/property/post", subPk, subDn];
NSString * completeTopicReply = [completeTopic stringByAppendingString:@"_reply"];
__block __weak typeof(self) this = self;
_uploadReplylistener = [BreezeLineDownstreamListener new];
_uploadReplylistener.msgTopic = completeTopicReply;
_uploadReplylistener.msgId = [NSString stringWithFormat:@"%lld",(long long)[[NSDate date]timeIntervalSince1970]];//请保证msgId的唯一性,
_uploadReplylistener.replyHandler = ^(NSDictionary *data) {
__strong typeof(self) strongSelf = this;
int code = ([data valueForKey:@"code"] != nil ? [[data valueForKey:@"code"] intValue] : -1);
if (code == 200) {
[strongSelf insertMsgWithColor:@"blue" main:@"设备属性上报成功" detail:nil];
} else {
[strongSelf insertMsgWithColor:@"blue" main: @"设备属性上报失败 : %@" detail:[NSString stringWithFormat:@"错误码 : %d", code]];
}
_uploadReplylistener = nil;
};
//listener必须自己保证生命周期,因为addDownStreamListener只是weak引用。
//如果在reply回来时listner已经被析构了,则事件不会上抛了
[_subdeviceLine addDownStreamListener:YES listener:_uploadReplylistener];
[_subdeviceLine subscribe:completeTopicReply complete:^(BOOL success, NSError *err) {
NSDictionary * uploadDic = @{@"id" : _uploadReplylistener.msgId,
@"version":@"1.0",
@"params" : @{@"propertyIdentifier22":@"higuketestproper"},
@"method":@"thing.event.property.post"};
NSData * uploadDat = [NSJSONSerialization dataWithJSONObject:uploadDic options:NSJSONWritingPrettyPrinted error:nil];
[_subdeviceLine uploadData:completeTopic data:uploadDat complete:^(BOOL success, NSError *err, id dataObj) {
NSLog(@"sub upload success : %@", success?@"YES":@"NO");
}];
}];
/**
_subdeviceLine 为上节中建立的虚拟线路,如果网络错误或者业务原因,不一定会有reply,
开发者应考虑reply超时的情况。
*/
//其中BreezeLineDownstreamListener定义如下
typedef void (^ iotReplyHandler)(NSDictionary * data);
@interface BreezeLineDownstreamListener : NSObject <LKMgwLineDownListener>
@property(nonatomic, copy) NSString * msgId;
@property(nonatomic, copy) NSString * msgTopic;
@property(nonatomic, strong) iotReplyHandler replyHandler;
@end
@implementation BreezeLineDownstreamListener
- (void)onDownstream:(NSString * _Nonnull)topic data:(id _Nullable)data {
NSLog(@"onDownstream topic : %@", topic);
NSLog(@"onDownstream data : %@", data);
NSDictionary * replyDict = nil;
if ([data isKindOfClass:[NSString class]]) {
NSData * replyData = [data dataUsingEncoding:NSUTF8StringEncoding];
replyDict = [NSJSONSerialization JSONObjectWithData:replyData options:NSJSONReadingMutableLeaves error:nil];
} else if ([data isKindOfClass:[NSDictionary class]]) {
replyDict = data;
}
if (replyDict == nil) {
return;
}
NSString * msgId = [replyDict valueForKey:@"id"];
if ([_msgId isEqualToString:msgId]) {
if (_replyHandler != nil) {
_replyHandler(replyDict);
}
}
}
- 子设备数据上云
跟上一节的区别是,数据上云没有业务回执,即云端不会下推 reply。开发者需要根据
具体的业务场景决定使用哪个接口。
代码示例
[_subdeviceLine uploadData:completeTopic data:uploadDat complete:^(BOOL success, NSError *err, id dataObj) {
NSLog(@"sub upload success : %@", success?@"YES":@"NO");
}];
子设备订阅/取消订阅 topic
- 子设备订阅 topic
NSString * subPk = [_subdeviceLine subDeviceProductKey];
NSString * subDn = [_subdeviceLine subDeviceDeviceName];
NSString * completeTopic = [NSString stringWithFormat:@"/sys/%@/%@/thing/service/property/set", subPk, subDn];
//[_subdeviceLine addDownStreamListener:YES listener:self];记得添加listener,否则无法收
//到云端下推信息
[_subdeviceLine subscribe:completeTopic complete:^(BOOL success, NSError * _Nonnull err) {
}];
- 子设备取消订阅 topic
NSString * subPk = [_subdeviceLine subDeviceProductKey];
NSString * subDn = [_subdeviceLine subDeviceDeviceName];
NSString * completeTopic = [NSString stringWithFormat:@"/sys/%@/%@/thing/service/property/set", subPk, subDn];
[_subdeviceLine unsubscribe:completeTopic complete:^(BOOL success, NSError * _Nonnull err) {
}];
监听云端下推子设备数据
代码示例
[_subdeviceLine addDownStreamListener:YES listener:self];
//其中self 实现了protocol LKMgwLineDownListener
更多功能
蓝牙设备如何接入移动端网关
蓝牙设备可以使用标准蓝牙协议,也可以使用阿里巴巴IoT蓝牙协议。
这里以阿里巴巴IoT蓝牙协议举例说明:
接入蓝牙 SDK,请参考链接
开发者需要实现 protocol LKIoTSubdeviceConn,示例如下:
/* 蓝牙的子设备连接类,按照协议LKIoTSubdeviceConn协议实现
*/
@interface LKIoTBleSubdeviceConn : NSObject <LKIoTSubdeviceConn>
- (instancetype)initWithBreeze:(Breeze *)breeze;
@end
#import "LKIoTBleSubdeviceConn.h"
#import <BreezeBizSDK/LKBreezeBiz.h>
typedef void (^LKIoTBleCompletionHandler)(NSError *error);
@interface LKIoTBleSubdeviceConn()
@property(nonatomic, strong) LKBreezeBiz *breezeBiz;
@property(nonatomic, strong) NSMutableDictionary *deviceInfo;
@property(nonatomic, copy) LKIoTBleCompletionHandler openCompletionHandler;
@end
@implementation LKIoTBleSubdeviceConn
- (instancetype)initWithBreeze:(Breeze *)breeze {
self = [super init];
if (self) {
_deviceInfo = [NSMutableDictionary new];
_breezeBiz = [[LKBreezeBiz alloc]initWithBreeze:breeze];
}
return self;
}
- (void)open:(void (^)(NSError *error))completionHandler {
[self.breezeBiz prepareDeviceInfo:^(NSDictionary *deviceInfo, NSError *error) {
if(error == nil) {
[self.deviceInfo addEntriesFromDictionary:deviceInfo];
[self.deviceInfo setObject:[deviceInfo objectForKey:@"productId"] forKey:@"clientId"];
[self.deviceInfo setObject:@"sha256" forKey:@"signMethod"];
[self.deviceInfo removeObjectForKey:@"productId"];
}
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(error);
});
}];
}
- (void)close:(void (^)(NSError *error))completionHandler {
}
- (BOOL)isReady {
return YES; //[self.breezeBiz isPeripheralReady];
}
- (NSDictionary *)getSubDeviceInfo {
return self.deviceInfo;
}
@end