Merge pull request #573 from coreemu/enhancement/emane-nem-process
EMANE process per nem
This commit is contained in:
commit
b508ad6406
26 changed files with 481 additions and 881 deletions
|
@ -28,10 +28,8 @@ from core.api.grpc.configservices_pb2 import (
|
||||||
from core.api.grpc.core_pb2 import ExecuteScriptRequest, GetConfigRequest
|
from core.api.grpc.core_pb2 import ExecuteScriptRequest, GetConfigRequest
|
||||||
from core.api.grpc.emane_pb2 import (
|
from core.api.grpc.emane_pb2 import (
|
||||||
EmaneLinkRequest,
|
EmaneLinkRequest,
|
||||||
GetEmaneConfigRequest,
|
|
||||||
GetEmaneEventChannelRequest,
|
GetEmaneEventChannelRequest,
|
||||||
GetEmaneModelConfigRequest,
|
GetEmaneModelConfigRequest,
|
||||||
SetEmaneConfigRequest,
|
|
||||||
SetEmaneModelConfigRequest,
|
SetEmaneModelConfigRequest,
|
||||||
)
|
)
|
||||||
from core.api.grpc.mobility_pb2 import (
|
from core.api.grpc.mobility_pb2 import (
|
||||||
|
@ -253,7 +251,6 @@ class CoreGrpcClient:
|
||||||
if asymmetric_links:
|
if asymmetric_links:
|
||||||
asymmetric_links = [x.to_proto() for x in asymmetric_links]
|
asymmetric_links = [x.to_proto() for x in asymmetric_links]
|
||||||
hooks = [x.to_proto() for x in session.hooks.values()]
|
hooks = [x.to_proto() for x in session.hooks.values()]
|
||||||
emane_config = {k: v.value for k, v in session.emane_config.items()}
|
|
||||||
emane_model_configs = []
|
emane_model_configs = []
|
||||||
mobility_configs = []
|
mobility_configs = []
|
||||||
wlan_configs = []
|
wlan_configs = []
|
||||||
|
@ -313,7 +310,6 @@ class CoreGrpcClient:
|
||||||
links=links,
|
links=links,
|
||||||
location=session.location.to_proto(),
|
location=session.location.to_proto(),
|
||||||
hooks=hooks,
|
hooks=hooks,
|
||||||
emane_config=emane_config,
|
|
||||||
emane_model_configs=emane_model_configs,
|
emane_model_configs=emane_model_configs,
|
||||||
wlan_configs=wlan_configs,
|
wlan_configs=wlan_configs,
|
||||||
mobility_configs=mobility_configs,
|
mobility_configs=mobility_configs,
|
||||||
|
@ -917,31 +913,6 @@ class CoreGrpcClient:
|
||||||
response = self.stub.SetWlanConfig(request)
|
response = self.stub.SetWlanConfig(request)
|
||||||
return response.result
|
return response.result
|
||||||
|
|
||||||
def get_emane_config(self, session_id: int) -> Dict[str, wrappers.ConfigOption]:
|
|
||||||
"""
|
|
||||||
Get session emane configuration.
|
|
||||||
|
|
||||||
:param session_id: session id
|
|
||||||
:return: response with a list of configuration groups
|
|
||||||
:raises grpc.RpcError: when session doesn't exist
|
|
||||||
"""
|
|
||||||
request = GetEmaneConfigRequest(session_id=session_id)
|
|
||||||
response = self.stub.GetEmaneConfig(request)
|
|
||||||
return wrappers.ConfigOption.from_dict(response.config)
|
|
||||||
|
|
||||||
def set_emane_config(self, session_id: int, config: Dict[str, str]) -> bool:
|
|
||||||
"""
|
|
||||||
Set session emane configuration.
|
|
||||||
|
|
||||||
:param session_id: session id
|
|
||||||
:param config: emane configuration
|
|
||||||
:return: True for success, False otherwise
|
|
||||||
:raises grpc.RpcError: when session doesn't exist
|
|
||||||
"""
|
|
||||||
request = SetEmaneConfigRequest(session_id=session_id, config=config)
|
|
||||||
response = self.stub.SetEmaneConfig(request)
|
|
||||||
return response.result
|
|
||||||
|
|
||||||
def get_emane_model_config(
|
def get_emane_model_config(
|
||||||
self, session_id: int, node_id: int, model: str, iface_id: int = -1
|
self, session_id: int, node_id: int, model: str, iface_id: int = -1
|
||||||
) -> Dict[str, wrappers.ConfigOption]:
|
) -> Dict[str, wrappers.ConfigOption]:
|
||||||
|
@ -1063,15 +1034,18 @@ class CoreGrpcClient:
|
||||||
response = self.stub.GetNodeConfigService(request)
|
response = self.stub.GetNodeConfigService(request)
|
||||||
return dict(response.config)
|
return dict(response.config)
|
||||||
|
|
||||||
def get_emane_event_channel(self, session_id: int) -> wrappers.EmaneEventChannel:
|
def get_emane_event_channel(
|
||||||
|
self, session_id: int, nem_id: int
|
||||||
|
) -> wrappers.EmaneEventChannel:
|
||||||
"""
|
"""
|
||||||
Retrieves the current emane event channel being used for a session.
|
Retrieves the current emane event channel being used for a session.
|
||||||
|
|
||||||
:param session_id: session to get emane event channel for
|
:param session_id: session to get emane event channel for
|
||||||
|
:param nem_id: nem id for the desired event channel
|
||||||
:return: emane event channel
|
:return: emane event channel
|
||||||
:raises grpc.RpcError: when session doesn't exist
|
:raises grpc.RpcError: when session doesn't exist
|
||||||
"""
|
"""
|
||||||
request = GetEmaneEventChannelRequest(session_id=session_id)
|
request = GetEmaneEventChannelRequest(session_id=session_id, nem_id=nem_id)
|
||||||
response = self.stub.GetEmaneEventChannel(request)
|
response = self.stub.GetEmaneEventChannel(request)
|
||||||
return wrappers.EmaneEventChannel.from_proto(response)
|
return wrappers.EmaneEventChannel.from_proto(response)
|
||||||
|
|
||||||
|
|
|
@ -630,10 +630,6 @@ def get_node_config_service_configs(session: Session) -> List[ConfigServiceConfi
|
||||||
return configs
|
return configs
|
||||||
|
|
||||||
|
|
||||||
def get_emane_config(session: Session) -> Dict[str, common_pb2.ConfigOption]:
|
|
||||||
return get_config_options(session.emane.config, session.emane.emane_config)
|
|
||||||
|
|
||||||
|
|
||||||
def get_mobility_node(
|
def get_mobility_node(
|
||||||
session: Session, node_id: int, context: ServicerContext
|
session: Session, node_id: int, context: ServicerContext
|
||||||
) -> Union[WlanNode, EmaneNet]:
|
) -> Union[WlanNode, EmaneNet]:
|
||||||
|
@ -663,7 +659,6 @@ def convert_session(session: Session) -> wrappers.Session:
|
||||||
x=x, y=y, z=z, lat=lat, lon=lon, alt=alt, scale=session.location.refscale
|
x=x, y=y, z=z, lat=lat, lon=lon, alt=alt, scale=session.location.refscale
|
||||||
)
|
)
|
||||||
hooks = get_hooks(session)
|
hooks = get_hooks(session)
|
||||||
emane_config = get_emane_config(session)
|
|
||||||
emane_model_configs = get_emane_model_configs(session)
|
emane_model_configs = get_emane_model_configs(session)
|
||||||
wlan_configs = get_wlan_configs(session)
|
wlan_configs = get_wlan_configs(session)
|
||||||
mobility_configs = get_mobility_configs(session)
|
mobility_configs = get_mobility_configs(session)
|
||||||
|
@ -685,7 +680,6 @@ def convert_session(session: Session) -> wrappers.Session:
|
||||||
default_services=default_services,
|
default_services=default_services,
|
||||||
location=location,
|
location=location,
|
||||||
hooks=hooks,
|
hooks=hooks,
|
||||||
emane_config=emane_config,
|
|
||||||
emane_model_configs=emane_model_configs,
|
emane_model_configs=emane_model_configs,
|
||||||
wlan_configs=wlan_configs,
|
wlan_configs=wlan_configs,
|
||||||
service_configs=service_configs,
|
service_configs=service_configs,
|
||||||
|
|
|
@ -33,14 +33,10 @@ from core.api.grpc.emane_pb2 import (
|
||||||
EmaneLinkResponse,
|
EmaneLinkResponse,
|
||||||
EmanePathlossesRequest,
|
EmanePathlossesRequest,
|
||||||
EmanePathlossesResponse,
|
EmanePathlossesResponse,
|
||||||
GetEmaneConfigRequest,
|
|
||||||
GetEmaneConfigResponse,
|
|
||||||
GetEmaneEventChannelRequest,
|
GetEmaneEventChannelRequest,
|
||||||
GetEmaneEventChannelResponse,
|
GetEmaneEventChannelResponse,
|
||||||
GetEmaneModelConfigRequest,
|
GetEmaneModelConfigRequest,
|
||||||
GetEmaneModelConfigResponse,
|
GetEmaneModelConfigResponse,
|
||||||
SetEmaneConfigRequest,
|
|
||||||
SetEmaneConfigResponse,
|
|
||||||
SetEmaneModelConfigRequest,
|
SetEmaneModelConfigRequest,
|
||||||
SetEmaneModelConfigResponse,
|
SetEmaneModelConfigResponse,
|
||||||
)
|
)
|
||||||
|
@ -266,7 +262,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
return core_pb2.StartSessionResponse(result=False, exceptions=exceptions)
|
return core_pb2.StartSessionResponse(result=False, exceptions=exceptions)
|
||||||
|
|
||||||
# emane configs
|
# emane configs
|
||||||
session.emane.config.update(request.emane_config)
|
|
||||||
for config in request.emane_model_configs:
|
for config in request.emane_model_configs:
|
||||||
_id = utils.iface_config_id(config.node_id, config.iface_id)
|
_id = utils.iface_config_id(config.node_id, config.iface_id)
|
||||||
session.emane.set_config(_id, config.model, config.config)
|
session.emane.set_config(_id, config.model, config.config)
|
||||||
|
@ -1045,36 +1040,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
node.updatemodel(config)
|
node.updatemodel(config)
|
||||||
return SetWlanConfigResponse(result=True)
|
return SetWlanConfigResponse(result=True)
|
||||||
|
|
||||||
def GetEmaneConfig(
|
|
||||||
self, request: GetEmaneConfigRequest, context: ServicerContext
|
|
||||||
) -> GetEmaneConfigResponse:
|
|
||||||
"""
|
|
||||||
Retrieve EMANE configuration of a session
|
|
||||||
|
|
||||||
:param request: get-EMANE-configuration request
|
|
||||||
:param context: context object
|
|
||||||
:return: get-EMANE-configuration response
|
|
||||||
"""
|
|
||||||
logger.debug("get emane config: %s", request)
|
|
||||||
session = self.get_session(request.session_id, context)
|
|
||||||
config = grpcutils.get_emane_config(session)
|
|
||||||
return GetEmaneConfigResponse(config=config)
|
|
||||||
|
|
||||||
def SetEmaneConfig(
|
|
||||||
self, request: SetEmaneConfigRequest, context: ServicerContext
|
|
||||||
) -> SetEmaneConfigResponse:
|
|
||||||
"""
|
|
||||||
Set EMANE configuration of a session
|
|
||||||
|
|
||||||
:param request: set-EMANE-configuration request
|
|
||||||
:param context: context object
|
|
||||||
:return: set-EMANE-configuration response
|
|
||||||
"""
|
|
||||||
logger.debug("set emane config: %s", request)
|
|
||||||
session = self.get_session(request.session_id, context)
|
|
||||||
session.emane.config.update(request.config)
|
|
||||||
return SetEmaneConfigResponse(result=True)
|
|
||||||
|
|
||||||
def GetEmaneModelConfig(
|
def GetEmaneModelConfig(
|
||||||
self, request: GetEmaneModelConfigRequest, context: ServicerContext
|
self, request: GetEmaneModelConfigRequest, context: ServicerContext
|
||||||
) -> GetEmaneModelConfigResponse:
|
) -> GetEmaneModelConfigResponse:
|
||||||
|
@ -1276,12 +1241,12 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
self, request: GetEmaneEventChannelRequest, context: ServicerContext
|
self, request: GetEmaneEventChannelRequest, context: ServicerContext
|
||||||
) -> GetEmaneEventChannelResponse:
|
) -> GetEmaneEventChannelResponse:
|
||||||
session = self.get_session(request.session_id, context)
|
session = self.get_session(request.session_id, context)
|
||||||
group = None
|
service = session.emane.nem_service.get(request.nem_id)
|
||||||
port = None
|
if not service:
|
||||||
device = None
|
context.abort(grpc.StatusCode.NOT_FOUND, f"unknown nem id {request.nem_id}")
|
||||||
if session.emane.eventchannel:
|
return GetEmaneEventChannelResponse(
|
||||||
group, port, device = session.emane.eventchannel
|
group=service.group, port=service.port, device=service.device
|
||||||
return GetEmaneEventChannelResponse(group=group, port=port, device=device)
|
)
|
||||||
|
|
||||||
def ExecuteScript(self, request, context):
|
def ExecuteScript(self, request, context):
|
||||||
existing_sessions = set(self.coreemu.sessions.keys())
|
existing_sessions = set(self.coreemu.sessions.keys())
|
||||||
|
|
|
@ -783,7 +783,6 @@ class Session:
|
||||||
x=0.0, y=0.0, z=0.0, lat=47.57917, lon=-122.13232, alt=2.0, scale=150.0
|
x=0.0, y=0.0, z=0.0, lat=47.57917, lon=-122.13232, alt=2.0, scale=150.0
|
||||||
)
|
)
|
||||||
hooks: Dict[str, Hook] = field(default_factory=dict)
|
hooks: Dict[str, Hook] = field(default_factory=dict)
|
||||||
emane_config: Dict[str, ConfigOption] = field(default_factory=dict)
|
|
||||||
metadata: Dict[str, str] = field(default_factory=dict)
|
metadata: Dict[str, str] = field(default_factory=dict)
|
||||||
file: Path = None
|
file: Path = None
|
||||||
options: Dict[str, ConfigOption] = field(default_factory=dict)
|
options: Dict[str, ConfigOption] = field(default_factory=dict)
|
||||||
|
@ -836,7 +835,6 @@ class Session:
|
||||||
default_services=default_services,
|
default_services=default_services,
|
||||||
location=SessionLocation.from_proto(proto.location),
|
location=SessionLocation.from_proto(proto.location),
|
||||||
hooks=hooks,
|
hooks=hooks,
|
||||||
emane_config=ConfigOption.from_dict(proto.emane_config),
|
|
||||||
metadata=dict(proto.metadata),
|
metadata=dict(proto.metadata),
|
||||||
file=file_path,
|
file=file_path,
|
||||||
options=options,
|
options=options,
|
||||||
|
@ -889,11 +887,6 @@ class Session:
|
||||||
self.links.append(link)
|
self.links.append(link)
|
||||||
return link
|
return link
|
||||||
|
|
||||||
def set_emane(self, config: Dict[str, str]) -> None:
|
|
||||||
for key, value in config.items():
|
|
||||||
option = ConfigOption(name=key, value=value)
|
|
||||||
self.emane_config[key] = option
|
|
||||||
|
|
||||||
def set_options(self, config: Dict[str, str]) -> None:
|
def set_options(self, config: Dict[str, str]) -> None:
|
||||||
for key, value in config.items():
|
for key, value in config.items():
|
||||||
option = ConfigOption(name=key, value=value)
|
option = ConfigOption(name=key, value=value)
|
||||||
|
|
|
@ -1046,8 +1046,6 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
||||||
self.handle_config_mobility(message_type, config_data)
|
self.handle_config_mobility(message_type, config_data)
|
||||||
elif config_data.object in self.session.mobility.models:
|
elif config_data.object in self.session.mobility.models:
|
||||||
replies = self.handle_config_mobility_models(message_type, config_data)
|
replies = self.handle_config_mobility_models(message_type, config_data)
|
||||||
elif config_data.object == self.session.emane.name:
|
|
||||||
replies = self.handle_config_emane(message_type, config_data)
|
|
||||||
elif config_data.object in EmaneModelManager.models:
|
elif config_data.object in EmaneModelManager.models:
|
||||||
replies = self.handle_config_emane_models(message_type, config_data)
|
replies = self.handle_config_emane_models(message_type, config_data)
|
||||||
else:
|
else:
|
||||||
|
@ -1379,36 +1377,6 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
||||||
|
|
||||||
return replies
|
return replies
|
||||||
|
|
||||||
def handle_config_emane(self, message_type, config_data):
|
|
||||||
replies = []
|
|
||||||
node_id = config_data.node
|
|
||||||
object_name = config_data.object
|
|
||||||
iface_id = config_data.iface_id
|
|
||||||
values_str = config_data.data_values
|
|
||||||
|
|
||||||
node_id = utils.iface_config_id(node_id, iface_id)
|
|
||||||
logger.debug(
|
|
||||||
"received configure message for %s nodenum: %s", object_name, node_id
|
|
||||||
)
|
|
||||||
if message_type == ConfigFlags.REQUEST:
|
|
||||||
logger.info("replying to configure request for %s model", object_name)
|
|
||||||
typeflags = ConfigFlags.NONE.value
|
|
||||||
config = self.session.emane.config
|
|
||||||
config_response = ConfigShim.config_data(
|
|
||||||
0, node_id, typeflags, self.session.emane.emane_config, config
|
|
||||||
)
|
|
||||||
replies.append(config_response)
|
|
||||||
elif message_type != ConfigFlags.RESET:
|
|
||||||
if not object_name:
|
|
||||||
logger.info("no configuration object for node %s", node_id)
|
|
||||||
return []
|
|
||||||
|
|
||||||
if values_str:
|
|
||||||
config = ConfigShim.str_to_dict(values_str)
|
|
||||||
self.session.emane.config = config
|
|
||||||
|
|
||||||
return replies
|
|
||||||
|
|
||||||
def handle_config_emane_models(self, message_type, config_data):
|
def handle_config_emane_models(self, message_type, config_data):
|
||||||
replies = []
|
replies = []
|
||||||
node_id = config_data.node
|
node_id = config_data.node
|
||||||
|
@ -1851,14 +1819,6 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
||||||
)
|
)
|
||||||
self.session.broadcast_config(config_data)
|
self.session.broadcast_config(config_data)
|
||||||
|
|
||||||
# send global emane config
|
|
||||||
config = self.session.emane.config
|
|
||||||
logger.debug("global emane config: values(%s)", config)
|
|
||||||
config_data = ConfigShim.config_data(
|
|
||||||
0, None, ConfigFlags.UPDATE.value, self.session.emane.emane_config, config
|
|
||||||
)
|
|
||||||
self.session.broadcast_config(config_data)
|
|
||||||
|
|
||||||
# send emane model configs
|
# send emane model configs
|
||||||
for node_id, model_configs in self.session.emane.node_configs.items():
|
for node_id, model_configs in self.session.emane.node_configs.items():
|
||||||
for model_name, config in model_configs.items():
|
for model_name, config in model_configs.items():
|
||||||
|
|
|
@ -227,12 +227,6 @@ class Ospfv3mdr(Ospfv3):
|
||||||
|
|
||||||
name: str = "OSPFv3MDR"
|
name: str = "OSPFv3MDR"
|
||||||
|
|
||||||
def data(self) -> Dict[str, Any]:
|
|
||||||
for iface in self.node.get_ifaces():
|
|
||||||
is_wireless = isinstance(iface.net, (WlanNode, EmaneNet))
|
|
||||||
logger.info("MDR wireless: %s", is_wireless)
|
|
||||||
return dict()
|
|
||||||
|
|
||||||
def quagga_iface_config(self, iface: CoreInterface) -> str:
|
def quagga_iface_config(self, iface: CoreInterface) -> str:
|
||||||
config = super().quagga_iface_config(iface)
|
config = super().quagga_iface_config(iface)
|
||||||
if isinstance(iface.net, (WlanNode, EmaneNet)):
|
if isinstance(iface.net, (WlanNode, EmaneNet)):
|
||||||
|
|
|
@ -1,32 +1,22 @@
|
||||||
"""
|
"""
|
||||||
emane.py: definition of an Emane class for implementing configuration control of an EMANE emulation.
|
Implements configuration and control of an EMANE emulation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
from collections import OrderedDict
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from pathlib import Path
|
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type, Union
|
||||||
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type
|
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
from core.config import ConfigGroup, Configuration
|
|
||||||
from core.emane import emanemanifest
|
|
||||||
from core.emane.emanemodel import EmaneModel
|
from core.emane.emanemodel import EmaneModel
|
||||||
from core.emane.linkmonitor import EmaneLinkMonitor
|
from core.emane.linkmonitor import EmaneLinkMonitor
|
||||||
from core.emane.modelmanager import EmaneModelManager
|
from core.emane.modelmanager import EmaneModelManager
|
||||||
from core.emane.nodes import EmaneNet
|
from core.emane.nodes import EmaneNet
|
||||||
from core.emulator.data import LinkData
|
from core.emulator.data import LinkData
|
||||||
from core.emulator.enumerations import (
|
from core.emulator.enumerations import LinkTypes, MessageFlags, RegisterTlvs
|
||||||
ConfigDataTypes,
|
|
||||||
LinkTypes,
|
|
||||||
MessageFlags,
|
|
||||||
RegisterTlvs,
|
|
||||||
)
|
|
||||||
from core.errors import CoreCommandError, CoreError
|
from core.errors import CoreCommandError, CoreError
|
||||||
from core.nodes.base import CoreNetworkBase, CoreNode, CoreNodeBase, NodeBase
|
from core.nodes.base import CoreNetworkBase, CoreNode, NodeBase
|
||||||
from core.nodes.interface import CoreInterface, TunTap
|
from core.nodes.interface import CoreInterface, TunTap
|
||||||
from core.xml import emanexml
|
from core.xml import emanexml
|
||||||
|
|
||||||
|
@ -36,15 +26,19 @@ if TYPE_CHECKING:
|
||||||
from core.emulator.session import Session
|
from core.emulator.session import Session
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from emane.events import EventService, PathlossEvent
|
from emane.events import EventService, PathlossEvent, CommEffectEvent, LocationEvent
|
||||||
from emane.events import LocationEvent
|
|
||||||
from emane.events.eventserviceexception import EventServiceException
|
from emane.events.eventserviceexception import EventServiceException
|
||||||
except ImportError:
|
except ImportError:
|
||||||
try:
|
try:
|
||||||
from emanesh.events import EventService
|
from emanesh.events import (
|
||||||
from emanesh.events import LocationEvent
|
EventService,
|
||||||
|
PathlossEvent,
|
||||||
|
CommEffectEvent,
|
||||||
|
LocationEvent,
|
||||||
|
)
|
||||||
from emanesh.events.eventserviceexception import EventServiceException
|
from emanesh.events.eventserviceexception import EventServiceException
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
CommEffectEvent = None
|
||||||
EventService = None
|
EventService = None
|
||||||
LocationEvent = None
|
LocationEvent = None
|
||||||
PathlossEvent = None
|
PathlossEvent = None
|
||||||
|
@ -62,10 +56,57 @@ class EmaneState(Enum):
|
||||||
NOT_READY = 2
|
NOT_READY = 2
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
class EmaneEventService:
|
||||||
class StartData:
|
def __init__(
|
||||||
node: CoreNodeBase
|
self, manager: "EmaneManager", device: str, group: str, port: int
|
||||||
ifaces: List[CoreInterface] = field(default_factory=list)
|
) -> None:
|
||||||
|
self.manager: "EmaneManager" = manager
|
||||||
|
self.device: str = device
|
||||||
|
self.group: str = group
|
||||||
|
self.port: int = port
|
||||||
|
self.running: bool = False
|
||||||
|
self.thread: Optional[threading.Thread] = None
|
||||||
|
logger.info("starting emane event service %s %s:%s", device, group, port)
|
||||||
|
self.events: EventService = EventService(
|
||||||
|
eventchannel=(group, port, device), otachannel=None
|
||||||
|
)
|
||||||
|
|
||||||
|
def start(self) -> None:
|
||||||
|
self.running = True
|
||||||
|
self.thread = threading.Thread(target=self.run, daemon=True)
|
||||||
|
self.thread.start()
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
"""
|
||||||
|
Run and monitor events.
|
||||||
|
"""
|
||||||
|
logger.info("subscribing to emane location events")
|
||||||
|
while self.running:
|
||||||
|
_uuid, _seq, events = self.events.nextEvent()
|
||||||
|
# this occurs with 0.9.1 event service
|
||||||
|
if not self.running:
|
||||||
|
break
|
||||||
|
for event in events:
|
||||||
|
nem, eid, data = event
|
||||||
|
if eid == LocationEvent.IDENTIFIER:
|
||||||
|
self.manager.handlelocationevent(nem, eid, data)
|
||||||
|
logger.info("unsubscribing from emane location events")
|
||||||
|
|
||||||
|
def stop(self) -> None:
|
||||||
|
"""
|
||||||
|
Stop service and monitoring events.
|
||||||
|
"""
|
||||||
|
self.events.breakloop()
|
||||||
|
self.running = False
|
||||||
|
if self.thread:
|
||||||
|
self.thread.join()
|
||||||
|
self.thread = None
|
||||||
|
for fd in self.events._readFd, self.events._writeFd:
|
||||||
|
if fd >= 0:
|
||||||
|
os.close(fd)
|
||||||
|
for f in self.events._socket, self.events._socketOTA:
|
||||||
|
if f:
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
class EmaneManager:
|
class EmaneManager:
|
||||||
|
@ -102,22 +143,22 @@ class EmaneManager:
|
||||||
self.eventmonthread: Optional[threading.Thread] = None
|
self.eventmonthread: Optional[threading.Thread] = None
|
||||||
|
|
||||||
# model for global EMANE configuration options
|
# model for global EMANE configuration options
|
||||||
self.emane_config: EmaneGlobalModel = EmaneGlobalModel(session)
|
|
||||||
self.config: Dict[str, str] = self.emane_config.default_values()
|
|
||||||
self.node_configs: Dict[int, Dict[str, Dict[str, str]]] = {}
|
self.node_configs: Dict[int, Dict[str, Dict[str, str]]] = {}
|
||||||
self.node_models: Dict[int, str] = {}
|
self.node_models: Dict[int, str] = {}
|
||||||
|
|
||||||
# link monitor
|
# link monitor
|
||||||
self.link_monitor: EmaneLinkMonitor = EmaneLinkMonitor(self)
|
self.link_monitor: EmaneLinkMonitor = EmaneLinkMonitor(self)
|
||||||
|
# emane event monitoring
|
||||||
|
self.services: Dict[str, EmaneEventService] = {}
|
||||||
|
self.nem_service: Dict[int, EmaneEventService] = {}
|
||||||
|
|
||||||
self.service: Optional[EventService] = None
|
def next_nem_id(self, iface: CoreInterface) -> int:
|
||||||
self.eventchannel: Optional[Tuple[str, int, str]] = None
|
nem_id = self.session.options.get_config_int("nem_id_start")
|
||||||
self.event_device: Optional[str] = None
|
|
||||||
|
|
||||||
def next_nem_id(self) -> int:
|
|
||||||
nem_id = int(self.config["nem_id_start"])
|
|
||||||
while nem_id in self.nems_to_ifaces:
|
while nem_id in self.nems_to_ifaces:
|
||||||
nem_id += 1
|
nem_id += 1
|
||||||
|
self.nems_to_ifaces[nem_id] = iface
|
||||||
|
self.ifaces_to_nems[iface] = nem_id
|
||||||
|
self.write_nem(iface, nem_id)
|
||||||
return nem_id
|
return nem_id
|
||||||
|
|
||||||
def get_config(
|
def get_config(
|
||||||
|
@ -203,60 +244,12 @@ class EmaneManager:
|
||||||
|
|
||||||
def config_reset(self, node_id: int = None) -> None:
|
def config_reset(self, node_id: int = None) -> None:
|
||||||
if node_id is None:
|
if node_id is None:
|
||||||
self.config = self.emane_config.default_values()
|
|
||||||
self.node_configs.clear()
|
self.node_configs.clear()
|
||||||
self.node_models.clear()
|
self.node_models.clear()
|
||||||
else:
|
else:
|
||||||
self.node_configs.get(node_id, {}).clear()
|
self.node_configs.get(node_id, {}).clear()
|
||||||
self.node_models.pop(node_id, None)
|
self.node_models.pop(node_id, None)
|
||||||
|
|
||||||
def deleteeventservice(self) -> None:
|
|
||||||
if self.service:
|
|
||||||
for fd in self.service._readFd, self.service._writeFd:
|
|
||||||
if fd >= 0:
|
|
||||||
os.close(fd)
|
|
||||||
for f in self.service._socket, self.service._socketOTA:
|
|
||||||
if f:
|
|
||||||
f.close()
|
|
||||||
self.service = None
|
|
||||||
self.event_device = None
|
|
||||||
|
|
||||||
def initeventservice(self, filename: str = None, shutdown: bool = False) -> None:
|
|
||||||
"""
|
|
||||||
Re-initialize the EMANE Event service.
|
|
||||||
The multicast group and/or port may be configured.
|
|
||||||
"""
|
|
||||||
self.deleteeventservice()
|
|
||||||
if shutdown:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get the control network to be used for events
|
|
||||||
group, port = self.config["eventservicegroup"].split(":")
|
|
||||||
self.event_device = self.config["eventservicedevice"]
|
|
||||||
eventnetidx = self.session.get_control_net_index(self.event_device)
|
|
||||||
if eventnetidx < 0:
|
|
||||||
logger.error(
|
|
||||||
"invalid emane event service device provided: %s", self.event_device
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
# make sure the event control network is in place
|
|
||||||
eventnet = self.session.add_remove_control_net(
|
|
||||||
net_index=eventnetidx, remove=False, conf_required=False
|
|
||||||
)
|
|
||||||
if eventnet is not None:
|
|
||||||
# direct EMANE events towards control net bridge
|
|
||||||
self.event_device = eventnet.brname
|
|
||||||
self.eventchannel = (group, int(port), self.event_device)
|
|
||||||
|
|
||||||
# disabled otachannel for event service
|
|
||||||
# only needed for e.g. antennaprofile events xmit by models
|
|
||||||
logger.info("using %s for event service traffic", self.event_device)
|
|
||||||
try:
|
|
||||||
self.service = EventService(eventchannel=self.eventchannel, otachannel=None)
|
|
||||||
except EventServiceException:
|
|
||||||
logger.exception("error instantiating emane EventService")
|
|
||||||
|
|
||||||
def add_node(self, emane_net: EmaneNet) -> None:
|
def add_node(self, emane_net: EmaneNet) -> None:
|
||||||
"""
|
"""
|
||||||
Add EMANE network object to this manager.
|
Add EMANE network object to this manager.
|
||||||
|
@ -301,41 +294,9 @@ class EmaneManager:
|
||||||
if not self._emane_nets:
|
if not self._emane_nets:
|
||||||
logger.debug("no emane nodes in session")
|
logger.debug("no emane nodes in session")
|
||||||
return EmaneState.NOT_NEEDED
|
return EmaneState.NOT_NEEDED
|
||||||
|
|
||||||
# check if bindings were installed
|
# check if bindings were installed
|
||||||
if EventService is None:
|
if EventService is None:
|
||||||
raise CoreError("EMANE python bindings are not installed")
|
raise CoreError("EMANE python bindings are not installed")
|
||||||
|
|
||||||
# control network bridge required for EMANE 0.9.2
|
|
||||||
# - needs to exist when eventservice binds to it (initeventservice)
|
|
||||||
otadev = self.config["otamanagerdevice"]
|
|
||||||
netidx = self.session.get_control_net_index(otadev)
|
|
||||||
logger.debug("emane ota manager device: index(%s) otadev(%s)", netidx, otadev)
|
|
||||||
if netidx < 0:
|
|
||||||
logger.error(
|
|
||||||
"EMANE cannot start, check core config. invalid OTA device provided: %s",
|
|
||||||
otadev,
|
|
||||||
)
|
|
||||||
return EmaneState.NOT_READY
|
|
||||||
|
|
||||||
self.session.add_remove_control_net(
|
|
||||||
net_index=netidx, remove=False, conf_required=False
|
|
||||||
)
|
|
||||||
eventdev = self.config["eventservicedevice"]
|
|
||||||
logger.debug("emane event service device: eventdev(%s)", eventdev)
|
|
||||||
if eventdev != otadev:
|
|
||||||
netidx = self.session.get_control_net_index(eventdev)
|
|
||||||
logger.debug("emane event service device index: %s", netidx)
|
|
||||||
if netidx < 0:
|
|
||||||
logger.error(
|
|
||||||
"emane cannot start due to invalid event service device: %s",
|
|
||||||
eventdev,
|
|
||||||
)
|
|
||||||
return EmaneState.NOT_READY
|
|
||||||
|
|
||||||
self.session.add_remove_control_net(
|
|
||||||
net_index=netidx, remove=False, conf_required=False
|
|
||||||
)
|
|
||||||
self.check_node_models()
|
self.check_node_models()
|
||||||
return EmaneState.SUCCESS
|
return EmaneState.SUCCESS
|
||||||
|
|
||||||
|
@ -351,21 +312,35 @@ class EmaneManager:
|
||||||
status = self.setup()
|
status = self.setup()
|
||||||
if status != EmaneState.SUCCESS:
|
if status != EmaneState.SUCCESS:
|
||||||
return status
|
return status
|
||||||
self.starteventmonitor()
|
self.startup_nodes()
|
||||||
self.buildeventservicexml()
|
|
||||||
with self._emane_node_lock:
|
|
||||||
logger.info("emane building xmls...")
|
|
||||||
start_data = self.get_start_data()
|
|
||||||
for data in start_data:
|
|
||||||
self.start_node(data)
|
|
||||||
if self.links_enabled():
|
if self.links_enabled():
|
||||||
self.link_monitor.start()
|
self.link_monitor.start()
|
||||||
return EmaneState.SUCCESS
|
return EmaneState.SUCCESS
|
||||||
|
|
||||||
def get_start_data(self) -> List[StartData]:
|
def startup_nodes(self) -> None:
|
||||||
node_map = {}
|
with self._emane_node_lock:
|
||||||
for node_id in sorted(self._emane_nets):
|
logger.info("emane building xmls...")
|
||||||
emane_net = self._emane_nets[node_id]
|
for emane_net, iface in self.get_ifaces():
|
||||||
|
self.start_iface(emane_net, iface)
|
||||||
|
|
||||||
|
def start_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None:
|
||||||
|
nem_id = self.next_nem_id(iface)
|
||||||
|
nem_port = self.get_nem_port(iface)
|
||||||
|
logger.info(
|
||||||
|
"starting emane for node(%s) iface(%s) nem(%s)",
|
||||||
|
iface.node.name,
|
||||||
|
iface.name,
|
||||||
|
nem_id,
|
||||||
|
)
|
||||||
|
config = self.get_iface_config(emane_net, iface)
|
||||||
|
self.setup_control_channels(nem_id, iface, config)
|
||||||
|
emanexml.build_platform_xml(nem_id, nem_port, emane_net, iface, config)
|
||||||
|
self.start_daemon(iface)
|
||||||
|
self.install_iface(iface, config)
|
||||||
|
|
||||||
|
def get_ifaces(self) -> List[Tuple[EmaneNet, CoreInterface]]:
|
||||||
|
ifaces = []
|
||||||
|
for emane_net in self._emane_nets.values():
|
||||||
if not emane_net.model:
|
if not emane_net.model:
|
||||||
logger.error("emane net(%s) has no model", emane_net.name)
|
logger.error("emane net(%s) has no model", emane_net.name)
|
||||||
continue
|
continue
|
||||||
|
@ -377,27 +352,60 @@ class EmaneManager:
|
||||||
iface.name,
|
iface.name,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
start_node = node_map.setdefault(iface.node, StartData(iface.node))
|
ifaces.append((emane_net, iface))
|
||||||
start_node.ifaces.append(iface)
|
return sorted(ifaces, key=lambda x: (x[1].node.id, x[1].node_id))
|
||||||
start_nodes = sorted(node_map.values(), key=lambda x: x.node.id)
|
|
||||||
for start_node in start_nodes:
|
|
||||||
start_node.ifaces = sorted(start_node.ifaces, key=lambda x: x.node_id)
|
|
||||||
return start_nodes
|
|
||||||
|
|
||||||
def start_node(self, data: StartData) -> None:
|
def setup_control_channels(
|
||||||
control_net = self.session.add_remove_control_net(
|
self, nem_id: int, iface: CoreInterface, config: Dict[str, str]
|
||||||
0, remove=False, conf_required=False
|
) -> None:
|
||||||
|
node = iface.node
|
||||||
|
# setup ota device
|
||||||
|
otagroup, _otaport = config["otamanagergroup"].split(":")
|
||||||
|
otadev = config["otamanagerdevice"]
|
||||||
|
ota_index = self.session.get_control_net_index(otadev)
|
||||||
|
self.session.add_remove_control_net(ota_index, conf_required=False)
|
||||||
|
if isinstance(node, CoreNode):
|
||||||
|
self.session.add_remove_control_iface(node, ota_index, conf_required=False)
|
||||||
|
# setup event device
|
||||||
|
eventgroup, eventport = config["eventservicegroup"].split(":")
|
||||||
|
eventdev = config["eventservicedevice"]
|
||||||
|
event_index = self.session.get_control_net_index(eventdev)
|
||||||
|
event_net = self.session.add_remove_control_net(
|
||||||
|
event_index, conf_required=False
|
||||||
)
|
)
|
||||||
emanexml.build_platform_xml(self, control_net, data)
|
if isinstance(node, CoreNode):
|
||||||
self.start_daemon(data.node)
|
self.session.add_remove_control_iface(
|
||||||
for iface in data.ifaces:
|
node, event_index, conf_required=False
|
||||||
self.install_iface(iface)
|
)
|
||||||
|
# initialize emane event services
|
||||||
def set_nem(self, nem_id: int, iface: CoreInterface) -> None:
|
service = self.services.get(event_net.brname)
|
||||||
if nem_id in self.nems_to_ifaces:
|
if not service:
|
||||||
raise CoreError(f"adding duplicate nem: {nem_id}")
|
try:
|
||||||
self.nems_to_ifaces[nem_id] = iface
|
service = EmaneEventService(
|
||||||
self.ifaces_to_nems[iface] = nem_id
|
self, event_net.brname, eventgroup, int(eventport)
|
||||||
|
)
|
||||||
|
self.services[event_net.brname] = service
|
||||||
|
self.nem_service[nem_id] = service
|
||||||
|
except EventServiceException:
|
||||||
|
raise CoreError(
|
||||||
|
"failed to start emane event services "
|
||||||
|
f"{event_net.brname} {eventgroup}:{eventport}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.nem_service[nem_id] = service
|
||||||
|
# setup multicast routes as needed
|
||||||
|
logger.info(
|
||||||
|
"node(%s) interface(%s) ota(%s:%s) event(%s:%s)",
|
||||||
|
node.name,
|
||||||
|
iface.name,
|
||||||
|
otagroup,
|
||||||
|
otadev,
|
||||||
|
eventgroup,
|
||||||
|
eventdev,
|
||||||
|
)
|
||||||
|
node.node_net_client.create_route(otagroup, otadev)
|
||||||
|
if eventgroup != otagroup:
|
||||||
|
node.node_net_client.create_route(eventgroup, eventdev)
|
||||||
|
|
||||||
def get_iface(self, nem_id: int) -> Optional[CoreInterface]:
|
def get_iface(self, nem_id: int) -> Optional[CoreInterface]:
|
||||||
return self.nems_to_ifaces.get(nem_id)
|
return self.nems_to_ifaces.get(nem_id)
|
||||||
|
@ -405,6 +413,68 @@ class EmaneManager:
|
||||||
def get_nem_id(self, iface: CoreInterface) -> Optional[int]:
|
def get_nem_id(self, iface: CoreInterface) -> Optional[int]:
|
||||||
return self.ifaces_to_nems.get(iface)
|
return self.ifaces_to_nems.get(iface)
|
||||||
|
|
||||||
|
def get_nem_port(self, iface: CoreInterface) -> int:
|
||||||
|
nem_id = self.get_nem_id(iface)
|
||||||
|
return int(f"47{nem_id:03}")
|
||||||
|
|
||||||
|
def get_nem_position(
|
||||||
|
self, iface: CoreInterface
|
||||||
|
) -> Optional[Tuple[int, float, float, int]]:
|
||||||
|
"""
|
||||||
|
Retrieves nem position for a given interface.
|
||||||
|
|
||||||
|
:param iface: interface to get nem emane position for
|
||||||
|
:return: nem position tuple, None otherwise
|
||||||
|
"""
|
||||||
|
nem_id = self.get_nem_id(iface)
|
||||||
|
if nem_id is None:
|
||||||
|
logger.info("nem for %s is unknown", iface.localname)
|
||||||
|
return
|
||||||
|
node = iface.node
|
||||||
|
x, y, z = node.getposition()
|
||||||
|
lat, lon, alt = self.session.location.getgeo(x, y, z)
|
||||||
|
if node.position.alt is not None:
|
||||||
|
alt = node.position.alt
|
||||||
|
node.position.set_geo(lon, lat, alt)
|
||||||
|
# altitude must be an integer or warning is printed
|
||||||
|
alt = int(round(alt))
|
||||||
|
return nem_id, lon, lat, alt
|
||||||
|
|
||||||
|
def set_nem_position(self, iface: CoreInterface) -> None:
|
||||||
|
"""
|
||||||
|
Publish a NEM location change event using the EMANE event service.
|
||||||
|
|
||||||
|
:param iface: interface to set nem position for
|
||||||
|
"""
|
||||||
|
position = self.get_nem_position(iface)
|
||||||
|
if position:
|
||||||
|
nemid, lon, lat, alt = position
|
||||||
|
event = LocationEvent()
|
||||||
|
event.append(nemid, latitude=lat, longitude=lon, altitude=alt)
|
||||||
|
self.publish_event(nemid, event, send_all=True)
|
||||||
|
|
||||||
|
def set_nem_positions(self, moved_ifaces: List[CoreInterface]) -> None:
|
||||||
|
"""
|
||||||
|
Several NEMs have moved, from e.g. a WaypointMobilityModel
|
||||||
|
calculation. Generate an EMANE Location Event having several
|
||||||
|
entries for each interface that has moved.
|
||||||
|
"""
|
||||||
|
if not moved_ifaces:
|
||||||
|
return
|
||||||
|
services = {}
|
||||||
|
for iface in moved_ifaces:
|
||||||
|
position = self.get_nem_position(iface)
|
||||||
|
if not position:
|
||||||
|
continue
|
||||||
|
nem_id, lon, lat, alt = position
|
||||||
|
service = self.nem_service.get(nem_id)
|
||||||
|
if not service:
|
||||||
|
continue
|
||||||
|
event = services.setdefault(service, LocationEvent())
|
||||||
|
event.append(nem_id, latitude=lat, longitude=lon, altitude=alt)
|
||||||
|
for service, event in services.items():
|
||||||
|
service.events.publish(0, event)
|
||||||
|
|
||||||
def write_nem(self, iface: CoreInterface, nem_id: int) -> None:
|
def write_nem(self, iface: CoreInterface, nem_id: int) -> None:
|
||||||
path = self.session.directory / "emane_nems"
|
path = self.session.directory / "emane_nems"
|
||||||
try:
|
try:
|
||||||
|
@ -414,23 +484,23 @@ class EmaneManager:
|
||||||
logger.exception("error writing to emane nem file")
|
logger.exception("error writing to emane nem file")
|
||||||
|
|
||||||
def links_enabled(self) -> bool:
|
def links_enabled(self) -> bool:
|
||||||
return self.config["link_enabled"] == "1"
|
return self.session.options.get_config_int("link_enabled") == 1
|
||||||
|
|
||||||
def poststartup(self) -> None:
|
def poststartup(self) -> None:
|
||||||
"""
|
"""
|
||||||
Retransmit location events now that all NEMs are active.
|
Retransmit location events now that all NEMs are active.
|
||||||
"""
|
"""
|
||||||
if not self.genlocationevents():
|
events_enabled = self.genlocationevents()
|
||||||
return
|
|
||||||
with self._emane_node_lock:
|
with self._emane_node_lock:
|
||||||
for node_id in sorted(self._emane_nets):
|
for node_id in sorted(self._emane_nets):
|
||||||
emane_net = self._emane_nets[node_id]
|
emane_net = self._emane_nets[node_id]
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"post startup for emane node: %s - %s", emane_net.id, emane_net.name
|
"post startup for emane node: %s - %s", emane_net.id, emane_net.name
|
||||||
)
|
)
|
||||||
emane_net.model.post_startup()
|
|
||||||
for iface in emane_net.get_ifaces():
|
for iface in emane_net.get_ifaces():
|
||||||
iface.setposition()
|
emane_net.model.post_startup(iface)
|
||||||
|
if events_enabled:
|
||||||
|
iface.setposition()
|
||||||
|
|
||||||
def reset(self) -> None:
|
def reset(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -441,6 +511,8 @@ class EmaneManager:
|
||||||
self._emane_nets.clear()
|
self._emane_nets.clear()
|
||||||
self.nems_to_ifaces.clear()
|
self.nems_to_ifaces.clear()
|
||||||
self.ifaces_to_nems.clear()
|
self.ifaces_to_nems.clear()
|
||||||
|
self.nems_to_ifaces.clear()
|
||||||
|
self.services.clear()
|
||||||
|
|
||||||
def shutdown(self) -> None:
|
def shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -452,22 +524,23 @@ class EmaneManager:
|
||||||
logger.info("stopping EMANE daemons")
|
logger.info("stopping EMANE daemons")
|
||||||
if self.links_enabled():
|
if self.links_enabled():
|
||||||
self.link_monitor.stop()
|
self.link_monitor.stop()
|
||||||
# shutdown interfaces and stop daemons
|
# shutdown interfaces
|
||||||
kill_emaned = "killall -q emane"
|
for _, iface in self.get_ifaces():
|
||||||
start_data = self.get_start_data()
|
node = iface.node
|
||||||
for data in start_data:
|
|
||||||
node = data.node
|
|
||||||
if not node.up:
|
if not node.up:
|
||||||
continue
|
continue
|
||||||
for iface in data.ifaces:
|
kill_cmd = f'pkill -f "emane.+{iface.name}"'
|
||||||
if isinstance(node, CoreNode):
|
|
||||||
iface.shutdown()
|
|
||||||
iface.poshook = None
|
|
||||||
if isinstance(node, CoreNode):
|
if isinstance(node, CoreNode):
|
||||||
node.cmd(kill_emaned, wait=False)
|
iface.shutdown()
|
||||||
|
node.cmd(kill_cmd, wait=False)
|
||||||
else:
|
else:
|
||||||
node.host_cmd(kill_emaned, wait=False)
|
node.host_cmd(kill_cmd, wait=False)
|
||||||
self.stopeventmonitor()
|
iface.poshook = None
|
||||||
|
# stop emane event services
|
||||||
|
while self.services:
|
||||||
|
_, service = self.services.popitem()
|
||||||
|
service.stop()
|
||||||
|
self.nem_service.clear()
|
||||||
|
|
||||||
def check_node_models(self) -> None:
|
def check_node_models(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -520,41 +593,14 @@ class EmaneManager:
|
||||||
color=color,
|
color=color,
|
||||||
)
|
)
|
||||||
|
|
||||||
def buildeventservicexml(self) -> None:
|
def start_daemon(self, iface: CoreInterface) -> None:
|
||||||
"""
|
"""
|
||||||
Build the libemaneeventservice.xml file if event service options
|
Start emane daemon for a given nem/interface.
|
||||||
were changed in the global config.
|
|
||||||
"""
|
|
||||||
need_xml = False
|
|
||||||
default_values = self.emane_config.default_values()
|
|
||||||
for name in ["eventservicegroup", "eventservicedevice"]:
|
|
||||||
a = default_values[name]
|
|
||||||
b = self.config[name]
|
|
||||||
if a != b:
|
|
||||||
need_xml = True
|
|
||||||
if not need_xml:
|
|
||||||
# reset to using default config
|
|
||||||
self.initeventservice()
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
group, port = self.config["eventservicegroup"].split(":")
|
|
||||||
except ValueError:
|
|
||||||
logger.exception("invalid eventservicegroup in EMANE config")
|
|
||||||
return
|
|
||||||
dev = self.config["eventservicedevice"]
|
|
||||||
emanexml.create_event_service_xml(group, port, dev, self.session.directory)
|
|
||||||
self.session.distributed.execute(
|
|
||||||
lambda x: emanexml.create_event_service_xml(
|
|
||||||
group, port, dev, self.session.directory, x
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def start_daemon(self, node: CoreNodeBase) -> None:
|
:param iface: interface to start emane daemon for
|
||||||
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
Start one EMANE daemon per node having a radio.
|
node = iface.node
|
||||||
Add a control network even if the user has not configured one.
|
|
||||||
"""
|
|
||||||
logger.info("starting emane daemons...")
|
|
||||||
loglevel = str(DEFAULT_LOG_LEVEL)
|
loglevel = str(DEFAULT_LOG_LEVEL)
|
||||||
cfgloglevel = self.session.options.get_config_int("emane_log_level")
|
cfgloglevel = self.session.options.get_config_int("emane_log_level")
|
||||||
realtime = self.session.options.get_config_bool("emane_realtime", default=True)
|
realtime = self.session.options.get_config_bool("emane_realtime", default=True)
|
||||||
|
@ -565,60 +611,25 @@ class EmaneManager:
|
||||||
if realtime:
|
if realtime:
|
||||||
emanecmd += " -r"
|
emanecmd += " -r"
|
||||||
if isinstance(node, CoreNode):
|
if isinstance(node, CoreNode):
|
||||||
otagroup, _otaport = self.config["otamanagergroup"].split(":")
|
|
||||||
otadev = self.config["otamanagerdevice"]
|
|
||||||
otanetidx = self.session.get_control_net_index(otadev)
|
|
||||||
eventgroup, _eventport = self.config["eventservicegroup"].split(":")
|
|
||||||
eventdev = self.config["eventservicedevice"]
|
|
||||||
eventservicenetidx = self.session.get_control_net_index(eventdev)
|
|
||||||
|
|
||||||
# control network not yet started here
|
|
||||||
self.session.add_remove_control_iface(
|
|
||||||
node, 0, remove=False, conf_required=False
|
|
||||||
)
|
|
||||||
if otanetidx > 0:
|
|
||||||
logger.info("adding ota device ctrl%d", otanetidx)
|
|
||||||
self.session.add_remove_control_iface(
|
|
||||||
node, otanetidx, remove=False, conf_required=False
|
|
||||||
)
|
|
||||||
if eventservicenetidx >= 0:
|
|
||||||
logger.info("adding event service device ctrl%d", eventservicenetidx)
|
|
||||||
self.session.add_remove_control_iface(
|
|
||||||
node, eventservicenetidx, remove=False, conf_required=False
|
|
||||||
)
|
|
||||||
# multicast route is needed for OTA data
|
|
||||||
logger.info("OTA GROUP(%s) OTA DEV(%s)", otagroup, otadev)
|
|
||||||
node.node_net_client.create_route(otagroup, otadev)
|
|
||||||
# multicast route is also needed for event data if on control network
|
|
||||||
if eventservicenetidx >= 0 and eventgroup != otagroup:
|
|
||||||
node.node_net_client.create_route(eventgroup, eventdev)
|
|
||||||
# start emane
|
# start emane
|
||||||
log_file = node.directory / f"{node.name}-emane.log"
|
log_file = node.directory / f"{iface.name}-emane.log"
|
||||||
platform_xml = node.directory / f"{node.name}-platform.xml"
|
platform_xml = node.directory / emanexml.platform_file_name(iface)
|
||||||
args = f"{emanecmd} -f {log_file} {platform_xml}"
|
args = f"{emanecmd} -f {log_file} {platform_xml}"
|
||||||
node.cmd(args)
|
node.cmd(args)
|
||||||
logger.info("node(%s) emane daemon running: %s", node.name, args)
|
|
||||||
else:
|
else:
|
||||||
log_file = self.session.directory / f"{node.name}-emane.log"
|
log_file = self.session.directory / f"{iface.name}-emane.log"
|
||||||
platform_xml = self.session.directory / f"{node.name}-platform.xml"
|
platform_xml = self.session.directory / emanexml.platform_file_name(iface)
|
||||||
args = f"{emanecmd} -f {log_file} {platform_xml}"
|
args = f"{emanecmd} -f {log_file} {platform_xml}"
|
||||||
node.host_cmd(args, cwd=self.session.directory)
|
node.host_cmd(args, cwd=self.session.directory)
|
||||||
logger.info("node(%s) host emane daemon running: %s", node.name, args)
|
|
||||||
|
|
||||||
def install_iface(self, iface: CoreInterface) -> None:
|
def install_iface(self, iface: CoreInterface, config: Dict[str, str]) -> None:
|
||||||
emane_net = iface.net
|
|
||||||
if not isinstance(emane_net, EmaneNet):
|
|
||||||
raise CoreError(
|
|
||||||
f"emane interface not connected to emane net: {emane_net.name}"
|
|
||||||
)
|
|
||||||
config = self.get_iface_config(emane_net, iface)
|
|
||||||
external = config.get("external", "0")
|
external = config.get("external", "0")
|
||||||
if isinstance(iface, TunTap) and external == "0":
|
if isinstance(iface, TunTap) and external == "0":
|
||||||
iface.set_ips()
|
iface.set_ips()
|
||||||
# at this point we register location handlers for generating
|
# at this point we register location handlers for generating
|
||||||
# EMANE location events
|
# EMANE location events
|
||||||
if self.genlocationevents():
|
if self.genlocationevents():
|
||||||
iface.poshook = emane_net.setnemposition
|
iface.poshook = self.set_nem_position
|
||||||
iface.setposition()
|
iface.setposition()
|
||||||
|
|
||||||
def doeventmonitor(self) -> bool:
|
def doeventmonitor(self) -> bool:
|
||||||
|
@ -640,68 +651,6 @@ class EmaneManager:
|
||||||
tmp = not self.doeventmonitor()
|
tmp = not self.doeventmonitor()
|
||||||
return tmp
|
return tmp
|
||||||
|
|
||||||
def starteventmonitor(self) -> None:
|
|
||||||
"""
|
|
||||||
Start monitoring EMANE location events if configured to do so.
|
|
||||||
"""
|
|
||||||
logger.info("emane start event monitor")
|
|
||||||
if not self.doeventmonitor():
|
|
||||||
return
|
|
||||||
if self.service is None:
|
|
||||||
logger.error(
|
|
||||||
"Warning: EMANE events will not be generated "
|
|
||||||
"because the emaneeventservice\n binding was "
|
|
||||||
"unable to load "
|
|
||||||
"(install the python-emaneeventservice bindings)"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
self.doeventloop = True
|
|
||||||
self.eventmonthread = threading.Thread(
|
|
||||||
target=self.eventmonitorloop, daemon=True
|
|
||||||
)
|
|
||||||
self.eventmonthread.start()
|
|
||||||
|
|
||||||
def stopeventmonitor(self) -> None:
|
|
||||||
"""
|
|
||||||
Stop monitoring EMANE location events.
|
|
||||||
"""
|
|
||||||
self.doeventloop = False
|
|
||||||
if self.service is not None:
|
|
||||||
self.service.breakloop()
|
|
||||||
# reset the service, otherwise nextEvent won"t work
|
|
||||||
self.initeventservice(shutdown=True)
|
|
||||||
|
|
||||||
if self.eventmonthread is not None:
|
|
||||||
self.eventmonthread.join()
|
|
||||||
self.eventmonthread = None
|
|
||||||
|
|
||||||
def eventmonitorloop(self) -> None:
|
|
||||||
"""
|
|
||||||
Thread target that monitors EMANE location events.
|
|
||||||
"""
|
|
||||||
if self.service is None:
|
|
||||||
return
|
|
||||||
logger.info(
|
|
||||||
"subscribing to EMANE location events. (%s)",
|
|
||||||
threading.currentThread().getName(),
|
|
||||||
)
|
|
||||||
while self.doeventloop is True:
|
|
||||||
_uuid, _seq, events = self.service.nextEvent()
|
|
||||||
|
|
||||||
# this occurs with 0.9.1 event service
|
|
||||||
if not self.doeventloop:
|
|
||||||
break
|
|
||||||
|
|
||||||
for event in events:
|
|
||||||
nem, eid, data = event
|
|
||||||
if eid == LocationEvent.IDENTIFIER:
|
|
||||||
self.handlelocationevent(nem, eid, data)
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
"unsubscribing from EMANE location events. (%s)",
|
|
||||||
threading.currentThread().getName(),
|
|
||||||
)
|
|
||||||
|
|
||||||
def handlelocationevent(self, rxnemid: int, eid: int, data: str) -> None:
|
def handlelocationevent(self, rxnemid: int, eid: int, data: str) -> None:
|
||||||
"""
|
"""
|
||||||
Handle an EMANE location event.
|
Handle an EMANE location event.
|
||||||
|
@ -717,7 +666,6 @@ class EmaneManager:
|
||||||
):
|
):
|
||||||
logger.warning("dropped invalid location event")
|
logger.warning("dropped invalid location event")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# yaw,pitch,roll,azimuth,elevation,velocity are unhandled
|
# yaw,pitch,roll,azimuth,elevation,velocity are unhandled
|
||||||
lat = attrs["latitude"]
|
lat = attrs["latitude"]
|
||||||
lon = attrs["longitude"]
|
lon = attrs["longitude"]
|
||||||
|
@ -812,87 +760,19 @@ class EmaneManager:
|
||||||
event = PathlossEvent()
|
event = PathlossEvent()
|
||||||
event.append(nem1, forward=rx1)
|
event.append(nem1, forward=rx1)
|
||||||
event.append(nem2, forward=rx2)
|
event.append(nem2, forward=rx2)
|
||||||
self.service.publish(nem1, event)
|
self.publish_event(nem1, event)
|
||||||
self.service.publish(nem2, event)
|
self.publish_event(nem2, event)
|
||||||
|
|
||||||
|
def publish_event(
|
||||||
class EmaneGlobalModel:
|
self,
|
||||||
"""
|
nem_id: int,
|
||||||
Global EMANE configuration options.
|
event: Union[PathlossEvent, CommEffectEvent, LocationEvent],
|
||||||
"""
|
send_all: bool = False,
|
||||||
|
) -> None:
|
||||||
name: str = "emane"
|
service = self.nem_service.get(nem_id)
|
||||||
bitmap: Optional[str] = None
|
if not service:
|
||||||
|
logger.error("no service to publish event nem(%s)", nem_id)
|
||||||
def __init__(self, session: "Session") -> None:
|
return
|
||||||
self.session: "Session" = session
|
if send_all:
|
||||||
self.core_config: List[Configuration] = [
|
nem_id = 0
|
||||||
Configuration(
|
service.events.publish(nem_id, event)
|
||||||
id="platform_id_start",
|
|
||||||
type=ConfigDataTypes.INT32,
|
|
||||||
default="1",
|
|
||||||
label="Starting Platform ID",
|
|
||||||
),
|
|
||||||
Configuration(
|
|
||||||
id="nem_id_start",
|
|
||||||
type=ConfigDataTypes.INT32,
|
|
||||||
default="1",
|
|
||||||
label="Starting NEM ID",
|
|
||||||
),
|
|
||||||
Configuration(
|
|
||||||
id="link_enabled",
|
|
||||||
type=ConfigDataTypes.BOOL,
|
|
||||||
default="1",
|
|
||||||
label="Enable Links?",
|
|
||||||
),
|
|
||||||
Configuration(
|
|
||||||
id="loss_threshold",
|
|
||||||
type=ConfigDataTypes.INT32,
|
|
||||||
default="30",
|
|
||||||
label="Link Loss Threshold (%)",
|
|
||||||
),
|
|
||||||
Configuration(
|
|
||||||
id="link_interval",
|
|
||||||
type=ConfigDataTypes.INT32,
|
|
||||||
default="1",
|
|
||||||
label="Link Check Interval (sec)",
|
|
||||||
),
|
|
||||||
Configuration(
|
|
||||||
id="link_timeout",
|
|
||||||
type=ConfigDataTypes.INT32,
|
|
||||||
default="4",
|
|
||||||
label="Link Timeout (sec)",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
self.emulator_config = None
|
|
||||||
self.parse_config()
|
|
||||||
|
|
||||||
def parse_config(self) -> None:
|
|
||||||
emane_prefix = self.session.options.get_config(
|
|
||||||
"emane_prefix", default=DEFAULT_EMANE_PREFIX
|
|
||||||
)
|
|
||||||
emane_prefix = Path(emane_prefix)
|
|
||||||
emulator_xml = emane_prefix / "share/emane/manifest/nemmanager.xml"
|
|
||||||
emulator_defaults = {
|
|
||||||
"eventservicedevice": DEFAULT_DEV,
|
|
||||||
"eventservicegroup": "224.1.2.8:45703",
|
|
||||||
"otamanagerdevice": DEFAULT_DEV,
|
|
||||||
"otamanagergroup": "224.1.2.8:45702",
|
|
||||||
}
|
|
||||||
self.emulator_config = emanemanifest.parse(emulator_xml, emulator_defaults)
|
|
||||||
|
|
||||||
def configurations(self) -> List[Configuration]:
|
|
||||||
return self.emulator_config + self.core_config
|
|
||||||
|
|
||||||
def config_groups(self) -> List[ConfigGroup]:
|
|
||||||
emulator_len = len(self.emulator_config)
|
|
||||||
config_len = len(self.configurations())
|
|
||||||
return [
|
|
||||||
ConfigGroup("Platform Attributes", 1, emulator_len),
|
|
||||||
ConfigGroup("CORE Configuration", emulator_len + 1, config_len),
|
|
||||||
]
|
|
||||||
|
|
||||||
def default_values(self) -> Dict[str, str]:
|
|
||||||
return OrderedDict(
|
|
||||||
[(config.id, config.default) for config in self.configurations()]
|
|
||||||
)
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ from typing import Dict, List, Optional, Set
|
||||||
|
|
||||||
from core.config import ConfigGroup, Configuration
|
from core.config import ConfigGroup, Configuration
|
||||||
from core.emane import emanemanifest
|
from core.emane import emanemanifest
|
||||||
from core.emane.nodes import EmaneNet
|
|
||||||
from core.emulator.data import LinkOptions
|
from core.emulator.data import LinkOptions
|
||||||
from core.emulator.enumerations import ConfigDataTypes
|
from core.emulator.enumerations import ConfigDataTypes
|
||||||
from core.errors import CoreError
|
from core.errors import CoreError
|
||||||
|
@ -16,6 +15,8 @@ from core.nodes.interface import CoreInterface
|
||||||
from core.xml import emanexml
|
from core.xml import emanexml
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
DEFAULT_DEV: str = "ctrl0"
|
||||||
|
MANIFEST_PATH: str = "share/emane/manifest"
|
||||||
|
|
||||||
|
|
||||||
class EmaneModel(WirelessModel):
|
class EmaneModel(WirelessModel):
|
||||||
|
@ -25,6 +26,17 @@ class EmaneModel(WirelessModel):
|
||||||
configurable parameters. Helper functions also live here.
|
configurable parameters. Helper functions also live here.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# default platform configuration settings
|
||||||
|
platform_controlport: str = "controlportendpoint"
|
||||||
|
platform_xml: str = "nemmanager.xml"
|
||||||
|
platform_defaults: Dict[str, str] = {
|
||||||
|
"eventservicedevice": DEFAULT_DEV,
|
||||||
|
"eventservicegroup": "224.1.2.8:45703",
|
||||||
|
"otamanagerdevice": DEFAULT_DEV,
|
||||||
|
"otamanagergroup": "224.1.2.8:45702",
|
||||||
|
}
|
||||||
|
platform_config: List[Configuration] = []
|
||||||
|
|
||||||
# default mac configuration settings
|
# default mac configuration settings
|
||||||
mac_library: Optional[str] = None
|
mac_library: Optional[str] = None
|
||||||
mac_xml: Optional[str] = None
|
mac_xml: Optional[str] = None
|
||||||
|
@ -57,20 +69,35 @@ class EmaneModel(WirelessModel):
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, emane_prefix: Path) -> None:
|
def load(cls, emane_prefix: Path) -> None:
|
||||||
"""
|
"""
|
||||||
Called after being loaded within the EmaneManager. Provides configured emane_prefix for
|
Called after being loaded within the EmaneManager. Provides configured
|
||||||
parsing xml files.
|
emane_prefix for parsing xml files.
|
||||||
|
|
||||||
:param emane_prefix: configured emane prefix path
|
:param emane_prefix: configured emane prefix path
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
manifest_path = "share/emane/manifest"
|
cls._load_platform_config(emane_prefix)
|
||||||
# load mac configuration
|
# load mac configuration
|
||||||
mac_xml_path = emane_prefix / manifest_path / cls.mac_xml
|
mac_xml_path = emane_prefix / MANIFEST_PATH / cls.mac_xml
|
||||||
cls.mac_config = emanemanifest.parse(mac_xml_path, cls.mac_defaults)
|
cls.mac_config = emanemanifest.parse(mac_xml_path, cls.mac_defaults)
|
||||||
# load phy configuration
|
# load phy configuration
|
||||||
phy_xml_path = emane_prefix / manifest_path / cls.phy_xml
|
phy_xml_path = emane_prefix / MANIFEST_PATH / cls.phy_xml
|
||||||
cls.phy_config = emanemanifest.parse(phy_xml_path, cls.phy_defaults)
|
cls.phy_config = emanemanifest.parse(phy_xml_path, cls.phy_defaults)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _load_platform_config(cls, emane_prefix: Path) -> None:
|
||||||
|
platform_xml_path = emane_prefix / MANIFEST_PATH / cls.platform_xml
|
||||||
|
cls.platform_config = emanemanifest.parse(
|
||||||
|
platform_xml_path, cls.platform_defaults
|
||||||
|
)
|
||||||
|
# remove controlport configuration, since core will set this directly
|
||||||
|
controlport_index = None
|
||||||
|
for index, configuration in enumerate(cls.platform_config):
|
||||||
|
if configuration.id == cls.platform_controlport:
|
||||||
|
controlport_index = index
|
||||||
|
break
|
||||||
|
if controlport_index is not None:
|
||||||
|
cls.platform_config.pop(controlport_index)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def configurations(cls) -> List[Configuration]:
|
def configurations(cls) -> List[Configuration]:
|
||||||
"""
|
"""
|
||||||
|
@ -78,7 +105,9 @@ class EmaneModel(WirelessModel):
|
||||||
|
|
||||||
:return: all configurations
|
:return: all configurations
|
||||||
"""
|
"""
|
||||||
return cls.mac_config + cls.phy_config + cls.external_config
|
return (
|
||||||
|
cls.platform_config + cls.mac_config + cls.phy_config + cls.external_config
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def config_groups(cls) -> List[ConfigGroup]:
|
def config_groups(cls) -> List[ConfigGroup]:
|
||||||
|
@ -87,11 +116,13 @@ class EmaneModel(WirelessModel):
|
||||||
|
|
||||||
:return: list of configuration groups.
|
:return: list of configuration groups.
|
||||||
"""
|
"""
|
||||||
mac_len = len(cls.mac_config)
|
platform_len = len(cls.platform_config)
|
||||||
|
mac_len = len(cls.mac_config) + platform_len
|
||||||
phy_len = len(cls.phy_config) + mac_len
|
phy_len = len(cls.phy_config) + mac_len
|
||||||
config_len = len(cls.configurations())
|
config_len = len(cls.configurations())
|
||||||
return [
|
return [
|
||||||
ConfigGroup("MAC Parameters", 1, mac_len),
|
ConfigGroup("Platform Parameters", 1, platform_len),
|
||||||
|
ConfigGroup("MAC Parameters", platform_len + 1, mac_len),
|
||||||
ConfigGroup("PHY Parameters", mac_len + 1, phy_len),
|
ConfigGroup("PHY Parameters", mac_len + 1, phy_len),
|
||||||
ConfigGroup("External Parameters", phy_len + 1, config_len),
|
ConfigGroup("External Parameters", phy_len + 1, config_len),
|
||||||
]
|
]
|
||||||
|
@ -111,10 +142,11 @@ class EmaneModel(WirelessModel):
|
||||||
emanexml.create_phy_xml(self, iface, config)
|
emanexml.create_phy_xml(self, iface, config)
|
||||||
emanexml.create_transport_xml(iface, config)
|
emanexml.create_transport_xml(iface, config)
|
||||||
|
|
||||||
def post_startup(self) -> None:
|
def post_startup(self, iface: CoreInterface) -> None:
|
||||||
"""
|
"""
|
||||||
Logic to execute after the emane manager is finished with startup.
|
Logic to execute after the emane manager is finished with startup.
|
||||||
|
|
||||||
|
:param iface: interface for post startup
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
logger.debug("emane model(%s) has no post setup tasks", self.name)
|
logger.debug("emane model(%s) has no post setup tasks", self.name)
|
||||||
|
@ -129,8 +161,7 @@ class EmaneModel(WirelessModel):
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
emane_net = self.session.get_node(self.id, EmaneNet)
|
self.session.emane.set_nem_positions(moved_ifaces)
|
||||||
emane_net.setnempositions(moved_ifaces)
|
|
||||||
except CoreError:
|
except CoreError:
|
||||||
logger.exception("error during update")
|
logger.exception("error during update")
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
from core.emane.nodes import EmaneNet
|
||||||
from core.emulator.data import LinkData
|
from core.emulator.data import LinkData
|
||||||
from core.emulator.enumerations import LinkTypes, MessageFlags
|
from core.emulator.enumerations import LinkTypes, MessageFlags
|
||||||
from core.nodes.network import CtrlNet
|
from core.nodes.network import CtrlNet
|
||||||
|
@ -24,7 +25,6 @@ except ImportError:
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from core.emane.emanemanager import EmaneManager
|
from core.emane.emanemanager import EmaneManager
|
||||||
|
|
||||||
DEFAULT_PORT: int = 47_000
|
|
||||||
MAC_COMPONENT_INDEX: int = 1
|
MAC_COMPONENT_INDEX: int = 1
|
||||||
EMANE_RFPIPE: str = "rfpipemaclayer"
|
EMANE_RFPIPE: str = "rfpipemaclayer"
|
||||||
EMANE_80211: str = "ieee80211abgmaclayer"
|
EMANE_80211: str = "ieee80211abgmaclayer"
|
||||||
|
@ -79,10 +79,10 @@ class EmaneLink:
|
||||||
|
|
||||||
|
|
||||||
class EmaneClient:
|
class EmaneClient:
|
||||||
def __init__(self, address: str) -> None:
|
def __init__(self, address: str, port: int) -> None:
|
||||||
self.address: str = address
|
self.address: str = address
|
||||||
self.client: shell.ControlPortClient = shell.ControlPortClient(
|
self.client: shell.ControlPortClient = shell.ControlPortClient(
|
||||||
self.address, DEFAULT_PORT
|
self.address, port
|
||||||
)
|
)
|
||||||
self.nems: Dict[int, LossTable] = {}
|
self.nems: Dict[int, LossTable] = {}
|
||||||
self.setup()
|
self.setup()
|
||||||
|
@ -189,9 +189,10 @@ class EmaneLinkMonitor:
|
||||||
self.running: bool = False
|
self.running: bool = False
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
self.loss_threshold = int(self.emane_manager.config["loss_threshold"])
|
options = self.emane_manager.session.options
|
||||||
self.link_interval = int(self.emane_manager.config["link_interval"])
|
self.loss_threshold = options.get_config_int("loss_threshold")
|
||||||
self.link_timeout = int(self.emane_manager.config["link_timeout"])
|
self.link_interval = options.get_config_int("link_interval")
|
||||||
|
self.link_timeout = options.get_config_int("link_timeout")
|
||||||
self.initialize()
|
self.initialize()
|
||||||
if not self.clients:
|
if not self.clients:
|
||||||
logger.info("no valid emane models to monitor links")
|
logger.info("no valid emane models to monitor links")
|
||||||
|
@ -204,22 +205,28 @@ class EmaneLinkMonitor:
|
||||||
|
|
||||||
def initialize(self) -> None:
|
def initialize(self) -> None:
|
||||||
addresses = self.get_addresses()
|
addresses = self.get_addresses()
|
||||||
for address in addresses:
|
for address, port in addresses:
|
||||||
client = EmaneClient(address)
|
client = EmaneClient(address, port)
|
||||||
if client.nems:
|
if client.nems:
|
||||||
self.clients.append(client)
|
self.clients.append(client)
|
||||||
|
|
||||||
def get_addresses(self) -> List[str]:
|
def get_addresses(self) -> List[Tuple[str, int]]:
|
||||||
addresses = []
|
addresses = []
|
||||||
nodes = self.emane_manager.getnodes()
|
nodes = self.emane_manager.getnodes()
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
|
control = None
|
||||||
|
ports = []
|
||||||
for iface in node.get_ifaces():
|
for iface in node.get_ifaces():
|
||||||
if isinstance(iface.net, CtrlNet):
|
if isinstance(iface.net, CtrlNet):
|
||||||
ip4 = iface.get_ip4()
|
ip4 = iface.get_ip4()
|
||||||
if ip4:
|
if ip4:
|
||||||
address = str(ip4.ip)
|
control = str(ip4.ip)
|
||||||
addresses.append(address)
|
if isinstance(iface.net, EmaneNet):
|
||||||
break
|
port = self.emane_manager.get_nem_port(iface)
|
||||||
|
ports.append(port)
|
||||||
|
if control:
|
||||||
|
for port in ports:
|
||||||
|
addresses.append((control, port))
|
||||||
return addresses
|
return addresses
|
||||||
|
|
||||||
def check_links(self) -> None:
|
def check_links(self) -> None:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
"""
|
"""
|
||||||
EMANE Bypass model for CORE
|
EMANE Bypass model for CORE
|
||||||
"""
|
"""
|
||||||
|
from pathlib import Path
|
||||||
from typing import List, Set
|
from typing import List, Set
|
||||||
|
|
||||||
from core.config import Configuration
|
from core.config import Configuration
|
||||||
|
@ -30,6 +31,5 @@ class EmaneBypassModel(emanemodel.EmaneModel):
|
||||||
phy_config: List[Configuration] = []
|
phy_config: List[Configuration] = []
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, emane_prefix: str) -> None:
|
def load(cls, emane_prefix: Path) -> None:
|
||||||
# ignore default logic
|
cls._load_platform_config(emane_prefix)
|
||||||
pass
|
|
||||||
|
|
|
@ -51,16 +51,25 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, emane_prefix: Path) -> None:
|
def load(cls, emane_prefix: Path) -> None:
|
||||||
|
cls._load_platform_config(emane_prefix)
|
||||||
shim_xml_path = emane_prefix / "share/emane/manifest" / cls.shim_xml
|
shim_xml_path = emane_prefix / "share/emane/manifest" / cls.shim_xml
|
||||||
cls.config_shim = emanemanifest.parse(shim_xml_path, cls.shim_defaults)
|
cls.config_shim = emanemanifest.parse(shim_xml_path, cls.shim_defaults)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def configurations(cls) -> List[Configuration]:
|
def configurations(cls) -> List[Configuration]:
|
||||||
return cls.config_shim
|
return cls.platform_config + cls.config_shim
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def config_groups(cls) -> List[ConfigGroup]:
|
def config_groups(cls) -> List[ConfigGroup]:
|
||||||
return [ConfigGroup("CommEffect SHIM Parameters", 1, len(cls.configurations()))]
|
platform_len = len(cls.platform_config)
|
||||||
|
return [
|
||||||
|
ConfigGroup("Platform Parameters", 1, platform_len),
|
||||||
|
ConfigGroup(
|
||||||
|
"CommEffect SHIM Parameters",
|
||||||
|
platform_len + 1,
|
||||||
|
len(cls.configurations()),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
def build_xml_files(self, config: Dict[str, str], iface: CoreInterface) -> None:
|
def build_xml_files(self, config: Dict[str, str], iface: CoreInterface) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -113,15 +122,9 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
|
||||||
Generate CommEffect events when a Link Message is received having
|
Generate CommEffect events when a Link Message is received having
|
||||||
link parameters.
|
link parameters.
|
||||||
"""
|
"""
|
||||||
service = self.session.emane.service
|
|
||||||
if service is None:
|
|
||||||
logger.warning("%s: EMANE event service unavailable", self.name)
|
|
||||||
return
|
|
||||||
|
|
||||||
if iface is None or iface2 is None:
|
if iface is None or iface2 is None:
|
||||||
logger.warning("%s: missing NEM information", self.name)
|
logger.warning("%s: missing NEM information", self.name)
|
||||||
return
|
return
|
||||||
|
|
||||||
# TODO: batch these into multiple events per transmission
|
# TODO: batch these into multiple events per transmission
|
||||||
# TODO: may want to split out seconds portion of delay and jitter
|
# TODO: may want to split out seconds portion of delay and jitter
|
||||||
event = CommEffectEvent()
|
event = CommEffectEvent()
|
||||||
|
@ -137,4 +140,4 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
|
||||||
unicast=int(convert_none(options.bandwidth)),
|
unicast=int(convert_none(options.bandwidth)),
|
||||||
broadcast=int(convert_none(options.bandwidth)),
|
broadcast=int(convert_none(options.bandwidth)),
|
||||||
)
|
)
|
||||||
service.publish(nem2, event)
|
self.session.emane.publish_event(nem2, event)
|
||||||
|
|
|
@ -9,7 +9,9 @@ from typing import Set
|
||||||
from core import constants, utils
|
from core import constants, utils
|
||||||
from core.config import Configuration
|
from core.config import Configuration
|
||||||
from core.emane import emanemodel
|
from core.emane import emanemodel
|
||||||
|
from core.emane.nodes import EmaneNet
|
||||||
from core.emulator.enumerations import ConfigDataTypes
|
from core.emulator.enumerations import ConfigDataTypes
|
||||||
|
from core.nodes.interface import CoreInterface
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -44,23 +46,23 @@ class EmaneTdmaModel(emanemodel.EmaneModel):
|
||||||
)
|
)
|
||||||
cls.mac_config.insert(0, config_item)
|
cls.mac_config.insert(0, config_item)
|
||||||
|
|
||||||
def post_startup(self) -> None:
|
def post_startup(self, iface: CoreInterface) -> None:
|
||||||
"""
|
|
||||||
Logic to execute after the emane manager is finished with startup.
|
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
|
||||||
# get configured schedule
|
# get configured schedule
|
||||||
config = self.session.emane.get_config(self.id, self.name)
|
emane_net = self.session.get_node(self.id, EmaneNet)
|
||||||
if not config:
|
config = self.session.emane.get_iface_config(emane_net, iface)
|
||||||
return
|
|
||||||
schedule = Path(config[self.schedule_name])
|
schedule = Path(config[self.schedule_name])
|
||||||
if not schedule.is_file():
|
if not schedule.is_file():
|
||||||
logger.warning("ignoring invalid tdma schedule: %s", schedule)
|
logger.error("ignoring invalid tdma schedule: %s", schedule)
|
||||||
return
|
return
|
||||||
# initiate tdma schedule
|
# initiate tdma schedule
|
||||||
event_device = self.session.emane.event_device
|
nem_id = self.session.emane.get_nem_id(iface)
|
||||||
logger.info(
|
if not nem_id:
|
||||||
"setting up tdma schedule: schedule(%s) device(%s)", schedule, event_device
|
logger.error("could not find nem for interface")
|
||||||
)
|
return
|
||||||
utils.cmd(f"emaneevent-tdmaschedule -i {event_device} {schedule}")
|
service = self.session.emane.nem_service.get(nem_id)
|
||||||
|
if service:
|
||||||
|
device = service.device
|
||||||
|
logger.info(
|
||||||
|
"setting up tdma schedule: schedule(%s) device(%s)", schedule, device
|
||||||
|
)
|
||||||
|
utils.cmd(f"emaneevent-tdmaschedule -i {device} {schedule}")
|
||||||
|
|
|
@ -4,7 +4,7 @@ share the same MAC+PHY model.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type
|
from typing import TYPE_CHECKING, Dict, List, Optional, Type
|
||||||
|
|
||||||
from core.emulator.data import InterfaceData, LinkData, LinkOptions
|
from core.emulator.data import InterfaceData, LinkData, LinkOptions
|
||||||
from core.emulator.distributed import DistributedServer
|
from core.emulator.distributed import DistributedServer
|
||||||
|
@ -110,67 +110,6 @@ class EmaneNet(CoreNetworkBase):
|
||||||
self.mobility = model(session=self.session, _id=self.id)
|
self.mobility = model(session=self.session, _id=self.id)
|
||||||
self.mobility.update_config(config)
|
self.mobility.update_config(config)
|
||||||
|
|
||||||
def _nem_position(
|
|
||||||
self, iface: CoreInterface
|
|
||||||
) -> Optional[Tuple[int, float, float, float]]:
|
|
||||||
"""
|
|
||||||
Creates nem position for emane event for a given interface.
|
|
||||||
|
|
||||||
:param iface: interface to get nem emane position for
|
|
||||||
:return: nem position tuple, None otherwise
|
|
||||||
"""
|
|
||||||
nem_id = self.session.emane.get_nem_id(iface)
|
|
||||||
ifname = iface.localname
|
|
||||||
if nem_id is None:
|
|
||||||
logger.info("nemid for %s is unknown", ifname)
|
|
||||||
return
|
|
||||||
node = iface.node
|
|
||||||
x, y, z = node.getposition()
|
|
||||||
lat, lon, alt = self.session.location.getgeo(x, y, z)
|
|
||||||
if node.position.alt is not None:
|
|
||||||
alt = node.position.alt
|
|
||||||
node.position.set_geo(lon, lat, alt)
|
|
||||||
# altitude must be an integer or warning is printed
|
|
||||||
alt = int(round(alt))
|
|
||||||
return nem_id, lon, lat, alt
|
|
||||||
|
|
||||||
def setnemposition(self, iface: CoreInterface) -> None:
|
|
||||||
"""
|
|
||||||
Publish a NEM location change event using the EMANE event service.
|
|
||||||
|
|
||||||
:param iface: interface to set nem position for
|
|
||||||
"""
|
|
||||||
if self.session.emane.service is None:
|
|
||||||
logger.info("position service not available")
|
|
||||||
return
|
|
||||||
position = self._nem_position(iface)
|
|
||||||
if position:
|
|
||||||
nemid, lon, lat, alt = position
|
|
||||||
event = LocationEvent()
|
|
||||||
event.append(nemid, latitude=lat, longitude=lon, altitude=alt)
|
|
||||||
self.session.emane.service.publish(0, event)
|
|
||||||
|
|
||||||
def setnempositions(self, moved_ifaces: List[CoreInterface]) -> None:
|
|
||||||
"""
|
|
||||||
Several NEMs have moved, from e.g. a WaypointMobilityModel
|
|
||||||
calculation. Generate an EMANE Location Event having several
|
|
||||||
entries for each interface that has moved.
|
|
||||||
"""
|
|
||||||
if len(moved_ifaces) == 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.session.emane.service is None:
|
|
||||||
logger.info("position service not available")
|
|
||||||
return
|
|
||||||
|
|
||||||
event = LocationEvent()
|
|
||||||
for iface in moved_ifaces:
|
|
||||||
position = self._nem_position(iface)
|
|
||||||
if position:
|
|
||||||
nemid, lon, lat, alt = position
|
|
||||||
event.append(nemid, latitude=lat, longitude=lon, altitude=alt)
|
|
||||||
self.session.emane.service.publish(0, event)
|
|
||||||
|
|
||||||
def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]:
|
def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]:
|
||||||
links = super().links(flags)
|
links = super().links(flags)
|
||||||
emane_manager = self.session.emane
|
emane_manager = self.session.emane
|
||||||
|
|
|
@ -59,6 +59,42 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions):
|
||||||
Configuration(
|
Configuration(
|
||||||
id="ovs", type=ConfigDataTypes.BOOL, default="0", label="Enable OVS"
|
id="ovs", type=ConfigDataTypes.BOOL, default="0", label="Enable OVS"
|
||||||
),
|
),
|
||||||
|
Configuration(
|
||||||
|
id="platform_id_start",
|
||||||
|
type=ConfigDataTypes.INT32,
|
||||||
|
default="1",
|
||||||
|
label="EMANE Platform ID Start",
|
||||||
|
),
|
||||||
|
Configuration(
|
||||||
|
id="nem_id_start",
|
||||||
|
type=ConfigDataTypes.INT32,
|
||||||
|
default="1",
|
||||||
|
label="EMANE NEM ID Start",
|
||||||
|
),
|
||||||
|
Configuration(
|
||||||
|
id="link_enabled",
|
||||||
|
type=ConfigDataTypes.BOOL,
|
||||||
|
default="1",
|
||||||
|
label="EMANE Links?",
|
||||||
|
),
|
||||||
|
Configuration(
|
||||||
|
id="loss_threshold",
|
||||||
|
type=ConfigDataTypes.INT32,
|
||||||
|
default="30",
|
||||||
|
label="EMANE Link Loss Threshold (%)",
|
||||||
|
),
|
||||||
|
Configuration(
|
||||||
|
id="link_interval",
|
||||||
|
type=ConfigDataTypes.INT32,
|
||||||
|
default="1",
|
||||||
|
label="EMANE Link Check Interval (sec)",
|
||||||
|
),
|
||||||
|
Configuration(
|
||||||
|
id="link_timeout",
|
||||||
|
type=ConfigDataTypes.INT32,
|
||||||
|
default="4",
|
||||||
|
label="EMANE Link Timeout (sec)",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
config_type: RegisterTlvs = RegisterTlvs.UTILITY
|
config_type: RegisterTlvs = RegisterTlvs.UTILITY
|
||||||
|
|
||||||
|
|
|
@ -19,40 +19,6 @@ if TYPE_CHECKING:
|
||||||
from core.gui.app import Application
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class GlobalEmaneDialog(Dialog):
|
|
||||||
def __init__(self, master: tk.BaseWidget, app: "Application") -> None:
|
|
||||||
super().__init__(app, "EMANE Configuration", master=master)
|
|
||||||
self.config_frame: Optional[ConfigFrame] = None
|
|
||||||
self.enabled: bool = not self.app.core.is_runtime()
|
|
||||||
self.draw()
|
|
||||||
|
|
||||||
def draw(self) -> None:
|
|
||||||
self.top.columnconfigure(0, weight=1)
|
|
||||||
self.top.rowconfigure(0, weight=1)
|
|
||||||
session = self.app.core.session
|
|
||||||
self.config_frame = ConfigFrame(
|
|
||||||
self.top, self.app, session.emane_config, self.enabled
|
|
||||||
)
|
|
||||||
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)
|
|
||||||
state = tk.NORMAL if self.enabled else tk.DISABLED
|
|
||||||
button = ttk.Button(frame, text="Apply", command=self.click_apply, state=state)
|
|
||||||
button.grid(row=0, column=0, sticky=tk.EW, padx=PADX)
|
|
||||||
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.destroy()
|
|
||||||
|
|
||||||
|
|
||||||
class EmaneModelDialog(Dialog):
|
class EmaneModelDialog(Dialog):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@ -180,8 +146,7 @@ class EmaneConfigDialog(Dialog):
|
||||||
def draw_emane_buttons(self) -> None:
|
def draw_emane_buttons(self) -> None:
|
||||||
frame = ttk.Frame(self.top)
|
frame = ttk.Frame(self.top)
|
||||||
frame.grid(sticky=tk.EW, pady=PADY)
|
frame.grid(sticky=tk.EW, pady=PADY)
|
||||||
for i in range(2):
|
frame.columnconfigure(0, weight=1)
|
||||||
frame.columnconfigure(i, weight=1)
|
|
||||||
|
|
||||||
image = images.from_enum(ImageEnum.EDITNODE, width=images.BUTTON_SIZE)
|
image = images.from_enum(ImageEnum.EDITNODE, width=images.BUTTON_SIZE)
|
||||||
self.emane_model_button = ttk.Button(
|
self.emane_model_button = ttk.Button(
|
||||||
|
@ -192,18 +157,7 @@ class EmaneConfigDialog(Dialog):
|
||||||
command=self.click_model_config,
|
command=self.click_model_config,
|
||||||
)
|
)
|
||||||
self.emane_model_button.image = image
|
self.emane_model_button.image = image
|
||||||
self.emane_model_button.grid(row=0, column=0, padx=PADX, sticky=tk.EW)
|
self.emane_model_button.grid(padx=PADX, sticky=tk.EW)
|
||||||
|
|
||||||
image = images.from_enum(ImageEnum.EDITNODE, width=images.BUTTON_SIZE)
|
|
||||||
button = ttk.Button(
|
|
||||||
frame,
|
|
||||||
text="EMANE options",
|
|
||||||
image=image,
|
|
||||||
compound=tk.RIGHT,
|
|
||||||
command=self.click_emane_config,
|
|
||||||
)
|
|
||||||
button.image = image
|
|
||||||
button.grid(row=0, column=1, sticky=tk.EW)
|
|
||||||
|
|
||||||
def draw_apply_and_cancel(self) -> None:
|
def draw_apply_and_cancel(self) -> None:
|
||||||
frame = ttk.Frame(self.top)
|
frame = ttk.Frame(self.top)
|
||||||
|
@ -216,10 +170,6 @@ class EmaneConfigDialog(Dialog):
|
||||||
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
button.grid(row=0, column=1, sticky=tk.EW)
|
button.grid(row=0, column=1, sticky=tk.EW)
|
||||||
|
|
||||||
def click_emane_config(self) -> None:
|
|
||||||
dialog = GlobalEmaneDialog(self, self.app)
|
|
||||||
dialog.show()
|
|
||||||
|
|
||||||
def click_model_config(self) -> None:
|
def click_model_config(self) -> None:
|
||||||
"""
|
"""
|
||||||
draw emane model configuration
|
draw emane model configuration
|
||||||
|
|
|
@ -38,7 +38,7 @@ class LinuxNetClient:
|
||||||
:param device: device to add route to
|
:param device: device to add route to
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
self.run(f"{IP} route add {route} dev {device}")
|
self.run(f"{IP} route replace {route} dev {device}")
|
||||||
|
|
||||||
def device_up(self, device: str) -> None:
|
def device_up(self, device: str) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -80,20 +80,6 @@ def create_iface_data(iface_element: etree.Element) -> InterfaceData:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_emane_config(session: "Session") -> etree.Element:
|
|
||||||
emane_configuration = etree.Element("emane_global_configuration")
|
|
||||||
config = session.emane.config
|
|
||||||
emulator_element = etree.SubElement(emane_configuration, "emulator")
|
|
||||||
for emulator_config in session.emane.emane_config.emulator_config:
|
|
||||||
value = config[emulator_config.id]
|
|
||||||
add_configuration(emulator_element, emulator_config.id, value)
|
|
||||||
core_element = etree.SubElement(emane_configuration, "core")
|
|
||||||
for core_config in session.emane.emane_config.core_config:
|
|
||||||
value = config[core_config.id]
|
|
||||||
add_configuration(core_element, core_config.id, value)
|
|
||||||
return emane_configuration
|
|
||||||
|
|
||||||
|
|
||||||
def create_emane_model_config(
|
def create_emane_model_config(
|
||||||
node_id: int,
|
node_id: int,
|
||||||
model: "EmaneModelType",
|
model: "EmaneModelType",
|
||||||
|
@ -104,22 +90,22 @@ def create_emane_model_config(
|
||||||
add_attribute(emane_element, "node", node_id)
|
add_attribute(emane_element, "node", node_id)
|
||||||
add_attribute(emane_element, "iface", iface_id)
|
add_attribute(emane_element, "iface", iface_id)
|
||||||
add_attribute(emane_element, "model", model.name)
|
add_attribute(emane_element, "model", model.name)
|
||||||
|
platform_element = etree.SubElement(emane_element, "platform")
|
||||||
|
for platform_config in model.platform_config:
|
||||||
|
value = config[platform_config.id]
|
||||||
|
add_configuration(platform_element, platform_config.id, value)
|
||||||
mac_element = etree.SubElement(emane_element, "mac")
|
mac_element = etree.SubElement(emane_element, "mac")
|
||||||
for mac_config in model.mac_config:
|
for mac_config in model.mac_config:
|
||||||
value = config[mac_config.id]
|
value = config[mac_config.id]
|
||||||
add_configuration(mac_element, mac_config.id, value)
|
add_configuration(mac_element, mac_config.id, value)
|
||||||
|
|
||||||
phy_element = etree.SubElement(emane_element, "phy")
|
phy_element = etree.SubElement(emane_element, "phy")
|
||||||
for phy_config in model.phy_config:
|
for phy_config in model.phy_config:
|
||||||
value = config[phy_config.id]
|
value = config[phy_config.id]
|
||||||
add_configuration(phy_element, phy_config.id, value)
|
add_configuration(phy_element, phy_config.id, value)
|
||||||
|
|
||||||
external_element = etree.SubElement(emane_element, "external")
|
external_element = etree.SubElement(emane_element, "external")
|
||||||
for external_config in model.external_config:
|
for external_config in model.external_config:
|
||||||
value = config[external_config.id]
|
value = config[external_config.id]
|
||||||
add_configuration(external_element, external_config.id, value)
|
add_configuration(external_element, external_config.id, value)
|
||||||
|
|
||||||
return emane_element
|
return emane_element
|
||||||
|
|
||||||
|
|
||||||
|
@ -376,8 +362,6 @@ class CoreXmlWriter:
|
||||||
self.scenario.append(metadata_elements)
|
self.scenario.append(metadata_elements)
|
||||||
|
|
||||||
def write_emane_configs(self) -> None:
|
def write_emane_configs(self) -> None:
|
||||||
emane_global_configuration = create_emane_config(self.session)
|
|
||||||
self.scenario.append(emane_global_configuration)
|
|
||||||
emane_configurations = etree.Element("emane_configurations")
|
emane_configurations = etree.Element("emane_configurations")
|
||||||
for node_id, model_configs in self.session.emane.node_configs.items():
|
for node_id, model_configs in self.session.emane.node_configs.items():
|
||||||
node_id, iface_id = utils.parse_iface_config_id(node_id)
|
node_id, iface_id = utils.parse_iface_config_id(node_id)
|
||||||
|
@ -591,7 +575,6 @@ class CoreXmlReader:
|
||||||
self.read_session_origin()
|
self.read_session_origin()
|
||||||
self.read_service_configs()
|
self.read_service_configs()
|
||||||
self.read_mobility_configs()
|
self.read_mobility_configs()
|
||||||
self.read_emane_global_config()
|
|
||||||
self.read_nodes()
|
self.read_nodes()
|
||||||
self.read_links()
|
self.read_links()
|
||||||
self.read_emane_configs()
|
self.read_emane_configs()
|
||||||
|
@ -729,28 +712,10 @@ class CoreXmlReader:
|
||||||
files.add(name)
|
files.add(name)
|
||||||
service.configs = tuple(files)
|
service.configs = tuple(files)
|
||||||
|
|
||||||
def read_emane_global_config(self) -> None:
|
|
||||||
emane_global_configuration = self.scenario.find("emane_global_configuration")
|
|
||||||
if emane_global_configuration is None:
|
|
||||||
return
|
|
||||||
emulator_configuration = emane_global_configuration.find("emulator")
|
|
||||||
configs = {}
|
|
||||||
for config in emulator_configuration.iterchildren():
|
|
||||||
name = config.get("name")
|
|
||||||
value = config.get("value")
|
|
||||||
configs[name] = value
|
|
||||||
core_configuration = emane_global_configuration.find("core")
|
|
||||||
for config in core_configuration.iterchildren():
|
|
||||||
name = config.get("name")
|
|
||||||
value = config.get("value")
|
|
||||||
configs[name] = value
|
|
||||||
self.session.emane.config = configs
|
|
||||||
|
|
||||||
def read_emane_configs(self) -> None:
|
def read_emane_configs(self) -> None:
|
||||||
emane_configurations = self.scenario.find("emane_configurations")
|
emane_configurations = self.scenario.find("emane_configurations")
|
||||||
if emane_configurations is None:
|
if emane_configurations is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
for emane_configuration in emane_configurations.iterchildren():
|
for emane_configuration in emane_configurations.iterchildren():
|
||||||
node_id = get_int(emane_configuration, "node")
|
node_id = get_int(emane_configuration, "node")
|
||||||
iface_id = get_int(emane_configuration, "iface")
|
iface_id = get_int(emane_configuration, "iface")
|
||||||
|
@ -768,18 +733,21 @@ class CoreXmlReader:
|
||||||
)
|
)
|
||||||
|
|
||||||
# read and set emane model configuration
|
# read and set emane model configuration
|
||||||
|
platform_configuration = emane_configuration.find("platform")
|
||||||
|
for config in platform_configuration.iterchildren():
|
||||||
|
name = config.get("name")
|
||||||
|
value = config.get("value")
|
||||||
|
configs[name] = value
|
||||||
mac_configuration = emane_configuration.find("mac")
|
mac_configuration = emane_configuration.find("mac")
|
||||||
for config in mac_configuration.iterchildren():
|
for config in mac_configuration.iterchildren():
|
||||||
name = config.get("name")
|
name = config.get("name")
|
||||||
value = config.get("value")
|
value = config.get("value")
|
||||||
configs[name] = value
|
configs[name] = value
|
||||||
|
|
||||||
phy_configuration = emane_configuration.find("phy")
|
phy_configuration = emane_configuration.find("phy")
|
||||||
for config in phy_configuration.iterchildren():
|
for config in phy_configuration.iterchildren():
|
||||||
name = config.get("name")
|
name = config.get("name")
|
||||||
value = config.get("value")
|
value = config.get("value")
|
||||||
configs[name] = value
|
configs[name] = value
|
||||||
|
|
||||||
external_configuration = emane_configuration.find("external")
|
external_configuration = emane_configuration.find("external")
|
||||||
for config in external_configuration.iterchildren():
|
for config in external_configuration.iterchildren():
|
||||||
name = config.get("name")
|
name = config.get("name")
|
||||||
|
|
|
@ -12,13 +12,11 @@ from core.emulator.distributed import DistributedServer
|
||||||
from core.errors import CoreError
|
from core.errors import CoreError
|
||||||
from core.nodes.base import CoreNode, CoreNodeBase
|
from core.nodes.base import CoreNode, CoreNodeBase
|
||||||
from core.nodes.interface import CoreInterface
|
from core.nodes.interface import CoreInterface
|
||||||
from core.nodes.network import CtrlNet
|
|
||||||
from core.xml import corexml
|
from core.xml import corexml
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from core.emane.emanemanager import EmaneManager, StartData
|
|
||||||
from core.emane.emanemodel import EmaneModel
|
from core.emane.emanemodel import EmaneModel
|
||||||
|
|
||||||
_MAC_PREFIX = "02:02"
|
_MAC_PREFIX = "02:02"
|
||||||
|
@ -146,74 +144,67 @@ def add_configurations(
|
||||||
|
|
||||||
|
|
||||||
def build_platform_xml(
|
def build_platform_xml(
|
||||||
emane_manager: "EmaneManager", control_net: CtrlNet, data: "StartData"
|
nem_id: int,
|
||||||
|
nem_port: int,
|
||||||
|
emane_net: EmaneNet,
|
||||||
|
iface: CoreInterface,
|
||||||
|
config: Dict[str, str],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Create platform xml for a specific node.
|
Create platform xml for a nem/interface.
|
||||||
|
|
||||||
:param emane_manager: emane manager with emane
|
:param nem_id: nem id for current node/interface
|
||||||
configurations
|
:param nem_port: control port to configure for emane
|
||||||
:param control_net: control net node for this emane
|
:param emane_net: emane network associate with node and interface
|
||||||
network
|
:param iface: node interface to create platform xml for
|
||||||
:param data: start data for a node connected to emane and associated interfaces
|
:param config: emane configuration for interface
|
||||||
:return: the next nem id that can be used for creating platform xml files
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
# create top level platform element
|
# create top level platform element
|
||||||
transport_configs = {"otamanagerdevice", "eventservicedevice"}
|
|
||||||
platform_element = etree.Element("platform")
|
platform_element = etree.Element("platform")
|
||||||
for configuration in emane_manager.emane_config.emulator_config:
|
for configuration in emane_net.model.platform_config:
|
||||||
name = configuration.id
|
name = configuration.id
|
||||||
if not isinstance(data.node, CoreNode) and name in transport_configs:
|
value = config[configuration.id]
|
||||||
value = control_net.brname
|
|
||||||
else:
|
|
||||||
value = emane_manager.config[name]
|
|
||||||
add_param(platform_element, name, value)
|
add_param(platform_element, name, value)
|
||||||
|
add_param(
|
||||||
|
platform_element, emane_net.model.platform_controlport, f"0.0.0.0:{nem_port}"
|
||||||
|
)
|
||||||
|
|
||||||
# create nem xml entries for all interfaces
|
# build nem xml
|
||||||
for iface in data.ifaces:
|
nem_definition = nem_file_name(iface)
|
||||||
emane_net = iface.net
|
nem_element = etree.Element(
|
||||||
if not isinstance(emane_net, EmaneNet):
|
"nem", id=str(nem_id), name=iface.localname, definition=nem_definition
|
||||||
raise CoreError(
|
)
|
||||||
f"emane interface not connected to emane net: {emane_net.name}"
|
|
||||||
)
|
|
||||||
nem_id = emane_manager.next_nem_id()
|
|
||||||
emane_manager.set_nem(nem_id, iface)
|
|
||||||
emane_manager.write_nem(iface, nem_id)
|
|
||||||
config = emane_manager.get_iface_config(emane_net, iface)
|
|
||||||
emane_net.model.build_xml_files(config, iface)
|
|
||||||
|
|
||||||
# build nem xml
|
# create model based xml files
|
||||||
nem_definition = nem_file_name(iface)
|
emane_net.model.build_xml_files(config, iface)
|
||||||
nem_element = etree.Element(
|
|
||||||
"nem", id=str(nem_id), name=iface.localname, definition=nem_definition
|
|
||||||
)
|
|
||||||
|
|
||||||
# check if this is an external transport
|
# check if this is an external transport
|
||||||
if is_external(config):
|
if is_external(config):
|
||||||
nem_element.set("transport", "external")
|
nem_element.set("transport", "external")
|
||||||
platform_endpoint = "platformendpoint"
|
platform_endpoint = "platformendpoint"
|
||||||
add_param(nem_element, platform_endpoint, config[platform_endpoint])
|
add_param(nem_element, platform_endpoint, config[platform_endpoint])
|
||||||
transport_endpoint = "transportendpoint"
|
transport_endpoint = "transportendpoint"
|
||||||
add_param(nem_element, transport_endpoint, config[transport_endpoint])
|
add_param(nem_element, transport_endpoint, config[transport_endpoint])
|
||||||
|
|
||||||
# define transport element
|
# define transport element
|
||||||
transport_name = transport_file_name(iface)
|
transport_name = transport_file_name(iface)
|
||||||
transport_element = etree.SubElement(
|
transport_element = etree.SubElement(
|
||||||
nem_element, "transport", definition=transport_name
|
nem_element, "transport", definition=transport_name
|
||||||
)
|
)
|
||||||
add_param(transport_element, "device", iface.name)
|
add_param(transport_element, "device", iface.name)
|
||||||
|
|
||||||
# add nem element to platform element
|
# add nem element to platform element
|
||||||
platform_element.append(nem_element)
|
platform_element.append(nem_element)
|
||||||
|
|
||||||
# generate and assign interface mac address based on nem id
|
# generate and assign interface mac address based on nem id
|
||||||
mac = _MAC_PREFIX + ":00:00:"
|
mac = _MAC_PREFIX + ":00:00:"
|
||||||
mac += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}"
|
mac += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}"
|
||||||
iface.set_mac(mac)
|
iface.set_mac(mac)
|
||||||
|
|
||||||
doc_name = "platform"
|
doc_name = "platform"
|
||||||
file_name = f"{data.node.name}-platform.xml"
|
file_name = platform_file_name(iface)
|
||||||
create_node_file(data.node, platform_element, doc_name, file_name)
|
create_node_file(iface.node, platform_element, doc_name, file_name)
|
||||||
|
|
||||||
|
|
||||||
def create_transport_xml(iface: CoreInterface, config: Dict[str, str]) -> None:
|
def create_transport_xml(iface: CoreInterface, config: Dict[str, str]) -> None:
|
||||||
|
@ -396,3 +387,7 @@ def phy_file_name(iface: CoreInterface) -> str:
|
||||||
:return: phy xml file name
|
:return: phy xml file name
|
||||||
"""
|
"""
|
||||||
return f"{iface.name}-phy.xml"
|
return f"{iface.name}-phy.xml"
|
||||||
|
|
||||||
|
|
||||||
|
def platform_file_name(iface: CoreInterface) -> str:
|
||||||
|
return f"{iface.name}-platform.xml"
|
||||||
|
|
|
@ -30,8 +30,9 @@ iface1 = iface_helper.create_iface(node2.id, 0)
|
||||||
session.add_link(node1=node2, node2=emane, iface1=iface1)
|
session.add_link(node1=node2, node2=emane, iface1=iface1)
|
||||||
|
|
||||||
# setup emane configurations using a dict mapping currently support values as strings
|
# setup emane configurations using a dict mapping currently support values as strings
|
||||||
session.set_emane({"eventservicettl": "2"})
|
emane.set_emane_model(
|
||||||
emane.set_emane_model(EmaneIeee80211abgModel.name, {"unicastrate": "3"})
|
EmaneIeee80211abgModel.name, {"eventservicettl": "2", "unicastrate": "3"}
|
||||||
|
)
|
||||||
|
|
||||||
# start session
|
# start session
|
||||||
core.start_session(session)
|
core.start_session(session)
|
||||||
|
|
|
@ -95,10 +95,6 @@ service CoreApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
// emane rpc
|
// emane rpc
|
||||||
rpc GetEmaneConfig (emane.GetEmaneConfigRequest) returns (emane.GetEmaneConfigResponse) {
|
|
||||||
}
|
|
||||||
rpc SetEmaneConfig (emane.SetEmaneConfigRequest) returns (emane.SetEmaneConfigResponse) {
|
|
||||||
}
|
|
||||||
rpc GetEmaneModelConfig (emane.GetEmaneModelConfigRequest) returns (emane.GetEmaneModelConfigResponse) {
|
rpc GetEmaneModelConfig (emane.GetEmaneModelConfigRequest) returns (emane.GetEmaneModelConfigResponse) {
|
||||||
}
|
}
|
||||||
rpc SetEmaneModelConfig (emane.SetEmaneModelConfigRequest) returns (emane.SetEmaneModelConfigResponse) {
|
rpc SetEmaneModelConfig (emane.SetEmaneModelConfigRequest) returns (emane.SetEmaneModelConfigResponse) {
|
||||||
|
@ -144,19 +140,18 @@ message StartSessionRequest {
|
||||||
repeated Link links = 3;
|
repeated Link links = 3;
|
||||||
repeated Hook hooks = 4;
|
repeated Hook hooks = 4;
|
||||||
SessionLocation location = 5;
|
SessionLocation location = 5;
|
||||||
map<string, string> emane_config = 6;
|
repeated wlan.WlanConfig wlan_configs = 6;
|
||||||
repeated wlan.WlanConfig wlan_configs = 7;
|
repeated emane.EmaneModelConfig emane_model_configs = 7;
|
||||||
repeated emane.EmaneModelConfig emane_model_configs = 8;
|
repeated mobility.MobilityConfig mobility_configs = 8;
|
||||||
repeated mobility.MobilityConfig mobility_configs = 9;
|
repeated services.ServiceConfig service_configs = 9;
|
||||||
repeated services.ServiceConfig service_configs = 10;
|
repeated services.ServiceFileConfig service_file_configs = 10;
|
||||||
repeated services.ServiceFileConfig service_file_configs = 11;
|
repeated Link asymmetric_links = 11;
|
||||||
repeated Link asymmetric_links = 12;
|
repeated configservices.ConfigServiceConfig config_service_configs = 12;
|
||||||
repeated configservices.ConfigServiceConfig config_service_configs = 13;
|
map<string, string> options = 13;
|
||||||
map<string, string> options = 14;
|
string user = 14;
|
||||||
string user = 15;
|
bool definition = 15;
|
||||||
bool definition = 16;
|
map<string, string> metadata = 16;
|
||||||
map<string, string> metadata = 17;
|
repeated Server servers = 17;
|
||||||
repeated Server servers = 18;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message StartSessionResponse {
|
message StartSessionResponse {
|
||||||
|
|
|
@ -4,23 +4,6 @@ package emane;
|
||||||
|
|
||||||
import "core/api/grpc/common.proto";
|
import "core/api/grpc/common.proto";
|
||||||
|
|
||||||
message GetEmaneConfigRequest {
|
|
||||||
int32 session_id = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetEmaneConfigResponse {
|
|
||||||
map<string, common.ConfigOption> config = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SetEmaneConfigRequest {
|
|
||||||
int32 session_id = 1;
|
|
||||||
map<string, string> config = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SetEmaneConfigResponse {
|
|
||||||
bool result = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetEmaneModelConfigRequest {
|
message GetEmaneModelConfigRequest {
|
||||||
int32 session_id = 1;
|
int32 session_id = 1;
|
||||||
int32 node_id = 2;
|
int32 node_id = 2;
|
||||||
|
@ -50,6 +33,7 @@ message GetEmaneModelConfig {
|
||||||
|
|
||||||
message GetEmaneEventChannelRequest {
|
message GetEmaneEventChannelRequest {
|
||||||
int32 session_id = 1;
|
int32 session_id = 1;
|
||||||
|
int32 nem_id = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetEmaneEventChannelResponse {
|
message GetEmaneEventChannelResponse {
|
||||||
|
|
|
@ -61,6 +61,7 @@ eval "$ifcommand" | awk '
|
||||||
/tmp\./ {print "removing interface " $1; system("ip link del " $1);}
|
/tmp\./ {print "removing interface " $1; system("ip link del " $1);}
|
||||||
/gt\./ {print "removing interface " $1; system("ip link del " $1);}
|
/gt\./ {print "removing interface " $1; system("ip link del " $1);}
|
||||||
/b\./ {print "removing bridge " $1; system("ip link set " $1 " down; ip link del " $1);}
|
/b\./ {print "removing bridge " $1; system("ip link set " $1 " down; ip link del " $1);}
|
||||||
|
/ctrl[0-9]+\./ {print "removing bridge " $1; system("ip link set " $1 " down; ip link del " $1);}
|
||||||
'
|
'
|
||||||
|
|
||||||
nft list ruleset | awk '
|
nft list ruleset | awk '
|
||||||
|
|
|
@ -83,11 +83,6 @@ class TestGrpc:
|
||||||
scale=location_scale,
|
scale=location_scale,
|
||||||
)
|
)
|
||||||
|
|
||||||
# setup global emane config
|
|
||||||
emane_config_key = "platform_id_start"
|
|
||||||
emane_config_value = "2"
|
|
||||||
session.set_emane({emane_config_key: emane_config_value})
|
|
||||||
|
|
||||||
# setup wlan config
|
# setup wlan config
|
||||||
wlan_config_key = "range"
|
wlan_config_key = "range"
|
||||||
wlan_config_value = "333"
|
wlan_config_value = "333"
|
||||||
|
@ -151,7 +146,6 @@ class TestGrpc:
|
||||||
location_alt,
|
location_alt,
|
||||||
)
|
)
|
||||||
assert real_session.location.refscale == location_scale
|
assert real_session.location.refscale == location_scale
|
||||||
assert real_session.emane.config[emane_config_key] == emane_config_value
|
|
||||||
set_wlan_config = real_session.mobility.get_model_config(
|
set_wlan_config = real_session.mobility.get_model_config(
|
||||||
wlan_node.id, BasicRangeModel.name
|
wlan_node.id, BasicRangeModel.name
|
||||||
)
|
)
|
||||||
|
@ -518,35 +512,6 @@ class TestGrpc:
|
||||||
assert config[range_key] == range_value
|
assert config[range_key] == range_value
|
||||||
assert wlan.model.range == int(range_value)
|
assert wlan.model.range == int(range_value)
|
||||||
|
|
||||||
def test_get_emane_config(self, grpc_server: CoreGrpcServer):
|
|
||||||
# given
|
|
||||||
client = CoreGrpcClient()
|
|
||||||
session = grpc_server.coreemu.create_session()
|
|
||||||
|
|
||||||
# then
|
|
||||||
with client.context_connect():
|
|
||||||
config = client.get_emane_config(session.id)
|
|
||||||
|
|
||||||
# then
|
|
||||||
assert len(config) > 0
|
|
||||||
|
|
||||||
def test_set_emane_config(self, grpc_server: CoreGrpcServer):
|
|
||||||
# given
|
|
||||||
client = CoreGrpcClient()
|
|
||||||
session = grpc_server.coreemu.create_session()
|
|
||||||
config_key = "platform_id_start"
|
|
||||||
config_value = "2"
|
|
||||||
|
|
||||||
# then
|
|
||||||
with client.context_connect():
|
|
||||||
result = client.set_emane_config(session.id, {config_key: config_value})
|
|
||||||
|
|
||||||
# then
|
|
||||||
assert result is True
|
|
||||||
config = session.emane.config
|
|
||||||
assert len(config) > 1
|
|
||||||
assert config[config_key] == config_value
|
|
||||||
|
|
||||||
def test_set_emane_model_config(self, grpc_server: CoreGrpcServer):
|
def test_set_emane_model_config(self, grpc_server: CoreGrpcServer):
|
||||||
# given
|
# given
|
||||||
client = CoreGrpcClient()
|
client = CoreGrpcClient()
|
||||||
|
|
|
@ -941,35 +941,3 @@ class TestGui:
|
||||||
|
|
||||||
config = coretlv.session.emane.get_config(wlan.id, EmaneIeee80211abgModel.name)
|
config = coretlv.session.emane.get_config(wlan.id, EmaneIeee80211abgModel.name)
|
||||||
assert config[config_key] == config_value
|
assert config[config_key] == config_value
|
||||||
|
|
||||||
def test_config_emane_request(self, coretlv: CoreHandler):
|
|
||||||
message = coreapi.CoreConfMessage.create(
|
|
||||||
0,
|
|
||||||
[
|
|
||||||
(ConfigTlvs.OBJECT, "emane"),
|
|
||||||
(ConfigTlvs.TYPE, ConfigFlags.REQUEST.value),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
coretlv.handle_broadcast_config = mock.MagicMock()
|
|
||||||
|
|
||||||
coretlv.handle_message(message)
|
|
||||||
|
|
||||||
coretlv.handle_broadcast_config.assert_called_once()
|
|
||||||
|
|
||||||
def test_config_emane_update(self, coretlv: CoreHandler):
|
|
||||||
config_key = "eventservicedevice"
|
|
||||||
config_value = "eth4"
|
|
||||||
values = {config_key: config_value}
|
|
||||||
message = coreapi.CoreConfMessage.create(
|
|
||||||
0,
|
|
||||||
[
|
|
||||||
(ConfigTlvs.OBJECT, "emane"),
|
|
||||||
(ConfigTlvs.TYPE, ConfigFlags.UPDATE.value),
|
|
||||||
(ConfigTlvs.VALUES, dict_to_str(values)),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
coretlv.handle_message(message)
|
|
||||||
|
|
||||||
config = coretlv.session.emane.config
|
|
||||||
assert config[config_key] == config_value
|
|
||||||
|
|
|
@ -328,10 +328,11 @@ session.add_link(node1=node1, node2=emane, iface1=iface1)
|
||||||
iface1 = iface_helper.create_iface(node2.id, 0)
|
iface1 = iface_helper.create_iface(node2.id, 0)
|
||||||
session.add_link(node1=node2, node2=emane, iface1=iface1)
|
session.add_link(node1=node2, node2=emane, iface1=iface1)
|
||||||
|
|
||||||
# setting global emane configuration
|
|
||||||
session.set_emane({"eventservicettl": "2"})
|
|
||||||
# setting emane specific emane model configuration
|
# setting emane specific emane model configuration
|
||||||
emane.set_emane_model(EmaneIeee80211abgModel.name, {"unicastrate": "3"})
|
emane.set_emane_model(EmaneIeee80211abgModel.name, {
|
||||||
|
"eventservicettl": "2",
|
||||||
|
"unicastrate": "3",
|
||||||
|
})
|
||||||
|
|
||||||
# start session
|
# start session
|
||||||
core.start_session(session)
|
core.start_session(session)
|
||||||
|
|
|
@ -541,13 +541,7 @@ proc wlanConfigDialogHelper { wi target apply } {
|
||||||
ttk::button $opts.model -text "model options" \
|
ttk::button $opts.model -text "model options" \
|
||||||
-image $plugin_img_edit -compound right -command "" -state disabled \
|
-image $plugin_img_edit -compound right -command "" -state disabled \
|
||||||
-command "configCap $target \[set g_selected_model\]"
|
-command "configCap $target \[set g_selected_model\]"
|
||||||
# global EMANE model uses no node in config request message, although any
|
pack $opts.model -side left -padx 4 -pady 4
|
||||||
# config will be stored with the EMANE node having the lowest ID
|
|
||||||
ttk::button $opts.gen -text "EMANE options" \
|
|
||||||
-image $plugin_img_edit -compound right \
|
|
||||||
-command "configCap -1 emane"
|
|
||||||
#-command "popupPluginsCapConfigHelper $wi config $target"
|
|
||||||
pack $opts.model $opts.gen -side left -padx 4 -pady 4
|
|
||||||
pack $opts -side top -anchor c -padx 4 -pady 4
|
pack $opts -side top -anchor c -padx 4 -pady 4
|
||||||
|
|
||||||
# show correct tab basic/emane based on selection
|
# show correct tab basic/emane based on selection
|
||||||
|
|
Loading…
Reference in a new issue