立即试用 商务报价
专业版
Widgets Development Guide

本页目录

Widgets Development Guide

介绍

ThingsBoard部件属于UI模块可以与任何IoT仪表板集成并提供最终功能给用户例如数据可视化、远程设备控制、警报管理和显示静态自定义html内容。
根据提供的功能每个部件定义代表特定的部件类型

创建部件

为了创建新的部件定义请导航“部件库”然后打开现有的“部件捆绑”或创建一个新的在“部件捆绑”视图中单击屏幕右下角的大“ +”按钮然后单击“创建新的窗口小部件类型”按钮。

image

选择窗口部件类型会弹出窗口会提示你选择要开发的相应部件类型

image

将根据先前选择的窗口小部件类型打开“窗口部件编辑器”页面该页面预填充了启动器窗口部件模板。

image

编辑器

它由工具栏和四个主要部分组成:

工具栏

image

窗口部件编辑器工具栏包含以下各项:

  • 部件标题字段 - 用于指定小部件定义的标题
  • 部件类型选择器 - 用于指定部件定义的类型
  • 运行按钮 - 用于运行部件代码并在“部件预览”部分中查看结果
  • 撤消按钮 - 将所有编辑器部分恢复为最新保存状态
  • 保存按钮 - 保存部件定义
  • 另存为按钮 - 通过指定新的部件类型名称和目标部件捆绑可以保存新的部件定义副本

HTML和CSS资源部分

第一个resources选项卡用于指定窗口部件使用的外部JavaScript/CSS资源。

image

第二个HTML选项卡包含部件html代码(注意:此内容可以为空并由代码动态创建)。

image

第三个CSS选项卡包含部件的CSS样式定义。

image

JavaScript

本节包含根据部件API的所有与窗口部件相关的JavaScript代码。

image

设置

第一个设置模式标签用于指定部件设置的json模式并使用react-schema-formbuilder

在生成的UI表单上显示部件设置的高级选项卡,设置序列化对象存储部件然后从部件Javascript代码访问。

image

第二个数据键设置选项卡用于指定数据键为json模式并使用react-schema-formbuilder

在生成的UI表单上显示部件设置的数据键配置选项卡,设置序列化对象存储部件数据源的特定数据键,这些设置可以通过部件JavaScript代码访问。

image

预览

本部分用于预览和测试窗口中部件定义,以迷你仪表板的形式通过当前窗口显示部件具有通常的ThingsBoard仪表板提供的几乎所有功能但有一些限制。

例如:出于调试目的在窗口中部件数据源部分中只能选择“功能”作为数据源类型。

image

API

所有与部件相关的代码都位于JavaScript部分并提供了对部件实例的引用的内置变量self部件函数都必须定义主self变量的属性。

self变量有ctx属性引用部件实例使用的所有API和数据的WidgetContext上下文对象。

以下是窗口部件上下文属性的简要说明:

属性 类型 描述
$container jQuery Object 部件的容器元素。可用于使用jQuery API动态访问或修改部件DOM。
$scope 动态部件组件 当前部件元素的角度范围对象。使用Angular方法构建窗口小部件时,可用于访问/修改范围属性。
width Number 部件容器的当前宽度(以像素为单位)。
height Number 部件容器的当前高度(以像素为单位)。
isEdit Boolean 指示仪表板是处于视图状态还是处于编辑状态。
isMobile Boolean 指示仪表板视图是否小于960px宽度(默认移动断点)。
widgetConfig Object 常见的窗口小部件配置,其中包含诸如颜色(文本颜色),backgroundColor(小部件背景颜色)等属性。
settings Object 根据定义的json模式包含小部件特定属性的小部件设置
units String 定义窗口小部件显示的值的单位文本的可选属性。对于简单的小部件(如卡片或仪表)很有用。
decimals Number 可选属性,用于定义应使用多少个位置来显示数值的小数部分。
hideTitlePanel Boolean 管理窗口小部件标题面板的可见性。对于具有自定义标题面板或不同状态的小部件很有用。
widgetTitle String 如果设置,将覆盖配置的窗口小部件标题文本。更改此属性后,必须调用updateWidgetParams()函数。
detectChanges() Function 介绍触发当前小部件的更改检测。由于窗口小部件数据更改而应更新窗口小部件HTML模板绑定时,必须调用此方法。
updateWidgetParams() Function 介绍使用运行时集属性(例如widgetTitlehideTitlePanel等)更新小部件。必须调用这些属性才能使这些属性更改生效。
defaultSubscription Object 请参阅对象订阅
timewindowFunctions Object 请参阅Timewindow功能
controlApi Object 请参阅Control API
actionsApi Object 请参阅Actions API
stateController Object 请参阅状态Controller
datasources 数组<数据源> 解析的窗口小部件数据源的数组。请参见订阅对象.

部件相关的JavaScript函数(注意:每个函数都是可选的):

