基于涂鸦 App SDK 快速地开发一款 iOS IoT App

更新时间Invalid date

概况

在 IoT 大行其道的今天,万物互联已经是趋势。通过 App 控制智能设备,且能够实现场景联动,已经不是做不做的选择题,而是破局企业端到端解决方案的关键。智能硬件厂家如雨后春笋般涌现,但是,物联网的大框架 DCM(Device、Connect、Manage)架构,让 IoT App 成为刚需。智能硬件需要和 App 去结合,才能更好地发挥它的作用。例如:

  • 使用 App 去控制灯光的亮度,调节空调的温度,远程通过 IPC 监控家庭情况等
  • 调动所有受控设备完成一个场景联动,如手机设置 回家场景,在下班回家的时候,点击 回家场景,一键开灯、水壶烧水、空调开始工作等等

但是,对于大部分企业而言,IoT App 开发不仅属于新颖项目,而且充满挑战。本教程介绍涂鸦 App SDK 完成智能家居 App 的开发为例,在此基础上,您能创建 App SDK、集成 SDK、创建家庭、设备配网和设备控制等步骤,完整地完成一款智能家居 App 的开发。

物料清单

硬件 (1)软件 (1)
  • (可选)一款智能硬件设备

    数量:1

    任何 Powered By Tuya 设备皆可,您也可以前往涂鸦智选平台采购样品。查看详情

