daemon: added core.location class variable type hinting

This commit is contained in:
Blake Harnden 2020-06-10 10:24:44 -07:00
parent fd341bd69b
commit 784c4d2419
5 changed files with 137 additions and 130 deletions

View file

@ -12,6 +12,7 @@ from core.emulator.emudata import LinkOptions
from core.emulator.enumerations import ConfigDataTypes, TransportType from core.emulator.enumerations import ConfigDataTypes, TransportType
from core.errors import CoreError from core.errors import CoreError
from core.location.mobility import WirelessModel from core.location.mobility import WirelessModel
from core.nodes.base import CoreNode
from core.nodes.interface import CoreInterface from core.nodes.interface import CoreInterface
from core.xml import emanexml from core.xml import emanexml
@ -139,13 +140,13 @@ class EmaneModel(WirelessModel):
""" """
logging.debug("emane model(%s) has no post setup tasks", self.name) 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 Invoked from MobilityModel when nodes are moved; this causes
emane location events to be generated for the nodes in the moved emane location events to be generated for the nodes in the moved
list, making EmaneModels compatible with Ns2ScriptedMobility. list, making EmaneModels compatible with Ns2ScriptedMobility.
:param moved: were nodes moved :param moved: moved nodes
:param moved_netifs: interfaces that were moved :param moved_netifs: interfaces that were moved
:return: nothing :return: nothing
""" """

View file

@ -6,7 +6,7 @@ import heapq
import threading import threading
import time import time
from functools import total_ordering from functools import total_ordering
from typing import Any, Callable from typing import Any, Callable, Dict, List, Optional, Tuple
class Timer(threading.Thread): class Timer(threading.Thread):
@ -16,34 +16,33 @@ class Timer(threading.Thread):
""" """
def __init__( 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: ) -> None:
""" """
Create a Timer instance. Create a Timer instance.
:param interval: time interval :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 args: function arguments
:param kwargs: function keyword arguments :param kwargs: function keyword arguments
""" """
super().__init__() super().__init__()
self.interval = interval self.interval: float = interval
self.function = function self.func: Callable[..., None] = func
self.finished: threading.Event = threading.Event()
self.finished = threading.Event() self._running: threading.Lock = threading.Lock()
self._running = threading.Lock()
# validate arguments were provided # validate arguments were provided
if args: if args is None:
self.args = args args = ()
else: self.args: Tuple[Any] = args
self.args = []
# validate keyword arguments were provided # validate keyword arguments were provided
if kwargs: if kwargs is None:
self.kwargs = kwargs kwargs = {}
else: self.kwargs: Dict[Any, Any] = kwargs
self.kwargs = {}
def cancel(self) -> bool: def cancel(self) -> bool:
""" """
@ -67,7 +66,7 @@ class Timer(threading.Thread):
self.finished.wait(self.interval) self.finished.wait(self.interval)
with self._running: with self._running:
if not self.finished.is_set(): if not self.finished.is_set():
self.function(*self.args, **self.kwargs) self.func(*self.args, **self.kwargs)
self.finished.set() self.finished.set()
@ -78,7 +77,12 @@ class Event:
""" """
def __init__( 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: ) -> None:
""" """
Create an Event instance. Create an Event instance.
@ -89,12 +93,12 @@ class Event:
:param args: function arguments :param args: function arguments
:param kwds: function keyword arguments :param kwds: function keyword arguments
""" """
self.eventnum = eventnum self.eventnum: int = eventnum
self.time = event_time self.time: float = event_time
self.func = func self.func: Callable[..., None] = func
self.args = args self.args: Tuple[Any] = args
self.kwds = kwds self.kwds: Dict[Any, Any] = kwds
self.canceled = False self.canceled: bool = False
def __lt__(self, other: "Event") -> bool: def __lt__(self, other: "Event") -> bool:
result = self.time < other.time result = self.time < other.time
@ -118,7 +122,6 @@ class Event:
:return: nothing :return: nothing
""" """
# XXX not thread-safe
self.canceled = True self.canceled = True
@ -131,14 +134,14 @@ class EventLoop:
""" """
Creates a EventLoop instance. Creates a EventLoop instance.
""" """
self.lock = threading.RLock() self.lock: threading.RLock = threading.RLock()
self.queue = [] self.queue: List[Event] = []
self.eventnum = 0 self.eventnum: int = 0
self.timer = None self.timer: Optional[Timer] = None
self.running = False self.running: bool = False
self.start = None self.start: Optional[float] = None
def __run_events(self) -> None: def _run_events(self) -> None:
""" """
Run events. Run events.
@ -161,9 +164,9 @@ class EventLoop:
with self.lock: with self.lock:
self.timer = None self.timer = None
if schedule: if schedule:
self.__schedule_event() self._schedule_event()
def __schedule_event(self) -> None: def _schedule_event(self) -> None:
""" """
Schedule event. Schedule event.
@ -177,7 +180,7 @@ class EventLoop:
delay = self.queue[0].time - time.monotonic() delay = self.queue[0].time - time.monotonic()
if self.timer: if self.timer:
raise ValueError("timer was already set") 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.daemon = True
self.timer.start() self.timer.start()
@ -194,7 +197,7 @@ class EventLoop:
self.start = time.monotonic() self.start = time.monotonic()
for event in self.queue: for event in self.queue:
event.time += self.start event.time += self.start
self.__schedule_event() self._schedule_event()
def stop(self) -> None: def stop(self) -> None:
""" """
@ -242,5 +245,5 @@ class EventLoop:
if self.timer is not None and self.timer.cancel(): if self.timer is not None and self.timer.cancel():
self.timer = None self.timer = None
if self.running and self.timer is None: if self.running and self.timer is None:
self.__schedule_event() self._schedule_event()
return event return event

