Commit 90c84f84d0a8e56523fca9bc7bd8a931340fdaaf

Authored by Vladyslav_Prykhodko
Committed by Andrew Shvayka
1 parent b0c6ce9f

UI: Add control widget support persistent command (Add settings: persistent and …

…persistentPollingInterval)
@@ -18,8 +18,8 @@ @@ -18,8 +18,8 @@
18 "resources": [], 18 "resources": [],
19 "templateHtml": "<div style=\"height: 100%; overflow-y: auto;\" id=\"device-terminal\"></div>", 19 "templateHtml": "<div style=\"height: 100%; overflow-y: auto;\" id=\"device-terminal\"></div>",
20 "templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n\n", 20 "templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n\n",
21 - "controllerScript": "var requestTimeout = 500;\nvar requestPersistent = false;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n if (self.ctx.settings.requestPersistent) {\n requestPersistent = self.ctx.settings.requestPersistent;\n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = command.trim();\n var requestUUID = uuidv4();\n if (localCommand === 'help') {\n printUsage(this);\n } else {\n var spaceIndex = localCommand.indexOf(' ');\n if (spaceIndex === -1 && !localCommand.length) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n } else {\n var params;\n if (spaceIndex === -1) {\n spaceIndex = localCommand.length;\n }\n var name = localCommand.substr(0, spaceIndex);\n var args = localCommand.substr(spaceIndex + 1);\n if (args.length) {\n try {\n params = JSON.parse(args);\n } catch (e) {\n params = args;\n }\n }\n performRpc(this, name, params, requestUUID);\n }\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt,\n enabled: rpcEnabled\n });\n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' <method> [params body]]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1:]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2:]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": 2, \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\n\nfunction performRpc(terminal, method, params, requestUUID) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout, requestPersistent, requestUUID).subscribe(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\n\nfunction uuidv4() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\n\n \nself.onDestroy = function() {\n}",  
22 - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\",\n \"requestPersistent\"\n ]\n}", 21 + "controllerScript": "var requestTimeout = 500;\nvar requestPersistent = false;\nvar persistentPollingInterval = 5000;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n if (self.ctx.settings.requestPersistent) {\n requestPersistent = self.ctx.settings.requestPersistent;\n }\n if (self.ctx.settings.persistentPollingInterval) {\n persistentPollingInterval = self.ctx.settings.persistentPollingInterval;\n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = command.trim();\n var requestUUID = uuidv4();\n if (localCommand === 'help') {\n printUsage(this);\n } else {\n var spaceIndex = localCommand.indexOf(' ');\n if (spaceIndex === -1 && !localCommand.length) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n } else {\n var params;\n if (spaceIndex === -1) {\n spaceIndex = localCommand.length;\n }\n var name = localCommand.substr(0, spaceIndex);\n var args = localCommand.substr(spaceIndex + 1);\n if (args.length) {\n try {\n params = JSON.parse(args);\n } catch (e) {\n params = args;\n }\n }\n performRpc(this, name, params, requestUUID);\n }\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt,\n enabled: rpcEnabled\n });\n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' <method> [params body]]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1:]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2:]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": 2, \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\n\nfunction performRpc(terminal, method, params, requestUUID) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout, requestPersistent, persistentPollingInterval, requestUUID).subscribe(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\n\nfunction uuidv4() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\n\n \nself.onDestroy = function() {\n self.ctx.controlApi.completedCommand();\n}",
  22 + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n }\n ]\n}",
