
智能灯是一款常见的智能设备,安装智能灯后,用户可以使用手机 App 轻松调整室内颜色和亮度,设置不同的照明场景来轻松创造出温暖、放松、有趣或者鼓舞人心的室内空间。
一款 Powered By Tuya 的五路彩灯设备即可,您也可以前往涂鸦智选平台采购样品。查看详情
在智能灯具的开发过程中,您会碰到各种各样的某路灯,例如一路灯或者五路灯。不同的数字代表照明产品能实现的功能不同,如下表所示。其中,五路灯有白光和彩光是 2 种模式切换,但不可同时启用。
| 一路灯 | 二路灯 | 三路灯 | 四路灯 | 五路灯 | 
|---|---|---|---|---|
| 亮度 | 色温+亮度 | HSV | 亮度+HSV | 色温+亮度+HSV | 
彩光的色彩模型常见的有 RGB 模型和 HSV 模型:
RGB 色彩模型
RGB(Red、Green、Blue)色彩模式是一种面向硬件的色彩模型,每个字母的取值为 0~255。RGB都是0时是黑色,都是255时是白色。
HSV 色彩模型
HSV(Hue、Saturation、Value)色彩模式是一种面向用户感官的色彩模型,侧重与色彩表示。它比 RGB 更接近人们对彩色的感知经验。
色相 H:用角度度量,取值范围为0°~360°,从红色开始按照逆时针方向计算,红色为0°,绿色为120°,蓝色为240°,黄色为60°,青色为180°,品红为300°。
饱和度 S:表示色彩的纯度。数值越高颜色则深而艳,数值越低则颜色逐渐变灰,取值范围为0.0~1.0,白色的S=0。
明度 V:表示颜色的明亮程度。取值范围为0.0(黑色)~1.0(白色)。

创建产品后,您可以为产品设置功能,包括标准功能、自定义功能和云功能。功能点是对以上产品功能的抽象表示,是具体智能设备功能的抽象,用于描述产品功能及其参数。更多详情,请参考 功能定义。
例如,假设某款产品的 PID 为 7z8knex5zdx80***,而且其功能点如下表所示:
| DP ID | 功能点 | 标识符 | 数据传输类型 | 功能点类型 | 功能点属性 | 
|---|---|---|---|---|---|
| 20 | 开关必选 | switch_led | 可下发可上报 | 布尔型 | |
| 21 | 模式必选 | work_mode | 可下发可上报 | 枚举型 | 枚举值white, colour, scene, music | 
| 22 | 亮度值必选 | bright_value | 可下发可上报 | 数值型 | 数值范围:10-1000, 间距:1, 倍数:0, 单位: | 
| 23 | 冷暖值必选 | temp_value | 可下发可上报 | 数值型 | 数值范围:0-1000, 间距:1, 倍数:0, 单位: | 
| 24 | 彩光必选 | colour_data | 可下发可上报 | 字符型 | |
| 25 | 场景必选 | scene_data | 可下发可上报 | 字符型 | |
| 26 | 倒计时剩余时间必选 | countdown | 可下发可上报 | 数值型 | 数值范围:0-86400, 间距:1, 倍数:0, 单位: | 
| 27 | 音乐灯 | music_data | 只下发 | 字符型 | |
| 28 | 调节必选 | control_data | 可下发可上报 | 字符型 | |
| 29 | 调试 | debug_data | 只下发 | 字符型 | 
安装涂鸦开发工具包
涂鸦提供官方的命令行工具(CLI),和涂鸦的面板定制协作平台深度集成,请务必安装,安装命令如下:
MacOS & Linux
curl -fsSL https://raw.githubusercontent.com/tuya/tuya-panel-cli/main/install.sh | sh
注意:首次安装需要根据提示设置环境变量。
Windows Powershell
iwr https://raw.githubusercontent.com/tuya/tuya-panel-cli/main/install.ps1 -useb | iex
说明:安装完成后运行
tuya-panel-cli help,如果展示所有可用命令的帮助信息,则安装成功。
注意:
iwr命令在低版本 Powershell 下不可用,请参考 iwr 无法被识别(Windows) 解决。
初始化模板工程
$ tuya-panel-cli init MyLampApp
$ cd MyLampApp
$ yarn && yarn start

