Prerequisites

Development Environment

See Panel MiniApp > Setting up the Environment.

Product Name: Gyro Sweeper Robot

Requirement Prototype

Since the product defines the function points owned by the panel and device, before developing a smart device panel, we first need to create a Gyro Sweeper Robot product, define which function points the product has, and then implement these function points in the panel.

First, register and log in to the Tuya Developer Platform, and create a product on the platform:

  1. Click Products > Product Development on the left side of the page, and click Create Product on the Product Development page.
  2. Under Standard Category, select Small Home Appliance, and for product category, select Sweeper Robot.
  3. Choose the intelligent method, select Gyro Sweeper for Product Solution, and complete the product information, such as filling in Product Name as Robot.

  1. Click Create Product to complete the product creation.
  2. After the product is created, you will enter the Add Standard Features page. Add features according to your needs, and then click Confirm.

🎉 After completing the above steps, a sweeper robot product named Robot has been created.

Create Panel MiniApp on the Developer Platform

Panel MiniApp development is operated on the MiniApp Developer platform. First, please go to the MiniApp Developer Platform to complete the platform registration and login.

For detailed operation steps, please refer to Create Panel MiniApp.

Create Project Based on Template in IDE

Open the IDE to create a panel MiniApp project based on the Sweeper Robot MiniApp Template, which needs to be operated on the Tuya MiniApp IDE.

For detailed operation steps, please refer to Initialize Project.

After completing the above steps, a panel MiniApp development template has been initialized. Here is an introduction to the project directory:

├── src
│  	├── components
│  	│   ├── CoverageMap // Coverage map component
│  	│   ├── GridMap // Grid map component
│  	│   ├── IconFont // IconFont component
│  	│   ├── Loading // Loading component
│  	├── constant
│  	│   ├── index.ts // All constant configurations
│  	├── devices // Device models
│  	├── hooks // hooks
│  	├── i18n // Internationalization
│  	├── pages
│   │   ├── home // Home page
│   │   ├── manual // Manual control page
│   │   ├── map // Map page
│   │   ├── more // Settings page
│   │   ├── record // Cleaning record page
│  	├── redux // redux
│   ├── res // Image resources & svg related
│   ├── styles // Global styles
│   ├── utils
│   │   ├── index.ts // Common business utility methods
│   ├── app.config.ts
│   ├── app.less
│   ├── app.tsx
│   ├── composeLayout.tsx
│   ├── global.config.ts
│   ├── mixins.less // less mixins
│   ├── routes.config.ts // Configure routes
│   ├── variables.less // less variables
├── typings // Global type definitions

Map Data Acquisition

Real-time map data acquisition includes two parts:

  1. Use ty.getGyroLatestCleanMap to get the latest cleaning map data stored in the cloud and display it
  2. Enable mqtt to listen to streaming data. During cleaning, the device will continuously report incremental map data. After we get this data, we continuously update the real-time map. This logic can be found in src/hooks/useMapData.ts

Map Components

MiniApp currently supports 2 types of map components:

You can import and use from @ray-js/gyro-map-component. Please refer to its configuration items

import React, { FC, useEffect, useState } from "react";
import { MapApiFuncGridMap, parseGridMapData } from "@ray-js/gyro-map-sdk";
import { useMapData } from "@/hooks/useMapData";
import { getSystemInfoSync } from "ray";
import { GridMapComponent } from "@ray-js/gyro-map-component";

type Props = {
  subRecordId?: string;
  onInitialized?: () => void;
};

const GridMap: FC<Props> = ({ subRecordId, onInitialized }) => {
  // useMapData encapsulates the logic for obtaining real-time map or cleaning map data
  const { mapData, mapConfig } = useMapData(subRecordId);
  const [mapApiFuncs, setMapApiFuncs] = useState<MapApiFuncGridMap | null>(
    null
  );

  useEffect(() => {
    if (mapData && mapApiFuncs) {
      // parseGridMapData is responsible for converting the original map data into the format required by the grid map component
      const gridMapData = parseGridMapData([mapData], mapConfig, false);

      const {
        mapData: convertedMapData,
        pathData: convertedPathData,
        pilePosition,
      } = gridMapData;

      mapApiFuncs.drawMap(convertedMapData);
      convertedPathData.length > 0 &&
        mapApiFuncs.drawCleanPath(convertedPathData);

      pilePosition &&
        convertedMapData.data?.length > 1 &&
        mapApiFuncs.drawChargingStation(pilePosition);
    }
  }, [mapData, mapApiFuncs]);

  return (
    <GridMapComponent
      config={{
        containerTop: "120px",
        containerHeight: `${getSystemInfoSync().screenHeight - 120}px`,
        showPath: false,
      }}
      onMapReady={(funcs) => {
        // onMapReady exports the APIs available for this map component, from this moment you can freely call these APIs to draw the map
        setMapApiFuncs(funcs);
        onInitialized && onInitialized();
      }}
    />
  );
};

export default GridMap;

You can import and use from @ray-js/gyro-map-component. Please refer to its configuration items

import React, { FC, useEffect, useState } from "react";
import { MapApiFuncCoverageMap, parseMapData } from "@ray-js/gyro-map-sdk";
import { useMapData } from "@/hooks/useMapData";
import { getSystemInfoSync } from "ray";
import { CoverageMapComponent } from "@ray-js/gyro-map-component";

type Props = {
  subRecordId?: string;
  onInitialized?: () => void;
};

const CoverageMap: FC<Props> = ({ subRecordId, onInitialized }) => {
  // useMapData encapsulates the logic for obtaining real-time map or cleaning map data
  const { mapData, mapConfig } = useMapData(subRecordId);
  const [mapApiFuncs, setMapApiFuncs] = useState<MapApiFuncCoverageMap | null>(
    null
  );

  useEffect(() => {
    if (mapData && mapApiFuncs) {
      // parseMapData is responsible for converting the original map data into the format required by the coverage map component
      const { pointsData, currentPos } = parseMapData([mapData], mapConfig);

      mapApiFuncs.drawMap && mapApiFuncs.drawMap(pointsData);

      mapApiFuncs.drawRobot && currentPos && mapApiFuncs.drawRobot(currentPos);
    }
  }, [mapData, mapApiFuncs]);

  return (
    <CoverageMapComponent
      config={{
        containerTop: "120px",
        containerHeight: `${getSystemInfoSync().screenHeight - 120}px`,
        borderWidth: 0.5,
      }}
      onMapReady={(funcs) => {
        // onMapReady exports the APIs available for this map component, from this moment you can freely call these APIs to draw the map
        setMapApiFuncs(funcs);
        onInitialized && onInitialized();
      }}
    />
  );
};

export default CoverageMap;

Cleaning Records List

Cleaning record related APIs have been encapsulated in src/api/request.ts, including:

Please pay attention to the processing of list data, which includes some complex data parsing

Cleaning Record Details

The cleaning record details actually reuse the map page, but with record-related parameters passed via the router. When the map page detects these parameters, it will enter the logic of obtaining cleaning record details data (refer to src/hooks/useMapData.ts).

Manual control is a general DP sending function, using DP direction_control.

The template has already encapsulated a simple manual control component and page, please refer to the src/pages/manual page.