#
引言智汀家庭云(SmartAssistant),立项于2021年,结合国内智能家居各厂商软件特点,研发“智汀家庭云”,并对该生态系统全面开源,为国内首个采用智能家居系统全生态开源协议(Apache License, Version 2.0)的软件。
核心功能
- 局域网内智能设备的发现,管理与场景互动
- 开放插件接口,并且提供插件开发SDK,方便第三方设备接入
- 智汀家庭云提供PC版、IOS版、安卓版的终端
- 通过绑定到智汀云帐号,提供外网控制的功能
#
1. 快速入门如果您机器上安装有 Docker 与 docker-compose 环境,可按照 使用 Docker 运行智汀家庭云的步骤体验智汀家庭云的基本功能。
您也可以使用虚拟机运行智汀家庭云,可按照 使用虚拟机运行智汀家庭云的步骤体验智汀家庭云的基本功能。
智汀家庭云是一个开源项目,如果您熟悉 go 编程语言,想参与到项目的开发中,可以访问 开发环境搭建 。
智汀家庭云提供插件系统支持第三方设备接入,如果您的设备不在我们的支持列表,可以参考 开发您的第一个插件了解插件开发相关内容。
#
1.1 使用 Docker 运行智汀家庭云本文档描述如何在docker上运行智汀家庭云(以下简称SA),并使用浏览器体验SA。
#
1.1.1 环境准备- linux主机
- docker
- docker-compose
#
1.1.2 运行SA0
#
1.1.2.1 自行创建docker-compose文件运行选择一个目录存放SA所需文件,执行以下命令:
wget -O smartassistant.zip https://github.com/zhiting-tech/smartassistant/releases/download/v2.0.2/smartassistant.2.0.2.zip && unzip smartassistant.zip
cd zt-smartassistant
docker-compose up
备注:
- 您可以通过最新版本的发布地址:https://github.com/zhiting-tech/smartassistant/releases,获取最新版本zip文件
#
1.1.3 测试运行情况服务启动后可以通过以下命令检查SA的状态:
curl http://localhost:9020/api/check
如果返回以下内容则说明服务已运行起来并且未被绑定
{"status":0,"reason":"成功","data":{"is_bind":false,"revision":""}}
#
1.1.4 使用智汀App进行体验#
1.1.4.1 SA体验演示- 点击添加智能设备发现SA
- 选中SA,点击添加
- 点击支持品牌,界面中会列出SA内置的一些插件
查询支持品牌
- 点击其中的一个插件,点击添加对插件进行安装
- 插件添加成功后,返回到首页家居,点击右上角+按钮就可以添加插件支持的设备
- 选中其中的一个设备点击添加,添加完成后返回到首页的家居即可对设备进行操控
#
1.1.5 使用专业版进行体验#
1.1.5.1 账号和密码设置SA服务启动后,如果未绑定,需要发送以下请求对SA进行绑定:
curl -X POST -d '{"device": {"model": "smart_assistant"}}' http://localhost:37965/api/devices
绑定成功后返回以下内容:
{ "status": 0, "reason": "成功", "data": { "device_id": 1, "plugin_url": "", "user_info": { "user_id": 1, "role_infos": null, "account_name": "", "nickname": "wyqicai", "token": "MTYzNTMxOTE1M3xOd3dBTkVoRE5WRXpTME5CV0VrMFVFOVFWa0pJUjA1UFNVRklRMEUxUkZaWVFsRkNWVWhUVWxWRFEwNUVTek5QU0VwVlJVVlJSMEU9fNXschLMFQtEogQo2AlJu4wfSJfLrsk994FwSGUhp-_3", "phone": "", "is_set_password": false }, "area_info": { "id": "20763937284831135" } }}
SA绑定成功后设置用户名和密码:
curl -X PUT -d '{"account_name": "admin","password": "123456"}' --header 'smart-assistant-token:MTYzNTMxOTE1M3xOd3dBTkVoRE5WRXpTME5CV0VrMFVFOVFWa0pJUjA1UFNVRklRMEUxUkZaWVFsRkNWVWhUVWxWRFEwNUVTek5QU0VwVlJVVlJSMEU9fNXschLMFQtEogQo2AlJu4wfSJfLrsk994FwSGUhp-_3' http://localhost:37965/api/users/1
其中header参数smart-assistant-token值为绑定成功后的token, 请求url path(/api/users/:id)中的路径参数id为绑定成功后返回的用户id。
设置成功后返回:
{"status":0,"reason":"成功"}
当用户账号和密码设置成功后打开浏览器,访问 http://localhost:9020 即可体验SA了。
#
1.1.5.2 SA体验演示- 使用账号密码登录
- 登录成功后,切换到我的
- 点击支持品牌,界面中会列出SA内置的一些插件
- 点击其中的一个插件对插件进行安装
- 插件添加成功后,返回到首页家居,点击右上角+按钮就可以添加插件支持的设备
- 选中其中的一个设备点击添加,添加完成后返回到首页的家居即可对设备进行操控
#
1.1.6 进一步了解如果您手上有智汀家庭云支持的硬件设备,可以安装第三方插件,然后通过智汀APP接入您的设备。
智汀家庭云是一个开源项目,如果如果您熟悉 go 编程语言,想进一步了解我们的项目,可以访问开发环境搭建
智汀家庭云提供插件系统支持第三方设备接入,如果您的设备不在我们的支持列表,可以参考 开发您的第一个插件了解插件开发相关内容。
#
1.2 使用虚拟机运行智汀家庭云本文档描述如何通过虚拟机体验运行智汀家庭云的基础功能,包括:
- 使用 virtualbox 运行智汀家庭云
- 通过智汀APP添加并初始化智汀家庭云
- 安装设备插件
- 添加设备,控制设备
#
1.2.1 环境准备本文档的所有操作均基于虚拟环境运行,可运行于 Windows,MacOS或者Linux上。
#
1.2.1.1 安装 virtualboxVirtualBox 是一个免费的虚拟机软件,可通过官方网站下载安装。
#
1.2.1.2 安装智汀APP智汀家庭云APP是智汀生态智能硬件管理平台,可以发现、连接和管理智能硬件设备,实现智能设备之间的互联互通、自动化控制、语音控制。
#
1.2.2 使用 virtualbox 运行智汀家庭云智汀官方提供一个基于 OpenWRT 的智汀家庭云虚拟机镜像,可通过 虚拟机下载地址 进行下载。
备注:
- 您可以通过最新版本的发布地址:https://github.com/zhiting-tech/smartassistant/releases,获取最新版本ova文件
运行 virtualbox 软件,点击“管理”菜单,选择“导入虚拟电脑”,点选上面下载的 smartassistant.2.0.2.ova 文件,点击“下一步”
在下一个对话框界面中点击“导入”,稍等片刻即可导入成功
然后在管理器界面中会增加一个名为“smartassistant”的虚拟机,选中,然后点击”启动“。
虚拟机启动后可能会提示“物理网卡未找到”,点击“更改网络设置”,配置主机网卡。
虚拟机启动后会自动通过DHCP协议请求获取IP,接入家庭局域网,然后启动智汀家庭云。
#
1.2.3 通过智汀APP添加并初始化智汀家庭云首先确保手机已连接WIFI,并且跟电脑是同一个局域网,然后启动智汀APP。
点击“添加智能设备”,智汀APP将会扫描局域网内的智汀家庭云设备,包括上面启动的虚拟机系统。
- 点击添加智能设备发现SA
- 选中SA,点击添加
- 点击支持品牌,界面中会列出SA内置的一些插件
- 点击支持品牌,界面中会列出SA内置的一些插件
- 点击其中的一个插件对插件进行安装
- 插件添加成功后,返回到首页家居,点击右上角+按钮就可以添加插件支持的设备
- 选中其中的一个设备点击添加,添加完成后返回到首页的家居即可对设备进行操控
查询支持品牌
- 点击其中的一个插件,点击添加对插件进行安装
- 插件添加成功后,返回到首页家居,点击右上角+按钮就可以添加插件支持的设备
- 选中其中的一个设备点击添加,添加完成后返回到首页的家居即可对设备进行操控
#
1.2.4 回顾恭喜您,已完成一次最简单的智汀家庭云体验之旅,通过上面的操作,您已经可以让智汀家庭云以虚拟机的形式在您的电脑上运行起来,并且通过安装demo插件,体验智汀家庭云设备添加与操作流程。
接下来,您还可以:
- 购买智能家居硬件(灯,开关),接入智汀家庭云
- 购买智汀家庭云主机,摆脱虚拟机的限制,让您的智能家居7x24稳定运行
- 体验场景任务功能,让家居真正智能起来
#
1.3 开发环境搭建此文档描述如何搭建智汀家庭云开发环境,下载,编译与运行。如果你只是想体验智汀家庭云的功能,可以先阅读 使用 Docker 运行智汀家庭云或者使用虚拟机运行智汀家庭云的步骤体验智汀家庭云的基本功能。;如果你是想进行插件开发,可参考 开发您的第一个插件。
#
1.3.1 环境准备- go 版本为 1.15.0 或以上
- 确保能生成 gRPC 代码,请参考 gRPC Quick start
- docker 与 docker-compose, 如果需要 smartassistant 与插件进行交互,则需要安装此依赖
#
1.3.2 步骤获取代码
git clone https://github.com/zhiting-tech/smartassistant.git
在项目根目录执行以下命令同步依赖
go mod tidy
执行以下命令,创建配置文件目录并复制示例配置文件到配置文件目录
mkdir -p /mnt/data/zt-smartassistant/configmkdir -p /mnt/data/zt-smartassistant/data/smartassistantcp ./app.yaml.example /mnt/data/zt-smartassistant/config/smartassistant.yaml
编译运行
go run ./cmd/smartassistant/main.go
如果已安装 docker 与 docker-compose,则可以通过以下命令进行打包与运行
make buildmake run
然后可以访问以下地址确认服务是否正常运行
curl http://localhost:37965/api/check
正常会返回
{"status":0,"reason":"成功","data":{"is_bind":false,"revision":""}}
#
1.3.3 Mac系统下开发环境搭建#
1.3.3.1 环境准备- 系统已经安装etcd。
- go 1.16。
- Goland IDE。
- Postman。
#
1.3.3.2 步骤克隆项目
git clone http://gitlab.yctc.tech/zhiting/smartassistant.git
使用Goland运行项目
使用Goland打开项目,配置GOPROXY。
GOPROXY=https://goproxy.cn,direct
运行smartassistant
拷贝项目下的app.yaml.example到项目根目录,并重命名为app.yaml。
cp app.yaml.example app.yaml
打开终端创建程序数据存储目录。
mkdir -p data/zt-smartassistant/data/smartassistant
修改配置文件app.yaml中的runtime_path和host_runtime_path,将这个参数改为数据存储目录的绝对路径。
启动Etcd。
修改项目下modules/plugin/discovery.go文件中的etcd服务器地址。
在ide中打开cmd/smartassistant/main.go文件,编辑运行配置。
其中,Program arguments配置使用-c=app.yaml来指定程序的配置文件。配置完成用点击运行即可。 如果成功运行后打开浏览器 http://localhost:37965/api/check 返回以下内容
{"status":0,"reason":"成功","data":{"is_bind":false,"revision":"","version":"latest"}}
说明smartassistant成功运行起来了。
运行plugin-demo
使用ide打开examples/plugin-demo/main.go文件,编辑运行配置。
其中,Environment配置一个LOCAL_IP的环境变量,如果不指定有可能会获取一个ipv6的ip地址导致smartassistant不能发现插件中的设备。配置完成后点击运行即可。
#
1.3.3.4 测试过程根据上述步骤将smartassistant和plugin-demo运行起来后,可以使用postman进行测试。
初始化smartassistant账号
请求方法:POST 请求url: http://localhost:37965/api/devices 请求参数:
{ "device": { "model": "smart_assistant" }}
发现插件中的设备
在Postman中创建WebSocket Request
请求url: http://localhost:37965/ws
请求头添加 smart-assistant-token
,该值为初始化smartassistant返回的token。
连接成功后,发送以下请求
{ "ID": 1, "Service": "discover"}
即可发现插件中的设备。
#
1.3.3.5 常见问题plugin-demo的依赖无法下载?
修改项目下的examples/plugin-demo/go.mod文件 将 github.com/zhiting-tech/smartassistant replace 成../../
replace github.com/zhiting-tech/smartassistant v1.5.0 => ../../
然后在命令行中,cd到plugin-demo目录下执行go mod tidy
smartassistant和plugin-demo都运行起来,但是使用websocket进行设备发现是没有返回数据。
可以使用
etcdctl get --prefix "/sa/plugins"
命令检查插件是否成功注册到etcd中。如果没有数据,可能是etcd不是本地启动的,导致plugin-demo连不上etcd。检查运行plugin-demo的步骤配置LOCAL_IP环境变量。检查运行smartassistant是否修改了modules/plugin/discovery.go文件中的etcd地址。根据快速入门使用docker-compose启动smartassistant无法与本地运行的插件进行通讯?
由于docker-compose启动的时候使用的host网络,但是host网络只能在linux系统使用docker host 网络,可以安装vritual box安装linux虚拟机进行测试。
#
1.4 开发您的第一个插件此文档描述如何开发一个简单插件,面向插件开发者。
开发前先阅读插件设计概要:插件系统设计技术概要
#
1.4.1 插件实现1) 获取sdk
go get github.com/zhiting-tech/smartassistant
2) 定义协议
package plugin
import ( "github.com/zhiting-tech/smartassistant/pkg/plugin/sdk/v2" "github.com/zhiting-tech/smartassistant/pkg/plugin/sdk/v2/definer" "github.com/zhiting-tech/smartassistant/pkg/thingmodel")
// 定义描述协议的结构type ProtocolDevice struct { light *definer.BaseService info *definer.BaseService // 插件所支持的协议 pc protocol}
func NewDevice(pc protocol) sdk.Device { return &ProtocolDevice{ id: pc.GetID(), model: pc.GetModel(), manufacturer: pc.GetManufacturer(), pc: pc, }}
3) 定义协议所需属性和信息
import "github.com/zhiting-tech/smartassistant/pkg/thingmodel"
// 定义属性或协议信息// 通过实现thingmodel.IAttribute的接口,以便sdk调用type OnOff struct { pd *ProtocolDevice}
func (l OnOff) Set(val interface{}) error { pwrState := map[]interface{}{ "pwr": val, } resp, err := l.pd.pc.SetState(pwrState) if err != nil { return err } // 设置属性完成后需要,通知到 smartassistant return l.pd.Switch.Notify(thingmodel.OnOff, val)}
func (l OnOff) Get() (interface{}, error) { resp, err := l.pd.pc.GetState() if err != nil { return nil, err } pwr, ok := resp["pwr"] if !ok { return nil, fmt.Errorf("on off get error is state nil") } return pwr, nil}
type Model struct { pd *ProtocolDevice}
func (l Model) Get() (interface{}, error) { return l.pd.model, nil}
func (l Model) Set(interface{}) error { return nil}
type Manufacturer struct { pd *ProtocolDevice}
func (l Manufacturer) Get() (interface{}, error) { return l.pd.manufacturer, nil}
func (l Manufacturer) Set(interface{}) error { return nil}
type Identify struct { pd *ProtocolDevice}
func (l Identify) Get() (interface{}, error) { return l.pd.id, nil}
func (l Identify) Set(interface{}) error { return nil}
4) 实现协议接口
package plugin
import ( "github.com/zhiting-tech/smartassistant/pkg/plugin/sdk/v2" "github.com/zhiting-tech/smartassistant/pkg/plugin/sdk/v2/definer" "github.com/zhiting-tech/smartassistant/pkg/thingmodel")
// 定义描述协议的结构type ProtocolDevice struct { Switch *definer.BaseService info *definer.BaseService pc protocol // 所描述的协议}
func NewDevice(pc protocol) sdk.Device { return &ProtocolDevice{ id: pc.GetID(), model: pc.GetModel(), manufacturer: pc.GetManufacturer(), pc: pc, }}
func (pd *ProtocolDevice) Info() sdk.DeviceInfo { // 该方法返回设备的主要信息 return sdk.DeviceInfo{ IID: pd.id, Model: pd.model, Manufacturer: pd.manufacturer, }}
func (pd *ProtocolDevice) Define(def *definer.Definer) { // 设置符合该协议设备的属性和相关配置(比如设备id、型号、厂商等,以及设备的属性) // 对每个属性和配置都可以有权限 thingmodel.OnOff.WithPermissions( thingmodel.AttributePermissionWrite, thingmodel.AttributePermissionRead, thingmodel.AttributePermissionNotify, ) pd.Switch = def.Instance(pd.id).NewSwitch() pd.Switch.Enable(thingmodel.OnOff, OnOff{pd})
pd.info = def.Instance(pd.id).NewInfo() pd.info.Enable(thingmodel.Model, Model{pd}) pd.info.Enable(thingmodel.Manufacturer, Manufacturer{pd}) pd.info.Enable(thingmodel.Identify, Identify{pd}) return}
func (pd *ProtocolDevice) Connect() error { // 提供给sdk主动进行设备tcp连接 return nil}
func (pd *ProtocolDevice) Disconnect() error { // 提供给sdk主动断开设备tcp连接 return nil}
func (pd *ProtocolDevice) Online(iid string) bool { // sdk 调用该接口检测设备是否在线 return true}
func Discover(ctx context.Context, devices chan<- sdk.Device) { // 这里需要实现一个发现设备的方法,给sdk调用 discoverer := NewDiscoverer() go discoverer.Run() defer discoverer.Close()
for { select { case d, ok := <-discoverer.C: if !ok { return } l := NewDevice(d) devices <- l } } return}
4) 初始化和运行
package main
import ( "log" "github.com/zhiting-tech/smartassistant/pkg/plugin/sdk/v2")
func main() { p := server.NewPluginServer(Discover) err := sdk.Run(p) if err != nil { log.Panicln(err) }}
#
1.4.2 开发范例demo-plugin (code: examples/plugin-demo) : 通过上文的插件实现教程实现的示例插件;这是一个模拟设备写的一个简单插件服务,不依赖硬件,实现了核心插件的功能
#
1.4.3 镜像编译和部署暂时仅支持以镜像方式安装插件,调试正常后,编译成镜像提供给SA
- Dockerfile示例参考
FROM golang:1.16-alpine as builderRUN apk add build-baseCOPY . /appWORKDIR /appRUN go env -w GOPROXY="goproxy.cn,direct"RUN go build -ldflags="-w -s" -o demo-plugin
FROM alpineWORKDIR /appCOPY --from=builder /app/demo-plugin /app/demo-plugin
# static fileCOPY ./html ./htmlENTRYPOINT ["/app/demo-plugin"]
- 编译镜像
docker build -f your_plugin_Dockerfile -t your_plugin_name
- 运行插件
docker run -net=host your_plugin_name
//注意:-net=host 参数只有linux环境才有用。
- 更多请参考设备类插件
#
2. 开发指南#
2.1 架构概述智汀家庭云,立项于2021年,结合国内智能家居各厂商软件特点,研发“智汀家庭云”,并对该生态系统全面开源,为国内首个采用智能家居系统全生态开源协议(Apache License, Version 2.0)的软件。
#
2.1.1 应用场景智汀家庭云可以离线运行在局域网内,也可以通过绑定到智汀云来获取更强大的功能。
运行在局域网环境时,用户可以通过智汀APP发现与管理智汀家庭云,安装插件来对设备进行管理与控制。
用户也可以通过在智汀云上面注册帐号,将智汀家庭云设备关联到云端,然后通过云端中转的方式支持在外网对设备进行控制。
智汀云同时提供设备直接接入云端虚拟家庭的功能,方便无智汀家庭云设备的用户使用;智汀云也提供虚拟家庭数据迁移到实体家庭的接口。
对于关联到云端的家庭,智汀云提供云对云接入功能,用户可授权小度,天猫精灵,Google Nest 等智能音箱直接对设备进行控制。
在开发过程中需要注意的是,任何情况下,用户的隐私都是最重要的,因此需保证只有在用户授权的情况下,第三方才可访问智汀家庭云;用户也可对第三方的授权进行控制
#
2.1.2 架构概述智汀家庭云运行在 Linux 主机下,通过 Docker 来对其中的服务进行部署与资源隔离。其中部分核心服务容器需要预先配置,并且随系统启动自动运行;而插件(plugin)类服务则是由 SA 调用 docker API 的方式进行管理。
插件启动后会运行一个 gRPC 服务以及一个可选的 HTTP 服务,SA 通过 docker API 监听插件运行状态,通过 gRPC 接口获取插件信息
#
2.1.2 功能模块智汀家庭云模块拆分为 internal 与 pkg 两个分组,其中 internal 为与项目业务逻辑相关性比较强功能模块分组;pkg 包含与业务关系不大的通用组件。其引用关系如下图所示:
其中比较重要的业务模块如下:
#
2.1.3 程序设计的规则参考- 基础模块使用单例模式实例化,但应避免直接使用全局变量,可使用 entity.DB(), pkg.Log() 的形式做一层封装; 汉模式延迟初始化应使用 sync.Once
- 基础模块只依赖其他基础模块,不应涉及业务逻辑
- 简单的业务模块(譬如只依赖基础模块),可直接使用单例模式,或者通过容器模块(app,command,server 等)进行实例化
- 依赖其他业务模块,或者两个模块间可能会进行相互调用而导致循环引用的,使用控制反转(依赖注入)技术进行处理,由容器模块进行实例化(请参考 ioc exmaple)
- 应用内避免使用 eventbus 等 pubsub 模型进行模块解耦;如需使用 pubsub,请在 event 包中对事件类型、消息进行预定义;禁止为了方便而直接使用 Bus.Pub("my_event", data) 的形式
- 尽量避免使用 init,应显式地在外层调用相关的 InitXXX() 函数
#
2.1.4 目录结构项目源码结构参考 Standard Go Project Layout;代码组织形式参考Clean Architecture
```text├── app.yaml 运行时配置文件├── app.yaml.example 配置文件范例├── build 打包相关的脚本│ ├── docker│ └── docs├── cmd│ └── smartassistant 入口命令├── docs 文档│ ├── guide│ ├── images│ └── tutorial├── internal│ ├── api 接口│ │ ├── area│ │ ├── brand│ │ ├── cloud│ │ ├── device│ │ ├── location│ │ ├── middleware│ │ ├── page│ │ ├── role│ │ ├── scene│ │ ├── scope│ │ ├── session│ │ ├── test│ │ ├── user│ │ └── utils│ │ ├── cloud│ │ └── response│ ├── cloud│ ├── config│ ├── entity│ ├── plugin│ │ ├── docker│ │ └── mocks│ ├── task│ ├── types│ │ └── status│ ├── utils│ │ ├── hash│ │ ├── jwt│ │ ├── session│ │ └── url│ └── websocket├── pkg 通用组件代码│ ├── errors│ ├── proxy│ ├── rand│ └── reverseproxy├── static├── Makefile make 配置└── README.md 项目介绍文档
如果您想进一步了解项目开发相关的内容,请参考 如何参与项目。
#
2.2 用户模块对智汀家庭云即smart-assistant(以下简称SA)的用户,角色,权限的说明。
#
2.2.1 用户#
2.2.1.1 SA的管理员当某用户使用智汀App为某个家庭/公司添加SA之后,该用户就是SA的管理员,创建者,拥有SA所有的权限,包括邀请其他用户加入该家庭,为成员分配角色等。
#
2.2.1.2 smart-assistant-tokensmart-assistant-token 是家庭成员使用SA功能的用户凭证。每个用户加入一个绑定了SA的家庭时,SA会给该用户下发凭证。一个SA的用户凭证 只允许在该SA下使用。smart-assistant-token只能通过添加SA或者加入其他添加了SA的家庭获取。 smart-assistant-token的生成使用了securecookie.CodecsFromPairs(),securecookie.EncodeMulti()方法。
#
2.2.1.3 设置账号密码智汀App默认使用smart-assistant-token进行用户鉴权。如果您拥有智汀专业版,您可以通过设置账号密码后,使用账号密码登录智汀专业版以 体验SA更多的功能。
#
2.2.1.4 邀请其他人加入您的家庭/公司拥有生成邀请码权限的用户可以通过生成邀请二维码,供他人扫描以加入您的家庭。生成二维码时需要您选择相关的角色信息,角色信息表示扫码的用户以什么 角色加入该家庭。每次生成的二维码有效期为十分钟,您可以邀请任何您信任的人加入您的SA。 每个用户可以多次扫描二维码加入您的SA,用户在该家庭的角 色以最后一次扫描的二维码为主。二维码的有效信息通过jwt生成,想了解jwt的详细信息可以阅读https://jwt.io/introduction。
#
2.2.1.5 将用户踢出您的家庭/公司您可以使用SA的删除成员功能,将用户踢出您的家庭/公司。注意: SA创建者不允许被删除。
#
2.2.1.6 注意事项- 每一个角色都拥有不同的权限,多个角色直接之间的权限是取并集的。如果您不想您的SA数据受到损失,在生成您的二维码时,谨慎选择角色信息。
- SA创建者不允许被删除
#
2.2.2 角色#
2.2.2.1 默认角色当您添加了SA后,SA会默认创建管理员和成员两个角色,同时将管理员的角色赋予您。不同的角色有不同的权限控制项,用户角色拥有的权限越高,可以使用SA的功能也就越多。
#
2.2.2.2 角色的创建、修改、删除- 角色的创建、修改、删除只能通过智汀专业版进行操作。
- 角色的创建包括角色名称,角色所拥有的权限。
- 角色的修改包括修改角色名称、增加或减少该角色拥有的权限。
- 角色的删除意味着您会删除该角色以及该角色拥有的权限,同时拥有该角色的用户也会失去该角色。这可能会导致某些用户无法使用SA的功能。
#
2.2.2.3 为用户设置角色SA的创建者和扫码加入SA的用户都拥有自己的角色。如果用户有修改成员角色的权限,也可以通过智汀专业版或智汀App设置某一个用户的角色,即增加或删除 这意味着该用户的权限会变高或变低,取决于用户本身拥有的角色。 注意: SA创建者的角色不可更改,创建者有且只有一个管理员角色。如果您是SA创建者,请避免您扫描该SA的二维码,这会导致您失去管理员的角色。
#
2.2.2.4 多角色SA允许一个用户拥有多个不同的角色。这意味着您在生成邀请二维码或者设置用户角色时可以选择多个角色。一个SA用户总的权限由该用户的所有角色拥有的权限决定。
#
2.2.2.5 注意事项- 管理员角色的权限是不可修改的
- SA创建者的角色不可更改,创建者有且只有一个管理员角色。如果您是SA创建者,请避免您扫描该SA的二维码,这会导致您失去管理员的角色。
#
2.2.3 权限#
2.2.3.1 说明权限是SA用于判断用户是否有对设备、家庭/公司、房间/区域、场景、角色等功能操作的依据。用户具有的权限由其被分配的角色获得。每一个功能都有不同 的权限,但类似修改删除等权限是所有功能都具有的。
- 与角色相关的权限
- 查看角色列表,允许用户查看该SA拥有的角色
- 新增角色,允许用户创建新的角色,并给该角色赋予权限
- 编辑角色,允许用户编辑角色名称和更改角色拥有的权限
- 删除角色,允许用户删除角色
- 与场景相关的权限
- 新增场景,允许用户创建场景
- 删除场景,允许用户删除场景
- 修改和删除场景,允许用户删除场景或者修改场景的设置
- 控制场景,允许用户在场景的执行任务中选择控制场景
- 与房间/区域相关的权限
- 添加房间/区域
- 调整顺序
- 修改房间名称
- 查看房间详情
- 删除房间
- 与家庭/公司相关的权限
- 生成邀请二维码,允许用户邀请其他人进入该家庭
- 修改名称
- 修改成员角色,新增或减少某一成员拥有的角色
- 删除成员,尽管您拥有删除成员的权限,但是您仍然不能删除SA创建者这一成员
- 与设备相关的权限
- 添加设备
- 删除设备,SA一旦被添加,任何人都不会拥有删除SA的权限。
- 修改设备,修改设备的权限包括修改设备的位置,名称等。
- 控制设备,不同的设备有不同的操作权限。eg:灯有开关,色温,色差灯控制权限,开关只有开关权限。
#
2.2.3.2 注意事项- 场景的修改和控制不仅仅取决于用户是否拥有修改和控制场景的权限,还包括该用户是否有对场景中的设备操作项的控制权限。
- eg:如果您拥有控制场景A的权限,但是您没有场景A里面设备B的开关控制权限,则您同样没有控制该场景A的权限。修改场景也是如此。
- 一旦您在编辑角色页面选择了修改、删除、控制设备这些权限项,SA会默认将该家庭下的所有设备的所有权限项都赋予这个角色。
- eg: 您选择了删除设备控制项,则该角色拥有删除该SA所有设备的权限。我们建议您通过编辑角色的高级设置选项来选择您要赋予该角色的设备权限。
#
2.3 设备模块对智汀家庭云即smart-assistant(以下简称SA)的设备模块的说明。
#
2.3.1 品牌品牌指的是智能设备的品牌,SA通过插件的形式对该品牌下的设备进行发现控制。理论上来说一个品牌对应一个插件服务。您可以通过项目 根目录下的品牌查看SA支持的品牌。关于插件服务的详细信息可以参考 设备类插件
#
2.3.2 设备的相关操作在SA中是通过一个个命令对设备进行操作的,如果您想使用这些命令操作某一品牌的设备,首先应该安装该品牌的插件。在SA中安装、更新、 移除插件。请参考设备类插件
SA处理设备命令的流程:客户端通过websocket消息的形式将对应的操作命令发送给SA,SA通过grpc的方式将消息转发给插件服务,插件 服务处理后,将处理的结果通过grpc的方式发送给SA,SA将处理结果以websocket消息返回给客户端。
#
2.3.2.1 设备的发现与添加- 发现设备 发现设备需向SA发送以下格式的websocket消息,字段说明: domain: 插件名称;service:设备命令。
{ "domain": "", "id": 1, "service": "discover"}
成功后SA会返回以下消息
{ "id": 1, "success": true, "result": { "device": { "id": "21394770a79648e6a3416239e1ebecb9", "address": "192.168.0.195:55443", "identity": "0x0000000012ed37c8", "name": "yeelight_ceiling17_0x0000000012ed37c8", "model": "ceiling17", "sw_version": "5", "manufacturer": "yeelight", "power": "on", "bright": 80, "color_mode": 2, "ct": 3017, "rgb": 0, "hue": 0, "sat": 0 } }}
manufacturer之后的字段为设备属性,取决于设备的类型
- 添加设备 将发现设备操作获取的设备主要信息通过添加设备接口以下列格式发送到SA。如果添加的设备为SA,则type为smart_assistant
{ "device": { "name": "nisi dolore eu est", "brand_id": "commodo es", "address": "pariatur sint", "identity": "velit ut ad", "model": "proident veniam", "type": "nisi Lorem in officia irure", "sw_version": "qui ut", "manufacturer": "aute Lorem pariatur volu", "plugin_id": "dolore reprehenderit" }}
SA会将该设备持久化保存在数据库中,之后便可通过插件控制设备。
#
2.3.2.2 设备控制与设备信息客户端同样是以websocket消息的形式将命令发送给SA。因为不同类型的设备的命令不一定相同,所以这里只以yeelight灯进行示例展示。更多类型设备的 消息格式请阅读 WebSocket API 消息定义
设备信息
{ "domain": "yeelight", "id": 1, "service": "state", "service_data": { "device_id": "device_id" }}
{ "id": 1, "result": { "state": { "power": "on/off", "brightness": 55, "color_temp": 4000 } }, "success": true}
开关
{ "domain": "yeelight", "id": 1, "service": "switch", "service_data": { "device_id": "device_id", "power": "on/off/toggle" }}
设置亮度
{ "domain": "yeelight", "id": 1, "service": "set_bright", "service_data": { "device_id": "device_id", "brightness": 100 }}
设置色温
{ "domain": "yeelight", "id": 1, "service": "set_color_temp", "service_data": { "device_id": "device_id", "color_temp": 100 }}
#
2.3.3. 设备的权限SA会从插件的安装目录 “插件安装目录:..//plugins” 读取每一个插件的config.yaml文件以获得该设备具有的操作功能。具体方法可以查看 ”获取设备的操作功能:device.go“ 文件中的GetDeviceActions()方法。SA为设备的每一个功能操作设置了权限 控制,这意味着您可能只能控制某个设备的一种或多种功能。关于权限的详细信息,您可以阅读 权限。您可以通过获取用户权限接口 来查看您拥有的设备控制权限。
#
2.4 设备场景场景是指通过SA实现设备联动。例如,自动检测今天的天气情况,今天无雨,定时智能音箱播放浇花提醒,并且播报今天的天气情况。 根据自身需求,把多种控制并发的事情编辑成一个场景,并命名,可以通过场景控制很多设备,实现一键操作的功能。
#
2.4.1 场景的相关操作#
2.4.1.1 创建场景创建智能场景前请确保您的家庭已添加设备,且用户是否拥有创建场景的权限。
- 场景名称
场景名称在该家庭下需要确保唯一性。
- 触发条件
通过配置触发条件,达到条件后能执行对应的任务,并且可以设置触发条件的生效时段。触发条件分为三种
手动执行,点击即可执行
定时执行,如每天8点
设备状态变化时,如开灯时,感应到人时
当触发条件为手动触发时只能添加一种触发条件。而选择其他两种可以添加多种,同时需要确定条件关系。条件关系可以选择
满足所有条件
满足任一条件
- 技术实现
系统中启动一个服务,作为消息队列(以下简称smq)的消费者,消费者不断去轮训消息队列,看看有没有新的数据,如果有就消费。 查看下面为伪代码:
for { select { case ct := <-ticker.C: fmt.Printf("current ticket at: %d:%d \n", ct.Minute(), ct.Second()) if pq.Len() == 0 { ticker.Reset(sleepTickTime) continue }
task := heap.Pop(pq).(*Task) now := time.Now() timeAt := now.Unix()
if task.Priority > timeAt { nextTick := time.Unix(task.Priority, 0).Sub(now) ticker.Reset(nextTick) qs.push(task) } else { ticker.Reset(defaultTickTime) go task.Run() } }}
当设置为手动执行的场景时,会添加一条任务数据,执行时间为当前时间,加进smq,等待消费者消费。
t = NewTask(WrapSceneFunc(scene, false), 0)PushTask(t, scene)
而设置为自动执行的场景时,会计算任务今天的下次执行时间,并添加任务数据,加进smq,等待消费者消费。
// 获取任务今天的下次执行时间days := time.Now().Sub(c.TimingAt).Hours() / 24nextTime := c.TimingAt.AddDate(0, 0, int(days))t = NewTaskAt(WrapSceneFunc(scene, true), nextTime)PushTask(t, scene)
如果自动执行场景的生效时段为重复性,那么会在每天 23: 55:00 进行第二天任务编排
// AddArrangeSceneTask 每天定时编排场景任务func AddArrangeSceneTask(executeTime time.Time) { var f TaskFunc f = func(task *Task) error { addSceneTaskByTime(executeTime.AddDate(0, 0, 1)) // 将下一个定时编排任务排进队列 AddArrangeSceneTask(executeTime.AddDate(0, 0, 1)) return nil } task := NewTaskAt(f, executeTime) MinHeapQueue.Push(task)}
// 每天 23:55:00 进行第二天任务编排AddArrangeSceneTask(now.EndOfDay().Add(-5 * time.Minute))
#
2.4.1.2 执行任务当满足触发条件后,可以自动执行配置好的执行任务。执行任务认为两种
智能设备,如开灯,播放音乐
控制场景,如开启夏季晚会场景
- 技术实现
任务执行,通过消费者消费smq中的任务,去执行run方法去执行对应的任务。
func (item *Task) Run() { fmt.Println("Run ", item.ToString()) if item.f != nil { f := item.f for _, wrapper := range item.wrappers { f = wrapper(f) } if err := f(item); err != nil { log.Println("task run err:", err) } }}
#
2.4.1.3 查看场景场景分成 “手动” 和 “自动” 两个执行类型,页面加载时判断用户是否拥有控制场景的权限,在页面展示中 “手动”场景排在“自动”场景的上方;
手动类场景为“执行”按键,可直接点击触发执行任务
自动类场景为“开关”按键,设置打开或者关闭状态
#
2.4.2 注意事项- 场景的修改和控制不仅仅取决于用户是否拥有修改和控制场景的权限,还包括该用户是否有对场景中的设备操作项的控制权限。
- eg:如果您拥有控制场景A的权限,但是您没有场景A里面设备B的开关控制权限,则您同样没有控制该场景A的权限。修改场景也是如此。
#
2.5 插件模块#
2.5.1 简述- 当前所说的插件仅指
设备类插件
,插件为SA提供额外的设备发现和控制功能; - 插件通过实现定义的grpc接口,以grpc服务的形式运行,提供接口给SA调用
- 插件同时需要http服务提供h5页面及静态文件
#
2.5.2 SA中插件的工作流程#
2.5.2.1 插件部署流程1) 插件开发者将开发好的插件服务编译成docker镜像提供给SA
2) SA根据插件的镜像地址判断本地是否已经拉取或更新
3) 用户安装插件后,SA根据镜像运行起容器,插件往注册中心注册服务
4) SA通过服务发现发现新的插件服务
#
2.5.2.2 插件使用流程1) 用户在界面上发现设备时对所有插件服务调用Discover接口,插件根据实现的接口返回所发现的设备
2) 用户添加设备并标记设备对应的插件
3) 用户请求设备的H5地址,进去插件自定义页面
4) 通过交互发起自定义指令给SA,SA将指令转发给对应的插件服务
#
2.5.2.3 接口文件http服务 sdk提供了方便的方法进行静态文件挂载和自定义api接口实现
package main import "github.com/zhiting-tech/smartassistant/pkg/plugin/sdk/server" func main() { ps := server.NewPluginServer() ps.HtmlRouter.Static("", "./html") apiGroup := ps.Router.Group("/api/") apiGroup.GET("")}
grpc服务, 通过实现protobuf定义的grpc接口来实现插件服务:
syntax = "proto3";package proto;option go_package = "../proto"; service Plugin {// Discover 发现时设备rpc Discover (empty) returns (stream device);rpc StateChange (empty) returns (stream state);rpc HealthCheck (healthCheckReq) returns (healthCheckResp);// GetAttributes TODO 考虑删除该接口,仅通过Connect获取模型,并通过回调更新属性rpc GetAttributes (GetAttributesReq) returns (GetAttributesResp);rpc SetAttributes (SetAttributesReq) returns (SetAttributesResp); rpc Connect (AuthReq) returns (GetAttributesResp);rpc Disconnect (AuthReq) returns (empty);} message AuthReq { string identity = 1; map<string, string> params = 2;} message ExecuteReq { string identity = 1; string cmd = 2; bytes data = 3;}message ExecuteResp { bool success = 1; string error = 2; bytes data = 3;}message GetAttributesReq { string identity = 1;} message GetAttributesResp { bool success = 1; string error = 2; repeated Instance instances = 3;}message Instance { string identity = 1; int32 instance_id = 2; bytes attributes = 3; string type = 4;} message SetAttributesReq { string identity = 1; bytes data = 2;} message SetAttributesResp { bool success = 1; string error = 2;} message Action { string identity = 1; int32 instance_id = 2; bytes attributes = 3;} message device { string identity = 1; string model = 2; string manufacturer = 3;bool authRequired = 4;} message empty {} message state { string identity = 1; int32 instance_id = 2; bytes attributes = 3;} message healthCheckReq {string identity = 1;} message healthCheckResp {string identity = 1;bool online = 2;}
注:grpc接口是通用的定义,SDK对接口实现了封装,开发者使用SDK时不需要关心,仅需要实现设备类型即可。
#
2.5.3 sdk为了方便开发者快速开发插件以及统一接口,我们提供sdk规范了接口以及预定义了设备模型,以下为sdk实现功能:
- 插件服务注册
- http服务
- grpc服务以及接口封装(包括设备属性获取、属性设置、消息通知等)
- 预定义模型
#
2.5.4 设备模型设计#
2.5.4.1 背景云对云接入时,需要对第三方云的命令进行解析,并通过SA对插件发起命令。
这就要求插件实现的命令必须要有统一的规范和标准,这样第三方就可以通过这个标准来控制SA的所有支持的设备。
同时也能方便SA更好的通过统一的接口以及命令来管理设备。
#
2.5.4.2 模型设计SDK预定义设备类型以及属性,开发者通过引入设备类型实现相关功能。
SDK通过反射获取设备的所有属性,将属性与命令做好对应关系,这样可以使得无论设备是什么形态,都能有统一的接口以及命令进行控制。
操作某个属性时,根据属性的tag对命令中的值进行解析和校验 模型例子如下:
package plugin
import "github.com/zhiting-tech/smartassistant/pkg/plugin/sdk/instance"
type Device struct { Light instance.LightBulb Info0 instance.Info // 根据实际设备功能组合定义}
func NewDevice() *Device { return &Device{ Light: instance.NewLightBulb(), Info0: instance.NewInfo(), }}
#
2.5.5 示例项目- demo-plugin (code: examples/plugin-demo)
#
2.5.6 开发指南参考:设备类插件
#
2.6 设备类插件开发前先阅读插件设计概要:插件系统设计技术概要
使用 plugin-sdk(code:/pkg/plugin/sdk) 可以忽略不重要的逻辑,快速实现插件
#
2.6.1 插件实现1) 获取sdk
go get github.com/zhiting-tech/smartassistant
2) 定义设备
sdk中提供了预定义的设备模型,使用模型可以方便SA有效进行管理和控制
请参考模型定义
package plugin
import ( "github.com/zhiting-tech/smartassistant/pkg/plugin/sdk/instance" "github.com/zhiting-tech/smartassistant/pkg/plugin/sdk/attribute")
type Device struct { Light instance.LightBulb Info0 instance.Info // 根据实际设备功能组合定义}
func NewDevice() *Device {
// 定义属性 lightBulb := instance.LightBulb{ Power: attribute.NewPower(), ColorTemp: instance.NewColorTemp(), // 可选字段需要初始化才能使用 Brightness: nil, // 可选字段不初始化则不从接口中显示 }
// 定义设备基础属性 info := instance.Info{ Name: attribute.NewName(), Identity: attribute.NewIdentity(), Model: attribute.NewModel(), Manufacturer: attribute.NewManufacturer(), Version: attribute.NewVersion(), } return &Device{ Light: lightBulb, Info0: info, }}
3) 实现设备接口 定义好设备之后,需要为设备实现如下几个方法:
package main
import ( "github.com/zhiting-tech/smartassistant/pkg/plugin/sdk/server")
type Device interface { Identity() string // 获取设备唯一值 Info() server.DeviceInfo // 设备详情 Setup() error // 初始化设备属性 Update() error // 更新设备所有属性值 Close() error // 回收所有资源 GetChannel() server.WatchChan // 返回通知channel}
实现如下:
package plugin
import ( "github.com/zhiting-tech/smartassistant/pkg/plugin/sdk/instance" "github.com/zhiting-tech/smartassistant/pkg/plugin/sdk/attribute" "github.com/zhiting-tech/smartassistant/pkg/plugin/sdk/server")
type Device struct { Light instance.LightBulb Info0 instance.Info // 根据实际设备功能组合定义}
func NewDevice() *Device { // 定义属性 lightBulb := instance.LightBulb{ Power: attribute.NewPower(), ColorTemp: attribute.NewColorTemp(), // 可选字段需要初始化才能使 Brightness: nil, // 可选字段不初始化则不从接口中显示 }
info := instance.Info{ Name: attribute.NewName(), Identity: attribute.NewIdentity(), Model: attribute.NewModel(), Manufacturer: attribute.NewManufacturer(), Version: attribute.NewVersion(), } return &Device{ Light: lightBulb, Info0: info, }}
func (d *Device) Info() server.DeviceInfo { // 该方法返回设备的主要信息 panic("implement me")}
func (d *Device) Setup() error { // 设置设备的属性和相关配置(比如设备id、型号、厂商等,以及设备的属性更新触发函数) panic("implement me")}
func (d *Device) Update() error { // 该方法在获取设备所有属性值时调用,通过调用attribute.SetBool()等方法更新 panic("implement me")}
func (d *Device) Close() error { // 自定义退出相关资源的回收 panic("implement me")}
func (d *Device) GetChannel() server.WatchChan { // 返回WatchChan频道,用于状态变更推送 panic("implement me")}
4) 初始化和运行
定义好设备和实现方法后,运行插件服务(包括grpc和http服务)
package main
import ( "log"
"github.com/zhiting-tech/smartassistant/pkg/plugin/sdk/server" sdk "github.com/zhiting-tech/smartassistant/pkg/server/sdk")
func main() { p := server.NewPluginServer() // 插件服务名 go func() { // 发现设备,并将设备添加到manager中 d := NewDevice() p.Manager.AddDevice(d) }() err := sdk.Run(p) if err != nil { log.Panicln(err) }}
这样服务就会运行起来,并通过SA的etcd地址0.0.0.0:2379注册插件服务, SA会通过etcd发现插件服务并且建立通道开始通信并且转发请求和命令
#
2.6.2 快速开始#
2.7 HTTP API 接口规范#
2.7.1 接口鉴权使用smartassistant接口,需将用户凭证smart-assistant-token,放在http请求的header中。格式如下:
"smart-assistant-token":"xxx"
#
2.7.2 返回标准数据结构smartassistant接口均返回JSON格式数据,格式如下:
{"status":0, // 状态码"reason":"", // 状态码描述"data":{} // 所有业务数据返回都包含在data对象中}
#
2.7.3 错误码列表#
2.8 WebSocket API 消息定义#
2.8.1 消息结构通常,一个 WebSocket 消息格式如下:
字段 | 类型 | 描述 | 所属消息类型 |
---|---|---|---|
type | string | 消息类型 | response/event |
service | string | 请求服务类型 | request |
event | string | 事件类型 | event |
data | Object | 消息的自定义数据 | event/request/response |
domain | string | 请求消息中使用,除特殊请求外均为插件id | request |
id | int64 | 消息ID,请求消息时必填,响应与请求id一致 | request/response |
success | bool | 响应时,返回是否成功 | response |
error | string | 响应时,返回错误 | response |
当前有三种消息:
- 客户端发起的请求消息,如下
{ "id": 1, "domain": "example", "service": "example", "data": { "custom_field": 1 }}
- 服务端对客户端请求的响应消息,如下
{ "type": "response", "id": 1, "data": { "custom_field": 1 }, "success": true, "error":"invalid argument"}
- 服务端发起的事件消息,如下
{ "type": "event", "event": "example", "data": { "custom_field": 1 }}
#
2.8.2 设备相关命令#
2.8.2.1 插件设备状态变更{ "type": "event", "event": "attribute_change", "domain": "zhiting", "data": { "plugin_id": "zhiting", "attr": { "iid": "2762071932", "aid": 1, "val": "on" } }}
#
2.8.2.2 设备增加{ "event_type": "device_increase", "data": { "device": { "id": 7, "name": "DoorSensor-EF-3.0", "plugin_id": "zhiting", "iid": "5c0272fffee7c66f", "model": "DoorSensor-EF-3.0", "manufacturer": "zhiting", "type": "window_door_sensor", "location_id": 0, "department_id": 0, "area_id": 59268327347862572 } }, "type": "event"}
#
2.8.2.3 发现设备请求:request
{ "id": 1, "service": "discover"}
响应:response
服务器会分多次响应设备
- auth_required: 表示是否需要认证/配对,true时需要提供足够的信息才能连接设备
- auth_param.name: 参数名字
- auth_param.type: 参数类型,(string/int/bool/float/select)
- auth_param.required: 是否必须
- auth_param.default: 默认值,没有则不返回该字段
- auth_param.min: 参数int/float时限制最小值,没有则不返回
- auth_param.max: 参数int/float时限制最大值,没有则不返回
- auth_param.options: type为select时的可选值,不是则不返回
- auth_param.option.name: 可选值名字
- auth_param.option.val: 可选值的值
{ "id": 1, "type": "", "data": { "device": { "name": "zhiting_M1", "iid": "hijklmn", "model": "M1", "manufacturer": "zhiting", "plugin_id": "demo", "auth_required": true, "auth_params": [ { "name": "pin", "type": "string", "required": true, "default": "", "min": 0, "max": 10, "options": [ { "name": "A", "val": "a" } ] } ] } }, "success": true}
#
2.8.2.4 连接设备/添加设备请求:request
- auth_params:跟据发现的响应的参数将auth_param.name作为key,将用户输入或者选择作为val
{ "id": 1, "domain": "zhiting", "service": "connect", "data": { "iid": "2095030692", "auth_params": { "pin": "12345678", "username": "username", "password": "123456" } }}
响应:response
{ "id": 1, "data": { "instances": [ { "iid": "id111", "services": [ { "type": "gateway", "attributes": [ { "aid": 1, "type": "on_off", "val_type": "", "permission": 0, "val": null } ] } ] }, { "iid": "id222", "services": [ { "type": "light", "attributes": [ { "aid": 1, "type": "on_off", "val_type": "", "permission": 0, "val": null }, { "aid": 2, "type": "brightness", "val_type": "", "min": 1, "max": 100, "permission": 0, "val": null } ] } ] }, { "iid": "id333", "services": [ { "type": "switch", "attributes": [ { "aid": 1, "type": "on_off", "val_type": "", "permission": 0, "val": null } ] }, { "type": "switch", "attributes": [ { "aid": 2, "type": "on_off", "val_type": "", "permission": 0, "val": null } ] }, { "type": "switch", "attributes": [ { "aid": 3, "type": "on_off", "val_type": "", "permission": 0, "val": null } ] } ] } ], "ota_support": true, "device": { "id": 1, "model": "model", "plugin_id": 1, "plugin_url": "http://127.0.0.1/index.html" } }, "success": true}
#
2.8.2.5 获取设备物模型请求:request
{ "id": 1, "domain": "zhiting", "service": "get_instances", "data": { "iid": "2095030692" }}
响应:response
{ "id": 1, "data": { "instances": [ { "iid": "id111", "services": [ { "type": "gateway", "attributes": [ { "aid": 1, "type": "on_off", "val_type": "", "permission": 0, "val": null } ] } ] }, { "iid": "id222", "services": [ { "type": "light", "attributes": [ { "aid": 1, "type": "on_off", "val_type": "", "permission": 0, "val": null }, { "aid": 2, "type": "brightness", "val_type": "", "min": 1, "max": 100, "permission": 0, "val": null } ] } ] }, { "iid": "id333", "services": [ { "type": "switch", "attributes": [ { "aid": 1, "type": "on_off", "val_type": "", "permission": 0, "val": null } ] }, { "type": "switch", "attributes": [ { "aid": 2, "type": "on_off", "val_type": "", "permission": 0, "val": null } ] }, { "type": "switch", "attributes": [ { "aid": 3, "type": "on_off", "val_type": "", "permission": 0, "val": null } ] } ] } ], "ota_support": true }, "success": true}
#
2.8.2.6 设置设备属性请求:request
{ "id": 1, "domain": "zhiting", "service": "set_attributes", "data": { "attributes": [ { "iid": "2095030692", "aid": 1, "val": "on" } ] }}
响应:response
{ "id": 1, "type": "response", "success": true, "error": "error"}
#
2.8.2.7 检查设备是否有固件更新请求:request
{ "id": 1, "domain": "zhiting", "service": "check_update", "data": { "iid": "2095030692" }}
响应:response
{ "id": 1, "type": "response", "data": { "current_version": "1.0.4", "latest_firmware": { "version": "1.0.4", "url": "https://sc.zhitingtech.com:11110/download/ZT-SW3ZLW001W_v1.0.4.bin", "info": "1.0.4更新" }, "update_available": false }, "success": true}
#
2.8.2.8 通过插件更新设备固件请求:request
{ "id": 1, "domain": "zhiting", "service": "ota", "data": { "iid": "2095030692" }}
响应:response
{ "id": 1, "type": "response", "data": null, "success": true}
#
2.8.2.9 断开连接/删除设备请求:request
{ "id": 1, "domain": "zhiting", "service": "disconnect", "data": { "iid": "2095030692" }}
响应:response
{ "id": 1, "type": "response", "success": true, "error": "error"}
#
2.8.2.10 设备状态变更(日志)请求:request
- size: 分页大小
- attr_type: 属性类型,不传则返回所有属性
- index: 不传则从头获取,滚动加载时使用最后一个state的id
- start_at:开始时间,不传则没有限制
- end_at: 结束时间,不传则不限制
{ "id": 1, "domain": "zhiting", "service": "device_states", "data": { "iid": "2095030692", "attr_type": "on_off", "size": 20, "index": 10, "start_at": 1645152639, "end_at": 1645152639 }}
响应:response
{ "id": 1, "type": "response", "data": { "states": [ { "id": 10, "timestamp": 1645152639, "type": "on_off", "val_type": "string", "val": "on" } ] }, "success": true}
#
2.8.2.11 网关列表请求:request
{ "id": 1, "domain": "zhiting", "service": "list_gateways", "data": { "model": "model" }}
响应:response
{ "id": 1, "type": "response", "data": { "gateways": [ { "name": "gateway", "iid": "iid", "model": "gateway", "logo_url": "www.example.com/logo.png", "plugin_id": "example" } ], "support_gateways": [ { "model": "gateway", "logo_url": "www.example.com/logo.png", "plugin_id": "example" } ] }, "success": true}
#
2.8.2.12 子设备列表请求:request
{ "id": 1, "domain": "zhiting", "service": "sub_devices", "data": { "iid": "2095030692" }}
响应:response
{ "id": 1, "type": "response", "data": { "devices": [ { "name": "motion_sensor", "logo_url": "www.example.com/logo.png", "plugin_url": "www.example.com/plugin/index.html" } ], "support_sub_devices": [ { "model": "motion_sensor", "logo_url": "www.example.com/logo.png", "provisioning_url": "www.example.com/plugin/provisioning.html" } ] }, "success": true}
#
2.9 用户认证与第三方授权智汀家庭云提供用户登录认证与第三方授权功能,包括用户凭证、Scope Token与临时密码。
#
2.9.1 用户凭证每一个用户帐号创建时都会自动生成一个随机的用户凭证,该凭证会保存在智汀家庭云与智汀 APP 内, 使用该凭证可以拥有该用户的所有权限。用户凭证通过 smart-assistant-token HTTP 请求头的方式发送。
#
2.9.2 Scope Token第三方 APP 或者智汀云需要请求智汀家庭云的接口时,需要使用 Scope Token 进行鉴权访问。Scope Token 在生成时 可以选择其拥有的接口权限范围,并且会设置过期时间,保证第三方无法越权使用。Scope Token 通过 scope-token HTTP 请求头发送。
Scope Token 为 JWT 格式,其校验密钥为用户凭证。通常情况下应包含以下字段:
- sa_id: 智汀家庭云初始化时的 ID
- uid: 智汀家庭云中对应的用户 ID
- exp:过期时间
- scopes: 权限范围,字符串,以(,)分隔
- 其他业务相关字段
注意:敏感信息不能存储于 JWT 中
Scope Token 授权流程如下:
#
2.9.3 临时密码通常情况下,智汀家庭云通过颁发 Scope Token 来限制第三方访问范围,但偶尔我们也需要让可信任的第三方执行某些管理功能, 但颁发具有全权限的 Scope Token 又担心存在风险;智汀家庭云提供临时密码来解决此问题:
- 智汀APP使用用户凭证请求智汀家庭云接口,生成临时密码,该临时密码具有用户的所有权限
- 将临时密码输入到可信任的第三方平台(如智汀云)
- 第三方平台凭此临时密码与附加的用户ID,SA-ID等信息访问智汀家庭云接口
- 智汀家庭云处理请求后立刻将该临时密码失效,避免越权使用
#
3. 参与项目您可以通过给我们提交问题反馈、提交合并请求(pull request)或者开发第三方插件的方式参与到项目中.
智汀家庭云是基于 Apache License, Version 2.0 发布的开源软件,您可以通过给我们提交问题反馈、 提交合并请求(pull request)或者开发第三方插件的方式参与到项目中。
#
3.1 提交问题反馈本项目使用 git issue 跟踪反馈问题,在提交问题反馈前,请先进行以下操作:
- 确保您的应用版本已经是最新,因为您遇到的问题可能在最新版本中已经修复
- 可以尝试切换到旧版本,测试问题是否依然存在,这有助于我们可以快速定位问题
- 查看项目的 issue 列表中是否已存在该问题
#
3.2 问题反馈需要包含的信息为了让项目开发者能快速复现问题,建议提交的问题反馈中至少包含以下信息:
- 应用程序版本,可以是某个 release 版本号,或者对应代码提交的 commit id
- 本地运行环境,譬如 Linux 发行版,Windows 或者 MacOS 版本,64位还是32位系统,越详细越好
- 使用的 Golang 版本号,如 Golang 1.16,Golang 1.15
- 开发者如何能复现 bug?可以包括一系列的操作,或者是一段代码,也可以是任何相关的上下文信息;当然,也是越详细越好
#
3.3 提交合并请求在开始编码前建议先阅读项目的快速入门文档以及开发文档,如 使用 Docker 运行智汀家庭云或者使用虚拟机运行智汀家庭云, 开发环境搭建, 架构概述等。
需要注意的是智汀家庭云基于 Apache License, Version 2.0 开源协议发布,请确保您的代码与该协议兼容。
#
3.3.1 编码规范编码规范主要参考 Uber Go Style Guide ( Uber Go 语言编码规范 )
#
3.3.2 开发流程项目主要包含以下分支:
- master 预发布的分支,我们会基于 master 分支来打新版本的标签,如 1.1.0,1.2.0
- dev 主开发分支,当新功能开发、测试完成后会合并到 master 分支上,建议基于此分支提交合并请求
- production 分支针对最新版本的修复,合并后会打 1.1.1,1.1.2 等标签进行发布
开发流程
- Fork 项目源码到您的帐号,然后从 master, dev 或者 production 分支进行开发
- 编写代码,并且同步更新文档
- 确保您的代码符合我们的编码规范以及开源协议
- 测试您的代码
- 提交合并请求到 dev 或者 production 分支
#
3.4 开发第三方插件您也可以通过开发插件的形式参与到项目中,完善智汀家庭云对第三方硬件的支持,让更多用户受惠。
可以先阅读开发您的第一个插件来快速入门插件开发, 然后阅读插件系统设计技术概要,设备类插件 等文档进一步了解插件的实现机制。
#
4. 开源协议智汀家庭云项目源码基于 APACHE LICENSE, VERSION 2.0 发布。
#
5. 附录#
5.1 错误码列表通用
0: 成功
1: 服务器异常
2: 错误请求
3: 找不到资源
家庭/公司
1000: 该家庭不存在
1001: 当前家庭创建者不允许退出家庭
1002: 请输入家庭名称
1003: 家庭名称长度不能超过30
1004: SA绑定失败
设备
2000: 设备已被添加
2001: 设备已被绑定
2002: 请输入设备名称
2003: 设备名称长度不能超过20
2004: 该设备不存在
2005:当前用户未绑定该设备
2006: 数据同步失败,请重试
2007: 数据已同步,禁止多次同步数据
房间/区域
3000: 该房间不存在
3001: 请输入房间名称
3002: 房间名称长度不能超过20
3003: 房间名称重复
场景
4000: 与其他场景重名,请修改
4001: 场景名称长度不能超过40
4002: 场景触发条件不存在
4003: 场景不存在
4004: 您没有创建场景的权限
4005: 您没有删除场景的权限
4006: 场景类型不允许修改
4007: 场景触发条件类型与配置不一致
4008: 定时触发条件只能添加一个
4009: 任务类型错误
4010: 设备操作类型不存在
4011: 设备操作未设置
4012: 没有场景或设备的控制权限
4013: 设备断连
4014: %s不正确
用户
5001: 用户名或密码错误
5002: 用户不能删除自己
5003: 用户不存在
5004: 当前用户名已存在,请重新输入
5005: 请输入用户名
5006: 用户名只能输入数字、字母、下划线,不能全部是数字
5007: 请输入昵称
5008: 昵称长度不能大于20位
5009: 昵称长度不能小于6位
5010: 请输入密码
5011: 密码不能少于6位
5012: 用户未登录
5013: 二维码无效
5014: 二维码已过期
5015: 二维码创建者无权限
5016: 获取二维码失败
5017: 该角色不存在
5018: 该角色已存在,请重新输入
5019: 请输入角色名称
5020: 角色名称不能超过20位
5021: 当前用户没有权限
云端SA
100000: 云端SA不支持该接口
100001: 云端家庭已迁移
#
5.2 模型定义#
5.2.1 Attributes#
Volumeproperty | value |
---|---|
type | volume |
permission | read/write/notify |
value_type | int |
#
On Offproperty | value |
---|---|
type | on_off |
permission | read/write/notify |
val_type | string |
valid_value | on/off/switch |
#
Brightnessproperty | value |
---|---|
type | brightness |
permission | read/write/notify |
val_type | int |
valid_value | 1-100 |
#
Color Temperatureproperty | value |
---|---|
type | color_temp |
permission | read/write/notify |
val_type | int |
#
RGBproperty | value |
---|---|
type | rgb |
permission | read/write/notify |
val_type | string |
valid_value | RGB Hex |
#
Modelproperty | value |
---|---|
type | model |
permission | read |
val_type | string |
#
Manufacturerproperty | value |
---|---|
type | manufacturer |
permission | read |
val_type | string |
#
Identifyproperty | value |
---|---|
type | identify |
permission | read |
val_type | string |
#
Versionproperty | value |
---|---|
type | version |
permission | read |
val_type | string |
#
Nameproperty | value |
---|---|
type | name |
permission | read |
value_type | string |
#
Current Positionproperty | value |
---|---|
type | current_position |
permission | read/notify |
value_type | int |
value | 0-100 |
#
Target Positionproperty | value |
---|---|
type | target_position |
permission | read/write/notify |
value_type | int |
value | 0-100 |
#
Stateproperty | value |
---|---|
type | state |
permission | read/notify |
value_type | int |
valid_value | 0/1/2 |
#
Styleproperty | value |
---|---|
type | contact_sensor_state |
permission | read/notify |
value_type | int |
valid_value | 0/1/2/3 |
#
Directionproperty | value |
---|---|
type | direction |
permission | read/notify |
value_type | int |
valid_value | 0/1 |
#
Upper Limitproperty | value |
---|---|
type | upper_limit |
permission | read/write/notify |
value_type | int |
valid_value | 0/1 |
#
Lower Limitproperty | value |
---|---|
type | lower_limit |
permission | read/write/notify |
value_type | int |
valid_value | 0/1 |
#
Humidityproperty | value |
---|---|
type | humidity |
permission | read/notify |
value_type | int |
#
Temperatureproperty | value |
---|---|
type | temperature |
permission | read/notify |
value_type | float |
#
Contact Sensor Stateproperty | value |
---|---|
type | contact_sensor_state |
permission | read/write/notify |
value_type | int |
#
Leak Detectedproperty | value |
---|---|
type | leak_detected |
permission | read/notify |
value_type | int |
#
Switch Eventproperty | value |
---|---|
type | switch_event |
permission | read/write/notify |
value_type | int |
#
Target Stateproperty | value |
---|---|
type | target_state |
permission | read/write/notify |
value_type | int |
#
Current Stateproperty | value |
---|---|
type | current_state |
permission | read/notify |
value_type | int |
#
Motion Detectedproperty | value |
---|---|
type | motion_detected |
permission | read/notify |
value_type | bool |
#
Batteryproperty | value |
---|---|
type | battery |
permission | read/notify |
value_type | float |
#
Lock Target Stateproperty | value |
---|---|
type | lock_target_state |
permission | read/write/notify |
value_type | int |
#
Logsproperty | value |
---|---|
type | logs |
permission | read/write/notify |
value_type | string |
#
Activeproperty | value |
---|---|
type | active |
permission | read/notify |
value_type | int |
#
Current Temperatureproperty | value |
---|---|
type | current_temperature |
permission | read/notify |
value_type | float |
#
Current Heating Cooling Stateproperty | value |
---|---|
type | current_heating_cooling_state |
permission | read/notify |
value_type | int |
#
Target Heating Cooling Stateproperty | value |
---|---|
type | target_heating_cooling_state |
permission | read/write/notify |
value_type | int |
#
Heating Threshold Temperatureproperty | value |
---|---|
type | heating_threshold_temperature |
permission | read/write/notify |
value_type | int |
#
Cooling Threshold Temperatureproperty | value |
---|---|
type | cooling_threshold_temperature |
permission | read/write/notify |
value_type | int |
#
Current Heater Cooler Stateproperty | value |
---|---|
type | current_heater_cooler_state |
permission | read/notify |
value_type | int |
#
Target Heater Cooler Stateproperty | value |
---|---|
type | target_heater_cooler_state |
permission | read/write/notify |
value_type | int |
#
Rotation Speedproperty | value |
---|---|
type | rotation_speed |
permission | read/write/notify |
value_type | int |
#
Swing Modeproperty | value |
---|---|
type | swing_mode |
permission | read/write/notify |
value_type | int |
#
Permit Joinproperty | value |
---|---|
type | permit_join |
permission | read/write/notify |
value_type | int |
#
Alertproperty | value |
---|---|
type | alert |
permission | |
value_type | int |
#
Status Low Batteryproperty | value |
---|---|
type | status_low_battery |
permission | read/write/notify |
value_type | int |
#
Contact Sensor Stateproperty | value |
---|---|
type | contact_sensor_state |
permission | read/notify |
value_type | int |
#
5.2.2 Services#
Switchproperty | value |
---|---|
type | switch |
attributes | On Off |
#
Outletproperty | value |
---|---|
type | outlet |
attributes | On Off |
#
Light Bulbproperty | value |
---|---|
type | light_bulb |
attributes | On Off Brightness Color Temperature RGB |
#
Gatewayproperty | value |
---|---|
type | gateway |
attributes | On Off |
#
Curtainproperty | value |
---|---|
type | gateway |
attributes | Current Position Target Position State Style Direction Upper Limit Lower Limit |
#
Contact Sensorproperty | value |
---|---|
type | contact_sensor |
attributes | Contact Sensor State Battery |
#
Temp Humidity Sensorproperty | value |
---|---|
type | temperature_humidity_sensor |
attributes | Temperature Humidity Battery |
#
Stateless Switchproperty | value |
---|---|
type | stateless_switch |
attributes | SwitchEvent Battery |
#
Security Systemproperty | value |
---|---|
type | security_system |
attributes | TargetState CurrentState |
#
MotionSensorproperty | value |
---|---|
type | motion_sensor |
attributes | Motion Detected Battery |
#
Lockproperty | value |
---|---|
type | lock |
attributes | LockTargetState Logs Battery |
#
HeaterCoolerproperty | value |
---|---|
type | heater_cooler |
attributes | Active Current Temperature Cooling Threshold Temperature Heating Threshold Temperature Current Heater Cooler State Target Heater Cooler State Rotation Speed Swing Mode |