From adad97e9174d14a3f474d06afbfe5b5d46646ebf Mon Sep 17 00:00:00 2001 From: rfnx Date: Sat, 1 Feb 2025 01:09:06 -0500 Subject: [PATCH 01/42] Add additional interfaces to AddInterfacePage --- meshchat.py | 204 +- .../interfaces/AddInterfacePage.vue | 1841 ++++++++++++++--- .../components/interfaces/Interface.vue | 12 + .../components/interfaces/InterfacesPage.vue | 2 +- .../nomadnetwork/NomadNetworkPage.vue | 2 +- src/frontend/js/Utils.js | 4 + 6 files changed, 1731 insertions(+), 334 deletions(-) diff --git a/meshchat.py b/meshchat.py index 3ed2e6a..5987477 100644 --- a/meshchat.py +++ b/meshchat.py @@ -329,8 +329,30 @@ class ReticulumMeshChat: if "interfaces" in self.reticulum.config: interfaces = self.reticulum.config["interfaces"] + processed_interfaces = {} + for interface_name, interface in interfaces.items(): + interface_data = interface.copy() + + # handle sub-interfaces for RNodeMultiInterface + if interface_data.get("type") == "RNodeMultiInterface": + sub_interfaces = [] + for sub_name, sub_config in interface_data.items(): + if sub_name not in {"type", "port", "interface_enabled", "selected_interface_mode", + "configured_bitrate"}: + if isinstance(sub_config, dict): + sub_config["name"] = sub_name + sub_interfaces.append(sub_config) + + # add sub-interfaces to the main interface data + interface_data["sub_interfaces"] = sub_interfaces + + for sub in sub_interfaces: + del interface_data[sub["name"]] + + processed_interfaces[interface_name] = interface_data + return web.json_response({ - "interfaces": interfaces, + "interfaces": processed_interfaces, }) # enable reticulum interface @@ -443,11 +465,32 @@ class ReticulumMeshChat: if "enabled" not in interface_details and "interface_enabled" not in interface_details: interface_details["interface_enabled"] = "true" + # additional AutoInterface options + if interface_type == "AutoInterface": + if data.get("group_id"): + interface_details["group_id"] = data.get("group_id") + if data.get("multicast_address_type"): + interface_details["multicast_address_type"] = data.get("multicast_address_type") + if data.get("devices"): + interface_details["devices"] = data.get("devices") + if data.get("ignored_devices"): + interface_details["ignored_devices"] = data.get("ignored_devices") + if data.get("discovery_scope"): + interface_details["discovery_scope"] = data.get("discovery_scope") + if data.get("discovery_port"): + interface_details["discovery_port"] = data.get("discovery_port") + if data.get("data_port"): + interface_details["data_port"] = data.get("data_port") + + # handle tcp client interface if interface_type == "TCPClientInterface": interface_target_host = data.get('target_host') interface_target_port = data.get('target_port') + # optional parameters for kiss_framing and i2p tunnelling + interface_kiss_framing = data.get('kiss_framing') + interface_i2p_tunneled = data.get('i2p_tunneled') # ensure target host provided if interface_target_host is None or interface_target_host == "": @@ -463,6 +506,13 @@ class ReticulumMeshChat: interface_details["target_host"] = data.get('target_host') interface_details["target_port"] = data.get('target_port') + interface_details["kiss_framing"] = interface_kiss_framing + interface_details["i2p_tunneled"] = interface_i2p_tunneled + + # handle I2P interface + if interface_type == "I2PInterface": + interface_details['connectable'] = "True" + interface_details["peers"] = data.get('peers') # handle tcp server interface if interface_type == "TCPServerInterface": @@ -470,6 +520,9 @@ class ReticulumMeshChat: interface_listen_ip = data.get('listen_ip') interface_listen_port = data.get('listen_port') + interface_network_device = data.get('device') + interface_prefer_ipv6 = data.get('prefer_ipv6') + # ensure listen ip provided if interface_listen_ip is None or interface_listen_ip == "": return web.json_response({ @@ -485,6 +538,11 @@ class ReticulumMeshChat: interface_details["listen_ip"] = data.get('listen_ip') interface_details["listen_port"] = data.get('listen_port') + if interface_network_device is not None and interface_network_device != "": + interface_details["device"] = interface_network_device + if interface_prefer_ipv6 is not None and interface_prefer_ipv6 != "" and interface_prefer_ipv6 != False: + interface_details["prefer_ipv6"] = True + # handle udp interface if interface_type == "UDPInterface": @@ -492,6 +550,7 @@ class ReticulumMeshChat: interface_listen_port = data.get('listen_port') interface_forward_ip = data.get('forward_ip') interface_forward_port = data.get('forward_port') + interface_network_device = data.get('device') # ensure listen ip provided if interface_listen_ip is None or interface_listen_ip == "": @@ -522,6 +581,9 @@ class ReticulumMeshChat: interface_details["forward_ip"] = data.get('forward_ip') interface_details["forward_port"] = data.get('forward_port') + if interface_network_device is not None and interface_network_device != "": + interface_details["network_device"] = interface_network_device + # handle rnode interface if interface_type == "RNodeInterface": @@ -575,6 +637,132 @@ class ReticulumMeshChat: interface_details["spreadingfactor"] = interface_spreadingfactor interface_details["codingrate"] = interface_codingrate + # Handle RNode Multi Interface + if interface_type == "RNodeMultiInterface": + interface_port = data.get("port") + sub_interfaces = data.get("sub_interfaces", []) + + if not interface_port: + return web.json_response({"message": "Port is required"}, status=422) + + if not isinstance(sub_interfaces, list) or not sub_interfaces: + return web.json_response({"message": "At least one sub-interface is required"}, status=422) + + interface_details["type"] = interface_type + interface_details["interface_enabled"] = True + interface_details["port"] = interface_port + + for idx, sub in enumerate(sub_interfaces): + # validate required fields for each sub-interface + required_subinterface_fields = ["name", "frequency", "bandwidth", "txpower", "spreadingfactor", + "codingrate", + "vport"] + missing_fields = [field for field in required_subinterface_fields if not sub.get(field)] + if missing_fields: + return web.json_response({ + "message": f"Sub-interface {idx + 1} is missing required field(s): {', '.join(missing_fields)}" + }, status=422) + + sub_interface_name = sub.get("name") + interface_details[sub_interface_name] = { + "interface_enabled": "true", + "frequency": int(sub["frequency"]), + "bandwidth": int(sub["bandwidth"]), + "txpower": int(sub["txpower"]), + "spreadingfactor": int(sub["spreadingfactor"]), + "codingrate": int(sub["codingrate"]), + "vport": int(sub["vport"]), + } + + interfaces[interface_name] = interface_details + + # Handle Serial, KISS, and AX25KISS + if interface_type == "SerialInterface" or interface_type == "KISSInterface" or interface_type == "AX25KISSInterface": + interface_port = data.get('port') + interface_speed = data.get('speed') + + required_fields = { + interface_port: "Port is required", + interface_speed: "Serial speed is required", + } + + for field, error_message in required_fields.items(): + if field is None or field == "": + return web.json_response({ + "message": error_message, + }, status=422) + + interface_details["port"] = interface_port + interface_details['connectable'] = "True" + interface_details["type"] = interface_type + interface_details["interface_enabled"] = True + + interface_details["speed"] = interface_speed + interface_details["databits"] = data.get('databits') + interface_details['parity'] = data.get('parity') + interface_details['stopbits'] = data.get('stopbits') + + # Handle KISS and AX25KISS specific options + if (interface_type == "KISSInterface" or interface_type == "AX25KISSInterface"): + interface_details["preamble"] = data.get('preamble') + interface_details["txtail"] = data.get('txtail') + interface_details['persistence'] = data.get('persistence') + interface_details['slottime'] = data.get('slottime') + interface_details['callsign'] = data.get('callsign') + interface_details['ssid'] = data.get('ssid') + + # RNode Airtime limits and station ID + callsign = data.get('callsign') + id_interval = data.get('id_interval') + airtime_limit_long = data.get('airtime_limit_long') + airtime_limit_short = data.get('airtime_limit_short') + + if callsign is not None and callsign != "": + interface_details["callsign"] = callsign + if id_interval is not None and id_interval != "": + interface_details["id_interval"] = id_interval + if airtime_limit_long is not None and airtime_limit_long != "": + interface_details["airtime_limit_long"] = airtime_limit_long + if airtime_limit_short is not None and airtime_limit_short != "": + interface_details["airtime_limit_short"] = airtime_limit_short + + # Handle Pipe Interface + if interface_type == "PipeInterface": + interface_command = data.get('command') + interface_respawn_delay = data.get('respawn_delay') + + required_fields = { + interface_command: "Command is required", + interface_respawn_delay: "Respawn delay is required", + } + + for field, error_message in required_fields.items(): + if field is None or field == "": + return web.json_response({ + "message": error_message, + }, status=422) + + interface_details["command"] = interface_command + interface_details["respawn_delay"] = interface_respawn_delay + + # Common interface options + inferred_bitrate = data.get('bitrate') + transport_mode = data.get('mode') + network_name = data.get('network_name') + ifac_passphrase = data.get('passphrase') + ifac_size = data.get('ifac_size') + + if inferred_bitrate is not None and inferred_bitrate != "": + interface_details["bitrate"] = inferred_bitrate + if transport_mode is not None and transport_mode != "": + interface_details["mode"] = transport_mode + if network_name is not None and network_name != "": + interface_details["network_name"] = network_name + if ifac_passphrase is not None and ifac_passphrase != "": + interface_details["passphrase"] = ifac_passphrase + if ifac_size is not None and ifac_size != "": + interface_details["ifac_size"] = ifac_size + # merge new interface into existing interfaces interfaces[interface_name] = interface_details self.reticulum.config["interfaces"] = interfaces @@ -619,7 +807,19 @@ class ReticulumMeshChat: for key, value in interface.items(): output.append(f" {key} = {value}") output.append("") - + + # Handle sub-interfaces for RNodeMultiInterface + if interface.get("type") == "RNodeMultiInterface": + for sub_name, sub_config in interface.items(): + if sub_name in {"type", "port", "interface_enabled"}: + continue + if isinstance(sub_config, dict): + output.append(f" [[[ {sub_name} ]]]") + for sub_key, sub_value in sub_config.items(): + output.append(f" {sub_key} = {sub_value}") + output.append("") + + return web.Response( text="\n".join(output), content_type="text/plain", diff --git a/src/frontend/components/interfaces/AddInterfacePage.vue b/src/frontend/components/interfaces/AddInterfacePage.vue index 1ffafc2..f54580c 100644 --- a/src/frontend/components/interfaces/AddInterfacePage.vue +++ b/src/frontend/components/interfaces/AddInterfacePage.vue @@ -1,174 +1,1023 @@ diff --git a/src/frontend/components/interfaces/Interface.vue b/src/frontend/components/interfaces/Interface.vue index 5aff14a..5eafded 100644 --- a/src/frontend/components/interfaces/Interface.vue +++ b/src/frontend/components/interfaces/Interface.vue @@ -36,6 +36,14 @@ + + + + + + + + @@ -186,6 +194,10 @@
• Bitrate: {{ formatBitsPerSecond(iface._stats?.bitrate ?? 0) }}
• TX: {{ formatBytes(iface._stats?.txb ?? 0) }}
• RX: {{ formatBytes(iface._stats?.rxb ?? 0) }}
+
• Noise Floor: {{ + iface._stats?.noise_floor + }} dBm +
• Clients: {{ iface._stats?.clients }}
diff --git a/src/frontend/components/interfaces/InterfacesPage.vue b/src/frontend/components/interfaces/InterfacesPage.vue index c25c890..52d3f8f 100644 --- a/src/frontend/components/interfaces/InterfacesPage.vue +++ b/src/frontend/components/interfaces/InterfacesPage.vue @@ -1,6 +1,6 @@ + + + + + + From 019ba93d8061373c5a63c7b820c7fc0e42823ef9 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sun, 2 Feb 2025 15:22:08 +1300 Subject: [PATCH 13/42] move optional rnode interface settings to own section --- .../interfaces/AddInterfacePage.vue | 93 +++++++++---------- 1 file changed, 43 insertions(+), 50 deletions(-) diff --git a/src/frontend/components/interfaces/AddInterfacePage.vue b/src/frontend/components/interfaces/AddInterfacePage.vue index 2155487..1527155 100644 --- a/src/frontend/components/interfaces/AddInterfacePage.vue +++ b/src/frontend/components/interfaces/AddInterfacePage.vue @@ -274,52 +274,6 @@ - -
- - - Show on-air RNode bitrate & link budget - - -
- -
-
-
- - -