23 "dataKeySettingsSchema": "{}\n", 23 "dataKeySettingsSchema": "{}\n",
24 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 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\":\"RPC debug terminal\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 24 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 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\":\"RPC debug terminal\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
25 } 25 }
@@ -55,7 +55,7 @@ @@ -55,7 +55,7 @@
55 "templateHtml": "<tb-knob [ctx]='ctx'></tb-knob>", 55 "templateHtml": "<tb-knob [ctx]='ctx'></tb-knob>",
56 "templateCss": "", 56 "templateCss": "",
57 "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n", 57 "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n",
58 - "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}", 58 + "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 \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\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 \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n }\n ]\n}",
59 "dataKeySettingsSchema": "{}\n", 59 "dataKeySettingsSchema": "{}\n",
60 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"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}" 60 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"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}"
61 } 61 }
@@ -73,7 +73,7 @@ @@ -73,7 +73,7 @@
73 "templateHtml": "<tb-switch [ctx]='ctx'></tb-switch>", 73 "templateHtml": "<tb-switch [ctx]='ctx'></tb-switch>",
74 "templateCss": "", 74 "templateCss": "",
75 "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n", 75 "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n",
76 - "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 \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n \"showOnOffLabels\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}", 76 + "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 \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n \"showOnOffLabels\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n }\n ]\n}",
77 "dataKeySettingsSchema": "{}\n", 77 "dataKeySettingsSchema": "{}\n",
78 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"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}" 78 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"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}"
79 } 79 }
@@ -91,7 +91,7 @@ @@ -91,7 +91,7 @@
91 "templateHtml": "<tb-round-switch [ctx]='ctx'></tb-round-switch>", 91 "templateHtml": "<tb-round-switch [ctx]='ctx'></tb-round-switch>",
92 "templateCss": "", 92 "templateCss": "",
93 "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n", 93 "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n",
94 - "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 \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}", 94 + "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 \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n }\n ]\n}",
95 "dataKeySettingsSchema": "{}\n", 95 "dataKeySettingsSchema": "{}\n",
96 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":false,\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\",\"title\":\"Round switch\",\"retrieveValueMethod\":\"rpc\",\"valueKey\":\"value\",\"parseValueFunction\":\"return data ? true : false;\",\"convertValueFunction\":\"return value;\"},\"title\":\"Round switch\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" 96 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":false,\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\",\"title\":\"Round switch\",\"retrieveValueMethod\":\"rpc\",\"valueKey\":\"value\",\"parseValueFunction\":\"return data ? true : false;\",\"convertValueFunction\":\"return value;\"},\"title\":\"Round switch\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}"
97 } 97 }
@@ -109,7 +109,7 @@ @@ -109,7 +109,7 @@
109 "templateHtml": "<tb-led-indicator [ctx]='ctx'></tb-led-indicator>", 109 "templateHtml": "<tb-led-indicator [ctx]='ctx'></tb-led-indicator>",
110 "templateCss": "", 110 "templateCss": "",
111 "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n", 111 "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n",
112 - "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\": \"LED title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"ledColor\": {\n \"title\": \"LED Color\",\n \"type\": \"string\",\n \"default\": \"green\"\n },\n \"performCheckStatus\": {\n \"title\": \"Perform RPC device status check\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"checkStatusMethod\": {\n \"title\": \"RPC check device status method\",\n \"type\": \"string\",\n \"default\": \"checkStatus\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve led status value using method\",\n \"type\": \"string\",\n \"default\": \"attribute\"\n },\n \"valueAttribute\": {\n \"title\": \"Device attribute/timeseries containing led status value\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse led status value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"valueAttribute\", \"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"ledColor\",\n \"type\": \"color\"\n },\n \"performCheckStatus\",\n \"checkStatusMethod\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueAttribute\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}", 112 + "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\": \"LED title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"ledColor\": {\n \"title\": \"LED Color\",\n \"type\": \"string\",\n \"default\": \"green\"\n },\n \"performCheckStatus\": {\n \"title\": \"Perform RPC device status check\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"checkStatusMethod\": {\n \"title\": \"RPC check device status method\",\n \"type\": \"string\",\n \"default\": \"checkStatus\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve led status value using method\",\n \"type\": \"string\",\n \"default\": \"attribute\"\n },\n \"valueAttribute\": {\n \"title\": \"Device attribute/timeseries containing led status value\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse led status value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n }\n },\n \"required\": [\"valueAttribute\", \"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"ledColor\",\n \"type\": \"color\"\n },\n \"performCheckStatus\",\n \"checkStatusMethod\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueAttribute\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n }\n ]\n}",
113 "dataKeySettingsSchema": "{}\n", 113 "dataKeySettingsSchema": "{}\n",
114 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":true,\"title\":\"Led indicator\",\"ledColor\":\"#4caf50\",\"valueAttribute\":\"value\",\"retrieveValueMethod\":\"attribute\",\"parseValueFunction\":\"return data ? true : false;\",\"performCheckStatus\":true,\"checkStatusMethod\":\"checkStatus\"},\"title\":\"Led indicator\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" 114 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":true,\"title\":\"Led indicator\",\"ledColor\":\"#4caf50\",\"valueAttribute\":\"value\",\"retrieveValueMethod\":\"attribute\",\"parseValueFunction\":\"return data ? true : false;\",\"performCheckStatus\":true,\"checkStatusMethod\":\"checkStatus\"},\"title\":\"Led indicator\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}"
115 } 115 }
@@ -126,8 +126,8 @@ @@ -126,8 +126,8 @@
126 "resources": [], 126 "resources": [],
127 "templateHtml": "<div class=\"tb-rpc-button\" fxLayout=\"column\">\n <div fxFlex=\"20\" class=\"title-container\" fxLayout=\"row\"\n fxLayoutAlign=\"center center\" [fxShow]=\"showTitle\">\n <span class=\"button-title\">{{title}}</span>\n </div>\n <div fxFlex=\"{{showTitle ? 80 : 100}}\" [ngStyle]=\"{paddingTop: showTitle ? '5px': '10px'}\"\n class=\"button-container\" fxLayout=\"column\" fxLayoutAlign=\"center center\">\n <div>\n <button mat-button (click)=\"sendCommand()\"\n [class.mat-raised-button]=\"styleButton?.isRaised\"\n [color]=\"styleButton?.isPrimary ? 'primary' : ''\"\n [ngStyle]=\"customStyle\">\n {{buttonLable}}\n </button>\n </div>\n </div>\n <div class=\"error-container\" [ngStyle]=\"{'background': error?.length ? 'rgba(255,255,255,0.25)' : 'none'}\"\n fxLayout=\"row\" fxLayoutAlign=\"center center\">\n <span class=\"button-error\">{{ error }}</span>\n </div>\n</div>", 127 "templateHtml": "<div class=\"tb-rpc-button\" fxLayout=\"column\">\n <div fxFlex=\"20\" class=\"title-container\" fxLayout=\"row\"\n fxLayoutAlign=\"center center\" [fxShow]=\"showTitle\">\n <span class=\"button-title\">{{title}}</span>\n </div>\n <div fxFlex=\"{{showTitle ? 80 : 100}}\" [ngStyle]=\"{paddingTop: showTitle ? '5px': '10px'}\"\n class=\"button-container\" fxLayout=\"column\" fxLayoutAlign=\"center center\">\n <div>\n <button mat-button (click)=\"sendCommand()\"\n [class.mat-raised-button]=\"styleButton?.isRaised\"\n [color]=\"styleButton?.isPrimary ? 'primary' : ''\"\n [ngStyle]=\"customStyle\">\n {{buttonLable}}\n </button>\n </div>\n </div>\n <div class=\"error-container\" [ngStyle]=\"{'background': error?.length ? 'rgba(255,255,255,0.25)' : 'none'}\"\n fxLayout=\"row\" fxLayoutAlign=\"center center\">\n <span class=\"button-error\">{{ error }}</span>\n </div>\n</div>",
128 "templateCss": ".tb-rpc-button {\n width: 100%;\n height: 100%;\n}\n\n.tb-rpc-button .title-container {\n font-weight: 500;\n white-space: nowrap;\n margin: 10px 0;\n}\n\n.tb-rpc-button .button-container div{\n min-width: 80%\n}\n\n.tb-rpc-button .button-container .mat-button{\n width: 100%;\n margin: 0;\n}\n\n.tb-rpc-button .error-container {\n position: absolute;\n top: 2%;\n right: 0;\n left: 0;\n z-index: 4;\n height: 14px;\n}\n\n.tb-rpc-button .error-container .button-error {\n color: #ff3315;\n white-space: nowrap;\n}", 128 "templateCss": ".tb-rpc-button {\n width: 100%;\n height: 100%;\n}\n\n.tb-rpc-button .title-container {\n font-weight: 500;\n white-space: nowrap;\n margin: 10px 0;\n}\n\n.tb-rpc-button .button-container div{\n min-width: 80%\n}\n\n.tb-rpc-button .button-container .mat-button{\n width: 100%;\n margin: 0;\n}\n\n.tb-rpc-button .error-container {\n position: absolute;\n top: 2%;\n right: 0;\n left: 0;\n z-index: 4;\n height: 14px;\n}\n\n.tb-rpc-button .error-container .button-error {\n color: #ff3315;\n white-space: nowrap;\n}",
129 - "controllerScript": "self.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges();\n });\n};\n\nfunction init() {\n let rpcEnabled = self.ctx.defaultSubscription.rpcEnabled;\n\n self.ctx.$scope.buttonLable = self.ctx.settings.buttonText;\n self.ctx.$scope.showTitle = self.ctx.settings.title &&\n self.ctx.settings.title.length ? true : false;\n self.ctx.$scope.title = self.ctx.settings.title;\n self.ctx.$scope.styleButton = self.ctx.settings.styleButton;\n\n if (self.ctx.settings.styleButton.isPrimary ===\n false) {\n self.ctx.$scope.customStyle = {\n 'background-color': self.ctx.$scope.styleButton.bgColor,\n 'color': self.ctx.$scope.styleButton.textColor\n };\n }\n\n if (!rpcEnabled) {\n self.ctx.$scope.error =\n 'Target device is not set!';\n }\n\n self.ctx.$scope.sendCommand = function() {\n var rpcMethod = self.ctx.settings.methodName;\n var rpcParams = self.ctx.settings.methodParams;\n var timeout = self.ctx.settings.requestTimeout;\n var oneWayElseTwoWay = self.ctx.settings.oneWayElseTwoWay ?\n true : false;\n\n var commandPromise;\n if (oneWayElseTwoWay) {\n commandPromise = self.ctx.controlApi.sendOneWayCommand(\n rpcMethod, rpcParams, timeout);\n } else {\n commandPromise = self.ctx.controlApi.sendTwoWayCommand(\n rpcMethod, rpcParams, timeout);\n }\n commandPromise.subscribe(\n function success() {\n self.ctx.$scope.error = \"\";\n self.ctx.detectChanges();\n },\n function fail(rejection) {\n if (self.ctx.settings.showError) {\n self.ctx.$scope.error =\n rejection.status + \": \" +\n rejection.statusText;\n self.ctx.detectChanges();\n }\n }\n );\n };\n}\n",  
130 - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"title\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"buttonText\": {\n \"title\": \"Button label\",\n \"type\": \"string\",\n \"default\": \"Send RPC\"\n },\n \"oneWayElseTwoWay\": {\n \"title\": \"Is One Way Command\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showError\": {\n \"title\": \"Show RPC command execution error\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"methodName\": {\n \"title\": \"RPC method\",\n \"type\": \"string\",\n \"default\": \"rpcCommand\"\n },\n \"methodParams\": {\n \"title\": \"RPC method params\",\n \"type\": \"string\",\n \"default\": \"{}\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 5000\n },\n \"styleButton\": {\n \"type\": \"object\",\n \"title\": \"Button Style\",\n \"properties\": {\n \"isRaised\": {\n \"type\": \"boolean\",\n \"title\": \"Raised\",\n \"default\": true\n },\n \"isPrimary\": {\n \"type\": \"boolean\",\n \"title\": \"Primary color\",\n \"default\": false\n },\n \"bgColor\": {\n \"type\": \"string\",\n \"title\": \"Button background color\",\n \"default\": null\n },\n \"textColor\": {\n \"type\": \"string\",\n \"title\": \"Button text color\",\n \"default\": null\n }\n }\n },\n \"required\": []\n }\n },\n \"form\": [\n \"title\",\n \"buttonText\",\n \"oneWayElseTwoWay\",\n \"showError\",\n \"methodName\",\n {\n \"key\": \"methodParams\",\n \"type\": \"json\"\n },\n \"requestTimeout\",\n {\n \"key\": \"styleButton\",\n \"items\": [\n \"styleButton.isRaised\",\n \"styleButton.isPrimary\",\n {\n \"key\": \"styleButton.bgColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"styleButton.textColor\",\n \"type\": \"color\"\n }\n ]\n }\n ]\n\n}", 129 + "controllerScript": "var requestPersistent = false;\nvar persistentPollingInterval = 5000;\n\nself.onInit = function() {\n if (self.ctx.settings.requestPersistent) {\n requestPersistent = self.ctx.settings.requestPersistent;\n }\n if (self.ctx.settings.persistentPollingInterval) {\n persistentPollingInterval = self.ctx.settings.persistentPollingInterval;\n }\n \n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges();\n });\n};\n\nfunction init() {\n let rpcEnabled = self.ctx.defaultSubscription.rpcEnabled;\n\n self.ctx.$scope.buttonLable = self.ctx.settings.buttonText;\n self.ctx.$scope.showTitle = self.ctx.settings.title &&\n self.ctx.settings.title.length ? true : false;\n self.ctx.$scope.title = self.ctx.settings.title;\n self.ctx.$scope.styleButton = self.ctx.settings.styleButton;\n\n if (self.ctx.settings.styleButton.isPrimary ===\n false) {\n self.ctx.$scope.customStyle = {\n 'background-color': self.ctx.$scope.styleButton.bgColor,\n 'color': self.ctx.$scope.styleButton.textColor\n };\n }\n\n if (!rpcEnabled) {\n self.ctx.$scope.error =\n 'Target device is not set!';\n }\n\n self.ctx.$scope.sendCommand = function() {\n var rpcMethod = self.ctx.settings.methodName;\n var rpcParams = self.ctx.settings.methodParams;\n var timeout = self.ctx.settings.requestTimeout;\n var oneWayElseTwoWay = self.ctx.settings.oneWayElseTwoWay ?\n true : false;\n\n var commandPromise;\n if (oneWayElseTwoWay) {\n commandPromise = self.ctx.controlApi.sendOneWayCommand(\n rpcMethod, rpcParams, timeout, requestPersistent, persistentPollingInterval);\n } else {\n commandPromise = self.ctx.controlApi.sendTwoWayCommand(\n rpcMethod, rpcParams, timeout, requestPersistent, persistentPollingInterval);\n }\n commandPromise.subscribe(\n function success() {\n self.ctx.$scope.error = \"\";\n self.ctx.detectChanges();\n },\n function fail(rejection) {\n if (self.ctx.settings.showError) {\n self.ctx.$scope.error =\n rejection.status + \": \" +\n rejection.statusText;\n self.ctx.detectChanges();\n }\n }\n );\n };\n}\n\nself.onDestroy = function() {\n self.ctx.controlApi.completedCommand();\n}\n",
  130 + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"title\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"buttonText\": {\n \"title\": \"Button label\",\n \"type\": \"string\",\n \"default\": \"Send RPC\"\n },\n \"oneWayElseTwoWay\": {\n \"title\": \"Is One Way Command\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showError\": {\n \"title\": \"Show RPC command execution error\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"methodName\": {\n \"title\": \"RPC method\",\n \"type\": \"string\",\n \"default\": \"rpcCommand\"\n },\n \"methodParams\": {\n \"title\": \"RPC method params\",\n \"type\": \"string\",\n \"default\": \"{}\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 5000\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n },\n \"styleButton\": {\n \"type\": \"object\",\n \"title\": \"Button Style\",\n \"properties\": {\n \"isRaised\": {\n \"type\": \"boolean\",\n \"title\": \"Raised\",\n \"default\": true\n },\n \"isPrimary\": {\n \"type\": \"boolean\",\n \"title\": \"Primary color\",\n \"default\": false\n },\n \"bgColor\": {\n \"type\": \"string\",\n \"title\": \"Button background color\",\n \"default\": null\n },\n \"textColor\": {\n \"type\": \"string\",\n \"title\": \"Button text color\",\n \"default\": null\n }\n }\n },\n \"required\": []\n }\n },\n \"form\": [\n \"title\",\n \"buttonText\",\n \"oneWayElseTwoWay\",\n \"showError\",\n \"methodName\",\n {\n \"key\": \"methodParams\",\n \"type\": \"json\"\n },\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n },\n {\n \"key\": \"styleButton\",\n \"items\": [\n \"styleButton.isRaised\",\n \"styleButton.isPrimary\",\n {\n \"key\": \"styleButton.bgColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"styleButton.textColor\",\n \"type\": \"color\"\n }\n ]\n }\n ]\n\n}",