函数 描述  
onInit() 当widget准备好初始化时调用的第一个函数。应该用于准备小部件DOM,处理小部件设置和初始订阅信息。  
onDataUpdated() 在部件订阅中有新数据可用时调用。可以从窗口部件上下文(ctx)的defaultSubscription object访问最新数据。  
onResize() 调整窗口小部件容器的大小时调用。可以从窗口小部件上下文(ctx)获得最新的宽度和高度。  
onEditModeChanged() 更改仪表板编辑模式时调用。最新模式由ctx的isEdit属性处理。  
onMobileModeChanged() 当仪表板视图宽度超过移动断点时调用。最新状态由ctx的isMobile属性处理。  
onDestroy() 当部件元素被销毁时调用。如有必要,应使用它来清理所有资源。  
getSettingsSchema() 返回窗口小部件设置架构json的可选函数以替代设置部分设置标签  
getDataKeySettingsSchema() 返回特定数据密钥设置方案json的可选函数,替代设置部分Settings schema section的数据密钥设置方案标签。  
typeParameters() 检索描述窗口小部件数据源参数的对象。请参阅类型参数对象类型参数对象。  
actionSources() 调用对象,该对象描述用于定义用户操作的可用窗口小部件操作源。请参阅操作源对象  

订阅对象

部件订阅对象包含所有订阅信息包括根据部件类型的当前数据。

根据部件类型订阅对象提供不同的数据结构。

对于最新值时间序列部件类型提供以下属性:

