从头开始做一个智能家居设备: iOS终端


前言


由于百度云有网页版的MQTT测试终端,所以前期我们可以用那个网页终端作为测试端即可.但是我们毕竟是处在移动互联网时代啊,我们总是用网页是什么鬼~,所以我们要使用手机终端来接入MQTT,从而实现手机终端控制物联网设备.网上的关于这方面的资料还是比较多的,我用到的三方是MQTTClient,个人感觉还是十分的简单.API方法也就那么多,所以上手很快.

在一切开始之前,我们先来回顾一下整个项目的代码逻辑.示意图如下所示.

首先是上线部分,这里主要是分为两种情况,一,用户终端已经不管在线与否,硬件上线都必须发布设备上线消息,里面包含设备的ID,设备的名称以及设备的功能管理等;二,当前用户上线,会发布一个终端设备上线信息,这时候所有的在线硬件设备都需要发送一遍设备信息,用户终端根据反馈回来的设备信息更新UI.

然后就是下线逻辑,用户终端下线不需要通知硬件,但是当硬件设备下线的时候,需要去通过遗嘱消息通知用户终端,用户终端根据其信息更新UI界面.

硬件发送温湿度消息逻辑比较简单只需要发送温湿度消息给终端即可.用户终端根据发送过来的消息更新UI界面.

用户终端发送控制硬件消息则和上面的有所不同.因为当硬件接受到控制消息做出对应改变的时候,终端需要根据设备当前的状态来显示UI,所以硬件设备还需要发送一个反馈消息给用户终端,告诉用户终端当前设备的状态.用户终端再根据这个状态修改对应的UI界面即可.

又一次分析了代码逻辑,接下来我们一起看一下MQTTClient提供的API方法以及常用属性.


MQTTClient的API


  • 初始化连接MQTT服务器操作.
- (void)connectTo:(NSString *)host
             port:(NSInteger)port
              tls:(BOOL)tls
        keepalive:(NSInteger)keepalive
            clean:(BOOL)clean
             auth:(BOOL)auth
             user:(NSString *)user
             pass:(NSString *)pass
             will:(BOOL)will
        willTopic:(NSString *)willTopic
          willMsg:(NSData *)willMsg
          willQos:(MQTTQosLevel)willQos
   willRetainFlag:(BOOL)willRetainFlag
     withClientId:(NSString *)clientId
   securityPolicy:(MQTTSSLSecurityPolicy *)securityPolicy
     certificates:(NSArray *)certificates
    protocolLevel:(MQTTProtocolVersion)protocolLevel
   connectHandler:(MQTTConnectHandler)connectHandler;
  • 主动断开与服务器的连接.
- (void)disconnectWithDisconnectHandler:(MQTTDisconnectHandler)disconnectHandler;
  • 发送消息,包括消息主体(data),主题(topic),消息质量等级(qos),是否是需要回执(retainFlag)等参数.
- (UInt16)sendData:(NSData *)data topic:(NSString *)topic qos:(MQTTQosLevel)qos retain:(BOOL)retainFlag;
  • 订阅主题字典属性,以订阅主题名为key值,以消息质量等级(Qos)为value值进行存储.
@property (strong, nonatomic) NSDictionary<NSString *, NSNumber *> *subscriptions;
  • MQTT当前状态属性,只读属性.
@property (nonatomic, readonly) MQTTSessionManagerState state;
  • MQTT代理属性
@property (weak, nonatomic) id<MQTTSessionManagerDelegate> delegate;
  • MQTT状态改变回调方法,MQTT代理方法之一.通过这个方法,我们可以监听MQTT的状态从而做出对应的UI改变.
- (void)sessionManager:(MQTTSessionManager *)sessionManager didChangeState:(MQTTSessionManagerState)newState;
  • MQTT接受消息方法,MQTT代理方法之一.通过这个方法,我们可以监听所有订阅的主题所接受到的消息.
- (void)handleMessage:(NSData *)data onTopic:(NSString *)topic retained:(BOOL)retained;

上面说了一堆API,接下来,我们就看一下我们如何使用MQTTClient的API来实现我们想要的功能吧.


代码解读


在讲解说明这个代码工程之前.我们先来聊聊我都定义了什么主题,所有主题如下所示.

主题名称 Qos 功能 硬件权限 终端权限
Client Qos0 设备信息主题 发布,订阅 发布,订阅
Will Qos0 遗嘱主题 发布,订阅 发布,订阅
Data Qos0 温湿度数据主题 发布 订阅
Order Qos0 用户指令主题 订阅 发布

其中硬件设备反馈是通过 Client 来进行发布的.还有个问题就是如果你的身份对某个主题没有权限,你去对该主题进行订阅和发布消息会导致MQTT的重新连接.

上面看完了主题设计部分,我们接下来一起来看一下iOS代码部分,对于UI部分,我们没有什么好说的.我们主要看一下MQTT实现部分.

首先,我们先用cocoapod导入 MQTTClient,如下所示.

  pod 'MQTTClient'

