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.

1.1 What is the panel?

  1. 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.
  2. 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.
  3. A 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.

1.2 Product, Panel, Device Relationship

The following diagram depicts the relationship between products, panels and devices

1.3 Create a product

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:

  1. Click on the left product menu, product development, create a product, take major appliances as an example, select the standard category, select major appliances -> air conditioner:

  1. Fill in the product name, enter "Universal Panel", and leave the rest of the options as default. Click the Create Product button to complete the product creation.

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:

4.1 create command

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

4.2 Select App Panel Type

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)

4.3 IoT Platform View Product ID

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.

4.4 Code configuration associated product ID

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"
  }
}

5.1 Start the project

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.

5.2 Install Tuya Mini Program IDE

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:

5.3 IDE import project

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.

7.1 Page routing configuration

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.

8.1 Using jotai state management to read device data

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>;
};

8.2 devInfo field description

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,

  1. codeIds: an object whose key is dpCode and value is dpId
  2. devId: The id of the device, the virtual device will start with vdevo
  3. deviceOnline: whether the device is online
  4. dps: an object, key is dpId, value is the state of DP
  5. idCodes: an object, as opposed to codeIds
  6. panelConfig: panel configuration, where bic is cloud function configuration
  7. productId: The product bound to the current device
  8. schema: The function point definition of the product, which describes the code, type, value range properties, icon icon of DP
  9. state: an object, key is dpCode, value is the state of DP
  10. ui: the uiId of the panel

Usually, 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).

8.3 Get product function definition (schema)

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

9.1 Using rpx units

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.

9.2 Adding styles using css module

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>;
};

10.1 Function Definition Schema Structure

For details, please refer to the document Custom Functions

10.2 Rendering Schema data display

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

11.1 bool function point data structure

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";
}

11.2 Install the extension library

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

11.3 Using the switch component TySwitch

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

11.4 Multilingual acquisition of function points

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.

12.1 Using the API of the 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,
  },
});

12.2 Issue Bool switch function point

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>
  );
};

13.1 enum function point data structure

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";
}

13.2 Using the popup component TyActionsheet and the list component TyCell

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.

14.1 string function point data structure

The schema property of the string type DP point is configured as follows:

{
  dptype: "obj";
  id: "20";
  type: "string";
}

14.2 Using the input component Input

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

15.1 value function point data structure

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: "%";
}

15.2 Using the Slider Component Slider

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.

16.1 bitmap function point data structure

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";
}

16.2 Using the notification component Notification

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.

17.1 Adding custom function points to IoT platform

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,

17.2 Custom function point attribute configuration

Click Add function to create a new bool DP:

-->

18.1 What problems do virtual appliances solve?

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:

18.3 App scan code to create virtual device

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.

18.4 Operation Guide

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.

18.5 Debugging the code we wrote!

Let's debug the code we just wrote.

19.1 Get virtual device ID

On the right side of the virtual device interface -> device information, click the copy button to the right of deviceId

19.2 Set real machine debugging parameters

Click Compile parameter settings, and fill in the real machine debugging parameters according to the format. For example: deviceId=vdevo165398364416684

19.3 App scan code to open the debugger

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:

panel-universal

The universal panel display after style optimization.

21.1 Upload source package

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

21.2 Add preview package whitelist

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.

21.3 Approval goes live

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.