部件订阅对象是IWidgetSubscription 的实例并根据 widget type包含所有订阅信息包括当前数据。

  • datasources - 具有(Array<Datasource>)结构如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    datasources = [
        {  // datasource
           type: 'entity',// type of the datasource. Can be "function" or "entity"
           name: 'name', // name of the datasource (in case of "entity" usually Entity name)
           aliasName: 'aliasName', // name of the alias used to resolve this particular datasource Entity
           entityName: 'entityName', // name of the Entity used as datasource
           entityType: 'DEVICE', // datasource Entity type (for ex. "DEVICE", "ASSET", "TENANT", etc.)
           entityId: '943b8cd0-576a-11e7-824c-0b1cb331ec92', // entity identificator presented as string uuid. 
           dataKeys: [ //  array of keys (Array<DataKey>) (attributes or timeseries) of the entity used to fetch data 
               { // dataKey
                    name: 'name', // the name of the particular entity attribute/timeseries 
                    type: 'timeseries', // type of the dataKey. Can be "timeseries", "attribute" or "function" 
                    label: 'Sin', // label of the dataKey. Used as display value (for ex. in the widget legend section) 
                    color: '#ffffff', // color of the key. Can be used by widget to set color of the key data (for ex. lines in line chart or segments in the pie chart).  
                    funcBody: "", // only applicable for datasource with type "function" and "function" key type. Defines body of the function to generate simulated data.
                    settings: {} // dataKey specific settings with structure according to the defined Data key settings json schema. See "Settings schema section".
               },
               //...
           ]
        },
        //...
    ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
    data = [
        {
            datasource: {}, // datasource object of this data. See datasource structure above.
            dataKey: {}, // dataKey for which the data is held. See dataKey structure above.
            data: [ // array of data points
                [   // data point
                    1498150092317, // unix timestamp of datapoint in milliseconds
                    1, // value, can be either string, numeric or boolean  
                ],
                //...
            ]  
        },
        //...
    ]     

Alarm部件提供以下属性:

  • alarmSource - (Datasource)有关获取警报的实体的信息并具有结构如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    alarmSource = {
         type: 'entity',// type of the alarm source. Can be "function" or "entity"
         name: 'name', // name of the alarm source (in case of "entity" usually Entity name)
         aliasName: 'aliasName', // name of the alias used to resolve this particular alarm source Entity
         entityName: 'entityName', // name of the Entity used as alarm source
         entityType: 'DEVICE', // alarm source Entity type (for ex. "DEVICE", "ASSET", "TENANT", etc.)
         entityId: '943b8cd0-576a-11e7-824c-0b1cb331ec92', // entity identificator presented as string uuid. 
         dataKeys: [ // array of keys indicating alarm fields used to display alarms data 
            { // dataKey
                 name: 'name', // the name of the particular alarm field 
                 type: 'alarm', // type of the dataKey. Only "alarm" in this case. 
                 label: 'Severity', // label of the dataKey. Used as display value (for ex. as a column title in the Alarms table) 
                 color: '#ffffff', // color of the key. Can be used by widget to set color of the key data.  
                 settings: {} // dataKey specific settings with structure according to the defined Data key settings json schema. See "Settings schema section".
            },
            //...
          ] 
    }
  • alarms - 具有(Array<Alarm>)结构如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    alarms = [
        { // alarm
            id: { // alarm id 
                entityType: "ALARM", 
                id: "943b8cd0-576a-11e7-824c-0b1cb331ec92"
            },
            createdTime: 1498150092317, // Alarm created time (unix timestamp)
            startTs: 1498150092316, // Alarm started time (unix timestamp)
            endTs: 1498563899065, // Alarm end time (unix timestamp)
            ackTs: 0, // Time of alarm aknowledgment (unix timestamp)
            clearTs: 0, // Time of alarm clear (unix timestamp)
            originator: { // Originator - id of entity produced this alarm 
                entityType: "ASSET", 
                id: "ceb16a30-4142-11e7-8b30-d5d66714ea5a"
            },
            originatorName: "Originator Name", // Name of originator entity
            type: "Temperature", // Type of the alarm            
            severity: "CRITICAL", // Severity of the alarm ("CRITICAL", "MAJOR", "MINOR", "WARNING", "INDETERMINATE") 
            status: "ACTIVE_UNACK", // Status of the alarm 
                                    // ("ACTIVE_UNACK" - active unacknowledged, 
                                    // "ACTIVE_ACK" - active acknowledged, 
                                    // "CLEARED_UNACK" - cleared unacknowledged, 
                                    // "CLEARED_ACK" - cleared acknowledged)
            details: {} // Alarm details object derived from alarm details json.
        }
    ]               

对于其他部件类型例如RPCStatic订阅对象是可选的不包含必要的信息。

Timewindow函数

Time-seriesAlarm部件可以使用(时间窗口函数)管理部件数据的时间范围

函数 描述
onUpdateTimewindow(startTimeMs, endTimeMs) 此功能可用于将当前订阅时间范围更新为由startTimeMsendTimeMs参数标识的历史时间范围。
onResetTimewindow() 根据窗口部件设置将订阅时间范围重置为由窗口部件时间窗口组件或仪表板时间窗口定义的默认时间范围。

Control API

提供RPC (Control)部件的(RpcApi)函数。

函数 描述
sendOneWayCommand(method, params, timeout) 向设备发送一种方法(无响应)RPC命令。返回命令执行承诺。method -RPC方法名称,字符串,params -RPC方法参数,自定义json对象,timeout -等待接收到响应/确认之前的最大延迟(以毫秒为单位)。
sendTwoWayCommand(method, params, timeout) 设备发送两种方式(带有响应)的RPC命令。在成功回调中返回带有响应主体的命令执行承诺。

Actions API

操作API提供的(WidgetActionsApi)函数。

函数 描述
getActionDescriptors(actionSourceId) 返回提供的actionSourceId的动作描述符的列表
handleWidgetAction($event, descriptor, entityId, entityName) 处理特定动作源产生的动作。$event -与Action相关的事件对象descriptoraction描述符, entityIdentityName -当前实体ID和名称(如果可用)由动作源提供。

State Controller

仪表板状态控制器(IStateController)函数。

函数 描述
openState(id, params, openRightLayout) 导航到新的仪表板状态。id -目标仪表盘状态的ID,params -与状态参数对象被新的状态下使用,openRightLayout -可选布尔参数强行打开右仪表盘布局如果存在于移动视图模式。
updateState(id, params, openRightLayout) 更新当前仪表板状态。id -目标仪表盘状态的可选id替换当前的状态ID,params -与状态参数对象更新当前状态参数,openRightLayout -可选布尔参数强行打开右仪表盘布局如果存在于移动视图模式。
getStateId() 返回当前的仪表板状态ID。
getStateParams() 返回当前仪表板状态参数。
getStateParamsByStateId(id) 返回由id标识的特定仪表板状态的状态参数。

类型参数对象

部件数据源参数的对象WidgetTypeParameters属性:

1
2
3
4
5
    return {
        maxDatasources: -1, // Maximum allowed datasources for this widget, -1 - unlimited
        maxDataKeys: -1, //Maximum allowed data keys for this widget, -1 - unlimited
        dataKeysOptional: false //Whether this widget can be configured with datasources without data keys
    }

对象操作来源

部件操作源对象(WidgetActionSource)分配用户操作:

1
2
3
4
5
6
   return {
        'headerButton': { // Action source Id (unique action source identificator)
           name: 'Header button', // Display name of action source, used in widget settings ('Actions' tab).
           multiple: true // Boolean property indicating if this action source supports multiple action definitions (for ex. multiple buttons in one cell, or only one action can by assigned on table row click.)
        }
    };   

创建简单部件

以下是一组简单的教程,介绍如何创建每种类型的最小部件。

使用Angular框架将减少ThingsBoard UI代码量。

顺便说一句你始终可以在部件代码中使用纯JavaScript或jQuery API。

最新值部件

件捆绑包视图中单击屏幕右下角的“+”大按钮,然后单击创建部件类型按钮。

选择部件类型弹出窗口中单击“新值”按钮。

打开部件编辑器”,并预先填充默认的新值模板部件的内容。

  • 清除“资源”部分CSS标签的内容。
  • 将以下HTML代码放入“资源”部分的HTML标签中:
1
2
3
4
5
6
7
8
  <div fxFlex fxLayout="column" style="height: 100%;" fxLayoutAlign="center stretch">
    <div>My first latest values widget.</div>
    <div fxFlex fxLayout="row" *ngFor="let dataKeyData of data" fxLayoutAlign="space-around center">
        <div>{{dataKeyData.dataKey.label}}:</div>
        <div>{{(dataKeyData.data[0] && dataKeyData.data[0][0]) | date : 'yyyy-MM-dd HH:mm:ss' }}</div>
        <div>{{dataKeyData.data[0] && dataKeyData.data[0][1]}}</div>
    </div>
  </div>
  • 将以下JavaScript代码放入“JavaScript”部分中:
1
2
3
4
5
6
7
    self.onInit = function() {
       self.ctx.$scope.data = self.ctx.defaultSubscription.data;
    }
        
    self.onDataUpdated = function() {
        self.ctx.detectChanges();
    }
  • 单击部件编辑器工具栏中的运行按钮,以便在部件预览部分中查看结果。

image

在此示例中subscriptiondata属性被分配给$scope并且可以在HTML模板中访问。

在HTML内部使用特殊的ng-repeat角度指令来迭代可用的dataKeys数据点并使用其时间戳呈现相应的最新值。

时间序列部件

部件捆绑包视图中单击屏幕右下角的“+”大按钮然后单击创建部件类型按钮。

在此示例中subscriptiondata属性被分配给$scope并且可以在HTML模板中访问。

在HTML中使用了一个特殊的*ngFor结构指令来迭代可用的dataKeys和数据然后渲染最新数据。

选择部件类型弹出窗口中单击时间系列按钮。

将打开部件编辑器其中预填充了默认的时间系列模板小部件的内容。

  • 清除“资源”部分CSS标签的内容。
  • 将以下HTML代码放入“资源”部分的HTML标签中:
1
2
3
.my-data-table th {
    text-align: left;
}
  • 将以下HTML代码放在“资源”部分的HTML选项卡中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  <mat-tab-group style="height: 100%;">
      <mat-tab *ngFor="let datasource of datasources; let $dsIndex = index" label="{{datasource.name}}">
          <table class="my-data-table" style="width: 100%;">
              <thead>
                  <tr>
                      <th>Timestamp</th>
                      <th *ngFor="let dataKeyData of datasourceData[$dsIndex]">{{dataKeyData.dataKey.label}}</th>
                  <tr>          
              </thead>
              <tbody>
                  <tr *ngFor="let data of datasourceData[$dsIndex][0].data; let $dataIndex = index">
                      <td>{{data[0] | date : 'yyyy-MM-dd HH:mm:ss'}}</td>
                      <td *ngFor="let dataKeyData of datasourceData[$dsIndex]">{{dataKeyData.data[$dataIndex] && dataKeyData.data[$dataIndex][1]}}</td>
                  </tr>      
              </tbody>          
          </table>          
      </mat-tab>       
  </mat-tab-group>
  • 将以下JavaScript代码放入“JavaScript”部分中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
self.onInit = function() {
    self.ctx.widgetTitle = 'My first Time-Series widget';
    self.ctx.$scope.datasources = self.ctx.defaultSubscription.datasources;
    self.ctx.$scope.data = self.ctx.defaultSubscription.data;
    
    self.ctx.$scope.datasourceData = [];
    
    var currentDatasource = null;
    var currentDatasourceIndex = -1;
    
    for (var i=0;i<self.ctx.$scope.data.length;i++) {
        var dataKeyData = self.ctx.$scope.data[i];
        if (dataKeyData.datasource != currentDatasource) {
            currentDatasource = dataKeyData.datasource
            currentDatasourceIndex++;
            self.ctx.$scope.datasourceData[currentDatasourceIndex] = [];
            
        } 
        self.ctx.$scope.datasourceData[currentDatasourceIndex].push(dataKeyData);
    }
    self.ctx.updateWidgetParams();

}

self.onDataUpdated = function() {
    self.ctx.detectChanges();
}
  • 单击部件编辑器工具栏中的运行按钮以便在部件预览部分中查看结果。

image

在此示例中,subscriptiondatasourcesdata属性被分配给$scope并且可以在HTML模板中访问。

引入了datasourceData范围属性,以通过数据源索引映射数据源特定的dataKeys数据以便在HTML模板中进行灵活访问。

在HTML内部使用特殊的ng-repeat角度指令来迭代可用的数据源并呈现相应的选项卡。

在每个选项卡内使用从datasource索引访问的datasourceData范围属性获取的dataKeys数据呈现表。

每个表都通过遍历所有dataKeyData对象来呈现列,并通过遍历每个dataKeyDatadata数组以呈现时间戳和值来呈现所有可用的数据点。

请注意此代码中的onDataUpdated函数是通过调用有角度的 $digest 函数实现的,该函数对于接收新数据时执行新的渲染周期是必需的。

RPC(控制)部件

在这个例子中subscriptiondatasourcesdata属性被分配给$scope并且可以在HTML模板中访问。

引入$scope.datasourceData属性以通过数据源索引映射数据源特定的dataKeys数据以便在HTML模板中访问。

在HTML中使用一个特殊的*ngFor结构指令来迭代可用数据源并呈现相应的选项卡。

在每个选项卡内使用从索引的方式访问的datasourceData范围属性获取的dataKeys。

通过迭代所有dataKeyData对象来呈现并通过迭代每个dataKeyDatadata数组显示所有可用数据和时间戳。

请注意在此代码中onDataUpdated函数通过调用detectChanges函数来实现以便在接收到新数据时执行新的更改检测周期。

选择部件类型弹出窗口中单击控制部件按钮。

将打开部件编辑器并预先填充默认的控制模板小部件的内容。

  • 清除“资源”部分CSS标签的内容。
  • 将以下HTML代码放入“资源”部分的HTML标签中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    <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>
  • 将以下JSON内容放入设置架构标签中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
    "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代码放入“JavaScript”部分中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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();
            }
            
        );
    }
    
}
  • 用小部件类型名称填充部件标题字段例如“我的第一个控件”。
  • 单击部件编辑器工具栏中的运行按钮以便在部件预览部分中查看结果。
  • 现在点击预览部分中的信息中心编辑按钮然后更改生成的小部件的大小然后单击仪表板应用按钮最终的小部件应如下图所示。

