本文档面向已经了解 面板小程序开发 的开发者,你需要充分的了解什么是面板小程序 产品功能 若您对上述概念有所疑问,我们推荐你去看一些预备知识。如果您已了解,可跳过本文。

理解关系

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

照明模板使用 SDM(Smart Device Model) 开发,关于 SDM 相关可以 查看 SDM 文档

相关概念

产品名称:照明光源五路灯

产品介绍

根据支持的白光、彩光 dp 不同,照明中灯分为一路至五路。

其含义为:

1.五路 - 支持彩光 colour_data 、白光亮度 bright_value 、白光色温 temp_value

2.四路 - 支持彩光、白光亮度

3.三路 - 支持彩光

4.二路 - 支持白光亮度、白光色温

5.一路 - 支持白光亮度

需求原型

  1. 开关 :点击中间按钮切换 switch_led 开关状态。
  2. 首页 :页面分为调光、开关、更多功能(断电记忆、停电勿扰、开关渐变等)面板根据产品所配置的功能点进行具体展示。
    如五路四路显示白光和彩光,一路二路只显示彩光,三路只显示彩光。
  3. 彩光调节: 第一个滑动条对应的是 hsv 模型中的 hue(色相),对应范围为0-360。
    第二个滑动条对应的是 hsv 模型中的 saturation(饱和度),对应范围为0-1000。
    第三个滑动条对应的是 hsv 模型中的 value(明度),对应范围为0-1000。
    滑动滑条时,通过 control_data 调节 dp 下发数据,该 dp 只下发不上报,需要通过节流控制每一条下发间隔300ms,松手时下发 colour_data彩光 dp。
  4. 彩光收藏颜色 :彩光收藏颜色最大个数为8个,默认为红、绿、蓝三个颜色不可删除。
    当有颜色为选中状态,且该颜色不是默认数据时,颜色中会出现删除按钮,再次点击选中颜色则将颜色删除。
    添加时往云端存储当前的 HSV 数值,当添加颜色重复时出现弹窗显示添加重复。
    选中状态会根据当前colour_data dp 去更新 index 值。
  5. 白光调节:第一个滑动条对应色温,通过 control_data 调节 dp 下发数据,该 dp 只下发不上报,松手时色温下发 temp_value ,亮度发 bright_valuedp。
  6. 白光收藏颜色:白光收藏颜色最大个数为8个,默认为亮度1000,色温 1000、500、0。
    当不支持色温时(一路灯、四路灯),默认配置为色温:1000,亮度分别为1000、500、0。
    三个默认颜色不可删除。当有颜色为选中状态,且该颜色不是默认数据时,颜色中会出现删除按钮,再次点击选中颜色则将颜色删除。
    添加时往云端存储当前的 HSV 数值,当添加颜色重复时出现弹窗显示添加重复。选中状态会根据当前bright_valuetemp_value dp 去更新 index 值。
  7. 默认灯光默认灯光组件即断电记忆。用于在产品断电重启之后,是否存储记忆上回用户设置的颜色、亮度色温、场景模式等。设置断定记忆需要维持当前设置5秒方可生效。
    面板具有以下三个功能选项:
    恢复记忆:恢复断电前的设备状态
    初始模式:使用产品默认配置的颜色和亮度
    用户定制:使用用户自定义的颜色和亮度
    点击用户定制跳转至颜色和亮度设置页,可实时预览灯光,选择完成后点击返回。用户定制小图标展示当前所选择的颜色。默认值为初始颜色和亮度,设置过之后根据设置的颜色、亮度显示。
  8. 停电勿扰:断电记忆功能用于设置电源通断电灯光开启的状态,支持对每次从电源开启的默认灯光状态进行设置。智能灯具在停电后再次来电时会自动亮灯,如果是在半夜来电,灯光亮起来时会惊扰到用户,影响睡眠。停电勿扰功能可以解决这个问题。
    功能演示
  9. 开关渐变:通过APP控制灯的开关,目前可以通过 IoT 平台配置开关过程是否渐变,但是渐变时间是固定的,不可调整。短时间内瞬间亮灯或灭灯,会使用户感觉灯光比较刺眼,影响用户使用感受。比如,在酒店场景下,客人进客房开灯的时候,开灯瞬间灯立刻亮起,灯光会特别刺眼。

功能汇总

当前照明模板必须的功能点:

switch_led,
work_mode,
bright_value,
temp_value,
colour_data,
control_data,

当前照明模板可选的功能点:

switch_gradient,
power_memory,
do_not_disturb,

