前提条件

构建内容

在此 Codelab 中,您将利用面板小程序开发构建出一个出行两轮车面板,通过 DP 协议、接口将实现以下能力:

学习内容

所需条件

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

产品名称:新滑板车蓝牙

需求原型

  1. 首页
    • 标题栏点击切换按钮可以切换设备需在出行回归 APP 上使用
    • 顶部区域展现了 4G 信号值、GPS 信号、蓝牙连接状态以及总里程。
    • GPS 和 4G 信号值都分为了五个等级,对应不同的图标
    • 中心区域分为两部分。一部分是设备信息相关,包括设备电量、续航里程、档位切换以及产品图标;另一部分则是快捷入口,其中车辆定位和车辆体检点击跳转到对应的二级页。
    • 骑行记录模块展示最近的一次骑行记录数据,点击更多跳转到骑行记录二级页,点击卡片跳转到骑行轨迹二级页。
    • 滑动上锁和解锁,点击‘GO'跳转至骑行导航二级页。

  1. 更多设置页
    • 展示了一些常见的功能设置,如:定速巡航、大灯开关、速度限制等

首先需要创建一个户外出行类产品,定义产品有哪些功能点,然后面板中再根据这些功能点一一实现。

进入IoT 平台,点击左侧产品菜单,产品开发,创建产品,选择标准类目 -> 户外出行 -> 滑板车:

选择功能点,这里根据自己需求选择即可,这些功能未选择不影响视频预览。

🎉 在这一步,我们创建了一个名为 PanelOutdoorScooter的出行滑板车产品。

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

这部分我们在 小程序开发者 平台上进行操作,注册登录 小程序开发者平台 进行操作。

详细的操作路径可参考 面板小程序 - 创建面板小程序

IDE 基于模板创建项目工程

这部分我们在 Tuya MiniApp Tools 上进行操作,打开 IDE 创建一个基于出行两轮车模板的面板小程序项目。

详细的操作路径可参考 面板小程序 - 初始化项目工程

上面的步骤我们已经初始化好了一个面板小程序的开发模板,下面我们介绍下工程目录。

├── src
│  	├── @types // 全局类型定义
│  	├── api
│  	│   ├── getCachedLaunchOptions.ts // 获取启动参数
│  	│   ├── getCachedSystemInfo.ts // 获取设备信息
│  	│   └── index.ts // 云端API
│  	├── components
│  	│   ├── battery // 电池组件
│  	│   ├── dynamic-number // 文字动态变化组件
│  	│   ├── record // 骑行记录组件
│  	│   ├── service-toast // C端续费服务弹窗
│  	│   ├── signal // 4G、GPS信号展示组件
│  	│   ├── sjs-slider // 滑块组件
│  	│   ├── top-bar // TopBar组件
│  	│   ├── unlock-slider // 滑动解锁组件
│  	│   └── index.ts
│  	├── constant // 常用配置
│  	├── devices // 设备模型
│  	├── hooks // hooks
│  	├── i18n // 多语言
│  	├── pages
│   │   ├── home // 首页
│   │   ├── more // 更多设置页
│  	├── redux // redux
│   ├── res // 本地图片
│   ├── styles // 全局样式
│   ├── utils // 常用工具方法
│   ├── app.config.ts
│   ├── app.less
│   ├── app.tsx
│   ├── composeLayout.tsx // 处理监听子设备添加,解绑,DP变化等
│   ├── global.config.ts
│   ├── routes.config.ts // 配置路由

产品需要开通出行增值服务才能对车辆进行操作,在代码路径 src/components/service-toast/index.tsx 中通过调用 ty.outdoor.getValueAddedServicesAbility 获取设备增值服务能力,其中 outdoor_data_cloud_store 表示出行增值服务;如果未购买该服务或服务已过期,则需要提示去购买或续费

// C端续费能力判断 (未购买增值服务或服务已过期 弹窗提示)
const checkServiceAbility = async () => {
  const res = await getCServicesAbility(devId)
  const { inService, isPidHadVAS, commodityUrl, hadPopup } = res
  dispatch(
    updateCommonInfo({
      inService,
      isPidHadVAS,
      commodityUrl: commodityUrl || '',
    })
  )
  if (!hadPopup && isPidHadVAS && inService) {
    // 品牌方有配置C端用户续费套餐有赠送
    if (commodityUrl && commodityUrl !== '') {
      ty.showModal({
        title: '',
        content: Strings.getLang('serviceActive'),
        confirmText: Strings.getLang('goCheck'),
        cancelText: Strings.getLang('confirm'),
        showCancel: true,
        cancelColor:
          theme === 'dark' ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.5)',
        success: ({ confirm, cancel }) => {
          if (confirm) {
            // 跳转详情
            ty.openInnerH5({ url: commodityUrl })
          }
        },
      })
    } else {
      // 涂鸦默认赠送
      ty.showModal({
        title: '',
        content: Strings.getLang('serviceActive'),
        showCancel: false,
        confirmText: Strings.getLang('confirm'),
      })
    }
    await setCServicesPop(devId)
  }
}

滑动开关锁,下发blelock_switch DP,下发前会检查增值服务是否过期,若服务过期则弹窗提示

// 车辆控制时检查权限
const checkPermissions = (params: ICheckPermissionParams) => {
  const {
    dpSchema,
    dpCode,
    inService,
    isPidHadVAS,
    isBleOnline,
    cancelCb,
    successCb,
  } = params
  const { extContent } = dpSchema[dpCode]
  const isExtNull = extContent === '' || extContent === undefined
  const route = isExtNull ? 0 : JSON.parse(extContent).route
  // 若智能服务已到期/未配置赠送用户未购买时,当用户通过蜂窝通道控制车辆时
  if (
    !inService &&
    isPidHadVAS &&
    (route === 0 || (route === 1 && !isBleOnline) || isExtNull)
  ) {
    ty.showModal({
      title: '',
      content: Strings.getLang('expireControl'), // 提示智能服务已到期,无法远程控制车辆
      showCancel: false,
      confirmText: Strings.getLang('confirm'),
      success: ({ confirm }) => {
        if (confirm && dpCode === 'blelock_switch') {
          cancelCb()
        }
      },
    })
    return
  }
  successCb()
}

// 下发开关锁DP
const changeDp = (val) => {
  const { extContent } = switchDpSchema
  const isExtNull = extContent === '' || extContent === undefined
  const route = isExtNull ? 0 : JSON.parse(extContent).route
  if (route > 0 && isBleOnline) {
    actions[blelockSwitch].set(val, { pipelines: [6, 5, 4, 3, 2, 1] })
  } else {
    actions[blelockSwitch].set(val)
  }
}

// 开锁
const openLock = () => {
  checkPermissions({
    dpCode: 'blelock_switch',
    inService,
    isPidHadVAS,
    dpSchema,
    isBleOnline,
    successCb: () => {
      setLoading(true)
      changeDp(true)
      clearTimeout(openLoadingTimer)
      openLoadingTimer = setTimeout(() => {
        // 开锁超时
        setValue(1)
        setLoading(false)
      }, 5000)
    },
    cancelCb: () => {
      // 被禁用滑块返回
      setValue(100)
      setTimeout(() => {
        setValue(1)
      }, 50)
    },
  })
}

// 关锁
const closeLock = () => {
  setLoading(true)
  changeDp(false)
  clearTimeout(closeLoadingTimer)
  closeLoadingTimer = setTimeout(() => {
    // 关锁超时
    setValue(100)
    setLoading(false)
  }, 5000)
}

定位

车辆体检

电池信息

骑行轨迹

骑行记录