Template

Tuya Panel Kit Template Docs

Tuya Panel Kit Template 是设备控制面板工程的最佳实践,包含了开发面板的基础框架,这篇文档会大致介绍一下模板为我们做了什么,以及我们需要如何基于模板开发一个全新的面板项目。

快速运行

请参考开启您的第一个面板工程

模板详解

目录详解

├── .babelrc             // babel配置文件
├── .eslintignore // 配置哪些文件不需要eslint
├── .eslintrc.js // eslint配置文件
├── .gitignore // 配置哪些文件不需要git
├── .npmrc // npm配置文件
├── README.md // 项目的信息,包括但不限于项目名、productId、作者、描述等。
├── index.android.js // 安卓入口
├── index.ios.js // IOS入口
├── index.js // 安卓入口(为了兼容)
├── package.json // npm依赖管理
├── rn-cli.config.js // metro配置文件
├── src
│   ├── components // 放置项目中用到的各复用的功能组件
│   ├── composeLayout.js // 封装处理了面板内部所需要的一些`设备事件`和`设备信息`
│   ├── containers // 放置项目的各个页面级别的组件
│   ├── i18n // 放置多语言配置文件
│   ├── main.js // 项目入口文件, 继承自`NavigatorLayout`,通过重写`hookRoute`方法将一些必要的配置传入,如:背景、topbar等。重写`renderScene`方法控制路由跳转。
│   ├── redux // 放置redux相关的一些代码
│   ├── res // 放置本地资源,包括图片,svg path等
│   └── utils // 放置面板内部会用到的一些常用函数等
└── yarn.lock

composeLayout详解

简单的来说,composeLayout这个高阶函数为我们所做的事情共三件;

  1. 面板初始化时,composeLayout处理原始的devInfo,初始化redux store

  2. 面板运行时,composeLayout监听设备变更相关的事件,并实时更新相应的redux store

  3. 传入的componentredux storeconnect

import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Provider, connect } from 'react-redux';
import { TYSdk } from 'tuya-panel-kit';
import {
devInfoChange,
deviceChange,
responseUpdateDp,
} from './redux/modules/common';

const TYEvent = TYSdk.event;
const TYDevice = TYSdk.device;

/**
*
* @param {Object} store - redux store
* @param {ReactComponent} component - 需要连接到redux store的组件,通常为即为main
*/
const composeLayout = (store, component) => {
const NavigatorLayoutContainer = connect(_.identity)(component);
const { dispatch } = store;

/**
* 此处监听了`设备数据变更`事件,
* 每当dp点数据变更时,会将变更的dp点状态同步更新到`redux`中去。
* 同理当设备信息变更时,也会将变更的设备信息值同步更新到`redux`中去。
*/
TYEvent.on('deviceDataChange', data => {
switch (data.type) {
case 'dpData':
dispatch(responseUpdateDp(data.payload));
break;
default:
dispatch(deviceChange(data.payload));
break;
}
});

/**
* 此处监听了`网络状态变更事件`事件,
* 每当设备信息变更时,会将变更的设备信息值同步更新到`redux`中去。
*/
TYEvent.on('networkStateChange', data => {
dispatch(deviceChange(data));
});

class PanelComponent extends Component {
static propTypes = {
// eslint-disable-next-line
devInfo: PropTypes.object.isRequired,
}

/**
* 如果面板进入时,`devInfo`已经存在(通常都会存在的)
* 这里会调用setDeviceInfo将原始的devInfo处理一下,并塞入`redux`
*
* 如果面板进入时,`devInfo`不存在,
* 那么会调用getDeviceInfo异步获取处理好的`devInfo`,并塞入`redux`
*/
constructor(props) {
super(props);
if (props && props.devInfo && props.devInfo.devId) {
TYDevice.setDeviceInfo(props.devInfo);
TYDevice.getDeviceInfo().then(data => dispatch(devInfoChange(data)));
// eslint-disable-next-line
} else if (props.preload) {
// do something
} else {
TYDevice.getDeviceInfo().then(data => dispatch(devInfoChange(data)));
}
}

render() {
return (
<Provider store={store}>
<NavigatorLayoutContainer />
</Provider>
);
}
}

return PanelComponent;
};

