前提条件

开发环境

详见 面板小程序 > 搭建环境

产品名称:惯导扫地机器人

需求原型

由于产品定义了面板和设备所拥有的功能点,所以在开发一个智能设备面板之前,我们首先需要创建一个惯导扫地机器人产品,定义产品有哪些功能点,然后再在面板中一一实现这些功能点。

首先,注册登录 涂鸦开发者平台,并在平台创建产品:

  1. 单击页面左侧 产品 > 产品开发,在 产品开发 页面单击 创建产品
  2. 标准类目 下选择 小家电,产品品类选择 扫地机器人
  3. 选择智能化方式,产品方案 选择 惯导型扫地机,并完善产品信息,如填写 产品名称Robot

  1. 单击 创建产品,完成产品创建。
  2. 产品创建完成后,进入到 添加标准功能 页面,根据自己需求添加功能即可,然后单击 确定

🎉 完成以上步骤后,一个名为 Robot 的扫地机器人产品创建完成。

开发者平台创建面板小程序

面板小程序的开发在 小程序开发者 平台上进行操作,首先请前往 小程序开发者平台 完成平台的注册登录。

详细操作步骤可以参考 创建面板小程序

IDE 基于模板创建项目工程

打开 IDE 创建一个基于 扫地机小程序模板 的面板小程序项目,需要在 Tuya MiniApp IDE 上操作。

详细操作步骤可以参考 初始化项目工程

完成以上步骤后,一个面板小程序的开发模板初始化完成。以下为工程目录的介绍:

├── src
│  	├── components
│  	│   ├── CoverageMap // 涂抹型地图组件
│  	│   ├── GridMap // 栅格型地图组件
│  	│   ├── IconFont // IconFont 组件
│  	│   ├── Loading // Loading 组件
│  	├── constant
│  	│   ├── index.ts // 存放所有的常量配置
│  	├── devices // 设备模型
│  	├── hooks // hooks
│  	├── i18n // 多语言
│  	├── pages
│   │   ├── home // 首页
│   │   ├── manual // 手动控制页面
│   │   ├── map // 地图页面
│   │   ├── more // 设置页面
│   │   ├── record // 清扫记录页面
│  	├── redux // redux
│   ├── res // 图片资源 & svg 相关
│   ├── styles // 全局样式
│   ├── utils
│   │   ├── index.ts // 业务常用工具方法
│   ├── app.config.ts
│   ├── app.less
│   ├── app.tsx
│   ├── composeLayout.tsx
│   ├── global.config.ts
│   ├── mixins.less // less mixins
│   ├── routes.config.ts // 配置路由
│   ├── variables.less // less variables
├── typings // 全局类型定义

地图数据的获取

实时地图数据的获取包含两个部分

  1. 通过ty.getGyroLatestCleanMap获取存放在云端的最新的清扫地图的数据并将其展示
  2. 开启 mqtt 监听流数据,清扫过程中设备将不断上报增量的地图数据,我们拿到这些数据后持续更新实时地图,这部分逻辑可以参考src/hooks/useMapData.ts

地图组件

小程序目前支持 2 种样式的地图组件:

可以从@ray-js/gyro-map-component引入使用这个地图,请参考它的配置项

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封装了获取实时地图或清扫地图数据的逻辑
  const { mapData, mapConfig } = useMapData(subRecordId);
  const [mapApiFuncs, setMapApiFuncs] = useState<MapApiFuncGridMap | null>(
    null
  );

  useEffect(() => {
    if (mapData && mapApiFuncs) {
      // parseGridMapData负责将原始地图数据转换为栅格型地图组件需要的格式
      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会将该地图组件可用的API导出,这个时刻起你可以自由调用这里的API来绘制地图
        setMapApiFuncs(funcs);
        onInitialized && onInitialized();
      }}
    />
  );
};

export default GridMap;

可以从@ray-js/gyro-map-component引入使用这个地图,请参考它的配置项

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封装了获取实时地图或清扫地图数据的逻辑
  const { mapData, mapConfig } = useMapData(subRecordId);
  const [mapApiFuncs, setMapApiFuncs] = useState<MapApiFuncCoverageMap | null>(
    null
  );

  useEffect(() => {
    if (mapData && mapApiFuncs) {
      // parseMapData负责将原始地图数据转换为涂抹型地图组件需要的格式
      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会将该地图组件可用的API导出,这个时刻起你可以自由调用这里的API来绘制地图
        setMapApiFuncs(funcs);
        onInitialized && onInitialized();
      }}
    />
  );
};

export default CoverageMap;

清扫记录列表

清扫记录相关的 API 已经封装在src/api/request.ts,包含:

请注意对列表数据的处理,这里包含了一些复杂的数据解析

清扫记录详情

清扫记录详情实际上复用了地图页面,只是路由传入了记录相关的参数,地图页面在检测到这些参数传入后将走入获取清扫记录详情数据的逻辑 (可参考src/hooks/useMapData.ts)。

手动控制是一般的 DP 下发功能,使用 DP direction_control

模板已经封装了简易的手动控制组件及页面,请参考 src/pages/manual 页面。