View file

@ -6,6 +6,7 @@ import logging
from typing import Tuple from typing import Tuple
import pyproj import pyproj
from pyproj import Transformer
from core.emulator.enumerations import RegisterTlvs from core.emulator.enumerations import RegisterTlvs
@ -20,21 +21,23 @@ class GeoLocation:
defined projections. defined projections.
""" """
name = "location" name: str = "location"
config_type = RegisterTlvs.UTILITY config_type: RegisterTlvs = RegisterTlvs.UTILITY
def __init__(self) -> None: def __init__(self) -> None:
""" """
Creates a GeoLocation instance. 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 CRS_WGS84, CRS_PROJ, always_xy=True
) )
self.to_geo = pyproj.Transformer.from_crs(CRS_PROJ, CRS_WGS84, always_xy=True) self.to_geo: Transformer = pyproj.Transformer.from_crs(
self.refproj = (0.0, 0.0, 0.0) CRS_PROJ, CRS_WGS84, always_xy=True
self.refgeo = (0.0, 0.0, 0.0) )
self.refxyz = (0.0, 0.0, 0.0) self.refproj: Tuple[float, float, float] = (0.0, 0.0, 0.0)
self.refscale = 1.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: def setrefgeo(self, lat: float, lon: float, alt: float) -> None:
""" """

View file