image

  • 单击部件编辑器工具栏中的保存按钮以保存小部件类型。

为了测试此窗口小部件如何执行RPC命令我们需要将该窗口小部件放置在某些仪表板上并绑定到使用RPC命令的某些设备为此请执行以下步骤:

  • 以租户管理员身份登录。
  • 导航至设备,并使用某些名称创建新设备例如 “我的RPC设备”。
  • 打开设备详细信息,然后单击“复制访问令牌”按钮,以将设备访问令牌复制到剪贴板。
  • 下载mqtt-js-rpc-from-server.shmqtt-js-rpc-from-server.js将这些文件放在某个文件夹中。
  • 编辑mqtt-js-rpc-from-server.sh-用剪贴板中的设备访问令牌替换 $ACCESS_TOKEN
  • 运行mqtt-js-rpc-from-server.sh脚本。如果一切正常,你应该在控制台中看到“已连接”消息。
  • 导航至仪表板并创建一个具有某些名称的新仪表板,例如。 “我的第一个控制仪表板”。打开此仪表板。
  • 点击信息中心的修改按钮。在仪表板编辑模式下,单击位于仪表板工具栏上的“实体别名”按钮。

image

  • 实体别名弹出窗口中单击“添加别名”。
  • 填写“别名”字段例如“我的RPC设备别名”。
  • 在“过滤器类型”字段中选择“实体列表”。
  • 在“类型”字段中选择“设备”。
  • 在“实体列表”字段中选择你的设备在此示例中“我的RPC设备”。

