nost-tools 2.0.0__py3-none-any.whl → 2.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nost-tools might be problematic. Click here for more details.
- nost_tools/__init__.py +29 -29
- nost_tools/application.py +800 -793
- nost_tools/application_utils.py +262 -262
- nost_tools/configuration.py +304 -304
- nost_tools/entity.py +73 -73
- nost_tools/errors.py +14 -14
- nost_tools/logger_application.py +192 -192
- nost_tools/managed_application.py +261 -261
- nost_tools/manager.py +472 -472
- nost_tools/observer.py +181 -181
- nost_tools/publisher.py +141 -141
- nost_tools/schemas.py +432 -426
- nost_tools/simulator.py +531 -531
- {nost_tools-2.0.0.dist-info → nost_tools-2.0.1.dist-info}/METADATA +118 -119
- nost_tools-2.0.1.dist-info/RECORD +18 -0
- {nost_tools-2.0.0.dist-info → nost_tools-2.0.1.dist-info}/licenses/LICENSE +29 -29
- nost_tools-2.0.0.dist-info/RECORD +0 -18
- {nost_tools-2.0.0.dist-info → nost_tools-2.0.1.dist-info}/WHEEL +0 -0
- {nost_tools-2.0.0.dist-info → nost_tools-2.0.1.dist-info}/top_level.txt +0 -0
nost_tools/observer.py
CHANGED
|
@@ -1,181 +1,181 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Provides base classes that implement the observer pattern to loosely couple an observable and observer.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from abc import ABC, abstractmethod
|
|
6
|
-
from datetime import datetime, timezone
|
|
7
|
-
from typing import List, Optional, Union
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class Observer(ABC):
|
|
11
|
-
"""
|
|
12
|
-
Abstract base class that can be notified of property changes from an associated :obj:`Observable`.
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
@abstractmethod
|
|
16
|
-
def on_change(
|
|
17
|
-
self, source: object, property_name: str, old_value: object, new_value: object
|
|
18
|
-
) -> None:
|
|
19
|
-
"""Callback notifying of a change.
|
|
20
|
-
|
|
21
|
-
Args:
|
|
22
|
-
source (object): object that triggered a property change
|
|
23
|
-
property_name (str): name of the changed property
|
|
24
|
-
old_value (object): old value of the named property
|
|
25
|
-
new_value (object): new value of the named property
|
|
26
|
-
"""
|
|
27
|
-
pass
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class RecordingObserver(Observer):
|
|
31
|
-
"""
|
|
32
|
-
Observer that records all changes.
|
|
33
|
-
"""
|
|
34
|
-
|
|
35
|
-
def __init__(
|
|
36
|
-
self,
|
|
37
|
-
property_filters: Optional[Union[str, List[str]]] = None,
|
|
38
|
-
timestamped: bool = False,
|
|
39
|
-
):
|
|
40
|
-
"""
|
|
41
|
-
Initializes a new recording obsever.
|
|
42
|
-
|
|
43
|
-
Args:
|
|
44
|
-
properties (Optional[Union[str,List[str]]]): optional list of property names to record
|
|
45
|
-
timestamped (bool): True, if the changes shall be timestamped
|
|
46
|
-
"""
|
|
47
|
-
if isinstance(property_filters, str):
|
|
48
|
-
self.property_filters = [property_filters]
|
|
49
|
-
else:
|
|
50
|
-
self.property_filters = property_filters
|
|
51
|
-
self.changes = []
|
|
52
|
-
self.timestamped = timestamped
|
|
53
|
-
|
|
54
|
-
def on_change(
|
|
55
|
-
self, source: object, property_name: str, old_value: object, new_value: object
|
|
56
|
-
) -> None:
|
|
57
|
-
"""Callback notifying of a change.
|
|
58
|
-
|
|
59
|
-
Args:
|
|
60
|
-
source (object): object that triggered a property change
|
|
61
|
-
property_name (str): name of the changed property
|
|
62
|
-
old_value (object): old value of the named property
|
|
63
|
-
new_value (object): new value of the named property
|
|
64
|
-
"""
|
|
65
|
-
if self.property_filters is None or property_name in self.property_filters:
|
|
66
|
-
change = {
|
|
67
|
-
"source": source,
|
|
68
|
-
"property_name": property_name,
|
|
69
|
-
"old_value": old_value,
|
|
70
|
-
"new_value": new_value,
|
|
71
|
-
}
|
|
72
|
-
if self.timestamped:
|
|
73
|
-
change["time"] = datetime.now(tz=timezone.utc)
|
|
74
|
-
self.changes.append(change)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
class Observable(object):
|
|
78
|
-
"""
|
|
79
|
-
Base class that can register (add/remove) and notify observers of property changes.
|
|
80
|
-
"""
|
|
81
|
-
|
|
82
|
-
def __init__(self):
|
|
83
|
-
"""
|
|
84
|
-
Initializes a new observable.
|
|
85
|
-
"""
|
|
86
|
-
# list of observers to be notified of events
|
|
87
|
-
self._observers = []
|
|
88
|
-
|
|
89
|
-
def add_observer(self, observer: Observer) -> None:
|
|
90
|
-
"""
|
|
91
|
-
Adds an observer to this observable.
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
observer (:obj:`Observer`): observer to be added
|
|
95
|
-
"""
|
|
96
|
-
self._observers.append(observer)
|
|
97
|
-
|
|
98
|
-
def remove_observer(self, observer: Observer) -> Observer:
|
|
99
|
-
"""
|
|
100
|
-
Removes an observer from this observable.
|
|
101
|
-
|
|
102
|
-
Args:
|
|
103
|
-
observer (:obj:`Observer`): obsever to be removed
|
|
104
|
-
|
|
105
|
-
Returns:
|
|
106
|
-
:obj:`Observer`: removed observer
|
|
107
|
-
"""
|
|
108
|
-
return self._observers.remove(observer)
|
|
109
|
-
|
|
110
|
-
def notify_observers(
|
|
111
|
-
self, property_name: str, old_value: object, new_value: object
|
|
112
|
-
) -> None:
|
|
113
|
-
"""
|
|
114
|
-
Notifies observers of a property change.
|
|
115
|
-
|
|
116
|
-
Args:
|
|
117
|
-
property_name (str): name of the changed property
|
|
118
|
-
old_value (object): old value of the named property
|
|
119
|
-
new_value (object): new value of the named property
|
|
120
|
-
"""
|
|
121
|
-
if old_value != new_value:
|
|
122
|
-
for observer in self._observers:
|
|
123
|
-
observer.on_change(self, property_name, old_value, new_value)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
# Add after the existing Observer class
|
|
127
|
-
class MessageObserver(ABC):
|
|
128
|
-
"""
|
|
129
|
-
Abstract base class for message observers that can receive RabbitMQ messages.
|
|
130
|
-
"""
|
|
131
|
-
|
|
132
|
-
@abstractmethod
|
|
133
|
-
def on_message(self, ch, method, properties, body) -> None:
|
|
134
|
-
"""Callback for when a message is received.
|
|
135
|
-
|
|
136
|
-
Args:
|
|
137
|
-
ch: Channel object
|
|
138
|
-
method: Method frame
|
|
139
|
-
properties: Message properties
|
|
140
|
-
body: Message body
|
|
141
|
-
"""
|
|
142
|
-
pass
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
class MessageObservable(Observable):
|
|
146
|
-
"""
|
|
147
|
-
Observable that can notify observers of received messages.
|
|
148
|
-
"""
|
|
149
|
-
|
|
150
|
-
def __init__(self):
|
|
151
|
-
"""Initialize message observable"""
|
|
152
|
-
super().__init__()
|
|
153
|
-
self._message_observers = []
|
|
154
|
-
|
|
155
|
-
def add_message_observer(self, observer: MessageObserver) -> None:
|
|
156
|
-
"""Add a message observer.
|
|
157
|
-
|
|
158
|
-
Args:
|
|
159
|
-
observer (MessageObserver): The observer to add
|
|
160
|
-
"""
|
|
161
|
-
self._message_observers.append(observer)
|
|
162
|
-
|
|
163
|
-
def remove_message_observer(self, observer: MessageObserver) -> None:
|
|
164
|
-
"""Remove a message observer.
|
|
165
|
-
|
|
166
|
-
Args:
|
|
167
|
-
observer (MessageObserver): The observer to remove
|
|
168
|
-
"""
|
|
169
|
-
self._message_observers.remove(observer)
|
|
170
|
-
|
|
171
|
-
def notify_message_observers(self, ch, method, properties, body) -> None:
|
|
172
|
-
"""Notify all message observers about a received message.
|
|
173
|
-
|
|
174
|
-
Args:
|
|
175
|
-
ch: Channel object
|
|
176
|
-
method: Method frame
|
|
177
|
-
properties: Message properties
|
|
178
|
-
body: Message body
|
|
179
|
-
"""
|
|
180
|
-
for observer in self._message_observers:
|
|
181
|
-
observer.on_message(ch, method, properties, body)
|
|
1
|
+
"""
|
|
2
|
+
Provides base classes that implement the observer pattern to loosely couple an observable and observer.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from typing import List, Optional, Union
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Observer(ABC):
|
|
11
|
+
"""
|
|
12
|
+
Abstract base class that can be notified of property changes from an associated :obj:`Observable`.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def on_change(
|
|
17
|
+
self, source: object, property_name: str, old_value: object, new_value: object
|
|
18
|
+
) -> None:
|
|
19
|
+
"""Callback notifying of a change.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
source (object): object that triggered a property change
|
|
23
|
+
property_name (str): name of the changed property
|
|
24
|
+
old_value (object): old value of the named property
|
|
25
|
+
new_value (object): new value of the named property
|
|
26
|
+
"""
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class RecordingObserver(Observer):
|
|
31
|
+
"""
|
|
32
|
+
Observer that records all changes.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
property_filters: Optional[Union[str, List[str]]] = None,
|
|
38
|
+
timestamped: bool = False,
|
|
39
|
+
):
|
|
40
|
+
"""
|
|
41
|
+
Initializes a new recording obsever.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
properties (Optional[Union[str,List[str]]]): optional list of property names to record
|
|
45
|
+
timestamped (bool): True, if the changes shall be timestamped
|
|
46
|
+
"""
|
|
47
|
+
if isinstance(property_filters, str):
|
|
48
|
+
self.property_filters = [property_filters]
|
|
49
|
+
else:
|
|
50
|
+
self.property_filters = property_filters
|
|
51
|
+
self.changes = []
|
|
52
|
+
self.timestamped = timestamped
|
|
53
|
+
|
|
54
|
+
def on_change(
|
|
55
|
+
self, source: object, property_name: str, old_value: object, new_value: object
|
|
56
|
+
) -> None:
|
|
57
|
+
"""Callback notifying of a change.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
source (object): object that triggered a property change
|
|
61
|
+
property_name (str): name of the changed property
|
|
62
|
+
old_value (object): old value of the named property
|
|
63
|
+
new_value (object): new value of the named property
|
|
64
|
+
"""
|
|
65
|
+
if self.property_filters is None or property_name in self.property_filters:
|
|
66
|
+
change = {
|
|
67
|
+
"source": source,
|
|
68
|
+
"property_name": property_name,
|
|
69
|
+
"old_value": old_value,
|
|
70
|
+
"new_value": new_value,
|
|
71
|
+
}
|
|
72
|
+
if self.timestamped:
|
|
73
|
+
change["time"] = datetime.now(tz=timezone.utc)
|
|
74
|
+
self.changes.append(change)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class Observable(object):
|
|
78
|
+
"""
|
|
79
|
+
Base class that can register (add/remove) and notify observers of property changes.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(self):
|
|
83
|
+
"""
|
|
84
|
+
Initializes a new observable.
|
|
85
|
+
"""
|
|
86
|
+
# list of observers to be notified of events
|
|
87
|
+
self._observers = []
|
|
88
|
+
|
|
89
|
+
def add_observer(self, observer: Observer) -> None:
|
|
90
|
+
"""
|
|
91
|
+
Adds an observer to this observable.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
observer (:obj:`Observer`): observer to be added
|
|
95
|
+
"""
|
|
96
|
+
self._observers.append(observer)
|
|
97
|
+
|
|
98
|
+
def remove_observer(self, observer: Observer) -> Observer:
|
|
99
|
+
"""
|
|
100
|
+
Removes an observer from this observable.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
observer (:obj:`Observer`): obsever to be removed
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
:obj:`Observer`: removed observer
|
|
107
|
+
"""
|
|
108
|
+
return self._observers.remove(observer)
|
|
109
|
+
|
|
110
|
+
def notify_observers(
|
|
111
|
+
self, property_name: str, old_value: object, new_value: object
|
|
112
|
+
) -> None:
|
|
113
|
+
"""
|
|
114
|
+
Notifies observers of a property change.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
property_name (str): name of the changed property
|
|
118
|
+
old_value (object): old value of the named property
|
|
119
|
+
new_value (object): new value of the named property
|
|
120
|
+
"""
|
|
121
|
+
if old_value != new_value:
|
|
122
|
+
for observer in self._observers:
|
|
123
|
+
observer.on_change(self, property_name, old_value, new_value)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# Add after the existing Observer class
|
|
127
|
+
class MessageObserver(ABC):
|
|
128
|
+
"""
|
|
129
|
+
Abstract base class for message observers that can receive RabbitMQ messages.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
@abstractmethod
|
|
133
|
+
def on_message(self, ch, method, properties, body) -> None:
|
|
134
|
+
"""Callback for when a message is received.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
ch: Channel object
|
|
138
|
+
method: Method frame
|
|
139
|
+
properties: Message properties
|
|
140
|
+
body: Message body
|
|
141
|
+
"""
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class MessageObservable(Observable):
|
|
146
|
+
"""
|
|
147
|
+
Observable that can notify observers of received messages.
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
def __init__(self):
|
|
151
|
+
"""Initialize message observable"""
|
|
152
|
+
super().__init__()
|
|
153
|
+
self._message_observers = []
|
|
154
|
+
|
|
155
|
+
def add_message_observer(self, observer: MessageObserver) -> None:
|
|
156
|
+
"""Add a message observer.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
observer (MessageObserver): The observer to add
|
|
160
|
+
"""
|
|
161
|
+
self._message_observers.append(observer)
|
|
162
|
+
|
|
163
|
+
def remove_message_observer(self, observer: MessageObserver) -> None:
|
|
164
|
+
"""Remove a message observer.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
observer (MessageObserver): The observer to remove
|
|
168
|
+
"""
|
|
169
|
+
self._message_observers.remove(observer)
|
|
170
|
+
|
|
171
|
+
def notify_message_observers(self, ch, method, properties, body) -> None:
|
|
172
|
+
"""Notify all message observers about a received message.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
ch: Channel object
|
|
176
|
+
method: Method frame
|
|
177
|
+
properties: Message properties
|
|
178
|
+
body: Message body
|
|
179
|
+
"""
|
|
180
|
+
for observer in self._message_observers:
|
|
181
|
+
observer.on_message(ch, method, properties, body)
|
nost_tools/publisher.py
CHANGED
|
@@ -1,141 +1,141 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Provides utility classes to help applications bind behavior to temporal events.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from abc import ABC, abstractmethod
|
|
6
|
-
from datetime import datetime, timedelta
|
|
7
|
-
from typing import TYPE_CHECKING
|
|
8
|
-
|
|
9
|
-
from .observer import Observer
|
|
10
|
-
from .simulator import Mode, Simulator
|
|
11
|
-
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from .application import Application
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class ScenarioTimeIntervalPublisher(Observer, ABC):
|
|
17
|
-
"""
|
|
18
|
-
Publishes messages at a regular interval (scenario time).
|
|
19
|
-
|
|
20
|
-
Provides the simulation with time status messages, also refered to as 'heartbeat messages',
|
|
21
|
-
or 'simulation time statuses'.
|
|
22
|
-
|
|
23
|
-
Attributes:
|
|
24
|
-
app (:obj:`Application`): application to publish messages
|
|
25
|
-
time_status_step (:obj:`timedelta`): scenario duration between time status messages
|
|
26
|
-
time_status_init (:obj:`datetime`): scenario time for first time status message
|
|
27
|
-
"""
|
|
28
|
-
|
|
29
|
-
def __init__(
|
|
30
|
-
self,
|
|
31
|
-
app: "Application",
|
|
32
|
-
time_status_step: timedelta = None,
|
|
33
|
-
time_status_init: datetime = None,
|
|
34
|
-
):
|
|
35
|
-
"""
|
|
36
|
-
Initializes a new scenario time interval publisher.
|
|
37
|
-
|
|
38
|
-
Args:
|
|
39
|
-
app (:obj:`Application`): application to publish messages
|
|
40
|
-
time_status_step (:obj:`timedelta`): scenario duration between time status messages
|
|
41
|
-
time_status_init (:obj:`datetime`): scenario time for first time status message
|
|
42
|
-
"""
|
|
43
|
-
self.app = app
|
|
44
|
-
self.time_status_step = time_status_step
|
|
45
|
-
self.time_status_init = time_status_init
|
|
46
|
-
self._next_time_status = None
|
|
47
|
-
# TODO: consider adding the `publish_message` callable as an argument rather than abstract method
|
|
48
|
-
|
|
49
|
-
@abstractmethod
|
|
50
|
-
def publish_message(self) -> None:
|
|
51
|
-
"""
|
|
52
|
-
Publishes a message.
|
|
53
|
-
"""
|
|
54
|
-
pass
|
|
55
|
-
|
|
56
|
-
def on_change(
|
|
57
|
-
self, source: object, property_name: str, old_value: object, new_value: object
|
|
58
|
-
) -> None:
|
|
59
|
-
"""
|
|
60
|
-
Publishes a message after a designated interval of scenario time.
|
|
61
|
-
|
|
62
|
-
Args:
|
|
63
|
-
source (object): observable that triggered the change
|
|
64
|
-
property_name (str): name of the changed property
|
|
65
|
-
old_value (obj): old value of the named property
|
|
66
|
-
new_value (obj): new value of the named property
|
|
67
|
-
"""
|
|
68
|
-
if property_name == Simulator.PROPERTY_MODE and new_value == Mode.INITIALIZED:
|
|
69
|
-
if self.time_status_init is None:
|
|
70
|
-
self._next_time_status = self.app.simulator.get_init_time()
|
|
71
|
-
else:
|
|
72
|
-
self._next_time_status = self.time_status_init
|
|
73
|
-
elif property_name == Simulator.PROPERTY_TIME:
|
|
74
|
-
while self._next_time_status <= new_value:
|
|
75
|
-
self.publish_message()
|
|
76
|
-
if self.time_status_step is None:
|
|
77
|
-
self._next_time_status += self.app.simulator.get_time_step()
|
|
78
|
-
else:
|
|
79
|
-
self._next_time_status += self.time_status_step
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
class WallclockTimeIntervalPublisher(Observer, ABC):
|
|
83
|
-
"""
|
|
84
|
-
Publishes messages at a regular interval (wallclock time).
|
|
85
|
-
|
|
86
|
-
Attributes:
|
|
87
|
-
app (:obj:`Application`): application to publish messages
|
|
88
|
-
time_status_step (:obj:`timedelta`): wallclock duration between time status messages
|
|
89
|
-
time_status_init (:obj:`datetime`): wallclock time for first time status message
|
|
90
|
-
"""
|
|
91
|
-
|
|
92
|
-
def __init__(
|
|
93
|
-
self,
|
|
94
|
-
app: "Application",
|
|
95
|
-
time_status_step: timedelta = None,
|
|
96
|
-
time_status_init: datetime = None,
|
|
97
|
-
):
|
|
98
|
-
"""
|
|
99
|
-
Initializes a new wallclock time interval publisher.
|
|
100
|
-
|
|
101
|
-
Args:
|
|
102
|
-
app (:obj:`Application`): application to publish messages
|
|
103
|
-
time_status_step (:obj:`timedelta`): wallclock duration between time status messages
|
|
104
|
-
time_status_init (:obj:`datetime`): wallclock time for first time status message
|
|
105
|
-
"""
|
|
106
|
-
self.app = app
|
|
107
|
-
self.time_status_step = time_status_step
|
|
108
|
-
self.time_status_init = time_status_init
|
|
109
|
-
self._next_time_status = None
|
|
110
|
-
|
|
111
|
-
@abstractmethod
|
|
112
|
-
def publish_message(self) -> None:
|
|
113
|
-
"""
|
|
114
|
-
Publishes a message.
|
|
115
|
-
"""
|
|
116
|
-
pass
|
|
117
|
-
|
|
118
|
-
def on_change(self, source, property_name, old_value, new_value):
|
|
119
|
-
"""
|
|
120
|
-
Publishes a message after a designated interval of scenario time.
|
|
121
|
-
|
|
122
|
-
Args:
|
|
123
|
-
source (object): observable that triggered the change
|
|
124
|
-
property_name (str): name of the changed property
|
|
125
|
-
old_value (obj): old value of the named property
|
|
126
|
-
new_value (obj): new value of the named property
|
|
127
|
-
"""
|
|
128
|
-
if property_name == Simulator.PROPERTY_MODE and new_value == Mode.INITIALIZED:
|
|
129
|
-
if self.time_status_init is None:
|
|
130
|
-
self._next_time_status = self.app.simulator.get_wallclock_time()
|
|
131
|
-
else:
|
|
132
|
-
self._next_time_status = self.time_status_init
|
|
133
|
-
elif property_name == Simulator.PROPERTY_TIME:
|
|
134
|
-
while self._next_time_status <= self.app.simulator.get_wallclock_time():
|
|
135
|
-
self.publish_message()
|
|
136
|
-
if self.time_status_step is None:
|
|
137
|
-
self._next_time_status += (
|
|
138
|
-
self.app.simulator.get_wallclock_time_step()
|
|
139
|
-
)
|
|
140
|
-
else:
|
|
141
|
-
self._next_time_status += self.time_status_step
|
|
1
|
+
"""
|
|
2
|
+
Provides utility classes to help applications bind behavior to temporal events.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from datetime import datetime, timedelta
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from .observer import Observer
|
|
10
|
+
from .simulator import Mode, Simulator
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from .application import Application
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ScenarioTimeIntervalPublisher(Observer, ABC):
|
|
17
|
+
"""
|
|
18
|
+
Publishes messages at a regular interval (scenario time).
|
|
19
|
+
|
|
20
|
+
Provides the simulation with time status messages, also refered to as 'heartbeat messages',
|
|
21
|
+
or 'simulation time statuses'.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
app (:obj:`Application`): application to publish messages
|
|
25
|
+
time_status_step (:obj:`timedelta`): scenario duration between time status messages
|
|
26
|
+
time_status_init (:obj:`datetime`): scenario time for first time status message
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
app: "Application",
|
|
32
|
+
time_status_step: timedelta = None,
|
|
33
|
+
time_status_init: datetime = None,
|
|
34
|
+
):
|
|
35
|
+
"""
|
|
36
|
+
Initializes a new scenario time interval publisher.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
app (:obj:`Application`): application to publish messages
|
|
40
|
+
time_status_step (:obj:`timedelta`): scenario duration between time status messages
|
|
41
|
+
time_status_init (:obj:`datetime`): scenario time for first time status message
|
|
42
|
+
"""
|
|
43
|
+
self.app = app
|
|
44
|
+
self.time_status_step = time_status_step
|
|
45
|
+
self.time_status_init = time_status_init
|
|
46
|
+
self._next_time_status = None
|
|
47
|
+
# TODO: consider adding the `publish_message` callable as an argument rather than abstract method
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def publish_message(self) -> None:
|
|
51
|
+
"""
|
|
52
|
+
Publishes a message.
|
|
53
|
+
"""
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
def on_change(
|
|
57
|
+
self, source: object, property_name: str, old_value: object, new_value: object
|
|
58
|
+
) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Publishes a message after a designated interval of scenario time.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
source (object): observable that triggered the change
|
|
64
|
+
property_name (str): name of the changed property
|
|
65
|
+
old_value (obj): old value of the named property
|
|
66
|
+
new_value (obj): new value of the named property
|
|
67
|
+
"""
|
|
68
|
+
if property_name == Simulator.PROPERTY_MODE and new_value == Mode.INITIALIZED:
|
|
69
|
+
if self.time_status_init is None:
|
|
70
|
+
self._next_time_status = self.app.simulator.get_init_time()
|
|
71
|
+
else:
|
|
72
|
+
self._next_time_status = self.time_status_init
|
|
73
|
+
elif property_name == Simulator.PROPERTY_TIME:
|
|
74
|
+
while self._next_time_status <= new_value:
|
|
75
|
+
self.publish_message()
|
|
76
|
+
if self.time_status_step is None:
|
|
77
|
+
self._next_time_status += self.app.simulator.get_time_step()
|
|
78
|
+
else:
|
|
79
|
+
self._next_time_status += self.time_status_step
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class WallclockTimeIntervalPublisher(Observer, ABC):
|
|
83
|
+
"""
|
|
84
|
+
Publishes messages at a regular interval (wallclock time).
|
|
85
|
+
|
|
86
|
+
Attributes:
|
|
87
|
+
app (:obj:`Application`): application to publish messages
|
|
88
|
+
time_status_step (:obj:`timedelta`): wallclock duration between time status messages
|
|
89
|
+
time_status_init (:obj:`datetime`): wallclock time for first time status message
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def __init__(
|
|
93
|
+
self,
|
|
94
|
+
app: "Application",
|
|
95
|
+
time_status_step: timedelta = None,
|
|
96
|
+
time_status_init: datetime = None,
|
|
97
|
+
):
|
|
98
|
+
"""
|
|
99
|
+
Initializes a new wallclock time interval publisher.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
app (:obj:`Application`): application to publish messages
|
|
103
|
+
time_status_step (:obj:`timedelta`): wallclock duration between time status messages
|
|
104
|
+
time_status_init (:obj:`datetime`): wallclock time for first time status message
|
|
105
|
+
"""
|
|
106
|
+
self.app = app
|
|
107
|
+
self.time_status_step = time_status_step
|
|
108
|
+
self.time_status_init = time_status_init
|
|
109
|
+
self._next_time_status = None
|
|
110
|
+
|
|
111
|
+
@abstractmethod
|
|
112
|
+
def publish_message(self) -> None:
|
|
113
|
+
"""
|
|
114
|
+
Publishes a message.
|
|
115
|
+
"""
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
def on_change(self, source, property_name, old_value, new_value):
|
|
119
|
+
"""
|
|
120
|
+
Publishes a message after a designated interval of scenario time.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
source (object): observable that triggered the change
|
|
124
|
+
property_name (str): name of the changed property
|
|
125
|
+
old_value (obj): old value of the named property
|
|
126
|
+
new_value (obj): new value of the named property
|
|
127
|
+
"""
|
|
128
|
+
if property_name == Simulator.PROPERTY_MODE and new_value == Mode.INITIALIZED:
|
|
129
|
+
if self.time_status_init is None:
|
|
130
|
+
self._next_time_status = self.app.simulator.get_wallclock_time()
|
|
131
|
+
else:
|
|
132
|
+
self._next_time_status = self.time_status_init
|
|
133
|
+
elif property_name == Simulator.PROPERTY_TIME:
|
|
134
|
+
while self._next_time_status <= self.app.simulator.get_wallclock_time():
|
|
135
|
+
self.publish_message()
|
|
136
|
+
if self.time_status_step is None:
|
|
137
|
+
self._next_time_status += (
|
|
138
|
+
self.app.simulator.get_wallclock_time_step()
|
|
139
|
+
)
|
|
140
|
+
else:
|
|
141
|
+
self._next_time_status += self.time_status_step
|