@ -9,7 +9,7 @@ import os
import threading import threading
import time import time
from functools import total_ordering 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 import utils
from core.config import ConfigGroup, ConfigurableOptions, Configuration, ModelManager from core.config import ConfigGroup, ConfigurableOptions, Configuration, ModelManager
@ -23,7 +23,7 @@ from core.emulator.enumerations import (
RegisterTlvs, RegisterTlvs,
) )
from core.errors import CoreError 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.interface import CoreInterface
from core.nodes.network import WlanNode from core.nodes.network import WlanNode
@ -47,7 +47,7 @@ class MobilityManager(ModelManager):
:param session: session this manager is tied to :param session: session this manager is tied to
""" """
super().__init__() super().__init__()
self.session = session self.session: "Session" = session
self.models[BasicRangeModel.name] = BasicRangeModel self.models[BasicRangeModel.name] = BasicRangeModel
self.models[Ns2ScriptedMobility.name] = Ns2ScriptedMobility self.models[Ns2ScriptedMobility.name] = Ns2ScriptedMobility
@ -178,7 +178,7 @@ class MobilityManager(ModelManager):
self.session.broadcast_event(event_data) self.session.broadcast_event(event_data)
def updatewlans( def updatewlans(
self, moved: List[NodeBase], moved_netifs: List[CoreInterface] self, moved: List[CoreNode], moved_netifs: List[CoreInterface]
) -> None: ) -> None:
""" """
A mobility script has caused nodes in the 'moved' list to move. 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. Used for managing arbitrary configuration parameters.
""" """
config_type = RegisterTlvs.WIRELESS config_type: RegisterTlvs = RegisterTlvs.WIRELESS
bitmap = None bitmap: str = None
position_callback = 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. Create a WirelessModel instance.
:param session: core session we are tied to :param session: core session we are tied to
:param _id: object id :param _id: object id
""" """
self.session = session self.session: "Session" = session
self.id = _id 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) May be used if the model can populate the GUI with wireless (green)
link lines. link lines.
@ -228,11 +228,11 @@ class WirelessModel(ConfigurableOptions):
""" """
return [] 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. Update this wireless model.
:param moved: flag is it was moved :param moved: moved nodes
:param moved_netifs: moved network interfaces :param moved_netifs: moved network interfaces
:return: nothing :return: nothing
""" """
@ -256,8 +256,8 @@ class BasicRangeModel(WirelessModel):
the GUI. the GUI.
""" """
name = "basic_range" name: str = "basic_range"
options = [ options: List[Configuration] = [
Configuration( Configuration(
_id="range", _id="range",
_type=ConfigDataTypes.UINT32, _type=ConfigDataTypes.UINT32,
@ -299,15 +299,15 @@ class BasicRangeModel(WirelessModel):
:param _id: object id :param _id: object id
""" """
super().__init__(session, _id) super().__init__(session, _id)
self.session = session self.session: "Session" = session
self.wlan = session.get_node(_id, WlanNode) self.wlan: WlanNode = session.get_node(_id, WlanNode)
self._netifs = {} self._netifs: Dict[CoreInterface, Tuple[float, float, float]] = {}
self._netifslock = threading.Lock() self._netifslock: threading.Lock = threading.Lock()
self.range = 0 self.range: int = 0
self.bw = None self.bw: Optional[int] = None
self.delay = None self.delay: Optional[int] = None
self.loss = None self.loss: Optional[float] = None
self.jitter = None self.jitter: Optional[int] = None
def _get_config(self, current_value: int, config: Dict[str, str], name: str) -> int: 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 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 positions have changed without recalc. Update positions from
node.position, then re-calculate links for those that have moved. node.position, then re-calculate links for those that have moved.
Assumes bidirectional links, with one calculation per node pair, where Assumes bidirectional links, with one calculation per node pair, where
one of the nodes has moved. one of the nodes has moved.
:param moved: flag is it was moved :param moved: moved nodes
:param moved_netifs: moved network interfaces :param moved_netifs: moved network interfaces
:return: nothing :return: nothing
""" """
@ -535,29 +535,35 @@ class WayPoint:
Maintains information regarding waypoints. 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. Creates a WayPoint instance.
:param time: waypoint time :param _time: waypoint time
:param nodenum: node id :param node_id: node id
:param coords: waypoint coordinates :param coords: waypoint coordinates
:param speed: waypoint speed :param speed: waypoint speed
""" """
self.time = time self.time: float = _time
self.nodenum = nodenum self.node_id: int = node_id
self.coords = coords self.coords: Tuple[float, float, float] = coords
self.speed = speed self.speed: float = speed
def __eq__(self, other: "WayPoint") -> bool: 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: def __ne__(self, other: "WayPoint") -> bool:
return not self == other return not self == other
def __lt__(self, other: "WayPoint") -> bool: def __lt__(self, other: "WayPoint") -> bool:
if self.time == other.time: if self.time == other.time:
return self.nodenum < other.nodenum return self.node_id < other.node_id
else: else:
return self.time < other.time return self.time < other.time
@ -567,12 +573,11 @@ class WayPointMobility(WirelessModel):
Abstract class for mobility models that set node waypoints. Abstract class for mobility models that set node waypoints.
""" """
name = "waypoint" name: str = "waypoint"
config_type = RegisterTlvs.MOBILITY config_type: RegisterTlvs = RegisterTlvs.MOBILITY
STATE_STOPPED: int = 0
STATE_STOPPED = 0 STATE_RUNNING: int = 1
STATE_RUNNING = 1 STATE_PAUSED: int = 2
STATE_PAUSED = 2
def __init__(self, session: "Session", _id: int) -> None: def __init__(self, session: "Session", _id: int) -> None:
""" """
@ -583,20 +588,21 @@ class WayPointMobility(WirelessModel):
:return: :return:
""" """
super().__init__(session=session, _id=_id) super().__init__(session=session, _id=_id)
self.state = self.STATE_STOPPED self.state: int = self.STATE_STOPPED
self.queue = [] self.queue: List[WayPoint] = []
self.queue_copy = [] self.queue_copy: List[WayPoint] = []
self.points = {} self.points: Dict[int, WayPoint] = {}
self.initial = {} self.initial: Dict[int, WayPoint] = {}
self.lasttime = None self.lasttime: Optional[float] = None
self.endtime = None self.endtime: Optional[int] = None
self.wlan = session.get_node(_id, WlanNode) self.timezero: float = 0.0
self.wlan: WlanNode = session.get_node(_id, WlanNode)
# these are really set in child class via confmatrix # these are really set in child class via confmatrix
self.loop = False self.loop: bool = False
self.refresh_ms = 50 self.refresh_ms: int = 50
# flag whether to stop scheduling when queue is empty # flag whether to stop scheduling when queue is empty
# (ns-3 sets this to False as new waypoints may be added from trace) # (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: def runround(self) -> None:
""" """
@ -684,16 +690,11 @@ class WayPointMobility(WirelessModel):
self.setnodeposition(node, x2, y2, z2) self.setnodeposition(node, x2, y2, z2)
del self.points[node.id] del self.points[node.id]
return True return True
# speed can be a velocity vector or speed value
if isinstance(speed, (float, int)): # linear speed value
# linear speed value alpha = math.atan2(y2 - y1, x2 - x1)
alpha = math.atan2(y2 - y1, x2 - x1) sx = speed * math.cos(alpha)
sx = speed * math.cos(alpha) sy = speed * math.sin(alpha)
sy = speed * math.sin(alpha)
else:
# velocity vector
sx = speed[0]
sy = speed[1]
# calculate dt * speed = distance moved # calculate dt * speed = distance moved
dx = sx * dt dx = sx * dt
@ -776,7 +777,7 @@ class WayPointMobility(WirelessModel):
if self.queue[0].time > now: if self.queue[0].time > now:
break break
wp = heapq.heappop(self.queue) wp = heapq.heappop(self.queue)
self.points[wp.nodenum] = wp self.points[wp.node_id] = wp
def copywaypoints(self) -> None: def copywaypoints(self) -> None:
""" """
@ -876,8 +877,8 @@ class Ns2ScriptedMobility(WayPointMobility):
BonnMotion. BonnMotion.
""" """
name = "ns2script" name: str = "ns2script"
options = [ options: List[Configuration] = [
Configuration( Configuration(
_id="file", _type=ConfigDataTypes.STRING, label="mobility script file" _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())) 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. Creates a Ns2ScriptedMobility instance.
@ -931,17 +932,14 @@ class Ns2ScriptedMobility(WayPointMobility):
:param _id: object id :param _id: object id
""" """
super().__init__(session, _id) super().__init__(session, _id)
self._netifs = {} self.file: Optional[str] = None
self._netifslock = threading.Lock() self.refresh_ms: Optional[int] = None
self.loop: Optional[bool] = None
self.file = None self.autostart: Optional[str] = None
self.refresh_ms = None self.nodemap: Dict[int, int] = {}
self.loop = None self.script_start: Optional[str] = None
self.autostart = None self.script_pause: Optional[str] = None
self.nodemap = {} self.script_stop: Optional[str] = None
self.script_start = None
self.script_pause = None
self.script_stop = None
def update_config(self, config: Dict[str, str]) -> None: def update_config(self, config: Dict[str, str]) -> None:
self.file = config["file"] self.file = config["file"]

View file

@ -2,15 +2,17 @@ import pytest
from core.location.mobility import WayPoint from core.location.mobility import WayPoint
POSITION = (0.0, 0.0, 0.0)
class TestMobility: class TestMobility:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"wp1, wp2, expected", "wp1, wp2, expected",
[ [
(WayPoint(10.0, 1, [0, 0], 1.0), WayPoint(1.0, 2, [0, 0], 1.0), False), (WayPoint(10.0, 1, POSITION, 1.0), WayPoint(1.0, 2, POSITION, 1.0), False),
(WayPoint(1.0, 1, [0, 0], 1.0), WayPoint(10.0, 2, [0, 0], 1.0), True), (WayPoint(1.0, 1, POSITION, 1.0), WayPoint(10.0, 2, POSITION, 1.0), True),
(WayPoint(1.0, 1, [0, 0], 1.0), WayPoint(1.0, 2, [0, 0], 1.0), True), (WayPoint(1.0, 1, POSITION, 1.0), WayPoint(1.0, 2, POSITION, 1.0), True),
(WayPoint(1.0, 2, [0, 0], 1.0), WayPoint(1.0, 1, [0, 0], 1.0), False), (WayPoint(1.0, 2, POSITION, 1.0), WayPoint(1.0, 1, POSITION, 1.0), False),
], ],
) )
def test_waypoint_lessthan(self, wp1, wp2, expected): def test_waypoint_lessthan(self, wp1, wp2, expected):