image

  • 实体别名中单击“添加”然后单击“保存”。
  • 点击信息中心的“+”按钮然后点击“创建部件”按钮。

image

  • 然后选择部件捆绑包 RPC小部件的保存位置选择“控件小部件”选项卡。
  • 点击小部件在此示例中“我的第一个控件”。
  • 添加小部件弹出窗口中在目标设备部分中选择你的设备别名在此示例中“我的RPC设备别名”。
  • 点击添加你的控件小部件将出现在仪表板中单击仪表板“应用更改”按钮以保存仪表板并退出编辑模式。
  • RPC方法名称字段中填充RPC方法名称对于前“测试方法”。
  • RPC参数字段“{param1:’value1’}”。
  • 点击发送RPC命令​​按钮。你应该在小部件中看到以下响应。

image

以下输出应在设备控制台中打印:

1
2
  request.topic: v1/devices/me/rpc/request/0
  request.body: {"method":"TestMethod","params":"{ param1: \"value1\" }"}

为了测试“Two way””RPC命令模式,我们需要更改相应的窗口小部件设置属性为此请执行以下步骤:

  • 点击信息中心的修改按钮.在仪表板编辑模式下,单击“控制”小部件标题中的“编辑小部件”按钮。
  • 在小部件详细信息视图中,选择“高级”标签,然后取消选中“Is One Way Command”复选框。

image

  • 点击部件详细信息标题中的应用更改按钮关闭详细信息然后点击信息中心应用更改按钮。
  • 与前面的步骤一样,用RPC方法名称和参数填充部件字段。 点击发送RPC命令​​按钮你应该在小部件中看到以下响应。

image

  • 停止mqtt-js-rpc-from-server.sh脚本。 点击发送RPC命令​​按钮。你应该在小部件中看到以下响应。

image

在此示例中controlApi用于发送RPC命令此外引入了自定义窗口小部件设置以配置RPC命令模式和RPC请求超时。

设备的响应由具有成功和失败回调的commandPromise进行处理并带有包含有关请求执行结果信息的相应响应或拒绝对象。

警报部件

部件捆绑视图中单击屏幕右下角的“+”按钮然后单击创建部件类型按钮。

选择部件类型弹出窗口中单击警报部件按钮。

使用默认的Alarm模板小部件的内容预填充打开部件编辑器

  • 将以下HTML代码放入“资源”部分的HTML标签中:
