ThingsBoard小部件属于UI模块可以与任何IoT仪表板集成并提供最终功能给用户,例如数据可视化,远程设备控制,警报管理和显示静态自定义html内容。
根据提供的功能每个部件定义代表特定的部件类型。
为了创建新的部件定义请导航“部件库”。
然后打开现有的“部件捆绑”或创建一个新的。
在“部件捆绑”视图中,单击屏幕右下角的大“ +”按钮,然后单击“创建新的窗口小部件类型”按钮。
选择窗口部件类型会弹出窗口会,提示你选择要开发的相应部件类型。
之后,将根据先前选择的窗口小部件类型打开“窗口部件编辑器”页面,该页面预填充了启动器窗口部件模板。
它由工具栏和四个主要部分组成:
窗口部件编辑器工具栏包含以下各项:
第一个resources选项卡用于指定窗口部件使用的外部JavaScript/CSS资源。
第二个HTML选项卡包含部件html代码(注意:此内容可以为空并由代码动态创建)。
第三个CSS选项卡包含部件的CSS样式定义。
本节包含根据部件API的所有与窗口部件相关的JavaScript代码。
第一个设置模式标签用于指定部件设置的json模式并使用react-schema-form builder。
在生成的UI表单上显示部件设置的高级选项卡。
设置序列化对象存储部件然后从部件Javascript代码访问。
第二个数据键设置选项卡用于指定数据键为json模式并使用react-schema-form builder。
在生成的UI表单上显示部件设置的数据键配置选项卡。
设置序列化对象存储部件数据源的特定数据键。
这些设置可以通过部件JavaScript代码访问。
本部分用于预览和测试窗口中部件定义。
以迷你仪表板的形式通过当前窗口显示部件。
具有通常的ThingsBoard仪表板提供的几乎所有功能但有一些限制。
例如:出于调试目的在窗口中部件数据源部分中只能选择“功能”作为数据源类型。
所有与部件相关的代码都位于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 | 介绍使用运行时集属性(例如widgetTitle,hideTitlePanel等)更新小部件。必须调用这些属性才能使这些属性更改生效。 |
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 = [
{ // 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".
},
//...
]
},
//...
]
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 = {
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 = [
{ // 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.
}
]
对于其他部件类型例如RPC或Static订阅对象是可选的不包含必要的信息。
Time-series或Alarm部件可以使用(TimewindowFunctions)管理部件数据的时间范围
函数 | 描述 |
---|---|
onUpdateTimewindow(startTimeMs, endTimeMs) |
此功能可用于将当前订阅时间范围更新为由startTimeMs和endTimeMs参数标识的历史时间范围。 |
onResetTimewindow() |
根据窗口部件设置将订阅时间范围重置为由窗口部件时间窗口组件或仪表板时间窗口定义的默认时间范围。 |
提供RPC (Control)部件的(RpcApi)函数。
函数 | 描述 |
---|---|
sendOneWayCommand(method, params, timeout) |
向设备发送一种方法(无响应)RPC命令。返回命令执行承诺。method -RPC方法名称,字符串,params -RPC方法参数,自定义json对象,timeout -等待接收到响应/确认之前的最大延迟(以毫秒为单位)。 |
sendTwoWayCommand(method, params, timeout) |
设备发送两种方式(带有响应)的RPC命令。在成功回调中返回带有响应主体的命令执行承诺。 |
操作API提供的(WidgetActionsApi)函数。
函数 | 描述 |
---|---|
getActionDescriptors(actionSourceId) |
返回提供的actionSourceId的动作描述符的列表 |
handleWidgetAction($event, descriptor, entityId, entityName) |
处理特定动作源产生的动作。$event -与Action相关的事件对象descriptoraction描述符, entityId和entityName -当前实体ID和名称(如果可用)由动作源提供。 |
仪表板状态控制器(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属性:
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)分配用户操作:
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。
在件捆绑包视图中单击屏幕右下角的“+”大按钮,然后单击创建部件类型按钮。
在选择部件类型弹出窗口中单击“新值”按钮。
打开部件编辑器”,并预先填充默认的新值模板部件的内容。
<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>
self.onInit = function() {
self.ctx.$scope.data = self.ctx.defaultSubscription.data;
}
self.onDataUpdated = function() {
self.ctx.detectChanges();
}
在此示例中,subscription的data属性被分配给 $scope 并且可以在HTML模板中访问。
在HTML内部,使用特殊的ng-repeat角度指令来迭代可用的dataKeys数据点并使用其时间戳呈现相应的最新值。
在部件捆绑包视图中,单击屏幕右下角的“+”大按钮,然后单击创建部件类型按钮。
在此示例中subscription的data属性被分配给$scope并且可以在HTML模板中访问。
在HTML中使用了一个特殊的*ngFor结构指令来迭代可用的dataKeys和数据然后渲染最新数据。
在选择部件类型弹出窗口中单击时间系列按钮。
将打开部件编辑器其中预填充了默认的时间系列模板小部件的内容。
.my-data-table th {
text-align: left;
}
<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>
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();
}
在此示例中,subscription的datasources和data属性被分配给$scope并且可以在HTML模板中访问。
引入了datasourceData范围属性,以通过数据源索引映射数据源特定的dataKeys数据以便在HTML模板中进行灵活访问。
在HTML内部,使用特殊的ng-repeat角度指令来迭代可用的数据源并呈现相应的选项卡。
在每个选项卡内,使用从datasource索引访问的datasourceData范围属性获取的dataKeys数据呈现表。
每个表都通过遍历所有dataKeyData对象来呈现列,并通过遍历每个dataKeyData的data数组以呈现时间戳和值来呈现所有可用的数据点。
请注意此代码中的onDataUpdated函数是通过调用有角度的 $digest 函数实现的,该函数对于接收新数据时执行新的渲染周期是必需的。
在这个例子中subscriptiondatasources和data属性被分配给$scope并且可以在HTML模板中访问。
引入$scope.datasourceData属性以通过数据源索引映射数据源特定的dataKeys数据以便在HTML模板中访问。
在HTML中使用一个特殊的*ngFor结构指令来迭代可用数据源并呈现相应的选项卡。
在每个选项卡内使用从索引的方式访问的datasourceData范围属性获取的dataKeys。
通过迭代所有dataKeyData对象来呈现并通过迭代每个dataKeyData的data数组显示所有可用数据和时间戳。
请注意在此代码中onDataUpdated函数通过调用detectChanges函数来实现以便在接收到新数据时执行新的更改检测周期。
在选择部件类型弹出窗口中单击控制部件按钮。
将打开部件编辑器并预先填充默认的控制模板小部件的内容。
<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"
]
}
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命令的某些设备。为此,请执行以下步骤:
以下输出应在设备控制台中打印:
request.topic: v1/devices/me/rpc/request/0
request.body: {"method":"TestMethod","params":"{ param1: \"value1\" }"}
为了测试“Two way”” RPC命令模式,我们需要更改相应的窗口小部件设置属性。为此,请执行以下步骤:
在此示例中controlApi用于发送RPC命令。此外,引入了自定义窗口小部件设置以配置RPC命令模式和RPC请求超时。
设备的响应由具有成功和失败回调的commandPromise进行处理,并带有包含有关请求执行结果信息的相应响应或拒绝对象。
在部件捆绑视图中单击屏幕右下角的“+”按钮然后单击创建部件类型按钮。
在选择部件类型弹出窗口中单击警报部件按钮。
使用默认的Alarm模板小部件的内容预填充打开部件编辑器。
.my-alarm-table th {
text-align: left;
}
<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>
{
"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"
}
]
}
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();
}
在此示例中订阅的alarmSource和alarms属性被分配给$scope并且可以在HTML模板中访问。
在HTML内部使用angular的ng-repeat指令进行遍历alarmSource可用警报的dataKeys并呈现相应的列。
在表中通过遍历alarms数组,显示对应的单元格dataKeys。
函数getAlarmValue是使用特殊的AlarmFields常量用来获取警报值该常量是从ThingsBoard UI的types中获取的并且可以通过Angular$injector进行访问。
在此示例中subscription的alarmSource和alarms属性被分配给$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模板小部件的内容。
<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>
{
"schema": {
"type": "object",
"title": "Settings",
"properties": {
"alertContent": {
"title": "Alert content",
"type": "string",
"default": "Content derived from alertContent property of widget settings."
}
}
},
"form": [
"alertContent"
]
}
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);
};
}
这只是一个静态HTML小部件因此没有订阅数据或使用了特殊的小部件API。
仅实现了自定义showAlert函数该函数显示具有小部件设置的alertContent属性内容的警报。
你可以在部件预览部分切换到仪表盘编辑模式,并通过在小部件详细信息的“高级”选项卡中更改小部件设置来更改alertContent的值。
然后你可以看到显示了新的警报内容。
以下是一些示例,这些示例演示如何重用/集成外部JavaScript库或现有代码以创建新的小部件。
在此示例中,将使用外部gauge.js库创建Latest Values仪表小部件。
在部件捆绑视图中单击屏幕右下角的“+”大按钮,然后单击创建部件类型按钮。
在此示例中Latest Values仪表小部件将使用外部gauge.js库创建。
在选择部件类型弹出窗口中单击Latest Values按钮。
将打开部件编辑器并预先填充默认的Latest Values模板小部件的内容。
https://bernii.github.io/gauge.js/dist/gauge.min.js
<canvas id="my-gauge"></canvas>
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);
}
在此示例中,使用了外部JS库的API,该API在Resources部分中注入相应的URL后才可用。
显示的值是从第一个dataKey的subscription data属性获得的。
创建窗口部件的另一种方法是使用现有的捆绑JavaScript代码。
在这种情况下,你可以创建自己的JavaScript类或Angular指令并将其捆绑到ThingsBoard UI代码中。
为了使此代码可在窗口部件中访问,你需要注册相应的Angular模块或将JavaScript类注入到全局变量(例如,对于window对象)。
一些ThingsBoard小部件已经使用这种方法。看看widget.service.js。
在这里,你可以找到如何注册一些捆绑的类或模块以供以后在ThingsBoard小部件中使用。
例如Timeseries-Flot小部件(来自Charts部件捆绑包)使用TbFlotJavaScript类,将其作为窗口属性注入widget.service.js中:
在此示例中将使用外部Chart.js库创建Time-Series折线图小部件。
在Widgets Bundle视图中单击屏幕右下角的大“+”按钮然后单击“创建新的小部件类型”按钮。
单击Select widget type弹出窗口上的Time-Series按钮。
打开Widget Editor并预先填充默认Time-Series模板小部件的内容。
https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js
<canvas id="myChart"></canvas>
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();
}
在这个例子中使用了外部JS库API它在Resources部分注入相应的URL后变得可用。
最初使用来自ctx的data属性的配置数据键准备图表数据集。
在onDataUpdated函数中将数据源数据转换为Chart.js折线图格式并推送到图表数据集。
请注意xAxis(时间轴)仅限于从ctx的timeWindow属性获得的当前时间窗口边界。
创建小部件的另一种方法是使用现有的捆绑JavaScript代码。
在这种情况下您可以创建自己的TypeScript类或Angular组件并将其捆绑到ThingsBoard UI代码中。
为了使该代码在小部件内可访问您需要注册相应的Angular模块或将TypeScript类注入全局变量(例如 window 对象)。
一些ThingsBoard小部件已经使用了这种方法看看polyfills.ts 或widget-components.module.ts。
在这里您可以找到如何注册一些捆绑的类或组件以供以后在ThingsBoard小部件中使用。
例如“Timeseries - Flot”小部件(来自“Charts”小部件捆绑包)使用 TbFlotTypeScript 类作为窗口属性注入polyfills.ts:
...
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中使用。
...
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功能。