export default composeLayout;

main详解

简单的来说,MainLayout这个入口组件为我们所做的事情只有一件;

  1. 继承了NavigatorLayout,帮助面板内部管理多页面。
import _ from 'lodash';
import React from 'react';
import { StyleSheet } from 'react-native';
import { TYSdk, NavigatorLayout } from 'tuya-panel-kit';
import composeLayout from './composeLayout';
import configureStore from './redux/configureStore';
import Home from './containers/Home';
import { formatUiConfig } from './utils';

/**
* 通过模板内置的store配置方法,生成一个store,
*/
export const store = configureStore();

const linearGradientBackground = {
'3%': '#FF7E38',
'90%': '#FF624C',
};

class MainLayout extends NavigatorLayout {
constructor(props) {
super(props);
console.log('TYSdk :', TYSdk);
}

/**
*
* @desc
* hookRoute 可以在这里针对特定路由做一些控制处理
*
* @param {Object} route
* @return {Object} - 提供给当前页面组件父容器布局的一些控制值
* {
* style: ViewPropTypes.style, // 容器样式,可在此调整背景颜色
* background: backgroundImage | linearGradientBackground, // 面板图片背景或渐变背景,渐变格式可参考LinearGradient和RadialGradient组件
* topbarStyle: ViewPropTypes.style, // TopBar样式,可在调整TopBar背景色
* topbarTextStyle: Text.propTypes.style, // TopBar的文字样式
* renderTopBar: () => {}, // 自定义渲染TopBar
* hideTopbar: true | false, // 控制是否隐藏 TopBar
* renderStatusBar: () => {}, // 自定义渲染StatusBar,IOS only
* showOfflineView: true | false, // 控制是否渲染 OfflineView
* OfflineView: ReactComponent, // 自定义的 OfflineView 组件
* }
*/
// eslint-disable-next-line
hookRoute(route) {
// switch (route.id) {
// case 'main':
// // eslint-disable-next-line
// route.background = background;
// break;

// default:
// break;
// }

return {
background: linearGradientBackground,
style: styles.fullview,
};
}


/**
* @desc
* 在此可以通过route中的id来判断使用哪个页面组件,
* 此外如果有额外的props需要传递给页面组件的,可以在此进行传递。
*
* @param {Object} route - route对象
* @param {object} navigator - Navigator对象,具体使用方法可参考https://facebook.github.io/react-native/docs/0.43/navigator.html
*/
renderScene(route, navigator) {
let component;
let schema = {};
let uiConfig = {};
/**
* 因为我们使用`composeLayout`包裹了`MainLayout`,
* 因此props里可以拿到所有`redux store`里传递下来的`props`.
*/
const { dispatch, devInfo, dpState, logs } = this.props;

if (!_.isEmpty(devInfo)) {
schema = devInfo.schema || {};
// 暂不建议使用,后续可能会有改动
uiConfig = formatUiConfig(devInfo);
}

switch (route.id) {
case 'main':
component =
<Home
dpData={{ state: dpState, schema, uiConfig }}
dispatch={dispatch}
navigator={navigator}
logs={logs}
/>;
break;

default:
break;
}

return component;
}
}

const styles = StyleSheet.create({
fullview: {
// backgroundColor: 'red',
},
});

export default composeLayout(store, MainLayout);

devInfo详解