1
2
3
.my-alarm-table th {
    text-align: left;
}
  • 将以下HTML代码放在“资源”部分的HTML选项卡中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  <div fxFlex fxLayout="column" style="height: 100%;">
      <div>My first Alarm widget.</div>
      <table class="my-alarm-table" style="width: 100%;">
          <thead>
              <tr>
                  <th *ngFor="let dataKey of alarmSource?.dataKeys">{{dataKey.label}}</th> 
              <tr>          
          </thead>
          <tbody>
              <tr *ngFor="let alarm of alarms">
                  <td *ngFor="let dataKey of alarmSource?.dataKeys" 
                      [ngStyle]="getAlarmCellStyle(alarm, dataKey)">
                      {{getAlarmValue(alarm, dataKey)}}
                  </td>
              </tr>      
          </tbody>          
      </table>          
  </div>
  • 将以下JSON内容放入“设置”部分的设置架构标签中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
    "schema": {
        "type": "object",
        "title": "AlarmTableSettings",
        "properties": {
            "alarmSeverityColorFunction": {
                "title": "Alarm severity color function: f(severity)",
                "type": "string",
                "default": "if(severity == 'CRITICAL') {return 'red';} else if (severity == 'MAJOR') {return 'orange';} else return 'green'; "
            }
        },
        "required": []
    },
    "form": [
        {
            "key": "alarmSeverityColorFunction",
            "type": "javascript"
        }
    ]
}
  • 将以下JavaScript代码放入”JavaScript”部分中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
self.onInit = function() {
    self.ctx.$scope.alarmSource = self.ctx.defaultSubscription.alarmSource;
    
    var alarmSeverityColorFunctionBody = self.ctx.settings.alarmSeverityColorFunction;
    if (typeof alarmSeverityColorFunctionBody === 'undefined' || !alarmSeverityColorFunctionBody.length) {
        alarmSeverityColorFunctionBody = "if(severity == 'CRITICAL') {return 'red';} else if (severity == 'MAJOR') {return 'orange';} else return 'green';";
    }
    
    var alarmSeverityColorFunction = null;
    try {
        alarmSeverityColorFunction = new Function('severity', alarmSeverityColorFunctionBody);
    } catch (e) {
        alarmSeverityColorFunction = null;
    }

    self.ctx.$scope.getAlarmValue = function(alarm, dataKey) {
        var alarmKey = dataKey.name;
        if (alarmKey === 'originator') {
            alarmKey = 'originatorName';
        }
        var value = alarm[alarmKey];
        if (alarmKey === 'createdTime') {
            return self.ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');
        } else {
            return value;
        }
    }
    
    self.ctx.$scope.getAlarmCellStyle = function(alarm, dataKey) {
        var alarmKey = dataKey.name;
        if (alarmKey === 'severity' && alarmSeverityColorFunction) {
            var severity = alarm[alarmKey];
            var color = alarmSeverityColorFunction(severity);
            return {
                color: color  
            };
        } 
        return {};
    }
}

self.onDataUpdated = function() {
    self.ctx.$scope.alarms = self.ctx.defaultSubscription.alarms;
    self.ctx.detectChanges();
}
  • 单击“部件编辑器工具栏”中的“运行”按钮,以便在“部件预览”部分中查看结果。

image

在此示例中订阅alarmSourcealarms属性被分配给$scope并且可以在HTML模板中访问。

在HTML内部使用angular的ng-repeat指令进行遍历alarmSource可用警报的dataKeys并呈现相应的列。

在表中通过遍历alarms数组显示对应的单元格dataKeys

函数getAlarmValue是使用特殊的AlarmFields常量用来获取警报值该常量是从ThingsBoard UI的types中获取的并且可以通过Angular$injector进行访问。

在此示例中subscriptionalarmSourcealarms属性被分配给$scope并且可以在HTML模板中访问。

在HTML中使用*ngFor结构指令来迭代alarmSource的可用警报dataKeys并呈现相应的列。

表格行通过迭代alarms数组呈现相应的单元格通过迭代dataKeys呈现。

函数getAlarmValue正在使用可通过date属性访问的DatePipe管道获取警报值并格式化createdTime警报属性ctx

函数getAlarmCellStyle用于为每个警报单元格分配自定义单元格样式。

getAlarmCellStyle函数中有对应的alarmSeverityColorFunction调用和严重性值以获取警报严重性单元格的颜色。

请注意在此代码中实现了onDataUpdated函数以便使用来自订阅的最新警报更新alarms属性并使用detectChanges()函数调用更改检测。

函数getAlarmCellStyle用于为每个报警单元分配自定义样式。

在此示例中我们引入了新的名为alarmSeverityColorFunction的设置属性该属性包含根据警报严重性返回颜色的函数主体。

请注意在此代码中实现了onDataUpdated函数以便使用来自订阅的最新警报来更新alarms属性。

Static部件

部件捆绑视图中单击屏幕右下角的“+”按钮然后单击创建部件类型按钮。

选择部件类型弹出窗口中单击Static部件按钮。

