daemon/gui: added support to configure wireless network for position calculations or not
This commit is contained in:
parent
d124820a86
commit
d20cb1ef58
10 changed files with 216 additions and 15 deletions
|
@ -20,6 +20,7 @@ from core.api.grpc.configservices_pb2 import (
|
|||
from core.api.grpc.core_pb2 import (
|
||||
ExecuteScriptRequest,
|
||||
GetConfigRequest,
|
||||
GetWirelessConfigRequest,
|
||||
LinkedRequest,
|
||||
WirelessConfigRequest,
|
||||
WirelessLinkedRequest,
|
||||
|
@ -1142,6 +1143,13 @@ class CoreGrpcClient:
|
|||
)
|
||||
self.stub.WirelessConfig(request)
|
||||
|
||||
def get_wireless_config(
|
||||
self, session_id: int, node_id: int
|
||||
) -> Dict[str, wrappers.ConfigOption]:
|
||||
request = GetWirelessConfigRequest(session_id=session_id, node_id=node_id)
|
||||
response = self.stub.GetWirelessConfig(request)
|
||||
return wrappers.ConfigOption.from_dict(response.config)
|
||||
|
||||
def connect(self) -> None:
|
||||
"""
|
||||
Open connection to server, must be closed manually.
|
||||
|
|
|
@ -29,6 +29,7 @@ from core.nodes.docker import DockerNode
|
|||
from core.nodes.interface import CoreInterface
|
||||
from core.nodes.lxd import LxcNode
|
||||
from core.nodes.network import CoreNetwork, CtrlNet, PtpNet, WlanNode
|
||||
from core.nodes.wireless import WirelessNode
|
||||
from core.services.coreservices import CoreService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -827,6 +828,9 @@ def configure_node(
|
|||
if node.mobility_config:
|
||||
config = {k: v.value for k, v in node.mobility_config.items()}
|
||||
session.mobility.set_model_config(node.id, Ns2ScriptedMobility.name, config)
|
||||
if isinstance(core_node, WirelessNode) and node.wireless_config:
|
||||
config = {k: v.value for k, v in node.wireless_config.items()}
|
||||
core_node.set_config(config)
|
||||
for service_name, service_config in node.service_configs.items():
|
||||
data = service_config.data
|
||||
config = ServiceConfig(
|
||||
|
|
|
@ -30,6 +30,8 @@ from core.api.grpc.configservices_pb2 import (
|
|||
)
|
||||
from core.api.grpc.core_pb2 import (
|
||||
ExecuteScriptResponse,
|
||||
GetWirelessConfigRequest,
|
||||
GetWirelessConfigResponse,
|
||||
LinkedRequest,
|
||||
LinkedResponse,
|
||||
WirelessConfigRequest,
|
||||
|
@ -1382,3 +1384,25 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
options2 = grpcutils.convert_options_proto(options2)
|
||||
wireless.link_config(request.node1_id, request.node2_id, options1, options2)
|
||||
return WirelessConfigResponse()
|
||||
|
||||
def GetWirelessConfig(
|
||||
self, request: GetWirelessConfigRequest, context: ServicerContext
|
||||
) -> GetWirelessConfigResponse:
|
||||
session = self.get_session(request.session_id, context)
|
||||
try:
|
||||
wireless = session.get_node(request.node_id, WirelessNode)
|
||||
configs = wireless.get_config()
|
||||
except CoreError:
|
||||
configs = {x.id: x for x in WirelessNode.options}
|
||||
config_options = {}
|
||||
for config in configs.values():
|
||||
config_option = common_pb2.ConfigOption(
|
||||
label=config.label,
|
||||
name=config.id,
|
||||
value=config.default,
|
||||
type=config.type.value,
|
||||
select=config.options,
|
||||
group=config.group,
|
||||
)
|
||||
config_options[config.id] = config_option
|
||||
return GetWirelessConfigResponse(config=config_options)
|
||||
|
|
|
@ -737,6 +737,7 @@ class Node:
|
|||
Tuple[str, Optional[int]], Dict[str, ConfigOption]
|
||||
] = field(default_factory=dict, repr=False)
|
||||
wlan_config: Dict[str, ConfigOption] = field(default_factory=dict, repr=False)
|
||||
wireless_config: Dict[str, ConfigOption] = field(default_factory=dict, repr=False)
|
||||
mobility_config: Dict[str, ConfigOption] = field(default_factory=dict, repr=False)
|
||||
service_configs: Dict[str, NodeServiceData] = field(
|
||||
default_factory=dict, repr=False
|
||||
|
@ -789,6 +790,7 @@ class Node:
|
|||
service_file_configs=service_file_configs,
|
||||
config_service_configs=config_service_configs,
|
||||
emane_model_configs=emane_configs,
|
||||
wireless_config=ConfigOption.from_dict(proto.wireless_config),
|
||||
)
|
||||
|
||||
def to_proto(self) -> core_pb2.Node:
|
||||
|
@ -840,6 +842,7 @@ class Node:
|
|||
service_configs=service_configs,
|
||||
config_service_configs=config_service_configs,
|
||||
emane_configs=emane_configs,
|
||||
wireless_config={k: v.to_proto() for k, v in self.wireless_config.items()},
|
||||
)
|
||||
|
||||
def set_wlan(self, config: Dict[str, str]) -> None:
|
||||
|
|
|
@ -44,6 +44,7 @@ class Configuration:
|
|||
label: str = None
|
||||
default: str = ""
|
||||
options: List[str] = field(default_factory=list)
|
||||
group: str = "Configuration"
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
self.label = self.label if self.label else self.id
|
||||
|
@ -78,6 +79,7 @@ class ConfigBool(Configuration):
|
|||
"""
|
||||
|
||||
type: ConfigDataTypes = ConfigDataTypes.BOOL
|
||||
value: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -87,6 +89,7 @@ class ConfigFloat(Configuration):
|
|||
"""
|
||||
|
||||
type: ConfigDataTypes = ConfigDataTypes.FLOAT
|
||||
value: float = 0.0
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -96,6 +99,7 @@ class ConfigInt(Configuration):
|
|||
"""
|
||||
|
||||
type: ConfigDataTypes = ConfigDataTypes.INT32
|
||||
value: int = 0
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -105,6 +109,7 @@ class ConfigString(Configuration):
|
|||
"""
|
||||
|
||||
type: ConfigDataTypes = ConfigDataTypes.STRING
|
||||
value: str = ""
|
||||
|
||||
|
||||
class ConfigurableOptions:
|
||||
|
|
|
@ -779,6 +779,9 @@ class CoreClient:
|
|||
)
|
||||
return config
|
||||
|
||||
def get_wireless_config(self, node_id: int) -> Dict[str, ConfigOption]:
|
||||
return self.client.get_wireless_config(self.session.id, node_id)
|
||||
|
||||
def get_mobility_config(self, node_id: int) -> Dict[str, ConfigOption]:
|
||||
config = self.client.get_mobility_config(self.session.id, node_id)
|
||||
logger.debug(
|
||||
|
|
55
daemon/core/gui/dialogs/wirelessconfig.py
Normal file
55
daemon/core/gui/dialogs/wirelessconfig.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from typing import TYPE_CHECKING, Dict, Optional
|
||||
|
||||
import grpc
|
||||
|
||||
from core.api.grpc.wrappers import ConfigOption, Node
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.themes import PADX, PADY
|
||||
from core.gui.widgets import ConfigFrame
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
from core.gui.graph.node import CanvasNode
|
||||
|
||||
|
||||
class WirelessConfigDialog(Dialog):
|
||||
def __init__(self, app: "Application", canvas_node: "CanvasNode"):
|
||||
super().__init__(app, f"Wireless Configuration - {canvas_node.core_node.name}")
|
||||
self.node: Node = canvas_node.core_node
|
||||
self.config_frame: Optional[ConfigFrame] = None
|
||||
self.config: Dict[str, ConfigOption] = {}
|
||||
try:
|
||||
config = self.node.wireless_config
|
||||
if not config:
|
||||
config = self.app.core.get_wireless_config(self.node.id)
|
||||
self.config: Dict[str, ConfigOption] = config
|
||||
self.draw()
|
||||
except grpc.RpcError as e:
|
||||
self.app.show_grpc_exception("Wireless Config Error", e)
|
||||
self.has_error: bool = True
|
||||
self.destroy()
|
||||
|
||||
def draw(self) -> None:
|
||||
self.top.columnconfigure(0, weight=1)
|
||||
self.top.rowconfigure(0, weight=1)
|
||||
self.config_frame = ConfigFrame(self.top, self.app, self.config)
|
||||
self.config_frame.draw_config()
|
||||
self.config_frame.grid(sticky=tk.NSEW, pady=PADY)
|
||||
self.draw_buttons()
|
||||
|
||||
def draw_buttons(self) -> None:
|
||||
frame = ttk.Frame(self.top)
|
||||
frame.grid(sticky=tk.EW)
|
||||
for i in range(2):
|
||||
frame.columnconfigure(i, weight=1)
|
||||
button = ttk.Button(frame, text="Apply", command=self.click_apply)
|
||||
button.grid(row=0, column=0, padx=PADX, sticky=tk.EW)
|
||||
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||
button.grid(row=0, column=1, sticky=tk.EW)
|
||||
|
||||
def click_apply(self) -> None:
|
||||
self.config_frame.parse_config()
|
||||
self.node.wireless_config = self.config
|
||||
self.destroy()
|
|
@ -16,6 +16,7 @@ from core.gui.dialogs.mobilityconfig import MobilityConfigDialog
|
|||
from core.gui.dialogs.nodeconfig import NodeConfigDialog
|
||||
from core.gui.dialogs.nodeconfigservice import NodeConfigServiceDialog
|
||||
from core.gui.dialogs.nodeservice import NodeServiceDialog
|
||||
from core.gui.dialogs.wirelessconfig import WirelessConfigDialog
|
||||
from core.gui.dialogs.wlanconfig import WlanConfigDialog
|
||||
from core.gui.frames.node import NodeInfoFrame
|
||||
from core.gui.graph import tags
|
||||
|
@ -219,6 +220,7 @@ class CanvasNode:
|
|||
# clear existing menu
|
||||
self.context.delete(0, tk.END)
|
||||
is_wlan = self.core_node.type == NodeType.WIRELESS_LAN
|
||||
is_wireless = self.core_node.type == NodeType.WIRELESS
|
||||
is_emane = self.core_node.type == NodeType.EMANE
|
||||
is_mobility = is_wlan or is_emane
|
||||
if self.app.core.is_runtime():
|
||||
|
@ -231,6 +233,10 @@ class CanvasNode:
|
|||
self.context.add_command(
|
||||
label="WLAN Config", command=self.show_wlan_config
|
||||
)
|
||||
if is_wireless:
|
||||
self.context.add_command(
|
||||
label="Wireless Config", command=self.show_wireless_config
|
||||
)
|
||||
if is_mobility and self.core_node.id in self.app.core.mobility_players:
|
||||
self.context.add_command(
|
||||
label="Mobility Player", command=self.show_mobility_player
|
||||
|
@ -268,6 +274,10 @@ class CanvasNode:
|
|||
self.context.add_command(
|
||||
label="WLAN Config", command=self.show_wlan_config
|
||||
)
|
||||
if is_wireless:
|
||||
self.context.add_command(
|
||||
label="Wireless Config", command=self.show_wireless_config
|
||||
)
|
||||
if is_mobility:
|
||||
self.context.add_command(
|
||||
label="Mobility Config", command=self.show_mobility_config
|
||||
|
@ -346,6 +356,10 @@ class CanvasNode:
|
|||
dialog = NodeConfigDialog(self.app, self)
|
||||
dialog.show()
|
||||
|
||||
def show_wireless_config(self) -> None:
|
||||
dialog = WirelessConfigDialog(self.app, self)
|
||||
dialog.show()
|
||||
|
||||
def show_wlan_config(self) -> None:
|
||||
dialog = WlanConfigDialog(self.app, self)
|
||||
if not dialog.has_error:
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
Defines a wireless node that allows programmatic link connectivity and
|
||||
configuration between pairs of nodes.
|
||||
"""
|
||||
|
||||
import copy
|
||||
import logging
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Dict, Tuple
|
||||
from typing import TYPE_CHECKING, Dict, List, Tuple
|
||||
|
||||
from core.config import ConfigBool, ConfigFloat, Configuration
|
||||
from core.emulator.data import LinkData, LinkOptions
|
||||
from core.emulator.enumerations import LinkTypes, MessageFlags
|
||||
from core.errors import CoreError
|
||||
|
@ -20,6 +21,18 @@ if TYPE_CHECKING:
|
|||
from core.emulator.distributed import DistributedServer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
CONFIG_ENABLED: bool = True
|
||||
CONFIG_RANGE: float = 400.0
|
||||
CONFIG_LOSS_RANGE: float = 300.0
|
||||
CONFIG_LOSS_FACTOR: float = 1.0
|
||||
CONFIG_DELAY_RANGE: float = 200.0
|
||||
CONFIG_DELAY_FACTOR: float = 1.0
|
||||
KEY_ENABLED: str = "movement"
|
||||
KEY_RANGE: str = "max-range"
|
||||
KEY_LOSS_RANGE: str = "loss-range"
|
||||
KEY_LOSS_FACTOR: str = "loss-factor"
|
||||
KEY_DELAY_RANGE: str = "delay-range"
|
||||
KEY_DELAY_FACTOR: str = "delay-factor"
|
||||
|
||||
|
||||
def calc_distance(
|
||||
|
@ -47,6 +60,33 @@ class WirelessLink:
|
|||
|
||||
|
||||
class WirelessNode(CoreNetworkBase):
|
||||
options: List[Configuration] = [
|
||||
ConfigBool(
|
||||
id=KEY_ENABLED,
|
||||
default="1" if CONFIG_ENABLED else "0",
|
||||
label="Movement Enabled?",
|
||||
),
|
||||
ConfigFloat(
|
||||
id=KEY_RANGE, default=str(CONFIG_RANGE), label="Max Range (pixels)"
|
||||
),
|
||||
ConfigFloat(
|
||||
id=KEY_LOSS_RANGE,
|
||||
default=str(CONFIG_LOSS_RANGE),
|
||||
label="Loss Start Range (pixels)",
|
||||
),
|
||||
ConfigFloat(
|
||||
id=KEY_LOSS_FACTOR, default=str(CONFIG_LOSS_FACTOR), label="Loss Factor"
|
||||
),
|
||||
ConfigFloat(
|
||||
id=KEY_DELAY_RANGE,
|
||||
default=str(CONFIG_DELAY_RANGE),
|
||||
label="Delay Start Range (pixels)",
|
||||
),
|
||||
ConfigFloat(
|
||||
id=KEY_DELAY_FACTOR, default=str(CONFIG_DELAY_FACTOR), label="Delay Factor"
|
||||
),
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
session: "Session",
|
||||
|
@ -57,6 +97,12 @@ class WirelessNode(CoreNetworkBase):
|
|||
super().__init__(session, _id, name, server)
|
||||
self.bridges: Dict[int, Tuple[CoreInterface, str]] = {}
|
||||
self.links: Dict[Tuple[int, int], WirelessLink] = {}
|
||||
self.position_enabled: bool = CONFIG_ENABLED
|
||||
self.max_range: float = CONFIG_RANGE
|
||||
self.loss_range: float = CONFIG_LOSS_RANGE
|
||||
self.loss_factor: float = CONFIG_LOSS_FACTOR
|
||||
self.delay_range: float = CONFIG_DELAY_RANGE
|
||||
self.delay_factor: float = CONFIG_DELAY_FACTOR
|
||||
|
||||
def startup(self) -> None:
|
||||
if self.up:
|
||||
|
@ -94,7 +140,8 @@ class WirelessNode(CoreNetworkBase):
|
|||
)
|
||||
# associate node iface with bridge
|
||||
iface.net_client.set_iface_master(bridge_name, iface.localname)
|
||||
# assign position callback
|
||||
# assign position callback, when enabled
|
||||
if self.position_enabled:
|
||||
iface.poshook = self.position_callback
|
||||
# save created bridge
|
||||
self.bridges[iface.node.id] = (iface, bridge_name)
|
||||
|
@ -226,21 +273,47 @@ class WirelessNode(CoreNetworkBase):
|
|||
for oiface, bridge_name in self.bridges.values():
|
||||
if iface == oiface:
|
||||
continue
|
||||
key = get_key(iface.node.id, oiface.node.id)
|
||||
link = self.links.get(key)
|
||||
if link.linked:
|
||||
self.calc_link(iface, oiface)
|
||||
|
||||
def calc_link(self, iface1: CoreInterface, iface2: CoreInterface) -> None:
|
||||
key = get_key(iface1.node.id, iface2.node.id)
|
||||
link = self.links.get(key)
|
||||
point1 = iface1.node.position.get()
|
||||
point2 = iface2.node.position.get()
|
||||
distance = calc_distance(point1, point2) - 250
|
||||
distance = max(distance, 0.0)
|
||||
loss = min((distance / 500) * 100.0, 100.0)
|
||||
node1_id = iface1.node.id
|
||||
node2_id = iface2.node.id
|
||||
options = LinkOptions(loss=loss, delay=0)
|
||||
self.link_config(node1_id, node2_id, options, options)
|
||||
distance = calc_distance(point1, point2)
|
||||
if distance >= self.max_range:
|
||||
if link.linked:
|
||||
self.link_control(iface1.node.id, iface2.node.id, False)
|
||||
else:
|
||||
if not link.linked:
|
||||
self.link_control(iface1.node.id, iface2.node.id, True)
|
||||
loss_distance = max(distance - self.loss_range, 0.0)
|
||||
loss = min(
|
||||
(loss_distance / self.max_range) * 100.0 * self.loss_factor, 100.0
|
||||
)
|
||||
delay_distance = max(distance - self.delay_range, 0.0)
|
||||
delay = (delay_distance / self.max_range) * 100.0 * self.delay_factor
|
||||
options = LinkOptions(loss=loss, delay=int(delay))
|
||||
self.link_config(iface1.node.id, iface2.node.id, options, options)
|
||||
|
||||
def adopt_iface(self, iface: CoreInterface, name: str) -> None:
|
||||
raise CoreError(f"{type(self)} does not support adopt interface")
|
||||
|
||||
def get_config(self) -> Dict[str, Configuration]:
|
||||
config = {x.id: x for x in copy.copy(self.options)}
|
||||
config[KEY_ENABLED].default = "1" if self.position_enabled else "0"
|
||||
config[KEY_RANGE].default = str(self.max_range)
|
||||
config[KEY_LOSS_RANGE].default = str(self.loss_range)
|
||||
config[KEY_LOSS_FACTOR].default = str(self.loss_factor)
|
||||
config[KEY_DELAY_RANGE].default = str(self.delay_range)
|
||||
config[KEY_DELAY_FACTOR].default = str(self.delay_factor)
|
||||
return config
|
||||
|
||||
def set_config(self, config: Dict[str, str]) -> None:
|
||||
logger.info("wireless config: %s", config)
|
||||
self.position_enabled = config[KEY_ENABLED] == "1"
|
||||
self.max_range = float(config[KEY_RANGE])
|
||||
self.loss_range = float(config[KEY_LOSS_RANGE])
|
||||
self.loss_factor = float(config[KEY_LOSS_FACTOR])
|
||||
self.delay_range = float(config[KEY_DELAY_RANGE])
|
||||
self.delay_factor = float(config[KEY_DELAY_FACTOR])
|
||||
|
|
|
@ -107,6 +107,8 @@ service CoreApi {
|
|||
}
|
||||
rpc WirelessConfig (WirelessConfigRequest) returns (WirelessConfigResponse) {
|
||||
}
|
||||
rpc GetWirelessConfig (GetWirelessConfigRequest) returns (GetWirelessConfigResponse) {
|
||||
}
|
||||
|
||||
// emane rpc
|
||||
rpc GetEmaneModelConfig (emane.GetEmaneModelConfigRequest) returns (emane.GetEmaneModelConfigResponse) {
|
||||
|
@ -624,6 +626,7 @@ message Node {
|
|||
map<string, services.NodeServiceConfig> service_configs = 18;
|
||||
map<string, configservices.ConfigServiceConfig> config_service_configs= 19;
|
||||
repeated emane.NodeEmaneConfig emane_configs = 20;
|
||||
map<string, common.ConfigOption> wireless_config = 21;
|
||||
}
|
||||
|
||||
message Link {
|
||||
|
@ -728,3 +731,12 @@ message WirelessConfigRequest {
|
|||
|
||||
message WirelessConfigResponse {
|
||||
}
|
||||
|
||||
message GetWirelessConfigRequest {
|
||||
int32 session_id = 1;
|
||||
int32 node_id = 2;
|
||||
}
|
||||
|
||||
message GetWirelessConfigResponse {
|
||||
map<string, common.ConfigOption> config = 1;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue