diff --git a/create_ohmpi_virtual_environment.sh b/create_ohmpi_virtual_environment.sh index b306cbaff76057c2d13353afc6e07180cf389e1c..f5bc4cbdca75346e225a992f1b21aae2e8a4b5b5 100755 --- a/create_ohmpi_virtual_environment.sh +++ b/create_ohmpi_virtual_environment.sh @@ -4,7 +4,7 @@ sudo apt-get install -y libatlas-base-dev # Create the virtual environment -python3 -m venv ohmpi +python3 -m venv ohmpy # Activate it source ohmpy/bin/activate || exit 1 # NOTE: Added || exit to avoid installing requirements in system python if the virtual environment can't be loaded diff --git a/doc/source/Ohmpi.rst b/doc/source/Ohmpi.rst index 9a579123c44ef3752365092644b405367eb6a3ad..3bfb3dc2a11385ff170aa658987d9b61fee72144 100644 --- a/doc/source/Ohmpi.rst +++ b/doc/source/Ohmpi.rst @@ -16,7 +16,7 @@ OhmPi project | Rémi CLEMENT, Vivien DUBOIS, Nicolas Forquet, INRAE, REVERSAAL, Villeurbanne, France | Yannick FARGIER, GERS-RRO, Univ Gustave Eiffel, IFSTTAR, Lyon, France | Hélène GUYARD, IGE Grenoble, Université Grenoble Alpes, Grenoble, France -| Olivier KAUFMANN, Arnaud WATELET, Université de Mons, Mons, Belgium +| Olivier KAUFMANN, Arnaud WATLET, Université de Mons, Mons, Belgium | Guillaume BLANCHY, ILVO, Merelbeke, Belgium| diff --git a/doc/source/Ohmpi_V2023/V2023_step_02.rst b/doc/source/Ohmpi_V2023/V2023_step_02.rst index b8e2dd9d9d60697c3bdde9f5358a7e5182e79ab5..763078b49d46d2f6dbeb2e9175f694ff76f636cc 100644 --- a/doc/source/Ohmpi_V2023/V2023_step_02.rst +++ b/doc/source/Ohmpi_V2023/V2023_step_02.rst @@ -159,7 +159,7 @@ which allows to realize precise current measurement around a shunt resistor. The .. warning:: - In this version, We used a shunt resistor of 2 ohms, which limits the current measurement to 48 mA. If the current is higher than this value, you just have to decrease the value of the shunt resistor.Change the shunt value in the code. + In this version, we used a shunt resistor of 2 ohms, which limits the current measurement to 48 mA. If the current is higher than this value, you just have to decrease the value of the shunt resistor. Don't forget to change the shunt value in the config.py file (value associated to key 'R_shunt' in the OHMPI_CONFIG dict). diff --git a/doc/source/V2023.rst b/doc/source/V2023.rst index 860f2a5aa431f267364213afb19abb0fdca5054b..b021377cf012a5a4e49a0ec9114a64d85d0b9dc5 100644 --- a/doc/source/V2023.rst +++ b/doc/source/V2023.rst @@ -101,11 +101,11 @@ Loggers ------- Loggers have been introduced in this release. They use the excellent logging python package. -Specific handlers have been implemented for running with ohmpi.py (one for logging to an mqtt broker (see `MQTT interface`_ for more details) and one for creating zipped rotated logs on disk. +Specific handlers have been implemented for running with ohmpi.py (one for logging to an mqtt broker (see `MQTT interface`_ for more details) and one for creating zipped rotated logs on disk). -Two loggers have been defined. The first one is dedicated to log operations execution. It is named exec_logger. The second one is dedicated to acquisition data. A third one is planned to log state of health data (SOH) in a future version. +Two loggers have been defined. The first one is dedicated to log operations execution. It is named exec_logger. The second one, named data_logger, is dedicated to log data. A third one is planned to log the state of health (SOH) of the system in a future version. -In the default version, logs are written to the console (print-like), stored locally in files (a zip is created after some time i.e. every day and/or when the size of the log exceeds a maximum size) and sent to an MQTT broker. Different logging levels may be defined for the different logs and handlers in the `Configuration file`_. +By default, logs are written to the console (print-like), stored locally in files (a zip is created after some time i.e. every day and/or when the size of the log exceeds a maximum size) and sent to an MQTT broker. Different logging levels may be defined for the different logs and handlers in the `Configuration file`_. Advanced users may write new handlers and edit the `setup_loggers.py` file to customize the logging mechanisms to their needs. @@ -138,9 +138,9 @@ Web interface This is a user friendly graphical interface for new users as well as running quick and easy acquisitions. -The raspberrypi of the OhmPi is used as a Wifi Access Point (AP) and runs +The Raspberry Pi of the OhmPi is used as a Wi-Fi Access Point (AP) and runs a small webserver to serve the 'index.html' interface. Using a laptop or -a mobile phone connected to the wifi of the Raspberry Pi, one can see this +a mobile phone connected to the Wi-Fi of the Raspberry Pi, one can see this interface, upload sequences, change parameters, run a sequence and download data. To configure the Raspberry Pi to act as an access point and run @@ -246,7 +246,7 @@ An example of MQTT broker that can be used is `Mosquitto <https://mosquitto.org/ MQTT messages include logging messages from the OhmPi and commands sent to the OhmPi. These messages can be examined easily using a third party software such as `MQTT Explorer <http://mqtt-explorer.com/>`_. -Commands send on the broker are received by the ohmpi.py script that runs on the OhmPi (make sure ohmpi.py starts on reboot) and further processed. +Commands sent on the broker are received by the ohmpi.py script that runs on the OhmPi (make sure ohmpi.py starts on reboot) and further processed. MQTT commands are sent in JSON format following the Python API with kwargs as illustrated below: .. code-block:: json @@ -302,10 +302,25 @@ MQTT commands are sent in JSON format following the Python API with kwargs as il Custom processing of messages and tailor-made dashboards for monitoring experiments may be designed using a browser-based flow editor such as `Node-red <http://mqtt-explorer.com/>`_. This may help designing complex IoT experiments and monitoring systems in which OhmPi is a component. -An example of a simple flow incorporating execution commands and data outputs from OhmPi can be found in the OhmPi examples. +Examples incorporating execution commands and data outputs from OhmPi can be found in the OhmPi examples. Once Node-RED is installed on the OhmPi, these examples can be accessed separately by running a command in the console such as : -.. figure:: img/node-red-flow.png +.. code-block:: console + node-red basic_ohmpi_flows_node-red.json + +These examples may require installing some additional node packages in order to work properly. This can be done in the `Palette Manager <https://nodered.org/docs/user-guide/editor/palette/manager> within Node-RED. + +.. figure:: img/node-red_flow.png Example flow in node-red to interact with an OhmPi. + +.. figure:: img/node-red_interface_control.png + + Example of a dashboard UI created with node-red to interact with an OhmPi - control tab. + +.. figure:: img/node-red_interface_data.png + + Example of a dashboard UI created with node-red to interact with an OhmPi - data visualization tab. + + For more documentation dedicated to node-red, please refer to the Node-red `cookbooks <https://cookbook.nodered.org/>`_. diff --git a/doc/source/img/architecture.png b/doc/source/img/architecture.png index 7b436454a3cad6aeca74595f1a951589fc3e92e6..bcee695323dcbba449bac67291f45fec627dbe51 100644 Binary files a/doc/source/img/architecture.png and b/doc/source/img/architecture.png differ diff --git a/doc/source/img/node-red_flow.png b/doc/source/img/node-red_flow.png new file mode 100644 index 0000000000000000000000000000000000000000..c81b1442d7cc086b41e848e579b3bd321ffde9c4 Binary files /dev/null and b/doc/source/img/node-red_flow.png differ diff --git a/doc/source/img/node-red_interface_control.png b/doc/source/img/node-red_interface_control.png new file mode 100644 index 0000000000000000000000000000000000000000..9ee028f5365758c5387ce3c1530fe3752d9e5a30 Binary files /dev/null and b/doc/source/img/node-red_interface_control.png differ diff --git a/doc/source/img/node-red_interface_data.png b/doc/source/img/node-red_interface_data.png new file mode 100644 index 0000000000000000000000000000000000000000..26db8a2f04c291b0cdbf6e5fa37a8f6c24ad9ba2 Binary files /dev/null and b/doc/source/img/node-red_interface_data.png differ diff --git a/examples/basic_ohmpi_flows_node-red.json b/examples/basic_ohmpi_flows_node-red.json index e6184859f8a3875961dfb3ac1696d8cc9f6798ea..e75647ae4baac8121971caacdecff5b113856ed0 100644 --- a/examples/basic_ohmpi_flows_node-red.json +++ b/examples/basic_ohmpi_flows_node-red.json @@ -6,6 +6,280 @@ "disabled": false, "info": "" }, + { + "id": "6ae7e77e.04c64", + "type": "mqtt-broker", + "name": "ohmpi_local_broker", + "broker": "127.0.0.1", + "port": "1883", + "clientid": "", + "autoConnect": true, + "usetls": false, + "compatmode": false, + "protocolVersion": "4", + "keepalive": "60", + "cleansession": true, + "birthTopic": "", + "birthQos": "0", + "birthPayload": "", + "birthMsg": {}, + "closeTopic": "", + "closeQos": "0", + "closePayload": "", + "closeMsg": {}, + "willTopic": "", + "willQos": "0", + "willPayload": "", + "willMsg": {}, + "userProps": "", + "sessionExpiry": "" + }, + { + "id": "142ad6ae.d55e29", + "type": "ui_group", + "name": "Buttons", + "tab": "5d888f29.07334", + "order": 1, + "disp": true, + "width": "6", + "collapse": false + }, + { + "id": "b0990b3c5ff3c09a", + "type": "ui_group", + "name": "Messages", + "tab": "5d888f29.07334", + "order": 2, + "disp": true, + "width": "16", + "collapse": true + }, + { + "id": "7792ecc419ecbb59", + "type": "ui_group", + "name": "Messages", + "tab": "5d888f29.07334", + "order": 3, + "disp": true, + "width": "16", + "collapse": true, + "className": "" + }, + { + "id": "5d888f29.07334", + "type": "ui_tab", + "name": "Home", + "icon": "dashboard", + "disabled": false, + "hidden": false + }, + { + "id": "f8ac98bca72a3bae", + "type": "ui_base", + "theme": { + "name": "theme-light", + "lightTheme": { + "default": "#0094CE", + "baseColor": "#0094CE", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "edited": true, + "reset": false + }, + "darkTheme": { + "default": "#097479", + "baseColor": "#097479", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "edited": false + }, + "customTheme": { + "name": "Untitled Theme 1", + "default": "#4B7930", + "baseColor": "#4B7930", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" + }, + "themeState": { + "base-color": { + "default": "#0094CE", + "value": "#0094CE", + "edited": false + }, + "page-titlebar-backgroundColor": { + "value": "#0094CE", + "edited": false + }, + "page-backgroundColor": { + "value": "#fafafa", + "edited": false + }, + "page-sidebar-backgroundColor": { + "value": "#ffffff", + "edited": false + }, + "group-textColor": { + "value": "#1bbfff", + "edited": false + }, + "group-borderColor": { + "value": "#ffffff", + "edited": false + }, + "group-backgroundColor": { + "value": "#ffffff", + "edited": false + }, + "widget-textColor": { + "value": "#111111", + "edited": false + }, + "widget-backgroundColor": { + "value": "#0094ce", + "edited": false + }, + "widget-borderColor": { + "value": "#ffffff", + "edited": false + }, + "base-font": { + "value": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" + } + }, + "angularTheme": { + "primary": "indigo", + "accents": "blue", + "warn": "red", + "background": "grey", + "palette": "light" + } + }, + "site": { + "name": "Node-RED Dashboard", + "hideToolbar": "false", + "allowSwipe": "false", + "lockMenu": "false", + "allowTempTheme": "true", + "dateFormat": "DD/MM/YYYY", + "sizes": { + "sx": 48, + "sy": 48, + "gx": 6, + "gy": 6, + "cx": 6, + "cy": 6, + "px": 0, + "py": 0 + } + } + }, + { + "id": "0ee92068968f05f1", + "type": "ui_spacer", + "z": "b19c51e9d4d25a33", + "name": "spacer", + "group": "7792ecc419ecbb59", + "order": 2, + "width": 5, + "height": 1 + }, + { + "id": "1b78d7bfae14207e", + "type": "ui_spacer", + "z": "b19c51e9d4d25a33", + "name": "spacer", + "group": "7792ecc419ecbb59", + "order": 3, + "width": 5, + "height": 1 + }, + { + "id": "0f72806163574caf", + "type": "ui_spacer", + "z": "b19c51e9d4d25a33", + "name": "spacer", + "group": "7792ecc419ecbb59", + "order": 4, + "width": 5, + "height": 1 + }, + { + "id": "4258f679adeaa5f6", + "type": "ui_spacer", + "z": "b19c51e9d4d25a33", + "name": "spacer", + "group": "7792ecc419ecbb59", + "order": 5, + "width": 5, + "height": 1 + }, + { + "id": "7f92e186430dc718", + "type": "ui_spacer", + "z": "b19c51e9d4d25a33", + "name": "spacer", + "group": "7792ecc419ecbb59", + "order": 6, + "width": 5, + "height": 1 + }, + { + "id": "41d806cf3e920625", + "type": "ui_spacer", + "z": "b19c51e9d4d25a33", + "name": "spacer", + "group": "7792ecc419ecbb59", + "order": 7, + "width": 5, + "height": 1 + }, + { + "id": "3430fccc9184a3a6", + "type": "ui_spacer", + "z": "b19c51e9d4d25a33", + "name": "spacer", + "group": "7792ecc419ecbb59", + "order": 8, + "width": 5, + "height": 1 + }, + { + "id": "88b1f28698ec539f", + "type": "ui_spacer", + "z": "b19c51e9d4d25a33", + "name": "spacer", + "group": "142ad6ae.d55e29", + "order": 3, + "width": 4, + "height": 1 + }, + { + "id": "d35dbad3536d9071", + "type": "ui_spacer", + "z": "b19c51e9d4d25a33", + "name": "spacer", + "group": "142ad6ae.d55e29", + "order": 7, + "width": 4, + "height": 1 + }, + { + "id": "d6098d03085c5fdd", + "type": "ui_spacer", + "z": "b19c51e9d4d25a33", + "name": "spacer", + "group": "142ad6ae.d55e29", + "order": 8, + "width": 4, + "height": 1 + }, + { + "id": "ae2e4e8d768a549e", + "type": "ui_spacer", + "z": "b19c51e9d4d25a33", + "name": "spacer", + "group": "142ad6ae.d55e29", + "order": 9, + "width": 4, + "height": 1 + }, { "id": "0f23781293c4b819", "type": "mqtt in", @@ -93,8 +367,8 @@ "name": "Run sequence", "group": "142ad6ae.d55e29", "order": 1, - "width": "1", - "height": "1", + "width": 1, + "height": 1, "passthru": false, "label": "⏺", "tooltip": "run sequence", @@ -121,8 +395,8 @@ "name": "Interrupt", "group": "142ad6ae.d55e29", "order": 2, - "width": "1", - "height": "1", + "width": 1, + "height": 1, "passthru": false, "label": " ◾", "tooltip": "interrupt sequence", @@ -148,8 +422,8 @@ "z": "b19c51e9d4d25a33", "group": "b0990b3c5ff3c09a", "order": 2, - "width": "16", - "height": "3", + "width": 16, + "height": 3, "name": "MQTT exec", "label": "Execution", "format": "{{msg.payload}}", @@ -165,8 +439,8 @@ "z": "b19c51e9d4d25a33", "group": "b0990b3c5ff3c09a", "order": 3, - "width": "16", - "height": "3", + "width": 16, + "height": 3, "name": "MQTT Data", "label": "Data", "format": "{{msg.payload}}", @@ -232,7 +506,7 @@ "tooltip": "", "place": "Select option", "group": "142ad6ae.d55e29", - "order": 5, + "order": 4, "width": 0, "height": 0, "passthru": true, @@ -365,8 +639,8 @@ "z": "b19c51e9d4d25a33", "group": "142ad6ae.d55e29", "order": 6, - "width": "2", - "height": "3", + "width": 2, + "height": 3, "name": "", "label": "Command to send", "format": "{{msg.payload}}", @@ -399,7 +673,7 @@ "z": "b19c51e9d4d25a33", "name": "", "group": "142ad6ae.d55e29", - "order": 7, + "order": 10, "width": 0, "height": 0, "passthru": false, @@ -537,9 +811,9 @@ "z": "b19c51e9d4d25a33", "name": "", "group": "7792ecc419ecbb59", - "order": 3, - "width": "11", - "height": "7", + "order": 1, + "width": 11, + "height": 7, "label": "chart", "chartType": "line", "legend": "true", @@ -617,7 +891,7 @@ "z": "b19c51e9d4d25a33", "name": "", "group": "7792ecc419ecbb59", - "order": 4, + "order": 9, "width": 0, "height": 0, "gtype": "gage", @@ -711,72 +985,5 @@ "91523713d9d4918e" ] ] - }, - { - "id": "6ae7e77e.04c64", - "type": "mqtt-broker", - "name": "ohmpi_local_broker", - "broker": "127.0.0.1", - "port": "1883", - "clientid": "", - "autoConnect": true, - "usetls": false, - "compatmode": false, - "protocolVersion": "4", - "keepalive": "60", - "cleansession": true, - "birthTopic": "", - "birthQos": "0", - "birthPayload": "", - "birthMsg": {}, - "closeTopic": "", - "closeQos": "0", - "closePayload": "", - "closeMsg": {}, - "willTopic": "", - "willQos": "0", - "willPayload": "", - "willMsg": {}, - "userProps": "", - "sessionExpiry": "" - }, - { - "id": "142ad6ae.d55e29", - "type": "ui_group", - "name": "Buttons", - "tab": "5d888f29.07334", - "order": 3, - "disp": true, - "width": "6", - "collapse": false - }, - { - "id": "b0990b3c5ff3c09a", - "type": "ui_group", - "name": "Messages", - "tab": "5d888f29.07334", - "order": 2, - "disp": true, - "width": "16", - "collapse": true - }, - { - "id": "7792ecc419ecbb59", - "type": "ui_group", - "name": "Messages", - "tab": "5d888f29.07334", - "order": 2, - "disp": true, - "width": "16", - "collapse": true, - "className": "" - }, - { - "id": "5d888f29.07334", - "type": "ui_tab", - "name": "Home", - "icon": "dashboard", - "disabled": false, - "hidden": false } -] +] \ No newline at end of file diff --git a/examples/node-red_interface.json b/examples/node-red_interface.json new file mode 100644 index 0000000000000000000000000000000000000000..e38a95d4bb416fe6d0efffa7928125c169b98449 --- /dev/null +++ b/examples/node-red_interface.json @@ -0,0 +1,2815 @@ +[ + { + "id": "b19c51e9d4d25a33", + "type": "tab", + "label": "Flow 1", + "disabled": false, + "info": "" + }, + { + "id": "c3dbd6595b0256a9", + "type": "subflow", + "name": "Send_commands", + "info": "", + "category": "", + "in": [], + "out": [ + { + "x": 1140, + "y": 240, + "wires": [ + { + "id": "71f435fecd185ab7", + "port": 0 + } + ] + } + ], + "env": [], + "meta": {}, + "color": "#DDAA99" + }, + { + "id": "1506d9deaa5de120", + "type": "subflow", + "name": "Update_settings", + "info": "", + "category": "", + "in": [], + "out": [ + { + "x": 1120, + "y": 120, + "wires": [ + { + "id": "1278358184e5b189", + "port": 0 + } + ] + } + ], + "env": [], + "meta": {}, + "color": "#DDAA99" + }, + { + "id": "4489e1d51c9522f3", + "type": "subflow", + "name": "Data_plots", + "info": "", + "category": "", + "in": [ + { + "x": 80, + "y": 300, + "wires": [ + { + "id": "9465b94aca687c9f" + }, + { + "id": "cc84bb5476128ec6" + }, + { + "id": "5bab870fe804aae8" + }, + { + "id": "f655ae35cc8d0036" + } + ] + } + ], + "out": [], + "env": [], + "meta": {}, + "color": "#DDAA99" + }, + { + "id": "b6b050428bd78f42", + "type": "subflow", + "name": "Data_table", + "info": "", + "category": "", + "in": [ + { + "x": 50, + "y": 30, + "wires": [ + { + "id": "61a6e472b87b237a" + } + ] + } + ], + "out": [], + "env": [], + "meta": {}, + "color": "#DDAA99" + }, + { + "id": "2924702c.b33a7", + "type": "subflow", + "name": "ui-table handler", + "info": "# ui-table handler\nUniversal handler for ui-table.\n## features\n- buffer table data\n- add or update individual rows or cells of the table\n- delete rows\n- clear tableData\n- handle column width\n- handle column order\n- hide und unhide columns\n- hide and unhide rows\n- records row order\n- support for nested columns [(column groups)](http://tabulator.info/examples/4.7#column-groups)\n- support for child rows (_children) [(nested data trees)](http://tabulator.info/examples/4.7#tree)\n\nFor real life example see:\n\n**syslog server** for logfile like table with filters\n\n**remote device table** for dynamically updated table with context menues\n\n**irrigation system** for sortable rows\n\n## sending data to ui-tabel\n\n- sending an `array` as discribed in ui-table will replace the complete table and delete all table edits\n \n if `msg.keepEdits=true` is added the existing edits will be kept.\n- send an `object` containing the updated properties of a table row by sending msg.<tableDataProp>.\n\n The table is updated using the `updateOrAddData` command. You can alter the command used by adding the `msg.tabulatorCommand` and `msg.tabulatorParameter`\n\n```\nmsg.tabulatorCommand=\"addData\";\nmsg.tabulatorParameter=[true];\n```\n## configuration\n- `tabulator` json formatted object containing configuration of the table. See ui-table for more details.\n- `property` property of the msg object that contains the data to be passed to ui-table. I.e. *state* `msg.state`\n- `index` index column to identify individual rows. Each message containing data must have a unique `msg.topic` to identify the row. Messages without this `msg.topic` will be droped. It is not nessesary but possible to display the index column in the table. Do not enable editing on this column otherwise you will loose the connection and another row will be added to the table as soon as a new message arrives!\n\n Defaults to *$topic* `msg.state.$topic`\n- `maxRows`maximum number of rows held by table widget. If grater than **0** the amount of rows in ui-table is limited. For this to work the index row must be a Number. ´rows < currentID-maxRows´ will be deleted.\n- `maxStore`maximum number of rows stored by this node for replay if a client connects. If grater than **0** the amount of rows in flow context is limited. for this to work the index row must be a Number. ´rows < currentID-maxStore´ will be deleted.\n- `dashboard` name of the dashboard tab to only update the table if the dashboard is visible. If empty the table will be updated on every tab change and connect.\n- `context` configuration of context data. The subflow will save or cache data in the flows context using `$parent.`. \n **tableData** caches the incoming data to restore it on `ui-control´ *change* messages.\n **tableConfig** saves column width and order to save the interactive table layot\n **tableEdit** saves edits on the table data otherwise it would be overwritten when new data arrives\n```json\n{\n \"tableData\": {\n \"name\": \"tableData\"\n },\n \"tableConfig\": {\n \"name\": \"tableConfig\",\n \"storage\": \"file\"\n },\n \"tableEdit\": {\n \"name\": \"tableEdit\",\n \"storage\": \"file\"\n }\n}\n```\n\n## commands\ncommands can be passed by sending a object as `msg.payload`\n\n```json\n{\n \"command\": \"delete\",\n \"object\": \"columnOrder\"\n}\n```\n\n- `deleteTable` tableCache\n- `deleteRow` delete single row. `object` matching index property\n- `ignoreRow` delete single row and put it on an ignore list. `object` matching index property\n- `unIgnoreRow`remove row from the ignore list. `object` matching index property\n- `unIgnoreRows`delte the ignore list. \n- `deleteRowOrder` delete custom row order\n- `deleteColumnOrder` delete custom column order\n This is important if you add or delete columns in the tabulator config otherwise the columns most likely don`t show up\n- `deleteColumnWidth` delete custom column width\n- `columnHide` hide a column. `object` matching column field\n- `columnUnHide` unhide a column. `object` matching column field\n- `columnsUnHide` unhide all hidden columns.\n- `setMaxStore` set maximum amount of rows in cache\n- `setMaxDisplay` set maximum amout of rows in ui-table\n- `getTable` get table data (as displayed) as an array (on 2nd output)\n \n## background\nui-table warps the powerfull tabluator library. This subflow makes it easier to unleash the powerfull features of ui-table", + "category": "dashboard", + "in": [ + { + "x": 54, + "y": 85, + "wires": [ + { + "id": "5eb0bd6b.74b794" + } + ] + } + ], + "out": [ + { + "x": 360, + "y": 85, + "wires": [ + { + "id": "5eb0bd6b.74b794", + "port": 1 + } + ] + }, + { + "x": 360, + "y": 136, + "wires": [ + { + "id": "5eb0bd6b.74b794", + "port": 2 + } + ] + } + ], + "env": [ + { + "name": "tabulator", + "type": "json", + "value": "{\"tabulator\":{\"responsiveLayout\":\"collapse\",\"responsiveLayoutCollapseStartOpen\":false,\"index\":\"$name\",\"layout\":\"fitColumns\",\"movableColumns\":true,\"groupBy\":\"\",\"columnResized\":\"function(column){ var newColumn = { field: column._column.field, visible: column._column.visible, width: column._column.width, widthFixed: column._column.widthFixed, widthStyled: column._column.widthStyled }; this.send({topic:this.config.topic,ui_control:{callback:'columnResized',columnWidths:newColumn}}); }\",\"columnMoved\":\"function(column, columns){ var newColumns=[]; columns.forEach(function (column) { newColumns.push({'field': column._column.definition.field, 'title': column._column.definition.title}); }); this.send({topic:this.config.topic,ui_control:{callback:'columnMoved',columns:newColumns}}); }\",\"rowFormatter\":\"function(row){ var data = row.getData(); switch (data.$state) { case \\\"lost\\\": row.getElement().style.backgroundColor = \\\"#9e2e66\\\"; row.getElement().style.color = \\\"#a6a6a6\\\"; break; case \\\"sleeping\\\": row.getElement().style.backgroundColor = \\\"#336699\\\"; break; case \\\"disconnected\\\": row.getElement().style.backgroundColor = \\\"#cc3300\\\"; row.getElement().style.color = \\\"#a6a6a6\\\"; break; case \\\"alert\\\": row.getElement().style.backgroundColor = \\\"#A6A6DF\\\"; break; case \\\"init\\\": row.getElement().style.backgroundColor = \\\"#f2f20d\\\"; break; case \\\"ready\\\": row.getElement().style.backgroundColor = \\\"\\\"; row.getElement().style.color = \\\"\\\"; break; } }\",\"columns\":[{\"formatter\":\"responsiveCollapse\",\"width\":30,\"minWidth\":30,\"align\":\"center\",\"resizable\":false,\"headerSort\":false,\"frozen\":true,\"title\":\"expand\",\"field\":\"expand\",\"headerVertical\":\"flip\"},{\"formatter\":\"function(cell, formatterParams, onRendered) { var html = cell.getValue(); return html; }\",\"title\":\"State\",\"field\":\"$stateIcon\",\"width\":100,\"frozen\":true,\"headerVertical\":\"flip\"},{\"formatter\":\"function(cell, formatterParams, onRendered) { var html = cell.getValue(); return html; }\",\"title\":\"Signal\",\"field\":\"signalIcon\",\"width\":100,\"frozen\":true,\"headerVertical\":\"flip\"},{\"title\":\"Name\",\"field\":\"$name\",\"width\":100,\"frozen\":true,\"headerVertical\":\"flip\"},{\"title\":\"State\",\"field\":\"$state\",\"width\":100,\"align\":\"center\",\"headerVertical\":\"flip\"},{\"title\":\"last-ready\",\"field\":\"lastSeenreadyFormatted\",\"width\":100,\"align\":\"left\",\"headerVertical\":\"flip\"},{\"title\":\"Homie\",\"field\":\"$homie\",\"width\":100,\"align\":\"left\",\"headerVertical\":\"flip\"},{\"title\":\"Platform\",\"field\":\"$implementation\",\"width\":100,\"align\":\"left\",\"headerVertical\":\"flip\"},{\"title\":\"Statistics\",\"columns\":[{\"title\":\"Interval\",\"field\":\"interval\",\"width\":100,\"headerVertical\":\"flip\"},{\"formatterParams\":{\"outputFormat\":\"d hh:mm:ss\",\"inputFormat\":\"seconds\",\"invalidPlaceholder\":\"(unknown)\"},\"title\":\"Uptime\",\"field\":\"uptime\",\"formatter\":\"function(cell, formatterParams, onRendered){ var pad = function (num) { return (\\\"0\\\"+num).slice(-2); }; var secs = Number(cell.getValue()); if (Number.isNaN(secs)) return; var minutes = Math.floor(secs / 60); secs = secs%60; var hours = Math.floor(minutes/60); minutes = minutes%60; var days = Math.floor(hours/24); hours = hours%24; if (days>0) return days+\\\"d \\\"+pad(hours)+\\\":\\\"+pad(minutes); else return pad(hours)+\\\":\\\"+pad(minutes)+\\\":\\\"+pad(secs); }\",\"width\":100,\"headerVertical\":\"flip\"},{\"formatterParams\":{\"min\":0,\"max\":100,\"color\":[\"red\",\"orange\",\"green\"],\"legend\":\"function (value) {if (value>0) return \\\"<span style='color:#FFFFFF;'>\\\"+value+\\\" %</span>\\\"; else return; }\",\"legendColor\":\"#FFFFFF\",\"legendAlign\":\"center\"},\"title\":\"Signal\",\"field\":\"signal\",\"formatter\":\"progress\",\"width\":100,\"headerVertical\":\"flip\"},{\"formatterParams\":{\"min\":2.5,\"max\":3.5,\"color\":[\"red\",\"green\",\"red\"],\"legend\":\"function (value) { if (value>0) return \\\"<span style='color:#FFFFFF;'>\\\"+value+\\\" V</span>\\\"; else return; }\",\"legendColor\":\"#101010\",\"legendAlign\":\"center\"},\"title\":\"Supply\",\"field\":\"supply\",\"formatter\":\"progress\",\"width\":100,\"headerVertical\":\"flip\"},{\"formatterParams\":{\"min\":0,\"max\":100,\"color\":[\"red\",\"orange\",\"green\"],\"legend\":\"function (value) { if (value>0) return \\\"<span style='color:#FFFFFF;'>\\\"+value+\\\" %</span>\\\"; else return; }\",\"legendColor\":\"#101010\",\"legendAlign\":\"center\"},\"title\":\"Battery\",\"field\":\"battery\",\"formatter\":\"progress\",\"width\":100,\"headerVertical\":\"flip\"},{\"formatterParams\":{\"min\":0,\"max\":100000,\"color\":[\"red\",\"orange\",\"green\"],\"legend\":\"function (value) { if (value>0) return \\\"<span style='color:#FFFFFF;'>\\\"+(value/1024).toFixed(2)+\\\" kB</span>\\\"; else return; }\",\"legendColor\":\"#101010\",\"legendAlign\":\"center\"},\"title\":\"Memory\",\"field\":\"freeheap\",\"formatter\":\"progress\",\"width\":100,\"headerVertical\":\"flip\"},{\"formatterParams\":{\"target\":\"_blank\",\"min\":0,\"max\":100,\"color\":[\"red\",\"orange\",\"green\"],\"legend\":\"function (value) { if (value>0) return \\\"<span style='color:#FFFFFF;'>\\\"+value+\\\" %</span>\\\"; else return; }\",\"legendColor\":\"#101010\",\"legendAlign\":\"center\"},\"title\":\"CPU load\",\"field\":\"cpuload\",\"formatter\":\"progress\",\"width\":100,\"headerVertical\":\"flip\"},{\"formatterParams\":{\"min\":20,\"max\":60,\"color\":[\"green\",\"orange\",\"red\"],\"legend\":\"function (value) { if (value>0) return \\\"<span style='color:#FFFFFF;'>\\\"+value+\\\" °C</span>\\\"; else return; }\",\"legendColor\":\"#101010\",\"legendAlign\":\"center\"},\"title\":\"CPU temp\",\"field\":\"cputemp\",\"formatter\":\"progress\",\"width\":100,\"headerVertical\":\"flip\"}]},{\"title\":\"Firmware\",\"columns\":[{\"formatter\":\"link\",\"formatterParams\":{\"labelField\":\"$localip\",\"urlPrefix\":\"http://\",\"target\":\"_blank\"},\"title\":\"IP\",\"field\":\"$localip\",\"width\":100},{\"title\":\"mac\",\"field\":\"$mac\",\"width\":100},{\"title\":\"Accsess Point\",\"field\":\"SSID\",\"width\":100},{\"title\":\"Firmware\",\"field\":\"name\",\"width\":100},{\"title\":\"Version\",\"field\":\"version\",\"width\":100},{\"title\":\"Last Boot Cause\",\"field\":\"lastBootCause\",\"width\":100},{\"title\":\"Reset Reason\",\"field\":\"resetReason\",\"width\":100}]}]},\"customHeight\":12}", + "ui": { + "icon": "font-awesome/fa-table", + "label": { + "en-US": "Tabulator" + }, + "type": "input", + "opts": { + "types": [ + "json", + "env" + ] + } + } + }, + { + "name": "tableDataProp", + "type": "str", + "value": "row", + "ui": { + "icon": "font-awesome/fa-tag", + "label": { + "en-US": "rowProperty" + } + } + }, + { + "name": "tableIndex", + "type": "str", + "value": "$topic", + "ui": { + "icon": "font-awesome/fa-indent", + "label": { + "en-US": "Index" + }, + "type": "input", + "opts": { + "types": [ + "str", + "json", + "env" + ] + } + } + }, + { + "name": "maxRows", + "type": "num", + "value": "0", + "ui": { + "icon": "font-awesome/fa-list-ol", + "type": "input", + "opts": { + "types": [ + "num", + "bool", + "env" + ] + } + } + }, + { + "name": "maxStore", + "type": "num", + "value": "0", + "ui": { + "icon": "font-awesome/fa-database", + "type": "input", + "opts": { + "types": [ + "num", + "env" + ] + } + } + }, + { + "name": "dashboard", + "type": "str", + "value": "Remote Device Table", + "ui": { + "icon": "font-awesome/fa-dashboard", + "label": { + "en-US": "Dashboard" + }, + "type": "input", + "opts": { + "types": [ + "str", + "env" + ] + } + } + }, + { + "name": "tableContext", + "type": "json", + "value": "{\"tableData\":{\"name\":\"tableData\"},\"tableConfig\":{\"name\":\"tableConfig\",\"storage\":\"file\"},\"tableEdit\":{\"name\":\"tableEdit\",\"storage\":\"file\"}}", + "ui": { + "icon": "font-awesome/fa-database", + "label": { + "en-US": "Context" + }, + "type": "input", + "opts": { + "types": [ + "json", + "env" + ] + } + } + } + ], + "color": "#3FADB5", + "icon": "node-red-dashboard/ui_slider.png", + "status": { + "x": 360, + "y": 34, + "wires": [ + { + "id": "5eb0bd6b.74b794", + "port": 0 + } + ] + } + }, + { + "id": "753d1cb452ebd76e", + "type": "subflow", + "name": "Set_sequence", + "info": "", + "category": "", + "in": [], + "out": [ + { + "x": 1100, + "y": 180, + "wires": [ + { + "id": "8e435b914e061d45", + "port": 0 + } + ] + } + ], + "env": [], + "meta": {}, + "color": "#DDAA99" + }, + { + "id": "142ad6ae.d55e29", + "type": "ui_group", + "name": "Commands", + "tab": "5d888f29.07334", + "order": 1, + "disp": true, + "width": "8", + "collapse": false, + "className": "" + }, + { + "id": "5d888f29.07334", + "type": "ui_tab", + "name": "Control", + "icon": "dashboard", + "order": 1, + "disabled": false, + "hidden": false + }, + { + "id": "ea73b76b.ee0738", + "type": "ui_base", + "theme": { + "name": "theme-dark", + "lightTheme": { + "default": "#0094CE", + "baseColor": "#0094CE", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "edited": true, + "reset": false + }, + "darkTheme": { + "default": "#097479", + "baseColor": "#097479", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "edited": true, + "reset": false + }, + "customTheme": { + "name": "Untitled Theme 1", + "default": "#4B7930", + "baseColor": "#4B7930", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" + }, + "themeState": { + "base-color": { + "default": "#097479", + "value": "#097479", + "edited": true + }, + "page-titlebar-backgroundColor": { + "value": "#097479", + "edited": false + }, + "page-backgroundColor": { + "value": "#111111", + "edited": false + }, + "page-sidebar-backgroundColor": { + "value": "#333333", + "edited": false + }, + "group-textColor": { + "value": "#0eb8c0", + "edited": false + }, + "group-borderColor": { + "value": "#555555", + "edited": false + }, + "group-backgroundColor": { + "value": "#333333", + "edited": false + }, + "widget-textColor": { + "value": "#eeeeee", + "edited": false + }, + "widget-backgroundColor": { + "value": "#097479", + "edited": false + }, + "widget-borderColor": { + "value": "#333333", + "edited": false + }, + "base-font": { + "value": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" + } + }, + "angularTheme": { + "primary": "indigo", + "accents": "blue", + "warn": "red", + "background": "grey", + "palette": "light" + } + }, + "site": { + "name": "OhmPi Node-RED interface", + "hideToolbar": "false", + "allowSwipe": "true", + "lockMenu": "true", + "allowTempTheme": "true", + "dateFormat": "DD/MM/YYYY", + "sizes": { + "sx": 48, + "sy": 48, + "gx": 10, + "gy": 10, + "cx": 6, + "cy": 6, + "px": 1, + "py": 1 + } + } + }, + { + "id": "5a09c1ee2a3419c3", + "type": "mqtt-broker", + "name": "ohmpi_local_broker", + "broker": "127.0.0.1", + "port": "1880", + "clientid": "", + "autoConnect": true, + "usetls": false, + "protocolVersion": "4", + "keepalive": "60", + "cleansession": true, + "birthTopic": "", + "birthQos": "0", + "birthPayload": "", + "birthMsg": {}, + "closeTopic": "", + "closeQos": "0", + "closePayload": "", + "closeMsg": {}, + "willTopic": "", + "willQos": "0", + "willPayload": "", + "willMsg": {}, + "userProps": "", + "sessionExpiry": "" + }, + { + "id": "7792ecc419ecbb59", + "type": "ui_group", + "name": "Latest reading", + "tab": "32170516a52092f8", + "order": 2, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "b0990b3c5ff3c09a", + "type": "ui_group", + "name": "Messages", + "tab": "5d888f29.07334", + "order": 2, + "disp": true, + "width": 18, + "collapse": true, + "className": "" + }, + { + "id": "32170516a52092f8", + "type": "ui_tab", + "name": "Data", + "icon": "show_chart", + "order": 2, + "disabled": false, + "hidden": false + }, + { + "id": "d1631b3685c8e6de", + "type": "ui_link", + "name": "OhmPi Documentation", + "link": "https://reversaal.gitlab.irstea.page/OhmPi/", + "icon": "help", + "target": "newtab", + "order": 4 + }, + { + "id": "df2cbd8c9c9960e1", + "type": "ui_group", + "name": "Sequence", + "tab": "32170516a52092f8", + "order": 3, + "disp": true, + "width": 20, + "collapse": false, + "className": "" + }, + { + "id": "6e40c7571e22771c", + "type": "ui_group", + "name": "Readings", + "tab": "5d888f29.07334", + "order": 3, + "disp": true, + "width": "26", + "collapse": false, + "className": "" + }, + { + "id": "65037a21a4930ac3", + "type": "ui_link", + "name": "HTTP interface", + "link": "http://localhost:8080", + "icon": "open_in_browser", + "target": "iframe", + "order": 3 + }, + { + "id": "5719585211dfe472", + "type": "ui_group", + "name": "Operating commands", + "tab": "32170516a52092f8", + "order": 1, + "disp": true, + "width": "20", + "collapse": false, + "className": "" + }, + { + "id": "1eec2080f96e5173", + "type": "ui_spacer", + "z": "4489e1d51c9522f3", + "name": "spacer", + "group": "5719585211dfe472", + "order": 1, + "width": "8", + "height": "1" + }, + { + "id": "95ddd6737dafe2a9", + "type": "ui_spacer", + "z": "4489e1d51c9522f3", + "name": "spacer", + "group": "5719585211dfe472", + "order": 4, + "width": "8", + "height": "1" + }, + { + "id": "0c2ea823ae91ebee", + "type": "ui_spacer", + "z": "4489e1d51c9522f3", + "name": "spacer", + "group": "df2cbd8c9c9960e1", + "order": 1, + "width": 4, + "height": 1 + }, + { + "id": "8ff142c0908285a2", + "type": "ui_spacer", + "z": "4489e1d51c9522f3", + "name": "spacer", + "group": "df2cbd8c9c9960e1", + "order": 3, + "width": 4, + "height": 1 + }, + { + "id": "d8e82bdc688ec296", + "type": "ui_spacer", + "z": "4489e1d51c9522f3", + "name": "spacer", + "group": "df2cbd8c9c9960e1", + "order": 4, + "width": 4, + "height": 1 + }, + { + "id": "39db56203da1df8c", + "type": "ui_spacer", + "z": "4489e1d51c9522f3", + "name": "spacer", + "group": "df2cbd8c9c9960e1", + "order": 5, + "width": 4, + "height": 1 + }, + { + "id": "d98b0dbbdb298175", + "type": "ui_spacer", + "z": "4489e1d51c9522f3", + "name": "spacer", + "group": "df2cbd8c9c9960e1", + "order": 6, + "width": 4, + "height": 1 + }, + { + "id": "ffbeef7d63cdca4c", + "type": "ui_spacer", + "z": "4489e1d51c9522f3", + "name": "spacer", + "group": "df2cbd8c9c9960e1", + "order": 7, + "width": 4, + "height": 1 + }, + { + "id": "301a0ff615cd7889", + "type": "ui_spacer", + "z": "4489e1d51c9522f3", + "name": "spacer", + "group": "df2cbd8c9c9960e1", + "order": 8, + "width": 4, + "height": 1 + }, + { + "id": "d4e2a1e33d6d3d8d", + "type": "ui_spacer", + "z": "4489e1d51c9522f3", + "name": "spacer", + "group": "df2cbd8c9c9960e1", + "order": 9, + "width": 4, + "height": 1 + }, + { + "id": "89dedbd3926cfdfb", + "type": "ui_spacer", + "z": "4489e1d51c9522f3", + "name": "spacer", + "group": "df2cbd8c9c9960e1", + "order": 10, + "width": 4, + "height": 1 + }, + { + "id": "53af5a741386fee7", + "type": "ui_spacer", + "z": "4489e1d51c9522f3", + "name": "spacer", + "group": "df2cbd8c9c9960e1", + "order": 11, + "width": 4, + "height": 1 + }, + { + "id": "c1981a4d6c9d959f", + "type": "ui_spacer", + "z": "4489e1d51c9522f3", + "name": "spacer", + "group": "df2cbd8c9c9960e1", + "order": 12, + "width": 4, + "height": 1 + }, + { + "id": "d1359c73fcd3a2a5", + "type": "ui_spacer", + "z": "4489e1d51c9522f3", + "name": "spacer", + "group": "df2cbd8c9c9960e1", + "order": 13, + "width": 4, + "height": 1 + }, + { + "id": "c92d320326331d56", + "type": "ui_spacer", + "z": "4489e1d51c9522f3", + "name": "spacer", + "group": "df2cbd8c9c9960e1", + "order": 14, + "width": 8, + "height": 1 + }, + { + "id": "24758628620b77e2", + "type": "ui_spacer", + "z": "4489e1d51c9522f3", + "name": "spacer", + "group": "df2cbd8c9c9960e1", + "order": 16, + "width": 8, + "height": 1 + }, + { + "id": "7a4e901e7faedf6c", + "type": "ui_spacer", + "z": "b19c51e9d4d25a33", + "name": "spacer", + "group": "142ad6ae.d55e29", + "order": 1, + "width": 2, + "height": 1 + }, + { + "id": "d0da538df7da49b5", + "type": "ui_spacer", + "z": "b19c51e9d4d25a33", + "name": "spacer", + "group": "142ad6ae.d55e29", + "order": 4, + "width": 2, + "height": 1 + }, + { + "id": "71f435fecd185ab7", + "type": "join", + "z": "c3dbd6595b0256a9", + "name": "", + "mode": "custom", + "build": "merged", + "property": "payload", + "propertyType": "msg", + "key": "topic", + "joiner": "\\n", + "joinerType": "str", + "accumulate": true, + "timeout": "", + "count": "3", + "reduceRight": false, + "reduceExp": "", + "reduceInit": "", + "reduceInitType": "", + "reduceFixup": "", + "x": 970, + "y": 240, + "wires": [ + [] + ] + }, + { + "id": "5eb07cfc6ea7e7e4", + "type": "change", + "z": "c3dbd6595b0256a9", + "name": "", + "rules": [ + { + "t": "set", + "p": "complete", + "pt": "msg", + "to": "true", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 710, + "y": 240, + "wires": [ + [ + "71f435fecd185ab7" + ] + ] + }, + { + "id": "87336107859f0f4c", + "type": "delay", + "z": "c3dbd6595b0256a9", + "name": "", + "pauseType": "delay", + "timeout": "250", + "timeoutUnits": "milliseconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 510, + "y": 240, + "wires": [ + [ + "5eb07cfc6ea7e7e4" + ] + ] + }, + { + "id": "ef7cd86d21809032", + "type": "function", + "z": "c3dbd6595b0256a9", + "name": "set cmd_id", + "func": "var newMsg = { payload: {\"cmd_id\": msg.payload }};\nreturn newMsg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 610, + "y": 200, + "wires": [ + [ + "71f435fecd185ab7" + ] + ] + }, + { + "id": "028e5762e321dce2", + "type": "uuid", + "z": "c3dbd6595b0256a9", + "uuidVersion": "v1", + "namespaceType": "", + "namespace": "", + "namespaceCustom": "", + "name": "", + "field": "payload", + "fieldType": "msg", + "x": 430, + "y": 200, + "wires": [ + [ + "ef7cd86d21809032" + ] + ] + }, + { + "id": "23f549ae6ee82d23", + "type": "function", + "z": "c3dbd6595b0256a9", + "name": "get cmd", + "func": "return global.get(\"command_tmp\");", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 600, + "y": 280, + "wires": [ + [ + "71f435fecd185ab7" + ] + ] + }, + { + "id": "3027158d54f370fd", + "type": "function", + "z": "c3dbd6595b0256a9", + "name": "get kwargs", + "func": "return global.get(\"kwargs\");", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 610, + "y": 320, + "wires": [ + [ + "71f435fecd185ab7" + ] + ] + }, + { + "id": "2d858a81f7e7f2b3", + "type": "ui_button", + "z": "c3dbd6595b0256a9", + "name": "", + "group": "142ad6ae.d55e29", + "order": 11, + "width": 0, + "height": 0, + "passthru": false, + "label": "Send command", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 200, + "y": 240, + "wires": [ + [ + "028e5762e321dce2", + "87336107859f0f4c", + "23f549ae6ee82d23", + "3027158d54f370fd" + ] + ] + }, + { + "id": "de9e803de4d28986", + "type": "ui_dropdown", + "z": "c3dbd6595b0256a9", + "name": "", + "label": "Command", + "tooltip": "", + "place": "Select option", + "group": "142ad6ae.d55e29", + "order": 9, + "width": 0, + "height": 0, + "passthru": true, + "multiple": false, + "options": [ + { + "label": "", + "value": "load_sequence", + "type": "str" + }, + { + "label": "", + "value": "reset_mux", + "type": "str" + }, + { + "label": "", + "value": "set_sequence", + "type": "str" + }, + { + "label": "", + "value": "update_settings", + "type": "str" + } + ], + "payload": "", + "topic": "command", + "topicType": "str", + "className": "", + "x": 190, + "y": 400, + "wires": [ + [ + "68222c7c6633d3fb" + ] + ] + }, + { + "id": "17f76c1517cb0285", + "type": "ui_text_input", + "z": "c3dbd6595b0256a9", + "name": "", + "label": "kwargs", + "tooltip": "", + "group": "142ad6ae.d55e29", + "order": 10, + "width": 0, + "height": 0, + "passthru": true, + "mode": "text", + "delay": "250", + "topic": "kwargs", + "sendOnBlur": false, + "className": "", + "topicType": "str", + "x": 179, + "y": 457, + "wires": [ + [ + "c25811ae6ee89f2d" + ] + ] + }, + { + "id": "c25811ae6ee89f2d", + "type": "json", + "z": "c3dbd6595b0256a9", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 329, + "y": 457, + "wires": [ + [ + "071930f0cabf3d94" + ] + ] + }, + { + "id": "68222c7c6633d3fb", + "type": "function", + "z": "c3dbd6595b0256a9", + "name": "set cmd", + "func": "var newMsg = { payload: {\"cmd\": msg.payload }};\nglobal.set(\"command_tmp\",newMsg)\nreturn global.get(\"command_tmp\");", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 400, + "y": 400, + "wires": [ + [] + ] + }, + { + "id": "071930f0cabf3d94", + "type": "function", + "z": "c3dbd6595b0256a9", + "name": "set kwargs", + "func": "var newMsg = { payload: {\"kwargs\": msg.payload }};\nglobal.set(\"kwargs\", newMsg)\nreturn global.get(\"kwargs\");", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 489, + "y": 457, + "wires": [ + [] + ] + }, + { + "id": "19e8f5b5b199d998", + "type": "ui_text", + "z": "c3dbd6595b0256a9", + "group": "142ad6ae.d55e29", + "order": 8, + "width": 0, + "height": 0, + "name": "", + "label": "<font size = 4.5>Manual command", + "format": "{{msg.payload}}", + "layout": "row-left", + "className": "", + "x": 260, + "y": 340, + "wires": [] + }, + { + "id": "1278358184e5b189", + "type": "join", + "z": "1506d9deaa5de120", + "name": "", + "mode": "custom", + "build": "merged", + "property": "payload", + "propertyType": "msg", + "key": "topic", + "joiner": "\\n", + "joinerType": "str", + "accumulate": true, + "timeout": "", + "count": "3", + "reduceRight": false, + "reduceExp": "", + "reduceInit": "", + "reduceInitType": "", + "reduceFixup": "", + "x": 970, + "y": 120, + "wires": [ + [] + ] + }, + { + "id": "5183385a4d4e6cce", + "type": "change", + "z": "1506d9deaa5de120", + "name": "", + "rules": [ + { + "t": "set", + "p": "complete", + "pt": "msg", + "to": "true", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 710, + "y": 120, + "wires": [ + [ + "1278358184e5b189" + ] + ] + }, + { + "id": "a91742bfc4517ad5", + "type": "delay", + "z": "1506d9deaa5de120", + "name": "", + "pauseType": "delay", + "timeout": "250", + "timeoutUnits": "milliseconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 510, + "y": 120, + "wires": [ + [ + "5183385a4d4e6cce" + ] + ] + }, + { + "id": "43a86ef16bc90f95", + "type": "function", + "z": "1506d9deaa5de120", + "name": "set cmd_id", + "func": "var newMsg = { payload: {\"cmd_id\": msg.payload }};\nreturn newMsg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 610, + "y": 80, + "wires": [ + [ + "1278358184e5b189" + ] + ] + }, + { + "id": "8d984843fded4a0b", + "type": "uuid", + "z": "1506d9deaa5de120", + "uuidVersion": "v1", + "namespaceType": "", + "namespace": "", + "namespaceCustom": "", + "name": "", + "field": "payload", + "fieldType": "msg", + "x": 430, + "y": 80, + "wires": [ + [ + "43a86ef16bc90f95" + ] + ] + }, + { + "id": "414271cceb77591d", + "type": "function", + "z": "1506d9deaa5de120", + "name": "set kwargs", + "func": "return global.get(\"kwargs_tmp\");", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 200, + "wires": [ + [ + "1278358184e5b189" + ] + ] + }, + { + "id": "d1ba46c1d65348db", + "type": "ui_form", + "z": "1506d9deaa5de120", + "name": "", + "label": "Settings", + "group": "142ad6ae.d55e29", + "order": 12, + "width": 0, + "height": 0, + "options": [ + { + "label": "Nb electrodes", + "value": "nb_electrodes", + "type": "number", + "required": false, + "rows": null + }, + { + "label": "Injection duration", + "value": "injection_duration", + "type": "number", + "required": false, + "rows": null + }, + { + "label": "Nb stack", + "value": "nb_stack", + "type": "number", + "required": false, + "rows": null + } + ], + "formValue": { + "nb_electrodes": "", + "injection_duration": "", + "nb_stack": "" + }, + "payload": "", + "submit": "Update settings", + "cancel": "cancel", + "topic": "topic", + "topicType": "msg", + "splitLayout": "", + "className": "", + "x": 180, + "y": 540, + "wires": [ + [ + "8d984843fded4a0b", + "40e18df2a4f6f7bc", + "7c3f483dc0a97154", + "92a5062165281e27", + "f260d8e69fe63dbb" + ] + ] + }, + { + "id": "a28983bae6d15cb6", + "type": "function", + "z": "1506d9deaa5de120", + "name": "set kwargs", + "func": "var settings = JSON.parse((msg.payload));\nfor (var key in settings) {\n if (settings[key] == null){\n delete settings[key]}\n }\n\nvar newMsg = {\n payload: {\n \"kwargs\": { \"settings\": settings}}}\nglobal.set(\"kwargs_tmp\", newMsg)\nreturn global.get(\"kwargs_tmp\");", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 530, + "y": 540, + "wires": [ + [] + ] + }, + { + "id": "65d9c7d700116da5", + "type": "function", + "z": "1506d9deaa5de120", + "name": "set cmd", + "func": "return { payload: { \"cmd\": \"update_settings\" } };", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 600, + "y": 160, + "wires": [ + [ + "1278358184e5b189" + ] + ] + }, + { + "id": "40e18df2a4f6f7bc", + "type": "function", + "z": "1506d9deaa5de120", + "name": "blank", + "func": "return { payload: { } };", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 350, + "y": 240, + "wires": [ + [ + "a91742bfc4517ad5" + ] + ] + }, + { + "id": "7c3f483dc0a97154", + "type": "delay", + "z": "1506d9deaa5de120", + "name": "", + "pauseType": "delay", + "timeout": "250", + "timeoutUnits": "milliseconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 510, + "y": 400, + "wires": [ + [ + "414271cceb77591d" + ] + ] + }, + { + "id": "92a5062165281e27", + "type": "delay", + "z": "1506d9deaa5de120", + "name": "", + "pauseType": "delay", + "timeout": "250", + "timeoutUnits": "milliseconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 410, + "y": 320, + "wires": [ + [ + "65d9c7d700116da5" + ] + ] + }, + { + "id": "f260d8e69fe63dbb", + "type": "json", + "z": "1506d9deaa5de120", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 350, + "y": 540, + "wires": [ + [ + "a28983bae6d15cb6" + ] + ] + }, + { + "id": "63021563a27e162b", + "type": "ui_chart", + "z": "4489e1d51c9522f3", + "name": "", + "group": "df2cbd8c9c9960e1", + "order": 2, + "width": 12, + "height": 6, + "label": "Measured resistances chart", + "chartType": "line", + "legend": "true", + "xformat": "HH:mm:ss", + "interpolate": "linear", + "nodata": "", + "dot": true, + "ymin": "", + "ymax": "", + "removeOlder": 1, + "removeOlderPoints": "", + "removeOlderUnit": "3600", + "cutout": 0, + "useOneColor": false, + "useUTC": false, + "colors": [ + "#1f77b4", + "#aec7e8", + "#ff7f0e", + "#2ca02c", + "#98df8a", + "#d62728", + "#ff9896", + "#9467bd", + "#c5b0d5" + ], + "outputs": 1, + "useDifferentColor": false, + "className": "", + "x": 660, + "y": 340, + "wires": [ + [] + ] + }, + { + "id": "f655ae35cc8d0036", + "type": "function", + "z": "4489e1d51c9522f3", + "name": "function 3", + "func": "var msg2 = { payload: JSON.parse(msg.payload.split(' | ')[2].slice(6,).split(`'`).join(`\"`))[\"R [ohm]\"] };\nreturn msg2;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 440, + "y": 380, + "wires": [ + [ + "d891753fb13281c2", + "63021563a27e162b" + ] + ] + }, + { + "id": "d891753fb13281c2", + "type": "debug", + "z": "4489e1d51c9522f3", + "name": "debug 6", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 600, + "y": 400, + "wires": [] + }, + { + "id": "8b35e61a7f7a37f8", + "type": "ui_gauge", + "z": "4489e1d51c9522f3", + "name": "", + "group": "7792ecc419ecbb59", + "order": 1, + "width": 6, + "height": 4, + "gtype": "gage", + "title": "Latest measured resistance", + "label": "R [Ohm]", + "format": "{{value}}", + "min": 0, + "max": "50", + "colors": [ + "#00b500", + "#e6e600", + "#ca3838" + ], + "seg1": "", + "seg2": "", + "className": "", + "x": 660, + "y": 100, + "wires": [] + }, + { + "id": "9465b94aca687c9f", + "type": "function", + "z": "4489e1d51c9522f3", + "name": "function 4", + "func": "var msg2 = { payload: JSON.parse(msg.payload.split(' | ')[2].slice(6,).split(`'`).join(`\"`))[\"R [ohm]\"] };\nreturn msg2;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 440, + "y": 100, + "wires": [ + [ + "8b35e61a7f7a37f8", + "763ea2c282b8b81a" + ] + ] + }, + { + "id": "763ea2c282b8b81a", + "type": "debug", + "z": "4489e1d51c9522f3", + "name": "debug 7", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 460, + "y": 40, + "wires": [] + }, + { + "id": "57fd4568a66aac99", + "type": "ui_gauge", + "z": "4489e1d51c9522f3", + "name": "", + "group": "7792ecc419ecbb59", + "order": 2, + "width": 6, + "height": 4, + "gtype": "gage", + "title": "Latest measured voltage", + "label": "Vmn [mV]", + "format": "{{value}}", + "min": "-1500", + "max": "1500", + "colors": [ + "#00b500", + "#e6e600", + "#ca3838" + ], + "seg1": "", + "seg2": "", + "className": "", + "x": 650, + "y": 160, + "wires": [] + }, + { + "id": "cc84bb5476128ec6", + "type": "function", + "z": "4489e1d51c9522f3", + "name": "function 5", + "func": "var msg2 = { payload: JSON.parse(msg.payload.split(' | ')[2].slice(6,).split(`'`).join(`\"`))[\"Vmn [mV]\"] };\nreturn msg2;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 440, + "y": 160, + "wires": [ + [ + "57fd4568a66aac99" + ] + ] + }, + { + "id": "035c20e764a1f057", + "type": "ui_gauge", + "z": "4489e1d51c9522f3", + "name": "", + "group": "7792ecc419ecbb59", + "order": 3, + "width": 6, + "height": 4, + "gtype": "gage", + "title": "Latest applied current", + "label": "I [mA]", + "format": "{{value}}", + "min": 0, + "max": "50", + "colors": [ + "#00b500", + "#e6e600", + "#ca3838" + ], + "seg1": "", + "seg2": "", + "className": "", + "x": 640, + "y": 220, + "wires": [] + }, + { + "id": "5bab870fe804aae8", + "type": "function", + "z": "4489e1d51c9522f3", + "name": "function 6", + "func": "var msg2 = { payload: JSON.parse(msg.payload.split(' | ')[2].slice(6,).split(`'`).join(`\"`))[\"I [mA]\"] };\nreturn msg2;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 440, + "y": 220, + "wires": [ + [ + "035c20e764a1f057" + ] + ] + }, + { + "id": "6efab6f30d54c251", + "type": "mqtt out", + "z": "4489e1d51c9522f3", + "name": "MQTT ctrl", + "topic": "ohmpi_0001/ctrl", + "qos": "2", + "retain": "false", + "respTopic": "", + "contentType": "", + "userProps": "", + "correl": "", + "expiry": "", + "broker": "5a09c1ee2a3419c3", + "x": 720, + "y": 600, + "wires": [] + }, + { + "id": "3725a076feb38906", + "type": "ui_button", + "z": "4489e1d51c9522f3", + "name": "Run sequence", + "group": "5719585211dfe472", + "order": 2, + "width": 2, + "height": 1, + "passthru": false, + "label": "Run", + "tooltip": "run sequence", + "color": "red", + "bgcolor": "lightgrey", + "className": "", + "icon": "play_circle_fill", + "payload": "{\"cmd_id\" :\"0\", \"cmd\":\"run_sequence_async\"}", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 200, + "y": 540, + "wires": [ + [ + "6efab6f30d54c251" + ] + ] + }, + { + "id": "6ef5acf43be3c8f9", + "type": "ui_button", + "z": "4489e1d51c9522f3", + "name": "Interrupt", + "group": "5719585211dfe472", + "order": 3, + "width": 2, + "height": 1, + "passthru": false, + "label": "Stop", + "tooltip": "interrupt sequence", + "color": "black", + "bgcolor": "lightgrey", + "className": "", + "icon": "stop", + "payload": "{\"cmd_id\" :\"0\", \"cmd\":\"interrupt\"}", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 180, + "y": 600, + "wires": [ + [ + "6efab6f30d54c251" + ] + ] + }, + { + "id": "e0f21bb5ada6c4d7", + "type": "function", + "z": "b6b050428bd78f42", + "name": "Add data", + "func": "msg.payload = {\n command: \"addData\", \n arguments: [\n [msg.payload],\n true\n ],\n returnPromise: true\n }\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 660, + "y": 80, + "wires": [ + [ + "3d18b988d090ebf5" + ] + ] + }, + { + "id": "81a5a8e6.5418f8", + "type": "function", + "z": "b6b050428bd78f42", + "name": "store data", + "func": "let tabledata = flow.get('tabledata2') || [];\ntabledata.push(msg.payload);\nflow.set('tabledata2',tabledata);\n\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 400, + "y": 80, + "wires": [ + [ + "e0f21bb5ada6c4d7" + ] + ], + "icon": "node-red/db.svg" + }, + { + "id": "e873e03a03791394", + "type": "ui_ui_control", + "z": "b6b050428bd78f42", + "name": "", + "events": "change", + "x": 400, + "y": 160, + "wires": [ + [ + "6870f5124586ee5f" + ] + ] + }, + { + "id": "6870f5124586ee5f", + "type": "function", + "z": "b6b050428bd78f42", + "name": "get data", + "func": "msg.payload = flow.get('tabledata2') || [];\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 570, + "y": 160, + "wires": [ + [ + "3d18b988d090ebf5" + ] + ], + "icon": "node-red/db.svg" + }, + { + "id": "61a6e472b87b237a", + "type": "function", + "z": "b6b050428bd78f42", + "name": "data", + "func": "let counter = context.get('counter') || 0;\ncounter++;\ncontext.set('counter', counter);\n\nvar data = JSON.parse(\n msg.payload.split(' | ')[2].slice(6,).split(`'`).join(`\"`));\n\n//data[\"id\"] = counter\n\nfor (var key in data) {\n data[key] = data[key].toString()\n};\nmsg.payload = data\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 80, + "wires": [ + [ + "81a5a8e6.5418f8" + ] + ] + }, + { + "id": "3d18b988d090ebf5", + "type": "ui_table", + "z": "b6b050428bd78f42", + "group": "df2cbd8c9c9960e1", + "name": "Latest readings", + "order": 17, + "width": 20, + "height": 11, + "columns": [ + { + "field": "time", + "title": "Time", + "width": "20%", + "align": "left", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "A", + "title": "A", + "width": "2%", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "B", + "title": "B", + "width": "2%", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "M", + "title": "M", + "width": "2%", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "N", + "title": "N", + "width": "2%", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "R [ohm]", + "title": "R [ohm]", + "width": "8%", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Vmn [mV]", + "title": "Vmn [mV]", + "width": "8%", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "I [mA]", + "title": "I [mA]", + "width": "8%", + "align": "left", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Ps [mV]", + "title": "Ps [mV]", + "width": "8%", + "align": "left", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "inj time [ms]", + "title": "Injection<br>Time [ms]", + "width": "8%", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "nbStack", + "title": "Nb<br>Stack", + "width": "5%", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Tx [V]", + "title": "Tx [V]", + "width": "4%", + "align": "left", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "CPU temp [degC]", + "title": "CPU<br>Temp [degC]", + "width": "8%", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Nb samples [-]", + "title": "Nb<br>Samples", + "width": "3%", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "cmd_id", + "title": "Cmd<br>ID", + "width": "6%", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + } + ], + "outputs": 1, + "cts": true, + "x": 900, + "y": 120, + "wires": [ + [] + ] + }, + { + "id": "43717a0158183d95", + "type": "ui_text", + "z": "b6b050428bd78f42", + "group": "df2cbd8c9c9960e1", + "order": 15, + "width": 4, + "height": 1, + "name": "", + "label": "<font size = 4.5>Latest readings", + "format": "{{msg.payload}}", + "layout": "row-left", + "className": "", + "x": 170, + "y": 160, + "wires": [] + }, + { + "id": "f67644bcd94bee9b", + "type": "ui_button", + "z": "b6b050428bd78f42", + "name": "", + "group": "7792ecc419ecbb59", + "order": 4, + "width": 0, + "height": 0, + "passthru": false, + "label": "Clear table", + "tooltip": "clear all table data", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "date", + "topic": "", + "topicType": "str", + "x": 410, + "y": 220, + "wires": [ + [ + "ef121340aa158d36" + ] + ] + }, + { + "id": "ef121340aa158d36", + "type": "function", + "z": "b6b050428bd78f42", + "name": "clearData", + "func": "\nmsg.payload={\n command:\"clearData\",\n arguments: [],\n returnPromise: true\n}\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 621, + "y": 220, + "wires": [ + [ + "3d18b988d090ebf5" + ] + ], + "info": "# clear data\n\nunfortunately this command (currently) do not send a promise back! So we have to pass it directly to the table handler" + }, + { + "id": "5eb0bd6b.74b794", + "type": "function", + "z": "2924702c.b33a7", + "name": "handle tableData", + "func": "var status = {fill:\"red\",shape:\"dot\",text: \"payload \"};\nvar tableIndex = env.get(\"tableIndex\") || \"$topic\";\nvar tableDataProp = env.get(\"tableDataProp\") || \"row\";\nvar tableContext = env.get(\"tableContext\");\nvar dashboard = env.get(\"dashboard\");\nvar maxRows = env.get(\"maxRows\") || 0;\nvar maxStore = env.get(\"maxStore\") || 0;\n\nif (!tableContext.hasOwnProperty(\"tableData\") || !tableContext.hasOwnProperty(\"tableConfig\")) {\n status.text=\"tableContext not defined\";\n node.error(status.text);\n return [{payload:status},null];\n}\n\n// context store to cache table data (memoryOnly prefered)\nvar tableData = flow.get(\"$parent.\"+tableContext.tableData.name,tableContext.tableData.storage);\nif (tableData===undefined) {\n node.warn(\"[ui-table handler] tableData initialized!\");\n tableData={};\n flow.set(\"$parent.\"+tableContext.tableData.name,tableData,tableContext.tableData.storage);\n}\n\n// context Store to save table configuration (file)\nvar tableConfig = flow.get(\"$parent.\"+tableContext.tableConfig.name,tableContext.tableConfig.storage);\nif (tableConfig===undefined) {\n node.warn(\"[ui-table handler] tableConfig initialized!\");\n tableConfig={ResponsiveLayout:true};\n flow.set(\"$parent.\"+tableContext.tableConfig.name,tableConfig,tableContext.tableConfig.storage);\n}\n\nif (tableConfig.hasOwnProperty(\"maxStore\")) maxStore=tableConfig.maxStore;\nif (tableConfig.hasOwnProperty(\"maxRows\")) maxRows=tableConfig.maxRows;\n\n// context Store to save table configuration (file)\nvar tableEdit;\nif (tableContext.hasOwnProperty(\"tableEdit\")) {\n tableEdit = flow.get(\"$parent.\"+tableContext.tableEdit.name,tableContext.tableEdit.storage);\n if (tableEdit===undefined) {\n node.warn(\"[ui-table handler] tableEdit initialized!\");\n tableEdit={};\n flow.set(\"$parent.\"+tableContext.tableEdit.name,tableEdit,tableContext.tableEdit.storage);\n }\n}\n\n// function to merge partial data into existing table row\nvar mergeObject = function (destination, source, filter) {\n for (let currentSource in source) {\n if (source.hasOwnProperty(currentSource)) {\n if (filter!==undefined && tableEdit && tableEdit.hasOwnProperty(filter) && tableEdit[filter].hasOwnProperty(currentSource)) {\n destination[currentSource]= tableEdit[filter][currentSource];\n source[currentSource]=tableEdit[filter][currentSource];\n } else {\n destination[currentSource]= source[currentSource];\n }\n } \n }\n return source;\n};\n\n// merge edits into a destination object respecting _children\nvar mergeEdits = function(destination) {\n \n var mergeChildEdits = function(children) {\n children.forEach(child => {\n if (child.hasOwnProperty(tableIndex) && tableEdit.hasOwnProperty(child[tableIndex])) {\n// node.warn([\"mergeChild\",child])\n Object.keys(tableEdit[child[tableIndex]]).forEach(edit => {\n if (child.hasOwnProperty(edit)) {\n child[edit]=tableEdit[child[tableIndex]][edit];\n// node.warn([\"mergeChild edit \",edit,child[edit]])\n }\n });\n }\n if (child.hasOwnProperty(\"_children\")) {\n mergeChildEdits(child._children);\n }\n })\n }\n \n\n Object.keys(destination).forEach(row => {\n if (destination[row].hasOwnProperty(tableIndex)) {\n if (tableEdit.hasOwnProperty(row)) {\n Object.keys(tableEdit[row]).forEach(edit => {\n destination[row][edit]=tableEdit[row][edit];\n });\n }\n if (destination[row].hasOwnProperty(\"_children\")) {\n mergeChildEdits(destination[row]._children);\n }\n }\n });\n}\n\n// deep search for a column including nested columns\nvar searchTabulatorColumn = function (columns,key,match) {\n var result;\n for (let column of columns) {\n if (column.hasOwnProperty(\"columns\")) {\n result = searchTabulatorColumn(column.columns,key,match);\n if (result!==undefined) return result;\n } else if (column.hasOwnProperty(key) && column[key]===match) {\n return column;\n }\n }\n};\n\n// command message to update add or update data on ui-table\nvar msgToTable={};\nmsgToTable.payload={\n \"command\":msg.tabulatorCommand || \"updateOrAddData\",\n \"arguments\": [],\n \"returnPromise\": false\n};\n\n// store data in tableData\nif (msg.hasOwnProperty(tableDataProp)) {\n // store data for later recover\n if (!msg.hasOwnProperty(\"topic\")) { // check if index existst\n status.text=\"msg.topic not defined!\";\n return [{payload:status},null];\n }\n if (!tableData.hasOwnProperty(msg.topic)){ // first seen\n if (maxRows>0 && Object.keys(tableData).lenght===0) {\n tableConfig.currentFirstRow=msg.topic;\n }\n tableData[msg.topic]={};\n if (tableEdit && tableEdit.hasOwnProperty(msg.topic)) { // table edits available!\n Object.keys(tableEdit[msg.topic]).forEach((key) => {\n msg[tableDataProp][key]=tableEdit[msg.topic][key];\n tableData[msg.topic][key]=tableEdit[msg.topic][key];\n })\n }\n if (maxStore>0 && typeof msg.topic === \"number\") { // limit rows in tableData\n let rowKeys = Object.keys(tableData);\n if (rowKeys.length>maxStore) {\n for (let i=0; i<(rowKeys.length-maxStore); i++) {\n delete tableData[rowKeys[i]];\n }\n }\n }\n }\n if (!tableData[msg.topic].hasOwnProperty(tableIndex)) tableData[msg.topic][tableIndex]=msg.topic;\n msg[tableDataProp]=mergeObject(tableData[msg.topic],msg[tableDataProp],msg.topic);\n msg[tableDataProp][tableIndex]=msg.topic;\n msgToTable.payload.arguments=[[msg[tableDataProp]]];\n // add aditional parameters\n if (msg.hasOwnProperty(\"tabulatorParameter\") && Array.isArray(msg.tabulatorParameter)) {\n for (let arg in msg.tabulatorParameter) msgToTable.payload.arguments.push(arg);\n }\n // delete rows if rows exceed maxRows\n /*\n if (maxRows>0 && tableConfig.hasOwnProperty(\"currentFirstRow\") && typeof tableData[msg.topic][tableIndex]===\"number\") {\n //node.warn([maxRows,tableConfig.hasOwnProperty(\"currentFirstRow\"),typeof tableData[msg.topic][tableIndex],tableConfig.currentFirstRow,tableData[msg.topic][tableIndex]-maxRows])\n if (tableConfig.currentFirstRow<tableData[msg.topic][tableIndex]-maxRows) {\n node.warn([\"maxRowExeeded\",tableConfig.currentFirstRow]);\n node.send([null,{payload:{\"command\":\"deleteRow\",\"arguments\": [tableConfig.currentFirstRow],\"returnPromise\": false}},null]);\n tableConfig.currentFirstRow++;\n }\n }*/\n if (maxRows>0 && typeof tableData[msg.topic][tableIndex]===\"number\" && msg.topic-maxRows>0) {\n node.send([null,{payload:{\"command\":\"deleteRow\",\"arguments\": [msg.topic-maxRows],\"returnPromise\": false}},null]);\n }\n status.fill=\"green\";\n status.text=msg.topic+\" updated\";\n return [{payload:status},msgToTable,null];\n} if (msg.payload===\"connect\" || (msg.payload===\"change\" && msg.name===dashboard) || (msg.hasOwnProperty(\"payload\") && msg.payload.hasOwnProperty(\"command\"))) { \n if (!msg.hasOwnProperty(\"ui_control\")) {\n msg.ui_control = env.get('tabulator');\n status.text+=\" ui_control added\";\n }\n //process commands\n //node.warn({\"command\":msg.payload.command,\"msg\":msg,\"object\":msg.payload.object})\n if (msg.payload.hasOwnProperty(\"command\") && msg.payload.command!=='getTable') {\n status.fill=\"blue\";\n switch(msg.payload.command) {\n case 'deleteTable':\n flow.set(\"$parent.\"+tableContext.tableData.name,undefined,tableContext.tableData.storage);\n tableData={};\n status.text=\"tabledata deleted\";\n node.warn(\"[ui-table handler] \"+\"tabledata deleted\");\n break;\n case 'deleteRow':\n case 'deleteDevice':\n var deleteRow = function(id) {\n // check if row is in root\n if (tableData.hasOwnProperty(id)) {\n delete tableData[id]\n return true;\n }\n // check if row is a child\n let deleteChildRow = function(children, id) {\n for(let i = 0; i < children.length; i++){\n if (children[i].hasOwnProperty(tableIndex) && children[i][tableIndex]===id) {\n children.splice(i, 1); \n return true; \n }\n if (children[i].hasOwnProperty(\"_children\")) {\n if (deleteChildRow(children[i]._children,id)) {\n if (children[i]._children.length === 0) {\n delete children[i]._children;\n }\n return true;\n }\n }\n }\n return false;\n };\n \n for (let row in tableData) {\n if (tableData[row].hasOwnProperty(\"_children\")) {\n if (deleteChildRow(tableData[row]._children,id)) return true;\n }\n }\n return false;\n }\n \n if (deleteRow(msg.payload.object)) {\n status.text=msg.payload.object+\" deleted\";\n } else {\n status.fill=\"yellow\";\n status.text=msg.payload.object+\" undefined\";\n }\n break;\n case 'ignoreRow':\n case 'ignoreDevice':\n if (tableData.hasOwnProperty(msg.payload.object)) {\n delete tableData[msg.payload.object];\n status.text=msg.payload.object+\" will be ignored\";\n if (!tableConfig.hasOwnProperty('ignoreDevice')) tableConfig.ignoreDevice={};\n tableConfig.ignoreDevice[msg.payload.object]=true;\n }\n break;\n case 'unIgnoreRow':\n case 'unIgnoreDevice':\n if (tableConfig.hasOwnProperty('ignoreDevice')) {\n delete tableConfig.ignoreDevice[msg.payload.object];\n }\n break;\n case 'unIgnoreRows':\n case 'unIgnoreDevices':\n delete tableConfig.ignoreDevice;\n break;\n case 'updateData':\n status.text=\"column \"+msg.payload.column+\" updated\";\n delete msg.ui_control;\n return [{payload:status},msg];\n case 'updateTable':\n status.text=msg.payload.command+\": \";\n break;\n case 'columnHide':\n if (!tableConfig.hasOwnProperty('columnVisible')) tableConfig.columnVisible={};\n tableConfig.columnVisible[msg.payload.object]=false;\n break;\n case 'columnUnHide':\n if (!tableConfig.hasOwnProperty('columnVisible')) tableConfig.columnVisible={};\n tableConfig.columnVisible[msg.payload.object]=true;\n break;\n case 'columnsUnHide':\n for (let column in tableConfig.columnVisible) {\n if (tableConfig.columnVisible.hasOwnProperty(column)) tableConfig.columnVisible[column]=true;\n }\n break;\n case 'refreshTable':\n break;\n case 'deleteColumnOrder':\n case 'restoreColumnOrder':\n delete tableConfig.columns;\n break;\n case 'deleteColumnWidth':\n case 'resetColumnWidth':\n delete tableConfig.columnWidths;\n break;\n case 'setResponsiveLayout':\n tableConfig.ResponsiveLayout=!tableConfig.ResponsiveLayout;\n break;\n case 'deleteRowOrder':\n delete tableConfig.rowOrder;\n break;\n case 'setMaxStore':\n tableConfig.maxStore=msg.payload.object;\n maxStore=msg.payload.object;\n break;\n case 'setMaxRows':\n tableConfig.maxRows=msg.payload.object;\n maxRows=msg.payload.object;\n break;\n default:\n status.fill=\"red\";\n status.text=\"unknown command \"+msg.payload.command;\n node.warn(\"[ui-table handler] \"+status.text);\n break;\n }\n flow.set(\"$parent.\"+tableContext.tableConfig.name,tableConfig,tableContext.tableConfig.storage);\n node.send([{payload:status},null,null]);\n }\n\n // crawl through tabulator arrays and updated user defined values\n var crawlTabulator = function (columns,match,config,property) {\n for (let column of columns) {\n if (column.hasOwnProperty(\"columns\")) {\n crawlTabulator(column.columns,match,config,property);\n } else if (config.hasOwnProperty(column[match])) column[property]=config[column.field];\n }\n };\n \n // restore custom column width\n if (tableConfig.hasOwnProperty(\"columnWidths\") && msg.hasOwnProperty(\"ui_control\")) {\n crawlTabulator(msg.ui_control.tabulator.columns,\"field\",tableConfig.columnWidths,\"width\");\n }\n \n // restore custom column hide/show\n if (tableConfig.hasOwnProperty(\"columnVisible\") && msg.hasOwnProperty(\"ui_control\")) {\n crawlTabulator(msg.ui_control.tabulator.columns,\"field\",tableConfig.columnVisible,\"visible\");\n }\n \n // restore custom responsive / standard view\n if (tableConfig.hasOwnProperty(\"ResponsiveLayout\")) {\n if (!tableConfig.ResponsiveLayout) {\n msg.ui_control.tabulator.responsiveLayout=false;\n }\n msg.ui_control.tabulator.columns.forEach((column,index) => {\n if (column.formatter===\"responsiveCollapse\") { // hide expand column on any position\n column.visible=tableConfig.ResponsiveLayout;\n return;\n }\n });\n }\n\n // sort columns\n if (tableConfig.hasOwnProperty(\"columns\") && msg.hasOwnProperty(\"ui_control\") && msg.ui_control.hasOwnProperty(\"tabulator\")) {\n var addedColumns = 0;\n var sortColumnsByLayout = function (sortColumns, columnsLayout, targetColumns) {\n for (var layoutColumn=0; layoutColumn<columnsLayout.length; layoutColumn++) {\n for (var sortColumn in sortColumns) {\n if (sortColumns[sortColumn].hasOwnProperty(\"columns\")) {\n targetColumns.push({\"title\":sortColumns[sortColumn].title, \"columns\":[]});\n sortColumnsByLayout(sortColumns[sortColumn].columns,columnsLayout,targetColumns[targetColumns.length-1].columns);\n layoutColumn=addedColumns; // jump forward after childes added\n } else {\n if (columnsLayout[layoutColumn].field===sortColumns[sortColumn].field){\n targetColumns.push(sortColumns[sortColumn]);\n addedColumns++;\n break;\n }\n }\n }\n }\n }; \n var newColumns=[];\n sortColumnsByLayout(msg.ui_control.tabulator.columns,tableConfig.columns,newColumns);\n msg.ui_control.tabulator.columns=newColumns;\n }\n\n // restore stored lines after connect\n\n let command = msg.payload.command;\n var tableArray;\n if (command===\"getTable\") {\n msg.payload.tableArray=[];\n tableArray=msg.payload.tableArray\n } else {\n msg.payload=[];\n tableArray=msg.payload;\n }\n \n var pushRowData = function(rowData) {\n // ignore rows in ignoreRows array\n if (tableConfig && tableConfig.hasOwnProperty(\"ignoreDevice\") && tableConfig.ignoreDevice[rowData]) {\n // do nothing\n } else {\n // merge edits into table\n if (tableEdit && tableEdit.hasOwnProperty(rowData)) {\n let tableRow = RED.util.cloneMessage(tableData[rowData]);\n Object.keys(tableEdit[rowData]).forEach((field) => {\n tableRow[field]=tableEdit[rowData][field];\n });\n tableArray.push(tableRow);\n } else {\n tableArray.push(tableData[rowData]);\n }\n }\n }\n \n if (tableConfig.hasOwnProperty(\"rowOrder\")) {\n // first check if new rows exits which are not in rowOrder\n Object.keys(tableData).forEach((key) => {\n if (tableConfig.rowOrder.indexOf(tableData[key][tableIndex])<0) {\n tableConfig.rowOrder.push(tableData[key][tableIndex]); // add row to the end of rowOrder\n }\n });\n tableConfig.rowOrder.forEach((value,index) => {\n node.warn([\"pushRowOrder\",value,tableData.hasOwnProperty(value),tableData[value]]);\n if (tableData.hasOwnProperty(value)) { // push rows in rowOrder sequence\n pushRowData(value);\n } else { // delete not existing rows from rowOrder\n tableConfig.rowOrder.splice(index,1)\n }\n });\n } else {\n for (let rowData in tableData) {\n pushRowData(rowData);\n }\n }\n // store the first index if maxRows limits amount of displayed lines\n if (maxRows>0 && tableData) {\n let tableKeys=Object.keys(tableData);\n if (tableKeys.length>0 && typeof tableData[tableKeys[0]][tableIndex] === \"number\") {\n tableConfig.currentFirstRow=tableData[tableKeys[0]][tableIndex];\n }\n }\n \n if (command=='getTable'){\n status.fill=\"blue\";\n status.text+=\" \"+tableArray.length+\" rows emitted\";\n return [{payload:status},null,msg];\n } else {\n status.fill=\"blue\";\n status.text+=\" \"+tableArray.length+\" rows restored\";\n return [{payload:status},msg,[{topic:\"maxRows\",payload:maxRows},{topic:\"maxStore\",payload:maxStore}]];\n }\n} if (msg.hasOwnProperty(\"ui_control\")) {\n // callback from tabulator\n status.fill=\"blue\";\n status.text=\"callback \"+msg.ui_control.callback;\n switch(msg.ui_control.callback) {\n case \"columnResized\": // save new column width\n if (tableConfig.columnWidths===undefined) tableConfig.columnWidths={};\n tableConfig.columnWidths[msg.ui_control.columnWidths.field]=msg.ui_control.columnWidths.width;\n flow.set(\"$parent.\"+tableContext.tableConfig.name,tableConfig,tableContext.tableConfig.storage);\n status.text=msg.ui_control.columnWidths.field+\"=\"+msg.ui_control.columnWidths.width+\"px\";\n break;\n case \"columnMoved\": // save new column order\n if (tableConfig.columns===undefined) tableConfig.columns=[];\n tableConfig.columns=msg.ui_control.columns;\n flow.set(\"$parent.\"+tableContext.tableConfig.name,tableConfig,tableContext.tableConfig.storage);\n status.text=\"new column order\";\n break;\n case \"cellEdited\":\n if (tableEdit) {\n if (!tableEdit.hasOwnProperty(msg[tableIndex])) tableEdit[msg[tableIndex]]={};\n tableEdit[msg[tableIndex]][msg.field] = msg.payload; // save data and mark as edited field\n flow.set(\"$parent.\"+tableContext.tableEdit.name,tableEdit,tableContext.tableEdit.storage);\n mergeEdits(tableData);\n flow.set(\"$parent.\"+tableContext.tableData.name,tableData,tableContext.tableData.storage);\n status.text=msg[tableIndex]+\" \"+msg.field+\" edited to \"+msg.payload;\n msg[tableDataProp]={};\n msg[tableDataProp][tableIndex]=msg[tableIndex];\n msg[tableDataProp][msg.field]=msg.payload;\n msgToTable.payload.arguments=[[msg[tableDataProp]]];\n node.send([{payload:status},null,msg]); // was node.send([{payload:status},msgToTable,msg]);\n } else {\n node.error(\"[ui-table handler] no tableEdit store defined!\")\n }\n break;\n case \"rowContext\":\n msg.ignoredDevices=[];\n for (let rowData in tableConfig.ignoreDevice) {\n if (tableConfig.ignoreDevice.hasOwnProperty(rowData)) {\n msg.ignoredDevices.push({\"text\":rowData,\"icon\":\"fa fa-plug\",\"topic\":\"unIgnoreDevice\",\"payload\":rowData}) \n }\n }\n break;\n case \"headerContext\":\n msg.hiddenColumns=[];\n let tabulatorConfig = env.get('tabulator');\n for (let column in tableConfig.columnVisible) {\n if (tableConfig.columnVisible.hasOwnProperty(column) &&\n !tableConfig.columnVisible[column]) {\n let configColumn=searchTabulatorColumn(tabulatorConfig.tabulator.columns,\"field\",column);\n let icon;\n if (configColumn.hasOwnProperty('title') && configColumn.title.toLowerCase().includes('</i>')) {\n // <i class='fa fa-star-half-o'></i> State\n let start=configColumn.title.indexOf(\"'fa \");\n let end=configColumn.title.indexOf(\"'\",start+1);\n icon=configColumn.title.substring(start+4,end);\n }\n msg.hiddenColumns.push({\"text\":column,\"icon\":icon,\"topic\":\"columnUnHide\",\"payload\":configColumn.field}) \n }\n }\n break;\n case \"rowMoved\":\n if (tableConfig.rowOrder===undefined) tableConfig.rowOrder={};\n tableConfig.rowOrder=msg.ui_control.rowOrder;\n flow.set(\"$parent.\"+tableContext.tableConfig.name,tableConfig,tableContext.tableConfig.storage);\n status.text=\"new row order\";\n break;\n default:\n // if rowIndex exists pass complete object\n if (msg.hasOwnProperty(tableIndex)) {\n msg.rowData=tableData[msg[tableIndex]];\n }\n status.text=\"pass message\";\n }\n return [{payload:status},null,msg];\n} \nif (Array.isArray(msg.payload)) {\n tableData={};\n \n msg.payload.forEach((row) => {\n if (row.hasOwnProperty(tableIndex)) {\n tableData[row[tableIndex]]=row;\n }\n });\n if (msg.keepEdits) {\n mergeEdits(tableData);\n }\n\n \n flow.set(\"$parent.\"+tableContext.tableData.name,tableData,tableContext.tableData.storage);\n if (tableContext.hasOwnProperty(\"tableEdit\") && !msg.keepEdits) {\n tableEdit={};\n flow.set(\"$parent.\"+tableContext.tableEdit.name,tableEdit,tableContext.tableEdit.storage);\n }\n status.fill=\"blue\"\n status.text=\"table replaced \"+msg.payload.length+\" rows\";\n return [{payload:status},msg,null];\n} \n \n// nothing to do bejond this point\nstatus.text+=\" [\"+msg.payload+\"]\";\nreturn [{payload:status},null];\n", + "outputs": 3, + "noerr": 0, + "initialize": "", + "finalize": "", + "x": 192, + "y": 85, + "wires": [ + [], + [], + [] + ], + "icon": "font-awesome/fa-table" + }, + { + "id": "a646103fce176d34", + "type": "function", + "z": "753d1cb452ebd76e", + "name": "set kwargs", + "func": "var newMsg = {\n payload: {\n \"kwargs\": { \"filename\": msg.payload}}}\nglobal.set(\"kwargs_sequence\", newMsg)\nreturn global.get(\"kwargs_sequence\");", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 530, + "y": 360, + "wires": [ + [ + "24edf8b8e8cc4fe9" + ] + ] + }, + { + "id": "fc325307f9b71ad7", + "type": "ui_text_input", + "z": "753d1cb452ebd76e", + "name": "", + "label": "Filename", + "tooltip": "", + "group": "142ad6ae.d55e29", + "order": 6, + "width": 0, + "height": 0, + "passthru": true, + "mode": "text", + "delay": "250", + "topic": "filename", + "sendOnBlur": false, + "className": "", + "topicType": "str", + "x": 180, + "y": 360, + "wires": [ + [ + "a646103fce176d34" + ] + ] + }, + { + "id": "6a463bf900cbcb18", + "type": "ui_button", + "z": "753d1cb452ebd76e", + "name": "", + "group": "142ad6ae.d55e29", + "order": 7, + "width": 0, + "height": 0, + "passthru": false, + "label": "Load sequence", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 180, + "y": 220, + "wires": [ + [ + "f7179af84070f140", + "60f30b88fa0fd1a5", + "a91347161001f3b5", + "c32e7279d6c9a272" + ] + ] + }, + { + "id": "c32e7279d6c9a272", + "type": "function", + "z": "753d1cb452ebd76e", + "name": "set kwargs", + "func": "return global.get(\"kwargs_sequence\");", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 610, + "y": 280, + "wires": [ + [ + "8e435b914e061d45" + ] + ] + }, + { + "id": "8e435b914e061d45", + "type": "join", + "z": "753d1cb452ebd76e", + "name": "", + "mode": "custom", + "build": "merged", + "property": "payload", + "propertyType": "msg", + "key": "topic", + "joiner": "\\n", + "joinerType": "str", + "accumulate": true, + "timeout": "", + "count": "3", + "reduceRight": false, + "reduceExp": "", + "reduceInit": "", + "reduceInitType": "", + "reduceFixup": "", + "x": 950, + "y": 200, + "wires": [ + [] + ] + }, + { + "id": "009df2f988c7e6ba", + "type": "change", + "z": "753d1cb452ebd76e", + "name": "", + "rules": [ + { + "t": "set", + "p": "complete", + "pt": "msg", + "to": "true", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 690, + "y": 200, + "wires": [ + [ + "8e435b914e061d45" + ] + ] + }, + { + "id": "ad9caa24ea8df8c7", + "type": "function", + "z": "753d1cb452ebd76e", + "name": "set cmd_id", + "func": "var newMsg = { payload: {\"cmd_id\": msg.payload }};\nreturn newMsg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 160, + "wires": [ + [ + "8e435b914e061d45" + ] + ] + }, + { + "id": "a91347161001f3b5", + "type": "function", + "z": "753d1cb452ebd76e", + "name": "set cmd", + "func": "return { payload: { \"cmd\": \"load_sequence\" } };", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 580, + "y": 240, + "wires": [ + [ + "8e435b914e061d45" + ] + ] + }, + { + "id": "60f30b88fa0fd1a5", + "type": "delay", + "z": "753d1cb452ebd76e", + "name": "", + "pauseType": "delay", + "timeout": "250", + "timeoutUnits": "milliseconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 490, + "y": 200, + "wires": [ + [ + "009df2f988c7e6ba" + ] + ] + }, + { + "id": "f7179af84070f140", + "type": "uuid", + "z": "753d1cb452ebd76e", + "uuidVersion": "v1", + "namespaceType": "", + "namespace": "", + "namespaceCustom": "", + "name": "", + "field": "payload", + "fieldType": "msg", + "x": 410, + "y": 160, + "wires": [ + [ + "ad9caa24ea8df8c7" + ] + ] + }, + { + "id": "24edf8b8e8cc4fe9", + "type": "debug", + "z": "753d1cb452ebd76e", + "name": "debug 8", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 740, + "y": 360, + "wires": [] + }, + { + "id": "f0e1c3fb6491d3de", + "type": "ui_text", + "z": "753d1cb452ebd76e", + "group": "142ad6ae.d55e29", + "order": 5, + "width": 8, + "height": 1, + "name": "", + "label": "<font size = 4.5>Load sequence", + "format": "{{msg.payload}}", + "layout": "row-left", + "className": "", + "x": 210, + "y": 300, + "wires": [] + }, + { + "id": "0f23781293c4b819", + "type": "mqtt in", + "z": "b19c51e9d4d25a33", + "name": "", + "topic": "ohmpi_0001/exec", + "qos": "2", + "datatype": "auto-detect", + "broker": "5a09c1ee2a3419c3", + "nl": false, + "rap": false, + "inputs": 0, + "x": 320, + "y": 40, + "wires": [ + [ + "a5740258089c58ac" + ] + ] + }, + { + "id": "36ba500fac1d0f38", + "type": "mqtt in", + "z": "b19c51e9d4d25a33", + "name": "", + "topic": "ohmpi_0001/data", + "qos": "2", + "datatype": "auto", + "broker": "5a09c1ee2a3419c3", + "nl": false, + "rap": false, + "inputs": 0, + "x": 320, + "y": 140, + "wires": [ + [ + "f6075b441607acc4", + "2a196662df41046e", + "946869bda15d570c", + "d3229390343731e3" + ] + ] + }, + { + "id": "16b2937ad25f25d2", + "type": "mqtt out", + "z": "b19c51e9d4d25a33", + "name": "MQTT ctrl", + "topic": "ohmpi_0001/ctrl", + "qos": "2", + "retain": "false", + "respTopic": "", + "contentType": "", + "userProps": "", + "correl": "", + "expiry": "", + "broker": "5a09c1ee2a3419c3", + "x": 820, + "y": 400, + "wires": [] + }, + { + "id": "c7c725c7f947f5a8", + "type": "mqtt in", + "z": "b19c51e9d4d25a33", + "name": "", + "topic": "ohmpi_0001/soh", + "qos": "2", + "datatype": "auto", + "broker": "5a09c1ee2a3419c3", + "nl": false, + "rap": false, + "inputs": 0, + "x": 320, + "y": 240, + "wires": [ + [] + ] + }, + { + "id": "06e401792488500e", + "type": "ui_button", + "z": "b19c51e9d4d25a33", + "name": "Run sequence", + "group": "142ad6ae.d55e29", + "order": 2, + "width": 2, + "height": 1, + "passthru": false, + "label": "Run", + "tooltip": "run sequence", + "color": "red", + "bgcolor": "lightgrey", + "className": "", + "icon": "play_circle_fill", + "payload": "{\"cmd_id\" :\"0\", \"cmd\":\"run_sequence_async\"}", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 320, + "y": 340, + "wires": [ + [ + "16b2937ad25f25d2", + "649dbc02e71571e2" + ] + ] + }, + { + "id": "c427102c051828b6", + "type": "ui_button", + "z": "b19c51e9d4d25a33", + "name": "Interrupt", + "group": "142ad6ae.d55e29", + "order": 3, + "width": 2, + "height": 1, + "passthru": false, + "label": "Stop", + "tooltip": "interrupt sequence", + "color": "black", + "bgcolor": "lightgrey", + "className": "", + "icon": "stop", + "payload": "{\"cmd_id\" :\"0\", \"cmd\":\"interrupt\"}", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 300, + "y": 400, + "wires": [ + [ + "16b2937ad25f25d2", + "649dbc02e71571e2" + ] + ] + }, + { + "id": "f4b6096e60252b62", + "type": "ui_text", + "z": "b19c51e9d4d25a33", + "group": "b0990b3c5ff3c09a", + "order": 3, + "width": 18, + "height": 7, + "name": "MQTT exec", + "label": "Execution", + "format": "{{msg.payload}}", + "layout": "col-center", + "className": "", + "x": 830, + "y": 40, + "wires": [] + }, + { + "id": "f6075b441607acc4", + "type": "ui_text", + "z": "b19c51e9d4d25a33", + "group": "b0990b3c5ff3c09a", + "order": 4, + "width": 18, + "height": 4, + "name": "MQTT Data", + "label": "Data", + "format": "{{msg.payload}}", + "layout": "col-center", + "className": "", + "x": 830, + "y": 140, + "wires": [] + }, + { + "id": "455f7fa404a19428", + "type": "ui_button", + "z": "b19c51e9d4d25a33", + "name": "", + "group": "b0990b3c5ff3c09a", + "order": 1, + "width": 0, + "height": 0, + "passthru": false, + "label": "clear messages", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "<p style=\"background-color:Tomato;\">", + "icon": "", + "payload": " ", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 580, + "y": 200, + "wires": [ + [ + "f6075b441607acc4", + "f4b6096e60252b62", + "649dbc02e71571e2" + ] + ] + }, + { + "id": "31071d204231a91b", + "type": "subflow:c3dbd6595b0256a9", + "z": "b19c51e9d4d25a33", + "name": "", + "x": 320, + "y": 540, + "wires": [ + [ + "16b2937ad25f25d2", + "649dbc02e71571e2" + ] + ] + }, + { + "id": "e62faba48a0960cc", + "type": "subflow:1506d9deaa5de120", + "z": "b19c51e9d4d25a33", + "name": "", + "x": 320, + "y": 640, + "wires": [ + [ + "16b2937ad25f25d2", + "649dbc02e71571e2" + ] + ] + }, + { + "id": "649dbc02e71571e2", + "type": "ui_text", + "z": "b19c51e9d4d25a33", + "group": "b0990b3c5ff3c09a", + "order": 2, + "width": 0, + "height": 0, + "name": "", + "label": "Command sent", + "format": "{{msg.payload}}", + "layout": "col-center", + "className": "", + "x": 840, + "y": 300, + "wires": [] + }, + { + "id": "2a196662df41046e", + "type": "subflow:4489e1d51c9522f3", + "z": "b19c51e9d4d25a33", + "name": "", + "x": 1190, + "y": 60, + "wires": [] + }, + { + "id": "946869bda15d570c", + "type": "subflow:b6b050428bd78f42", + "z": "b19c51e9d4d25a33", + "name": "", + "x": 1190, + "y": 100, + "wires": [] + }, + { + "id": "d16e9ecebccafc37", + "type": "ui_table", + "z": "b19c51e9d4d25a33", + "group": "6e40c7571e22771c", + "name": "Latest reading", + "order": 1, + "width": 0, + "height": 0, + "columns": [ + { + "field": "time", + "title": "Time", + "width": "20%", + "align": "left", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "A", + "title": "A", + "width": "2%", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "B", + "title": "B", + "width": "2%", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "M", + "title": "M", + "width": "2%", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "N", + "title": "N", + "width": "2%", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "R [ohm]", + "title": "R [ohm]", + "width": "8%", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Vmn [mV]", + "title": "Vmn [mV]", + "width": "8%", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "I [mA]", + "title": "I [mA]", + "width": "8%", + "align": "left", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Ps [mV]", + "title": "Ps [mV]", + "width": "8%", + "align": "left", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "inj time [ms]", + "title": "Injection<br>Time [ms]", + "width": "8%", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "nbStack", + "title": "Nb<br>Stack", + "width": "5%", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Tx [V]", + "title": "Tx [V]", + "width": "4%", + "align": "left", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "CPU temp [degC]", + "title": "CPU<br>Temp [degC]", + "width": "8%", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Nb samples [-]", + "title": "Nb<br>Samples", + "width": "3%", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "cmd_id", + "title": "Cmd<br>ID", + "width": "6%", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + } + ], + "outputs": 1, + "cts": true, + "x": 1160, + "y": 140, + "wires": [ + [] + ] + }, + { + "id": "d3229390343731e3", + "type": "function", + "z": "b19c51e9d4d25a33", + "name": "data", + "func": "var data = JSON.parse(\n msg.payload.split(' | ')[2].slice(6,).split(`'`).join(`\"`));\n\nfor (var key in data) {\n data[key] = data[key].toString()\n};\nmsg.payload = data\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 810, + "y": 180, + "wires": [ + [ + "eda4397c88dfcf6d" + ] + ] + }, + { + "id": "eda4397c88dfcf6d", + "type": "function", + "z": "b19c51e9d4d25a33", + "name": "Add data", + "func": "msg.payload = {\n command: \"addData\", \n arguments: [\n [msg.payload],\n true\n ],\n returnPromise: true\n }\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 960, + "y": 180, + "wires": [ + [ + "d16e9ecebccafc37" + ] + ] + }, + { + "id": "357d22b7680ff21c", + "type": "ui_button", + "z": "b19c51e9d4d25a33", + "name": "", + "group": "6e40c7571e22771c", + "order": 2, + "width": 0, + "height": 0, + "passthru": false, + "label": "Clear table", + "tooltip": "clear all table data", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "date", + "topic": "", + "topicType": "str", + "x": 950, + "y": 220, + "wires": [ + [ + "f46aca539df9df53" + ] + ] + }, + { + "id": "f46aca539df9df53", + "type": "function", + "z": "b19c51e9d4d25a33", + "name": "clearData", + "func": "\nmsg.payload={\n command:\"clearData\",\n arguments: [],\n returnPromise: true\n}\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1121, + "y": 220, + "wires": [ + [ + "d16e9ecebccafc37" + ] + ], + "info": "# clear data\n\nunfortunately this command (currently) do not send a promise back! So we have to pass it directly to the table handler" + }, + { + "id": "a5740258089c58ac", + "type": "function", + "z": "b19c51e9d4d25a33", + "name": "function 7", + "func": "var exec_log = flow.get('exec_log') || [];\nexec_log.push(msg.payload);\nflow.set(\"exec_log\", exec_log);\nvar log = exec_log.slice(-10,).join(\"<br>\")\nmsg.payload = log;\nreturn msg;\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 600, + "y": 40, + "wires": [ + [ + "f4b6096e60252b62" + ] + ] + }, + { + "id": "521b0d9b132502a5", + "type": "subflow:753d1cb452ebd76e", + "z": "b19c51e9d4d25a33", + "name": "", + "x": 330, + "y": 480, + "wires": [ + [ + "649dbc02e71571e2", + "16b2937ad25f25d2" + ] + ] + } +] \ No newline at end of file diff --git a/ohmpi.py b/ohmpi.py index f31e069b4770a3f55bcb96b12c8aabfa4e1f0c4a..cf0508897d7817d84944a781fee05d6db7d963b4 100644 --- a/ohmpi.py +++ b/ohmpi.py @@ -6,7 +6,7 @@ Hardware: Licensed under CERN-OHL-S v2 or any later version Software: Licensed under the GNU General Public License v3.0 Ohmpi.py is a program to control a low-cost and open hardware resistivity meter OhmPi that has been developed by Rémi CLEMENT (INRAE), Vivien DUBOIS (INRAE), Hélène GUYARD (IGE), Nicolas FORQUET (INRAE), Yannick FARGIER (IFSTTAR) -Olivier KAUFMANN (UMONS), Arnaud WATELET (UMONS) and Guillaume BLANCHY (FNRS/ULiege). +Olivier KAUFMANN (UMONS), Arnaud WATLET (UMONS) and Guillaume BLANCHY (FNRS/ULiege). """ import os @@ -47,8 +47,6 @@ except ImportError as error: except Exception as error: print(colored(f'Unexpected error: {error}', 'red')) arm64_imports = None - exit() - class OhmPi(object): """ OhmPi class. @@ -1068,10 +1066,26 @@ class OhmPi(object): if self.on_pi: acquired_data = self.run_measurement(quad, **kwargs) else: # for testing, generate random data + sum_vmn = np.random.rand(1)[0] * 1000. + sum_i = np.random.rand(1)[0] * 100. + cmd_id = np.random.randint(1000) acquired_data = { - 'A': [quad[0]], 'B': [quad[1]], 'M': [quad[2]], 'N': [quad[3]], - 'R [ohm]': np.abs(np.random.randn(1)) + "time": datetime.now().isoformat(), + "A": quad[0], + "B": quad[1], + "M": quad[2], + "N": quad[3], + "inj time [ms]": self.settings['injection_duration'] * 1000., + "Vmn [mV]": sum_vmn, + "I [mA]": sum_i, + "R [ohm]": sum_vmn / sum_i, + "Ps [mV]": np.random.randn(1)[0] * 100., + "nbStack": self.settings['nb_stack'], + "Tx [V]": np.random.randn(1)[0] * 5., + "CPU temp [degC]": np.random.randn(1)[0] * 50., + "Nb samples [-]": self.nb_samples, } + self.data_logger.info(acquired_data) # switch mux off self.switch_mux_off(quad)