打开部件编辑器并预先填充默认的**Static模板小部件的内容。

  • 将以下HTML代码放入“资源”部分的HTML标签中:
1
2
3
4
  <div fxFlex fxLayout="column" style="height: 100%;" fxLayoutAlign="space-around stretch">
    <h3 style="text-align: center;">My first static widget.</h3>
    <button mat-raised-button color="primary" (click)="showAlert()">Click me</button>
  </div>
  • 将以下JSON内容放入“设置”的设置架构标签中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
    "schema": {
        "type": "object",
        "title": "Settings",
        "properties": {
            "alertContent": {
                "title": "Alert content",
                "type": "string",
                "default": "Content derived from alertContent property of widget settings."
            }
        }
    },
    "form": [
        "alertContent"
    ]
}
  • 将以下JavaScript代码放入“ JavaScript”部分中:
1
2
3
4
5
6
7
8
9
10
11
self.onInit = function() {

    self.ctx.$scope.showAlert = function() {
        var alertContent = self.ctx.settings.alertContent;
        if (!alertContent) {
            alertContent = "Content derived from alertContent property of widget settings.";
        }
        window.alert(alertContent);  
    };

}
  • 单击部件编辑器工具栏中的运行按钮,以便在部件预览部分中查看结果。

image

这只是一个静态HTML小部件因此没有订阅数据或使用了特殊的小部件API。

仅实现了自定义showAlert函数该函数显示具有小部件设置的alertContent属性内容的警报。

你可以在部件预览部分切换到仪表盘编辑模式并通过在小部件详细信息的“高级”选项卡中更改小部件设置来更改alertContent的值。

然后你可以看到显示了新的警报内容。

集成现有代码创建部件定义

以下是一些示例,这些示例演示如何重用/集成外部JavaScript库或现有代码以创建新的小部件。

使用外部JavaScript库

在此示例中,将使用外部gauge.js库创建Latest Values仪表小部件。

部件捆绑视图中单击屏幕右下角的“+”大按钮然后单击创建部件类型按钮。

Latest Values实例

在此示例中Latest Values仪表小部件将使用外部gauge.js库创建。

选择部件类型弹出窗口中单击Latest Values按钮。

将打开部件编辑器并预先填充默认的Latest Values模板小部件的内容。

  • 打开资源标签然后单击“添加”然后插入以下链接:
1
https://bernii.github.io/gauge.js/dist/gauge.min.js  
  • 清除“资源”部分CSS标签的内容。
  • 将以下HTML代码放入“资源”部分的HTML标签中:
1
  <canvas id="my-gauge"></canvas>
  • 将以下JavaScript代码放入“ JavaScript”部分中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var canvasElement;
var gauge;

self.onInit = function() {
    canvasElement = $('#my-gauge', self.ctx.$container)[0];
    gauge = new Gauge(canvasElement);
    gauge.minValue = -1000; 
    gauge.maxValue = 1000; 
    gauge.animationSpeed = 16; 
    self.onResize();
}

self.onResize = function() {
    canvasElement.width = self.ctx.width;
    canvasElement.height = self.ctx.height;
    gauge.update(true);
    gauge.render();
}

self.onDataUpdated = function() {
    var value = self.ctx.defaultSubscription.data[0].data[0][1];
    gauge.set(value);
}
  • 单击部件编辑器工具栏中的运行按钮以便在部件预览部分中查看结果。

image

在此示例中,使用了外部JS库的API该API在Resources部分中注入相应的URL后才可用。

显示的值是从第一个dataKey的subscriptiondata属性获得的。

使用现有的JavaScript代码

创建窗口部件的另一种方法是使用现有的捆绑JavaScript代码。

在这种情况下你可以创建自己的JavaScript类或Angular指令并将其捆绑到ThingsBoard UI代码中。

为了使此代码可在窗口部件中访问你需要注册相应的Angular模块或将JavaScript类注入到全局变量(例如,对于window对象)。

一些ThingsBoard小部件已经使用这种方法。看看widget.service.js

在这里,你可以找到如何注册一些捆绑的类或模块以供以后在ThingsBoard小部件中使用。

例如Timeseries-Flot小部件(来自Charts部件捆绑包)使用TbFlotJavaScript类,将其作为窗口属性注入widget.service.js中:

Time-Series实例

在此示例中将使用外部Chart.js库创建Time-Series折线图小部件。

Widgets Bundle视图中单击屏幕右下角的大“+”按钮然后单击“创建新的小部件类型”按钮。

单击Select widget type弹出窗口上的Time-Series按钮。

打开Widget Editor并预先填充默认Time-Series模板小部件的内容。

  • 打开Resources标签并点击“添加”然后插入以下链接:
1
https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js
  • 清除“资源”部分CSS选项卡的内容。
  • 将以下HTML代码放在“资源”部分的HTML选项卡中:
1
  <canvas id="myChart"></canvas>
  • 将以下JavaScript代码放入“JavaScript”部分:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
var myChart;