整体的代码部分在Helps目录下的MQTTManager单例类中.其实主要MQTT方法有连接方法,断开方法,重新连接方法,订阅和取消订阅主题方法,发送消息等方法.如下所示.


/**
 绑定连接MQTT服务器

 @param username 账号
 @param password 密码
 @param topicArray 主题名称数组
 @param isSSL 是否是SSL连接
 */
- (void)bindWithUserName:(NSString *)username password:(NSString *)password topicArray:(NSArray <NSString *>*)topicArray isSSL:(BOOL)isSSL;


/**
 主动断开MQTT服务器
 */
- (void)disconnectService;


/**
 重新连接MQTT服务器
 */
- (void)reloadConectService;


/**
 订阅某个主题

 @param topic 主题名称
 */
- (void)subscribeTopic:(NSString *)topic;


/**
 取消订阅
 
 @param topic 主题名称
 */
- (void)unsubscribeTopic:(NSString *)topic;


/**
 发送字符串类型的消息

 @param stringMessage 字符串消息
 @param topic 主题
 */
- (void)sendMQTTStringMessage:(NSString *)stringMessage topic:(NSString *)topic;


/**
 发送字典类型的消息

 @param mapMessage 字典类型消息
 @param topic 主题
 */
- (void)sendMQTTMapMessage:(NSDictionary *)mapMessage topic:(NSString *)topic;

然后,我定义了几个通知消息名称,为什么使用通知,主要是可能会有多个界面都需要MQTT的相关数据,所以定了通知来进行数据的传递.通知名称如下所示.

//收到消息的通知,object携带类型为MQTTMessageModel
#define ReceiveMessageNotificationName @"ReceiveMessageNotificationName"

//MQTT状态发生改变的通知,不携带object.也可以使用KVO监听单例中mqttState的变化
#define MQTTChangeStateNotificationName @"MQTTChangeStateNotificationName"

//MQTT的可操作指令发送改变的通知
#define MQTTOrderChangeStateNotificationName @"MQTTOrderChangeStateNotificationName"

//MQTT的f设备返回指令信息的通知 带有@{@"clientID":xxx, @"switchID":xxx}信息
#define MQTTOrderResponseStateNotificationName @"MQTTOrderResponseStateNotificationName"

MQTTManager.m 实现逻辑部分主要说明两个方法,一个是状态回调方法,一个是数据接受回调方法.

我们一一来看,先来看状态回调方法.状态回调方法中不管是哪种状态都会发出通知消息,进行对应的UI界面更新操作. 有一种特殊情况需要注意,那就是当设备连接成功之后,我们需要在MQTTClientTopic发送设备的信息.包括设备的类型(0:用户终端 1:硬件),设备名称,设备ID等消息.这样当在线硬件接受到消息之后就同样会发布硬件信息,这样在用户终端就会知道那个设备是处于在线状态,便于我们去操作和查看.

//状态监听代理方法
- (void)sessionManager:(MQTTSessionManager *)sessionManager didChangeState:(MQTTSessionManagerState)newState {
    
    switch (newState) {
        case MQTTSessionManagerStateConnected:{
            NSLog(@"eventCode -- 连接成功");
            NSDictionary *message = @{
                                      @"type":@(3),
                                      @"data":@{
                                              @"clientType":@(0),
                                              @"clientName":@"骚栋的手机",
                                              @"clientID":self.cliendId
                                              },
                                      };
            
            self.mqttState = MQTTStateDidConnect;
            [self sendMQTTMapMessage:message topic:MQTTClientTopic];
            break;
        }
        case MQTTSessionManagerStateConnecting:
            NSLog(@"eventCode -- 连接中");
            self.mqttState = MQTTStateConnecting;
            break;
        case MQTTSessionManagerStateClosed:
            NSLog(@"eventCode -- 连接被关闭");
            self.mqttState = MQTTStateDisConnect;
            break;
        case MQTTSessionManagerStateError:
            NSLog(@"eventCode -- 连接错误");
            self.mqttState = MQTTStateDisConnect;
            break;
        case MQTTSessionManagerStateClosing:
            NSLog(@"eventCode -- 关闭中");
            self.mqttState = MQTTStateDisConnect;
            break;
        case MQTTSessionManagerStateStarting:
            NSLog(@"eventCode -- 连接开始");
            break;
        default:
            break;
    }
    [[NSNotificationCenter defaultCenter] postNotificationName:MQTTChangeStateNotificationName object:nil];

}

数据接受回调方法中主要做的操作是根据接受到的数据去更新UI.其中有温湿度数据,反馈数据,遗嘱消息,设备消息等,然后根据不同的消息发布不同的通知消息,从而进行UI的修改操作.整体代码如下所示.