开关

参数

取值

dpid

20

code

switch_led

type

布尔型(Bool)

mode

可下发可上报(rw)

工作模式

参数

取值

dpid

21

code

work_mode

type

枚举型(Enum)

mode

可下发可上报(rw)

property

枚举值: white, colour, scene, music

白光亮度

参数

取值

dpid

22

code

bright_value

type

数值型(Value)

mode

可下发可上报(rw)

property

数值范围: 10-1000, 间距: 1, 倍数: 0, 单位:

冷暖值

参数

取值

dpid

23

code

temp_value

type

数值型(Value)

mode

可下发可上报(rw)

property

数值范围: 0-1000, 间距: 1, 倍数: 0, 单位:

以上为raw类型以外功能点,raw 类型 dp 请查看具体协议。

首先需要创建一个照明类产品,定义产品有哪些功能点,然后面板中再根据这些功能点一一实现。

进入IoT 平台,点击左侧产品菜单,产品开发,创建产品,选择标准类目 -> 照明 -> 光源:

选择功能点,这里我们只需要默认的标准功能即可。

🎉 在这一步,我们创建了一个名为 PublicLamp 的照明光源产品。

开发者平台创建面板小程序

这部分我们在 小程序开发者 平台上进行操作,注册登录 小程序开发者平台

拉取并运行模板项目

面板模板仓库

拉取项目

git clone https://github.com/Tuya-Community/tuya-ray-demo.git

进入照明模板,安装依赖并启动项目

cd ./examples/panel-lamp

打开IDE并点击导入

选择下载的路径并导入

并关联到已经创建的面板小程序与产品。


绑定具体设备即可,IDE 会自动安装依赖并构建项目。

工程目录

上面的步骤我们已经初始化好了一个面板小程序的开发模板,下面我们介绍下工程目录。

├── src
│ ├── app.config.ts # 自动生成配置
│ ├── app.tsx # App 根组件
│ ├── components # 组件目录
│ ├── constant # 常量目录
│ ├── devices # 智能设备模型目录
│ │ ├── index.ts # 定义并导出智能设备模型
│ │ ├── protocols # 定义当前设备所需要的dp声明
│ │ ├─────parses # 定义当前设备所需要的dp复杂协议
│ │ └── schema.ts # 当前智能设备 DP 功能点描述,IDE 可自动生成
│ ├── global.config.ts
│ ├── hooks # 自定义 hooks 目录
│ ├── i18n # 多语言目录
│ ├── pages # 页面目录
│ └── routes.config.ts # 路由配置
│─── typings #业务类型定义目录
│ └── sdm.d.ts #智能设备类型定义文件

需求实现

1. IDE 生成 SDM schema到项目。

生成 SDM schema 至项目中,可以查看 src/devices/schema.ts

export const defaultSchema = [
  {
    attr: 641,
    canTrigger: true,
    code: "switch_led",
    defaultRecommend: true,
    editPermission: false,
    executable: true,
    extContent: "",
    iconname: "icon-dp_power",
    id: 20,
    mode: "rw",
    name: "开关",
    property: {
      type: "bool"
    },
    type: "obj"
  },
  {
    attr: 640,
    canTrigger: true,
    code: "work_mode",
    defaultRecommend: true,
    editPermission: false,
    executable: true,
    extContent: "",
    iconname: "icon-dp_mode",
    id: 21,
    mode: "rw",
    name: "模式",
    property: {
      range: ["white", "colour", "scene", "music"],
      type: "enum"
    },
    type: "obj"
  }
  // ...
];

2. 需求 -首页 调节滑动条下发彩光调色

分析需求:

  1. 首页添加组件 开关按钮
    1. 根据 switch_led 下发的值展示不同按钮图片。
    2. 点击 开关组件触发事件,上报「开」「关」的 dp 值。
  2. 确认开关 dp 为 switch_led,可作为属性传入组件。
  3. 使用 useProps 获取实时下发的 dp 值。示例:
    import React from "react";
    import { View } from "@ray-js/ray";
    import { useProps } from '@ray-js/panel-sdk';
    
    export default function () {
      const power = useProps(props => props.switch_led);
    	return (
         <View style={{ flex: 1 }}>
             <View>switch_led: {power}</View>
         </View>
    	);
    }
    
  4. 使用 useActions 下发 dp,如何通过sdm下发dp,请参考:sdm下发教程


示例:

import React from "react";
import { View } from "@ray-js/ray";
import { useActions } from '@ray-js/panel-sdk';
   