步骤

  • 第 1 步:创建 App SDK

    请确保开始开发前,您已经创建了一款智能硬件产品,详细步骤请参考 五分钟快速入门

    1. 涂鸦 IoT 平台 App 工作台 中,单击 创建 App

      image.png

    2. 填写 App 相关信息后,单击 确认

      • 应用名称:填写您的 App 名称。
      • iOS 应用包名:填写您的 iOS App 包名(建议格式:com.xxxxx.xxxxx)。
      • 安卓应用包名:填写您的安卓 App 包名(两者可以保持一致,也可以不一致)。
      • 渠道标识符:不是必填项,如果不填写,系统会根据包名自动生成。
    3. 根据您的实际需求选择一个或多个方案,然后根据 Podfile 和 Gradle 进行 SDK 的集成。

      image.png

    4. 点击获取密码,获取 SDK 的 AppKeyAppSecret、安全图片等信息,后续步骤中会使用到这些信息。

      image.png

  • 第 2 步:使用 CocoaPods 集成 SDK

    说明:涂鸦 App SDK 最低支持的系统版本为 9.0。

    1. Podfile 文件中添加以下内容:

      platform :ios, '9.0'
      
      target 'Your_Project_Name' do
      	pod "TuyaSmartHomeKit"
      end
      
    2. 在项目根目录下执行 pod update 命令进行集成。

    3. 初始化 SDK。

      1. 打开项目设置,Target > General,修改 Bundle Identifier 为涂鸦 IoT 平台上对应的 iOS 包名。

      2. 导入安全图片到工程根目录,重命名为 t_s.bmp,并加入「项目设置 => Target => Build Phases => Copy Bundle Resources」中。

      3. 在项目的PrefixHeader.pch文件添加以下内容。

        #import <TuyaSmartHomeKit/TuyaSmartKit.h>
        
      4. 打开AppDelegate.m文件,在 [AppDelegate application:didFinishLaunchingWithOptions:] 方法中初始化SDK。

        SDK接口

        - (void)startWithAppKey:(NSString *)appKey secretKey:(NSString *)secretKey;
        

        参数说明

        • appKey:App 证明信息,相当于 ID,您可以在 IoT 平台中获取。
        • secretKey:App 密钥的 Key,您可以在 IoT 平台中获取。

        示例代码

        [[TuyaSmartSDK sharedInstance] startWithAppKey:<#your_app_key#> secretKey:<#your_secret_key#>];
        
    4. 打开 Debug 模式。

      在开发的过程中可以开启 Debug 模式,打印一些日志用于分析问题,开发结束上线后建议关闭。

      #ifdef DEBUG
          [[TuyaSmartSDK sharedInstance] setDebugMode:YES];
      #else
      #endif
      
  • 第 3 步:用户注册和登录

    涂鸦 IoT 平台支持多种用户体系,包括手机、邮箱、UID 等。其中手机支持验证码登录和密码登录两种方式,UID 登录主要用于您已经建立了账号体系的场景。本教程将采用手机验证码注册登录。

    类名 说明
    TuyaSmartUser 涂鸦用户相关的类

    在注册登录方法中,需要提供 countryCode 参数(国家区号),用于就近选择涂鸦 IoT 平台的可用区。各个可用区的数据是相互独立的,因此在 中国大陆(86) 注册的账号,在 美国(1) 无法使用,会报错用户不存在。

    1. 获取手机验证码和注册手机账号密码。

      // 发送验证码	
      [[TuyaSmartUser sharedInstance] sendVerifyCode:@"86" phoneNumber:@"1300****" type:1 success:^{
          [TPDemoProgressUtils showSuccess:@"Verification code sent successfully" toView:nil];
      } failure:^(NSError *error) {
          [TPDemoProgressUtils showError:error.localizedDescription];
      }];
      
      // 通过验证码注册账号
      [[TuyaSmartUser sharedInstance] registerByPhone:@"86" phoneNumber:@"1300****" password:@"***" code:@"code" success:^{
          // 注册成功,跳转到首页
          [[TYDemoApplicationImpl sharedInstance] resetRootViewController:[TYDemoTabBarViewController class]];
      } failure:^(NSError *error) {
          [TPDemoProgressUtils showError:error.localizedDescription];
      }];
      
    2. 判断用户是否已经登录。

      在程序启动后,如果登录过了就不需要重复登录,session的有效期是45天,直接到设备列表页面。如果没有登录过,就先到登录页进行登录,登录成功后再跳转到设备列表页。

      // 判断是否已经登录
      if ([TuyaSmartUser sharedInstance].isLogin) {
          // 首页
          [self resetRootViewController:[TYDemoTabBarViewController class]];
      } else {
          // 登录页
          [[TYDemoRouteManager sharedInstance] openRoute:kTYDemoPopLoginVC withParams:nil];
      }
      
    3. Session 失效处理。

      长期未登录或者密码修改后的账号,在访问服务端接口的时候会返回 Session 过期的错误,需要监听 TuyaSmartUserNotificationUserSessionInvalid 通知,跳转至登录页面重新登录。

      - (void)loadNotification {
          [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sessionInvalid) name:TuyaSmartUserNotificationUserSessionInvalid object:nil];
      }
      
      - (void)sessionInvalid {
              NSLog(@"sessionInvalid");
              //跳转至登录页面
              MyLoginViewController *vc = [[MyLoginViewController alloc] init];
              self.window.rootViewController = vc;
            [self.window makeKeyAndVisible];
      }
      
  • 第 4 步:为用户创建家庭

    用户登录成功后,需要通过 TuyaSmartHomeManager 去获取整个家庭列表的信息,然后初始化其中的一个家庭TuyaSmartHome,获取家庭详情信息,就可以对家庭中的设备进行操作控制。

    类名 说明
    TuyaSmartHomeManager 获取家庭列表、家庭列表排序、添加家庭
    TuyaSmartHomeManagerDelegate 增删家庭、MQTT连接成功回调
    1. 家庭列表获取。

      在设备列表页面,您可以先获取用户下的家庭列表,如果没有家庭,则创建一个默认家庭,避免设备被激活时没有归属的家庭。

      获取到家庭列表之后,您就可以初始化一个家庭,然后从涂鸦 IoT 云端获取家庭的详情,刷新首页的设备列表。

      - (void)initData {
          
          _homeManager = [[TuyaSmartHomeManager alloc] init];
          _homeManager.delegate = self;
          
          // 获取本地的当前家庭
          NSString *homeId = [[NSUserDefaults standardUserDefaults] objectForKey:kDefaultCurrentHomeId];
          if ([homeId longLongValue] > 0) {
              self.home = [TuyaSmartHome homeWithHomeId:[homeId longLongValue]];
              if (self.home) {
                  self.home.delegate = self;
                  self.topBarView.leftItem.title = [NSString stringWithFormat:@"%@ ∨", self.home.homeModel.name];
                  [TYDemoSmartHomeManager sharedInstance].currentHomeModel = self.home.homeModel;
                  // 刷新设备列表数据
                  [self reloadDataFromCloud];
              } else {
                  // 如果没有,获取第一个家庭
                  [self loadFirstHomeData];
              }
          } else {
              // 如果没有,获取第一个家庭
              [self loadFirstHomeData];
          }
      }
      
      - (void)reloadDataFromCloud {
      
          WEAKSELF_AT
          [self.refreshControl beginRefreshing];
          // 获取当前家庭的详情,并刷新列表
          [self.home getHomeDetailWithSuccess:^(TuyaSmartHomeModel *homeModel) {
              
              [weakSelf_AT reloadData];
          } failure:^(NSError *error) {
              if ([error.localizedFailureReason isEqualToString:@"PERMISSION_DENIED"]) {
                  [weakSelf_AT loadFirstHomeData];
              }
              [weakSelf_AT.refreshControl endRefreshing];
          }];
      }	
      
    2. 新增一个家庭。

      // add home
      - (void)rightBtnAction {
          NSString *homeName = [NSString stringWithFormat:@"Home_%@", @(self.homeManager.homes.count)];
          WEAKSELF_AT
          [self.homeManager addHomeWithName:homeName geoName:@"hangzhou" rooms:@[@"room1"] latitude:0 longitude:0 success:^(long long homeId) {
              [TPDemoProgressUtils showSuccess:@"Add Success" toView:nil];
              // 切换到新增家庭
              TuyaSmartHome *home = [TuyaSmartHome homeWithHomeId:homeId];
              [weakSelf_AT swithCurrentHomeIdWithHomeModel:home.homeModel];
          } failure:^(NSError *error) {
              [TPDemoProgressUtils showError:error.localizedDescription];
          }];
      }
      
    3. 家庭列表信息变化回调。

      实现 TuyaSmartHomeManagerDelegate 代理协议后,可以在家庭列表更变的回调中进行处理。

      #pragma mark - TuyaSmartHomeManagerDelegate
      
      // 添加一个家庭
      - (void)homeManager:(TuyaSmartHomeManager *)manager didAddHome:(TuyaSmartHomeModel *)home {
          NSLog(@"Add a home %@", home.name);
      }
      
      // 删除一个家庭
      - (void)homeManager:(TuyaSmartHomeManager *)manager didRemoveHome:(long long)homeId {
          // 如果删除的家庭是当前家庭,当前家庭切换到另外一个
          if ([TYDemoSmartHomeManager sharedInstance].currentHomeModel.homeId == homeId) {
              [self loadFirstHomeData];
          }
      }
      
      // MQTT连接成功
      - (void)serviceConnectedSuccess {
          // 去云端获取当前家庭的详情,然后去刷新 UI
          [self reloadDataFromCloud];
      }
      
    4. 单个家庭信息变化的回调。

      实现 TuyaSmartHomeDelegate 代理协议后,可以在单个家庭信息更变的回调中进行处理。

      #pragma mark - TuyaSmartHomeDelegate
      
      // 家庭的信息更新,例如name
      - (void)homeDidUpdateInfo:(TuyaSmartHome *)home {
          [self.tableView reloadData];
      }
      
      // 添加一个房间
      - (void)home:(TuyaSmartHome *)home didAddRoom:(TuyaSmartRoomModel *)room {
          [self.tableView reloadData];
      }
      
      // 删除一个房间
      - (void)home:(TuyaSmartHome *)home didRemoveRoom:(long long)roomId {
          [self.tableView reloadData];
      }
      
      // 我收到的共享设备列表变化
      - (void)homeDidUpdateSharedInfo:(TuyaSmartHome *)home {
          [self.tableView reloadData];
      }
      
      // 房间信息变更,例如name
      - (void)home:(TuyaSmartHome *)home roomInfoUpdate:(TuyaSmartRoomModel *)room {
          [self.tableView reloadData];
      }
      
      // 房间与设备,群组的关系变化
      - (void)home:(TuyaSmartHome *)home roomRelationUpdate:(TuyaSmartRoomModel *)room {
          [self.tableView reloadData];
      }
      
      // 添加设备
      - (void)home:(TuyaSmartHome *)home didAddDeivice:(TuyaSmartDeviceModel *)device {
          [self.tableView reloadData];
      }
      
      // 删除设备
      - (void)home:(TuyaSmartHome *)home didRemoveDeivice:(NSString *)devId {
          [self.tableView reloadData];
      }
      
      // 设备信息更新,例如name
      - (void)home:(TuyaSmartHome *)home deviceInfoUpdate:(TuyaSmartDeviceModel *)device {
          [self.tableView reloadData];
      }
      
      // 设备DP数据更新
      - (void)home:(TuyaSmartHome *)home device:(TuyaSmartDeviceModel *)device dpsUpdate:(NSDictionary *)dps {
          [self.tableView reloadData];
      }
      
      // 添加群组
      - (void)home:(TuyaSmartHome *)home didAddGroup:(TuyaSmartGroupModel *)group {
          [self.tableView reloadData];
      }
      
      // 群组DP数据更新
      - (void)home:(TuyaSmartHome *)home group:(TuyaSmartGroupModel *)group dpsUpdate:(NSDictionary *)dps {
          [self.tableView reloadData];
      }
      
      // 删除群组
      - (void)home:(TuyaSmartHome *)home didRemoveGroup:(NSString *)groupId {
          [self.tableView reloadData];
      }
      
      // 群组信息更新,例如name
      - (void)home:(TuyaSmartHome *)home groupInfoUpdate:(TuyaSmartGroupModel *)group {
          [self.tableView reloadData];
      }
      
  • 第 5 步:设备 Wi-Fi 快连配网

    设备配网是指设备在云端进行激活注册。

    类名 说明
    TuyaSmartActivator(单例) 提供 Wi-Fi 快连配网、热点配网、有线设备激活、子设备激活等配网能力
    需要在主线程中调用该类。

    Wi-Fi 快连配网又称快连模式(SmartConfig)或 EZ 配网。用户将手机连接到家用路由器后,利用路由器广播报与设备进行通信配对。

    image1111.png

    - (void)addDeviceWithEZMode {
        
        // 获取 Token
        WEAKSELF_AT
        id<TYDemoDeviceListModuleProtocol> impl = [[TYDemoConfiguration sharedInstance] serviceOfProtocol:@protocol(TYDemoDeviceListModuleProtocol)];
        long long homeId = [impl currentHomeId];
        [[TuyaSmartActivator sharedInstance] getTokenWithHomeId:homeId success:^(NSString *token) {
    
            // 开始配网
            [weakSelf_AT commitEZModeActionWithToken:token];
        } failure:^(NSError *error) {
            
            info = [NSString stringWithFormat:@"%@: token fetch failed, error message is %@",NSStringFromSelector(_cmd),error.localizedDescription];
            [weakSelf_AT appendConsoleLog:info];
        }];
    }
    // 开始配网
    - (void)commitEZModeActionWithToken:(NSString *)token {
        [TuyaSmartActivator sharedInstance].delegate = self;
        [[TuyaSmartActivator sharedInstance] startConfigWiFi:TYActivatorModeEZ ssid:self.ssidField.text password:self.passwordField.text token:token timeout:timeout];
    }
    
    #pragma mark - TuyaSmartActivatorDelegate
    
    - (void)activator:(TuyaSmartActivator *)activator didReceiveDevice:(TuyaSmartDeviceModel *)deviceModel error:(NSError *)error {
        
        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(countDown) object:nil];
        timeout = timeLeft;
        [self hideProgressView];
        
        NSString *info = [NSString stringWithFormat:@"%@: Finished!", NSStringFromSelector(_cmd)];
        [self appendConsoleLog:info];
        if (error) {
            // 配网失败
            info = [NSString stringWithFormat:@"%@: Error-%@!", NSStringFromSelector(_cmd), error.localizedDescription];
            [self appendConsoleLog:info];
        } else {
            // 配网成功
            info = [NSString stringWithFormat:@"%@: Success-You've added device %@ successfully!", NSStringFromSelector(_cmd), deviceModel.name];
            [self appendConsoleLog:info];
        }
    }
    
  • 第 6 步:控制智能设备

    设备激活并联网后,就可以进行智能设备的控制,然后监听设备控制变化的回调。

    类名 说明
    TuyaSmartDevice 设备管理类
    TuyaSmartDeviceModel 设备数据模型类

    在进行设备控制之前,建议您先了解设备功能点的概念。功能点是对产品功能的抽象表示,是具体智能设备功能的抽象,用于描述产品功能及其参数。更多详情,请参考 自定义功能 中的功能点相关信息。其中功能数据类型您需要注意以下事项:

    • 控制命令的发送需要特别注意数据类型。

      例如,功能点的数据类型是数值型(value),那控制命令发送的应该是 @{@"2": @(25)} 而不是 @{@"2": @"25"}

    • 透传类型传输的 byte 数组是字符串格式(16 进制字符串)、字母需小写并且必须是 偶数位

      例如,正确的格式是: @{@"1": @"011f"} 而不是 @{@"1": @"11f"}

    TuyaSmartDeviceModel 类的 dps 属性(NSDictionary 类型)定义了智能设备的状态,称作数据点(DP)或功能点。

    dps 字典里的每个 key 对应一个 DP ID,value 对应一个 DP 取值。具体产品的功能点定义请参考 涂鸦 IoT 平台 的产品功能,如下图所示:

    image.png

    发送控制指令按照 {"<dpId>":"<dpValue>"} 的格式。根据后台该产品的功能点定义,进行如下类似的控制逻辑:

    示例代码

    - (void)publishDps {
        // self.device = [TuyaSmartDevice deviceWithDeviceId:@"your_device_id"];
        // self.device.delegate = self;
        
        NSDictionary *dps;
        //设置 DP ID 为1的布尔型功能点示例 作用:开关打开
        dps = @{@"1": @(YES)};
        //设置 DP ID 为4的字符串型功能点示例 作用:设置RGB颜色为ff5500
        dps = @{@"4": @"ff5500"};
        //设置 DP ID 为5的枚举型功能点示例 作用:设置档位为2档
        dps = @{@"5": @"2"};
        //设置 DP ID 为6的数值型功能点示例 作用:设置温度为20°
        dps = @{@"6": @(20)};
        //设置 DP ID 为15的透传型(byte数组)功能点示例 作用:透传红外数据为1122
        dps = @{@"15": @"1122"};
        //多个功能合并发送
        dps = @{@"1": @(YES), @"4": @(ff5500)};
        [self.device publishDps:dps success:^{
            NSLog(@"publishDps success");
            //下发成功,状态上报通过 deviceDpsUpdate方法回调
        } failure:^(NSError *error) {
            NSLog(@"publishDps failure: %@", error);
        }];
    }
    
  • 第 7 步:设备初始化和设备变化代理监听

    您需要通过 TuyaSmartHome 初始化一个 home 实例,然后调用接口 getHomeDetailWithSuccess:failure: 获取家庭的详情,同步过家庭的详情后,初始化设备才能成功。错误的设备 ID 可能会导致初始化失败,此时设备的实例返回 nil

    - (void)publishDps {
        self.device = [TuyaSmartDevice deviceWithDeviceId:@"your_device_id"];
        self.device.delegate = self;
        
        [self.device publishDps:@{@"1" : @{YES}} success:^{
            NSLog(@"publishDps success");
            // 下发成功,状态上报通过 deviceDpsUpdate方法回调
        } failure:^(NSError *error) {
            NSLog(@"publishDps failure: %@", error);
        }];
    }
    
    #pragma mark - TuyaSmartDeviceDelegate
    
    /// DP 数据更新
    - (void)device:(TuyaSmartDevice *)device dpsUpdate:(NSDictionary *)dps {
        [self.tableView reloadData];
    }
    
    /// 设备信息更新
    - (void)deviceInfoUpdate:(TuyaSmartDevice *)device {
        
    }
    
    /// 设备被移除
    - (void)deviceRemoved:(TuyaSmartDevice *)device {
        
    }
    

小结

您已经完成了一个 IoT App 对智能硬件设备的激活入网,设备控制等主要环节,接下来可以继续尝试更多更丰富的控制功能吧。