#
引言智汀家庭云Web版 是一款结合智慧中心(SA)的Web客户端,支持用户在SA所在局域网环境下实现智能家居的设备控制等功能。
#
1. 开发前准备项目运行环境
要运行智汀家庭云,你必须要在本地搭建好以下环境
- 安装nodejs的开发环境(node安装参考教程)
- 下载一个集成开发工具,一般这里推荐vscode,直接去官网下载安装即可
技能准备
以下是主要涉及的技术,为了你能顺利的进行开发,你必须先熟悉他们
项目获取
当你的环境安装好了,还有所需的技能已经掌握,那么现在你可以从源码开源网站获取我们的项目。
Git Hub
名称 URL 描述 smartassistant-vue https://github.com/zhiting-tech/smartassistant-vue Web源码 gitee
名称 URL 描述 smartassistant-vue https://gitee.net/zhiting-tech/smartassistant-vue Web源码
安装依赖
项目获取成功后,直接用vscode打开项目
如图:
然后用快捷键 “ctrl + ~” 打开命令窗口
然后我们可以在窗口输入以下命令安装项目所需的依赖
npm install 或 yarn install
如图:
如果你不是用vscode打开
那么你可以用你操作系统的命令窗口进入到该项目根目录
执行上面命令一样可以安装项目依赖
如图:
启动项目
等你项目依赖安装完后,你可以执行以下命令启动项目
npm run serve
如图:
#
2. 快速开始#
2.1 开发工具#
2.2 源码地址Git Hub
名称 URL 描述 smartassistant-vue https://github.com/zhiting-tech/smartassistant-vue Web源码 gitee
名称 URL 描述 smartassistant-vue https://gitee.net/zhiting-tech/smartassistant-vue Web源码
#
2.3 构建版本- 首先我们通过git远程拉取项目
- 项目拉取成功后,进入到项目的根目录,安装项目依赖
- 安装好了依赖,我们就可以启动项目了
那现在怎么让我们的项目整个流程走通,我们需要启动我们的后端项目
依赖后端Golang项目
有了后端接口,我们如何能让我们的项目调用到我们自己的接口?
修改我们接口的请求地址
在我们项目的根目录下找到vue.config.js这个配置文件,打开我们的vue.config.js
我们只需要把tartget改成接口的地址,我们就可以调用接口的数据了
更多配置请查看传送门
新增账号
为了使我们更流畅的体验项目,我们需要一个测试账号
一般在本地新增账号有两种方式:
- 手动往数据库里添加一个
- 下载智汀app,连接sa后设置一个账号密码
体验专业版
账号登录成功后,进入智慧家庭云,体验Web端专业版
#
3. 开发指南#
3.1 项目架构这里主要讲下项目的结构和技术架构,方便我们快速开始开发
#
3.1.1 项目技术架构vuejs特性
- vuejs体积小
- 学习成本低
- 能快速提交开发效率
- vuejs的生态完善
webpack特性
- 模块化
- 按需加载
- 丰富的插件
- 丰富的配置
vant特性
- 提供 60 多个高质量组件,覆盖移动端各类场景
- 性能极佳,组件平均体积不到 1kb(min+gzip)
- 单元测试覆盖率 90%+,提供稳定性保障
- 完善的中英文文档和示例
- 支持 Vue 2 & Vue 3
- 支持按需引入
- 支持主题定制
- 支持国际化
- 支持 TypeScript
- 支持 SSR
#
3.1.2 项目结构│ .browserslistrc │ .editorconfig│ .eslintrc.js // eslint配置文件│ .gitignore // git提交忽略配置│ babel.config.js // babel配置文件│ package-lock.json // 依赖缓存文件│ package.json // package配置文件│ postcss.config.js // postcss独立配置文件│ README.md // 说明文件│ vue.config.js // webpack相关配置文件├──public // 静态资源文件├──plugins // 插件集合└─src ├─api │ ├──http.js // api请求文件,每个api请求都在这里 │ ├──instance.js // api请求总入口,在这里可以做请求的统一处理 ├─assets // 资源文件夹 │ ├─lang // 多语言文件夹 │ ├─bus // 全局vue bus │ ├─components // 通用组件库 │ ├─router.js // 路由文件 │ ├─store.js // store相关 │ ├─App.vue // 程序入口vue文件 │ ├─main.js // 程序入口文件 │ ├─utils // 相关工具的方法集合 │ └─views // 页面文件
#
3.1.3 示例:hello word怎样添加一个hello word的页面?
- 步骤1: 在views文件夹新建一个hello-world的文件夹,然后在hello-world文件夹下新建一个index.vue的文件,index.vue的文件内容为
// 模板模块<template><div>hello word</div></template>// 逻辑模块<script></script>// 样式模块<style scoped></styple>
- 步骤2: 建完页面文件后,我们需要在router.js路由文件配置hello-world页面的路由
// 引入页面文件import HelloWorld from './views/hello-world/index.vue'
// 配置路由文件export default new Router({ mode: 'hash', base: process.env.BASE_URL, routes: [ { path: '/hello-world', name: 'device', component: HelloWorld } ]})
- 步骤3:我想在hello-world页面上添加一张图片怎么办?我们可以把我们的图片资源丢到assets目录下,然后用相对路径引入就可以了
例:要引入名为hello.png的图片
<template> <div> <img src=".././img/hello.png" /> <p>hello word</p> </div></template>
- 步骤4:我要用公用组件库的组件怎么办?我们可以在components目录下引入要使用的组件
例:
// 模板模块<template> <div> <HelloComponent/> <img src=".././img/hello.png" /> <p>hello word</p> </div></template>// 引入组件<script>import HelloComponent from '@/components/HelloComponent.vue'export default { // 在页面注册组件 compoents: { HelloComponent }}</script>
- 步骤5:如果我们需要接口数据,我们可以在api文件夹下的http.js增加我们的接口
/*** 请求列子*/export const example = params => http.g( `${apiHeader}/examle`, params)
- 步骤6:在浏览器输入<localhost:8080/#/hello-world>就可以看到我们的hello页面了
#
3.2 功能结构智汀智能生态,从功能结构上来说,包括家庭/公司、房间/区域管理,成员、角色权限管理,插件管理、设备管理以及设备场景等。
如下图:
其中,对于Web端专业版,核心在于设备的通讯、发现、控制,用户角色权限控制以及插件包的开发,接下来我们将详细进行介绍。
#
3.3 业务功能:设备通讯对于智汀家庭云Web版而已,与设备的通讯都离不开智慧中心的通讯。在这里,我们采用Websocket技术,以Websocket建立长链接进行通讯信息接收传输。
Websocket 介绍
Websocket是一种在单个TCP连接上进行全双工通信的协议,Websocket API也被W3C定为标准,Websocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。
Websocket的构造函数、常量、属性、方法,事件 ->Websocket
本应用已封装Websocket方法"ws-plugin"上传npm 官网,可在官网搜索 “ws-plugin”查看调用方法
- “ws-plugin” 的安装
npm i ws-plugin --save
- “ws-plugin” 的页面引入
import Socket from 'ws-plugin'const ws = new Socket({ url: null, // 链接的通道的地址 heartTime: 60 * 1000 * 3, // 心跳时间间隔 heartMsg: 'ping', // 心跳信息,默认为'ping' isRestroy: false, // 是否销毁 reconnectCount: 10, // 自动重连次数,-1为无限次重连 reconnectTime: 5000, // 自动重连时间间隔,单位毫秒,默认5秒 onOpen: null, // 连接成功的回调 onClose: null, // 关闭的回调 onMessage: null, // 消息的回调 onError: null // 错误的回调})
// 方法调用// 已建立通讯ws.onOpen(() => { // do something here})
// 接收通讯信息ws.onMessage((data) => { // do something here})
- 相关调用方法:
方法名称 | 方法说明 | 参数 |
---|---|---|
onOpen | 连接成功的回调函数 | 传一个回调函数 |
onClose | 关闭 ws 连接 | 传一个回调函数 |
onMessage | 接受信息回调 | 传一个回调函数 |
onError | 连接错误回调 | 传一个回调函数 |
reconnectTime | 重连时间 | 默认值 5 * 1000(单位:毫秒) |
完成通讯,接下来我就可以获取我们需要的信息对设备进行一些操作了,很nice!!
#
3.4 业务功能:设备发现此时,上面设备通讯提到的Websocket通讯就派上用场了。
我们先回忆一下,设备发现的流程:
#
3.4.1 启动设备发现智汀智慧首页点击添加设备进入到设备扫描发现页:
设备发现中...
首先根据后端需要发一个Websocket请求一定参数到服务,此时页面UI执行动画扫面展示效果,等待数据返回结果做处理
#
3.4.2 设备发现结束后接收到数据结果信息后根据是否成功展示反馈结果:
1)成功有其他设备存在
则如图:
2)失败或者不存设备
则如图:
#
3.4.3 实现原理代码在全局vuex已存Websocket方法
import { mapGetters } from 'vuex'
export default { data() { return { isScan: true, discoverList: [], msgId: 1, timer: null // 未发现显示定时器 } }, computed: { ...mapGetters(['websocket']) }, mounted() { this.scanDevice() }, methods: { // 扫描方法 scanDevice() { this.isScan = true // 发送发现指令 this.msgId = Date.now() this.websocket.send({ domain: 'yeelight', id: this.msgId, service: 'discover' }) // 接受消息 this.websocket.onmessage((data) => { const msg = JSON.parse(data) if (msg.id === this.msgId) { if (!msg.result) { return } const { device } = msg.result const isExist = this.discoverList.find(item => item.identity === device.identity) if (!isExist) { clearTimeout(this.timer) this.discoverList.push(device) } } }) // 未发现设置显示 this.timer = setTimeout(() => { this.isScan = false }, 5000) } }}
#
3.5 业务功能:设备控制主要就是对设备一些操作控制比如:开启/关闭、光的亮度、音量、温度…等等调节操作机制。
设备控制页是一个独立单应用管理(独立插件包),插件开发下面将有单独介绍
设备控制页的展示效果:
设备控制同样是走Websocket通讯控制,根据不同类型设备传相对应参数获取当前控制状态
主要的实现原理:
- 开关
点击传输一个参数类型 “off” 或者 “on” 根据接收的信息启动跟关闭
openLight(status) { if (status) { // 开灯 this.ws.send({ domain: 'yeelight', id: 1, service: 'switch', service_data: { device_id: this.deviceId, power: 'on' // 开启 } }) } else { // 关灯 this.ws.send({ domain: 'yeelight', id: 1, service: 'switch', service_data: { device_id: this.deviceId, power: 'off' // 关闭 } }) } this.isOn = status}
- 调节亮度
拉拽温度进度条高温或者低温模式,向Websocket输送一个温度值,同样根据接收的结果启动亮度值
// 亮度变化lightChange(val) { // 灯关闭不发送指令 if (!this.isOn) { return } // 调节亮度 this.ws.send({ domain: 'yeelight', id: 1, service: 'set_bright', service_data: { device_id: this.deviceId, brightness: val // 亮度值 } })}
- 调节色温
拉拽色温进度条,向Websocket输送一个色温值,同样根据接收的结果启动色温值
// 色温变化temperatureChange(val) { // 灯关闭不发送指令 if (!this.isOn) { return } // 调节色温 this.ws.send({ domain: 'yeelight', id: 1, service: 'set_color_temp', service_data: { device_id: this.deviceId, color_temp: val // 色温值 } })}
#
3.6 业务功能:权限控制权限管理
智能设备的安全性一大部分是依赖权限管理
今天我们来说说智汀家庭云是怎么做权限管理的
首先,我们看下角色管理的页面
高级设置页面
我们看下角色模板接口返回的数据
{ "status": 0, "reason": "成功", "data": { "role": { "permissions": { "device": [{ "permission": { "name": "添加设备", "action": "add", "target": "device", "attribute": "" }, "allow": false }], "device_advanced": { "locations": [{ "name": "小台灯^_^", "permissions": [{ "permission": { "name": "控制开关", "action": "control", "target": "device-3", "attribute": "power" }, "allow": false }] }] }, "area": [{ "permission": { "name": "生成邀请码", "action": "get", "target": "area", "attribute": "invite_code" }, "allow": false }], "location": [{ "permission": { "name": "添加房间/区域", "action": "add", "target": "location", "attribute": "" }, "allow": false }], "role": [{ "permission": { "name": "查看角色列表", "action": "get", "target": "role", "attribute": "" }, "allow": false }], "scene": [{ "permission": { "name": "新增场景", "action": "add", "target": "scene", "attribute": "" }, "allow": false }] }, "is_manager": false } }}
结合页面和模板数据我们可以看到,权限模板接口主要返回分几个大模块
// 权限结构体{ "permission": { "name": "新增场景", "action": "add", "target": "scene", "attribute": "" }, "allow": false}
{ "device": [{...}], // 设备相关控制权限 "area": [{...}], // 家庭/公司相关控制权限 "location": [{...}], // 房间/区域相关控制权限 "scene": [{...}], // 场景相关控制权限 "role": [{...}], // 角色相关控制权限 "device_advanced": {...}, // 设备的单独控制权限}
我们可以看到主要权限分几大块
- 设备
- 家庭
- 场景
- 角色
- 设备操作
权限使用
我们设置好了权限之后,那怎么用这个权限呢?
我们先看下获取权限的接口(/users/userId/permissions)的返回值
{ "status": 0, "reason": "成功", "data": { "permissions": { "add_device": true, // 添加设备 "add_location": true, // 添加房间 "add_role": true, // 添加角色 "add_scene": true, // 添加场景 "control_device": true, // 控制设备 "control_scene": true, // 控制场景 "delete_area_member": true, // 删除家庭成员 "delete_device": true, // 删除设备 "delete_location": true, // 删除房间 "delete_role": true, // 删除角色 "delete_scene": true, // 删除场景 "get_area_invite_code": true, // 生成邀请码 "get_location": true, // 查看房间详情 "get_role": true, // 查看角色列表 "update_area_member_role": true, // 修改成员角色 "update_area_name": true, // 修改家庭名称 "update_device": true, // 更新设备 "update_location_name": true, // 修改房间名称 "update_location_order": true, // 调整房间顺序 "update_role": true, // 更新角色 "update_scene": true // 修改场景 } }}
我们初始进入到页面的时候就要去调用该接口
然后把权限的结果保存在一个全局的变量permission中
进入到页面就可以根据权限做不同的页面显示和操作控制
#
3.7 扩展开发:插件开发新建插件包
我们的插件包都是放在plugins文件夹下的
如果你需要新开发一个插件包,你可以在plugins文件夹下新建一个命为xxx的项目,
目录结构如下
├──plugins ├─yeelight // yeelight插件包 ├─assets // 资源文件夹 │ ├─components // 通用组件库 │ ├─router.js // 路由文件 │ ├─App.vue // 程序入口vue文件 │ ├─main.js // 程序入口文件 │ └─views // 页面文件
目录结构可以根据插件的复杂程度自行扩展
如何启动插件包项目?
建好插件包目录后,我们可以进到智汀专业版项目的根目录,
然后通过以下命令可以启动插件包项目
// xxx为你插件包的名称,例如启动yeelight插件包// npm run plugin yeelightnpm run plugin xxx
#
3.7.1 前端源码分析1. 我们看下主要的代码
插件包主要功能是实现智能设备的控制,智能设备的控制主要是通过Websocket发送指令去控制的
这里引用了智汀封装好的Websocket插件包ws-plugin
插件控制的相关指令可以点击这里插件模块
created() { // 获取device_id参数 const { search, href } = window.location const params = search ? this.getUrlParams(search) : this.getUrlParams(href) this.deviceId = Number(params.device_id) this.lightType = params.model || 'ceiling17' this.token = params.token if (params.name) { this.deviceName = params.name } // 生成连接 const self = this // 这里生成一个全局ws对象 用户和后端进行通讯 this.ws = new Socket({ url: `ws://192.168.0.84:8088/ws?token=${this.token}`, onOpen() { self.initData() }, onMessage(data) { self.handleMessage(data) } })}
这里有两个重要的变量,这两个参数都是从插件链接传进来的
- token 用户的身份凭证,这里主要用来做权限控制
- device_id 设备id,就是发送控制指令时需要知道是控制哪个设备
进入页面时我们要建立一个Websocket长连接,用来发送操作指令和同步更新设备状态
2. 如何做权限控制?
进入插件时我们要做好权限控制,无权限用户无法在插件页面操作。
当我们Websocket链接成功后,我们需要发一个指令去拿用户的操作权限。
指令如下:
this.ws.send({ domain: 'plugin', id: this.perId, service: 'get_actions', service_data: { device_id: this.deviceId }})
发送指令后,后端会返回用户的权限信息,结构如下
{ id: 21620636295485, // 消息di result: { actions: { set_bright: { // 操作权限名称 cmd: 'set_bright', name: '调节亮度', is_permit: true // 是否有权限 true false }, set_color_temp: { // 操作权限名称 cmd: 'set_color_temp', name: '调节色温', is_permit: true // 是否有权限 true false }, switch: { // 操作权限名称 cmd: 'switch', name: '开关', is_permit: true // 是否有权限 true false } } }, success: true }
然后拿到权限信息后就可以在页面做对应的操作
3. 如何控制设备?
我们也是通过发送指令的方法控制设备
例如:打开灯
// 开灯 deviceId就是设备id,从插件链接获得this.ws.send({ domain: 'yeelight', id: 1, service: 'switch', service_data: { device_id: this.deviceId, power: 'on' }})
其他操作也一样,只是操作的指令有所区别
4. 如何同步设备状态?
每个设备都有初始状态,我们怎么同步设备的初始状态呢
同样的也是发送指令
this.ws.send({ domain: 'yeelight', id: this.stateId, type: 'call_service', service: 'state', service_data: { device_id: this.deviceId }})
后端送到指令后会返回设备的初始状态,结构如下:
// 初始化数据返回结构体{ id: 8384959175, // 消息id result: { state: { brightness: 0, // 灯亮度 color_temp: 0, // 灯色温 is_online: false, // 设备是否在线 true false power: '' // 灯的开关状态 on 开 off 关 } }, success: true}
拿到设备后,我们就可以对设备进行初始化值
5. 如何发布我们的插件?
当我们的插件包开发完成后,就打包编译我们的前端和后端文件, 填写我们的配置文件 结构如下:
然后在智汀家庭云上传我们的插件包,等审核通过后就可以看到我们的插件包了。
#
3.7.2 插件包集成打包我们的插件包
// xxx为你插件包的名称,例如打包yeelight插件包// npm run plugin yeelight buildnpm run plugin xxx build
打包后的文件在根目录下plugin文件下
插件的后端文件也要编译好
最终插件包的目录如下:
- html文件夹是插件的页面文件,也就是(html,js,css等静态资源文件)
- yeelight-plugin是插件后端的编译文件
- config.yaml是插件的配置文件
我们看下插件的配置文件包括哪些信息
这些信息都是很重要的,不能遗漏。
我们的插件可以包含多个设备,每个设备有所属的品牌。
智汀用户通过添加安装我们的插件包就可以通过智汀app发现我们插件支持的设备,连接设备,操作我们的设备。
#
3.8 扩展开发:jsBridge使用#
3.8.1 概述智汀Web端专业版作为做智汀APP的辅助程序,所以免不了要跟APP那边做交互
jsBridge就是我们交互的桥梁,通过jsBridge我们可以很顺畅的跟APP那边进行交互
#
3.8.2 webview ua改造概述:为了区别于在APP内嵌入h5,加入特有的ua,用于判断是否在app内嵌入,在原有的ua上加上字符串“zhitingua”
我们可以通过判断ua就可以判断出我们的智汀专业版是否是在app内打开
/** * 判断h5是否在app内打开*/isApp() { const ua = navigator.userAgent if (ua.includes('zhitingua')) { return true } return false}
#
3.8.3 JS-SDK文档APP直接在Webview内注入了一个zhiting的全局对象,并实现了invoke方法
我们只要调用此方法并传入相应的参数就可以调用app内提供的方法
#
3.8.3.1 查看app连网类型// res为回调结果,类型为json数据格式 zhiting.invoke('networkType', {}, (res) => { // do something // type为连网类型,可选值有 2g 3g 4g 5g wifi 空 // type为空时表示无网 var type = res.type})
#
3.8.3.2 设置标题属性// res为回调结果,类型为json数据格式 zhiting.invoke('setTitle',{ title: '标题名称', // 标题名称 color: '#333333', // 标题颜色,rgb值 background: '#ffffff', // 标题栏背景色,rgb值 isShow: true // 是否显示标题栏,默认显示})
#
3.8.3.3 获取用户信息// res为回调结果,类型为json数据格式 zhiting.invoke('getUserInfo', {}, (res) => { // do something // token为app保存的登录凭证 // userId为当前用户的id var userId = res.userId var token = res.token})
#
3.8.3.4 是否是专业版APP// res为回调结果,类型为json数据格式 zhiting.invoke('isProfession', {}, (res) => { // result为是否专业版判断结果 true false var isProfession = res.result})
#
3.9 部署应用部署应用是指编译或构建代码并将生成的 JavaScript、CSS 和 HTML 托管到 Web 服务器上的过程。
构建和托管你的应用
我们的项目开发完后,怎么发布,首先要构建我们的项目
npm run build
在根目录执行该命令后,项目的构建文件会输出到dist文件夹
我们下一步就是把我们的dist文件夹托管在服务器上,配置好请求转发和域名
至此,我们的应用已经部署成功了