131 "dataKeySettingsSchema": "{}\n", 131 "dataKeySettingsSchema": "{}\n",
132 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":5000,\"oneWayElseTwoWay\":true,\"buttonText\":\"Send RPC\",\"styleButton\":{\"isRaised\":true,\"isPrimary\":false},\"methodName\":\"rpcCommand\",\"methodParams\":\"{}\"},\"title\":\"RPC Button\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 132 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":5000,\"oneWayElseTwoWay\":true,\"buttonText\":\"Send RPC\",\"styleButton\":{\"isRaised\":true,\"isPrimary\":false},\"methodName\":\"rpcCommand\",\"methodParams\":\"{}\"},\"title\":\"RPC Button\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
133 } 133 }
@@ -69,11 +69,27 @@ public class RpcV2Controller extends AbstractRpcController { @@ -69,11 +69,27 @@ public class RpcV2Controller extends AbstractRpcController {
69 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") 69 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
70 @RequestMapping(value = "/persistent/{rpcId}", method = RequestMethod.GET) 70 @RequestMapping(value = "/persistent/{rpcId}", method = RequestMethod.GET)
71 @ResponseBody 71 @ResponseBody
72 - public Rpc getPersistedRpc(@PathVariable("rpcId") String strRpc) throws ThingsboardException { 72 + public ResponseEntity<Rpc> getPersistedRpc(@PathVariable("rpcId") String strRpc) throws ThingsboardException {
73 checkParameter("RpcId", strRpc); 73 checkParameter("RpcId", strRpc);
74 try { 74 try {
75 RpcId rpcId = new RpcId(UUID.fromString(strRpc)); 75 RpcId rpcId = new RpcId(UUID.fromString(strRpc));
76 - return checkRpcId(rpcId, Operation.READ); 76 + Rpc rpc = checkRpcId(rpcId, Operation.READ);
  77 + HttpStatus status;
  78 + switch (rpc.getStatus()) {
  79 + case FAILED:
  80 + status = HttpStatus.BAD_GATEWAY;
  81 + break;
  82 + case TIMEOUT:
  83 + status = HttpStatus.GATEWAY_TIMEOUT;
  84 + break;
  85 + case QUEUED:
  86 + case DELIVERED:
  87 + status = HttpStatus.ACCEPTED;
  88 + break;
  89 + default:
  90 + status = HttpStatus.OK;
  91 + }
  92 + return new ResponseEntity<>(rpc, status);
77 } catch (Exception e) { 93 } catch (Exception e) {
78 throw handleException(e); 94 throw handleException(e);
79 } 95 }
@@ -69,8 +69,11 @@ export interface WidgetSubscriptionApi { @@ -69,8 +69,11 @@ export interface WidgetSubscriptionApi {
69 } 69 }
70 70
71 export interface RpcApi { 71 export interface RpcApi {
72 - sendOneWayCommand: (method: string, params?: any, timeout?: number, persistent?: boolean, requestUUID?: string) => Observable<any>;  
73 - sendTwoWayCommand: (method: string, params?: any, timeout?: number, persistent?: boolean, requestUUID?: string) => Observable<any>; 72 + sendOneWayCommand: (method: string, params?: any, timeout?: number, persistent?: boolean,
  73 + persistentPollingInterval?: number, requestUUID?: string) => Observable<any>;
  74 + sendTwoWayCommand: (method: string, params?: any, timeout?: number, persistent?: boolean,
  75 + persistentPollingInterval?: number, requestUUID?: string) => Observable<any>;
  76 + completedCommand: () => void;
74 } 77 }
75 78
76 export interface IWidgetUtils { 79 export interface IWidgetUtils {
@@ -301,8 +304,10 @@ export interface IWidgetSubscription { @@ -301,8 +304,10 @@ export interface IWidgetSubscription {
301 onResetTimewindow(): void; 304 onResetTimewindow(): void;
302 updateTimewindowConfig(newTimewindow: Timewindow): void; 305 updateTimewindowConfig(newTimewindow: Timewindow): void;
303 306
304 - sendOneWayCommand(method: string, params?: any, timeout?: number, persistent?: boolean, requestUUID?: string): Observable<any>;  
305 - sendTwoWayCommand(method: string, params?: any, timeout?: number, persistent?: boolean, requestUUID?: string): Observable<any>; 307 + sendOneWayCommand(method: string, params?: any, timeout?: number, persistent?: boolean,
  308 + persistentPollingInterval?: number, requestUUID?: string): Observable<any>;
  309 + sendTwoWayCommand(method: string, params?: any, timeout?: number, persistent?: boolean,
  310 + persistentPollingInterval?: number, requestUUID?: string): Observable<any>;
306 clearRpcError(): void; 311 clearRpcError(): void;
307 312
308 subscribe(): void; 313 subscribe(): void;
@@ -35,7 +35,7 @@ import { @@ -35,7 +35,7 @@ import {
35 LegendKeyData, 35 LegendKeyData,
36 widgetType 36 widgetType
37 } from '@app/shared/models/widget.models'; 37 } from '@app/shared/models/widget.models';
38 -import { HttpErrorResponse } from '@angular/common/http'; 38 +import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
39 import { 39 import {
40 calculateIntervalStartEndTime, 40 calculateIntervalStartEndTime,
41 calculateTsOffset, 41 calculateTsOffset,
@@ -49,7 +49,7 @@ import { @@ -49,7 +49,7 @@ import {
49 toHistoryTimewindow, 49 toHistoryTimewindow,
50 WidgetTimewindow 50 WidgetTimewindow
51 } from '@app/shared/models/time/time.models'; 51 } from '@app/shared/models/time/time.models';
52 -import { forkJoin, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs'; 52 +import { forkJoin, Observable, of, ReplaySubject, Subject, throwError, timer } from 'rxjs';
53 import { CancelAnimationFrame } from '@core/services/raf.service'; 53 import { CancelAnimationFrame } from '@core/services/raf.service';
54 import { EntityType } from '@shared/models/entity-type.models'; 54 import { EntityType } from '@shared/models/entity-type.models';
55 import { createLabelFromDatasource, deepClone, isDefined, isDefinedAndNotNull, isEqual } from '@core/utils'; 55 import { createLabelFromDatasource, deepClone, isDefined, isDefinedAndNotNull, isEqual } from '@core/utils';
@@ -67,8 +67,9 @@ import { @@ -67,8 +67,9 @@ import {
67 KeyFilter, 67 KeyFilter,
68 updateDatasourceFromEntityInfo 68 updateDatasourceFromEntityInfo
69 } from '@shared/models/query/query.models'; 69 } from '@shared/models/query/query.models';
70 -import { map } from 'rxjs/operators'; 70 +import { filter, map, switchMap, takeUntil } from 'rxjs/operators';
71 import { AlarmDataListener } from '@core/api/alarm-data.service'; 71 import { AlarmDataListener } from '@core/api/alarm-data.service';
  72 +import { PersistentRpc } from '@shared/models/rpc.models';
72 73
73 const moment = moment_; 74 const moment = moment_;
74 75
@@ -644,12 +645,14 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -644,12 +645,14 @@ export class WidgetSubscription implements IWidgetSubscription {
644 } 645 }
645 } 646 }
646 647
647 - sendOneWayCommand(method: string, params?: any, timeout?: number, persistent?: boolean, requestUUID?: string): Observable<any> {  
648 - return this.sendCommand(true, method, params, timeout, persistent, requestUUID); 648 + sendOneWayCommand(method: string, params?: any, timeout?: number, persistent?: boolean,
  649 + persistentPollingInterval?: number, requestUUID?: string): Observable<any> {
  650 + return this.sendCommand(true, method, params, timeout, persistent, persistentPollingInterval, requestUUID);
649 } 651 }
650 652
651 - sendTwoWayCommand(method: string, params?: any, timeout?: number, persistent?: boolean, requestUUID?: string): Observable<any> {  
652 - return this.sendCommand(false, method, params, timeout, persistent, requestUUID); 653 + sendTwoWayCommand(method: string, params?: any, timeout?: number, persistent?: boolean,
  654 + persistentPollingInterval?: number, requestUUID?: string): Observable<any> {
  655 + return this.sendCommand(false, method, params, timeout, persistent, persistentPollingInterval, requestUUID);
653 } 656 }
654 657
655 clearRpcError(): void { 658 clearRpcError(): void {
@@ -658,8 +661,15 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -658,8 +661,15 @@ export class WidgetSubscription implements IWidgetSubscription {
658 this.callbacks.onRpcErrorCleared(this); 661 this.callbacks.onRpcErrorCleared(this);
659 } 662 }
660 663
661 - sendCommand(oneWayElseTwoWay: boolean, method: string, params?: any,  
662 - timeout?: number, persistent?: boolean, requestUUID?: string): Observable<any> { 664 + completedCommand(): void {
  665 + this.executingSubjects.forEach(subject => {
  666 + subject.next();
  667 + subject.complete();
  668 + });
  669 + }
  670 +
  671 + sendCommand(oneWayElseTwoWay: boolean, method: string, params?: any, timeout?: number,
  672 + persistent?: boolean, persistentPollingInterval?: number, requestUUID?: string): Observable<any> {
663 if (!this.rpcEnabled) { 673 if (!this.rpcEnabled) {
664 return throwError(new Error('Rpc disabled!')); 674 return throwError(new Error('Rpc disabled!'));
665 } else { 675 } else {
@@ -677,7 +687,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -677,7 +687,7 @@ export class WidgetSubscription implements IWidgetSubscription {
677 if (timeout && timeout > 0) { 687 if (timeout && timeout > 0) {
678 requestBody.timeout = timeout; 688 requestBody.timeout = timeout;
679 } 689 }
680 - const rpcSubject: Subject<any> = new ReplaySubject<any>(); 690 + const rpcSubject: Subject<any> = new Subject<any>();
681 this.executingRpcRequest = true; 691 this.executingRpcRequest = true;
682 this.callbacks.rpcStateChanged(this); 692 this.callbacks.rpcStateChanged(this);
683 if (this.ctx.utils.widgetEditMode) { 693 if (this.ctx.utils.widgetEditMode) {
@@ -695,7 +705,19 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -695,7 +705,19 @@ export class WidgetSubscription implements IWidgetSubscription {
695 } else { 705 } else {
696 this.executingSubjects.push(rpcSubject); 706 this.executingSubjects.push(rpcSubject);
697 (oneWayElseTwoWay ? this.ctx.deviceService.sendOneWayRpcCommand(this.targetDeviceId, requestBody) : 707 (oneWayElseTwoWay ? this.ctx.deviceService.sendOneWayRpcCommand(this.targetDeviceId, requestBody) :
698 - this.ctx.deviceService.sendTwoWayRpcCommand(this.targetDeviceId, requestBody)) 708 + this.ctx.deviceService.sendTwoWayRpcCommand(this.targetDeviceId, requestBody)).pipe(
  709 + switchMap((response) => {
  710 + if (persistent && persistentPollingInterval > 0) {
  711 + return timer(persistentPollingInterval / 2, persistentPollingInterval).pipe(
  712 + switchMap(() => this.ctx.deviceService.getPersistedRpc(response.rpcId, true)),
  713 + filter((persistentResponse: HttpResponse<PersistentRpc>) => persistentResponse.status !== 202),
  714 + map(persistentResponse => persistentResponse.body.response),
  715 + takeUntil(rpcSubject)
  716 + );
  717 + }
  718 + return of(response);
  719 + })
  720 + )
699 .subscribe((responseBody) => { 721 .subscribe((responseBody) => {
700 this.rpcRejection = null; 722 this.rpcRejection = null;
701 this.rpcErrorText = null; 723 this.rpcErrorText = null;
@@ -17,7 +17,7 @@ @@ -17,7 +17,7 @@
17 import { Injectable } from '@angular/core'; 17 import { Injectable } from '@angular/core';
18 import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; 18 import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 import { Observable, ReplaySubject } from 'rxjs'; 19 import { Observable, ReplaySubject } from 'rxjs';
20 -import { HttpClient } from '@angular/common/http'; 20 +import { HttpClient, HttpResponse } from '@angular/common/http';
21 import { PageLink } from '@shared/models/page/page-link'; 21 import { PageLink } from '@shared/models/page/page-link';
22 import { PageData } from '@shared/models/page/page-data'; 22 import { PageData } from '@shared/models/page/page-data';
23 import { 23 import {
@@ -30,6 +30,7 @@ import { @@ -30,6 +30,7 @@ import {
30 } from '@app/shared/models/device.models'; 30 } from '@app/shared/models/device.models';
31 import { EntitySubtype } from '@app/shared/models/entity-type.models'; 31 import { EntitySubtype } from '@app/shared/models/entity-type.models';
32 import { AuthService } from '@core/auth/auth.service'; 32 import { AuthService } from '@core/auth/auth.service';
  33 +import { PersistentRpc } from '@shared/models/rpc.models';
33 34
34 @Injectable({ 35 @Injectable({
35 providedIn: 'root' 36 providedIn: 'root'
@@ -137,6 +138,14 @@ export class DeviceService { @@ -137,6 +138,14 @@ export class DeviceService {
137 return this.http.post<Device>(`/api/rpc/twoway/${deviceId}`, requestBody, defaultHttpOptionsFromConfig(config)); 138 return this.http.post<Device>(`/api/rpc/twoway/${deviceId}`, requestBody, defaultHttpOptionsFromConfig(config));
138 } 139 }
139 140
  141 + public getPersistedRpc(rpcId: string, fullResponse = false,
  142 + config?: RequestConfig): Observable<PersistentRpc | HttpResponse<PersistentRpc>> {
  143 + return this.http.get<PersistentRpc>(`/api/rpc/persistent/${rpcId}`, {
  144 + ...defaultHttpOptionsFromConfig(config),
  145 + observe: fullResponse ? 'response' : undefined
  146 + });
  147 + }
  148 +
140 public findByQuery(query: DeviceSearchQuery, 149 public findByQuery(query: DeviceSearchQuery,
141 config?: RequestConfig): Observable<Array<Device>> { 150 config?: RequestConfig): Observable<Array<Device>> {
142 return this.http.post<Array<Device>>('/api/devices', query, defaultHttpOptionsFromConfig(config)); 151 return this.http.post<Array<Device>>('/api/devices', query, defaultHttpOptionsFromConfig(config));
@@ -170,7 +179,7 @@ export class DeviceService { @@ -170,7 +179,7 @@ export class DeviceService {
170 public getEdgeDevices(edgeId: string, pageLink: PageLink, type: string = '', 179 public getEdgeDevices(edgeId: string, pageLink: PageLink, type: string = '',
171 config?: RequestConfig): Observable<PageData<DeviceInfo>> { 180 config?: RequestConfig): Observable<PageData<DeviceInfo>> {
172 return this.http.get<PageData<DeviceInfo>>(`/api/edge/${edgeId}/devices${pageLink.toQuery()}&type=${type}`, 181 return this.http.get<PageData<DeviceInfo>>(`/api/edge/${edgeId}/devices${pageLink.toQuery()}&type=${type}`,
173 - defaultHttpOptionsFromConfig(config)) 182 + defaultHttpOptionsFromConfig(config));
174 } 183 }
175 184
176 } 185 }
@@ -36,6 +36,8 @@ interface KnobSettings { @@ -36,6 +36,8 @@ interface KnobSettings {
36 getValueMethod: string; 36 getValueMethod: string;
37 setValueMethod: string; 37 setValueMethod: string;
38 requestTimeout: number; 38 requestTimeout: number;
  39 + requestPersistent: boolean;
  40 + persistentPollingInterval: number;
39 } 41 }
40 42
41 @Component({ 43 @Component({
@@ -80,6 +82,8 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { @@ -80,6 +82,8 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
80 82
81 private isSimulated: boolean; 83 private isSimulated: boolean;
82 private requestTimeout: number; 84 private requestTimeout: number;
  85 + private requestPersistent: boolean;
  86 + private persistentPollingInterval: number;
83 private getValueMethod: string; 87 private getValueMethod: string;
84 private setValueMethod: string; 88 private setValueMethod: string;
85 89
@@ -138,6 +142,7 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { @@ -138,6 +142,7 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
138 if (this.knobResize$) { 142 if (this.knobResize$) {
139 this.knobResize$.disconnect(); 143 this.knobResize$.disconnect();
140 } 144 }
  145 + this.ctx.controlApi.completedCommand();
141 } 146 }
142 147
143 private init() { 148 private init() {
@@ -160,8 +165,8 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { @@ -160,8 +165,8 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
160 maxValue: this.maxValue, 165 maxValue: this.maxValue,
161 gaugeType: 'donut', 166 gaugeType: 'donut',
162 dashThickness: 2, 167 dashThickness: 2,
163 - donutStartAngle: 3/4*Math.PI,  
164 - donutEndAngle: 9/4*Math.PI, 168 + donutStartAngle: 3 / 4 * Math.PI,
  169 + donutEndAngle: 9 / 4 * Math.PI,
165 animation: false 170 animation: false
166 }; 171 };
167 172
@@ -209,10 +214,10 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { @@ -209,10 +214,10 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
209 e.preventDefault(); 214 e.preventDefault();
210 const offset = this.knob.offset(); 215 const offset = this.knob.offset();
211 const center = { 216 const center = {
212 - y : offset.top + this.knob.height()/2,  
213 - x: offset.left + this.knob.width()/2 217 + y: offset.top + this.knob.height() / 2,
  218 + x: offset.left + this.knob.width() / 2
214 }; 219 };
215 - const rad2deg = 180/Math.PI; 220 + const rad2deg = 180 / Math.PI;
216 221
217 $(document).on('mousemove.rem touchmove.rem', (ev) => { 222 $(document).on('mousemove.rem touchmove.rem', (ev) => {
218 this.moving = true; 223 this.moving = true;
@@ -220,21 +225,20 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { @@ -220,21 +225,20 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
220 225
221 const a = center.y - t.pageY; 226 const a = center.y - t.pageY;
222 const b = center.x - t.pageX; 227 const b = center.x - t.pageX;
223 - let deg = Math.atan2(a,b)*rad2deg;  
224 - if(deg < 0){ 228 + let deg = Math.atan2(a, b) * rad2deg;
  229 + if (deg < 0) {
225 deg = 360 + deg; 230 deg = 360 + deg;
226 } 231 }
227 232
228 - if(this.startDeg === -1){ 233 + if (this.startDeg === -1) {
229 this.startDeg = deg; 234 this.startDeg = deg;
230 } 235 }
231 236
232 - let tmp = Math.floor((deg-this.startDeg) + this.rotation); 237 + let tmp = Math.floor((deg - this.startDeg) + this.rotation);
233 238
234 - if(tmp < 0){ 239 + if (tmp < 0) {
235 tmp = 360 + tmp; 240 tmp = 360 + tmp;
236 - }  
237 - else if(tmp > 359){ 241 + } else if (tmp > 359) {
238 tmp = tmp % 360; 242 tmp = tmp % 360;
239 } 243 }
240 244
@@ -251,7 +255,7 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { @@ -251,7 +255,7 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
251 } 255 }
252 } 256 }
253 } 257 }
254 - if(Math.abs(tmp - this.lastDeg) > 180){ 258 + if (Math.abs(tmp - this.lastDeg) > 180) {
255 this.startDeg = deg; 259 this.startDeg = deg;
256 this.rotation = this.currentDeg; 260 this.rotation = this.currentDeg;
257 return false; 261 return false;
@@ -260,12 +264,12 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { @@ -260,12 +264,12 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
260 this.currentDeg = tmp; 264 this.currentDeg = tmp;
261 this.lastDeg = tmp; 265 this.lastDeg = tmp;
262 266
263 - this.knobTopPointerContainer.css('transform','rotate('+(this.currentDeg)+'deg)'); 267 + this.knobTopPointerContainer.css('transform', 'rotate(' + (this.currentDeg) + 'deg)');
264 this.turn(this.degreeToRatio(this.currentDeg)); 268 this.turn(this.degreeToRatio(this.currentDeg));
265 }); 269 });
266 270
267 - $(document).on('mouseup.rem touchend.rem',() => {  
268 - if(this.newValue !== this.rpcValue && this.moving) { 271 + $(document).on('mouseup.rem touchend.rem', () => {
  272 + if (this.newValue !== this.rpcValue && this.moving) {
269 this.rpcUpdateValue(this.newValue); 273 this.rpcUpdateValue(this.newValue);
270 } 274 }
271 this.knob.off('.rem'); 275 this.knob.off('.rem');
@@ -285,6 +289,14 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { @@ -285,6 +289,14 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
285 if (settings.requestTimeout) { 289 if (settings.requestTimeout) {
286 this.requestTimeout = settings.requestTimeout; 290 this.requestTimeout = settings.requestTimeout;
287 } 291 }
  292 + this.requestPersistent = false;
  293 + if (settings.requestPersistent) {
  294 + this.requestPersistent = settings.requestPersistent;
  295 + }
  296 + this.persistentPollingInterval = 5000;
  297 + if (settings.persistentPollingInterval) {
  298 + this.persistentPollingInterval = settings.persistentPollingInterval;
  299 + }
288 this.getValueMethod = 'getValue'; 300 this.getValueMethod = 'getValue';
289 if (settings.getValueMethod && settings.getValueMethod.length) { 301 if (settings.getValueMethod && settings.getValueMethod.length) {
290 this.getValueMethod = settings.getValueMethod; 302 this.getValueMethod = settings.getValueMethod;
@@ -312,15 +324,15 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { @@ -312,15 +324,15 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
312 } 324 }
313 325
314 private degreeToRatio(degree: number): number { 326 private degreeToRatio(degree: number): number {
315 - return (degree-this.minDeg)/(this.maxDeg-this.minDeg); 327 + return (degree - this.minDeg) / (this.maxDeg - this.minDeg);
316 } 328 }
317 329
318 private ratioToDegree(ratio: number): number { 330 private ratioToDegree(ratio: number): number {
319 - return this.minDeg + ratio*(this.maxDeg-this.minDeg); 331 + return this.minDeg + ratio * (this.maxDeg - this.minDeg);
320 } 332 }
321 333
322 private turn(ratio: number) { 334 private turn(ratio: number) {
323 - this.newValue = Number((this.minValue + (this.maxValue - this.minValue)*ratio).toFixed(this.ctx.decimals)); 335 + this.newValue = Number((this.minValue + (this.maxValue - this.minValue) * ratio).toFixed(this.ctx.decimals));
324 if (this.canvasBar.value !== this.newValue) { 336 if (this.canvasBar.value !== this.newValue) {
325 this.canvasBar.value = this.newValue; 337 this.canvasBar.value = this.newValue;
326 } 338 }
@@ -339,14 +351,14 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { @@ -339,14 +351,14 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
339 this.setFontSize(this.knobTitle, this.title, this.knobTitleContainer.height(), this.knobTitleContainer.width()); 351 this.setFontSize(this.knobTitle, this.title, this.knobTitleContainer.height(), this.knobTitleContainer.width());
340 this.setFontSize(this.knobError, this.error, this.knobErrorContainer.height(), this.knobErrorContainer.width()); 352 this.setFontSize(this.knobError, this.error, this.knobErrorContainer.height(), this.knobErrorContainer.width());
341 const minmaxHeight = this.knobMinmaxContainer.height(); 353 const minmaxHeight = this.knobMinmaxContainer.height();
342 - this.minmaxLabel.css({fontSize: minmaxHeight+'px', lineHeight: minmaxHeight+'px'}); 354 + this.minmaxLabel.css({fontSize: minmaxHeight + 'px', lineHeight: minmaxHeight + 'px'});
343 this.checkValueSize(); 355 this.checkValueSize();
344 } 356 }
345 357
346 private checkValueSize() { 358 private checkValueSize() {
347 - const fontSize = this.knobValueContainer.height()/3.3; 359 + const fontSize = this.knobValueContainer.height() / 3.3;
348 const containerWidth = this.knobValueContainer.width(); 360 const containerWidth = this.knobValueContainer.width();
349 - this.setFontSize(this.knobValue, this.value+'', fontSize, containerWidth); 361 + this.setFontSize(this.knobValue, this.value + '', fontSize, containerWidth);
350 } 362 }
351 363
352 private setFontSize(element: JQuery<HTMLElement>, text: string, fontSize: number, maxWidth: number) { 364 private setFontSize(element: JQuery<HTMLElement>, text: string, fontSize: number, maxWidth: number) {
@@ -358,19 +370,19 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { @@ -358,19 +370,19 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
358 } 370 }
359 textWidth = this.measureTextWidth(text, fontSize); 371 textWidth = this.measureTextWidth(text, fontSize);
360 } 372 }
361 - element.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'}); 373 + element.css({fontSize: fontSize + 'px', lineHeight: fontSize + 'px'});
362 } 374 }
363 375
364 private measureTextWidth(text: string, fontSize: number): number { 376 private measureTextWidth(text: string, fontSize: number): number {
365 - this.textMeasure.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'}); 377 + this.textMeasure.css({fontSize: fontSize + 'px', lineHeight: fontSize + 'px'});
366 this.textMeasure.html(text); 378 this.textMeasure.html(text);
367 return this.textMeasure.width(); 379 return this.textMeasure.width();
368 } 380 }
369 381
370 private setValue(value: number) { 382 private setValue(value: number) {
371 - const ratio = (value-this.minValue) / (this.maxValue - this.minValue); 383 + const ratio = (value - this.minValue) / (this.maxValue - this.minValue);
372 this.rotation = this.lastDeg = this.currentDeg = this.ratioToDegree(ratio); 384 this.rotation = this.lastDeg = this.currentDeg = this.ratioToDegree(ratio);
373 - this.knobTopPointerContainer.css('transform','rotate('+(this.currentDeg)+'deg)'); 385 + this.knobTopPointerContainer.css('transform', 'rotate(' + (this.currentDeg) + 'deg)');
374 if (this.canvasBar.value !== value) { 386 if (this.canvasBar.value !== value) {
375 this.canvasBar.value = value; 387 this.canvasBar.value = value;
376 } 388 }
@@ -402,7 +414,8 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { @@ -402,7 +414,8 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
402 414
403 private rpcRequestValue() { 415 private rpcRequestValue() {
404 this.error = ''; 416 this.error = '';
405 - this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout).subscribe( 417 + this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout,
  418 + this.requestPersistent, this.persistentPollingInterval).subscribe(
406 (responseBody) => { 419 (responseBody) => {
407 if (isNumber(responseBody)) { 420 if (isNumber(responseBody)) {
408 const numValue = Number(Number(responseBody).toFixed(this.ctx.decimals)); 421 const numValue = Number(Number(responseBody).toFixed(this.ctx.decimals));
@@ -429,7 +442,8 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { @@ -429,7 +442,8 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
429 this.executingUpdateValue = true; 442 this.executingUpdateValue = true;
430 } 443 }
431 this.error = ''; 444 this.error = '';
432 - this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, value, this.requestTimeout).subscribe( 445 + this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, value, this.requestTimeout,
  446 + this.requestPersistent, this.persistentPollingInterval).subscribe(
433 () => { 447 () => {
434 this.executingUpdateValue = false; 448 this.executingUpdateValue = false;
435 if (this.scheduledValue != null && this.scheduledValue !== this.rpcValue) { 449 if (this.scheduledValue != null && this.scheduledValue !== this.rpcValue) {
@@ -44,6 +44,8 @@ interface LedIndicatorSettings { @@ -44,6 +44,8 @@ interface LedIndicatorSettings {
44 valueAttribute: string; 44 valueAttribute: string;
45 parseValueFunction: string; 45 parseValueFunction: string;
46 requestTimeout: number; 46 requestTimeout: number;
  47 + requestPersistent: boolean;
  48 + persistentPollingInterval: number;
47 } 49 }
48 50
49 @Component({ 51 @Component({
@@ -77,6 +79,8 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe @@ -77,6 +79,8 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe
77 79
78 private isSimulated: boolean; 80 private isSimulated: boolean;
79 private requestTimeout: number; 81 private requestTimeout: number;
  82 + private requestPersistent: boolean;
  83 + private persistentPollingInterval: number;
80 private retrieveValueMethod: RetrieveValueMethod; 84 private retrieveValueMethod: RetrieveValueMethod;
81 private parseValueFunction: (data: any) => boolean; 85 private parseValueFunction: (data: any) => boolean;
82 private performCheckStatus: boolean; 86 private performCheckStatus: boolean;
@@ -141,6 +145,7 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe @@ -141,6 +145,7 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe
141 if (this.ledResize$) { 145 if (this.ledResize$) {
142 this.ledResize$.disconnect(); 146 this.ledResize$.disconnect();
143 } 147 }
  148 + this.ctx.controlApi.completedCommand();
144 } 149 }
145 150
146 private init() { 151 private init() {
@@ -168,6 +173,14 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe @@ -168,6 +173,14 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe
168 if (settings.requestTimeout) { 173 if (settings.requestTimeout) {
169 this.requestTimeout = settings.requestTimeout; 174 this.requestTimeout = settings.requestTimeout;
170 } 175 }
  176 + this.requestPersistent = false;
  177 + if (settings.requestPersistent) {
  178 + this.requestPersistent = settings.requestPersistent;
  179 + }
  180 + this.persistentPollingInterval = 5000;
  181 + if (settings.persistentPollingInterval) {
  182 + this.persistentPollingInterval = settings.persistentPollingInterval;
  183 + }
171 this.retrieveValueMethod = 'attribute'; 184 this.retrieveValueMethod = 'attribute';
172 if (settings.retrieveValueMethod && settings.retrieveValueMethod.length) { 185 if (settings.retrieveValueMethod && settings.retrieveValueMethod.length) {
173 this.retrieveValueMethod = settings.retrieveValueMethod; 186 this.retrieveValueMethod = settings.retrieveValueMethod;
@@ -219,11 +232,11 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe @@ -219,11 +232,11 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe
219 fontSize--; 232 fontSize--;
220 textWidth = this.measureTextWidth(text, fontSize); 233 textWidth = this.measureTextWidth(text, fontSize);
221 } 234 }
222 - element.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'}); 235 + element.css({fontSize: fontSize + 'px', lineHeight: fontSize + 'px'});
223 } 236 }
224 237
225 private measureTextWidth(text: string, fontSize: number): number { 238 private measureTextWidth(text: string, fontSize: number): number {
226 - this.textMeasure.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'}); 239 + this.textMeasure.css({fontSize: fontSize + 'px', lineHeight: fontSize + 'px'});
227 this.textMeasure.text(text); 240 this.textMeasure.text(text);
228 return this.textMeasure.width(); 241 return this.textMeasure.width();
229 } 242 }
@@ -259,7 +272,8 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe @@ -259,7 +272,8 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe
259 return; 272 return;
260 } 273 }
261 this.error = ''; 274 this.error = '';
262 - this.ctx.controlApi.sendTwoWayCommand(this.checkStatusMethod, null, this.requestTimeout).subscribe( 275 + this.ctx.controlApi.sendTwoWayCommand(this.checkStatusMethod, null, this.requestTimeout,
  276 + this.requestPersistent, this.persistentPollingInterval).subscribe(
263 (responseBody) => { 277 (responseBody) => {
264 const status = !!responseBody; 278 const status = !!responseBody;
265 if (status) { 279 if (status) {
@@ -313,7 +327,7 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe @@ -313,7 +327,7 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe
313 ); 327 );
314 } 328 }
315 329
316 - private onDataUpdated (subscription: IWidgetSubscription, detectChanges: boolean) { 330 + private onDataUpdated(subscription: IWidgetSubscription, detectChanges: boolean) {
317 let value = false; 331 let value = false;
318 const data = subscription.data; 332 const data = subscription.data;
319 if (data.length) { 333 if (data.length) {
@@ -38,6 +38,8 @@ interface RoundSwitchSettings { @@ -38,6 +38,8 @@ interface RoundSwitchSettings {
38 parseValueFunction: string; 38 parseValueFunction: string;
39 convertValueFunction: string; 39 convertValueFunction: string;
40 requestTimeout: number; 40 requestTimeout: number;
  41 + requestPersistent: boolean;
  42 + persistentPollingInterval: number;
41 } 43 }
42 44
43 @Component({ 45 @Component({
@@ -67,6 +69,8 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes @@ -67,6 +69,8 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes
67 69
68 private isSimulated: boolean; 70 private isSimulated: boolean;
69 private requestTimeout: number; 71 private requestTimeout: number;
  72 + private requestPersistent: boolean;
  73 + private persistentPollingInterval: number;
70 private retrieveValueMethod: RetrieveValueMethod; 74 private retrieveValueMethod: RetrieveValueMethod;
71 private valueKey: string; 75 private valueKey: string;
72 private parseValueFunction: (data: any) => boolean; 76 private parseValueFunction: (data: any) => boolean;
@@ -125,6 +129,7 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes @@ -125,6 +129,7 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes
125 if (this.switchResize$) { 129 if (this.switchResize$) {
126 this.switchResize$.disconnect(); 130 this.switchResize$.disconnect();
127 } 131 }
  132 + this.ctx.controlApi.completedCommand();
128 } 133 }
129 134
130 private init() { 135 private init() {
@@ -143,6 +148,14 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes @@ -143,6 +148,14 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes
143 if (settings.requestTimeout) { 148 if (settings.requestTimeout) {
144 this.requestTimeout = settings.requestTimeout; 149 this.requestTimeout = settings.requestTimeout;
145 } 150 }
  151 + this.requestPersistent = false;
  152 + if (settings.requestPersistent) {
  153 + this.requestPersistent = settings.requestPersistent;
  154 + }
  155 + this.persistentPollingInterval = 5000;
  156 + if (settings.persistentPollingInterval) {
  157 + this.persistentPollingInterval = settings.persistentPollingInterval;
  158 + }
146 this.retrieveValueMethod = 'rpc'; 159 this.retrieveValueMethod = 'rpc';
147 if (settings.retrieveValueMethod && settings.retrieveValueMethod.length) { 160 if (settings.retrieveValueMethod && settings.retrieveValueMethod.length) {
148 this.retrieveValueMethod = settings.retrieveValueMethod; 161 this.retrieveValueMethod = settings.retrieveValueMethod;
@@ -193,7 +206,7 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes @@ -193,7 +206,7 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes
193 const width = this.switchContainer.width(); 206 const width = this.switchContainer.width();
194 const height = this.switchContainer.height(); 207 const height = this.switchContainer.height();
195 const size = Math.min(width, height); 208 const size = Math.min(width, height);
196 - const scale = size/260; 209 + const scale = size / 260;
197 this.switchElement.css({ 210 this.switchElement.css({
198 '-webkit-transform': `scale(${scale})`, 211 '-webkit-transform': `scale(${scale})`,
199 '-moz-transform': `scale(${scale})`, 212 '-moz-transform': `scale(${scale})`,
@@ -213,11 +226,11 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes @@ -213,11 +226,11 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes
213 fontSize--; 226 fontSize--;
214 textWidth = this.measureTextWidth(text, fontSize); 227 textWidth = this.measureTextWidth(text, fontSize);
215 } 228 }
216 - element.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'}); 229 + element.css({fontSize: fontSize + 'px', lineHeight: fontSize + 'px'});
217 } 230 }
218 231
219 private measureTextWidth(text: string, fontSize: number): number { 232 private measureTextWidth(text: string, fontSize: number): number {
220 - this.textMeasure.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'}); 233 + this.textMeasure.css({fontSize: fontSize + 'px', lineHeight: fontSize + 'px'});
221 this.textMeasure.text(text); 234 this.textMeasure.text(text);
222 return this.textMeasure.width(); 235 return this.textMeasure.width();
223 } 236 }
@@ -239,7 +252,8 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes @@ -239,7 +252,8 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes
239 252
240 private rpcRequestValue() { 253 private rpcRequestValue() {
241 this.error = ''; 254 this.error = '';
242 - this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout).subscribe( 255 + this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout,
  256 + this.requestPersistent, this.persistentPollingInterval).subscribe(
243 (responseBody) => { 257 (responseBody) => {
244 this.setValue(this.parseValueFunction(responseBody)); 258 this.setValue(this.parseValueFunction(responseBody));
245 }, 259 },
@@ -260,7 +274,8 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes @@ -260,7 +274,8 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes
260 this.executingUpdateValue = true; 274 this.executingUpdateValue = true;
261 } 275 }
262 this.error = ''; 276 this.error = '';
263 - this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, this.convertValueFunction(value), this.requestTimeout).subscribe( 277 + this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, this.convertValueFunction(value), this.requestTimeout,
  278 + this.requestPersistent, this.persistentPollingInterval).subscribe(
264 () => { 279 () => {
265 this.executingUpdateValue = false; 280 this.executingUpdateValue = false;
266 if (this.scheduledValue != null && this.scheduledValue !== this.rpcValue) { 281 if (this.scheduledValue != null && this.scheduledValue !== this.rpcValue) {
@@ -42,6 +42,8 @@ interface SwitchSettings { @@ -42,6 +42,8 @@ interface SwitchSettings {
42 parseValueFunction: string; 42 parseValueFunction: string;
43 convertValueFunction: string; 43 convertValueFunction: string;
44 requestTimeout: number; 44 requestTimeout: number;
  45 + requestPersistent: boolean;
  46 + persistentPollingInterval: number;
45 } 47 }
46 48
47 @Component({ 49 @Component({
@@ -74,6 +76,8 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy @@ -74,6 +76,8 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy
74 76
75 private isSimulated: boolean; 77 private isSimulated: boolean;
76 private requestTimeout: number; 78 private requestTimeout: number;
  79 + private requestPersistent: boolean;
  80 + private persistentPollingInterval: number;
77 private retrieveValueMethod: RetrieveValueMethod; 81 private retrieveValueMethod: RetrieveValueMethod;
78 private valueKey: string; 82 private valueKey: string;
79 private parseValueFunction: (data: any) => boolean; 83 private parseValueFunction: (data: any) => boolean;
@@ -133,6 +137,7 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy @@ -133,6 +137,7 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy
133 if (this.switchResize$) { 137 if (this.switchResize$) {
134 this.switchResize$.disconnect(); 138 this.switchResize$.disconnect();
135 } 139 }
  140 + this.ctx.controlApi.completedCommand();
136 } 141 }
137 142
138 private init() { 143 private init() {
@@ -152,6 +157,14 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy @@ -152,6 +157,14 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy
152 if (settings.requestTimeout) { 157 if (settings.requestTimeout) {
153 this.requestTimeout = settings.requestTimeout; 158 this.requestTimeout = settings.requestTimeout;
154 } 159 }
  160 + this.requestPersistent = false;
  161 + if (settings.requestPersistent) {
  162 + this.requestPersistent = settings.requestPersistent;
  163 + }
  164 + this.persistentPollingInterval = 5000;
  165 + if (settings.persistentPollingInterval) {
  166 + this.persistentPollingInterval = settings.persistentPollingInterval;
  167 + }
155 this.retrieveValueMethod = 'rpc'; 168 this.retrieveValueMethod = 'rpc';
156 if (settings.retrieveValueMethod && settings.retrieveValueMethod.length) { 169 if (settings.retrieveValueMethod && settings.retrieveValueMethod.length) {
157 this.retrieveValueMethod = settings.retrieveValueMethod; 170 this.retrieveValueMethod = settings.retrieveValueMethod;
@@ -257,7 +270,8 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy @@ -257,7 +270,8 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy
257 270
258 private rpcRequestValue() { 271 private rpcRequestValue() {
259 this.error = ''; 272 this.error = '';
260 - this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout).subscribe( 273 + this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout,
  274 + this.requestPersistent, this.persistentPollingInterval).subscribe(
261 (responseBody) => { 275 (responseBody) => {
262 this.setValue(this.parseValueFunction(responseBody)); 276 this.setValue(this.parseValueFunction(responseBody));
263 this.ctx.detectChanges(); 277 this.ctx.detectChanges();
@@ -279,7 +293,8 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy @@ -279,7 +293,8 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy
279 this.executingUpdateValue = true; 293 this.executingUpdateValue = true;
280 } 294 }
281 this.error = ''; 295 this.error = '';
282 - this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, this.convertValueFunction(value), this.requestTimeout).subscribe( 296 + this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, this.convertValueFunction(value), this.requestTimeout,
  297 + this.requestPersistent, this.persistentPollingInterval).subscribe(
283 () => { 298 () => {
284 this.executingUpdateValue = false; 299 this.executingUpdateValue = false;
285 if (this.scheduledValue != null && this.scheduledValue !== this.rpcValue) { 300 if (this.scheduledValue != null && this.scheduledValue !== this.rpcValue) {
@@ -202,6 +202,13 @@ export class WidgetContext { @@ -202,6 +202,13 @@ export class WidgetContext {
202 } else { 202 } else {
203 return of(null); 203 return of(null);
204 } 204 }
  205 + },
  206 + completedCommand: () => {
  207 + if (this.defaultSubscription) {
  208 + return this.defaultSubscription.completedCommand();
  209 + } else {
  210 + return of(null);
  211 + }
205 } 212 }
206 }; 213 };
207 214
@@ -471,6 +471,12 @@ export const widgetContextCompletions: TbEditorCompletions = { @@ -471,6 +471,12 @@ export const widgetContextCompletions: TbEditorCompletions = {
471 description: 'RPC request persistent', 471 description: 'RPC request persistent',
472 type: 'boolean', 472 type: 'boolean',
473 optional: true 473 optional: true
  474 + },
  475 + {
  476 + name: 'persistentPollingInterval',
  477 + description: 'Polling interval in milliseconds to get persistent RPC command response',
  478 + type: 'number',
  479 + optional: true
474 } 480 }
475 ], 481 ],
476 return: { 482 return: {
@@ -504,6 +510,12 @@ export const widgetContextCompletions: TbEditorCompletions = { @@ -504,6 +510,12 @@ export const widgetContextCompletions: TbEditorCompletions = {
504 description: 'RPC request persistent', 510 description: 'RPC request persistent',
505 type: 'boolean', 511 type: 'boolean',
506 optional: true 512 optional: true
  513 + },
  514 + {
  515 + name: 'persistentPollingInterval',
  516 + description: 'Polling interval in milliseconds to get persistent RPC command response',
  517 + type: 'number',
  518 + optional: true
507 } 519 }
508 ], 520 ],
509 return: { 521 return: {
@@ -35,7 +35,8 @@ export enum EntityType { @@ -35,7 +35,8 @@ export enum EntityType {
35 WIDGET_TYPE = 'WIDGET_TYPE', 35 WIDGET_TYPE = 'WIDGET_TYPE',
36 API_USAGE_STATE = 'API_USAGE_STATE', 36 API_USAGE_STATE = 'API_USAGE_STATE',
37 TB_RESOURCE = 'TB_RESOURCE', 37 TB_RESOURCE = 'TB_RESOURCE',
38 - OTA_PACKAGE = 'OTA_PACKAGE' 38 + OTA_PACKAGE = 'OTA_PACKAGE',
  39 + RPC = 'RPC'
39 } 40 }
40 41
41 export enum AliasEntityType { 42 export enum AliasEntityType {
@@ -26,6 +26,8 @@ export * from './entity-id'; @@ -26,6 +26,8 @@ export * from './entity-id';
26 export * from './entity-view-id'; 26 export * from './entity-view-id';
27 export * from './event-id'; 27 export * from './event-id';
28 export * from './has-uuid'; 28 export * from './has-uuid';
  29 +export * from './ota-package-id';
  30 +export * from './rpc-id';
29 export * from './rule-chain-id'; 31 export * from './rule-chain-id';
30 export * from './rule-node-id'; 32 export * from './rule-node-id';
31 export * from './tenant-id'; 33 export * from './tenant-id';
  1 +///
  2 +/// Copyright © 2016-2021 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { EntityId } from '@shared/models/id/entity-id';
  18 +import { EntityType } from '@shared/models/entity-type.models';
  19 +
  20 +export class RpcId implements EntityId {
  21 + entityType = EntityType.RPC;
  22 + id: string;
  23 + constructor(id: string) {
  24 + this.id = id;
  25 + }
  26 +}
@@ -41,6 +41,7 @@ export * from './oauth2.models'; @@ -41,6 +41,7 @@ export * from './oauth2.models';
41 export * from './queue.models'; 41 export * from './queue.models';
42 export * from './relation.models'; 42 export * from './relation.models';
43 export * from './resource.models'; 43 export * from './resource.models';
  44 +export * from './rpc.models';
44 export * from './rule-chain.models'; 45 export * from './rule-chain.models';
45 export * from './rule-node.models'; 46 export * from './rule-node.models';
46 export * from './settings.models'; 47 export * from './settings.models';
  1 +///
  2 +/// Copyright © 2016-2021 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { TenantId } from '@shared/models/id/tenant-id';
  18 +import { RpcId } from '@shared/models/id/rpc-id';
  19 +import { DeviceId } from '@shared/models/id/device-id';
  20 +
  21 +export enum RpcStatus {
  22 + QUEUED = 'QUEUED',
  23 + DELIVERED = 'DELIVERED',
  24 + SUCCESSFUL = 'SUCCESSFUL',
  25 + TIMEOUT = 'TIMEOUT',
  26 + FAILED = 'FAILED'
  27 +}
  28 +
  29 +export interface PersistentRpc {
  30 + id: RpcId;
  31 + createdTime: number;
  32 + expirationTime: number;
  33 + status: RpcStatus;
  34 + response: any;
  35 + request: {
  36 + id: string;
  37 + };
  38 + deviceId: DeviceId;
  39 + tenantId: TenantId;
  40 +}