如何使用 Ray 开发万能面板(万字 全流程 手把手教学)

1. IoT 平台创建产品

面板作为 IoT 智能设备在 App 终端上的产品形态,创建产品之前,首先来了解一下什么是面板,以及和产品设备之间的关系。

1.1 面板是什么?

  1. 面板 是运行在智能生活 AppOEM App(涂鸦定制 App) 上的界面交互程序,用于控制 智能设备 的运行,展示 智能设备 实时状态。
  2. 产品 将 面板 与 智能设备 联系起来,产品描述了其具备的功能、在 App 上面板显示的名称、智能设备拥有的功能点等。
  3. 智能设备 是搭载了 涂鸦智能模组 的设备,通常在设备上都会贴有一张 二维码,使用 智能生活 App 扫描二维码,即可在 App 中获取并安装该设备的控制 面板

1.2 产品、面板、设备关系

下图描述了产品、面板和设备三者之间的关系

1.3 创建产品

由于产品定义了面板和设备所拥有的功能点,所以在开发一个智能设备面板之前,我们首先需要创建一个产品,定义产品有哪些功能点,然后面板中再根据这些功能点一一实现。

这部分我们在 IoT 平台上进行操作,注册登录 IoT 平台:

  1. 点击左侧产品菜单,产品开发,创建产品,以大家电为例,选择标准类目,选择大家电 -> 空调:

  1. 填写产品名称,输入"万能面板",其余选项默认即可,点击创建产品按钮,完成产品创建。

2. 产品功能点定义

创建完成产品后,进入功能定义页面,这里列出了空调类目下可选的标准功能点,这里我们点击全部选择,点击确定完成产品初始功能点设置:

 

3. 小程序平台创建小程序

现在我们已经有了一个产品,并且功能点已设置完成,接下来就进入面板小程序的开发流程。前往注册登录涂鸦小程序开发者平台,创建我们的小程序项目。

点击新建,输入小程序名称"万能面板",小程序类型选择 面板小程序,面板类型选择公版,点击确定完成创建:

 

4. 安装小程序 IDE 创建项目代码

