diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index f42caa14..7b5ff417 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -12,6 +12,7 @@ from core.emulator.emudata import LinkOptions from core.emulator.enumerations import ConfigDataTypes, TransportType from core.errors import CoreError from core.location.mobility import WirelessModel +from core.nodes.base import CoreNode from core.nodes.interface import CoreInterface from core.xml import emanexml @@ -139,13 +140,13 @@ class EmaneModel(WirelessModel): """ logging.debug("emane model(%s) has no post setup tasks", self.name) - def update(self, moved: bool, moved_netifs: List[CoreInterface]) -> None: + def update(self, moved: List[CoreNode], moved_netifs: List[CoreInterface]) -> None: """ Invoked from MobilityModel when nodes are moved; this causes emane location events to be generated for the nodes in the moved list, making EmaneModels compatible with Ns2ScriptedMobility. - :param moved: were nodes moved + :param moved: moved nodes :param moved_netifs: interfaces that were moved :return: nothing """ diff --git a/daemon/core/location/event.py b/daemon/core/location/event.py index 8826c42b..7f8a33a1 100644 --- a/daemon/core/location/event.py +++ b/daemon/core/location/event.py @@ -6,7 +6,7 @@ import heapq import threading import time from functools import total_ordering -from typing import Any, Callable +from typing import Any, Callable, Dict, List, Optional, Tuple class Timer(threading.Thread): @@ -16,34 +16,33 @@ class Timer(threading.Thread): """ def __init__( - self, interval: float, function: Callable, args: Any = None, kwargs: Any = None + self, + interval: float, + func: Callable[..., None], + args: Tuple[Any] = None, + kwargs: Dict[Any, Any] = None, ) -> None: """ Create a Timer instance. :param interval: time interval - :param function: function to call when timer finishes + :param func: function to call when timer finishes :param args: function arguments :param kwargs: function keyword arguments """ super().__init__() - self.interval = interval - self.function = function - - self.finished = threading.Event() - self._running = threading.Lock() - + self.interval: float = interval + self.func: Callable[..., None] = func + self.finished: threading.Event = threading.Event() + self._running: threading.Lock = threading.Lock() # validate arguments were provided - if args: - self.args = args - else: - self.args = [] - + if args is None: + args = () + self.args: Tuple[Any] = args # validate keyword arguments were provided - if kwargs: - self.kwargs = kwargs - else: - self.kwargs = {} + if kwargs is None: + kwargs = {} + self.kwargs: Dict[Any, Any] = kwargs def cancel(self) -> bool: """ @@ -67,7 +66,7 @@ class Timer(threading.Thread): self.finished.wait(self.interval) with self._running: if not self.finished.is_set(): - self.function(*self.args, **self.kwargs) + self.func(*self.args, **self.kwargs) self.finished.set() @@ -78,7 +77,12 @@ class Event: """ def __init__( - self, eventnum: int, event_time: float, func: Callable, *args: Any, **kwds: Any + self, + eventnum: int, + event_time: float, + func: Callable[..., None], + *args: Any, + **kwds: Any ) -> None: """ Create an Event instance. @@ -89,12 +93,12 @@ class Event: :param args: function arguments :param kwds: function keyword arguments """ - self.eventnum = eventnum - self.time = event_time - self.func = func - self.args = args - self.kwds = kwds - self.canceled = False + self.eventnum: int = eventnum + self.time: float = event_time + self.func: Callable[..., None] = func + self.args: Tuple[Any] = args + self.kwds: Dict[Any, Any] = kwds + self.canceled: bool = False def __lt__(self, other: "Event") -> bool: result = self.time < other.time @@ -118,7 +122,6 @@ class Event: :return: nothing """ - # XXX not thread-safe self.canceled = True @@ -131,14 +134,14 @@ class EventLoop: """ Creates a EventLoop instance. """ - self.lock = threading.RLock() - self.queue = [] - self.eventnum = 0 - self.timer = None - self.running = False - self.start = None + self.lock: threading.RLock = threading.RLock() + self.queue: List[Event] = [] + self.eventnum: int = 0 + self.timer: Optional[Timer] = None + self.running: bool = False + self.start: Optional[float] = None - def __run_events(self) -> None: + def _run_events(self) -> None: """ Run events. @@ -161,9 +164,9 @@ class EventLoop: with self.lock: self.timer = None if schedule: - self.__schedule_event() + self._schedule_event() - def __schedule_event(self) -> None: + def _schedule_event(self) -> None: """ Schedule event. @@ -177,7 +180,7 @@ class EventLoop: delay = self.queue[0].time - time.monotonic() if self.timer: raise ValueError("timer was already set") - self.timer = Timer(delay, self.__run_events) + self.timer = Timer(delay, self._run_events) self.timer.daemon = True self.timer.start() @@ -194,7 +197,7 @@ class EventLoop: self.start = time.monotonic() for event in self.queue: event.time += self.start - self.__schedule_event() + self._schedule_event() def stop(self) -> None: """ @@ -242,5 +245,5 @@ class EventLoop: if self.timer is not None and self.timer.cancel(): self.timer = None if self.running and self.timer is None: - self.__schedule_event() + self._schedule_event() return event diff --git a/daemon/core/location/geo.py b/daemon/core/location/geo.py index 4ff56dd6..6c8eb651 100644 --- a/daemon/core/location/geo.py +++ b/daemon/core/location/geo.py @@ -6,6 +6,7 @@ import logging from typing import Tuple import pyproj +from pyproj import Transformer from core.emulator.enumerations import RegisterTlvs @@ -20,21 +21,23 @@ class GeoLocation: defined projections. """ - name = "location" - config_type = RegisterTlvs.UTILITY + name: str = "location" + config_type: RegisterTlvs = RegisterTlvs.UTILITY def __init__(self) -> None: """ Creates a GeoLocation instance. """ - self.to_pixels = pyproj.Transformer.from_crs( + self.to_pixels: Transformer = pyproj.Transformer.from_crs( CRS_WGS84, CRS_PROJ, always_xy=True ) - self.to_geo = pyproj.Transformer.from_crs(CRS_PROJ, CRS_WGS84, always_xy=True) - self.refproj = (0.0, 0.0, 0.0) - self.refgeo = (0.0, 0.0, 0.0) - self.refxyz = (0.0, 0.0, 0.0) - self.refscale = 1.0 + self.to_geo: Transformer = pyproj.Transformer.from_crs( + CRS_PROJ, CRS_WGS84, always_xy=True + ) + self.refproj: Tuple[float, float, float] = (0.0, 0.0, 0.0) + self.refgeo: Tuple[float, float, float] = (0.0, 0.0, 0.0) + self.refxyz: Tuple[float, float, float] = (0.0, 0.0, 0.0) + self.refscale: float = 1.0 def setrefgeo(self, lat: float, lon: float, alt: float) -> None: """ diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 3ca46418..87cd7141 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -9,7 +9,7 @@ import os import threading import time from functools import total_ordering -from typing import TYPE_CHECKING, Dict, List, Tuple +from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple from core import utils from core.config import ConfigGroup, ConfigurableOptions, Configuration, ModelManager @@ -23,7 +23,7 @@ from core.emulator.enumerations import ( RegisterTlvs, ) from core.errors import CoreError -from core.nodes.base import CoreNode, NodeBase +from core.nodes.base import CoreNode from core.nodes.interface import CoreInterface from core.nodes.network import WlanNode @@ -47,7 +47,7 @@ class MobilityManager(ModelManager): :param session: session this manager is tied to """ super().__init__() - self.session = session + self.session: "Session" = session self.models[BasicRangeModel.name] = BasicRangeModel self.models[Ns2ScriptedMobility.name] = Ns2ScriptedMobility @@ -178,7 +178,7 @@ class MobilityManager(ModelManager): self.session.broadcast_event(event_data) def updatewlans( - self, moved: List[NodeBase], moved_netifs: List[CoreInterface] + self, moved: List[CoreNode], moved_netifs: List[CoreInterface] ) -> None: """ A mobility script has caused nodes in the 'moved' list to move. @@ -204,21 +204,21 @@ class WirelessModel(ConfigurableOptions): Used for managing arbitrary configuration parameters. """ - config_type = RegisterTlvs.WIRELESS - bitmap = None - position_callback = None + config_type: RegisterTlvs = RegisterTlvs.WIRELESS + bitmap: str = None + position_callback: Callable[[CoreInterface], None] = None - def __init__(self, session: "Session", _id: int): + def __init__(self, session: "Session", _id: int) -> None: """ Create a WirelessModel instance. :param session: core session we are tied to :param _id: object id """ - self.session = session - self.id = _id + self.session: "Session" = session + self.id: int = _id - def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List: + def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ May be used if the model can populate the GUI with wireless (green) link lines. @@ -228,11 +228,11 @@ class WirelessModel(ConfigurableOptions): """ return [] - def update(self, moved: bool, moved_netifs: List[CoreInterface]) -> None: + def update(self, moved: List[CoreNode], moved_netifs: List[CoreInterface]) -> None: """ Update this wireless model. - :param moved: flag is it was moved + :param moved: moved nodes :param moved_netifs: moved network interfaces :return: nothing """ @@ -256,8 +256,8 @@ class BasicRangeModel(WirelessModel): the GUI. """ - name = "basic_range" - options = [ + name: str = "basic_range" + options: List[Configuration] = [ Configuration( _id="range", _type=ConfigDataTypes.UINT32, @@ -299,15 +299,15 @@ class BasicRangeModel(WirelessModel): :param _id: object id """ super().__init__(session, _id) - self.session = session - self.wlan = session.get_node(_id, WlanNode) - self._netifs = {} - self._netifslock = threading.Lock() - self.range = 0 - self.bw = None - self.delay = None - self.loss = None - self.jitter = None + self.session: "Session" = session + self.wlan: WlanNode = session.get_node(_id, WlanNode) + self._netifs: Dict[CoreInterface, Tuple[float, float, float]] = {} + self._netifslock: threading.Lock = threading.Lock() + self.range: int = 0 + self.bw: Optional[int] = None + self.delay: Optional[int] = None + self.loss: Optional[float] = None + self.jitter: Optional[int] = None def _get_config(self, current_value: int, config: Dict[str, str], name: str) -> int: """ @@ -374,14 +374,14 @@ class BasicRangeModel(WirelessModel): position_callback = set_position - def update(self, moved: bool, moved_netifs: List[CoreInterface]) -> None: + def update(self, moved: List[CoreNode], moved_netifs: List[CoreInterface]) -> None: """ Node positions have changed without recalc. Update positions from node.position, then re-calculate links for those that have moved. Assumes bidirectional links, with one calculation per node pair, where one of the nodes has moved. - :param moved: flag is it was moved + :param moved: moved nodes :param moved_netifs: moved network interfaces :return: nothing """ @@ -535,29 +535,35 @@ class WayPoint: Maintains information regarding waypoints. """ - def __init__(self, time: float, nodenum: int, coords, speed: float): + def __init__( + self, + _time: float, + node_id: int, + coords: Tuple[float, float, float], + speed: float, + ) -> None: """ Creates a WayPoint instance. - :param time: waypoint time - :param nodenum: node id + :param _time: waypoint time + :param node_id: node id :param coords: waypoint coordinates :param speed: waypoint speed """ - self.time = time - self.nodenum = nodenum - self.coords = coords - self.speed = speed + self.time: float = _time + self.node_id: int = node_id + self.coords: Tuple[float, float, float] = coords + self.speed: float = speed def __eq__(self, other: "WayPoint") -> bool: - return (self.time, self.nodenum) == (other.time, other.nodenum) + return (self.time, self.node_id) == (other.time, other.node_id) def __ne__(self, other: "WayPoint") -> bool: return not self == other def __lt__(self, other: "WayPoint") -> bool: if self.time == other.time: - return self.nodenum < other.nodenum + return self.node_id < other.node_id else: return self.time < other.time @@ -567,12 +573,11 @@ class WayPointMobility(WirelessModel): Abstract class for mobility models that set node waypoints. """ - name = "waypoint" - config_type = RegisterTlvs.MOBILITY - - STATE_STOPPED = 0 - STATE_RUNNING = 1 - STATE_PAUSED = 2 + name: str = "waypoint" + config_type: RegisterTlvs = RegisterTlvs.MOBILITY + STATE_STOPPED: int = 0 + STATE_RUNNING: int = 1 + STATE_PAUSED: int = 2 def __init__(self, session: "Session", _id: int) -> None: """ @@ -583,20 +588,21 @@ class WayPointMobility(WirelessModel): :return: """ super().__init__(session=session, _id=_id) - self.state = self.STATE_STOPPED - self.queue = [] - self.queue_copy = [] - self.points = {} - self.initial = {} - self.lasttime = None - self.endtime = None - self.wlan = session.get_node(_id, WlanNode) + self.state: int = self.STATE_STOPPED + self.queue: List[WayPoint] = [] + self.queue_copy: List[WayPoint] = [] + self.points: Dict[int, WayPoint] = {} + self.initial: Dict[int, WayPoint] = {} + self.lasttime: Optional[float] = None + self.endtime: Optional[int] = None + self.timezero: float = 0.0 + self.wlan: WlanNode = session.get_node(_id, WlanNode) # these are really set in child class via confmatrix - self.loop = False - self.refresh_ms = 50 + self.loop: bool = False + self.refresh_ms: int = 50 # flag whether to stop scheduling when queue is empty # (ns-3 sets this to False as new waypoints may be added from trace) - self.empty_queue_stop = True + self.empty_queue_stop: bool = True def runround(self) -> None: """ @@ -684,16 +690,11 @@ class WayPointMobility(WirelessModel): self.setnodeposition(node, x2, y2, z2) del self.points[node.id] return True - # speed can be a velocity vector or speed value - if isinstance(speed, (float, int)): - # linear speed value - alpha = math.atan2(y2 - y1, x2 - x1) - sx = speed * math.cos(alpha) - sy = speed * math.sin(alpha) - else: - # velocity vector - sx = speed[0] - sy = speed[1] + + # linear speed value + alpha = math.atan2(y2 - y1, x2 - x1) + sx = speed * math.cos(alpha) + sy = speed * math.sin(alpha) # calculate dt * speed = distance moved dx = sx * dt @@ -776,7 +777,7 @@ class WayPointMobility(WirelessModel): if self.queue[0].time > now: break wp = heapq.heappop(self.queue) - self.points[wp.nodenum] = wp + self.points[wp.node_id] = wp def copywaypoints(self) -> None: """ @@ -876,8 +877,8 @@ class Ns2ScriptedMobility(WayPointMobility): BonnMotion. """ - name = "ns2script" - options = [ + name: str = "ns2script" + options: List[Configuration] = [ Configuration( _id="file", _type=ConfigDataTypes.STRING, label="mobility script file" ), @@ -923,7 +924,7 @@ class Ns2ScriptedMobility(WayPointMobility): ConfigGroup("ns-2 Mobility Script Parameters", 1, len(cls.configurations())) ] - def __init__(self, session: "Session", _id: int): + def __init__(self, session: "Session", _id: int) -> None: """ Creates a Ns2ScriptedMobility instance. @@ -931,17 +932,14 @@ class Ns2ScriptedMobility(WayPointMobility): :param _id: object id """ super().__init__(session, _id) - self._netifs = {} - self._netifslock = threading.Lock() - - self.file = None - self.refresh_ms = None - self.loop = None - self.autostart = None - self.nodemap = {} - self.script_start = None - self.script_pause = None - self.script_stop = None + self.file: Optional[str] = None + self.refresh_ms: Optional[int] = None + self.loop: Optional[bool] = None + self.autostart: Optional[str] = None + self.nodemap: Dict[int, int] = {} + self.script_start: Optional[str] = None + self.script_pause: Optional[str] = None + self.script_stop: Optional[str] = None def update_config(self, config: Dict[str, str]) -> None: self.file = config["file"] diff --git a/daemon/tests/test_mobility.py b/daemon/tests/test_mobility.py index e2e8f90e..aab7b30f 100644 --- a/daemon/tests/test_mobility.py +++ b/daemon/tests/test_mobility.py @@ -2,15 +2,17 @@ import pytest from core.location.mobility import WayPoint +POSITION = (0.0, 0.0, 0.0) + class TestMobility: @pytest.mark.parametrize( "wp1, wp2, expected", [ - (WayPoint(10.0, 1, [0, 0], 1.0), WayPoint(1.0, 2, [0, 0], 1.0), False), - (WayPoint(1.0, 1, [0, 0], 1.0), WayPoint(10.0, 2, [0, 0], 1.0), True), - (WayPoint(1.0, 1, [0, 0], 1.0), WayPoint(1.0, 2, [0, 0], 1.0), True), - (WayPoint(1.0, 2, [0, 0], 1.0), WayPoint(1.0, 1, [0, 0], 1.0), False), + (WayPoint(10.0, 1, POSITION, 1.0), WayPoint(1.0, 2, POSITION, 1.0), False), + (WayPoint(1.0, 1, POSITION, 1.0), WayPoint(10.0, 2, POSITION, 1.0), True), + (WayPoint(1.0, 1, POSITION, 1.0), WayPoint(1.0, 2, POSITION, 1.0), True), + (WayPoint(1.0, 2, POSITION, 1.0), WayPoint(1.0, 1, POSITION, 1.0), False), ], ) def test_waypoint_lessthan(self, wp1, wp2, expected):