thingsboard-aplcache
Changes
ui/src/app/common/types.constant.js 23(+22 -1)
ui/src/app/extension/index.js 3(+3 -0)
ui/src/app/locale/locale.constant.js 30(+30 -0)
Details
ui/src/app/common/types.constant.js 23(+22 -1)
diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js
index ef6ffde..66ed196 100644
--- a/ui/src/app/common/types.constant.js
+++ b/ui/src/app/common/types.constant.js
@@ -384,7 +384,8 @@ export default angular.module('thingsboard.types', [])
extensionType: {
http: "HTTP",
mqtt: "MQTT",
- opc: "OPC UA"
+ opc: "OPC UA",
+ modbus: "MODBUS"
},
extensionValueType: {
string: 'value.string',
@@ -428,6 +429,26 @@ export default angular.module('thingsboard.types', [])
PKCS12: "PKCS12",
JKS: "JKS"
},
+ extensionModbusFunctionCodes: {
+ 1: "Read Coils (1)",
+ 2: "Read Discrete Inputs (2)",
+ 3: "Read Multiple Holding Registers (3)",
+ 4: "Read Input Registers (4)"
+ },
+ extensionModbusTransports: {
+ tcp: "TCP",
+ udp: "UDP",
+ rtu: "RTU"
+ },
+ extensionModbusRtuParities: {
+ none: "none",
+ even: "even",
+ odd: "odd"
+ },
+ extensionModbusRtuEncodings: {
+ ascii: "ascii",
+ rtu: "rtu"
+ },
latestTelemetry: {
value: "LATEST_TELEMETRY",
name: "attribute.scope-latest-telemetry",
diff --git a/ui/src/app/extension/extension-dialog.controller.js b/ui/src/app/extension/extension-dialog.controller.js
index 2b0eded..4b8221a 100644
--- a/ui/src/app/extension/extension-dialog.controller.js
+++ b/ui/src/app/extension/extension-dialog.controller.js
@@ -49,7 +49,7 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
"brokers": []
};
}
- if (vm.extension.type === "OPC UA") {
+ if (vm.extension.type === "OPC UA" || vm.extension.type === "MODBUS") {
vm.extension.configuration = {
"servers": []
};
diff --git a/ui/src/app/extension/extension-dialog.tpl.html b/ui/src/app/extension/extension-dialog.tpl.html
index 4e200d5..fcf7840 100644
--- a/ui/src/app/extension/extension-dialog.tpl.html
+++ b/ui/src/app/extension/extension-dialog.tpl.html
@@ -62,6 +62,7 @@
<div tb-extension-form-http config="vm.extension.configuration" is-add="vm.isAdd" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.http"></div>
<div tb-extension-form-mqtt config="vm.extension.configuration" is-add="vm.isAdd" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.mqtt"></div>
<div tb-extension-form-opc configuration="vm.extension.configuration" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.opc"></div>
+ <div tb-extension-form-modbus configuration="vm.extension.configuration" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.modbus"></div>
</fieldset>
</md-content>
</div>
diff --git a/ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js b/ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js
new file mode 100644
index 0000000..aec5625
--- /dev/null
+++ b/ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js
@@ -0,0 +1,131 @@
+/*
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import 'brace/ext/language_tools';
+import 'brace/mode/json';
+import 'brace/theme/github';
+
+import './extension-form.scss';
+
+/* eslint-disable angular/log */
+
+import extensionFormModbusTemplate from './extension-form-modbus.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function ExtensionFormModbusDirective($compile, $templateCache, $translate, types) {
+
+
+ var linker = function(scope, element) {
+
+
+ function Server() {
+ this.transport = {
+ "type": "tcp",
+ "host": "localhost",
+ "port": 502,
+ "timeout": 3000
+ };
+ this.devices = []
+ }
+
+ function Device() {
+ this.unitId = 1;
+ this.deviceName = "";
+ this.attributesPollPeriod = 1000;
+ this.timeseriesPollPeriod = 1000;
+ this.attributes = [];
+ this.timeseries = [];
+ }
+
+ function Tag(globalPollPeriod) {
+ this.tag = "";
+ this.type = "long";
+ this.pollPeriod = globalPollPeriod;
+ this.functionCode = 3;
+ this.address = 0;
+ this.registerCount = 1;
+ this.bit = 0;
+ this.byteOrder = "BIG";
+ }
+
+
+ var template = $templateCache.get(extensionFormModbusTemplate);
+ element.html(template);
+
+ scope.types = types;
+ scope.theForm = scope.$parent.theForm;
+
+
+ if (!scope.configuration.servers.length) {
+ scope.configuration.servers.push(new Server());
+ }
+
+ scope.addServer = function(serversList) {
+ serversList.push(new Server());
+ scope.theForm.$setDirty();
+ };
+
+ scope.addDevice = function(deviceList) {
+ deviceList.push(new Device());
+ scope.theForm.$setDirty();
+ };
+
+ scope.addNewAttribute = function(device) {
+ device.attributes.push(new Tag(device.attributesPollPeriod));
+ scope.theForm.$setDirty();
+ };
+
+ scope.addNewTimeseries = function(device) {
+ device.timeseries.push(new Tag(device.timeseriesPollPeriod));
+ scope.theForm.$setDirty();
+ };
+
+ scope.removeItem = (item, itemList) => {
+ var index = itemList.indexOf(item);
+ if (index > -1) {
+ itemList.splice(index, 1);
+ }
+ scope.theForm.$setDirty();
+ };
+
+
+ $compile(element.contents())(scope);
+
+
+ scope.collapseValidation = function(index, id) {
+ var invalidState = angular.element('#'+id+':has(.ng-invalid)');
+ if(invalidState.length) {
+ invalidState.addClass('inner-invalid');
+ }
+ };
+
+ scope.expandValidation = function (index, id) {
+ var invalidState = angular.element('#'+id);
+ invalidState.removeClass('inner-invalid');
+ };
+
+ };
+
+ return {
+ restrict: "A",
+ link: linker,
+ scope: {
+ configuration: "=",
+ isAdd: "="
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/src/app/extension/extensions-forms/extension-form-modbus.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-modbus.tpl.html
new file mode 100644
index 0000000..122aeaf
--- /dev/null
+++ b/ui/src/app/extension/extensions-forms/extension-form-modbus.tpl.html
@@ -0,0 +1,818 @@
+<!--
+
+ Copyright © 2016-2018 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<md-card class="extension-form extension-modbus">
+ <md-card-title>
+ <md-card-title-text>
+ <span translate class="md-headline">extension.configuration</span>
+ </md-card-title-text>
+ </md-card-title>
+
+ <md-card-content>
+ <v-accordion id="modbus-server-configs-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)">
+ <v-pane id="modbus-servers-pane" expanded="true">
+ <v-pane-header>
+ {{ 'extension.modbus-server' | translate }}
+ </v-pane-header>
+
+ <v-pane-content>
+ <div ng-if="configuration.servers.length === 0">
+ <span translate layout-align="center center" class="tb-prompt">extension.modbus-add-server-prompt</span>
+ </div>
+
+ <div ng-if="configuration.servers.length > 0">
+ <ol class="list-group">
+ <li class="list-group-item" ng-repeat="(serverIndex, server) in configuration.servers">
+ <md-button aria-label="{{ 'action.remove' | translate }}"
+ class="md-icon-button"
+ ng-click="removeItem(server, configuration.servers)"
+ ng-hide="configuration.servers.length < 2"
+ >
+ <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'action.remove' | translate }}
+ </md-tooltip>
+ </md-button>
+
+ <md-card>
+ <md-card-content>
+
+ <div layout="row">
+
+ <md-input-container flex="50" class="md-block tb-container-for-select">
+ <label translate>extension.modbus-transport</label>
+ <md-select required
+ name="transportType_{{serverIndex}}"
+ ng-model="server.transport.type"
+ >
+ <md-option ng-value="transportType"
+ ng-repeat="(transportType, transportValue) in types.extensionModbusTransports"
+ ><span ng-bind="transportValue"></span></md-option>
+ </md-select>
+ <div ng-messages="theForm['transportType_' + serverIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ </div>
+ </md-input-container>
+
+ </div>
+
+ <div layout="row" ng-if="server.transport.type == 'tcp'">
+ <md-input-container flex="33" class="md-block">
+ <label translate>extension.host</label>
+ <input required name="transportHost_{{serverIndex}}" ng-model="server.transport.host">
+ <div ng-messages="theForm['transportHost_' + serverIndex].$error">
+ <div translate ng-message="required">extension.field-required</div>
+ </div>
+ </md-input-container>
+
+ <md-input-container flex="33" class="md-block">
+ <label translate>extension.port</label>
+ <input type="number"
+ required
+ name="transportPort_{{serverIndex}}"
+ ng-model="server.transport.port"
+ min="1"
+ max="65535"
+ >
+ <div ng-messages="theForm['transportPort_' + serverIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ <div translate
+ ng-message="min"
+ >extension.port-range</div>
+ <div translate
+ ng-message="max"
+ >extension.port-range</div>
+ </div>
+ </md-input-container>
+
+ <md-input-container flex="33" class="md-block">
+ <label translate>extension.timeout</label>
+ <input type="number"
+ required name="transportTimeout_{{serverIndex}}"
+ ng-model="server.transport.timeout"
+ >
+ <div ng-messages="theForm['transportTimeout_' + serverIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ </div>
+ </md-input-container>
+ </div>
+
+ <div layout="row" ng-if="server.transport.type == 'udp'">
+ <md-input-container flex="33" class="md-block">
+ <label translate>extension.host</label>
+ <input required name="transportHost_{{serverIndex}}" ng-model="server.transport.host">
+ <div ng-messages="theForm['transportHost_' + serverIndex].$error">
+ <div translate ng-message="required">extension.field-required</div>
+ </div>
+ </md-input-container>
+
+ <md-input-container flex="33" class="md-block">
+ <label translate>extension.port</label>
+ <input type="number"
+ required
+ name="transportPort_{{serverIndex}}"
+ ng-model="server.transport.port"
+ min="1"
+ max="65535"
+ >
+ <div ng-messages="theForm['transportPort_' + serverIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ <div translate
+ ng-message="min"
+ >extension.port-range</div>
+ <div translate
+ ng-message="max"
+ >extension.port-range</div>
+ </div>
+ </md-input-container>
+
+ <md-input-container flex="33" class="md-block">
+ <label translate>extension.timeout</label>
+ <input type="number"
+ required name="transportTimeout_{{serverIndex}}"
+ ng-model="server.transport.timeout"
+ >
+ <div ng-messages="theForm['transportTimeout_' + serverIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ </div>
+ </md-input-container>
+ </div>
+
+ <div ng-if="server.transport.type == 'rtu'">
+
+ <div layout="row">
+ <md-input-container flex="70" class="md-block">
+ <label translate>extension.modbus-port-name</label>
+ <input required name="transportPortName_{{serverIndex}}" ng-model="server.transport.portName">
+ <div ng-messages="theForm['transportPortName_' + serverIndex].$error">
+ <div translate ng-message="required">extension.field-required</div>
+ </div>
+ </md-input-container>
+
+ <md-input-container flex="30" class="md-block">
+ <label translate>extension.timeout</label>
+ <input type="number"
+ required name="transportTimeout_{{serverIndex}}"
+ ng-model="server.transport.timeout"
+ >
+ <div ng-messages="theForm['transportTimeout_' + serverIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ </div>
+ </md-input-container>
+ </div>
+
+ <div layout="row">
+ <md-input-container flex="50" class="md-block tb-container-for-select">
+ <label translate>extension.modbus-encoding</label>
+ <md-select required
+ name="transportEncoding_{{serverIndex}}"
+ ng-model="server.transport.encoding"
+ >
+ <md-option ng-value="encodingType"
+ ng-repeat="(encodingType, encodingValue) in types.extensionModbusRtuEncodings"
+ ><span ng-bind="encodingValue"></span></md-option>
+ </md-select>
+ <div ng-messages="theForm['transportEncoding_' + serverIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ </div>
+ </md-input-container>
+
+ <md-input-container flex="50" class="md-block tb-container-for-select">
+ <label translate>extension.modbus-parity</label>
+ <md-select name="transportParity_{{serverIndex}}" ng-model="server.transport.parity">
+ <md-option ng-repeat="(parityKey, parityValue) in types.extensionModbusRtuParities"
+ ng-value="parityValue"
+ >
+ {{parityValue}}
+ </md-option>
+ </md-select>
+ <div ng-messages="theForm['transportParity_' + serverIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ </div>
+ </md-input-container>
+
+ </div>
+
+ <div layout="row">
+ <md-input-container flex="33" class="md-block">
+ <label translate>extension.modbus-baudrate</label>
+ <input type="number"
+ required name="transportBaudRate_{{serverIndex}}"
+ ng-model="server.transport.baudRate"
+ >
+ <div ng-messages="theForm['transportBaudRate_' + serverIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ </div>
+ </md-input-container>
+
+ <md-input-container flex="33" class="md-block">
+ <label translate>extension.modbus-databits</label>
+ <input type="number"
+ required
+ name="transportDataBits_{{serverIndex}}"
+ ng-model="server.transport.dataBits"
+ min="7"
+ max="8"
+ >
+ <div ng-messages="theForm['transportDataBits_' + serverIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ <div translate
+ ng-message="min"
+ >extension.modbus-databits-range</div>
+ <div translate
+ ng-message="max"
+ >extension.modbus-databits-range</div>
+ </div>
+ </md-input-container>
+
+ <md-input-container flex="33" class="md-block">
+ <label translate>extension.modbus-stopbits</label>
+ <input type="number"
+ required
+ name="transportStopBits_{{serverIndex}}"
+ ng-model="server.transport.stopBits"
+ min="1"
+ max="2"
+ >
+ <div ng-messages="theForm['transportStopBits_' + serverIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ <div translate
+ ng-message="min"
+ >extension.modbus-stopbits-range</div>
+ <div translate
+ ng-message="max"
+ >extension.modbus-stopbits-range</div>
+ </div>
+ </md-input-container>
+ </div>
+ </div>
+
+ <v-accordion id="modbus-mapping-accordion"
+ class="vAccordion--default"
+ onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)">
+ <v-pane id="modbus-mapping-pane_{{serverIndex}}">
+ <v-pane-header>
+ {{ 'extension.mapping' | translate }}
+ </v-pane-header>
+ <v-pane-content>
+ <div ng-if="server.devices.length > 0">
+ <ol class="list-group">
+ <li class="list-group-item"
+ ng-repeat="(deviceIndex, device) in server.devices"
+ >
+ <md-button aria-label="{{ 'action.remove' | translate }}"
+ class="md-icon-button"
+ ng-click="removeItem(device, server.devices)"
+ >
+ <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'action.remove' | translate }}
+ </md-tooltip>
+ </md-button>
+
+ <md-card>
+ <md-card-content>
+ <div flex layout="row">
+ <md-input-container flex="80" class="md-block">
+ <label translate>extension.modbus-device-name</label>
+ <input required
+ name="deviceName_{{serverIndex}}{{deviceIndex}}"
+ ng-model="device.deviceName"
+ >
+ <div ng-messages="theForm['deviceName_' + serverIndex + deviceIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ </div>
+ </md-input-container>
+
+ <md-input-container flex="20" class="md-block">
+ <label translate>extension.modbus-unit-id</label>
+ <input type="number"
+ required
+ name="unitId_{{serverIndex}}{{deviceIndex}}"
+ ng-model="device.unitId"
+ min="1"
+ max="247"
+ >
+
+ <div ng-messages="theForm['unitId_' + serverIndex + deviceIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ <div translate
+ ng-message="min"
+ >extension.modbus-unit-id-range</div>
+ <div translate
+ ng-message="max"
+ >extension.modbus-unit-id-range</div>
+ </div>
+ </md-input-container>
+ </div>
+
+ <div flex layout="row">
+ <md-input-container flex="50" class="md-block">
+ <label translate>extension.modbus-attributes-poll-period</label>
+ <input type="number"
+ required
+ name="attributesPollPeriod_{{serverIndex}}{{deviceIndex}}"
+ ng-model="device.attributesPollPeriod"
+ min="1"
+ >
+
+ <div ng-messages="theForm['attributesPollPeriod_' + serverIndex + deviceIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ <div translate
+ ng-message="min"
+ >extension.modbus-poll-period-range</div>
+ </div>
+ </md-input-container>
+
+ <md-input-container flex="50" class="md-block">
+ <label translate>extension.modbus-timeseries-poll-period</label>
+ <input type="number"
+ required
+ name="timeseriesPollPeriod_{{serverIndex}}{{deviceIndex}}"
+ ng-model="device.timeseriesPollPeriod"
+ min="1"
+ >
+
+ <div ng-messages="theForm['timeseriesPollPeriod_' + serverIndex + deviceIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ <div translate
+ ng-message="min"
+ >extension.modbus-poll-period-range</div>
+ </div>
+ </md-input-container>
+
+ </div>
+
+
+ <v-accordion id="modbus-attributes-accordion"
+ class="vAccordion--default"
+ onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)">
+ <v-pane id="modbus-attributes-pane_{{serverIndex}}{{deviceIndex}}">
+ <v-pane-header>
+ {{ 'extension.attributes' | translate }}
+ </v-pane-header>
+ <v-pane-content>
+ <div ng-show="device.attributes.length > 0">
+ <ol class="list-group">
+ <li class="list-group-item"
+ ng-repeat="(attributeIndex, attribute) in device.attributes"
+ >
+ <md-button aria-label="{{ 'action.remove' | translate }}"
+ class="md-icon-button"
+ ng-click="removeItem(attribute, device.attributes)">
+ <ng-md-icon icon="close"
+ aria-label="{{ 'action.remove' | translate }}"
+ ></ng-md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'action.remove' | translate }}
+ </md-tooltip>
+ </md-button>
+ <md-card>
+ <md-card-content>
+
+ <section flex layout="row">
+ <md-input-container flex="50" class="md-block">
+ <label translate>extension.modbus-tag</label>
+ <input required
+ name="modbusAttributeTag_{{serverIndex}}{{deviceIndex}}{{attributeIndex}}"
+ ng-model="attribute.tag"
+ >
+ <div ng-messages="theForm['modbusAttributeTag_' + serverIndex + deviceIndex + attributeIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ </div>
+ </md-input-container>
+
+ <md-input-container flex="30" class="md-block tb-container-for-select">
+ <label translate>extension.type</label>
+ <md-select required name="modbusAttributeType_{{serverIndex}}{{deviceIndex}}{{attributeIndex}}"
+ ng-model="attribute.type"
+ >
+ <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType"
+ ng-value="attrType"
+ >
+ {{attrTypeValue | translate}}
+ </md-option>
+ </md-select>
+ <div ng-messages="theForm['modbusAttributeType_' + serverIndex + deviceIndex + attributeIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ </div>
+ </md-input-container>
+
+ <md-input-container flex="20" class="md-block">
+ <label translate>extension.modbus-poll-period</label>
+ <input type="number"
+ name="pollPeriod_{{serverIndex}}{{deviceIndex}}"
+ ng-model="attribute.pollPeriod"
+ min="1"
+ >
+
+ <div ng-messages="theForm['pollPeriod_' + serverIndex + deviceIndex].$error">
+ <div translate
+ ng-message="min"
+ >extension.modbus-poll-period-range</div>
+ </div>
+ </md-input-container>
+ </section>
+
+ <section flex layout="row">
+ <md-input-container flex="50" class="md-block tb-container-for-select">
+ <label translate>extension.type</label>
+ <md-select required name="modbusAttributeFunctionCode_{{serverIndex}}{{deviceIndex}}{{attributeIndex}}"
+ ng-model="attribute.functionCode"
+ >
+ <md-option ng-repeat="(functionCode, functionName) in types.extensionModbusFunctionCodes"
+ ng-value="functionCode"
+ >
+ {{functionName}}
+ </md-option>
+ </md-select>
+ <div ng-messages="theForm['modbusAttributeFunctionCode_' + serverIndex + deviceIndex + attributeIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ </div>
+ </md-input-container>
+
+ <md-input-container flex="50" class="md-block">
+ <label translate>extension.modbus-register-address</label>
+ <input type="number"
+ required
+ name="address_{{serverIndex}}{{deviceIndex}}"
+ ng-model="attribute.address"
+ min="0"
+ max="65535"
+ >
+
+ <div ng-messages="theForm['address_' + serverIndex + deviceIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ <div translate
+ ng-message="min"
+ >extension.modbus-register-address-range</div>
+ <div translate
+ ng-message="max"
+ >extension.modbus-register-address-range</div>
+ </div>
+ </md-input-container>
+
+ </section>
+
+ <section flex layout="row">
+ <md-input-container flex="50" class="md-block" ng-if="attribute.type != 'boolean'">
+ <label translate>extension.modbus-register-count</label>
+ <input type="number"
+ name="registerCount_{{serverIndex}}{{deviceIndex}}"
+ ng-model="attribute.registerCount"
+ min="1"
+ >
+
+ <div ng-messages="theForm['registerCount_' + serverIndex + deviceIndex].$error">
+ <div translate
+ ng-message="min"
+ >extension.modbus-register-count-range</div>
+ </div>
+ </md-input-container>
+
+ <md-input-container flex="50" class="md-block" ng-if="attribute.type == 'boolean' && attribute.functionCode >= 3">
+ <label translate>extension.modbus-register-bit-index</label>
+ <input type="number"
+ required
+ name="bit_{{serverIndex}}{{deviceIndex}}"
+ ng-model="attribute.bit"
+ min="0"
+ max="15"
+ >
+
+ <div ng-messages="theForm['bit_' + serverIndex + deviceIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ <div translate
+ ng-message="min"
+ >extension.modbus-register-bit-index-range</div>
+ <div translate
+ ng-message="max"
+ >extension.modbus-register-bit-index-range</div>
+ </div>
+ </md-input-container>
+ </section>
+
+ <section flex layout="row">
+ <md-input-container flex="100" class="md-block" ng-if="attribute.functionCode >= 3">
+ <label translate>extension.modbus-byte-order</label>
+ <input required
+ name="modbusByteOrder_{{serverIndex}}{{deviceIndex}}{{attributeIndex}}"
+ ng-model="attribute.byteOrder"
+ >
+ <div ng-messages="theForm['modbusByteOrder_' + serverIndex + deviceIndex + attributeIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ </div>
+ </md-input-container>
+
+ </section>
+
+
+ </md-card-content>
+ </md-card>
+ </li>
+ </ol>
+ </div>
+ <div flex layout="row" layout-align="start center">
+ <md-button class="md-primary md-raised"
+ ng-click="addNewAttribute(device)"
+ aria-label="{{ 'action.add' | translate }}"
+ >
+ <md-icon class="material-icons">add</md-icon>
+ <span translate>extension.add-attribute</span>
+ </md-button>
+ </div>
+ </v-pane-content>
+ </v-pane>
+ </v-accordion>
+
+ <v-accordion id="modbus-timeseries-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)">
+ <v-pane id="modbus-timeseries-pane_{{serverIndex}}{{deviceIndex}}">
+ <v-pane-header>
+ {{ 'extension.timeseries' | translate }}
+ </v-pane-header>
+ <v-pane-content>
+ <div ng-show="device.timeseries.length > 0">
+ <ol class="list-group">
+ <li class="list-group-item"
+ ng-repeat="(timeseriesIndex, timeserie) in device.timeseries"
+ >
+ <md-button aria-label="{{ 'action.remove' | translate }}"
+ class="md-icon-button"
+ ng-click="removeItem(timeserie, device.timeseries)">
+ <ng-md-icon icon="close"
+ aria-label="{{ 'action.remove' | translate }}"
+ ></ng-md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'action.remove' | translate }}
+ </md-tooltip>
+ </md-button>
+ <md-card>
+ <md-card-content>
+
+ <section flex layout="row">
+ <md-input-container flex="50" class="md-block">
+ <label translate>extension.modbus-tag</label>
+ <input required
+ name="modbusTimeserieTag_{{serverIndex}}{{deviceIndex}}{{timeseriesIndex}}"
+ ng-model="timeserie.tag"
+ >
+ <div ng-messages="theForm['modbusTimeserieTag_' + serverIndex + deviceIndex + timeseriesIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ </div>
+ </md-input-container>
+
+ <md-input-container flex="30" class="md-block tb-container-for-select">
+ <label translate>extension.type</label>
+ <md-select required name="modbusTimeserieType_{{serverIndex}}{{deviceIndex}}{{timeseriesIndex}}"
+ ng-model="timeserie.type"
+ >
+ <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType"
+ ng-value="attrType"
+ >
+ {{attrTypeValue | translate}}
+ </md-option>
+ </md-select>
+ <div ng-messages="theForm['modbusTimeserieType_' + serverIndex + deviceIndex + timeseriesIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ </div>
+ </md-input-container>
+
+ <md-input-container flex="20" class="md-block">
+ <label translate>extension.modbus-poll-period</label>
+ <input type="number"
+ name="pollPeriod_{{serverIndex}}{{deviceIndex}}"
+ ng-model="timeserie.pollPeriod"
+ min="1"
+ >
+
+ <div ng-messages="theForm['pollPeriod_' + serverIndex + deviceIndex].$error">
+ <div translate
+ ng-message="min"
+ >extension.modbus-poll-period-range</div>
+ </div>
+ </md-input-container>
+ </section>
+
+ <section flex layout="row">
+ <md-input-container flex="50" class="md-block tb-container-for-select">
+ <label translate>extension.type</label>
+ <md-select required name="modbusTimeserieFunctionCode_{{serverIndex}}{{deviceIndex}}{{timeseriesIndex}}"
+ ng-model="timeserie.functionCode"
+ >
+ <md-option ng-repeat="(functionCode, functionName) in types.extensionModbusFunctionCodes"
+ ng-value="functionCode"
+ >
+ {{functionName}}
+ </md-option>
+ </md-select>
+ <div ng-messages="theForm['modbusTimeserieFunctionCode_' + serverIndex + deviceIndex + timeseriesIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ </div>
+ </md-input-container>
+
+ <md-input-container flex="50" class="md-block">
+ <label translate>extension.modbus-register-address</label>
+ <input type="number"
+ required
+ name="address_{{serverIndex}}{{deviceIndex}}"
+ ng-model="timeserie.address"
+ min="0"
+ max="65535"
+ >
+
+ <div ng-messages="theForm['address_' + serverIndex + deviceIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ <div translate
+ ng-message="min"
+ >extension.modbus-register-address-range</div>
+ <div translate
+ ng-message="max"
+ >extension.modbus-register-address-range</div>
+ </div>
+ </md-input-container>
+ </section>
+
+ <section flex layout="row">
+ <md-input-container flex="50" class="md-block" ng-if="timeserie.type != 'boolean'">
+ <label translate>extension.modbus-register-count</label>
+ <input type="number"
+ name="registerCount_{{serverIndex}}{{deviceIndex}}"
+ ng-model="timeserie.registerCount"
+ min="1"
+ >
+
+ <div ng-messages="theForm['registerCount_' + serverIndex + deviceIndex].$error">
+ <div translate
+ ng-message="min"
+ >extension.modbus-register-count-range</div>
+ </div>
+ </md-input-container>
+
+ <md-input-container flex="50" class="md-block" ng-if="timeserie.type == 'boolean' && timeserie.functionCode >= 3">
+ <label translate>extension.modbus-register-bit-index</label>
+ <input type="number"
+ required
+ name="bit_{{serverIndex}}{{deviceIndex}}"
+ ng-model="timeserie.bit"
+ min="0"
+ max="15"
+ >
+
+ <div ng-messages="theForm['bit_' + serverIndex + deviceIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ <div translate
+ ng-message="min"
+ >extension.modbus-register-bit-index-range</div>
+ <div translate
+ ng-message="max"
+ >extension.modbus-register-bit-index-range</div>
+ </div>
+ </md-input-container>
+ </section>
+
+ <section flex layout="row">
+ <md-input-container flex="100" class="md-block" ng-if="timeserie.functionCode >= 3">
+ <label translate>extension.modbus-byte-order</label>
+ <input required
+ name="modbusByteOrder_{{serverIndex}}{{deviceIndex}}{{timeseriesIndex}}"
+ ng-model="timeserie.byteOrder"
+ >
+ <div ng-messages="theForm['modbusByteOrder_' + serverIndex + deviceIndex + timeseriesIndex].$error">
+ <div translate
+ ng-message="required"
+ >extension.field-required</div>
+ </div>
+ </md-input-container>
+
+ </section>
+ </md-card-content>
+ </md-card>
+ </li>
+ </ol>
+ </div>
+ <div flex layout="row" layout-align="start center">
+ <md-button class="md-primary md-raised"
+ ng-click="addNewTimeseries(device)"
+ aria-label="{{ 'action.add' | translate }}"
+ >
+ <md-icon class="material-icons">add</md-icon>
+ <span translate>extension.add-timeseries</span>
+ </md-button>
+ </div>
+ </v-pane-content>
+ </v-pane>
+ </v-accordion>
+
+
+ </md-card-content>
+ </md-card>
+ </li>
+ </ol>
+ </div>
+ <div flex
+ layout="row"
+ layout-align="start center"
+ >
+ <md-button class="md-primary md-raised"
+ ng-click="addDevice(server.devices)"
+ aria-label="{{ 'action.add' | translate }}"
+ >
+ <md-icon class="material-icons">add</md-icon>
+ <span translate>extension.add-device</span>
+ </md-button>
+ </div>
+ </v-pane-content>
+ </v-pane>
+ </v-accordion>
+
+ </md-card-content>
+ </md-card>
+ </li>
+ </ol>
+
+ <div flex
+ layout="row"
+ layout-align="start center"
+ >
+ <md-button class="md-primary md-raised"
+ ng-click="addServer(configuration.servers)"
+ aria-label="{{ 'action.add' | translate }}"
+ >
+ <md-icon class="material-icons">add</md-icon>
+ <span translate>extension.modbus-add-server</span>
+ </md-button>
+ </div>
+
+ </div>
+ </v-pane-content>
+ </v-pane>
+ </v-accordion>
+ <!--{{config}}-->
+ </md-card-content>
+</md-card>
\ No newline at end of file
ui/src/app/extension/index.js 3(+3 -0)
diff --git a/ui/src/app/extension/index.js b/ui/src/app/extension/index.js
index f04880c..8e29348 100644
--- a/ui/src/app/extension/index.js
+++ b/ui/src/app/extension/index.js
@@ -17,6 +17,8 @@ import ExtensionTableDirective from './extension-table.directive';
import ExtensionFormHttpDirective from './extensions-forms/extension-form-http.directive';
import ExtensionFormMqttDirective from './extensions-forms/extension-form-mqtt.directive'
import ExtensionFormOpcDirective from './extensions-forms/extension-form-opc.directive';
+import ExtensionFormModbusDirective from './extensions-forms/extension-form-modbus.directive';
+
import {ParseToNull} from './extension-dialog.controller';
export default angular.module('thingsboard.extension', [])
@@ -24,5 +26,6 @@ export default angular.module('thingsboard.extension', [])
.directive('tbExtensionFormHttp', ExtensionFormHttpDirective)
.directive('tbExtensionFormMqtt', ExtensionFormMqttDirective)
.directive('tbExtensionFormOpc', ExtensionFormOpcDirective)
+ .directive('tbExtensionFormModbus', ExtensionFormModbusDirective)
.directive('parseToNull', ParseToNull)
.name;
\ No newline at end of file
ui/src/app/locale/locale.constant.js 30(+30 -0)
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index 252884d..0f0a1df 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -866,8 +866,10 @@ export default angular.module('thingsboard.locale', [])
"response-timeout": "Response timeout in milliseconds",
"topic-expression": "Topic expression",
"client-scope": "Client scope",
+ "add-device": "Add device",
"opc-server": "Servers",
"opc-add-server": "Add server",
+ "opc-add-server-prompt": "Please add server",
"opc-application-name": "Application name",
"opc-application-uri": "Application uri",
"opc-scan-period-in-seconds": "Scan period in seconds",
@@ -882,6 +884,34 @@ export default angular.module('thingsboard.locale', [])
"opc-keystore-key-password":"Key password",
"opc-device-node-pattern":"Device node pattern",
"opc-device-name-pattern":"Device name pattern",
+ "modbus-server": "Servers/slaves",
+ "modbus-add-server": "Add server/slave",
+ "modbus-add-server-prompt": "Please add server/slave",
+ "modbus-transport": "Transport",
+ "modbus-port-name": "Serial port name",
+ "modbus-encoding": "Encoding",
+ "modbus-parity": "Parity",
+ "modbus-baudrate": "Baud rate",
+ "modbus-databits": "Data bits",
+ "modbus-stopbits": "Stop bits",
+ "modbus-databits-range": "Data bits should be in a range from 7 to 8.",
+ "modbus-stopbits-range": "Stop bits should be in a range from 1 to 2.",
+ "modbus-unit-id": "Unit ID",
+ "modbus-unit-id-range": "Unit ID should be in a range from 1 to 247.",
+ "modbus-device-name":"Device name",
+ "modbus-poll-period": "Poll period (ms)",
+ "modbus-attributes-poll-period": "Attributes poll period (ms)",
+ "modbus-timeseries-poll-period": "Timeseries poll period (ms)",
+ "modbus-poll-period-range": "Poll period should be positive value.",
+ "modbus-tag": "Tag",
+ "modbus-function": "Function",
+ "modbus-register-address": "Register address",
+ "modbus-register-address-range": "Register address should be in a range from 0 to 65535.",
+ "modbus-register-bit-index": "Bit index",
+ "modbus-register-bit-index-range": "Bit index should be in a range from 0 to 15.",
+ "modbus-register-count": "Register count",
+ "modbus-register-count-range": "Register count should be a positive value.",
+ "modbus-byte-order": "Byte order",
"sync": {
"status": "Status",