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

发布时间:2024年01月24日

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. 结束

  • 恭喜你 🎉 完成了本教程的学习!
  • 立即开发自己的项目面板。
文章来源:https://blog.csdn.net/Ms_Smart/article/details/135755908
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。