此前在NavigatorLayout文档中,已经解释了未经处理的devInfo对象,而在此处则是由composeLayout处理过后的devInfo对象,也是面板内部常用到且比较重要的一个东西,后续面板内部使用devInfo请以下面这份为准,下面会解释一下一些常用的字段的作用。

  • name: 设备名称
  • productId: 产品id
  • uiId: 当前产品对应的面板id
  • bv: 硬件基线版本
  • devId: 设备Id
  • gwId: 网关Id, 如果是单品,devId一般和gwId相等
  • ability: 只有蓝牙设备使用, 如果是单点蓝牙设备,值是5
  • appOnline: App是否在线
  • deviceOnline: 设备是否在线
  • isLocalOnline: 局域网是否在线
  • isShare: 是否是共享设备
  • isVDevice: 是否是演示设备
  • groupId: 群组设备Id,可用于判断是否群组设备
  • networkType: 设备的在线类型
  • capability: 设备的能力类型, 标志设备支持什么能力, 如支持zigbee, 红外, 蓝牙等
  • schema: 设备所属产品的功能点(dp, data point)定义, 功能点解释请看dp解释
  • state: dp点的状态

FAQs

1. 如何更改设备状态

更改设备状态在面板内部来说就是我们需要下发(publish)一个指令给设备,设备在成功接收到后会上报(report)回来这个指令。简单来说,我们如果需要更改设备的状态,通过以下代码即可。

import { TYSdk } from 'tuya-panel-kit';

const TYDevice = TYSdk.device;

const data = { [dpCode]: dpValue };

TYDevice.putDeviceData(data);

以上方式是最简单的操作方式,但我们更推荐通过redux中已经定义好的action来下发指令,通过一种统一的处理手段我们可以更方便地应对后续接口名变更等情况。

import { updateDp } from 'path/redux/modules/common';

const data = { [dpCode]: dpValue };

dispatch(updateDp(data));

2. 常用的事件系列

1. 具体使用方式:

import { TYSdk } from 'tuya-panel-kit';

const TYEvent = TYSdk.event;

TYEvent.on(yourEventName, yourHandler);

2. 监听事件相关方法:

  • TYEvent.on: 开始监听事件
  • TYEvent.off: 取消监听事件
  • TYEvent.emit: 主动触发事件

3. 常用的事件名:

  • deviceDataChange:核心事件,该事件总共分为三块,通过返回值中的type字段区分:type为dpData时,代表dp点状态变更,即设备将dp点状态上报了;type为devInfo时,代表设备信息改变通知,如设备名字改等;type为deviceOnline时,代表设备在线状态变更
  • networkStateChange:app网络状态变更通知
  • bluetoothChange:蓝牙在线状态变更通知
  • onLinkageTimeUpdate:定时状态变更通知
  • NAVIGATOR_ON_WILL_FOCUS: NavigatorLayout内部封装的路由变更事件
  • NAVIGATOR_ON_DID_FOCUS: NavigatorLayout内部封装的路由变更事件

注意不要忘记在unmount的时候取消监听事件噢

3. 常用的方法系列

1. 具体使用方式:

import { TYSdk } from 'tuya-panel-kit';

const TYDevice = TYSdk.device;
const TYNative = TYSdk.native;

2. dp点相关方法:

  • 下发dp点(与硬件端交互核心方法):
TYDevice.putDeviceData(data: object) => void
  • 根据code获取dpId:
TYDevice.getDpIdByCode(dpCode: string) => number | string
  • 根据dpId获取code:
TYDevice.getDpCodeById(dpId: string) => string
  • 检查dp点是否存在:
// 传入参数为dpId或者dpCode
TYDevice.checkDpExist(idOrCode: string) => boolean

3. 数据相关方法:

  • 请求云端接口:
TYNative.apiRequest(
{
a: apiName,
v: apiVersion
postData: params,
},
d => successHandle(d),
e => errorHandle(e),
);
  • 跳转到云定时页面
TYNative.gotoDpAlarm({
category: category,
repeat: 0, // 0代表需要选择重复、1代表不需要
data: [{
dpId: dpId,
dpName: dpName,
selected: 0,
rangeKeys: [true, false],
rangeValues: [dpValue1, dpValue2]
}]
});

电话咨询

在线咨询

400-881-8611