self.onInit = function() {
    
    var chartData = {
        datasets: []
    };

    for (var i=0; i < self.ctx.data.length; i++) {
        var dataKey = self.ctx.data[i].dataKey;
        var dataset = {
            label: dataKey.label,
            data: [],
            borderColor: dataKey.color,
            fill: false
        };
        chartData.datasets.push(dataset);
    }
    
    var options = {
        maintainAspectRatio: false,
        legend: {
            display: false
        },
        scales: {
        xAxes: [{
            type: 'time',
            ticks: {
                maxRotation: 0,
                autoSkipPadding: 30
            }
        }]
    }
    };
    
    var canvasElement = $('#myChart', self.ctx.$container)[0];
    var canvasCtx = canvasElement.getContext('2d');
    myChart = new Chart(canvasCtx, {
        type: 'line',
        data: chartData,
        options: options
    });
    self.onResize();
}

self.onResize = function() {
    myChart.resize();
}

self.onDataUpdated = function() {
    for (var i = 0; i < self.ctx.data.length; i++) {
        var datasourceData = self.ctx.data[i];
        var dataSet = datasourceData.data;
        myChart.data.datasets[i].data.length = 0;
        var data = myChart.data.datasets[i].data;
        for (var d = 0; d < dataSet.length; d++) {
            var tsValuePair = dataSet[d];
            var ts = tsValuePair[0];
            var value = tsValuePair[1];
            data.push({t: ts, y: value});
        }
    }
    myChart.options.scales.xAxes[0].ticks.min = self.ctx.timeWindow.minTime;
    myChart.options.scales.xAxes[0].ticks.max = self.ctx.timeWindow.maxTime;
    myChart.update();
}
  • 单击Widget Editor Toolbar上的Run按钮以在Widget preview部分查看结果。

image

在这个例子中使用了外部JS库API它在Resources部分注入相应的URL后变得可用。

最初使用来自ctxdata属性的配置数据键准备图表数据集。

onDataUpdated函数中将数据源数据转换为Chart.js折线图格式并推送到图表数据集。

请注意xAxis(时间轴)仅限于从ctxtimeWindow属性获得的当前时间窗口边界。

使用现有的JavaScript代码

创建小部件的另一种方法是使用现有的捆绑JavaScript代码。

在这种情况下您可以创建自己的TypeScript类或Angular组件并将其捆绑到ThingsBoard UI代码中。

为了使该代码在小部件内可访问您需要注册相应的Angular模块或将TypeScript类注入全局变量(例如 window 对象)。

一些ThingsBoard小部件已经使用了这种方法看看polyfills.tswidget-components.module.ts

在这里您可以找到如何注册一些捆绑的类或组件以供以后在ThingsBoard小部件中使用。

例如“Timeseries - Flot”小部件(来自“Charts”小部件捆绑包)使用TbFlotTypeScript 类作为窗口属性注入polyfills.ts

1
2
3
4
5
6
7
8
...

import { TbFlot } from '@home/components/widget/lib/flot-widget';
...

    (window as any).TbFlot = TbFlot;
...

另一个示例是使用“Angular”指令tb-timeseries-table-widget该文件已注册为thingsboard.api.widget的Angular模块依赖 widget.service.js中的Angular模块的依赖项。

因此此指令可在小部件模板HTML中使用。

另一个例子是使用Angular组件的“Timeseries table”小部件(来自“Cards”Widgets Bundle)tb-timeseries-table-widget它被注册为widget-components内的WidgetComponentsModuleAngular模块的依赖项widget-components.module.ts

因此该组件可在小部件模板HTML中使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
...

import { TimeseriesTableWidgetComponent } from '@home/components/widget/lib/timeseries-table-widget.component';

...

@NgModule({
  declarations:
    [
...
      TimeseriesTableWidgetComponent,
...
    ],
...
  exports: [
...
      TimeseriesTableWidgetComponent,
...
  ],
...    
})
export class WidgetComponentsModule { }

小部件代码调试技巧

调试的最简单方法是Web控制台输出。

只需将console.log(…)函数放在部件JavaScript代码的任何部分内即可。

然后单击运行按钮以重新启动小部件代码,并在Web控制台中观察调试信息。

另一种最有效的调试方法是调用浏览器调试器。

debugger;语句放入你感兴趣的部件代码的位置然后单击运行按钮重新启动小部件代码。

浏览器调试器(如果启用)将自动在调试器语句处暂停代码执行,你将能够使用浏览器调试工具来分析脚本执行。

下一步

  • 入门指南 - 快速学习ThingsBoard相关功能。

  • 安装指南 - 学习如何在各种操作系统上安装ThingsBoard。

  • 连接设备 - 学习如何根据你的连接方式或解决方案连接设备。

  • 可 视 化 - 学习如何配置复杂的ThingsBoard仪表板说明。

  • 数据处理 - 学习如何使用ThingsBoard规则引擎。

  • 数据分析 - 学习如何使用规则引擎执行基本的分析任务。

  • 硬件样品 - 学习如何将各种硬件平台连接到ThingsBoard。

  • 高级功能 - 学习高级ThingsBoard功能。