安装并打开小程序 IDE 工具(前往下载小程序 IDE

使用涂鸦 IoT 平台账号登录 IDE 后,使用智能生活 App 扫码授权 IDE:

4.1 创建项目代码

点击新建,输入项目名称:万能面板,关联智能小程序选择第 3 步小程序平台创建的小程序,关联产品选择第 1 步在 IoT 平台创建的产品:

点击下一步选择模版,选择 App 面板开发 Ray 应用(jotai),生成 Ray 面板项目(Ray 类似 Taro,是一个多端研发框架,编写一套代码编译到多端)

4.2 启动项目

创建后,点击工具栏在 vscode 打开项目代码:

涂鸦小程序 IDE 工具导入小程序后会自动安装依赖,并实时编译运行。

如果出现Error(MiniKit 不存在指定的版本 2.3.3)类似的错误,点击环境配置 -> Kit 管理,选择推荐的版本即可:

 

5. 小程序开发工作流程

面板小程序开发主要围绕 IoT 平台、小程序开发平台、小程序 IDE 之间进行,用一张图来概括整体流程:

 

6. 项目代码结构

以上完成了一个面板小程序的创建流程,下面进入到实际代码的开发教程。

6.1 代码目录结构

首先了解项目的目录结构:

6.2 页面路由配置

编写代码主要在 pages 文件夹中进行,创建你的页面代码,然后将路由地址配置到 src/routes.config.ts 文件中:

其他文件的说明,会在下文开发步骤中顺路提到。

7. 编写代码

7.1 使用 jotai 状态管理读取设备数据

在 IDE 中点击 在 vscode 打开 按钮,打开 vscode 编辑器,修改 src/pages/home/index.tsx 文件,清空内容,输入以下代码:

import React from "react";

import { useDevInfo } from "@ray-js/panel-sdk";
import { useAtomValue } from "jotai";
import { selectDpStateAtom } from "@/atoms";
import { View } from "@ray-js/ray";

export default () => {
  // 项目启动时,会自动拉取 IoT 平台 productId 对应的产品信息
  const devInfo = useDevInfo() || {};

  // 从 jotai 中读取 dpState 数据
  const dpState = useAtomValue(selectDpStateAtom) || {};

  console.log("devInfo", devInfo); // 打印查看 devInfo 内容
  console.log("dpState", dpState); // 打印查看 dpState 内容

  return <View>hello world</View>;
};

7.2 IDE 调试器 console 打印日志

查看 IDE 调试 console,可以看到打印出 devInfo 内容:

7.3 devInfo 字段说明

在 IDE 调试器 console 面板中默认可以看到有很多输出,jotai 状态中内置有很多数据,其中最重要的就是 devInfo ,即设备信息(Device Information

需要了解 devInfo 中几个重要的属性,

  1. codeIds: 一个对象,key 是 dpCode,值是 dpId
  2. devId: 设备的 id,虚拟设备会以 vdevo 开头
  3. deviceOnline: 设备是否在线
  4. dps: 一个对象,key 是 dpId,值是 DP 的状态
  5. idCodes: 一个对象,与 codeIds 相反
  6. panelConfig: 面板配置,其中 bic 是云功能配置
  7. productId: 当前设备绑定的产品
  8. schema: 产品的功能点定义,其中描述了 DP 的 code、类型、值范围属性、icon 图标
  9. state: 一个对象,key 是 dpCode,值是 DP 的状态
  10. ui: 面板的 uiId

通常代码中获取 devInfo 是通过 jotai 状态管理 API 来读取。(本文中的 DP 即 功能点,来自 IoT 平台功能定义)

7.4 获取产品功能定义(schema)

在 devInfo 中可以获取到产品的功能定义

const schema = devInfo.schema; // 功能点定义
const state = devInfo.state; // 设备功能点状态

遍历 schema 可以获取到产品所有的 DP 功能点及属性:

schema.map(item => {
  // item.code
  // item.property
  // ...
  return <Text>{item.name}</Text>;
});

这里根据 item.property.type 就可以判断并渲染不同类型 DP 功能点的 UI 展示

 

8. 样式定义

8.1 使用 rpx 单位

Ray 开发样式使用 less 语言,支持 css module,新建文件 index.module.less,编写内容例如:

src/pages/home/index.module.less

.container {
  border-radius: 24rpx;
  background-color: #fff;
  margin-bottom: 24rpx;
}

rpx 单位是 Ray 框架提供的特有单位,能够做到不同设备上的自适应。

8.2 使用 css module 方式添加样式

在代码中使用样式:

import React from "react";

import { useDevInfo } from "@ray-js/panel-sdk";
import { View } from "@ray-js/components";
import styles from "./index.module.less"; // 注意样式引入方式

export default () => {
  const devInfo = useDevInfo() || {};

  // 添加 className
  return <View className={styles.container}>hello world</View>;
};

 

9. 渲染 DP 的 6 种基本类型

9.1 功能定义 Schema 的结构

  • 在涂鸦智能设备的功能定义中,一共有 6 种类型的 DP,分别是布尔型、数值型、枚举型、故障型、字符型、透传型。
  • 每种 DP 有不同的数值类型和范围,在面板渲染时需要根据类型和范围渲染 UI 视图。

通过 console 日志可以了解到 schema 的数据结构:

详细请参考文档 自定义功能

9.2 渲染 Schema 数据展示

src/home/index.tsx 编写输入以下代码:

import React from "react";

import { useAtomValue } from "jotai";
import { useDevInfo } from "@ray-js/panel-sdk";
import { selectDpStateAtom } from "@/atoms";
import { View } from "@ray-js/components";

export default () => {
  const devInfo = useDevInfo() || {};
  const dpState = useAtomValue(selectDpStateAtom) || {};

  console.log("devInfo", devInfo);
  console.log("dpState", dpState);

  return (
    <View>
      {Object.keys(devInfo.schema || {}).map(dpCode => {
        // 遍历渲染每个功能点
        return (
          <View key={dpCode}>
            {dpCode}: {dpState[dpCode]}
          </View>
        );
      })}
    </View>
  );
};

重新编译,可以看到 IDE 左边面板中显示了所有 DP 的 code 及对应设备状态 :

 

10. Bool 布尔类型功能点渲染

10.1 bool 功能点数据结构

在 IDE console 面板中,可以看到 bool 类型功能点的数据结构,例如:

{
  dptype: "obj";
  id: "39";
  type: "bool";
}

10.2 安装扩展组件库

使用工具包 @ray-js/components-ty 提供的开关组件 TySwitch 来渲染,执行以下命令,安装 Ray 提供的扩展组件库:

yarn add @ray-js/components-ty

10.3 使用开关组件 TySwitch

继续编写代码,输入以下内容:

import React from "react";

import { useAtomValue } from "jotai";
import { useDevInfo } from "@ray-js/panel-sdk";
import { selectDpStateAtom } from "@/atoms";
import { TySwitch } from "@ray-js/components-ty";
import { View } from "@ray-js/components";

export default () => {
  const devInfo = useDevInfo() || {};
  const dpState = useAtomValue(selectDpStateAtom) || {};

  console.log("devInfo", devInfo);

  const schema = devInfo.schema || {};

  return (
    <View>
      {Object.keys(schema).map(dpCode => {
        const props = schema[dpCode];
        // 判断如果是 bool 类型,返回 TySwitch
        if (props.type === "bool") {
          return (
            <View>
              {dpCode}: <TySwitch checked={dpState[dpCode]} />
            </View>
          );
        }
        return (
          <View key={dpCode}>
            {dpCode}: {dpState[dpCode]}
          </View>
        );
      })}
    </View>
  );
};

编译后,IDE 中渲染界面如下,看到所有的 bool 型 DP 已经成功渲染出了开关组件

10.4 功能点多语言获取

DP 在功能定义时,在产品信息中已经有相应的多语言文本,这里使用 src/i18n 下的 Strings 工具获取 DP 文本,输入以下代码:

import React from "react";

import { useAtomValue } from "jotai";
import { useDevInfo } from "@ray-js/panel-sdk";
import { selectDpStateAtom } from "@/atoms";
import { TySwitch } from "@ray-js/components-ty";
import { View } from "@ray-js/components";
import Strings from "@/i18n";

export default () => {
  const devInfo = useDevInfo() || {};
  const dpState = useAtomValue(selectDpStateAtom) || {};

  console.log("devInfo", devInfo);

  const schema = devInfo.schema || {};

  return (
    <View>
      {Object.keys(schema).map(dpCode => {
        const props = schema[dpCode];
        if (props.type === "bool") {
          // 使用 Strings.getDpLang 方法获取多语言
          return (
            <View>
              {Strings.getDpLang(dpCode)}:{" "}
              <TySwitch checked={dpState[dpCode]} />
            </View>
          );
        }
        return (
          <View key={dpCode}>
            {dpCode}: {dpState[dpCode]}
          </View>
        );
      })}
    </View>
  );
};

再次编译查看 IDE 渲染结果,可以看到所有的 bool 类型 DP 文本都已显示出中文:

这样就完成了 Bool 类型 DP 功能点的 UI 渲染。

 

11. 使用虚拟设备调试 Bool 功能点

11.1 使用下发 DP 功能点 API

上文介绍了如何编写代码渲染 DP 点,但是要控制设备运行,还需要进行 DP 下发, 使用 ray 框架 API 下发能力,例如:

import { publishDps } from "@ray-js/api";

// 下发给 deviceId 对应设备,dps中的 key 是 dpId,value 是 dpValue。(关于 dps、dpId 的说明可以看 `编写代码` 一节)
publishDps({
  deviceId: devInfo.devId,
  dps: {
    1: true
  }
});

11.2 下发 Bool 开关功能点

现在来示例开关功能点的 DP 下发 操作,编写代码:

import React from "react";

import { useAtomValue } from "jotai";
import { useDevInfo } from "@ray-js/panel-sdk";
import { selectDpStateAtom } from "@/atoms";
import { TySwitch } from "@ray-js/components-ty";
import { View } from "@ray-js/components";

export default () => {
  const devInfo = useDevInfo() || {};
  const dpState = useAtomValue(selectDpStateAtom) || {};

  const schema = devInfo.schema || {};

  const putDeviceData = (code, value) => {
    const dpId = schema[code].id;
    publishDps({
      deviceId: devInfo.devId,
      dps: {
        [dpId]: value
      }
    });
  };

  return (
    <View>
      {Object.keys(schema).map(dpCode => {
        const props = schema[dpCode];
        // 判断如果是 bool 类型,返回 TySwitch
        if (props.type === "bool") {
          return (
            <View>
              {dpCode}:{" "}
              <TySwitch
                checked={dpState[dpCode]}
                onChange={value => putDeviceData(dpCode, value)}
              />
            </View>
          );
        }
        return (
          <View key={dpCode}>
            {dpCode}: {dpState[dpCode]}
          </View>
        );
      })}
    </View>
  );
};

11.3 虚拟设备插件调试

IDE 编译后运行,使用虚拟设备插件进行调试,在调试器 Virtual Device 面板中,选择可视化面板,点击调试器中的开关,可以看到左边模拟器中的开关收到了 DP 上报,并做出了相同的切换动作。

 

12. Enum 枚举类型功能点渲染

12.1 enum 功能点数据结构

enum 类型 DP 点的 schema 属性配置如下:

{
  dptype: "obj";
  id: "20";
  range: ["cancel", "1h", "2h", "3h", "4h", "5h", "6h"];
  type: "enum";
}

12.2 使用弹窗组件 TyActionsheet 和列表组件 TyCell

range 中的即枚举条目,使用 @ray-js/components-ty 中的 ActionSheet 弹窗组件 + Cell 列表组件 进行渲染,编写以下代码:

import React, { useState } from "react";

import { useAtomValue } from "jotai";
import { useDevInfo } from "@ray-js/panel-sdk";
import { selectDpStateAtom } from "@/atoms";
import { TyActionsheet, TyCell, TySwitch } from "@ray-js/components-ty";
import { Button, ScrollView, View } from "@ray-js/components";
import Strings from "@/i18n";

export default () => {
  const devInfo = useDevInfo() || {};
  const dpState = useAtomValue(selectDpStateAtom) || {};

  console.log("devInfo", devInfo);

  const schema = devInfo.schema || {};

  const [showEnumDp, setShowEnumDp] = useState(null);

  return (
    <View>
      {Object.keys(schema).map(dpCode => {
        const props = schema[dpCode];
        // 这里为了文章篇幅,忽略其他类型的。本地代码可复制上节 bool 类型的到这里
        if (props.type === "enum") {
          return (
            <View>
              {Strings.getDpLang(dpCode)}:
              <Button onClick={() => setShowEnumDp(dpCode)}>
                {Strings.getDpLang(dpCode, dpState[dpCode])}
              </Button>
              <TyActionsheet
                header={Strings.getDpLang(dpCode)}
                show={showEnumDp === dpCode}
                onCancel={() => setShowEnumDp(null)}
              >
                <View style={{ overflow: "auto", height: "200rpx" }}>
                  <TyCell.Row
                    rowKey="title"
                    dataSource={props.range.map(item => ({
                      title: Strings.getDpLang(dpCode, item)
                    }))}
                  />
                </View>
              </TyActionsheet>
            </View>
          );
        }
        return (
          <View key={dpCode}>
            {dpCode}: {dpState[dpCode]}
          </View>
        );
      })}
    </View>
  );
};

点击按钮,触发 ActionSheet 弹窗,弹窗中使用 Cell 列表组件 渲染多个枚举项,效果如下:

 

13. String 字符类型功能点渲染

13.1 string 功能点数据结构

string 类型 DP 点的 schema 属性配置如下:

{
  dptype: "obj";
  id: "20";
  type: "string";
}

13.2 使用输入组件 Input

字符型可以使用 Input 组件渲染,实现如下:

import { useAtomValue } from "jotai";
import { useDevInfo } from "@ray-js/panel-sdk";
import { selectDpStateAtom } from "@/atoms";
import { Input } from "@ray-js/components";

export default () => {
  const devInfo = useDevInfo() || {};
  const dpState = useAtomValue(selectDpStateAtom) || {};

  console.log("devInfo", devInfo);

  const schema = devInfo.schema || {};

  const [showEnumDp, setShowEnumDp] = useState(null);

  return (
    <View>
      {Object.keys(schema).map(dpCode => {
        const props = schema[dpCode];
        // 这里为了文章篇幅,忽略其他类型的。本地代码可复制上节类型的到这里
        if (props.type === "string") {
          return <Input value={dpState[dpCode]} />;
        }
        return (
          <View key={dpCode}>
            {dpCode}: {dpState[dpCode]}
          </View>
        );
      })}
    </View>
  );
};

Raw 类型 DP 一般不做渲染,如果需要可以按照 String 类型进行处理

 

14. Value 字符类型功能点渲染

14.1 value 功能点数据结构

value 类型 DP 点的 schema 属性配置如下:

{
  dptype: "obj";
  id: "18";
  max: 100;
  min: 0;
  scale: 0;
  step: 1;
  type: "value";
  unit: "%";
}

14.2 使用滑动条组件 Slider

其中定义了数值范围和单位,使用 Slider 组件进行渲染:

import { useAtomValue } from "jotai";
import { useDevInfo } from "@ray-js/panel-sdk";
import { selectDpStateAtom } from "@/atoms";

import { Slider } from "@ray-js/components";

export default () => {
  const devInfo = useDevInfo() || {};
  const dpState = useAtomValue(selectDpStateAtom) || {};

  console.log("devInfo", devInfo);

  const schema = devInfo.schema || {};

  const [showEnumDp, setShowEnumDp] = useState(null);

  return (
    <View>
      {Object.keys(schema).map(dpCode => {
        const props = schema[dpCode];
        // 这里为了文章篇幅,忽略其他类型的。本地代码可复制上节类型的到这里
        if (props.type === "value") {
          return (
            <View>
              {Strings.getDpLang(dpCode)}:
              <Slider
                step={props?.step}
                max={props?.max}
                min={props?.min}
                value={dpState[dpCode]}
              />
            </View>
          );
        }
        return (
          <View key={dpCode}>
            {dpCode}: {dpState[dpCode]}
          </View>
        );
      })}
    </View>
  );
};

Slider 组件是一个滑动条组件,可以根据数值范围进行约束,IDE 渲染如下,所有 value 类型 DP 都渲染出了滑动条:

 

15. Bitmap 故障类型功能点渲染

15.1 bitmap 功能点数据结构

bitmap 类型一般用作故障上报,属性配置如下:

{
  dptype: "obj";
  id: "22";
  label: ["sensor_fault", "temp_fault"];
  maxlen: 2;
  type: "bitmap";
}

15.2 使用消息提醒组件 Notification

故障型 DP 使用弹窗渲染,这里使用 @ray-js/ray-components-plus 提供的 Notification 来实现:

import { useAtomValue } from "jotai";
import { useDevInfo } from "@ray-js/panel-sdk";
import { selectDpStateAtom } from "@/atoms";

import { Notification } from "@ray-js/ray-components-plus";

export default () => {
  const devInfo = useDevInfo() || {};
  const dpState = useAtomValue(selectDpStateAtom) || {};

  console.log("devInfo", devInfo);

  const schema = devInfo.schema || {};

  // 弹窗消息在 useEffect 中进行
  useEffect(() => {
    Object.keys(schema).forEach(dpCode => {
      const props = schema[dpCode];
      if (props.type === "bitmap") {
        Notification.show({
          message: Strings.getFaultStrings(dpCode, dpState[dpCode]),
          icon: "warning"
        });
      }
    });
  }, [schema, dpState]);

  return (
    <View>
      {Object.keys(schema).map(dpCode => {
        const props = schema[dpCode];
        // 这里为了文章篇幅,忽略其他类型的。本地代码可复制上节类型的到这里
        return (
          <View key={dpCode}>
            {dpCode}: {dpState[dpCode]}
          </View>
        );
      })}
    </View>
  );
};

bitmap 类型通常用来做故障上报,以弹窗消息提示,所以需要在 useEffect 中遍历 schema 找到 bitmap 类型并弹窗提醒。

 

16. 自定义功能点

16.1 IoT 平台添加自定义功能点

以上 schema 中是家电类目自带的标准 DP 点,我们还可以增加自定义的 DP,打开 IoT 平台产品详情 -> 功能定义 -> 自定义功能 一栏。

16.2 自定义功能点属性配置

点击添加功能,新建一个 bool 型 DP:

16.3 IDE 刷新拉取自定义功能点配置

然后回到 IDE 中,点击编译按钮刷新面板,可以看到渲染出了我们新增加的一个 DP 点:

 

17. 虚拟设备调试

17.1 虚拟设备解决什么问题?

面板运行时需要获取设备信息来显示设备的运行状态,在开发阶段,如果没有真实的智能硬件设备,我们可以借助虚拟设备来辅助调试,可以达到和真实设备一样的效果。虚拟设备和真实设备对于面板小程序来说可以是等效的,其关系如下图所示:

17.2 授权 IDE 登录

在使用虚拟设备前,请先确认已授权登录态,点击右上角登录,使用智能生活 App 扫描二维码授权登录。

17.3 App 扫码创建虚拟设备

登录后进入万能面板项目,点击调试工具 Virtual Device,使用智能生活 App 扫码创建虚拟设备:

17.4 操作指南

虚拟设备面板中分为 3 个区块,左边是兜底面板用于展示当前产品的 DP 功能点,右侧是虚拟设备 DP 控制列表,可以改变 DP 状态然后点击上报按钮,下方是 Log 面板用于输出 MQTT 日志。点击"可视化面板",切换到兜底面板视图,与控制面板功能相同,更加直观的展现产品功能点。

17.5 调试我们编写的代码!

下面来调试我们刚编写的代码:

 

18. 真机调试

18.1 获取虚拟设备 ID

在虚拟设备界面右侧 -> 设备信息,点击 deviceId 右侧的复制按钮

18.2 设置真机调试参数

点击编译参数设置,按照格式填入真机调试参数。 例如:deviceId=vdevo165398364416684

18.3 App 扫码打开调试器

设置好编译参数后,点击真机调试按钮(真机需要前往下载智能生活 App

使用智能生活 App 扫描二维码,打开并进入小程序面板,可以在真机上进行调试万能面板。

 

19. 示例开源代码

到此你已经熟悉了 6 种 DP 点的渲染及下发,附上万能面板代码开源地址:

panel-universal

经过样式优化后的万能面板展示:

 

20. 小程序投放流程

20.1 上传源码包

在 IDE 中完成调试后,点击上传源码按钮,输入版本号及说明,点击上传预览包

上传完成后,在小程序开发者平台版本管理中可以看到已经上传的版本列表

20.2 添加预览包白名单

在上传完成源码包后,在小程序开发者平台 -> 版本管理中,选择需要预发布的版本,设为体验版:

点击白名单页面,添加自己智能生活 App 的账号:

添加完后,可以点击 版本管理 -> 体验二维码,查看小程序码,使用智能生活 App 扫码预览小程序。

20.3 审核上线

非官方主体的面板小程序,在提交审核前需要完善 IoT 平台展示信息。其余审核上线注意项可参考:上线审核

 

21. 结束

  • 恭喜你 🎉 完成了本教程的学习!
  • 立即开发自己的项目面板。
  • 31
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IoT砖家涂拉拉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值