本文档面向已经了解 面板小程序开发
的开发者,你需要充分的了解什么是面板小程序
产品功能
若您对上述概念有所疑问,我们推荐你去看一些预备知识。如果您已了解,可跳过本文。
面板作为 IoT 智能设备在 App 终端上的产品形态,创建产品之前,首先来了解一下什么是面板,以及和产品、设备之间的关系。
面板
是运行在 智能生活 App
、OEM App(涂鸦定制 App)
上的界面交互程序,用于控制 智能设备
的运行,展示 智能设备
实时状态。产品
将 面板
与 智能设备
联系起来,产品描述了其具备的功能、在 App 上面板显示的名称、智能设备拥有的功能点等。智能设备
是搭载了 涂鸦智能模组
的设备,通常在设备上都会贴有一张二维码,使用 智能生活 App
扫描二维码,即可在 App
中获取并安装该设备的控制 面板
。产品
、面板
、设备
之间的关系可参考下图。产品名称:通用 wifi 门锁
首页
进行展示安全守护天数、门锁状态显示。Wi-Fi 门锁指通过 Wi-Fi模组
实现联网控制的智能门锁。具体功能支持如下
功能 |
说明 |
全链路通信加密 |
App——服务器——Wi-Fi 模组,通信链路全部加密,杜绝通信安全风险 |
App 远程开门 |
门锁请求后,App 响应,可以同意开门或拒绝开门 |
门锁状态显示 |
App 上可参看门锁的反锁、童锁等状态信息 |
电量状态显示 |
电量值或电量状态 |
开门记录 |
解锁方式+解锁 id+时间,用户可以通过 App 查询近 12 个月的开门记录,不会丢失数据。支持指纹+密码这样的组合开门 |
告警记录 |
低电报警、密码错误、指纹错误、房卡错误、防撬、高温等 |
用户管理 |
通过 App 添加用户,与解锁 id 通过 App 手动关联 |
临时密码管理 |
最多支持 10 组临时密码,支持创建和删除,临时密码创建后,自动复制,可以在微信、短信中粘贴,临时密码短信支持海外的手机号,可以选择不同的国家码 |
详细可参考Wi-Fi 门锁接入方案
访客在门锁硬件上输入特定的开门密码(厂家自行决定触发方式,可以是指定的物理按钮,或者特殊的开门码,例如 888#)触发门锁远程开门功能,主人能在特定时间内,通过 APP 对该开门请求进行响应,可以同意开门,或拒绝开门
当前门锁模板必须的功能点:
lock_motor_state,
unlock_app,
remote_no_pd_setkey,
remote_no_dp_key,
参数 |
取值 |
dpid |
55 |
code |
lock_motor_state |
type |
布尔型(Bool) |
mode |
可下发可上报(rw) |
参数 |
取值 |
dpid |
15 |
code |
unlock_app |
type |
数值型(Value) |
mode |
只上报(ro) |
function |
当发生 app 远程开门的事件后,门锁上报远程开门的记录。使用记录型数据上报 |
参数 |
取值 |
dpid |
49 |
code |
remote_no_pd_setkey |
type |
透传型(Raw) |
mode |
可下发可上报(rw) |
参数 |
取值 |
dpid |
50 |
code |
remote_no_dp_key |
type |
数值型(Value) |
type |
透传型(Raw) |
mode |
可下发可上报(rw) |
上面为首页
需求必须的功能点
,其他功能点这里不做介绍。
首先需要创建一个门锁类产品,定义产品有哪些功能点,然后面板中再根据这些功能点一一实现。
进入IoT 平台,点击左侧产品菜单,产品开发,创建产品,选择标准类目 -> 智能锁 -> 家用门锁PRO
:
选择功能点,这里我们只需要默认的标准功能即可。
🎉 在这一步,我们创建了一个名为 PanelLock
的家用门锁产品。
这部分我们在 小程序开发者
平台上进行操作,注册登录 小程序开发者平台。
IDE 中点击新建,选择门锁模板。
导入 IDE,并关联到已经创建的面板小程序与产品。
上面的步骤我们已经初始化好了一个面板小程序
的开发模板,下面我们介绍下工程目录。
├── src
│ ├── api // 云端API
│ ├── components
│ │ ├── LatestLog // 最近一条日志组件
│ │ ├── RemoteOpenDoor // 远程开门组件
│ │ ├── StatusBar // 门锁状态栏
│ │ ├── connect.tsx
│ │ └── index.ts
│ ├── config // 常用配置
│ ├── constant // 放置常用变量
│ ├── i18n // 多语言
│ ├── hooks // 常用hooks
│ ├── models // redux
│ │ ├── combine.ts // combineReducers
│ │ ├── configureStore.ts
│ │ ├── index.ts
│ │ ├── store.ts
│ │ └── modules
│ │ ├── common.ts // 基础 action 及 reducer
│ ├── pages
│ │ ├── home // 首页
│ │ ├── ...
│ ├── res // 本地图片
│ ├── utils // 常用工具方法
│ ├── app.config.ts
│ ├── app.tsx
│ ├── composeLayout.tsx // 处理监听子设备添加,解绑,DP变化等
│ ├── global.config.ts
│ ├── routes.config.ts // 配置路由
首页
通过操作 App 进行远程开门分析需求:
RemoteOpenDoor
。lock_motor_state
获取门锁开启、关闭状态,考虑到门锁设备离线状态,共计三种状态。remote_no_dp_key
dp 值获取开锁成功、失败状态相关代码:
import React, { useMemo, useState, useRef, useEffect } from
'react';
import {
Text,
View,
Image,
showToast,
hideToast,
onDpDataChange,
offDpDataChange,
showModal,
} from '@ray-js/ray';
import { useStore } from 'react-redux';
import RotateImage from '@ray/rotate-image';
import Res from '@/res';
import Strings from '@/i18n';
import { dpCodes } from '@/config';
import { formatDpChange } from '@/utils';
import { remoteOpen as remoteOpenApi } from '@/api';
import { usePanelStore, useDeviceOnline } from '@/hooks';
import styles from './index.module.less';
const { remoteNoDpKey } = dpCodes;
enum DoorStatus {
open = 'open',
close = 'close',
offline = 'offline',
}
/**
* 开启状态:#F67352
* 关闭状态:#5EAAFF
* 离线状态:#A2A2A2
*/
const statusInfo = {
open: {
bg: Res.showOpenImage,
iconBg: Res.openImage,
text: Strings.getLang('close'),
color: '#F67352',
},
close: {
bg: Res.showImage,
iconBg: Res.lockImage,
text: Strings.getLang('clickOpen'),
color: '#5EAAFF',
},
offline: {
bg: Res.outLineImage,
iconBg: '',
text: Strings.getLang('offline'),
color: '#A2A2A2',
},
};
const RemoteOpenDoor = () => {
const store = useStore();
const { dpState} = usePanelStore();
const deviceOnline = useDeviceOnline();
const { lock_motor_state } = dpState;
const [loading, setLoading] = useState(false);
const timer = useRef(null);
const loadRef = useRef(loading);
const status = useMemo(
() =>
deviceOnline ? (lock_motor_state ? DoorStatus.open : DoorStatus.close) : DoorStatus.offline,
[deviceOnline, lock_motor_state]
);
const text = useMemo(() => {
let _text = statusInfo[status].text;
if (deviceOnline) {
_text = loading
? lock_motor_state
? Strings.getLang('locking')
: Strings.getLang('unlocking')
: statusInfo[status].text;
}
return _text;
}, [deviceOnline, lock_motor_state, loading, status]);
useEffect(() => {
onDpDataChange(dpDataChangeHandle);
return () => {
offDpDataChange(dpDataChangeHandle);
};
}, []);
useEffect(() => {
loadRef.current = loading;
}, [loading]);
const dpDataChangeHandle = data => {
const { getState } = store;
const { devInfo: _devInfo } = getState();
const _dpState = formatDpChange(_devInfo.arrSchema, data.dps);
if (Object.keys(_dpState).length > 10) return;
const remoteOpenResult = _dpState[remoteNoDpKey];
if (remoteOpenResult) {
setLoading(false);
clearTimer();
const result = remoteOpenResult.slice(0, 2);
if (result === '00') {
showToast({ title: Strings.getLang('openSuccess'), icon: 'success' });
} else {
showToast({ title: Strings.getLang('openError'), icon: 'error' });
}
}
};
const remoteOpen = async () => {
if (loading) return;
if (!deviceOnline) {
showModal({
title: Strings.getLang('offline'),
content: Strings.getLang('outLineTip'),
confirmText: Strings.getLang('confirm'),
showCancel: false,
});
return;
}
setLoading(true);
try {
await remoteOpenApi(!lock_motor_state);
} catch (e) {
setLoading(false);
clearTimer();
showToast({ title: e?.innerError?.errorMsg, icon: 'error' });
}
setTimer();
};
const setTimer = () => {
timer.current = setTimeout(() => {
if (loadRef.current) {
showToast({
title: Strings.getLang('outLineTip'),
icon: 'none',
});
} else {
hideToast();
}
setLoading(false);
}, 15000);
};
const clearTimer = () => {
timer.current && clearTimeout(timer.current);
};
return (
<View className={styles.container}>
{status !== DoorStatus.offline && (
<RotateImage active={loading} rotateImage={statusInfo[status].bg} onLongClick={() => remoteOpen()}>
<View className={styles.centerView}>
{!!(statusInfo[status].iconBg) && (
<Image src={statusInfo[status].iconBg} className={styles.centerImage} />
)}
<Text
className={styles.lockText}
style={{ color: statusInfo[status].color, marginTop: deviceOnline ? '0' : '160rpx' }}
>
{text}
</Text>
</View>
</RotateImage>
)}
{status === DoorStatus.offline && (
<View className={styles.outLineWrap}>
<Image src={statusInfo[status].bg} className={styles.outLineBg} />
<Text className={styles.outLineText} style={{ color: statusInfo[status].color }}>
{text}
</Text>
</View>
)}
</View>
);
};
export default RemoteOpenDoor;
引入了代码之后,我们的首页有了一个可长按点击的圆形按钮,这时我们可以修改 IDE 右侧控制面板 -> lock_motor_state
-> false/true 进行虚拟面板 dp
点的下发与上报,查看 IDE 控制面板的状态来验证我们的产品功能