This document is intended for developers who understand panel miniapp development, panel miniapp, and product functions. If you have questions about the above knowledge, learn the following prerequisite knowledge. Skip this document if you have mastered the mentioned knowledge.
A panel is a product that represents an IoT smart device in the Tuya Smart Life app. Before you create a product, you shall understand the definition of the panel and the relationship among the panel, product, and device.
The strip lights template is developed based on SDM (Smart Device Model)
. For more information about SDM
, see Smart Device Model.
Product name: Dreamcolor strip lights (RGB)
The strip lights support cool white light (C), cool and warm light (CW), colored light (RGB), cool white and colored light (RGBC), and white and colored light (RGBCW) modes based on the data points of the white light mode and multicolor light mode.
Definition:
colour_data
, white light brightness bright_value
, and white light color temperature temp_value
.switch_led
by tapping on the on/off button.paint_colour_data
data point to change the color of the above strip lights.paint_colour_data
data point to change the color of the selected strip lights. Note that the strip lights are selected only when they are highlighted. Users can select the strip lights by sliding.paint_colour_data
data point to change the color of the above strip lights.paint_colour_data
data point to change the color of the selected strip lights. Users can select the strip lights by sliding.dreamlight_scene_mode
data point is sent, and the strip lights change accordingly.dreamlightmic_music_data
data point.countdown
data point in seconds.The strip lights template must have the following data points.
switch_led,
work_mode,
paint_colour_data,
dreamlight_scene_mode,
dreamlightmic_music_data,
light_pixel,
lightpixel_number_set,
The strip lights template has the following available data points that can be selected.
countdown,
light_length,
Parameter | Value |
dpid | 20 |
code | switch_led |
type | Boolean |
mode | Send and report (read-write) |
Parameter | Value |
dpid | 21 |
code | work_mode |
type | Enum |
mode | Send and report (read-write) |
property | Enum values: white, colour, scene, music |
Parameter | Value |
dpid | 26 |
code | countdown |
type | value |
mode | Send and report (read-write) |
property | { "unit": "s", "min": 0, "max": 86400, "scale": 0, "step": 1, "type": "value"} |
Parameter | Value |
dpid | 46 |
code | light_length |
type | value |
mode | Report only (read-only) |
property | {"unit": "cm", "min": 1, "max": 10000, "scale": 0, "step": 1, "type": "value" } |
Parameter | Value |
dpid | 47 |
code | light_pixel |
type | value |
mode | Report only (read-only) |
property | { "min": 1, "max": 1024, "scale": 0, "step": 1, "type": "value" } |
Parameter | Value |
dpid | 53 |
code | lightpixel_number_set |
type | value |
mode | Send and report (read-write) |
property | { "min": 1, "max": 1000, "scale": 0, "step": 1, "type": "value" } |
The data points with a data type other than Raw
are listed above. For data points with the data type of Raw
, see the specific protocol.
Create dreamcolor strip lights, define data points, and configure the data points in the panel.
Log in to Tuya Developer Platform and create dreamcolor strip lights.
Note: This template only supports dreamcolor RGB strip lights. For other modes, configure the product according to the logic.
π By now, dreamcolor strip lights named PublicLamp are created.
Log in to Tuya MiniApp Developer Platform to create a panel miniapp.
For more information, see Panel MiniApp > Create panel miniapp.
Panel template repository: Repositories.
git clone https://github.com/Tuya-Community/tuya-ray-materials.git
cd ./template/PublicPanelStripLamp
By now, the initialization of the development of a panel miniapp is completed. The introduction to the project directory is as follows.
βββ public
β βββ images # Static resources of the project.
βββ src
β βββ app.config.ts # Configurations generated automatically.
β βββ app.tsx # App root component.
β βββ components # Component directory.
β βββ constant # Constant directory.
β βββ containers # Aggregated component directory.
β βββ devices # Smart device model directory.
β β βββ index.ts # Smart device model definition and export.
β β βββ protocols # Declaration definition of the DP required by the device.
β β ββββββparses # Complex protocol definition of the DP required by the device.
β β βββ schema.ts # Description of the DP of the current smart device, which can be generated automatically by IDE.
β βββ global.config.ts
β βββ hooks # Custom hooks directory.
β βββ i18n # Multilingual directory.
β βββ pages # Page directory.
β βββ routes.config.ts # Route configuration.
ββββ typings # Definition directory of business types.
β βββ sdm.d.ts # Definition files of smart device type.
Generate SDM schema
for the project by referring to 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: "Switch",
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: "Mode",
property: {
range: ["white", "colour", "scene", "music"],
type: "enum",
},
type: "obj",
},
// ...
] as const;
switch_led
.switch_led
. If yes, pass this attribute to the component.useProps
to get the value of the data point sent in real time.Example: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
to send the data point. For more information about how to use SDM to send data points, see useActions.Example:
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>
);
}
Implement the on/off button component based on the analysis. For more information, see ControlBar.
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>
);
};
paint_colour_data
. You can view the data point protocol for project creation on Tuya Developer Platform and see parsers for data point sending.paint_colour_data
data point stores only the latest light data.dreamlight_scene_mode
data point.dreamlightmic_music_data
data point.src/devices/protocols/index
.import dpParser from './parsers';
import { lampSchemaMap } from '@/devices/schema';
const { colour_data } = lampSchemaMap;
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,
},
],
};
src/devices/protocols/parsers
and reference them in src/devices/index
. import _ from 'lodash-es';
import { utils } from '@ray-js/panel-sdk';
import { lampSchemaMap } from '@/devices/schema';
import { log } from '@/utils';
/** Dimmer mode */
export enum DimmerMode {
white,
colour,
colourCard,
combination,
}
export type DimmerTab = keyof typeof DimmerMode;
/** Sliding type */
enum SmearMode {
all,
single,
clear,
}
interface ColourData {
hue: number;
saturation: number;
value: number;
}
/** Value type of dimmer */
export interface DimmerValue {
colour?: ColourData;
colourCard?: ColourData;
combination?: ColourData[];
}
/** Sliding DP data */
export interface SmearDataType {
/** Version number */
version: number;
/** Mode (0: white light mode, 1: multicolor light mode, 2: color picker, 3: combination) */
dimmerMode: DimmerMode;
/** Sliding effect (0: none, 1: gradient) */
effect?: number;
/** Number of strip lights UI segments */
ledNumber?: number;
/** Sliding action (0: paint bucket, 1: sliding, 2: eraser) */
smearMode?: SmearMode;
/** Multicolor light hue */
hue?: number;
/** Multicolor light saturation */
saturation?: number;
/** Multicolor light value */
value?: number;
/** White light brightness */
brightness?: number;
/** White light color temperature */
temperature?: number;
/** Multicolor light mode applied */
isColour?: boolean;
/** Selection type (0: contiguous, 1: single) */
singleType?: number;
/** Number of controlled strip lights */
quantity?: number;
/** Combination type */
combineType?: number;
/** Color combination */
combination?: ColourData[];
/** No. */
indexs?: Set<number>;
}
const { toFixed, generateDpStrStep, numToHexString } = utils;
const { paint_colour_data } = lampSchemaMap;
function nToHS(value = 0, num = 2) {
return numToHexString(value || 0, num);
}
/** Conversion to number */
function toN(n: any) {
return +n || 0;
}
function avgSplit(str = '', num = 1) {
const reg = new RegExp(`. {1,${num}}`, 'g');
return str.match(reg) || [];
}
function sToN(str = '', base = 16) {
return parseInt(str, base) || 0;
}
const _defaultValue = {
version: 0,
dimmerMode: DimmerMode.colour,
effect: 0,
smearMode: SmearMode.all,
hue: 0,
saturation: 1000,
value: 1000,
brightness: 1000,
temperature: 1000,
ledNumber: 20,
} as SmearDataType;
export default class SmearFormater {
uuid: string;
defaultValue: SmearDataType;
constructor(uuid = paint_colour_data.code, defaultValue = _defaultValue) {
this.uuid = uuid;
this.defaultValue = defaultValue;
}
parser(val = ''): SmearDataType {
const validVal = (val || '').slice(0, 1) === '0';
if (!val || typeof val !== 'string' || !validVal) {
console.warn(paint_colour_data.code, 'Parsing failed because DP data error occurred.', val);
return this.defaultValue;
}
const step = generateDpStrStep(val);
const version = toN(step(2).value); // Version number
const dimmerMode = toN(step(2).value); // Dimmer mode
const effect = toN(step(2).value); // Dimming effect
const ledNumber = toN(step(2).value); // Number of segments
const result = {
version,
dimmerMode,
effect,
ledNumber,
} as SmearDataType;
if (dimmerMode === DimmerMode.white) {
// White light
result.smearMode = toN(step(2).value); // Dimming action
result.brightness = toN(step(4).value); // B (Brightness)
result.temperature = toN(step(4).value); // T (Color temperature)
} else if ([DimmerMode.colour, DimmerMode.colourCard].includes(dimmerMode)) {
// Multicolor light/color picker
const smearMode = toN(step(2).value); // Dimming action
result.smearMode = smearMode;
result.hue = toN(step(4).value); // H
result.saturation = toN(step(4).value); // S
result.value = toN(step(4).value); // V
if ([SmearMode.clear, SmearMode.single].includes(smearMode)) {
const singleDataStr = toFixed(toN(step(2).value).toString(2), 8); // Segment selection
const singleType = sToN(singleDataStr.slice(0, 1), 2);
result.singleType = singleType; // Segment selection (0: contiguous, 1: non-contiguous)
result.quantity = sToN(singleDataStr.slice(1), 2); // Segment selection (contiguous: no meaning, non-contiguous: number of segments)
let indexItem = step(2);
const indexs = new Set<number>();
while (!indexItem.done) {
indexs.add(toN(indexItem.value - 1));
indexItem = step(2);
}
if (indexItem.done) {
indexItem.value ! == null && indexs.add(toN(indexItem.value - 1));
}
result.indexs = indexs;
}
} else if (dimmerMode === DimmerMode.combination) {
result.smearMode = SmearMode.all;
// Combination of colors
result.combineType = toN(step(2).value);
const getHSV = () => {
const hue = toN(step(4).value);
const saturation = toN(step(4).value);
const lastItem = step(4);
const value = toN(lastItem.value);
return { hue, saturation, value, done: lastItem.done };
};
result.combination = [];
let _done = false;
while (! _done) {
const { hue, saturation, value, done } = getHSV();
const res = { hue, saturation, value };
result.combination.push(res);
_done = done;
}
}
log(result, 'SmearDataType result');
return result;
}
formatter(_data: SmearDataType): string {
const data = {
...this.defaultValue,
..._data,
};
const {
version = 0,
dimmerMode = DimmerMode.colour,
effect = 0,
ledNumber = 20,
smearMode = SmearMode.all,
hue = 0,
saturation = 0,
value = 0,
brightness = 0,
temperature = 0,
combineType = 0,
combination = [],
} = data;
// White light mode does not support gradient effect.
const _effect = nToHS(dimmerMode === DimmerMode.white ? 0 : effect);
let result = `${nToHS(version)}${nToHS(dimmerMode)}${_effect}${nToHS(ledNumber)}`;
if (dimmerMode === DimmerMode.white) {
// White light
result += `${nToHS(smearMode)}${nToHS(brightness, 4)}${nToHS(temperature, 4)}`;
} else if ([DimmerMode.colour, DimmerMode.colourCard].includes(dimmerMode)) {
// Multicolor light/color picker
const isClear = smearMode === SmearMode.clear;
if (isClear) {
// 0 is sent if the eraser is tapped to turn off all strip lights.
const _hue = 0;
const _saturation = 0;
const _value = 0;
result += `${nToHS(smearMode)}${nToHS(_hue, 4)}${nToHS(_saturation, 4)}${nToHS(_value, 4)}`;
} else {
result += `${nToHS(smearMode)}${nToHS(hue, 4)}${nToHS(saturation, 4)}${nToHS(value, 4)}`;
}
if ([SmearMode.single, SmearMode.clear].includes(smearMode)) {
const { singleType = 1, indexs = new Set() } = data;
const quantity = indexs.size;
const singleDataStr = `${nToHS(
parseInt(`${singleType}${toFixed(quantity.toString(2), 7)}`, 2)
)}`;
const indexsStr = `${[...indexs].reduce((acc, cur) => acc + nToHS(cur + 1), '')}`;
result += `${singleDataStr}${indexsStr}`;
}
} else if (dimmerMode === DimmerMode.combination) {
// Combination
const colors = combination.map(
item => `${nToHS(item.hue, 4)}${nToHS(item.saturation, 4)}${nToHS(item.value, 4)}`
);
result += `${nToHS(combineType)}${colors.join('')}`;
}
log(new SmearFormater().parser(result), 'SmearDataType formatter');
return result;
}
}
src/devices/protocols/parsers/index.ts
is used for batch export.import dpParser from "./parsers";
import { lampSchemaMap } from "@/devices/schema";
const { dreamlightmic_music_data, paint_colour_data } = lampSchemaMap;
export const protocols = {
[dreamlightmic_music_data.code]: dpParser.micMusicTransformer,
[paint_colour_data.code]: dpParser.smearFormatter,
};