//接受到消息的回调代理方法
- (void)handleMessage:(NSData *)data onTopic:(NSString *)topic retained:(BOOL)retained {
    
    NSDictionary *message = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
    int type = [message[@"type"] intValue];
    MQTTMessageModel *messageModel = [[MQTTMessageModel alloc] init];
    [messageModel setValuesForKeysWithDictionary:message[@"data"]];
    switch (type) {
        case 0:
            //温湿度数据
            messageModel.messageType = MQTTMessageTypeData;
            break;
        case 1:{
            //反馈数据
            messageModel.messageType = MQTTMessageTypeResponse;
            
            for (ClientModel *clientModel in self.clientArray) {
                if ([clientModel.clientID isEqualToString:messageModel.clientID]) {
                    for (SwitchModel *switchModel in clientModel.switchArray) {
                        if ([switchModel.switchID isEqualToString:messageModel.switchID]) {
                            switchModel.switchState = messageModel.isOn;
                            break;
                        }
                    }
                    break;
                }
            }
            
            [[NSNotificationCenter defaultCenter] postNotificationName:MQTTOrderResponseStateNotificationName object:@{@"clientID":messageModel.clientID,
                                                                                                                       @"switchID":messageModel.switchID,
                                                                                                                       @"isOn":messageModel.isOn}];
            break;
        }
        case 2:{
            //遗嘱离线数据
            messageModel.messageType = MQTTMessageTypeWill;
            
            //移除所有相关的指令信息
            for (NSInteger i = self.clientArray.count - 1; i >= 0; i--) {
                ClientModel *clientModel = self.clientArray[i];
                if ([clientModel.clientID isEqualToString:messageModel.clientID]) {
                    [self.clientArray removeObject:clientModel];
                }
            }
            [[NSNotificationCenter defaultCenter] postNotificationName:MQTTOrderChangeStateNotificationName object:messageModel];
            break;
        }
        case 3:{
            //设备信息
            messageModel.messageType = MQTTMessageTypeClient;
            
            ClientModel *clientModel = [[ClientModel alloc] init];
            [clientModel setValuesForKeysWithDictionary:message[@"data"]];
            NSArray *switchs = message[@"data"][@"switchs"];
            for (NSDictionary * switchDic in switchs) {
                SwitchModel *switchModel = [[SwitchModel alloc] init];
                switchModel.clientID = clientModel.clientID;
                [switchModel setValuesForKeysWithDictionary:switchDic];
                [clientModel.switchArray addObject:switchModel];
            }

            if (clientModel.clientEunmType == ClientTypeESP8266) {
                BOOL isHaveClient = NO;
                //查看数组中是否有该设备的信息
                for (ClientModel *nowClientModel in self.clientArray) {
                    if ([clientModel.clientID isEqualToString:nowClientModel.clientID]) {
                        isHaveClient = YES;
                        break;
                    }
                }
                if (!isHaveClient) {
                    [self.clientArray addObject:clientModel];
                    [[NSNotificationCenter defaultCenter] postNotificationName:MQTTOrderChangeStateNotificationName object:messageModel];
                }
            }
            
            break;
        }
    }
    
    [[NSNotificationCenter defaultCenter] postNotificationName:ReceiveMessageNotificationName object:messageModel];
}

其他的代码都比较简单,连接过程也不过多的叙述了,大家自行修改测试即可.


结语


好了,用户iOS终端代码已经完成了,整体来说还是比较简单.因为MQTT协议主要就是订阅和发布.不像其他的即时通讯协议有很多的规定和规则.故造成API也很简单.这个就不过多叙述了.其他的安卓方面的MQTT以及微信小程序的MQTT都比较好实现,这里我不会😂,我就不多说了,各位看官自行百度吧.最后放上Demo的传送门,大家自行修改Macros目录下的SDPrefixHeader.pch宏定义参数即可.如果有任何问题,欢迎在评论区批评指导,谢谢大家了.

iOS 终端Github传送门


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,335评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,895评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,766评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,918评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,042评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,169评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,219评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,976评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,393评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,711评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,876评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,562评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,193评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,903评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,699评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,764评论 2 351

推荐阅读更多精彩内容

  • 前言 前面的几篇博客都已经把准备工作做的明明白白了~,接下来我们正式的进入我们的硬件代码编写部分.当然了在一切开始...
    神经骚栋阅读 1,291评论 1 1
  • 前言 搞智能家居就不得不说到网络通信,因为我们需要通过各种终端去控制我们的智能家居设备,所以网络通信这方面是必备的...
    神经骚栋阅读 6,297评论 0 11
  • 网络编程 1. 概论 建立连接:通过IP或者域名来连接两台设备,通过端口号找到对应的通信程序 通信协议:要传输的数...
    陵无山阅读 3,923评论 0 12
  • 简介 MQTT 全称为 Message Queuing Telemetry Transport(消息队列遥测传输)...
    殖民_FE阅读 4,401评论 1 6
  • 才华横溢的伟大诗人徐志摩与林徽因、陆小曼的爱情,至今仍津津乐道。然而,一个普普通通的女子张幼仪,在这一段段爱恨情仇...
    小依妈阅读 320评论 0 2