export default function () {
  const handleTogglePower = () => {
    actions.switch_led.toggle({ throttle: 300 });
  };
  return (
   	<View style={{ flex: 1 }}>
   		<Button img={power} onClick={handleTogglePower} />
   	</View>
  	);
	}

根据上述分析,我们来实现开关组件。

import React from "react";
import res from "@/res";
import { View, Image } from "@ray-js/ray";
import dpCodes from "@/config/dpCodes";
import { Button } from "@/components";
import { useActions, useProps } from '@ray-js/panel-sdk';
import styles from "./index.module.less";


export const PowerButton = () => {
  const power = useProps(props => props.switch_led);
	const actions = useActions();
  const handleTogglePower = () => {
    actions.switch_led.toggle({ throttle: 300 });
  };
	return (
		<View className={styles.container}>
			<Image className={styles.bg} mode="aspectFill" src={bottom_dark} />
			<Button
				img={power}
				onClick={handleTogglePower}
				imgClassName={styles.powerBtn}
				className={styles.powerBox}
			/>
		</View>
	);
};

开关组件实现

  1. 首页 首页根据是否有彩光、白光 dp 显示对应 tab 页。
  2. 调光功能,彩光主要以 HSV 模型,将 hue(色调)、saturation(饱和度)、value(明度)三个数据整合成 colour_data 彩光 dp 进行下发。白光以色温、亮度分别用temp_valuebright_value dp 进行数值下发。
  3. 收藏颜色时,将记忆的颜色数值存储到云端,后续进面板时主动拉取云端数据,方便操作。
  4. 对于简单协议转换,可以直接在 src/devices/protocols/index中声明,如下:
    import dpParser from './parsers';
    import dpCodes from '@/config/dpCodes';
    const { colourCode } = dpCodes;
    
    export const protocols = {
      [colourCode]: [
         {
          name: 'hue' as const,
          bytes: 2,
          default: 0,
          defaultValue: 0,
        },
        {
          name: 'saturation' as const,
          bytes: 2,
          defaultValue: 1,
        },
        {
          name: 'value' as const,
          bytes: 2,
          defaultValue: 1,
        },
      ],
    };
    
    
  5. 对于高级协议转换,可将对应协议规则放到 src/devices/protocols 中,再到 src/devices/index 中进行引用,如下:
    //彩光dp转换协议 dpParses/ColorFormatter.ts文件
    import { transform } from "./transform";
    
    export default class ColourFormatter {
    	uuid: string;
    	defaultValue: {
         hue: number,
         saturation: number,
         value: number,
    	};
    
    	constructor(uuid = "colour_data_raw", defaultValue = null) {
         this.defaultValue = {
             hue: 0,
             saturation: 1000,
             value: 1000,
         };
         this.uuid = uuid;
         if (defaultValue) {
             this.defaultValue = defaultValue;
         }
    	}
    
    	equal(source, target) {
         return source === target;
    	}
    
    	parser(val = "") {
         // 自定义解析
         const { length } = val;
         if (!length) {
             console.log("数据有问题,无法解析");
             return this.defaultValue;
         }
         const generator = transform(val);
         generator.next();
         // 版本
         const hue = parseInt(generator.next(4).value, 16);
         const saturation = parseInt(generator.next(2).value, 16) * 10;
         const value = parseInt(generator.next(2).value, 16) * 10;
         return {
             hue,
             saturation,
             value,
         };
    	}
    
    	to16(value, length) {
         let result = Number(value).toString(16);
         if (result.length < length) {
             result = result.padStart(length, "0");
         }
         return result;
    	}
    
    	formatter(data) {
         // 自定义格式转为16进制
         const { h = 0, s = 1000, v = 1000 } = data;
         const hStr = this.to16(Math.floor(h), 4);
         const sStr = this.to16(Math.floor(s / 10), 2);
         const vStr = this.to16(Math.floor(v / 10), 2);
         return `${hStr}${sStr}${vStr}`;
    	}
    }
    
    dpParsers/index.ts 集中导出处理
    import ColorFormatter from "./ColorFormatter.ts";
    
    export const colorTransformer = new ColorFormatter();
    
    export default {
    	colorTransformer,
    };
    
    再放入 dpMaps 中引用
    import dpCodes from "@/config/dpCodes";
    import dpParser from "./dpParsers";
    
    export default {
    	colour_data: dpParser.colorTransformer,
    };
    

其它

复杂协议声明

HSV 模型

调光组件实现
sdm使用文档