基于 Thingsboard 平台自定义 RPC 控制类小部件示例
1. 小部件介绍
小部件(Thingboard Widget)是可以轻松集成进平台仪表板(Dashboard)的 UI 组件,它可以提供数据可视化、远程设备控制、警报管理、展示静态 Html 内容等功能。根据提供的特性,每个小部件定义代表一个特定的小部件类型。
2. 创建小部件
使用 tenant 或 sysadmin 帐号登陆系统,点击【部件库】菜单 -> 右上角【+】号 -> 【创建新的部件组】
根据提示填写标题、描述信息,如果有图片可以上传
添加完成后,在列表中打开该部件组
点击【添加新的部件类型】-> 选择想要创建的部件类型(此处选择控件部件)
此时出现该类型控件对应的默认示例
3. 部件编辑器
3.1 简介
部件编辑器是一个用来开发自定义部件的小型集成开发环境,主要包括顶部功能工具栏和四个主要部分
- 资源 / HTML / CSS
- JavaScript
- 设置模式(Settings schema)
- 部件预览(Widget preview)
3.2 资源 / HTML / CSS
这部分包含三个页签:
-
资源
用于加载部件所使用的外部 JS / CSS 资源。
-
HTML
编写部件 HTML 代码。
-
CSS
编写部件样式。
3.3 JavaScript
这部分包含所有与部件相关的 JS 代码,根据官方提供的 Widget API
3.4 设置模式 (Settings schema)
这部分包含三个页签:
-
设置模式
用于指定小部件 UI 表单设置的 json 模式,该表单是通过 react-schema-form builder 自动生成的 ,该设置显示在小部件设置的高级(Advanced)选项卡中。由该模式序列化的 Settings 对象用于存储特定的小部件设置,并可通过小部件JavaScript 代码访问。
-
数据键设置模式
用于指定数据键设置的 json 模式,以便使用 react-schema-form builder 自动生成UI表单。这个生成的 UI 表单显示在数据键设置模式对话框的高级(Advanced)选项卡中。由该模式序列化的 Settings 对象用于存储小部件中定义的数据源的每个数据键的特定设置,这些设置可以通过小部件 JavaScript 代码访问。
-
Widget settings
用于配置部件缩略图及描述信息。
3.5 部件预览 (Widget preview)
此部分用于预览和测试自定义的小部件。它作为一个迷你仪表板来显示当前自定义小部件的一个实例。
4. 基础部件 API
所有与部件相关的代码都位于 JavaScript 部分,可以使用内置变量 self,它是一个部件实例的引用。
部件的每一个函数都应该作为 self 变量的属性来定义。
self 变量有一个 WidgetContext 类型的 ctx
属性,它是包含这个部件实例所有必要的 API 和数据的一个上下文引用。
要创建一个新部件,需要实现下列函数,但每个函数都是可选的,可以根据小部件特定的行为来实现。
函数名 | 描述 |
---|---|
onInit() | 小部件准备好进行初始化时调用的第一个函数。用于准备小部件DOM、处理小部件设置和初始订阅信息。 |
onDataUpdated() | 当从小部件订阅获得新数据时调用。 |
onResize() | 当小部件容器调整大小时调用。 |
onEditModeChanged() | 在更改仪表板编辑模式时调用。 |
onMobileModeChanged() | 当仪表板视图宽度跨越移动断点时调用。 |
onDestroy() | 销毁时调用。 |
getSettingsSchema() | 返回设置模式 json,作为设置模式选项卡对应部分的替代选项。 |
getDataKeySettingsSchema() | 返回数据键设置模式 json,作为数据键设置模式选项卡对应部分的替代选项。 |
typeParameters() | 返回描述小部件数据源参数的 WidgetTypeParameters 对象。 |
actionSources() | 返回描述定义用户操作的可用小部件操作源 (WidgetActionSource) 的映射。 |
4.1 订阅对象 (Subscription object)
小部件订阅对象是 IWidgetSubscription 的实例,根据小部件类型包含所有订阅信息,也包括当前数据。订阅对象根据小部件类型提供不同的数据结构。
4.2 时间窗口函数 (Timewindow functions)
用于管理带有时间窗口的小部件数据的时间框架,可以在时间序列或报警部件中使用。
4.3 控制 API (Control API)
为 RPC (控制) 小部件提供 API 函数的对象。
函数名 | 描述 |
---|---|
sendOneWayCommand(method, params, timeout) | 发送单向(无响应) RPC 命令到设备。 |
sendTwoWayCommand(method, params, timeout) | 发送双向(带响应) RPC 命令到设备。 |
4.4 动作 API (Actions API)
一个用于执行用户自定义动作的 API 函数集。
4.5 状态控制器 (State Controller)
仪表板状态控制器 (IStateController) 的实例引用,提供 API 来管理当前仪表板状态。
4.6 类型参数对象 (Type parameters object)
描述部件数据源参数的对象。
4.7 动作资源对象 (Action sources object)
描述可用的小部件动作源的映射,用户动作可以分配给它。
5. 创建 RPC(控制)部件示例
-
清空 CSS 编辑器中的内容
-
在 HTML 中填入如下代码
<form #rpcForm="ngForm" (submit)="sendCommand()"> <div class="mat-content mat-padding" fxLayout="column"> <mat-form-field class="mat-block"> <mat-label>RPC method</mat-label> <input matInput required name="rpcMethod" #rpcMethodField="ngModel" [(ngModel)]="rpcMethod"/> <mat-error *ngIf="rpcMethodField.hasError('required')"> RPC method name is required. </mat-error> </mat-form-field> <mat-form-field class="mat-block"> <mat-label>RPC params</mat-label> <input matInput required name="rpcParams" #rpcParamsField="ngModel" [(ngModel)]="rpcParams"/> <mat-error *ngIf="rpcParamsField.hasError('required')"> RPC params is required. </mat-error> </mat-form-field> <button [disabled]="rpcForm.invalid || !rpcForm.dirty" mat-raised-button color="primary" type="submit" > Send RPC command </button> <div> <label>RPC command response</label> <div style="width: 100%; height: 100px; border: solid 2px gray" [innerHTML]="rpcCommandResponse"> </div> </div> </div> </form>
-
在设置模式中填入如下代码
{ "schema": { "type": "object", "title": "Settings", "properties": { "oneWayElseTwoWay": { "title": "Is One Way Command", "type": "boolean", "default": true }, "requestTimeout": { "title": "RPC request timeout", "type": "number", "default": 500 } }, "required": [] }, "form": [ "oneWayElseTwoWay", "requestTimeout" ] }
-
在 JavaScript 中填入如下代码
self.onInit = function() { self.ctx.$scope.sendCommand = function() { var rpcMethod = self.ctx.$scope.rpcMethod; var rpcParams = self.ctx.$scope.rpcParams; var timeout = self.ctx.settings.requestTimeout; var oneWayElseTwoWay = self.ctx.settings.oneWayElseTwoWay ? true : false; var commandObservable; if (oneWayElseTwoWay) { commandObservable = self.ctx.controlApi.sendOneWayCommand(rpcMethod, rpcParams, timeout); } else { commandObservable = self.ctx.controlApi.sendTwoWayCommand(rpcMethod, rpcParams, timeout); } commandObservable.subscribe( function (response) { if (oneWayElseTwoWay) { self.ctx.$scope.rpcCommandResponse = "Command was successfully received by device.<br> No response body because of one way command mode."; } else { self.ctx.$scope.rpcCommandResponse = "Response from device:<br>"; self.ctx.$scope.rpcCommandResponse += JSON.stringify(response, undefined, 2); } self.ctx.detectChanges(); }, function (rejection) { self.ctx.$scope.rpcCommandResponse = "Failed to send command to the device:<br>" self.ctx.$scope.rpcCommandResponse += "Status: " + rejection.status + "<br>"; self.ctx.$scope.rpcCommandResponse += "Status text: '" + rejection.statusText + "'"; self.ctx.detectChanges(); } ); } }
-
输入部件名称,点击保存,再点击运行,红框处即小部件预览效果,如下图:
为了测试控件 RPC 控制功能,需要将其放在一个仪表板中,并绑定到一个使用 RPC 控制的设备。步骤如下:
-
使用租户(tenant)管理员登陆
-
在【设备】菜单下添加新设备
- 打开设备详情页,点击【复制访问令牌】按钮
- 下载 mqtt-js-rpc-from-server.sh 和 mqtt-js-rpc-from-server.js 并放入同一个文件夹下
- 编辑 mqtt-js-rpc-from-server.sh , 将 $ACCESS_TOKEN 替换为刚才复制的访问令牌
- 如果是本地源码启动的 TB 服务,需要编辑 mqtt-js-rpc-from-server.js , 修改服务器地址为
localhost
- 使用 Linux Shell 执行 mqtt-js-rpc-from-server.sh,运行成功则显示
connected
- 在【仪表板库】菜单下新建仪表板并打开,点击【实体别名】-【添加别名】
- 点击【添加新的部件】,选择之前创建的控制部件
- 填入 RPC method 和 RPC params,点击 Send RPC commond 发送模拟命令,收到如下响应
此时设备端收到如下内容
- 测试发送双向 RPC 命令,编辑部件,在【高级】页签中,取消勾选【Is One Way Command】,保存即可。
填入上一步单向命令的方法和参数,点击发送命令按钮
关闭设备端 js 脚本,再次发送命令,响应如下:
在本例中,使用 controlApi 发送 RPC 命令。此外,为了配置 RPC 命令模式和 RPC 请求超时,引入了自定义小部件设置。来自设备的响应由 commandObservable 处理。它具有成功回调和失败回调,并具有相应的响应,或者包含关于请求执行结果信息的拒绝对象。