From aebe4c5b2078565e182c65a8b76a89a64aa6654a Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 28 Apr 2022 16:38:58 +0300 Subject: [PATCH] UI: Add GPIO widgets settings forms. --- .../system/widget_bundles/gpio_widgets.json | 12 +- .../data-key-config-dialog.component.html | 1 + .../data-key-config-dialog.component.ts | 2 + .../widget/data-key-config.component.html | 1 + .../widget/data-key-config.component.ts | 4 + .../components/widget/data-keys.component.ts | 1 + .../label-widget-settings.component.html | 2 +- .../cards/label-widget-settings.component.ts | 25 +-- ...logue-gauge-widget-settings.component.html | 2 +- ...nalogue-gauge-widget-settings.component.ts | 29 ++-- ...digital-gauge-widget-settings.component.ts | 65 +++++--- ...pio-control-widget-settings.component.html | 96 +++++++++++ .../gpio-control-widget-settings.component.ts | 145 +++++++++++++++++ .../settings/gpio/gpio-item.component.html | 73 +++++++++ .../settings/gpio/gpio-item.component.scss | 40 +++++ .../lib/settings/gpio/gpio-item.component.ts | 150 ++++++++++++++++++ .../gpio-panel-widget-settings.component.html | 55 +++++++ .../gpio-panel-widget-settings.component.ts | 114 +++++++++++++ .../lib/settings/widget-settings.module.ts | 21 ++- .../components/json-content.component.html | 2 +- .../components/json-content.component.ts | 9 ++ ui-ngx/src/app/shared/models/widget.models.ts | 7 +- .../assets/locale/locale.constant-en_US.json | 20 +++ 23 files changed, 820 insertions(+), 56 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-control-widget-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-control-widget-settings.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-item.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-item.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-item.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-panel-widget-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-panel-widget-settings.component.ts diff --git a/application/src/main/data/json/system/widget_bundles/gpio_widgets.json b/application/src/main/data/json/system/widget_bundles/gpio_widgets.json index 41583a4a8f..e539824aca 100644 --- a/application/src/main/data/json/system/widget_bundles/gpio_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/gpio_widgets.json @@ -19,8 +19,9 @@ "templateHtml": "
\n
\n
\n
\n {{ cell.label }}\n
\n {{cell.pin}}\n \n \n \n \n {{cell.pin}}\n
\n {{ cell.label }}\n
\n
\n \n \n \n
\n
\n
\n {{rpcErrorText}}\n \n
", "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.gpio-panel section[fxflex] {\n min-width: 0px;\n}\n\n\n.switch-panel {\n margin: 0;\n height: 32px;\n width: 66px;\n min-width: 66px;\n}\n\n.switch-panel mat-slide-toggle {\n margin: 0;\n width: 36px;\n min-width: 36px;\n}\n\n.switch-panel.col-0 mat-slide-toggle {\n margin-left: 8px;\n margin-right: 4px;\n}\n\n.switch-panel.col-1 mat-slide-toggle {\n margin-left: 4px;\n margin-right: 8px;\n}\n\n.gpio-row {\n height: 32px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.switch-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.switch-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", "controllerScript": "var namespace;\nvar cssParser = new cssjs();\n\nself.onInit = function() {\n var utils = self.ctx.$injector.get(self.ctx.servicesMap.get('utils'));\n namespace = 'gpio-control-' + utils.guid();\n cssParser.testMode = false;\n cssParser.cssPreviewNamespace = namespace;\n self.ctx.$container.addClass(namespace);\n self.ctx.ngZone.run(function() {\n init(); \n });\n}\n\nfunction init() {\n \n var i, gpio;\n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n scope.gpioList = [];\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false\n }\n );\n }\n\n scope.requestTimeout = settings.requestTimeout || 1000;\n\n scope.switchPanelBackgroundColor = settings.switchPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioStatusRequest = {\n method: \"getGpioStatus\",\n paramsBody: \"{}\"\n };\n \n if (settings.gpioStatusRequest) {\n scope.gpioStatusRequest.method = settings.gpioStatusRequest.method || scope.gpioStatusRequest.method;\n scope.gpioStatusRequest.paramsBody = settings.gpioStatusRequest.paramsBody || scope.gpioStatusRequest.paramsBody;\n }\n \n scope.gpioStatusChangeRequest = {\n method: \"setGpioStatus\",\n paramsBody: \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n };\n \n if (settings.gpioStatusChangeRequest) {\n scope.gpioStatusChangeRequest.method = settings.gpioStatusChangeRequest.method || scope.gpioStatusChangeRequest.method;\n scope.gpioStatusChangeRequest.paramsBody = settings.gpioStatusChangeRequest.paramsBody || scope.gpioStatusChangeRequest.paramsBody;\n }\n \n scope.parseGpioStatusFunction = \"return body[pin] === true;\";\n \n if (settings.parseGpioStatusFunction && settings.parseGpioStatusFunction.length > 0) {\n scope.parseGpioStatusFunction = settings.parseGpioStatusFunction;\n }\n \n scope.parseGpioStatusFunction = new Function(\"body, pin\", scope.parseGpioStatusFunction);\n \n function requestGpioStatus() {\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusRequest.method, \n scope.gpioStatusRequest.paramsBody, \n scope.requestTimeout)\n .subscribe(\n function success(responseBody) {\n for (var g = 0; g < scope.gpioList.length; g++) {\n var gpio = scope.gpioList[g];\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled; \n self.ctx.detectChanges();\n }\n }\n );\n }\n \n function changeGpioStatus(gpio) {\n var pin = gpio.pin + '';\n var enabled = !gpio.enabled;\n enabled = enabled === true ? 'true' : 'false';\n var paramsBody = scope.gpioStatusChangeRequest.paramsBody;\n var requestBody = JSON.parse(paramsBody.replace(\"\\\"{$pin}\\\"\", pin).replace(\"\\\"{$enabled}\\\"\", enabled));\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusChangeRequest.method, \n requestBody, scope.requestTimeout)\n .subscribe(\n function success(responseBody) {\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled;\n self.ctx.detectChanges();\n }\n );\n }\n \n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n }\n\n scope.gpioClick = function($event, gpio) {\n if (scope.rpcEnabled && !scope.executingRpcRequest) {\n changeGpioStatus(gpio);\n }\n };\n \n scope.gpioToggleChange = function($event, gpio) {\n gpio.enabled = !$event.checked;\n $event.source.toggle();\n self.ctx.detectChanges();\n }\n \n if (scope.rpcEnabled) {\n requestGpioStatus(); \n }\n \n self.onResize();\n}\n\nself.onResize = function() {\n var scope = self.ctx.$scope;\n var rowCount = scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n scope.prefferedRowHeight = prefferedRowHeight;\n var ratio = prefferedRowHeight/32;\n \n var css = '.mat-slide-toggle .mat-slide-toggle-bar {\\n' +\n ' height: ' + 14*ratio+'px;\\n'+\n ' width: ' + 36*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-thumb-container {\\n' +\n ' height: ' + 20*ratio+'px;\\n'+\n ' width: ' + 20*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-thumb {\\n' +\n ' height: ' + 20*ratio+'px;\\n'+\n ' width: ' + 20*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-ripple {\\n' +\n ' height: ' + 40*ratio+'px;\\n'+\n ' width: ' + 40*ratio+'px;\\n'+\n ' top: calc(50% - '+20*ratio+'px);\\n'+\n ' left: calc(50% - '+20*ratio+'px);\\n'+\n '}\\n';\n css += '.gpio-left-label, .gpio-right-label {\\n' +\n ' font-size: ' + 16*ratio+'px;\\n'+\n '}\\n';\n var pinsFontSize = Math.max(9, 12*ratio);\n css += '.pin {\\n' +\n ' font-size: ' + pinsFontSize+'px;\\n'+\n '}\\n';\n\n cssParser.createStyleElement(namespace, css);\n \n self.ctx.detectChanges();\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"gpioList\": {\n \"title\": \"Gpio switches\",\n \"type\": \"array\",\n \"minItems\" : 1,\n \"items\": {\n \"title\": \"Gpio switch\",\n \"type\": \"object\",\n \"properties\": {\n \"pin\": {\n \"title\": \"Pin\",\n \"type\": \"number\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n },\n \"row\": {\n \"title\": \"Row\",\n \"type\": \"number\"\n },\n \"col\": {\n \"title\": \"Column\",\n \"type\": \"number\"\n }\n },\n \"required\": [\"pin\", \"label\", \"row\", \"col\"]\n }\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"switchPanelBackgroundColor\": {\n \"title\": \"Switches panel background color\",\n \"type\": \"string\",\n \"default\": \"#008a00\"\n },\n \"gpioStatusRequest\": {\n \"title\": \"GPIO status request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"getGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"gpioStatusChangeRequest\": {\n \"title\": \"GPIO status change request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"setGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"parseGpioStatusFunction\": {\n \"title\": \"Parse gpio status function\",\n \"type\": \"string\",\n \"default\": \"return body[pin] === true;\"\n } \n },\n \"required\": [\"gpioList\", \n \"requestTimeout\",\n \"switchPanelBackgroundColor\",\n \"gpioStatusRequest\",\n \"gpioStatusChangeRequest\",\n \"parseGpioStatusFunction\"]\n },\n \"form\": [\n \"gpioList\",\n \"requestTimeout\",\n {\n \"key\": \"switchPanelBackgroundColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"gpioStatusRequest\",\n \"items\": [\n \"gpioStatusRequest.method\",\n {\n \"key\": \"gpioStatusRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"gpioStatusChangeRequest\",\n \"items\": [\n \"gpioStatusChangeRequest.method\",\n {\n \"key\": \"gpioStatusChangeRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"parseGpioStatusFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/rpc/parse_gpio_status_fn\"\n }\n ]\n}", + "settingsSchema": "", "dataKeySettingsSchema": "{}\n", + "settingsDirective": "tb-gpio-control-widget-settings", "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"Basic GPIO Control\"}" } }, @@ -37,8 +38,9 @@ "templateHtml": "
\n
\n
\n
\n {{ cell.label }}\n
\n {{cell.pin}}\n \n \n \n \n {{cell.pin}}\n
\n {{ cell.label }}\n
\n
\n \n \n \n
\n
\n
\n
", "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.gpio-panel section[fxflex] {\n min-width: 0px;\n}\n\n\n.gpio-panel tb-led-light > div {\n margin: auto;\n}\n\n.led-panel {\n margin: 0;\n width: 66px;\n min-width: 66px;\n}\n\n.led-container {\n width: 48px;\n min-width: 48px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.led-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.led-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", "controllerScript": "var namespace;\nvar cssParser = new cssjs();\n\nself.onInit = function() {\n var utils = self.ctx.$injector.get(self.ctx.servicesMap.get('utils'));\n namespace = 'gpio-panel-' + utils.guid();\n cssParser.testMode = false;\n cssParser.cssPreviewNamespace = namespace;\n self.ctx.$container.addClass(namespace);\n self.ctx.ngZone.run(function() {\n init(); \n });\n}\n\nfunction init() {\n var i, gpio;\n \n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n \n scope.gpioList = [];\n scope.gpioByPin = {};\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false,\n colorOn: tinycolor(gpio.color).lighten(20).toHexString(),\n colorOff: tinycolor(gpio.color).darken().toHexString()\n }\n );\n scope.gpioByPin[gpio.pin] = scope.gpioList[scope.gpioList.length-1];\n }\n\n scope.ledPanelBackgroundColor = settings.ledPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n } \n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var changed = false;\n for (var d = 0; d < self.ctx.data.length; d++) {\n var cellData = self.ctx.data[d];\n var dataKey = cellData.dataKey;\n var gpio = self.ctx.$scope.gpioByPin[dataKey.label];\n if (gpio) {\n var enabled = false;\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n enabled = (tvPair[1] === true || tvPair[1] === 'true');\n }\n if (gpio.enabled != enabled) {\n changed = true;\n gpio.enabled = enabled;\n }\n }\n }\n if (changed) {\n self.ctx.detectChanges();\n } \n}\n\nself.onResize = function() {\n var rowCount = self.ctx.$scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n self.ctx.$scope.prefferedRowHeight = prefferedRowHeight;\n \n var ratio = prefferedRowHeight/32;\n \n var css = '.gpio-left-label, .gpio-right-label {\\n' +\n ' font-size: ' + 16*ratio+'px;\\n'+\n '}\\n';\n var pinsFontSize = Math.max(9, 12*ratio);\n css += '.pin {\\n' +\n ' font-size: ' + pinsFontSize+'px;\\n'+\n '}\\n';\n \n cssParser.createStyleElement(namespace, css); \n \n self.ctx.detectChanges();\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"gpioList\": {\n \"title\": \"Gpio leds\",\n \"type\": \"array\",\n \"minItems\" : 1,\n \"items\": {\n \"title\": \"Gpio led\",\n \"type\": \"object\",\n \"properties\": {\n \"pin\": {\n \"title\": \"Pin\",\n \"type\": \"number\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n },\n \"row\": {\n \"title\": \"Row\",\n \"type\": \"number\"\n },\n \"col\": {\n \"title\": \"Column\",\n \"type\": \"number\"\n },\n \"color\": {\n \"title\": \"Color\",\n \"type\": \"string\",\n \"default\": \"red\"\n }\n },\n \"required\": [\"pin\", \"label\", \"row\", \"col\", \"color\"]\n }\n },\n \"ledPanelBackgroundColor\": {\n \"title\": \"LED panel background color\",\n \"type\": \"string\",\n \"default\": \"#008a00\"\n } \n },\n \"required\": [\"gpioList\", \n \"ledPanelBackgroundColor\"]\n },\n \"form\": [\n {\n \"key\": \"gpioList\",\n \"items\": [\n \"gpioList[].pin\",\n \"gpioList[].label\",\n \"gpioList[].row\",\n \"gpioList[].col\",\n {\n \"key\": \"gpioList[].color\",\n \"type\": \"color\"\n }\n ]\n },\n {\n \"key\": \"ledPanelBackgroundColor\",\n \"type\": \"color\"\n }\n ]\n}", + "settingsSchema": "", "dataKeySettingsSchema": "{}\n", + "settingsDirective": "tb-gpio-panel-widget-settings", "defaultConfig": "{\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"color\":\"#008000\",\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"color\":\"#ffff00\",\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"color\":\"#cf006f\",\"_uniqueKey\":2}],\"ledPanelBackgroundColor\":\"#b71c1c\"},\"title\":\"Basic GPIO Panel\",\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"1\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.22518255793320163,\"funcBody\":\"var period = time % 1500;\\nreturn period < 500;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"2\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.7008206860666621,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 500 && period < 1000;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"3\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.42600325102193426,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 1000;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}}}" } }, @@ -55,8 +57,9 @@ "templateHtml": "
\n
\n
\n
\n {{ cell.label }}\n
\n {{cell.pin}}\n \n \n \n \n {{cell.pin}}\n
\n {{ cell.label }}\n
\n
\n \n \n \n
\n
\n
\n
", "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.gpio-panel section[fxflex] {\n min-width: 0px;\n}\n\n.gpio-panel tb-led-light > div {\n margin: auto;\n}\n\n.led-panel {\n margin: 0;\n width: 66px;\n min-width: 66px;\n}\n\n.led-container {\n width: 48px;\n min-width: 48px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.led-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.led-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", "controllerScript": "var namespace;\nvar cssParser = new cssjs();\n\nself.onInit = function() {\n var utils = self.ctx.$injector.get(self.ctx.servicesMap.get('utils'));\n namespace = 'gpio-panel-' + utils.guid();\n cssParser.testMode = false;\n cssParser.cssPreviewNamespace = namespace;\n self.ctx.$container.addClass(namespace);\n self.ctx.ngZone.run(function() {\n init(); \n });\n}\n\nfunction init() {\n var i, gpio;\n \n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n \n scope.gpioList = [];\n scope.gpioByPin = {};\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false,\n colorOn: tinycolor(gpio.color).lighten(20).toHexString(),\n colorOff: tinycolor(gpio.color).darken().toHexString()\n }\n );\n scope.gpioByPin[gpio.pin] = scope.gpioList[scope.gpioList.length-1];\n }\n\n scope.ledPanelBackgroundColor = settings.ledPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n } \n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var changed = false;\n for (var d = 0; d < self.ctx.data.length; d++) {\n var cellData = self.ctx.data[d];\n var dataKey = cellData.dataKey;\n var gpio = self.ctx.$scope.gpioByPin[dataKey.label];\n if (gpio) {\n var enabled = false;\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n enabled = (tvPair[1] === true || tvPair[1] === 'true');\n }\n if (gpio.enabled != enabled) {\n changed = true;\n gpio.enabled = enabled;\n }\n }\n }\n if (changed) {\n self.ctx.detectChanges();\n } \n}\n\nself.onResize = function() {\n var rowCount = self.ctx.$scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n self.ctx.$scope.prefferedRowHeight = prefferedRowHeight;\n \n var ratio = prefferedRowHeight/32;\n \n var css = '.gpio-left-label, .gpio-right-label {\\n' +\n ' font-size: ' + 16*ratio+'px;\\n'+\n '}\\n';\n var pinsFontSize = Math.max(9, 12*ratio);\n css += '.pin {\\n' +\n ' font-size: ' + pinsFontSize+'px;\\n'+\n '}\\n';\n \n cssParser.createStyleElement(namespace, css); \n \n self.ctx.detectChanges();\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"gpioList\": {\n \"title\": \"Gpio leds\",\n \"type\": \"array\",\n \"minItems\" : 1,\n \"items\": {\n \"title\": \"Gpio led\",\n \"type\": \"object\",\n \"properties\": {\n \"pin\": {\n \"title\": \"Pin\",\n \"type\": \"number\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n },\n \"row\": {\n \"title\": \"Row\",\n \"type\": \"number\"\n },\n \"col\": {\n \"title\": \"Column\",\n \"type\": \"number\"\n },\n \"color\": {\n \"title\": \"Color\",\n \"type\": \"string\",\n \"default\": \"red\"\n }\n },\n \"required\": [\"pin\", \"label\", \"row\", \"col\", \"color\"]\n }\n },\n \"ledPanelBackgroundColor\": {\n \"title\": \"LED panel background color\",\n \"type\": \"string\",\n \"default\": \"#008a00\"\n } \n },\n \"required\": [\"gpioList\", \n \"ledPanelBackgroundColor\"]\n },\n \"form\": [\n {\n \"key\": \"gpioList\",\n \"items\": [\n \"gpioList[].pin\",\n \"gpioList[].label\",\n \"gpioList[].row\",\n \"gpioList[].col\",\n {\n \"key\": \"gpioList[].color\",\n \"type\": \"color\"\n }\n ]\n },\n {\n \"key\": \"ledPanelBackgroundColor\",\n \"type\": \"color\"\n }\n ]\n}", + "settingsSchema": "", "dataKeySettingsSchema": "{}\n", + "settingsDirective": "tb-gpio-panel-widget-settings", "defaultConfig": "{\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"gpioList\":[{\"pin\":1,\"label\":\"3.3V\",\"row\":0,\"col\":0,\"color\":\"#fc9700\",\"_uniqueKey\":0},{\"pin\":2,\"label\":\"5V\",\"row\":0,\"col\":1,\"color\":\"#fb0000\",\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 2 (I2C1_SDA)\",\"row\":1,\"col\":0,\"color\":\"#02fefb\",\"_uniqueKey\":2},{\"color\":\"#fb0000\",\"pin\":4,\"label\":\"5V\",\"row\":1,\"col\":1},{\"color\":\"#02fefb\",\"pin\":5,\"label\":\"GPIO 3 (I2C1_SCL)\",\"row\":2,\"col\":0},{\"color\":\"#000000\",\"pin\":6,\"label\":\"GND\",\"row\":2,\"col\":1},{\"color\":\"#00fd00\",\"pin\":7,\"label\":\"GPIO 4 (GPCLK0)\",\"row\":3,\"col\":0},{\"color\":\"#fdfb00\",\"pin\":8,\"label\":\"GPIO 14 (UART_TXD)\",\"row\":3,\"col\":1},{\"color\":\"#000000\",\"pin\":9,\"label\":\"GND\",\"row\":4,\"col\":0},{\"color\":\"#fdfb00\",\"pin\":10,\"label\":\"GPIO 15 (UART_RXD)\",\"row\":4,\"col\":1},{\"color\":\"#00fd00\",\"pin\":11,\"label\":\"GPIO 17\",\"row\":5,\"col\":0},{\"color\":\"#00fd00\",\"pin\":12,\"label\":\"GPIO 18\",\"row\":5,\"col\":1},{\"color\":\"#00fd00\",\"pin\":13,\"label\":\"GPIO 27\",\"row\":6,\"col\":0},{\"color\":\"#000000\",\"pin\":14,\"label\":\"GND\",\"row\":6,\"col\":1},{\"color\":\"#00fd00\",\"pin\":15,\"label\":\"GPIO 22\",\"row\":7,\"col\":0},{\"color\":\"#00fd00\",\"pin\":16,\"label\":\"GPIO 23\",\"row\":7,\"col\":1},{\"color\":\"#fc9700\",\"pin\":17,\"label\":\"3.3V\",\"row\":8,\"col\":0},{\"color\":\"#00fd00\",\"pin\":18,\"label\":\"GPIO 24\",\"row\":8,\"col\":1},{\"color\":\"#fd01fd\",\"pin\":19,\"label\":\"GPIO 10 (SPI_MOSI)\",\"row\":9,\"col\":0},{\"color\":\"#000000\",\"pin\":20,\"label\":\"GND\",\"row\":9,\"col\":1},{\"color\":\"#fd01fd\",\"pin\":21,\"label\":\"GPIO 9 (SPI_MISO)\",\"row\":10,\"col\":0},{\"color\":\"#00fd00\",\"pin\":22,\"label\":\"GPIO 25\",\"row\":10,\"col\":1},{\"color\":\"#fd01fd\",\"pin\":23,\"label\":\"GPIO 11 (SPI_SCLK)\",\"row\":11,\"col\":0},{\"color\":\"#fd01fd\",\"pin\":24,\"label\":\"GPIO 8 (SPI_CE0)\",\"row\":11,\"col\":1},{\"color\":\"#000000\",\"pin\":25,\"label\":\"GND\",\"row\":12,\"col\":0},{\"color\":\"#fd01fd\",\"pin\":26,\"label\":\"GPIO 7 (SPI_CE1)\",\"row\":12,\"col\":1},{\"color\":\"#ffffff\",\"pin\":27,\"label\":\"ID_SD\",\"row\":13,\"col\":0},{\"color\":\"#ffffff\",\"pin\":28,\"label\":\"ID_SC\",\"row\":13,\"col\":1},{\"color\":\"#00fd00\",\"pin\":29,\"label\":\"GPIO 5\",\"row\":14,\"col\":0},{\"color\":\"#000000\",\"pin\":30,\"label\":\"GND\",\"row\":14,\"col\":1},{\"color\":\"#00fd00\",\"pin\":31,\"label\":\"GPIO 6\",\"row\":15,\"col\":0},{\"color\":\"#00fd00\",\"pin\":32,\"label\":\"GPIO 12\",\"row\":15,\"col\":1},{\"color\":\"#00fd00\",\"pin\":33,\"label\":\"GPIO 13\",\"row\":16,\"col\":0},{\"color\":\"#000000\",\"pin\":34,\"label\":\"GND\",\"row\":16,\"col\":1},{\"color\":\"#00fd00\",\"pin\":35,\"label\":\"GPIO 19\",\"row\":17,\"col\":0},{\"color\":\"#00fd00\",\"pin\":36,\"label\":\"GPIO 16\",\"row\":17,\"col\":1},{\"color\":\"#00fd00\",\"pin\":37,\"label\":\"GPIO 26\",\"row\":18,\"col\":0},{\"color\":\"#00fd00\",\"pin\":38,\"label\":\"GPIO 20\",\"row\":18,\"col\":1},{\"color\":\"#000000\",\"pin\":39,\"label\":\"GND\",\"row\":19,\"col\":0},{\"color\":\"#00fd00\",\"pin\":40,\"label\":\"GPIO 21\",\"row\":19,\"col\":1}],\"ledPanelBackgroundColor\":\"#008a00\"},\"title\":\"Raspberry Pi GPIO Panel\",\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"7\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.22518255793320163,\"funcBody\":\"var period = time % 1500;\\nreturn period < 500;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"11\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.7008206860666621,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 500 && period < 1000;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"12\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.42600325102193426,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 1000;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"13\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.48362241571415243,\"funcBody\":\"var period = time % 1500;\\nreturn period < 500;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"29\",\"color\":\"#607d8b\",\"settings\":{},\"_hash\":0.7217670147518815,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 500 && period < 1000;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}}}" } }, @@ -73,8 +76,9 @@ "templateHtml": "
\n
\n
\n
\n {{ cell.label }}\n
\n {{cell.pin}}\n \n \n \n \n {{cell.pin}}\n
\n {{ cell.label }}\n
\n
\n \n \n \n
\n
\n
\n {{rpcErrorText}}\n \n
", "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.gpio-panel section[fxflex] {\n min-width: 0px;\n}\n\n.switch-panel {\n margin: 0;\n height: 32px;\n width: 66px;\n min-width: 66px;\n}\n\n.switch-panel mat-slide-toggle {\n margin: 0;\n width: 36px;\n min-width: 36px;\n}\n\n.switch-panel.col-0 mat-slide-toggle {\n margin-left: 8px;\n margin-right: 4px;\n}\n\n.switch-panel.col-1 mat-slide-toggle {\n margin-left: 4px;\n margin-right: 8px;\n}\n\n.gpio-row {\n height: 32px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.switch-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.switch-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", "controllerScript": "var namespace;\nvar cssParser = new cssjs();\n\nself.onInit = function() {\n var utils = self.ctx.$injector.get(self.ctx.servicesMap.get('utils'));\n namespace = 'gpio-control-' + utils.guid();\n cssParser.testMode = false;\n cssParser.cssPreviewNamespace = namespace;\n self.ctx.$container.addClass(namespace);\n self.ctx.ngZone.run(function() {\n init(); \n });\n}\n\nfunction init() {\n \n var i, gpio;\n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n scope.gpioList = [];\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false\n }\n );\n }\n\n scope.requestTimeout = settings.requestTimeout || 1000;\n\n scope.switchPanelBackgroundColor = settings.switchPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioStatusRequest = {\n method: \"getGpioStatus\",\n paramsBody: \"{}\"\n };\n \n if (settings.gpioStatusRequest) {\n scope.gpioStatusRequest.method = settings.gpioStatusRequest.method || scope.gpioStatusRequest.method;\n scope.gpioStatusRequest.paramsBody = settings.gpioStatusRequest.paramsBody || scope.gpioStatusRequest.paramsBody;\n }\n \n scope.gpioStatusChangeRequest = {\n method: \"setGpioStatus\",\n paramsBody: \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n };\n \n if (settings.gpioStatusChangeRequest) {\n scope.gpioStatusChangeRequest.method = settings.gpioStatusChangeRequest.method || scope.gpioStatusChangeRequest.method;\n scope.gpioStatusChangeRequest.paramsBody = settings.gpioStatusChangeRequest.paramsBody || scope.gpioStatusChangeRequest.paramsBody;\n }\n \n scope.parseGpioStatusFunction = \"return body[pin] === true;\";\n \n if (settings.parseGpioStatusFunction && settings.parseGpioStatusFunction.length > 0) {\n scope.parseGpioStatusFunction = settings.parseGpioStatusFunction;\n }\n \n scope.parseGpioStatusFunction = new Function(\"body, pin\", scope.parseGpioStatusFunction);\n \n function requestGpioStatus() {\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusRequest.method, \n scope.gpioStatusRequest.paramsBody, \n scope.requestTimeout)\n .subscribe(\n function success(responseBody) {\n for (var g = 0; g < scope.gpioList.length; g++) {\n var gpio = scope.gpioList[g];\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled; \n self.ctx.detectChanges();\n }\n }\n );\n }\n \n function changeGpioStatus(gpio) {\n var pin = gpio.pin + '';\n var enabled = !gpio.enabled;\n enabled = enabled === true ? 'true' : 'false';\n var paramsBody = scope.gpioStatusChangeRequest.paramsBody;\n var requestBody = JSON.parse(paramsBody.replace(\"\\\"{$pin}\\\"\", pin).replace(\"\\\"{$enabled}\\\"\", enabled));\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusChangeRequest.method, \n requestBody, scope.requestTimeout)\n .subscribe(\n function success(responseBody) {\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled;\n self.ctx.detectChanges();\n }\n );\n }\n \n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n }\n\n scope.gpioClick = function($event, gpio) {\n if (scope.rpcEnabled && !scope.executingRpcRequest) {\n changeGpioStatus(gpio);\n }\n };\n \n scope.gpioToggleChange = function($event, gpio) {\n gpio.enabled = !$event.checked;\n $event.source.toggle();\n self.ctx.detectChanges();\n }\n \n if (scope.rpcEnabled) {\n requestGpioStatus(); \n }\n \n self.onResize();\n}\n\nself.onResize = function() {\n var scope = self.ctx.$scope;\n var rowCount = scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n scope.prefferedRowHeight = prefferedRowHeight;\n var ratio = prefferedRowHeight/32;\n \n var css = '.mat-slide-toggle .mat-slide-toggle-bar {\\n' +\n ' height: ' + 14*ratio+'px;\\n'+\n ' width: ' + 36*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-thumb-container {\\n' +\n ' height: ' + 20*ratio+'px;\\n'+\n ' width: ' + 20*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-thumb {\\n' +\n ' height: ' + 20*ratio+'px;\\n'+\n ' width: ' + 20*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-ripple {\\n' +\n ' height: ' + 40*ratio+'px;\\n'+\n ' width: ' + 40*ratio+'px;\\n'+\n ' top: calc(50% - '+20*ratio+'px);\\n'+\n ' left: calc(50% - '+20*ratio+'px);\\n'+\n '}\\n';\n css += '.gpio-left-label, .gpio-right-label {\\n' +\n ' font-size: ' + 16*ratio+'px;\\n'+\n '}\\n';\n var pinsFontSize = Math.max(9, 12*ratio);\n css += '.pin {\\n' +\n ' font-size: ' + pinsFontSize+'px;\\n'+\n '}\\n';\n\n cssParser.createStyleElement(namespace, css);\n \n self.ctx.detectChanges();\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"gpioList\": {\n \"title\": \"Gpio switches\",\n \"type\": \"array\",\n \"minItems\" : 1,\n \"items\": {\n \"title\": \"Gpio switch\",\n \"type\": \"object\",\n \"properties\": {\n \"pin\": {\n \"title\": \"Pin\",\n \"type\": \"number\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n },\n \"row\": {\n \"title\": \"Row\",\n \"type\": \"number\"\n },\n \"col\": {\n \"title\": \"Column\",\n \"type\": \"number\"\n }\n },\n \"required\": [\"pin\", \"label\", \"row\", \"col\"]\n }\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"switchPanelBackgroundColor\": {\n \"title\": \"Switches panel background color\",\n \"type\": \"string\",\n \"default\": \"#008a00\"\n },\n \"gpioStatusRequest\": {\n \"title\": \"GPIO status request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"getGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"gpioStatusChangeRequest\": {\n \"title\": \"GPIO status change request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"setGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"parseGpioStatusFunction\": {\n \"title\": \"Parse gpio status function\",\n \"type\": \"string\",\n \"default\": \"return body[pin] === true;\"\n } \n },\n \"required\": [\"gpioList\", \n \"requestTimeout\",\n \"switchPanelBackgroundColor\",\n \"gpioStatusRequest\",\n \"gpioStatusChangeRequest\",\n \"parseGpioStatusFunction\"]\n },\n \"form\": [\n \"gpioList\",\n \"requestTimeout\",\n {\n \"key\": \"switchPanelBackgroundColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"gpioStatusRequest\",\n \"items\": [\n \"gpioStatusRequest.method\",\n {\n \"key\": \"gpioStatusRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"gpioStatusChangeRequest\",\n \"items\": [\n \"gpioStatusChangeRequest.method\",\n {\n \"key\": \"gpioStatusChangeRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"parseGpioStatusFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/rpc/parse_gpio_status_fn\"\n }\n ]\n}", + "settingsSchema": "", "dataKeySettingsSchema": "{}\n", + "settingsDirective": "tb-gpio-control-widget-settings", "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#008a00\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":7,\"label\":\"GPIO 4 (GPCLK0)\",\"row\":3,\"col\":0,\"_uniqueKey\":0},{\"pin\":11,\"label\":\"GPIO 17\",\"row\":5,\"col\":0,\"_uniqueKey\":1},{\"pin\":12,\"label\":\"GPIO 18\",\"row\":5,\"col\":1,\"_uniqueKey\":2},{\"_uniqueKey\":3,\"pin\":13,\"label\":\"GPIO 27\",\"row\":6,\"col\":0},{\"_uniqueKey\":4,\"pin\":15,\"label\":\"GPIO 22\",\"row\":7,\"col\":0},{\"_uniqueKey\":5,\"pin\":16,\"label\":\"GPIO 23\",\"row\":7,\"col\":1},{\"_uniqueKey\":6,\"pin\":18,\"label\":\"GPIO 24\",\"row\":8,\"col\":1},{\"_uniqueKey\":7,\"pin\":22,\"label\":\"GPIO 25\",\"row\":10,\"col\":1},{\"_uniqueKey\":8,\"pin\":29,\"label\":\"GPIO 5\",\"row\":14,\"col\":0},{\"_uniqueKey\":9,\"pin\":31,\"label\":\"GPIO 6\",\"row\":15,\"col\":0},{\"_uniqueKey\":10,\"pin\":32,\"label\":\"GPIO 12\",\"row\":15,\"col\":1},{\"_uniqueKey\":11,\"pin\":33,\"label\":\"GPIO 13\",\"row\":16,\"col\":0},{\"_uniqueKey\":12,\"pin\":35,\"label\":\"GPIO 19\",\"row\":17,\"col\":0},{\"_uniqueKey\":13,\"pin\":36,\"label\":\"GPIO 16\",\"row\":17,\"col\":1},{\"_uniqueKey\":14,\"pin\":37,\"label\":\"GPIO 26\",\"row\":18,\"col\":0},{\"_uniqueKey\":15,\"pin\":38,\"label\":\"GPIO 20\",\"row\":18,\"col\":1},{\"_uniqueKey\":16,\"pin\":40,\"label\":\"GPIO 21\",\"row\":19,\"col\":1}]},\"title\":\"Raspberry Pi GPIO Control\"}" } } diff --git a/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.html index 9cfe9611fc..4f85de08b0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.html @@ -34,6 +34,7 @@ [dataKeySettingsDirective]="data.dataKeySettingsDirective" [entityAliasId]="data.entityAliasId" [dashboard]="data.dashboard" + [aliasController]="data.aliasController" [widget]="data.widget" [showPostProcessing]="data.showPostProcessing" [callbacks]="data.callbacks" diff --git a/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.ts index 4e88f389dc..5c67242958 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.ts @@ -26,12 +26,14 @@ import { DataKey, Widget } from '@shared/models/widget.models'; import { DataKeysCallbacks } from './data-keys.component.models'; import { DataKeyConfigComponent } from '@home/components/widget/data-key-config.component'; import { Dashboard } from '@shared/models/dashboard.models'; +import { IAliasController } from '@core/api/widget-api.models'; export interface DataKeyConfigDialogData { dataKey: DataKey; dataKeySettingsSchema: any; dataKeySettingsDirective: string; dashboard: Dashboard; + aliasController: IAliasController; widget: Widget; entityAliasId?: string; showPostProcessing?: boolean; diff --git a/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.html b/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.html index f6e9acda1b..1fbd396cce 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.html @@ -101,6 +101,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.ts index b0d8847c1b..6ce4a98a09 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.ts @@ -42,6 +42,7 @@ import { JsFuncComponent } from '@shared/components/js-func.component'; import { JsonFormComponentData } from '@shared/components/json-form/json-form-component.models'; import { WidgetService } from '@core/http/widget.service'; import { Dashboard } from '@shared/models/dashboard.models'; +import { IAliasController } from '@core/api/widget-api.models'; @Component({ selector: 'tb-data-key-config', @@ -73,6 +74,9 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con @Input() dashboard: Dashboard; + @Input() + aliasController: IAliasController; + @Input() widget: Widget; diff --git a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts index fc36cb5ae6..d2361bbf9d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts @@ -422,6 +422,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie dataKeySettingsSchema: this.datakeySettingsSchema, dataKeySettingsDirective: this.dataKeySettingsDirective, dashboard: this.dashboard, + aliasController: this.aliasController, widget: this.widget, entityAliasId: this.entityAliasId, showPostProcessing: this.widgetType !== widgetType.alarm, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-widget-settings.component.html index 472978c097..5c7598b7c6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-widget-settings.component.html @@ -25,7 +25,7 @@
-
= []; - if (settings.labels) { - settings.labels.forEach((label) => { + if (labels) { + labels.forEach((label) => { labelsControls.push(this.fb.control(label, [Validators.required])); }); } - this.labelWidgetSettingsForm = this.fb.group({ - backgroundImageUrl: [settings.backgroundImageUrl, [Validators.required]], - labels: this.fb.array(labelsControls) - }); + return this.fb.array(labelsControls); } labelsFormArray(): FormArray { return this.labelWidgetSettingsForm.get('labels') as FormArray; } - public trackByLabel(index: number, labelControl: AbstractControl): any { - const label: LabelWidgetLabel = labelControl.value; - return label; + public trackByLabelControl(index: number, labelControl: AbstractControl): any { + return labelControl; } public removeLabel(index: number) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/analogue-gauge-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/analogue-gauge-widget-settings.component.html index 3da794f7ab..fcce29b1b4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/analogue-gauge-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/analogue-gauge-widget-settings.component.html @@ -204,7 +204,7 @@
-
= []; - if (settings.highlights) { - settings.highlights.forEach((highlight) => { - highlightsControls.push(this.fb.control(highlight, [Validators.required])); - }); - } this.analogueGaugeWidgetSettingsForm = this.fb.group({ // Radial gauge settings @@ -155,7 +149,7 @@ export class AnalogueGaugeWidgetSettingsComponent extends WidgetSettingsComponen // Highlights settings highlightsWidth: [settings.highlightsWidth, [Validators.min(0)]], - highlights: this.fb.array(highlightsControls), + highlights: this.prepareHighlightsFormArray(settings.highlights), // Animation settings animation: [settings.animation, []], @@ -214,13 +208,26 @@ export class AnalogueGaugeWidgetSettingsComponent extends WidgetSettingsComponen this.analogueGaugeWidgetSettingsForm.get('animationRule').updateValueAndValidity({emitEvent}); } + protected doUpdateSettings(settingsForm: FormGroup, settings: WidgetSettings) { + settingsForm.setControl('highlights', this.prepareHighlightsFormArray(settings.highlights), {emitEvent: false}); + } + + private prepareHighlightsFormArray(highlights: GaugeHighlight[] | undefined): FormArray { + const highlightsControls: Array = []; + if (highlights) { + highlights.forEach((highlight) => { + highlightsControls.push(this.fb.control(highlight, [Validators.required])); + }); + } + return this.fb.array(highlightsControls); + } + highlightsFormArray(): FormArray { return this.analogueGaugeWidgetSettingsForm.get('highlights') as FormArray; } - public trackByHighlight(index: number, highlightControl: AbstractControl): any { - const highlight: GaugeHighlight = highlightControl.value; - return highlight; + public trackByHighlightControl(index: number, highlightControl: AbstractControl): any { + return highlightControl; } public removeHighlight(index: number) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.ts index 40958f8965..6b4cf54b15 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.ts @@ -21,7 +21,6 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { GaugeType } from '@home/components/widget/lib/canvas-digital-gauge'; import { CdkDragDrop } from '@angular/cdk/drag-drop'; -import { GaugeHighlight } from '@home/components/widget/lib/settings/gauge/gauge-highlight.component'; import { FixedColorLevel, fixedColorLevelValidator @@ -108,26 +107,6 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent } protected onSettingsSet(settings: WidgetSettings) { - - const levelColorsControls: Array = []; - const fixedLevelColorsControls: Array = []; - const ticksValueControls: Array = []; - if (settings.levelColors) { - settings.levelColors.forEach((levelColor) => { - levelColorsControls.push(this.fb.control(levelColor, [Validators.required])); - }); - } - if (settings.fixedLevelColors) { - settings.fixedLevelColors.forEach((fixedLevelColor) => { - fixedLevelColorsControls.push(this.fb.control(fixedLevelColor, [fixedColorLevelValidator])); - }); - } - if (settings.ticksValue) { - settings.ticksValue.forEach((tickValue) => { - ticksValueControls.push(this.fb.control(tickValue, [Validators.required])); - }); - } - this.digitalGaugeWidgetSettingsForm = this.fb.group({ // Common gauge settings @@ -146,8 +125,8 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent // Gauge bar colors settings gaugeColor: [settings.gaugeColor, []], useFixedLevelColor: [settings.useFixedLevelColor, []], - levelColors: this.fb.array(levelColorsControls), - fixedLevelColors: this.fb.array(fixedLevelColorsControls), + levelColors: this.prepareLevelColorFormArray(settings.levelColors), + fixedLevelColors: this.prepareFixedLevelColorFormArray(settings.fixedLevelColors), // Title settings showTitle: [settings.showTitle, []], @@ -173,7 +152,7 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent showTicks: [settings.showTicks, []], tickWidth: [settings.tickWidth, [Validators.min(0)]], colorTicks: [settings.colorTicks, []], - ticksValue: this.fb.array(ticksValueControls), + ticksValue: this.prepareTicksValueFormArray(settings.ticksValue), // Animation settings animation: [settings.animation, []], @@ -275,12 +254,48 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent this.digitalGaugeWidgetSettingsForm.get('animationRule').updateValueAndValidity({emitEvent}); } + protected doUpdateSettings(settingsForm: FormGroup, settings: WidgetSettings) { + settingsForm.setControl('levelColors', this.prepareLevelColorFormArray(settings.levelColors), {emitEvent: false}); + settingsForm.setControl('fixedLevelColors', this.prepareFixedLevelColorFormArray(settings.fixedLevelColors), {emitEvent: false}); + settingsForm.setControl('ticksValue', this.prepareTicksValueFormArray(settings.ticksValue), {emitEvent: false}); + } + + private prepareLevelColorFormArray(levelColors: string[] | undefined): FormArray { + const levelColorsControls: Array = []; + if (levelColors) { + levelColors.forEach((levelColor) => { + levelColorsControls.push(this.fb.control(levelColor, [Validators.required])); + }); + } + return this.fb.array(levelColorsControls); + } + + private prepareFixedLevelColorFormArray(fixedLevelColors: FixedColorLevel[] | undefined): FormArray { + const fixedLevelColorsControls: Array = []; + if (fixedLevelColors) { + fixedLevelColors.forEach((fixedLevelColor) => { + fixedLevelColorsControls.push(this.fb.control(fixedLevelColor, [fixedColorLevelValidator])); + }); + } + return this.fb.array(fixedLevelColorsControls); + } + + private prepareTicksValueFormArray(ticksValue: ValueSourceProperty[] | undefined): FormArray { + const ticksValueControls: Array = []; + if (ticksValue) { + ticksValue.forEach((tickValue) => { + ticksValueControls.push(this.fb.control(tickValue, [Validators.required])); + }); + } + return this.fb.array(ticksValueControls); + } + levelColorsFormArray(): FormArray { return this.digitalGaugeWidgetSettingsForm.get('levelColors') as FormArray; } public trackByLevelColor(index: number, levelColorControl: AbstractControl): any { - return index; + return levelColorControl; } public removeLevelColor(index: number) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-control-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-control-widget-settings.component.html new file mode 100644 index 0000000000..cef8746c93 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-control-widget-settings.component.html @@ -0,0 +1,96 @@ + +
+
+ widgets.gpio.panel-settings + + +
+ widgets.gpio.gpio-switches +
+
+
+ + +
+
+
+ widgets.gpio.no-gpio-switches +
+
+ +
+
+
+
+
+ widgets.rpc.rpc-settings + + widgets.rpc.request-timeout + + +
+ widgets.gpio.gpio-status-request + + widgets.gpio.method-name + + + + +
+
+ widgets.gpio.gpio-status-change-request + + widgets.gpio.method-name + + + + +
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-control-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-control-widget-settings.component.ts new file mode 100644 index 0000000000..b398394d1a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-control-widget-settings.component.ts @@ -0,0 +1,145 @@ +/// +/// Copyright © 2016-2022 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 { Component } from '@angular/core'; +import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; +import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { GpioItem, gpioItemValidator } from '@home/components/widget/lib/settings/gpio/gpio-item.component'; +import { ContentType } from '@shared/models/constants'; + +@Component({ + selector: 'tb-gpio-control-widget-settings', + templateUrl: './gpio-control-widget-settings.component.html', + styleUrls: ['./../widget-settings.scss'] +}) +export class GpioControlWidgetSettingsComponent extends WidgetSettingsComponent { + + gpioControlWidgetSettingsForm: FormGroup; + + contentTypes = ContentType; + + constructor(protected store: Store, + private fb: FormBuilder) { + super(store); + } + + protected settingsForm(): FormGroup { + return this.gpioControlWidgetSettingsForm; + } + + protected defaultSettings(): WidgetSettings { + return { + switchPanelBackgroundColor: '#008a00', + gpioList: [], + requestTimeout: 500, + gpioStatusRequest: { + method: 'getGpioStatus', + paramsBody: '{}' + }, + gpioStatusChangeRequest: { + method: 'setGpioStatus', + paramsBody: '{\n "pin": "{$pin}",\n "enabled": "{$enabled}"\n}' + }, + parseGpioStatusFunction: 'return body[pin] === true;' + }; + } + + protected onSettingsSet(settings: WidgetSettings) { + this.gpioControlWidgetSettingsForm = this.fb.group({ + + // Panel settings + + switchPanelBackgroundColor: [settings.switchPanelBackgroundColor, [Validators.required]], + + // --> GPIO switches + + gpioList: this.prepareGpioListFormArray(settings.gpioList), + + // RPC settings + + requestTimeout: [settings.requestTimeout, [Validators.min(0), Validators.required]], + + // --> GPIO status request + + gpioStatusRequest: this.fb.group({ + method: [settings.gpioStatusRequest?.method, [Validators.required]], + paramsBody: [settings.gpioStatusRequest?.paramsBody, [Validators.required]] + }, {validators: [Validators.required]}), + + // --> GPIO status change request + + gpioStatusChangeRequest: this.fb.group({ + method: [settings.gpioStatusChangeRequest?.method, [Validators.required]], + paramsBody: [settings.gpioStatusChangeRequest?.paramsBody, [Validators.required]] + }, {validators: [Validators.required]}), + + parseGpioStatusFunction: [settings.parseGpioStatusFunction, [Validators.required]] + }); + } + + protected doUpdateSettings(settingsForm: FormGroup, settings: WidgetSettings) { + settingsForm.setControl('gpioList', this.prepareGpioListFormArray(settings.gpioList), {emitEvent: false}); + } + + private prepareGpioListFormArray(gpioList: GpioItem[] | undefined): FormArray { + const gpioListControls: Array = []; + if (gpioList) { + gpioList.forEach((gpioItem) => { + gpioListControls.push(this.fb.control(gpioItem, [gpioItemValidator(false)])); + }); + } + return this.fb.array(gpioListControls, [(control: AbstractControl) => { + const gpioItems = control.value; + if (!gpioItems || !gpioItems.length) { + return { + gpioItems: true + }; + } + return null; + }]); + } + + gpioListFormArray(): FormArray { + return this.gpioControlWidgetSettingsForm.get('gpioList') as FormArray; + } + + public trackByGpioItem(index: number, gpioItemControl: AbstractControl): any { + return gpioItemControl; + } + + public removeGpioItem(index: number) { + (this.gpioControlWidgetSettingsForm.get('gpioList') as FormArray).removeAt(index); + } + + public addGpioItem() { + const gpioItem: GpioItem = { + pin: null, + label: null, + row: null, + col: null + }; + const gpioListArray = this.gpioControlWidgetSettingsForm.get('gpioList') as FormArray; + const gpioItemControl = this.fb.control(gpioItem, [gpioItemValidator(false)]); + (gpioItemControl as any).new = true; + gpioListArray.push(gpioItemControl); + this.gpioControlWidgetSettingsForm.updateValueAndValidity(); + if (!this.gpioControlWidgetSettingsForm.valid) { + this.onSettingsChanged(this.gpioControlWidgetSettingsForm.value); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-item.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-item.component.html new file mode 100644 index 0000000000..9ebbdeefd9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-item.component.html @@ -0,0 +1,73 @@ + + + +
+ +
+
+
+
+
+
+
+ + +
+
+ +
+ +
+
+ + widgets.gpio.pin + + + + widgets.gpio.label + + +
+
+ + widgets.gpio.row + + + + widgets.gpio.column + + +
+ + +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-item.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-item.component.scss new file mode 100644 index 0000000000..5190223be1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-item.component.scss @@ -0,0 +1,40 @@ +/** + * Copyright © 2016-2022 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. + */ +:host { + display: block; + .mat-expansion-panel { + box-shadow: none; + &.gpio-item { + border: 1px groove rgba(0, 0, 0, .25); + .mat-expansion-panel-header { + padding: 0 24px 0 8px; + &.mat-expanded { + height: 48px; + } + } + } + } +} + +:host ::ng-deep { + .mat-expansion-panel { + &.gpio-item { + .mat-expansion-panel-body { + padding: 0 8px 8px; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-item.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-item.component.ts new file mode 100644 index 0000000000..9d3c9ef8de --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-item.component.ts @@ -0,0 +1,150 @@ +/// +/// Copyright © 2016-2022 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 { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'; +import { + AbstractControl, + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALUE_ACCESSOR, ValidationErrors, ValidatorFn, + Validators +} from '@angular/forms'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TranslateService } from '@ngx-translate/core'; +import { isNumber } from '@core/utils'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; + +export interface GpioItem { + pin: number; + label: string; + row: number; + col: number; + color?: string; +} + +export function gpioItemValidator(hasColor: boolean): ValidatorFn { + return (control: AbstractControl) => { + const gpioItem: GpioItem = control.value; + if (!gpioItem + || !isNumber(gpioItem.pin) || gpioItem.pin < 1 + || !isNumber(gpioItem.row) || gpioItem.row < 0 + || !isNumber(gpioItem.col) || gpioItem.col < 0 || gpioItem.col > 1 + || !gpioItem.label + || (hasColor && !gpioItem.color) + ) { + return { + gpioItem: true + }; + } + return null; + }; +} + +@Component({ + selector: 'tb-gpio-item', + templateUrl: './gpio-item.component.html', + styleUrls: ['./gpio-item.component.scss', './../widget-settings.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => GpioItemComponent), + multi: true + } + ] +}) +export class GpioItemComponent extends PageComponent implements OnInit, ControlValueAccessor { + + @Input() + disabled: boolean; + + @Input() + expanded = false; + + @Input() + hasColor = false; + + @Output() + removeGpioItem = new EventEmitter(); + + private modelValue: GpioItem; + + private propagateChange = null; + + public gpioItemFormGroup: FormGroup; + + constructor(protected store: Store, + private translate: TranslateService, + private domSanitizer: DomSanitizer, + private fb: FormBuilder) { + super(store); + } + + ngOnInit(): void { + this.gpioItemFormGroup = this.fb.group({ + pin: [null, [Validators.required, Validators.min(1)]], + label: [null, [Validators.required]], + row: [null, [Validators.required, Validators.min(0)]], + col: [null, [Validators.required, Validators.min(0), Validators.max(1)]] + }); + if (this.hasColor) { + this.gpioItemFormGroup.addControl('color', this.fb.control(null, [Validators.required])); + } + this.gpioItemFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.gpioItemFormGroup.disable({emitEvent: false}); + } else { + this.gpioItemFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: GpioItem): void { + this.modelValue = value; + this.gpioItemFormGroup.patchValue( + value, {emitEvent: false} + ); + } + + gpioItemHtml(): SafeHtml { + const value: GpioItem = this.gpioItemFormGroup.value; + const pin = isNumber(value.pin) && value.pin > 0 ? value.pin : 'Undefined'; + const row = isNumber(value.row) && value.row > -1 ? value.row : 'Undefined'; + const col = isNumber(value.col) && value.col > -1 ? value.col : 'Undefined'; + const label = value.label || 'Undefined'; + return this.domSanitizer.bypassSecurityTrustHtml(`${label} (pin:${pin}) - [row:${row}:col:${col}]`); + } + + private updateModel() { + const value: GpioItem = this.gpioItemFormGroup.value; + this.modelValue = value; + this.propagateChange(this.modelValue); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-panel-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-panel-widget-settings.component.html new file mode 100644 index 0000000000..1562e42496 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-panel-widget-settings.component.html @@ -0,0 +1,55 @@ + +
+
+ widgets.gpio.panel-settings + + +
+ widgets.gpio.gpio-leds +
+
+
+ + +
+
+
+ widgets.gpio.no-gpio-leds +
+
+ +
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-panel-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-panel-widget-settings.component.ts new file mode 100644 index 0000000000..d39ceade3e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-panel-widget-settings.component.ts @@ -0,0 +1,114 @@ +/// +/// Copyright © 2016-2022 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 { Component } from '@angular/core'; +import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; +import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { GpioItem, gpioItemValidator } from '@home/components/widget/lib/settings/gpio/gpio-item.component'; + +@Component({ + selector: 'tb-gpio-panel-widget-settings', + templateUrl: './gpio-panel-widget-settings.component.html', + styleUrls: ['./../widget-settings.scss'] +}) +export class GpioPanelWidgetSettingsComponent extends WidgetSettingsComponent { + + gpioPanelWidgetSettingsForm: FormGroup; + + constructor(protected store: Store, + private fb: FormBuilder) { + super(store); + } + + protected settingsForm(): FormGroup { + return this.gpioPanelWidgetSettingsForm; + } + + protected defaultSettings(): WidgetSettings { + return { + ledPanelBackgroundColor: '#008a00', + gpioList: [] + }; + } + + protected onSettingsSet(settings: WidgetSettings) { + this.gpioPanelWidgetSettingsForm = this.fb.group({ + + // Panel settings + + ledPanelBackgroundColor: [settings.ledPanelBackgroundColor, [Validators.required]], + + // --> GPIO leds + + gpioList: this.prepareGpioListFormArray(settings.gpioList), + + }); + } + + protected doUpdateSettings(settingsForm: FormGroup, settings: WidgetSettings) { + settingsForm.setControl('gpioList', this.prepareGpioListFormArray(settings.gpioList), {emitEvent: false}); + } + + private prepareGpioListFormArray(gpioList: GpioItem[] | undefined): FormArray { + const gpioListControls: Array = []; + if (gpioList) { + gpioList.forEach((gpioItem) => { + gpioListControls.push(this.fb.control(gpioItem, [gpioItemValidator(true)])); + }); + } + return this.fb.array(gpioListControls, [(control: AbstractControl) => { + const gpioItems = control.value; + if (!gpioItems || !gpioItems.length) { + return { + gpioItems: true + }; + } + return null; + }]); + } + + gpioListFormArray(): FormArray { + return this.gpioPanelWidgetSettingsForm.get('gpioList') as FormArray; + } + + public trackByGpioItem(index: number, gpioItemControl: AbstractControl): any { + return gpioItemControl; + } + + public removeGpioItem(index: number) { + (this.gpioPanelWidgetSettingsForm.get('gpioList') as FormArray).removeAt(index); + } + + public addGpioItem() { + const gpioItem: GpioItem = { + pin: null, + label: null, + row: null, + col: null, + color: null + }; + const gpioListArray = this.gpioPanelWidgetSettingsForm.get('gpioList') as FormArray; + const gpioItemControl = this.fb.control(gpioItem, [gpioItemValidator(true)]); + (gpioItemControl as any).new = true; + gpioListArray.push(gpioItemControl); + this.gpioPanelWidgetSettingsForm.updateValueAndValidity(); + if (!this.gpioPanelWidgetSettingsForm.valid) { + this.onSettingsChanged(this.gpioPanelWidgetSettingsForm.value); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts index cd27ca9135..6c9993b0b4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts @@ -156,6 +156,13 @@ import { import { GatewayEventsWidgetSettingsComponent } from '@home/components/widget/lib/settings/gateway/gateway-events-widget-settings.component'; +import { GpioItemComponent } from '@home/components/widget/lib/settings/gpio/gpio-item.component'; +import { + GpioControlWidgetSettingsComponent +} from '@home/components/widget/lib/settings/gpio/gpio-control-widget-settings.component'; +import { + GpioPanelWidgetSettingsComponent +} from '@home/components/widget/lib/settings/gpio/gpio-panel-widget-settings.component'; @NgModule({ declarations: [ @@ -213,7 +220,10 @@ import { EdgeQuickOverviewWidgetSettingsComponent, GatewayConfigWidgetSettingsComponent, GatewayConfigSingleDeviceWidgetSettingsComponent, - GatewayEventsWidgetSettingsComponent + GatewayEventsWidgetSettingsComponent, + GpioItemComponent, + GpioControlWidgetSettingsComponent, + GpioPanelWidgetSettingsComponent ], imports: [ CommonModule, @@ -275,7 +285,10 @@ import { EdgeQuickOverviewWidgetSettingsComponent, GatewayConfigWidgetSettingsComponent, GatewayConfigSingleDeviceWidgetSettingsComponent, - GatewayEventsWidgetSettingsComponent + GatewayEventsWidgetSettingsComponent, + GpioItemComponent, + GpioControlWidgetSettingsComponent, + GpioPanelWidgetSettingsComponent ] }) export class WidgetSettingsModule { @@ -323,5 +336,7 @@ export const widgetSettingsComponentsMap: {[key: string]: Type
- +