diff --git a/application/src/main/data/json/system/widget_bundles/control_widgets.json b/application/src/main/data/json/system/widget_bundles/control_widgets.json
index b05a144c0a..ce667afe47 100644
--- a/application/src/main/data/json/system/widget_bundles/control_widgets.json
+++ b/application/src/main/data/json/system/widget_bundles/control_widgets.json
@@ -48,9 +48,25 @@
"templateHtml": "",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onResize = function() {\n if (self.ctx.resize) {\n self.ctx.resize();\n }\n}\n\nself.onDestroy = function() {\n}\n",
- "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\",\n \"default\": 0\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\",\n \"default\": 100\n },\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"number\",\n \"default\": 50\n },\n \"title\": {\n \"title\": \"Knob title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"getValueMethod\": {\n \"title\": \"Get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"Set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"minValue\", \"maxValue\", \"getValueMethod\", \"setValueMethod\", \"requestTimeout\"]\n },\n \"form\": [\n \"minValue\",\n \"maxValue\",\n \"initialValue\",\n \"getValueMethod\",\n \"setValueMethod\",\n \"title\",\n \"requestTimeout\"\n ]\n}",
+ "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\",\n \"default\": 0\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\",\n \"default\": 100\n },\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"number\",\n \"default\": 50\n },\n \"title\": {\n \"title\": \"Knob title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"getValueMethod\": {\n \"title\": \"Get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"Set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"minValue\", \"maxValue\", \"getValueMethod\", \"setValueMethod\", \"requestTimeout\"]\n },\n \"form\": [\n \"minValue\",\n \"maxValue\",\n \"initialValue\",\n \"title\",\n \"getValueMethod\",\n \"setValueMethod\",\n \"requestTimeout\"\n ]\n}",
"dataKeySettingsSchema": "{}\n",
- "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"maxValue\":100,\"initialValue\":50,\"minValue\":0,\"title\":\"Knob control\"},\"title\":\"Knob Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}"
+ "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"maxValue\":100,\"initialValue\":50,\"minValue\":0,\"title\":\"Knob control\",\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\"},\"title\":\"Knob Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}"
+ }
+ },
+ {
+ "alias": "switch_control",
+ "name": "Switch Control",
+ "descriptor": {
+ "type": "rpc",
+ "sizeX": 4,
+ "sizeY": 2.5,
+ "resources": [],
+ "templateHtml": "",
+ "templateCss": "",
+ "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onResize = function() {\n if (self.ctx.resize) {\n self.ctx.resize();\n }\n}\n\nself.onDestroy = function() {\n}\n",
+ "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showOnOffLabels\": {\n \"title\": \"Show on/off labels\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"getValueMethod\": {\n \"title\": \"Get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"Set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"getValueMethod\", \"setValueMethod\", \"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n \"showOnOffLabels\",\n \"getValueMethod\",\n \"setValueMethod\",\n \"requestTimeout\"\n ]\n}",
+ "dataKeySettingsSchema": "{}\n",
+ "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":false,\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\",\"showOnOffLabels\":true,\"title\":\"Switch control\"},\"title\":\"Switch Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}"
}
}
]
diff --git a/ui/src/app/widget/lib/rpc/index.js b/ui/src/app/widget/lib/rpc/index.js
index ba07eee559..3edbdff9aa 100644
--- a/ui/src/app/widget/lib/rpc/index.js
+++ b/ui/src/app/widget/lib/rpc/index.js
@@ -15,7 +15,9 @@
*/
import tbKnob from './knob.directive';
+import tbSwitch from './switch.directive';
export default angular.module('thingsboard.widgets.rpc', [
- tbKnob
+ tbKnob,
+ tbSwitch
]).name;
diff --git a/ui/src/app/widget/lib/rpc/knob.directive.js b/ui/src/app/widget/lib/rpc/knob.directive.js
index d62d0fbba8..f921854a3b 100644
--- a/ui/src/app/widget/lib/rpc/knob.directive.js
+++ b/ui/src/app/widget/lib/rpc/knob.directive.js
@@ -63,7 +63,7 @@ function KnobController($element, $scope, $document) {
knobTitleContainer = knob.find('.title-container'),
knobTitle = knob.find('.knob-title'),
knobMinmaxContainer = knob.find('.minmax-container'),
- minmaxLanel = knob.find('.minmax-label'),
+ minmaxLabel = knob.find('.minmax-label'),
textMeasure = knob.find('#text-measure'),
startDeg = -1,
currentDeg = 0,
@@ -92,8 +92,6 @@ function KnobController($element, $scope, $document) {
vm.maxValue = angular.isDefined(vm.ctx.settings.maxValue) ? vm.ctx.settings.maxValue : 100;
vm.title = angular.isDefined(vm.ctx.settings.title) ? vm.ctx.settings.title : '';
- vm.darkTheme = vm.ctx.settings.theme == 'dark';
-
var canvasBarData = {
renderTo: canvasBarElement[0],
hideValue: true,
@@ -267,7 +265,7 @@ function KnobController($element, $scope, $document) {
setFontSize(knobTitle, vm.title, knobTitleContainer.height(), knobTitleContainer.width());
setFontSize(knobError, vm.error, knobErrorContainer.height(), knobErrorContainer.width());
var minmaxHeight = knobMinmaxContainer.height();
- minmaxLanel.css({'fontSize': minmaxHeight+'px', 'lineHeight': minmaxHeight+'px'});
+ minmaxLabel.css({'fontSize': minmaxHeight+'px', 'lineHeight': minmaxHeight+'px'});
checkValueSize();
}
diff --git a/ui/src/app/widget/lib/rpc/knob.scss b/ui/src/app/widget/lib/rpc/knob.scss
index 3e57f663c0..afd9077876 100644
--- a/ui/src/app/widget/lib/rpc/knob.scss
+++ b/ui/src/app/widget/lib/rpc/knob.scss
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-$knob-img: url('./knob.svg');
+$knob-img: url('./svg/knob.svg');
$bars-margin-pct: percentage(0.033);
$background-margin-pct: percentage(0.05);
@@ -33,9 +33,6 @@ $background-color: #e6e7e8;
width:100%;
height:100%;
background: $background-color;
- &.dark {
- background: #000;
- }
.knob {
position: relative;
diff --git a/ui/src/app/widget/lib/rpc/knob.tpl.html b/ui/src/app/widget/lib/rpc/knob.tpl.html
index 93276b44ed..ffd670fb3c 100644
--- a/ui/src/app/widget/lib/rpc/knob.tpl.html
+++ b/ui/src/app/widget/lib/rpc/knob.tpl.html
@@ -16,7 +16,7 @@
-->
-
+
diff --git a/ui/src/app/widget/lib/rpc/knob.svg b/ui/src/app/widget/lib/rpc/svg/knob.svg
similarity index 100%
rename from ui/src/app/widget/lib/rpc/knob.svg
rename to ui/src/app/widget/lib/rpc/svg/knob.svg
diff --git a/ui/src/app/widget/lib/rpc/svg/thumb-bar-checked.svg b/ui/src/app/widget/lib/rpc/svg/thumb-bar-checked.svg
new file mode 100644
index 0000000000..57fa2b423e
--- /dev/null
+++ b/ui/src/app/widget/lib/rpc/svg/thumb-bar-checked.svg
@@ -0,0 +1,43 @@
+
diff --git a/ui/src/app/widget/lib/rpc/svg/thumb-bar.svg b/ui/src/app/widget/lib/rpc/svg/thumb-bar.svg
new file mode 100644
index 0000000000..0b952933a4
--- /dev/null
+++ b/ui/src/app/widget/lib/rpc/svg/thumb-bar.svg
@@ -0,0 +1,42 @@
+
diff --git a/ui/src/app/widget/lib/rpc/svg/thumb-checked.svg b/ui/src/app/widget/lib/rpc/svg/thumb-checked.svg
new file mode 100644
index 0000000000..6d59b94736
--- /dev/null
+++ b/ui/src/app/widget/lib/rpc/svg/thumb-checked.svg
@@ -0,0 +1,40 @@
+
diff --git a/ui/src/app/widget/lib/rpc/svg/thumb.svg b/ui/src/app/widget/lib/rpc/svg/thumb.svg
new file mode 100644
index 0000000000..03e9ea0f77
--- /dev/null
+++ b/ui/src/app/widget/lib/rpc/svg/thumb.svg
@@ -0,0 +1,37 @@
+
diff --git a/ui/src/app/widget/lib/rpc/switch.directive.js b/ui/src/app/widget/lib/rpc/switch.directive.js
new file mode 100644
index 0000000000..b84b2f1c36
--- /dev/null
+++ b/ui/src/app/widget/lib/rpc/switch.directive.js
@@ -0,0 +1,214 @@
+/*
+ * Copyright © 2016-2017 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 './switch.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import switchTemplate from './switch.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+export default angular.module('thingsboard.widgets.rpc.switch', [])
+ .directive('tbSwitch', Switch)
+ .name;
+
+/*@ngInject*/
+function Switch() {
+ return {
+ restrict: "E",
+ scope: true,
+ bindToController: {
+ ctx: '='
+ },
+ controller: SwitchController,
+ controllerAs: 'vm',
+ templateUrl: switchTemplate
+ };
+}
+
+/*@ngInject*/
+function SwitchController($element, $scope) {
+ let vm = this;
+
+ vm.showTitle = false;
+ vm.value = false;
+ vm.error = '';
+
+ var switchElement = angular.element('.switch', $element),
+ switchContainer = angular.element('#switch-container', $element),
+ mdSwitch = angular.element('md-switch', switchElement),
+ onoffContainer = angular.element('.onoff-container', $element),
+ onLabel = angular.element('.on-label', $element),
+ offLabel = angular.element('.off-label', $element),
+ switchTitleContainer = angular.element('.title-container', $element),
+ switchTitle = angular.element('.switch-title', $element),
+ textMeasure = angular.element('#text-measure', $element),
+ switchErrorContainer = angular.element('.error-container', $element),
+ switchError = angular.element('.switch-error', $element);
+
+
+ vm.onValue = onValue;
+
+ $scope.$watch('vm.ctx', () => {
+ if (vm.ctx) {
+ init();
+ }
+ });
+
+ function init() {
+
+ vm.title = angular.isDefined(vm.ctx.settings.title) ? vm.ctx.settings.title : '';
+ vm.showTitle = vm.title && vm.title.length ? true : false;
+ vm.showOnOffLabels = angular.isDefined(vm.ctx.settings.showOnOffLabels) ? vm.ctx.settings.showOnOffLabels : true;
+ vm.ctx.resize = resize;
+ $scope.$applyAsync(() => {
+ resize();
+ });
+ var initialValue = angular.isDefined(vm.ctx.settings.initialValue) ? vm.ctx.settings.initialValue : false;
+ setValue(initialValue);
+
+ var subscription = vm.ctx.defaultSubscription;
+ var rpcEnabled = subscription.rpcEnabled;
+
+ vm.isSimulated = $scope.widgetEditMode;
+
+ vm.requestTimeout = 500;
+ if (vm.ctx.settings.requestTimeout) {
+ vm.requestTimeout = vm.ctx.settings.requestTimeout;
+ }
+ vm.getValueMethod = 'getValue';
+ if (vm.ctx.settings.getValueMethod && vm.ctx.settings.getValueMethod.length) {
+ vm.getValueMethod = vm.ctx.settings.getValueMethod;
+ }
+ vm.setValueMethod = 'setValue';
+ if (vm.ctx.settings.setValueMethod && vm.ctx.settings.setValueMethod.length) {
+ vm.setValueMethod = vm.ctx.settings.setValueMethod;
+ }
+ if (!rpcEnabled) {
+ onError('Target device is not set!');
+ } else {
+ if (!vm.isSimulated) {
+ rpcRequestValue();
+ }
+ }
+ }
+
+ const switchAspectRation = 2.7893;
+
+ function resize() {
+ var width = switchContainer.width();
+ var height;
+ if (vm.showOnOffLabels) {
+ height = switchContainer.height()*2/3;
+ } else {
+ height = switchContainer.height();
+ }
+ var ratio = width/height;
+ if (ratio > switchAspectRation) {
+ width = height*switchAspectRation;
+ } else {
+ height = width/switchAspectRation;
+ }
+ switchElement.css({width: width, height: height});
+ mdSwitch.css('height', height+'px');
+ mdSwitch.css('width', width+'px');
+ mdSwitch.css('min-width', width+'px');
+ angular.element('.md-container', mdSwitch).css('height', height+'px');
+ angular.element('.md-container', mdSwitch).css('width', width+'px');
+
+
+ if (vm.showTitle) {
+ setFontSize(switchTitle, vm.title, switchTitleContainer.height() * 2 / 3, switchTitleContainer.width());
+ }
+
+ if (vm.showOnOffLabels) {
+ onoffContainer.css({width: width, height: switchContainer.height() / 3});
+ setFontSize(onLabel, 'OFF', onoffContainer.height(), onoffContainer.width() / 2);
+ setFontSize(offLabel, 'OFF', onoffContainer.height(), onoffContainer.width() / 2);
+ }
+
+ setFontSize(switchError, vm.error, switchErrorContainer.height(), switchErrorContainer.width());
+ }
+
+ function setValue(value) {
+ vm.value = value ? true : false;
+ }
+
+ function onValue() {
+ rpcUpdateValue(vm.value);
+ }
+
+ function onError(error) {
+ $scope.$applyAsync(() => {
+ vm.error = error;
+ setFontSize(switchError, vm.error, switchErrorContainer.height(), switchErrorContainer.width());
+ });
+ }
+
+ function setFontSize(element, text, fontSize, maxWidth) {
+ var textWidth = measureTextWidth(text, fontSize);
+ while (textWidth > maxWidth) {
+ fontSize--;
+ textWidth = measureTextWidth(text, fontSize);
+ }
+ element.css({'fontSize': fontSize+'px', 'lineHeight': fontSize+'px'});
+ }
+
+ function measureTextWidth(text, fontSize) {
+ textMeasure.css({'fontSize': fontSize+'px', 'lineHeight': fontSize+'px'});
+ textMeasure.text(text);
+ return textMeasure.width();
+ }
+
+ function rpcRequestValue() {
+ vm.error = '';
+ vm.ctx.controlApi.sendTwoWayCommand(vm.getValueMethod, null, vm.requestTimeout).then(
+ (responseBody) => {
+ setValue(responseBody);
+ },
+ () => {
+ var errorText = vm.ctx.defaultSubscription.rpcErrorText;
+ onError(errorText);
+ }
+ );
+ }
+
+ function rpcUpdateValue(value) {
+ if (vm.executingUpdateValue) {
+ vm.scheduledValue = value;
+ return;
+ } else {
+ vm.scheduledValue = null;
+ vm.rpcValue = value;
+ vm.executingUpdateValue = true;
+ }
+ vm.error = '';
+ vm.ctx.controlApi.sendOneWayCommand(vm.setValueMethod, value, vm.requestTimeout).then(
+ () => {
+ vm.executingUpdateValue = false;
+ if (vm.scheduledValue != null && vm.scheduledValue != vm.rpcValue) {
+ rpcUpdateValue(vm.scheduledValue);
+ }
+ },
+ () => {
+ vm.executingUpdateValue = false;
+ var errorText = vm.ctx.defaultSubscription.rpcErrorText;
+ onError(errorText);
+ }
+ );
+ }
+}
diff --git a/ui/src/app/widget/lib/rpc/switch.scss b/ui/src/app/widget/lib/rpc/switch.scss
new file mode 100644
index 0000000000..9d696328e4
--- /dev/null
+++ b/ui/src/app/widget/lib/rpc/switch.scss
@@ -0,0 +1,127 @@
+/**
+ * Copyright © 2016-2017 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.
+ */
+
+$thumb-img: url('./svg/thumb.svg');
+$thumb-checked-img: url('./svg/thumb-checked.svg');
+$thumb-bar-img: url('./svg/thumb-bar.svg');
+$thumb-bar-checked-img: url('./svg/thumb-bar-checked.svg');
+
+$background-color: #e6e7e8;
+
+$error-height: 14px;
+
+.tb-switch {
+ width:100%;
+ height:100%;
+ background: $background-color;
+
+ .error-container {
+ position:absolute;
+ top: 1%;
+ left: 0;
+ right: 0;
+ z-index:4;
+ height: $error-height;
+ .switch-error {
+ color: #ff3315;
+ white-space: nowrap;
+ }
+ }
+
+ .onoff-container {
+ height: 100%;
+ color: #757575;
+ font-weight: 500;
+ white-space: nowrap;
+ .off-label {
+ color: #b7b5b5;
+ }
+ .on-label {
+ color: #ff7e57;
+ text-shadow: #ff6e4a 1px 1px 10px, #ffd1c3 1px 1px 10px;
+ }
+ }
+ .title-container {
+ .switch-title {
+ color: #757575;
+ font-weight: 500;
+ white-space: nowrap;
+ }
+ }
+
+ #switch-container {
+ padding-left: 10px;
+ padding-right: 10px;
+ }
+ .switch {
+ position: relative;
+ md-switch {
+ margin: 0;
+ position:absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ .md-container {
+ margin: 0;
+ }
+ .md-bar {
+ left: 0;
+ width: 100%;
+ top: 0;
+ height: 100%;
+ border-radius: 0;
+ background:$thumb-bar-img no-repeat;
+ background-size: contain;
+ }
+ .md-thumb-container {
+ left: 0.25%;
+ width: 50%;
+ top: 5%;
+ height: 90%;
+ }
+ .md-thumb {
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 100%;
+ background:$thumb-img no-repeat;
+ background-size: contain;
+ border-radius: 0;
+ box-shadow: none;
+ }
+
+ &.md-checked {
+ .md-bar {
+ background:$thumb-bar-checked-img no-repeat;
+ background-size: contain;
+ }
+ .md-thumb {
+ background:$thumb-checked-img no-repeat;
+ background-size: contain;
+ }
+ }
+
+ }
+ }
+ #text-measure {
+ position: absolute;
+ visibility: hidden;
+ height: auto;
+ width: auto;
+ white-space: nowrap;
+ }
+}
diff --git a/ui/src/app/widget/lib/rpc/switch.tpl.html b/ui/src/app/widget/lib/rpc/switch.tpl.html
new file mode 100644
index 0000000000..387b1596ac
--- /dev/null
+++ b/ui/src/app/widget/lib/rpc/switch.tpl.html
@@ -0,0 +1,40 @@
+
+
+
+
+ {{ vm.error }}
+
+
+ {{vm.title}}
+
+
+
+
+
+
+
+ OFF
+
+ ON
+
+
+
+
+