diff --git a/application/src/main/data/json/system/widget_bundles/control_widgets.json b/application/src/main/data/json/system/widget_bundles/control_widgets.json
index 130c0c6dd5..03a311fe8c 100644
--- a/application/src/main/data/json/system/widget_bundles/control_widgets.json
+++ b/application/src/main/data/json/system/widget_bundles/control_widgets.json
@@ -18,8 +18,8 @@
"resources": [],
"templateHtml": "
",
"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",
- "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 += ' [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}",
- "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}",
+ "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 += ' [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}",
+ "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}",
"dataKeySettingsSchema": "{}\n",
"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\":{}}"
}
@@ -55,7 +55,7 @@
"templateHtml": "",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n",
- "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\",\n \"default\": 0\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\",\n \"default\": 100\n },\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"number\",\n \"default\": 50\n },\n \"title\": {\n \"title\": \"Knob title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"getValueMethod\": {\n \"title\": \"Get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"Set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"minValue\", \"maxValue\", \"getValueMethod\", \"setValueMethod\", \"requestTimeout\"]\n },\n \"form\": [\n \"minValue\",\n \"maxValue\",\n \"initialValue\",\n \"title\",\n \"getValueMethod\",\n \"setValueMethod\",\n \"requestTimeout\"\n ]\n}",
+ "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\",\n \"default\": 0\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\",\n \"default\": 100\n },\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"number\",\n \"default\": 50\n },\n \"title\": {\n \"title\": \"Knob title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"getValueMethod\": {\n \"title\": \"Get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"Set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"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}",
"dataKeySettingsSchema": "{}\n",
"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}"
}
@@ -73,7 +73,7 @@
"templateHtml": "",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n",
- "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showOnOffLabels\": {\n \"title\": \"Show on/off labels\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"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}",
+ "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}",
"dataKeySettingsSchema": "{}\n",
"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}"
}
@@ -91,7 +91,7 @@
"templateHtml": "",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n",
- "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"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}",
+ "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}",
"dataKeySettingsSchema": "{}\n",
"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}"
}
@@ -109,7 +109,7 @@
"templateHtml": "",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n",
- "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"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}",
+ "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}",
"dataKeySettingsSchema": "{}\n",
"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}"
}
@@ -126,8 +126,8 @@
"resources": [],
"templateHtml": "",
"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}",
- "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",
- "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}",
+ "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",
+ "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}",
"dataKeySettingsSchema": "{}\n",
"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\":{}}"
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java b/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java
index ae9081c39a..b7c617259f 100644
--- a/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java
+++ b/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java
@@ -69,11 +69,27 @@ public class RpcV2Controller extends AbstractRpcController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/persistent/{rpcId}", method = RequestMethod.GET)
@ResponseBody
- public Rpc getPersistedRpc(@PathVariable("rpcId") String strRpc) throws ThingsboardException {
+ public ResponseEntity getPersistedRpc(@PathVariable("rpcId") String strRpc) throws ThingsboardException {
checkParameter("RpcId", strRpc);
try {
RpcId rpcId = new RpcId(UUID.fromString(strRpc));
- return checkRpcId(rpcId, Operation.READ);
+ Rpc rpc = checkRpcId(rpcId, Operation.READ);
+ HttpStatus status;
+ switch (rpc.getStatus()) {
+ case FAILED:
+ status = HttpStatus.BAD_GATEWAY;
+ break;
+ case TIMEOUT:
+ status = HttpStatus.GATEWAY_TIMEOUT;
+ break;
+ case QUEUED:
+ case DELIVERED:
+ status = HttpStatus.ACCEPTED;
+ break;
+ default:
+ status = HttpStatus.OK;
+ }
+ return new ResponseEntity<>(rpc, status);
} catch (Exception e) {
throw handleException(e);
}
diff --git a/ui-ngx/src/app/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts
index 9f99e7aed5..75c43caa95 100644
--- a/ui-ngx/src/app/core/api/widget-api.models.ts
+++ b/ui-ngx/src/app/core/api/widget-api.models.ts
@@ -69,8 +69,11 @@ export interface WidgetSubscriptionApi {
}
export interface RpcApi {
- sendOneWayCommand: (method: string, params?: any, timeout?: number, persistent?: boolean, requestUUID?: string) => Observable;
- sendTwoWayCommand: (method: string, params?: any, timeout?: number, persistent?: boolean, requestUUID?: string) => Observable;
+ sendOneWayCommand: (method: string, params?: any, timeout?: number, persistent?: boolean,
+ persistentPollingInterval?: number, requestUUID?: string) => Observable;
+ sendTwoWayCommand: (method: string, params?: any, timeout?: number, persistent?: boolean,
+ persistentPollingInterval?: number, requestUUID?: string) => Observable;
+ completedCommand: () => void;
}
export interface IWidgetUtils {
@@ -301,8 +304,10 @@ export interface IWidgetSubscription {
onResetTimewindow(): void;
updateTimewindowConfig(newTimewindow: Timewindow): void;
- sendOneWayCommand(method: string, params?: any, timeout?: number, persistent?: boolean, requestUUID?: string): Observable;
- sendTwoWayCommand(method: string, params?: any, timeout?: number, persistent?: boolean, requestUUID?: string): Observable;
+ sendOneWayCommand(method: string, params?: any, timeout?: number, persistent?: boolean,
+ persistentPollingInterval?: number, requestUUID?: string): Observable;
+ sendTwoWayCommand(method: string, params?: any, timeout?: number, persistent?: boolean,
+ persistentPollingInterval?: number, requestUUID?: string): Observable;
clearRpcError(): void;
subscribe(): void;
diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts
index adcdc29054..d888f759a8 100644
--- a/ui-ngx/src/app/core/api/widget-subscription.ts
+++ b/ui-ngx/src/app/core/api/widget-subscription.ts
@@ -35,7 +35,7 @@ import {
LegendKeyData,
widgetType
} from '@app/shared/models/widget.models';
-import { HttpErrorResponse } from '@angular/common/http';
+import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
import {
calculateIntervalStartEndTime,
calculateTsOffset,
@@ -49,7 +49,7 @@ import {
toHistoryTimewindow,
WidgetTimewindow
} from '@app/shared/models/time/time.models';
-import { forkJoin, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs';
+import { forkJoin, Observable, of, ReplaySubject, Subject, throwError, timer } from 'rxjs';
import { CancelAnimationFrame } from '@core/services/raf.service';
import { EntityType } from '@shared/models/entity-type.models';
import { createLabelFromDatasource, deepClone, isDefined, isDefinedAndNotNull, isEqual } from '@core/utils';
@@ -67,8 +67,9 @@ import {
KeyFilter,
updateDatasourceFromEntityInfo
} from '@shared/models/query/query.models';
-import { map } from 'rxjs/operators';
+import { filter, map, switchMap, takeUntil } from 'rxjs/operators';
import { AlarmDataListener } from '@core/api/alarm-data.service';
+import { PersistentRpc } from '@shared/models/rpc.models';
const moment = moment_;
@@ -644,12 +645,14 @@ export class WidgetSubscription implements IWidgetSubscription {
}
}
- sendOneWayCommand(method: string, params?: any, timeout?: number, persistent?: boolean, requestUUID?: string): Observable {
- return this.sendCommand(true, method, params, timeout, persistent, requestUUID);
+ sendOneWayCommand(method: string, params?: any, timeout?: number, persistent?: boolean,
+ persistentPollingInterval?: number, requestUUID?: string): Observable {
+ return this.sendCommand(true, method, params, timeout, persistent, persistentPollingInterval, requestUUID);
}
- sendTwoWayCommand(method: string, params?: any, timeout?: number, persistent?: boolean, requestUUID?: string): Observable {
- return this.sendCommand(false, method, params, timeout, persistent, requestUUID);
+ sendTwoWayCommand(method: string, params?: any, timeout?: number, persistent?: boolean,
+ persistentPollingInterval?: number, requestUUID?: string): Observable {
+ return this.sendCommand(false, method, params, timeout, persistent, persistentPollingInterval, requestUUID);
}
clearRpcError(): void {
@@ -658,8 +661,15 @@ export class WidgetSubscription implements IWidgetSubscription {
this.callbacks.onRpcErrorCleared(this);
}
- sendCommand(oneWayElseTwoWay: boolean, method: string, params?: any,
- timeout?: number, persistent?: boolean, requestUUID?: string): Observable {
+ completedCommand(): void {
+ this.executingSubjects.forEach(subject => {
+ subject.next();
+ subject.complete();
+ });
+ }
+
+ sendCommand(oneWayElseTwoWay: boolean, method: string, params?: any, timeout?: number,
+ persistent?: boolean, persistentPollingInterval?: number, requestUUID?: string): Observable {
if (!this.rpcEnabled) {
return throwError(new Error('Rpc disabled!'));
} else {
@@ -677,7 +687,7 @@ export class WidgetSubscription implements IWidgetSubscription {
if (timeout && timeout > 0) {
requestBody.timeout = timeout;
}
- const rpcSubject: Subject = new ReplaySubject();
+ const rpcSubject: Subject = new Subject();
this.executingRpcRequest = true;
this.callbacks.rpcStateChanged(this);
if (this.ctx.utils.widgetEditMode) {
@@ -695,7 +705,19 @@ export class WidgetSubscription implements IWidgetSubscription {
} else {
this.executingSubjects.push(rpcSubject);
(oneWayElseTwoWay ? this.ctx.deviceService.sendOneWayRpcCommand(this.targetDeviceId, requestBody) :
- this.ctx.deviceService.sendTwoWayRpcCommand(this.targetDeviceId, requestBody))
+ this.ctx.deviceService.sendTwoWayRpcCommand(this.targetDeviceId, requestBody)).pipe(
+ switchMap((response) => {
+ if (persistent && persistentPollingInterval > 0) {
+ return timer(persistentPollingInterval / 2, persistentPollingInterval).pipe(
+ switchMap(() => this.ctx.deviceService.getPersistedRpc(response.rpcId, true)),
+ filter((persistentResponse: HttpResponse) => persistentResponse.status !== 202),
+ map(persistentResponse => persistentResponse.body.response),
+ takeUntil(rpcSubject)
+ );
+ }
+ return of(response);
+ })
+ )
.subscribe((responseBody) => {
this.rpcRejection = null;
this.rpcErrorText = null;
diff --git a/ui-ngx/src/app/core/http/device.service.ts b/ui-ngx/src/app/core/http/device.service.ts
index 682dcc3620..77ee67f8c8 100644
--- a/ui-ngx/src/app/core/http/device.service.ts
+++ b/ui-ngx/src/app/core/http/device.service.ts
@@ -17,7 +17,7 @@
import { Injectable } from '@angular/core';
import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { Observable, ReplaySubject } from 'rxjs';
-import { HttpClient } from '@angular/common/http';
+import { HttpClient, HttpResponse } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link';
import { PageData } from '@shared/models/page/page-data';
import {
@@ -30,6 +30,7 @@ import {
} from '@app/shared/models/device.models';
import { EntitySubtype } from '@app/shared/models/entity-type.models';
import { AuthService } from '@core/auth/auth.service';
+import { PersistentRpc } from '@shared/models/rpc.models';
@Injectable({
providedIn: 'root'
@@ -137,6 +138,14 @@ export class DeviceService {
return this.http.post(`/api/rpc/twoway/${deviceId}`, requestBody, defaultHttpOptionsFromConfig(config));
}
+ public getPersistedRpc(rpcId: string, fullResponse = false,
+ config?: RequestConfig): Observable> {
+ return this.http.get(`/api/rpc/persistent/${rpcId}`, {
+ ...defaultHttpOptionsFromConfig(config),
+ observe: fullResponse ? 'response' : undefined
+ });
+ }
+
public findByQuery(query: DeviceSearchQuery,
config?: RequestConfig): Observable> {
return this.http.post>('/api/devices', query, defaultHttpOptionsFromConfig(config));
@@ -170,7 +179,7 @@ export class DeviceService {
public getEdgeDevices(edgeId: string, pageLink: PageLink, type: string = '',
config?: RequestConfig): Observable> {
return this.http.get>(`/api/edge/${edgeId}/devices${pageLink.toQuery()}&type=${type}`,
- defaultHttpOptionsFromConfig(config))
+ defaultHttpOptionsFromConfig(config));
}
}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.ts
index 847037efe4..547f91b7bb 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.ts
@@ -36,6 +36,8 @@ interface KnobSettings {
getValueMethod: string;
setValueMethod: string;
requestTimeout: number;
+ requestPersistent: boolean;
+ persistentPollingInterval: number;
}
@Component({
@@ -80,6 +82,8 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
private isSimulated: boolean;
private requestTimeout: number;
+ private requestPersistent: boolean;
+ private persistentPollingInterval: number;
private getValueMethod: string;
private setValueMethod: string;
@@ -138,6 +142,7 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
if (this.knobResize$) {
this.knobResize$.disconnect();
}
+ this.ctx.controlApi.completedCommand();
}
private init() {
@@ -160,8 +165,8 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
maxValue: this.maxValue,
gaugeType: 'donut',
dashThickness: 2,
- donutStartAngle: 3/4*Math.PI,
- donutEndAngle: 9/4*Math.PI,
+ donutStartAngle: 3 / 4 * Math.PI,
+ donutEndAngle: 9 / 4 * Math.PI,
animation: false
};
@@ -209,10 +214,10 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
e.preventDefault();
const offset = this.knob.offset();
const center = {
- y : offset.top + this.knob.height()/2,
- x: offset.left + this.knob.width()/2
+ y: offset.top + this.knob.height() / 2,
+ x: offset.left + this.knob.width() / 2
};
- const rad2deg = 180/Math.PI;
+ const rad2deg = 180 / Math.PI;
$(document).on('mousemove.rem touchmove.rem', (ev) => {
this.moving = true;
@@ -220,21 +225,20 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
const a = center.y - t.pageY;
const b = center.x - t.pageX;
- let deg = Math.atan2(a,b)*rad2deg;
- if(deg < 0){
+ let deg = Math.atan2(a, b) * rad2deg;
+ if (deg < 0) {
deg = 360 + deg;
}
- if(this.startDeg === -1){
+ if (this.startDeg === -1) {
this.startDeg = deg;
}
- let tmp = Math.floor((deg-this.startDeg) + this.rotation);
+ let tmp = Math.floor((deg - this.startDeg) + this.rotation);
- if(tmp < 0){
+ if (tmp < 0) {
tmp = 360 + tmp;
- }
- else if(tmp > 359){
+ } else if (tmp > 359) {
tmp = tmp % 360;
}
@@ -251,7 +255,7 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
}
}
}
- if(Math.abs(tmp - this.lastDeg) > 180){
+ if (Math.abs(tmp - this.lastDeg) > 180) {
this.startDeg = deg;
this.rotation = this.currentDeg;
return false;
@@ -260,12 +264,12 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
this.currentDeg = tmp;
this.lastDeg = tmp;
- this.knobTopPointerContainer.css('transform','rotate('+(this.currentDeg)+'deg)');
+ this.knobTopPointerContainer.css('transform', 'rotate(' + (this.currentDeg) + 'deg)');
this.turn(this.degreeToRatio(this.currentDeg));
});
- $(document).on('mouseup.rem touchend.rem',() => {
- if(this.newValue !== this.rpcValue && this.moving) {
+ $(document).on('mouseup.rem touchend.rem', () => {
+ if (this.newValue !== this.rpcValue && this.moving) {
this.rpcUpdateValue(this.newValue);
}
this.knob.off('.rem');
@@ -285,6 +289,14 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
if (settings.requestTimeout) {
this.requestTimeout = settings.requestTimeout;
}
+ this.requestPersistent = false;
+ if (settings.requestPersistent) {
+ this.requestPersistent = settings.requestPersistent;
+ }
+ this.persistentPollingInterval = 5000;
+ if (settings.persistentPollingInterval) {
+ this.persistentPollingInterval = settings.persistentPollingInterval;
+ }
this.getValueMethod = 'getValue';
if (settings.getValueMethod && settings.getValueMethod.length) {
this.getValueMethod = settings.getValueMethod;
@@ -312,15 +324,15 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
}
private degreeToRatio(degree: number): number {
- return (degree-this.minDeg)/(this.maxDeg-this.minDeg);
+ return (degree - this.minDeg) / (this.maxDeg - this.minDeg);
}
private ratioToDegree(ratio: number): number {
- return this.minDeg + ratio*(this.maxDeg-this.minDeg);
+ return this.minDeg + ratio * (this.maxDeg - this.minDeg);
}
private turn(ratio: number) {
- this.newValue = Number((this.minValue + (this.maxValue - this.minValue)*ratio).toFixed(this.ctx.decimals));
+ this.newValue = Number((this.minValue + (this.maxValue - this.minValue) * ratio).toFixed(this.ctx.decimals));
if (this.canvasBar.value !== this.newValue) {
this.canvasBar.value = this.newValue;
}
@@ -339,14 +351,14 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
this.setFontSize(this.knobTitle, this.title, this.knobTitleContainer.height(), this.knobTitleContainer.width());
this.setFontSize(this.knobError, this.error, this.knobErrorContainer.height(), this.knobErrorContainer.width());
const minmaxHeight = this.knobMinmaxContainer.height();
- this.minmaxLabel.css({fontSize: minmaxHeight+'px', lineHeight: minmaxHeight+'px'});
+ this.minmaxLabel.css({fontSize: minmaxHeight + 'px', lineHeight: minmaxHeight + 'px'});
this.checkValueSize();
}
private checkValueSize() {
- const fontSize = this.knobValueContainer.height()/3.3;
+ const fontSize = this.knobValueContainer.height() / 3.3;
const containerWidth = this.knobValueContainer.width();
- this.setFontSize(this.knobValue, this.value+'', fontSize, containerWidth);
+ this.setFontSize(this.knobValue, this.value + '', fontSize, containerWidth);
}
private setFontSize(element: JQuery, text: string, fontSize: number, maxWidth: number) {
@@ -358,19 +370,19 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
}
textWidth = this.measureTextWidth(text, fontSize);
}
- element.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'});
+ element.css({fontSize: fontSize + 'px', lineHeight: fontSize + 'px'});
}
private measureTextWidth(text: string, fontSize: number): number {
- this.textMeasure.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'});
+ this.textMeasure.css({fontSize: fontSize + 'px', lineHeight: fontSize + 'px'});
this.textMeasure.html(text);
return this.textMeasure.width();
}
private setValue(value: number) {
- const ratio = (value-this.minValue) / (this.maxValue - this.minValue);
+ const ratio = (value - this.minValue) / (this.maxValue - this.minValue);
this.rotation = this.lastDeg = this.currentDeg = this.ratioToDegree(ratio);
- this.knobTopPointerContainer.css('transform','rotate('+(this.currentDeg)+'deg)');
+ this.knobTopPointerContainer.css('transform', 'rotate(' + (this.currentDeg) + 'deg)');
if (this.canvasBar.value !== value) {
this.canvasBar.value = value;
}
@@ -402,7 +414,8 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
private rpcRequestValue() {
this.error = '';
- this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout).subscribe(
+ this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout,
+ this.requestPersistent, this.persistentPollingInterval).subscribe(
(responseBody) => {
if (isNumber(responseBody)) {
const numValue = Number(Number(responseBody).toFixed(this.ctx.decimals));
@@ -429,7 +442,8 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
this.executingUpdateValue = true;
}
this.error = '';
- this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, value, this.requestTimeout).subscribe(
+ this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, value, this.requestTimeout,
+ this.requestPersistent, this.persistentPollingInterval).subscribe(
() => {
this.executingUpdateValue = false;
if (this.scheduledValue != null && this.scheduledValue !== this.rpcValue) {
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.ts
index de288d7d5a..92ea5ba8db 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.ts
@@ -44,6 +44,8 @@ interface LedIndicatorSettings {
valueAttribute: string;
parseValueFunction: string;
requestTimeout: number;
+ requestPersistent: boolean;
+ persistentPollingInterval: number;
}
@Component({
@@ -77,6 +79,8 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe
private isSimulated: boolean;
private requestTimeout: number;
+ private requestPersistent: boolean;
+ private persistentPollingInterval: number;
private retrieveValueMethod: RetrieveValueMethod;
private parseValueFunction: (data: any) => boolean;
private performCheckStatus: boolean;
@@ -141,6 +145,7 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe
if (this.ledResize$) {
this.ledResize$.disconnect();
}
+ this.ctx.controlApi.completedCommand();
}
private init() {
@@ -168,6 +173,14 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe
if (settings.requestTimeout) {
this.requestTimeout = settings.requestTimeout;
}
+ this.requestPersistent = false;
+ if (settings.requestPersistent) {
+ this.requestPersistent = settings.requestPersistent;
+ }
+ this.persistentPollingInterval = 5000;
+ if (settings.persistentPollingInterval) {
+ this.persistentPollingInterval = settings.persistentPollingInterval;
+ }
this.retrieveValueMethod = 'attribute';
if (settings.retrieveValueMethod && settings.retrieveValueMethod.length) {
this.retrieveValueMethod = settings.retrieveValueMethod;
@@ -219,11 +232,11 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe
fontSize--;
textWidth = this.measureTextWidth(text, fontSize);
}
- element.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'});
+ element.css({fontSize: fontSize + 'px', lineHeight: fontSize + 'px'});
}
private measureTextWidth(text: string, fontSize: number): number {
- this.textMeasure.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'});
+ this.textMeasure.css({fontSize: fontSize + 'px', lineHeight: fontSize + 'px'});
this.textMeasure.text(text);
return this.textMeasure.width();
}
@@ -259,7 +272,8 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe
return;
}
this.error = '';
- this.ctx.controlApi.sendTwoWayCommand(this.checkStatusMethod, null, this.requestTimeout).subscribe(
+ this.ctx.controlApi.sendTwoWayCommand(this.checkStatusMethod, null, this.requestTimeout,
+ this.requestPersistent, this.persistentPollingInterval).subscribe(
(responseBody) => {
const status = !!responseBody;
if (status) {
@@ -313,7 +327,7 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe
);
}
- private onDataUpdated (subscription: IWidgetSubscription, detectChanges: boolean) {
+ private onDataUpdated(subscription: IWidgetSubscription, detectChanges: boolean) {
let value = false;
const data = subscription.data;
if (data.length) {
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.ts
index a0e894d711..44c7d8d8b1 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.ts
@@ -38,6 +38,8 @@ interface RoundSwitchSettings {
parseValueFunction: string;
convertValueFunction: string;
requestTimeout: number;
+ requestPersistent: boolean;
+ persistentPollingInterval: number;
}
@Component({
@@ -67,6 +69,8 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes
private isSimulated: boolean;
private requestTimeout: number;
+ private requestPersistent: boolean;
+ private persistentPollingInterval: number;
private retrieveValueMethod: RetrieveValueMethod;
private valueKey: string;
private parseValueFunction: (data: any) => boolean;
@@ -125,6 +129,7 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes
if (this.switchResize$) {
this.switchResize$.disconnect();
}
+ this.ctx.controlApi.completedCommand();
}
private init() {
@@ -143,6 +148,14 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes
if (settings.requestTimeout) {
this.requestTimeout = settings.requestTimeout;
}
+ this.requestPersistent = false;
+ if (settings.requestPersistent) {
+ this.requestPersistent = settings.requestPersistent;
+ }
+ this.persistentPollingInterval = 5000;
+ if (settings.persistentPollingInterval) {
+ this.persistentPollingInterval = settings.persistentPollingInterval;
+ }
this.retrieveValueMethod = 'rpc';
if (settings.retrieveValueMethod && settings.retrieveValueMethod.length) {
this.retrieveValueMethod = settings.retrieveValueMethod;
@@ -193,7 +206,7 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes
const width = this.switchContainer.width();
const height = this.switchContainer.height();
const size = Math.min(width, height);
- const scale = size/260;
+ const scale = size / 260;
this.switchElement.css({
'-webkit-transform': `scale(${scale})`,
'-moz-transform': `scale(${scale})`,
@@ -213,11 +226,11 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes
fontSize--;
textWidth = this.measureTextWidth(text, fontSize);
}
- element.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'});
+ element.css({fontSize: fontSize + 'px', lineHeight: fontSize + 'px'});
}
private measureTextWidth(text: string, fontSize: number): number {
- this.textMeasure.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'});
+ this.textMeasure.css({fontSize: fontSize + 'px', lineHeight: fontSize + 'px'});
this.textMeasure.text(text);
return this.textMeasure.width();
}
@@ -239,7 +252,8 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes
private rpcRequestValue() {
this.error = '';
- this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout).subscribe(
+ this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout,
+ this.requestPersistent, this.persistentPollingInterval).subscribe(
(responseBody) => {
this.setValue(this.parseValueFunction(responseBody));
},
@@ -260,7 +274,8 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes
this.executingUpdateValue = true;
}
this.error = '';
- this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, this.convertValueFunction(value), this.requestTimeout).subscribe(
+ this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, this.convertValueFunction(value), this.requestTimeout,
+ this.requestPersistent, this.persistentPollingInterval).subscribe(
() => {
this.executingUpdateValue = false;
if (this.scheduledValue != null && this.scheduledValue !== this.rpcValue) {
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.ts
index d2cf53265f..697c837a6e 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.ts
@@ -42,6 +42,8 @@ interface SwitchSettings {
parseValueFunction: string;
convertValueFunction: string;
requestTimeout: number;
+ requestPersistent: boolean;
+ persistentPollingInterval: number;
}
@Component({
@@ -74,6 +76,8 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy
private isSimulated: boolean;
private requestTimeout: number;
+ private requestPersistent: boolean;
+ private persistentPollingInterval: number;
private retrieveValueMethod: RetrieveValueMethod;
private valueKey: string;
private parseValueFunction: (data: any) => boolean;
@@ -133,6 +137,7 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy
if (this.switchResize$) {
this.switchResize$.disconnect();
}
+ this.ctx.controlApi.completedCommand();
}
private init() {
@@ -152,6 +157,14 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy
if (settings.requestTimeout) {
this.requestTimeout = settings.requestTimeout;
}
+ this.requestPersistent = false;
+ if (settings.requestPersistent) {
+ this.requestPersistent = settings.requestPersistent;
+ }
+ this.persistentPollingInterval = 5000;
+ if (settings.persistentPollingInterval) {
+ this.persistentPollingInterval = settings.persistentPollingInterval;
+ }
this.retrieveValueMethod = 'rpc';
if (settings.retrieveValueMethod && settings.retrieveValueMethod.length) {
this.retrieveValueMethod = settings.retrieveValueMethod;
@@ -257,7 +270,8 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy
private rpcRequestValue() {
this.error = '';
- this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout).subscribe(
+ this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout,
+ this.requestPersistent, this.persistentPollingInterval).subscribe(
(responseBody) => {
this.setValue(this.parseValueFunction(responseBody));
this.ctx.detectChanges();
@@ -279,7 +293,8 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy
this.executingUpdateValue = true;
}
this.error = '';
- this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, this.convertValueFunction(value), this.requestTimeout).subscribe(
+ this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, this.convertValueFunction(value), this.requestTimeout,
+ this.requestPersistent, this.persistentPollingInterval).subscribe(
() => {
this.executingUpdateValue = false;
if (this.scheduledValue != null && this.scheduledValue !== this.rpcValue) {
diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts
index 2be39957e8..f93c5d184a 100644
--- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts
+++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts
@@ -202,6 +202,13 @@ export class WidgetContext {
} else {
return of(null);
}
+ },
+ completedCommand: () => {
+ if (this.defaultSubscription) {
+ return this.defaultSubscription.completedCommand();
+ } else {
+ return of(null);
+ }
}
};
diff --git a/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts b/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts
index c2facf99db..fcbdd2d1ca 100644
--- a/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts
+++ b/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts
@@ -471,6 +471,12 @@ export const widgetContextCompletions: TbEditorCompletions = {
description: 'RPC request persistent',
type: 'boolean',
optional: true
+ },
+ {
+ name: 'persistentPollingInterval',
+ description: 'Polling interval in milliseconds to get persistent RPC command response',
+ type: 'number',
+ optional: true
}
],
return: {
@@ -504,6 +510,12 @@ export const widgetContextCompletions: TbEditorCompletions = {
description: 'RPC request persistent',
type: 'boolean',
optional: true
+ },
+ {
+ name: 'persistentPollingInterval',
+ description: 'Polling interval in milliseconds to get persistent RPC command response',
+ type: 'number',
+ optional: true
}
],
return: {
diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts
index d088cbc88a..1d54b29fa9 100644
--- a/ui-ngx/src/app/shared/models/entity-type.models.ts
+++ b/ui-ngx/src/app/shared/models/entity-type.models.ts
@@ -35,7 +35,8 @@ export enum EntityType {
WIDGET_TYPE = 'WIDGET_TYPE',
API_USAGE_STATE = 'API_USAGE_STATE',
TB_RESOURCE = 'TB_RESOURCE',
- OTA_PACKAGE = 'OTA_PACKAGE'
+ OTA_PACKAGE = 'OTA_PACKAGE',
+ RPC = 'RPC'
}
export enum AliasEntityType {
diff --git a/ui-ngx/src/app/shared/models/id/public-api.ts b/ui-ngx/src/app/shared/models/id/public-api.ts
index 534b50b0f5..3b490a6c97 100644
--- a/ui-ngx/src/app/shared/models/id/public-api.ts
+++ b/ui-ngx/src/app/shared/models/id/public-api.ts
@@ -26,6 +26,8 @@ export * from './entity-id';
export * from './entity-view-id';
export * from './event-id';
export * from './has-uuid';
+export * from './ota-package-id';
+export * from './rpc-id';
export * from './rule-chain-id';
export * from './rule-node-id';
export * from './tenant-id';
diff --git a/ui-ngx/src/app/shared/models/id/rpc-id.ts b/ui-ngx/src/app/shared/models/id/rpc-id.ts
new file mode 100644
index 0000000000..8b3d0b07fd
--- /dev/null
+++ b/ui-ngx/src/app/shared/models/id/rpc-id.ts
@@ -0,0 +1,26 @@
+///
+/// Copyright © 2016-2021 The Thingsboard Authors
+///
+/// Licensed under the Apache License, Version 2.0 (the "License");
+/// you may not use this file except in compliance with the License.
+/// You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing, software
+/// distributed under the License is distributed on an "AS IS" BASIS,
+/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+/// See the License for the specific language governing permissions and
+/// limitations under the License.
+///
+
+import { EntityId } from '@shared/models/id/entity-id';
+import { EntityType } from '@shared/models/entity-type.models';
+
+export class RpcId implements EntityId {
+ entityType = EntityType.RPC;
+ id: string;
+ constructor(id: string) {
+ this.id = id;
+ }
+}
diff --git a/ui-ngx/src/app/shared/models/public-api.ts b/ui-ngx/src/app/shared/models/public-api.ts
index fddcd55033..ddfba7b381 100644
--- a/ui-ngx/src/app/shared/models/public-api.ts
+++ b/ui-ngx/src/app/shared/models/public-api.ts
@@ -41,6 +41,7 @@ export * from './oauth2.models';
export * from './queue.models';
export * from './relation.models';
export * from './resource.models';
+export * from './rpc.models';
export * from './rule-chain.models';
export * from './rule-node.models';
export * from './settings.models';
diff --git a/ui-ngx/src/app/shared/models/rpc.models.ts b/ui-ngx/src/app/shared/models/rpc.models.ts
new file mode 100644
index 0000000000..75ac132394
--- /dev/null
+++ b/ui-ngx/src/app/shared/models/rpc.models.ts
@@ -0,0 +1,40 @@
+///
+/// Copyright © 2016-2021 The Thingsboard Authors
+///
+/// Licensed under the Apache License, Version 2.0 (the "License");
+/// you may not use this file except in compliance with the License.
+/// You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing, software
+/// distributed under the License is distributed on an "AS IS" BASIS,
+/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+/// See the License for the specific language governing permissions and
+/// limitations under the License.
+///
+
+import { TenantId } from '@shared/models/id/tenant-id';
+import { RpcId } from '@shared/models/id/rpc-id';
+import { DeviceId } from '@shared/models/id/device-id';
+
+export enum RpcStatus {
+ QUEUED = 'QUEUED',
+ DELIVERED = 'DELIVERED',
+ SUCCESSFUL = 'SUCCESSFUL',
+ TIMEOUT = 'TIMEOUT',
+ FAILED = 'FAILED'
+}
+
+export interface PersistentRpc {
+ id: RpcId;
+ createdTime: number;
+ expirationTime: number;
+ status: RpcStatus;
+ response: any;
+ request: {
+ id: string;
+ };
+ deviceId: DeviceId;
+ tenantId: TenantId;
+}