本文档面向已经了解 面板小程序开发
的开发者,你需要充分的了解什么是面板小程序
产品功能
若您对上述概念有所疑问,我们推荐你去看一些预备知识。如果您已了解,可跳过本文。
面板
是运行在 智能生活 App
、OEM App(涂鸦定制 App)
上的界面交互程序,用于控制 智能设备
的运行,展示 智能设备
实时状态。产品
将 面板
与 智能设备
联系起来,产品描述了其具备的功能、在 App 上面板显示的名称、智能设备拥有的功能点等。智能设备
是搭载了 涂鸦智能模组
的设备,通常在设备上都会贴有一张二维码,使用 智能生活 App
扫描二维码,即可在 App 中获取并安装该设备的控制 面板
。产品
、面板
、设备
之间的关系可参考下图。照明模板使用 SDM(Smart Device Model)
开发,关于 SDM
相关可以 查看 SDM 文档
产品名称:照明光源五路灯
根据支持的白光、彩光 dp 不同,照明中灯分为一路至五路。
其含义为:
1.五路
- 支持彩光 colour_data
、白光亮度 bright_value
、白光色温 temp_value
2.四路
- 支持彩光、白光亮度
3.三路
- 支持彩光
4.二路
- 支持白光亮度、白光色温
5.一路
- 支持白光亮度
开关
:点击中间按钮切换 switch_led
开关状态。首页
:页面分为调光、开关、更多功能(断电记忆、停电勿扰、开关渐变等)面板根据产品所配置的功能点进行具体展示。彩光调节
: 第一个滑动条对应的是 hsv 模型中的 hue(色相),对应范围为0-360。control_data
调节 dp 下发数据,该 dp 只下发不上报,需要通过节流控制每一条下发间隔300ms,松手时下发 colour_data
彩光 dp。 彩光收藏颜色
:彩光收藏颜色最大个数为8个,默认为红、绿、蓝三个颜色不可删除。colour_data
dp 去更新 index 值。 白光调节
:第一个滑动条对应色温,通过 control_data
调节 dp 下发数据,该 dp 只下发不上报,松手时色温下发 temp_value
,亮度发 bright_value
dp。 白光收藏颜色
:白光收藏颜色最大个数为8个,默认为亮度1000,色温 1000、500、0。bright_value
和temp_value
dp 去更新 index 值。默认灯光
:默认灯光组件即断电记忆。用于在产品断电重启之后,是否存储记忆上回用户设置的颜色、亮度色温、场景模式等。设置断定记忆需要维持当前设置5秒方可生效。停电勿扰
:断电记忆功能用于设置电源通断电灯光开启的状态,支持对每次从电源开启的默认灯光状态进行设置。智能灯具在停电后再次来电时会自动亮灯,如果是在半夜来电,灯光亮起来时会惊扰到用户,影响睡眠。停电勿扰功能可以解决这个问题。开关渐变
:通过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 #智能设备类型定义文件
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"
}
// ...
];
首页
调节滑动条下发彩光调色分析需求:
开关按钮
。switch_led
下发的值展示不同按钮图片。开关
组件触发事件,上报「开」「关」的 dp 值。switch_led
,可作为属性传入组件。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>
);
}
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>
);
};
首页
首页根据是否有彩光、白光 dp 显示对应 tab 页。colour_data
彩光 dp 进行下发。白光以色温、亮度分别用temp_value
和 bright_value
dp 进行数值下发。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,
},
],
};
//彩光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,
};