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蓝牙协议举例说明:

  1. 接入蓝牙 SDK,请参考链接

  2. 开发者需要实现 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

results matching ""

    No results matching ""