打开 涂鸦面板-RN App,点击需要调试的虚拟设备的面板进行调试。
注意: 调试时主机和手机的Wi-Fi需保持同步。
iOS 调试
Android 调试
com.ty.panel.dev 应用。最终模板运行成功的示例可参考下图:

本模板工程基于彩灯五路模版:
支持以下功能:
支持以下品类:
Wi-Fi
蓝牙
蓝牙 Mesh
Zigbee
说明: 此处仅描述脚手架相关的目录,通用目录结构信息参考 RN 0.59 脚手架文档。
├── src
│   ├── api // 放置项目中用到的一系列云端 api
│   ├── components // 放置项目中用到的复用的功能组件
│   ├── composeLayout.tsx // 封装处理了面板内部所需要的一些`设备事件`和`设备信息`
│   ├── config // 放置面板内部一些常用的配置文件
│   ├── containers // 放置项目的各个页面级别的组件
│   ├── i18n // 放置多语言配置文件
│   ├── main.tsx // 项目入口文件, 继承自 `NavigatorLayout`,可通过重写 `hookRoute` 方法将一些必要的配置传入,例如背景、topbar 等;重写 `renderScene` 方法控制路由跳转
│   ├── redux // 放置 redux 相关的一些代码
│   ├── res // 放置本地资源,包括图片,svg path 等
│   └── utils // 放置面板内部会用到的一些常用工具方法等
更换彩光颜色模型示例代码
导入需要更换的彩光底图。
重写彩光模型代码。
定位至彩光模型代码处,即lampClassic -> components -> HuePicker -> index.js。
重写代码如下:
	import PropTypes, { number } from 'prop-types';
	import React, { Component } from 'react';
	import { View, Image, PanResponder, StyleSheet, ViewStyle, ViewPropTypes } from 'react-native';
	import { Utils } from 'tuya-panel-kit';
	export default class HuePicker extends Component {
	static propTypes = {
		accessibilityLabel: PropTypes.string,
		style: ViewPropTypes.style,
		disabled: PropTypes.bool,
		radius: PropTypes.number,
		innerRadius: PropTypes.number,
		thumbRadius: PropTypes.number,
		thumbInnerRadius: PropTypes.number,
		RingBackground: PropTypes.oneOfType([PropTypes.number, PropTypes.string, React.ReactElement]),
		hue: PropTypes.number,
		onValueChange: PropTypes.func,
		onComplete: PropTypes.func,
	};
	static defaultProps = {
		accessibilityLabel: 'HuePicker',
		style: null,
		disabled: false,
		radius: 130,
		innerRadius: 70,
		thumbRadius: 25,
		thumbInnerRadius: 20,
		RingBackground: require('./lamp_colorBg.png'),
		hue: 0,
		onValueChange: () => {},
		onComplete: () => {},
	};
	constructor(props) {
		super(props);
const { radius, thumbRadius } = props;
		this.cx = radius - thumbRadius;
		this.cy = radius - thumbRadius;
		// 可拖动圆球至原点的固定距离(令圆球始终在在色环中居中)
		this.fixedLength = radius - this.ringSize * 0.5;
		this._panResponder = PanResponder.create({
		onStartShouldSetPanResponder: this.shouldSetResponder,
		onMoveShouldSetPanResponder: this.shouldSetResponder,
		onPanResponderGrant: this._handleResponderGrant,
		onPanResponderMove: this._handleResponderMove,
		onPanResponderRelease: this._handleResponderRelease,
		onPanResponderTerminationRequest: () => false,
		onPanResponderTerminate: this._handleResponderRelease,
		// onStartShouldSetResponderCapture: () => false,
		onMoveShouldSetPanResponderCapture: () => false,
		});
	}
	componentWillReceiveProps(nextProps) {
		const { radius, innerRadius, thumbRadius } = nextProps;
const {
		radius: prevRadius,
		innerRadius: prevInnerRadius,
		thumbRadius: prevThumbRadius,
		} = this.props;
		if (
		prevRadius !== radius ||
		prevInnerRadius !== innerRadius ||
		prevThumbRadius !== thumbRadius
		) {
		this.cx = radius - thumbRadius;
		this.cy = radius - thumbRadius;
		this.fixedLength = radius - this.ringSize * 0.5;
		}
	}
	// 圆环尺寸
	get ringSize() {
		const { radius, innerRadius } = this.props;
return radius - innerRadius;
	}
	getRadianByCoord(xRelativeOrigin, yRelativeOrigin) {
		const { thumbRadius } = this.props;
		const xRelativeCenter = xRelativeOrigin - this.cx - thumbRadius;
const yRelativeCenter = yRelativeOrigin - this.cy - thumbRadius;
		let rad = Math.atan2(yRelativeCenter, xRelativeCenter);
		if (xRelativeCenter > 0 && yRelativeCenter > 0) rad = Math.PI * 2 - rad;
		if (xRelativeCenter < 0 && yRelativeCenter > 0) rad = Math.PI * 2 - rad;
		if (xRelativeCenter < 0 && yRelativeCenter < 0) rad = Math.abs(rad);
		if (xRelativeCenter > 0 && yRelativeCenter < 0) rad = Math.abs(rad);
		if (xRelativeCenter === 0 && yRelativeCenter > 0) rad = (Math.PI * 3) / 2;
		if (xRelativeCenter === 0 && yRelativeCenter < 0) rad = Math.PI / 2;
		return rad;
	}
	getHueByCoord(xRelativeOrigin, yRelativeOrigin) {
		// 0 ~ 2π
		const rad = this.getRadianByCoord(xRelativeOrigin, yRelativeOrigin);
return (rad * 180) / Math.PI;
	}
	getCoordByHue(hue) {
		const rad = ((360 - hue) * Math.PI) / 180;
		const x = this.cx + this.fixedLength * Math.cos(rad);
const y = this.cy + this.fixedLength * Math.sin(rad);
		return { x, y };
	}
	getColorInfoByHue(hue) {
		const { r, g, b } = Utils.ColorUtils.hsvToRgb(hue, 1, 1);
		return {
	r,
		g,
		b,
		rgbString: `rgb(${r}, ${g}, ${b})`,
		};
	}
	thumbRef;
	thumbInnerRef;
	cx;
	cy;
	fixedLength;
	_panResponder;
	xRelativeOriginStart;
	yRelativeOriginStart;
	shouldSetResponder = e => {
		// eslint-disable-next-line react/destructuring-assignment
		if (this.props.disabled) {
	return false;
		}
		const { locationX, locationY } = e.nativeEvent;
		// 是否在可点击范围内
		const { innerRadius, radius, thumbRadius } = this.props;
		const xRelativeCenter = locationX - this.cx - thumbRadius;
		const yRelativeCenter = locationY - this.cy - thumbRadius;
		const len = Math.sqrt(xRelativeCenter ** 2 + yRelativeCenter ** 2);
		if (len >= innerRadius && len <= radius) {
		return true;
		}
		return false;
	};
	_moveTo(xRelativeOrigin, yRelativeOrigin, callback) {
		const hue = Math.round(this.getHueByCoord(xRelativeOrigin, yRelativeOrigin));
		const { x = 0, y = 0 } = this.getCoordByHue(hue);
const color = this.getColorInfoByHue(hue);
		this.updateThumbStyle({
		transform: [
			{
			translateX: x,
			},
			{
			translateY: y,
			},
		],
		});
		this.updateThumbInnerStyle({
		backgroundColor: color.rgbString,
		});
		typeof callback === 'function' && callback(hue, color);
	}
	_handleResponderGrant = e => {
		const { locationX, locationY } = e.nativeEvent;
		this.xRelativeOriginStart = locationX;
this.yRelativeOriginStart = locationY;
	};
	_handleResponderMove = (e, gestureState) => {
		const { dx, dy } = gestureState;
		const { onValueChange } = this.props;
const xRelativeOrigin = this.xRelativeOriginStart + dx;
		const yRelativeOrigin = this.yRelativeOriginStart + dy;
		this._moveTo(xRelativeOrigin, yRelativeOrigin, onValueChange);
	};
	_handleResponderRelease = (e, gestureState) => {
		const { dx, dy } = gestureState;
		const { onComplete } = this.props;
const xRelativeOrigin = this.xRelativeOriginStart + dx;
		const yRelativeOrigin = this.yRelativeOriginStart + dy;
		// eslint-disable-next-line no-undef
		this._moveTo(xRelativeOrigin, yRelativeOrigin, onComplete);
		this.xRelativeOriginStart = 0;
		this.yRelativeOriginStart = 0;
	};
	updateThumbStyle(style) {
		if (this.thumbRef) {
		this.thumbRef.setNativeProps({ style });
}
	}
	updateThumbInnerStyle(style) {
		if (this.thumbInnerRef) {
		this.thumbInnerRef.setNativeProps({ style });
}
	}
	renderRingBackground() {
		const { radius, RingBackground } = this.props;
		if (typeof RingBackground === 'number') {
	return (
			<Image
			style={{
				width: radius * 2,
				height: radius * 2,
				borderRadius: radius,
			}}
			source={RingBackground}
			/>
		);
		}
		if (React.isValidElement(RingBackground)) {
		return React.cloneElement(RingBackground, {
			style: {
			width: radius * 2,
			height: radius * 2,
			borderRadius: radius,
			...RingBackground.props.style,
			},
		});
		}
	}
	render() {
		const {
		accessibilityLabel,
	style,
		disabled,
		radius,
		thumbRadius,
		thumbInnerRadius,
		hue,
		} = this.props;
		const { x = 0, y = 0 } = this.getCoordByHue(hue);
		const { rgbString } = this.getColorInfoByHue(hue);
		return (
		<View
			accessibilityLabel={accessibilityLabel}
			style={style}
			pointerEvents="box-only"
			{...this._panResponder.panHandlers}
		>
			{/* 圆环 */}
			<View style={[styles.sectionRing, { width: radius * 2, height: radius * 2 }]}>
			{this.renderRingBackground()}
			</View>
			{/* 圆球 */}
			<View
			ref={ref => {
		this.thumbRef = ref;
			}}
			style={[
				styles.sectionThumb,
				{
				width: thumbRadius * 2,
				height: thumbRadius * 2,
				borderRadius: thumbRadius,
				opacity: disabled ? 0 : 1,
				transform: [
					{ translateX: x, },
					{ ranslateY: y, },
				],
				},
			]}
			>
			<View
				ref={ref => {
				this.thumbInnerRef = ref;
				}}
				style={{
				width: thumbInnerRadius * 2,
				height: thumbInnerRadius * 2,
				borderRadius: thumbInnerRadius,
				backgroundColor: rgbString,
				}}
				/>
			</View>
		</View>
		);
	}
	}
	const styles = StyleSheet.create({
	sectionRing: {
		alignItems: 'center',
		justifyContent: 'center',
	},
	sectionThumb: {
		alignItems: 'center',
justifyContent: 'center',
		position: 'absolute',
		backgroundColor: '#fff',
		shadowOffset: { width: 2, height: 2 },
		shadowColor: '#000',
		shadowOpacity: 0.5,
		elevation: 2,
		transform: [
		{ translateX: 0, },
		{ translateY: 0, },
		],
	},
	});

运行以下命令。
cd MyLampApp
tuya-panel-cli package ./
打包前会对项目的合法性进行一次校验,校验通过才会开始构建。
开始打包时,tuya-panel-cli 会将所选择的项目打包到临时目录,成功后,会自动打开该临时目录。
打包会生成 3 个 UI 包, 打出三个包名示例如下:
{name}-android*{rnVersion}*{version}.tar.gz     - 安卓面板 UI 包
{name}-ios*{rnVersion}*{version}.tar.gz         - 苹果面板 UI 包
{name}-sources.tar.gz           - 源码包,不需要进行任何操作
说明:打包过程如果遇到错误,请修复工程源码错误后再继续构建。或者求助
developer@tuya.com(请在邮件中附上企业名称和联系方式)。
上传对应平台的 UI 包。
生成体验二维码。
您可以通过设置面板测试账号按钮将涂鸦智能 App 的用户账号设置为体验账号。这样,该用户即可通过扫描体验二维码快速体验 UI 资源包。
发布上线。
体验无误后,点击发布上线功能,您即可将该 UI 包发布上线。上线后,在页面下方会生成最新一条的 UI 资源包上线记录,便于您实时体验线上版本。

该内容对您有帮助吗?
是我要提建议