ⓘ A stub or PCB antenna might have around 1 dBi of gain, where a directional Yagi might have 5 dBi of gain.

-
-
-
- -
-

On-Air Calculations

-
-
-
Sensitivity
-
{{ RNodeInterfaceLoRaParameters.sensitivity }}
-
-
-
Data Rate
-
{{ RNodeInterfaceLoRaParameters.dataRate }}
-
-
-
Link Budget
-
{{ RNodeInterfaceLoRaParameters.linkBudget }}
-
-
-
- -
- -
-

ⓘ The RNode Multi Interface is used for custom devices with multiple LoRa transceivers such as the openCom XL.

@@ -678,6 +632,49 @@ + + + + + + @@ -779,7 +776,6 @@ export default { isEditingInterface: false, showAllSettings: false, // more interface settings, used for TCPInterface and RNodeInterface - showRNodeLoRaParameters: false, // accordion for displaying RNode physical LoRa parameters showRNodeSubInterfaces: false, // accordion for adding multiple RNode interfaces appInfo: null, @@ -1265,9 +1261,6 @@ export default { toggleAllSettings() { this.showAllSettings = !this.showAllSettings; }, - toggleRNodeLoRaParameters() { - this.showRNodeLoRaParameters = !this.showRNodeLoRaParameters; - }, toggleRNodeSubInterfaces() { this.showRNodeSubInterfaces = !this.showRNodeSubInterfaces; }, From 9b62f60e189dd30847c735a39cd269c1c90f2c42 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sun, 2 Feb 2025 15:35:02 +1300 Subject: [PATCH 14/42] simplify ui for ip2 interface peers --- .../interfaces/AddInterfacePage.vue | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/frontend/components/interfaces/AddInterfacePage.vue b/src/frontend/components/interfaces/AddInterfacePage.vue index 1527155..244d2fa 100644 --- a/src/frontend/components/interfaces/AddInterfacePage.vue +++ b/src/frontend/components/interfaces/AddInterfacePage.vue @@ -163,24 +163,21 @@
-
- ⓘ To use the I2P interface, you must have an I2P router running on your system. When the I2P Interface is added for the first time Reticulum will generate a new I2P address for the interface and begin listening for inbound traffic. -
- - Manage Peers - -
-
- - -
- + +
+
ⓘ To use the I2P interface, you must have an I2P router running on your system. When the I2P Interface is added for the first time Reticulum will generate a new I2P address for the interface and begin listening for inbound traffic.
+ +
+
+ +
+
@@ -749,7 +746,7 @@
- From f87a360d5c558070b2ccfac0e8d17c69570fd6c7 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sun, 2 Feb 2025 15:49:29 +1300 Subject: [PATCH 15/42] move optional tcp server interface and udp interface settings to own section --- .../interfaces/AddInterfacePage.vue | 62 ++++++++++--------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/src/frontend/components/interfaces/AddInterfacePage.vue b/src/frontend/components/interfaces/AddInterfacePage.vue index 244d2fa..f9e36b3 100644 --- a/src/frontend/components/interfaces/AddInterfacePage.vue +++ b/src/frontend/components/interfaces/AddInterfacePage.vue @@ -123,32 +123,6 @@
-
- - Additional Server Interface settings - -
-
- - ⓘ Binds the interface to a specific network interface - -
-
-
- - ⓘ Binds the TCP Server Interface to an IPv6 address -
- -
-
-
-
@@ -461,7 +435,7 @@
- ⓘ Using this interface, Reticulum can use any program as an interface via stdin and stdout. This can be usedto easily create virtual interfaces, or to interface with custom hardware or other systems. +
ⓘ Using this interface, Reticulum can use any program as an interface via stdin and stdout. This can be usedto easily create virtual interfaces, or to interface with custom hardware or other systems.
@@ -560,7 +534,7 @@
- ⓘ Enabled when connecting to software that uses KISS framing such as packet radio sound modems. For KISS connections through serial hardware select "KISS Interface" as the interface type. + Enable this when connecting to software that uses KISS framing such as packet radio sound modems. For KISS connections through serial hardware select "KISS Interface" as the interface type.
@@ -568,7 +542,7 @@
- ⓘ Enables tunnelling through an I2P Connection using the TCPClientInterface + Enables tunnelling through an I2P Connection using the TCPClientInterface
@@ -577,6 +551,36 @@ + + + + + + From 9741cdcd604f6402b98525ad83b5a58b5511a1e1 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sun, 2 Feb 2025 16:12:23 +1300 Subject: [PATCH 16/42] adjust rnode subinterfaces ui --- .../interfaces/AddInterfacePage.vue | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/frontend/components/interfaces/AddInterfacePage.vue b/src/frontend/components/interfaces/AddInterfacePage.vue index f9e36b3..5a82883 100644 --- a/src/frontend/components/interfaces/AddInterfacePage.vue +++ b/src/frontend/components/interfaces/AddInterfacePage.vue @@ -254,17 +254,18 @@
-
- - Manage RNode Sub-Interfaces - -
-
+ +
+ +
+
+ +
@@ -280,6 +281,7 @@
+
@@ -294,6 +296,7 @@
+
@@ -310,9 +313,11 @@ class="w-full bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white">
- + + +
- +
@@ -777,7 +782,6 @@ export default { isEditingInterface: false, showAllSettings: false, // more interface settings, used for TCPInterface and RNodeInterface - showRNodeSubInterfaces: false, // accordion for adding multiple RNode interfaces appInfo: null, transportEnabled: false, @@ -1262,9 +1266,6 @@ export default { toggleAllSettings() { this.showAllSettings = !this.showAllSettings; }, - toggleRNodeSubInterfaces() { - this.showRNodeSubInterfaces = !this.showRNodeSubInterfaces; - }, addSubInterface() { this.RNodeMultiInterface.subInterfaces.push({ name: '', From dacd2ea3f2ed8b6938bf6d6bfe7423aedb394a8c Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sun, 2 Feb 2025 16:13:54 +1300 Subject: [PATCH 17/42] remove unused component --- .../interfaces/AddInterfacePage.vue | 2 -- .../interfaces/ExpandingSectionHeader.vue | 21 ------------------- 2 files changed, 23 deletions(-) delete mode 100644 src/frontend/components/interfaces/ExpandingSectionHeader.vue diff --git a/src/frontend/components/interfaces/AddInterfacePage.vue b/src/frontend/components/interfaces/AddInterfacePage.vue index 5a82883..02e34b7 100644 --- a/src/frontend/components/interfaces/AddInterfacePage.vue +++ b/src/frontend/components/interfaces/AddInterfacePage.vue @@ -768,14 +768,12 @@ \ No newline at end of file From dabd6c4a3735eb265a7ea65b9ef5039e661422a6 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sun, 2 Feb 2025 16:35:57 +1300 Subject: [PATCH 18/42] ui adjustments --- .../interfaces/AddInterfacePage.vue | 93 ++++++++++--------- 1 file changed, 51 insertions(+), 42 deletions(-) diff --git a/src/frontend/components/interfaces/AddInterfacePage.vue b/src/frontend/components/interfaces/AddInterfacePage.vue index 02e34b7..cad3425 100644 --- a/src/frontend/components/interfaces/AddInterfacePage.vue +++ b/src/frontend/components/interfaces/AddInterfacePage.vue @@ -63,7 +63,7 @@
- +
- +
- +
- +
- +
- +
- +
@@ -140,7 +140,7 @@
ⓘ To use the I2P interface, you must have an I2P router running on your system. When the I2P Interface is added for the first time Reticulum will generate a new I2P address for the interface and begin listening for inbound traffic.
- +
- + @@ -168,7 +168,7 @@
- +
- + @@ -221,7 +221,7 @@
- +
@@ -229,7 +229,7 @@
- + @@ -237,7 +237,7 @@
- + @@ -248,7 +248,7 @@

ⓘ The RNode Multi Interface is used for custom devices with multiple LoRa transceivers such as the openCom XL.

- + @@ -256,7 +256,7 @@
- +
@@ -324,28 +324,30 @@
- - +
+ + +
- +
-
- +
+
-
- +
+
-
- +
+
@@ -354,7 +356,7 @@
-
+
- +
- ⓘ Enables AX.25 Framing
- +
- +
- +
- +
- +
- +
- +
+
ⓘ Using this interface, Reticulum can use any program as an interface via stdin and stdout. This can be usedto easily create virtual interfaces, or to interface with custom hardware or other systems.
- - - - + +
+ + +
+ +
+ + +
+
From c5e4776dc12709250101fb295b12f0ada60ed612 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sun, 2 Feb 2025 17:00:37 +1300 Subject: [PATCH 19/42] tidy ui for on air rnode bitrate and link budget --- .../interfaces/AddInterfacePage.vue | 82 +++++++++---------- 1 file changed, 39 insertions(+), 43 deletions(-) diff --git a/src/frontend/components/interfaces/AddInterfacePage.vue b/src/frontend/components/interfaces/AddInterfacePage.vue index cad3425..1891ae1 100644 --- a/src/frontend/components/interfaces/AddInterfacePage.vue +++ b/src/frontend/components/interfaces/AddInterfacePage.vue @@ -459,6 +459,45 @@
+ + + + + + @@ -647,49 +686,6 @@ - - - - - - From 6c43c2cc4fba62e041c2867e69ffb604e0c5c134 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sun, 2 Feb 2025 17:02:43 +1300 Subject: [PATCH 20/42] revert so interfaces page can scroll --- src/frontend/components/interfaces/InterfacesPage.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/components/interfaces/InterfacesPage.vue b/src/frontend/components/interfaces/InterfacesPage.vue index 52d3f8f..c25c890 100644 --- a/src/frontend/components/interfaces/InterfacesPage.vue +++ b/src/frontend/components/interfaces/InterfacesPage.vue @@ -1,6 +1,6 @@