daemon/gui: added support to configure wireless network for position calculations or not

This commit is contained in:
Blake Harnden 2022-04-14 16:31:14 -07:00
parent d124820a86
commit d20cb1ef58
10 changed files with 216 additions and 15 deletions

View file

@ -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.

View file

@ -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(

View file

@ -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)

View file

@ -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:

View file

@ -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:

View file

@ -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(

View 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()

View file

@ -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:

View file

@ -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])

View file

@ -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;
}