Panel
is the product form of IoT smart devices on the App terminal. Before creating a product
, let's first understand what a panel
is and the relationship between product
and device
.
Panel
is an interface interaction program running on Smart Life App
, OEM App (Tuya Custom App)
, It is used to control the operation of smart equipment
and display the real-time status of smart equipment
.Product
links the panel
with the smart device
, the product describes the functions it has, the name displayed on the panel on the app, the function points that the smart device has, etc.smart device
is a device equipped with a Tuya Smart Module
. Usually, a QR code
will be pasted on the device. Use the Smart Life App
to scan the QR code, and then you can get the QR code in the App. Install the control panel for the device.The following diagram depicts the relationship between products, panels and devices
Since the product defines the function points possessed by the panel and the device, before developing a smart device panel, we first need to create a product, define which function points the product has, and then implement the panel one by one according to these function points.
In this part, we operate on the IoT platform, register and log in to the IoT platform:
After creating the product, go to the Function Definition
page, which lists the optional standard function points under the air conditioner category. Here we click Select All
, and click OK to complete the initial function point setting of the product:
Now that we have a product, and the function points have been set, we will enter the development process of the panel Miniapp. Go to register and log in to Tuya Mini Program Developer Platform to create our Mini Program project.
Click New, enter the Miniapp name "Universal Panel", select `Panel Miniapp' for the Miniapp type, select Public Edition for the panel type, and click OK to complete the creation:
Initialize the code project with the following command: Ensure node version >= v16.7.0, npm version >= v7.20.3
yarn create @ray-js/app my-ray-app
Select App panel to develop Ray application (jotai)
, generate Ray
panel project (Ray
is similar to Taro
, it is a multi-end R&D framework, write a set of code to compile to multi-end)
Open the project, modify the project.tuya.json
file in the root directory, add the productId
field, and fill in the product pId
created on the IoT
platform.
project.tuya.json
file fill in productId
:
{
"projectname": "device-panel",
"i18n": false,
"description": "Item description",
"baseversion": "2.2.3",
"miniprogramRoot": "./dist/tuya",
"productId": "lm5ixpr9lbkv4xed",
"dependencies": {
"BaseKit": "2.1.2",
"MiniKit": "2.3.3",
"TYKit": "2.0.7",
"DeviceKit": "2.1.6"
}
}
After the native code is created, execute the following command to start the project:
yarn && yarn start:tuya
After execution, it will monitor the changes of local code files and compile them in real time. At the same time, a dist
directory will be generated in the directory, that is, the compilation product of the Miniapp. To run, you need to install the Tuya Miniapp IDE tool
.
Install and open the Mini Program IDE tool (Go to Download Mini Program IDE)
After opening, use the Smart Life App to scan the code to authorize the IDE:
Select Import, Directory Select the newly generated project directory, and Associate Smart Mini Program select the project name created on the platform.
If there is an error like Error(MiniKit does not have the specified version 2.3.3)
, click Environment Configuration -> Kit Management, and select the recommended version:
The panel Miniapp development mainly revolves around the IoT platform, the Miniapp development platform, and the Miniapp IDE. A picture is used to summarize the overall process:
The above completes the creation process of a panel applet, and the following is the development tutorial of the actual code.
Writing code is mainly done in the pages folder, create your page code, and then configure the routing address to the src/routes.config.ts
file:
The description of other files will be mentioned in the following development steps.
Click the Open in vscode
button in the IDE to open the vscode editor, modify the src/pages/home/index.tsx
file, clear the contents, and enter the following code:
import React from "react";
import { useDevInfo } from "@ray-js/panel-sdk";
import { useAtomValue } from "jotai";
import { selectDpStateAtom } from "@/atoms";
import { View } from "@ray-js/components";
export default () => {
// When the project starts, the product information corresponding to the IoT platform productId will be automatically pulled
const devInfo = useDevInfo() || {};
// read dpState data from jotai
const dpState = useAtomValue(selectDpStateAtom) || {};
console.log("devInfo", devInfo); // print and view devInfo content
console.log("dpState", dpState); // print and view the content of dpState
return <View>hello world</View>;
};
By default, you can see a lot of output in the console
panel of the IDE
debugger. There is a lot of data built into the jotai state, the most important of which is devInfo
, which is the device information (Device Information
)
Need to understand several important properties in devInfo
,
codeIds
: an object whose key is dpCode
and value is dpId
devId
: The id
of the device, the virtual device will start with vdevo
deviceOnline
: whether the device is onlinedps
: an object, key
is dpId
, value is the state of DP
idCodes
: an object, as opposed to codeIds
panelConfig
: panel configuration, where bic
is cloud function configurationproductId
: The product bound to the current deviceschema
: The function point definition of the product, which describes the code
, type, value range properties, icon
icon of DP
state
: an object, key
is dpCode
, value is the state of DP
ui
: the uiId
of the panelUsually, the devInfo obtained in the code is read through the jotai state management
API. (DP
in this article is function point
, from the IoT platform function definition).
The function definition
of the product can be obtained in devInfo
:
const schema = devInfo.schema; // function point definition
const state = devInfo.state; // device function point state
Traversing the schema
can get all the DP
function points and attributes of the product:
schema.map((item) => {
// item.code
// item.property
// ...
return <Text>{item.name}</Text>;
});
Here, according to item.property.type
, you can judge and render the UI
display of different types of DP
function points
The Ray development style uses the less language, supports css module, and creates a new file index.module.less
. Write the content such as:
src/pages/home/index.module.less
.container {
border-radius: 24rpx;
background-color: #fff;
margin-bottom: 24rpx;
}
The rpx
unit is a unique unit provided by the Ray framework, which can be adapted to different devices.
Use styles in code:
import React from "react";
import { useDevInfo } from "@ray-js/panel-sdk";
import { View } from "@ray-js/components";
import styles from "./index.module.less"; // Note how styles are imported
export default () => {
const devInfo = useDevInfo() || {};
// add className
return <View className={styles.container}>hello world</View>;
};
DP
, namely Boolean, numeric, enumeration, fault, character, and transparent transmission.DP
has a different value type and range, and the UI view needs to be rendered according to the type and range when the panel is rendered.For details, please refer to the document Custom Functions
src/home/index.tsx
Write the following code:
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) => {
// Traverse and render each function point
return (
<View key={dpCode}>
{dpCode}: {dpState[dpCode]}
</View>
);
})}
</View>
);
};
Recompile, you can see that the code
of all DP
and the corresponding device status are displayed in the left panel of IDE
In the IDE console panel, you can see the data structure of function points of type bool
, for example:
{
dptype: "obj";
id: "39";
type: "bool";
}
To render using the switch component TySwitch
provided by the toolkit @ray-js/components-ty
, execute the following command to install the extended component library provided by Ray:
yarn add @ray-js/components-ty
Go ahead and write the code and enter the following:
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];
// Judging if it is a bool type, return TySwitch
if (props.type === "bool") {
return (
<View>
{dpCode}: <TySwitch checked={dpState[dpCode]} />
</View>
);
}
return (
<View key={dpCode}>
{dpCode}: {dpState[dpCode]}
</View>
);
})}
</View>
);
};
After compiling, the rendering interface in the IDE is as follows, and you can see that all bool DPs have successfully rendered the switch component
When the DP function is defined, there are corresponding multilingual texts in the product information. Here, use the Strings tool under src/i18n
to obtain the DP
text, and enter the following code:
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") {
// Use Strings.getDpLang method to get multiple languages
return (
<View>
{Strings.getDpLang(dpCode)}:{" "}
<TySwitch checked={dpState[dpCode]} />
</View>
);
}
return (
<View key={dpCode}>
{dpCode}: {dpState[dpCode]}
</View>
);
})}
</View>
);
};
Compile again to view the IDE rendering results, you can see that all bool type DP texts have been displayed in Chinese
This completes the UI rendering of the Bool
type DP
function point.
The above describes how to write code to render DP points, but to control the operation of the device, it is also necessary to perform DP delivery
, using the ray
framework API
delivery capability, for example:
import { publishDps } from "@ray-js/api";
// Sent to the device corresponding to deviceId, the key in dps is dpId, and the value is dpValue. (For the description of dps and dpId, please refer to the `Writing Code` section)
publishDps({
deviceId: devInfo.devId,
dps: {
1: true,
},
});
Now let's take an example of the DP release
operation of the switch function point, and write the code:
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];
// Judging if it is a bool type, return 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>
);
};
The schema
property of the enum
type DP
point is configured as follows:
{
dptype: "obj";
id: "20";
range: ["cancel", "1h", "2h", "3h", "4h", "5h", "6h"];
type: "enum";
}
The enumeration items in the range are rendered using the ActionSheet popup component
+ Cell list component
in @ray-js/components-ty
, write the following code:
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];
// For the sake of article length, other types are ignored here. The local code can copy the bool type of the previous section to here
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>
);
};
Click the button to trigger the ActionSheet popup window
, and use the Cell list component
to render multiple enumeration items in the popup window.
The schema
property of the string
type DP
point is configured as follows:
{
dptype: "obj";
id: "20";
type: "string";
}
Characters can be rendered using the Input
component, implemented as follows:
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];
// For the sake of article length, other types are ignored here. The local code can be copied to the type of the previous section here
if (props.type === "string") {
return <Input value={dpState[dpCode]} />;
}
return (
<View key={dpCode}>
{dpCode}: {dpState[dpCode]}
</View>
);
})}
</View>
);
};
Raw
type DP
generally does not render, if necessary, it can be processed according to String
type
The schema
attribute of the value
type DP
point is configured as follows:
{
dptype: "obj";
id: "18";
max: 100;
min: 0;
scale: 0;
step: 1;
type: "value";
unit: "%";
}
It defines the numerical range and units, which are rendered using the Slider
component:
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];
// For the sake of article length, other types are ignored here. The local code can be copied to the type of the previous section here
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>
);
};
The Slider
component is a slider component, which can be constrained according to the value range.
The bitmap
type is generally used for fault reporting. The properties are configured as follows:
{
dptype: "obj";
id: "22";
label: ["sensor_fault", "temp_fault"];
maxlen: 2;
type: "bitmap";
}
Failure-type DP
uses popup rendering, which is implemented here using Notification
provided by @ray-js/ray-components-plus
:
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 || {};
// The popup message is done in 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];
// For the sake of article length, other types are ignored here. The local code can be copied to the type of the previous section here
return (
<View key={dpCode}>
{dpCode}: {dpState[dpCode]}
</View>
);
})}
</View>
);
};
The bitmap
type is usually used for fault reporting and pop-up message prompts, so you need to traverse the schema in useEffect to find the bitmap type and pop-up reminders.
The above schema is the standard DP point that comes with the home appliance category. We can also add a custom DP, open the IoT platform product details -> function definition -> custom function
column,
Click Add function to create a new bool DP:
-->
When the panel is running, it needs to obtain device information
to display device running status
. In the development stage, if there is no real smart hardware device
, we can use virtual device
to assist debugging, which can achieve the same performance as the real device. Effect. The virtual device and the real device can be equivalent to the panel Miniapp, and the relationship is shown in the following figure:
After logging in, enter the Universal Panel
project, click the debugging tool Virtual Device
, and use the Smart Life App
to scan the code to create a virtual device.
The virtual device panel is divided into 3 blocks, the left panel is the bottom panel used to display the DP
function points of the current product, and the right side is the virtual device DP
control list, you can change the DP
state and then click the report button, below Is the Log
panel for outputting MQTT
logs. Click "Visualization Panel" to switch to the bottom panel view, which has the same function as the control panel, and displays the product function points more intuitively.
Let's debug the code we just wrote.
On the right side of the virtual device interface -> device information, click the copy button to the right of deviceId
Click Compile parameter settings, and fill in the real machine debugging parameters according to the format. For example: deviceId=vdevo165398364416684
After setting the compilation parameters, click the Real Machine Debug
button (the real machine needs to go to Download Smart Life App)
Use the Smart Life App
to scan the QR code, open and enter the Miniapp panel, you can debug the universal panel on the real machine.
By now, you are familiar with the rendering and distribution of 6 DP
points. Attach the open source address of the universal panel code:
The universal panel display after style optimization.
After debugging in IDE
, click the upload source button, enter the version number and description, and click upload preview package
After the upload is complete, you can see the list of uploaded versions in the version management of the Mini Program developer platform
After uploading the source code package, in Mini Program Developer Platform -> Version Management, select the version that needs to be pre-released and set it as the experience version
Click on the whitelist page to add your own Smart Life App
account
After adding, you can click Version Management -> Experience QR Code, check the Miniapp code, and use the Smart Life App to scan the code to preview the Miniapp.
The panel Miniapp of an unofficial subject needs to improve the IoT platform display information before submitting it for review. Please refer to Online Review for the rest of the online review items.