juniconnlib-core 1.1.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.
@@ -0,0 +1,81 @@
1
+ from agentlib.utils.plugin_import import ModuleImport
2
+
3
+ import juniconnlib_core._version as package_version
4
+ from juniconnlib_core.modules import (
5
+ devtools,
6
+ fiware_iota_communicators,
7
+ fiware_iota_device_provisioner,
8
+ fiware_ocb_communicators,
9
+ fiware_ocb_entity_provisioner,
10
+ fiware_subscriptions,
11
+ influxdb_writer,
12
+ json_schema_parser,
13
+ )
14
+
15
+ version = package_version.version
16
+ __version__ = package_version.__version__
17
+ __version_tuple__ = package_version.__version_tuple__
18
+ version_tuple = package_version.version_tuple
19
+
20
+ __all__ = [
21
+ "__version__",
22
+ "__version_tuple__",
23
+ "version",
24
+ "version_tuple",
25
+ "MODULE_TYPES",
26
+ ]
27
+
28
+
29
+ # This property MODULE_TYPES has to exist with exactly this name, so that the plugin
30
+ # is usable in the agentlib
31
+
32
+ MODULE_TYPES = {
33
+ "json_schema_parser": ModuleImport(
34
+ import_path="juniconnlib_core.modules.json_schema_parser",
35
+ class_name=json_schema_parser.GeneralJsonSchemaParser.__name__,
36
+ ),
37
+ "mqtt_communicator_send_to_iota": ModuleImport(
38
+ import_path="juniconnlib_core.modules.fiware_iota_communicators",
39
+ class_name=fiware_iota_communicators.MQTTCommunicatorSendToIoTA.__name__,
40
+ ),
41
+ "mqtt_communicator_receive_from_iota": ModuleImport(
42
+ import_path="juniconnlib_core.modules.fiware_iota_communicators",
43
+ class_name=fiware_iota_communicators.MQTTCommunicatorReceiveFromIoTA.__name__,
44
+ ),
45
+ "debug_listener": ModuleImport(
46
+ import_path="juniconnlib_core.modules.devtools",
47
+ class_name=devtools.DebugListener.__name__,
48
+ ),
49
+ "debug_sender": ModuleImport(
50
+ import_path="juniconnlib_core.modules.devtools",
51
+ class_name=devtools.DebugSender.__name__,
52
+ ),
53
+ "fiware_iota_device_provisioner": ModuleImport(
54
+ import_path="juniconnlib_core.modules.fiware_iota_device_provisioner",
55
+ class_name=fiware_iota_device_provisioner.FiwareIoTADeviceProvisioner.__name__,
56
+ ),
57
+ "influxdb_writer": ModuleImport(
58
+ import_path="juniconnlib_core.modules.influxdb_writer",
59
+ class_name=influxdb_writer.InfluxdbWriter.__name__,
60
+ ),
61
+ "fiware_ocb_entity_provisioner": ModuleImport(
62
+ import_path="juniconnlib_core.modules.fiware_ocb_entity_provisioner",
63
+ class_name=fiware_ocb_entity_provisioner.FiwareOCBEntityProvisioner.__name__,
64
+ ),
65
+ "fiware_mqtt_subscription_handler": ModuleImport(
66
+ import_path="juniconnlib_core.modules.fiware_subscriptions",
67
+ class_name=fiware_subscriptions.FiwareMQTTSubscriptionHandler.__name__,
68
+ ),
69
+ "fiware_http_subscription_handler": ModuleImport(
70
+ import_path="juniconnlib_core.modules.fiware_subscriptions",
71
+ class_name=fiware_subscriptions.FiwareHTTPSubscriptionHandler.__name__,
72
+ ),
73
+ "mqtt_communicator_receive_from_ocb": ModuleImport(
74
+ import_path="juniconnlib_core.modules.fiware_ocb_communicators",
75
+ class_name=fiware_ocb_communicators.MQTTCommunicatorReceiveFromOCB.__name__,
76
+ ),
77
+ "http_communicator_send_to_ocb": ModuleImport(
78
+ import_path="juniconnlib_core.modules.fiware_ocb_communicators",
79
+ class_name=fiware_ocb_communicators.HTTPCommunicatorSendToOCB.__name__,
80
+ ),
81
+ }
@@ -0,0 +1,34 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
12
+
13
+ TYPE_CHECKING = False
14
+ if TYPE_CHECKING:
15
+ from typing import Tuple
16
+ from typing import Union
17
+
18
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
20
+ else:
21
+ VERSION_TUPLE = object
22
+ COMMIT_ID = object
23
+
24
+ version: str
25
+ __version__: str
26
+ __version_tuple__: VERSION_TUPLE
27
+ version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
30
+
31
+ __version__ = version = '1.1.1'
32
+ __version_tuple__ = version_tuple = (1, 1, 1)
33
+
34
+ __commit_id__ = commit_id = None
File without changes
@@ -0,0 +1,150 @@
1
+ import datetime as dt
2
+ import json
3
+ import os
4
+ import re
5
+ from pathlib import Path
6
+ from typing import Any, Dict, Optional, Type, Union
7
+
8
+ from fidere.models import Identifier
9
+ from pydantic import BaseModel, ConfigDict, Field, field_serializer
10
+
11
+
12
+ def timestamp_with_timezone():
13
+ return dt.datetime.now(tz=dt.timezone.utc)
14
+
15
+
16
+ class Location(BaseModel):
17
+ """Representation of a single location.
18
+
19
+ It can either identify a specific room together with the building
20
+ and precise wing it is situated in or a specific wing together with
21
+ the according building.
22
+
23
+ The class is mainly planned to be used in context of the FZJ campus.
24
+ """
25
+
26
+ building: str = Field(
27
+ description="Building number in a standardized format consisting "
28
+ "of just 4"
29
+ "e.g. 2.1 -> 0210, 12.15 -> 1215, 9.7 -> 0970, etc.",
30
+ )
31
+ wing: str = Field(
32
+ description="Building wing, e.g. U, V, Z. The wing is mandatory."
33
+ )
34
+ room: str | None = Field(default=None, description="Room number")
35
+
36
+ class Config:
37
+ extra = "ignore"
38
+
39
+ @classmethod
40
+ def number2code(cls: Type[BaseModel], building_number: str | int) -> str:
41
+ """This function transforms buildings names like "02.1", "12.15"
42
+ into the standardized format "0210", "1215".
43
+
44
+ For more information on the standardized format, please take a
45
+ look at :class:juniconnlib_core.data_models.fiware_models.BuildingNumber
46
+
47
+ Args:
48
+ building_number (str, int): number of the building
49
+
50
+ Returns:
51
+ building code in the standardized format, e.g. 0210, 1215, 0970
52
+ """
53
+ if re.match(r"^\d{4}(\D*)", building_number):
54
+ return building_number
55
+ # return '{0[0]:0>2}{0[1]:0<2}'.format(building_number.split('.'))
56
+ # simpler to understand is below:
57
+ # (Does the replacement by adding '0' only if after removing ".", we don't have a 4-digit number.)
58
+ # We keep the building wing, if present.
59
+ return re.sub(
60
+ r"^(\d{3})(\D*)$", r"\g<1>0\g<2>", building_number.replace(".", "")
61
+ )
62
+
63
+
64
+ class AttributeUpdate(BaseModel):
65
+ """Representation of an attribute update of a FIWARE entity.
66
+
67
+ This is meant to be used as a container for all necessary information
68
+ needed to exchange information on updates for or from FIWARE attributes"""
69
+
70
+ entity_id: str = Field(description="FIWARE entity identifier")
71
+ entity_type: str = Field(description="FIWARE entity type")
72
+ payload: dict[str, Any] = Field(description="MQTT payload")
73
+ timestamp: dt.datetime | None = Field(
74
+ description="Timestamp when the attribute update was initiated",
75
+ default_factory=timestamp_with_timezone,
76
+ )
77
+ additional_data: dict[str, Any] | None = Field(
78
+ default={},
79
+ description="Additional data that can be added to the for other "
80
+ "modules to work upon",
81
+ )
82
+ model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid")
83
+
84
+ @field_serializer("timestamp")
85
+ def serialize_datetime(self, timestamp: dt.datetime) -> str:
86
+ return timestamp.isoformat(timespec="milliseconds")
87
+
88
+
89
+ class IotaMqttMessage(BaseModel):
90
+ """Contains data for an MQTT message for the IoT agent such as
91
+ topic, payload, etc."""
92
+
93
+ device_id: str = Field(description="FIWARE device ID")
94
+ device_type: Optional[str] = Field(
95
+ default=None, description="FIWARE device type"
96
+ )
97
+ apikey: Optional[str] = Field(
98
+ default=None, description="IoT agent API key"
99
+ )
100
+ payload: Dict = Field(description="MQTT payload")
101
+
102
+ def topic(self) -> str:
103
+ """Creates a MQTT-topic in the format required by the FIWARE MQTT IoT
104
+ agent.
105
+
106
+ Returns:
107
+ MQTT-topic compatible to the requirements of the FIWARE IoT
108
+ agent.
109
+ """
110
+ return f"/json/{self.apikey}/{self.device_id}/attrs"
111
+
112
+
113
+ class CredentialsConfig(BaseModel):
114
+ """Model class for storing username and password (e.g. for use with MQTT)"""
115
+
116
+ username: str
117
+ password: str
118
+
119
+ @classmethod
120
+ def load_from_file(
121
+ cls: Type[BaseModel], credentials_file: str | Path
122
+ ) -> BaseModel:
123
+ """Loads credentials from a file and returns an instance of
124
+ :class:`CredentialsConfig`
125
+
126
+ Args:
127
+ credentials_file (str, Path): path to credentials file. The file
128
+ should be in JSON format and contain at least the keys
129
+ "username" and "password".
130
+
131
+ Returns:
132
+ CredentialsConfig: Credentials read from file and validated
133
+ """
134
+ credentials_file = credentials_file
135
+ if not os.path.exists(credentials_file):
136
+ raise FileNotFoundError(
137
+ f"Could not find keycloak credentials file {credentials_file}!"
138
+ )
139
+ with open(credentials_file) as json_input:
140
+ json_data = json.load(json_input)
141
+
142
+ return CredentialsConfig.model_validate(json_data)
143
+
144
+
145
+ class JsonSchemaInstance(BaseModel):
146
+ """A JSON schema to be used with the `JsonSchemaParser` module."""
147
+
148
+ model_config = ConfigDict(arbitrary_types_allowed=True, allow_extra=False)
149
+ instance: dict[str, Any]
150
+ identifier: Union[str, Identifier]
@@ -0,0 +1,277 @@
1
+ import re
2
+ from typing import Any, Literal, Type
3
+
4
+ from filip.models import FiwareHeader
5
+ from filip.models.ngsi_v2.context import ContextAttribute, ContextEntity
6
+ from pydantic import Field, field_validator
7
+
8
+
9
+ def generate_entity_id(entity_type: str, entity_content: Any) -> str:
10
+ """Generate an entity id based on the entity type and entity content.
11
+
12
+ Args:
13
+ entity_type (str): Type of the entity, e.g. Building, Wing, MessdasMeter
14
+ entity_content (Any): Hashable object, e.g. 0970, 0130_W, 0130_W_2001A, etc.
15
+
16
+ Returns:
17
+ unique FIWARE entity id based on a SHAKE256 hash of the `entity_content`.
18
+ """
19
+ from hashlib import shake_256
20
+
21
+ plain_id = f"{entity_type}_{entity_content}".encode("utf-8")
22
+ hash_id = shake_256(plain_id).hexdigest(16)
23
+
24
+ return f"urn:ngsi-ld:{entity_type}:{hash_id}"
25
+
26
+
27
+ def generate_api_key(
28
+ fiware_header: FiwareHeader, entity_type: str, resource: str = "/iot/json"
29
+ ) -> str:
30
+ """Generate a unique API key based on the FIWARE service, entity type and
31
+ resource.
32
+
33
+ Args:
34
+ fiware_header (FiwareHeader): FIWARE header.
35
+ entity_type (str): Type of the entity or more specific device.
36
+ resource (str): Resource of the devices service group (default is "/iot/json").
37
+
38
+ Returns:
39
+ Unique API key.
40
+ """
41
+ from hashlib import shake_256
42
+
43
+ plain_id = (
44
+ f"{fiware_header.service}_{fiware_header.service_path}_"
45
+ f"{entity_type}_{resource}"
46
+ ).encode("utf-8")
47
+ return shake_256(plain_id).hexdigest(16)
48
+
49
+
50
+ class BuildingNumber(ContextAttribute):
51
+ """A string of four numbers to identify a building. The
52
+ :class:`BuildingNumber` is meant to be used as an attribute in a
53
+ `ContextEntity`. The building number should be a string consisting of
54
+ exactly four numbers. For any further usage this formatting of four digits
55
+ is called the "standardized format".
56
+
57
+ A certain amount of conversion between incompatible formats is done
58
+ as shown in examples.
59
+
60
+ Examples:
61
+ 9.7 -> 0970
62
+ 10.21 -> 1021
63
+ 02.1 -> 0210
64
+ """
65
+
66
+ value: str = Field(
67
+ title="Building number",
68
+ description="Building number in the standardized format, e.g. 0970, 1021, etc.",
69
+ )
70
+
71
+ @field_validator("value")
72
+ def validate_number(cls, building_number: str) -> str:
73
+ """Validate building number. It must be in the standardized format,
74
+ e.g. 0970, 1021, etc.
75
+
76
+ For more information on the standardized format, please take a
77
+ look at :class:juniconnlib_core.data_models.fiware_models.BuildingNumber
78
+
79
+ Args:
80
+ building_number: building number to validate.
81
+
82
+ Returns:
83
+ str: building number in case of the corrected format.
84
+
85
+ Raises:
86
+ ValueError: If building number is not in the
87
+ standard format (four digits).
88
+ """
89
+ number = BuildingNumber.normalize_building_number_format(
90
+ building_number
91
+ )
92
+ if number != building_number:
93
+ raise ValueError(
94
+ "Building number has a wrong format! Expected the "
95
+ "standardized format, e.g. 0970, 1021, etc."
96
+ )
97
+ return building_number
98
+
99
+ @classmethod
100
+ def normalize_building_number_format(cls, building_number: str) -> str:
101
+ """Format the supplied `building_number` in the standardized format.
102
+
103
+ In practice, the buildings are named similar to: 02.1, 12.15, 09.7
104
+ In the standardized format: 0210, 1215, 0970. Some have 15.83-A,
105
+ so the regex can be a little more complex. It's idempotent if
106
+ building_number is already in standardized format or any other
107
+ format lacking a '.'
108
+
109
+ Args:
110
+ building_number (str): e.g. 09.7
111
+
112
+ Returns:
113
+ str: The normalized number e.g. 0970
114
+ """
115
+ if re.match(r"^\d{4}(\D*)", building_number):
116
+ return building_number
117
+
118
+ parts = building_number.split(".")
119
+ # add leading or trailing zero if necessary
120
+ area_number = re.sub(r"^(\d)$", r"0\g<1>", parts[0])
121
+ part2 = re.sub(r"^(\d)(\D*)$", r"\g<1>0\g<2>", parts[1])
122
+
123
+ return f"{area_number}{part2}"
124
+
125
+
126
+ class Building(ContextEntity):
127
+ """A data model based `ContextEntity` used as representation of a single
128
+ building as FIWARE entity. It holds a number for the building in addition
129
+ to some other data like area.
130
+
131
+ For more information on the standardized format, please take a
132
+ look at :class:juniconnlib_core.data_models.fiware_models.BuildingNumber
133
+
134
+ This class is meant to be used in scope of the FZJ campus, but can
135
+ easily be used for buildings outside this scope as well."""
136
+
137
+ type: Literal["Building"] = Field(description="Entity type 'Building'")
138
+ number: BuildingNumber | ContextAttribute = Field(
139
+ title="Building number",
140
+ description="Building number in the standardized format, e.g. "
141
+ "0970, 1021, etc. or or `ContextAttribute`",
142
+ )
143
+ area: ContextAttribute = Field(
144
+ description="`ContextAttribute`-object containing the area of the "
145
+ "building in [m^2]"
146
+ )
147
+ year: ContextAttribute = Field(
148
+ description="`ContextAttribute`-object containing the year "
149
+ "of the building."
150
+ )
151
+
152
+ @field_validator("number")
153
+ def cast_to_building_number(cls, v):
154
+ """Convert a possible `ContextAttribute` to a `BuildingNumber` instance"""
155
+ if isinstance(v, ContextAttribute):
156
+ v = BuildingNumber(**dict(v))
157
+ return v
158
+
159
+ @classmethod
160
+ def generate_building_id(
161
+ cls: Type["Building"], building_number: str
162
+ ) -> "str":
163
+ """Generates a FIWARE building ID from a building number.
164
+
165
+ Args:
166
+ building_number (str): building number in the standardized
167
+ format, e.g. 0970, 1021, etc.
168
+
169
+ Returns:
170
+ FIWARE building entity ID.
171
+ """
172
+ entity_content = BuildingNumber.normalize_building_number_format(
173
+ building_number
174
+ )
175
+ return generate_entity_id(
176
+ "Building", entity_content=entity_content
177
+ ) # TODO: should use the type attribute instead of the hardcoded string, but that is not working?
178
+
179
+ @field_validator("number", mode="before")
180
+ def convert_from_context_entity(cls, value):
181
+ if isinstance(value, ContextAttribute):
182
+ return value.model_dump()
183
+ return value
184
+
185
+
186
+ class Wing(ContextEntity):
187
+ type: Literal["Wing"] = Field(description="Has to be 'Wing'")
188
+ # _WING_TYPE = 'Wing'
189
+ refBuilding: ContextAttribute = Field(description="number of the building")
190
+ number: ContextAttribute = Field(
191
+ description="letter of the wing, e.g. U, V, W"
192
+ )
193
+
194
+ @field_validator("number")
195
+ def validate_number(cls, number: ContextAttribute):
196
+ """Validate building number.
197
+
198
+ Args:
199
+ number (ContextAttribute): Wing number as ContextAttribute
200
+
201
+ Returns:
202
+ ContextAttribute: building number
203
+
204
+ Raises:
205
+ ValueError: In case the value of the ContextAttribute is
206
+ not an uppercase letter
207
+ """
208
+ if not number.value.isupper():
209
+ raise ValueError(
210
+ "Wing number has a wrong format! Expected upper case, e.g. U, V, W etc."
211
+ )
212
+ return number
213
+
214
+ @classmethod
215
+ def generate_wing_id(
216
+ cls: Type["Wing"], wing_number: str, building_number: str
217
+ ) -> "str":
218
+ """Generates a FIWARE wing id by hashing a wing letter and building number.
219
+
220
+ Args:
221
+ wing_number (str): letter of the wing, e.g. U, V, W
222
+ building_number (str): building number in the standardized
223
+
224
+ Returns:
225
+ str: wing entity ID
226
+ """
227
+ normalized_building_number = (
228
+ BuildingNumber.normalize_building_number_format(building_number)
229
+ )
230
+ normalized_wing_number = wing_number.upper()
231
+ wing_entity_content = (
232
+ f"{normalized_building_number}_{normalized_wing_number}"
233
+ )
234
+ return generate_entity_id(
235
+ entity_type="Wing", entity_content=wing_entity_content
236
+ )
237
+
238
+
239
+ class Room(ContextEntity):
240
+ type: Literal["Room"] = Field(description="Has to be 'Room'")
241
+ # _ROOM_TYPE = 'Room'
242
+ refBuilding: ContextAttribute = Field(description="id of the building")
243
+ refWing: ContextAttribute = Field(description="id of the wing")
244
+ number: ContextAttribute = Field(
245
+ description="number of the room, e.g. 300, 4002, 120a"
246
+ )
247
+
248
+ @field_validator("number")
249
+ def validate_number(cls, number: ContextAttribute):
250
+ """Room numbers have very different formats, so for now no validation
251
+ is done."""
252
+ return number
253
+
254
+ @classmethod
255
+ def generate_room_id(
256
+ cls: Type["Room"],
257
+ room_number: str,
258
+ wing_number: str,
259
+ building_number: str,
260
+ ) -> "str":
261
+ """Generates a FIWARE room id by hashing a room descriptor, wing
262
+ letter and building number.
263
+
264
+ Args:
265
+ room_number (str): number of the room, e.g. 300, 4002, 120a
266
+ wing_number (str): number of the wing, e.g. U, V, W
267
+ building_number (str): building number in the standardized format
268
+ """
269
+ normalized_building_number = (
270
+ BuildingNumber.normalize_building_number_format(building_number)
271
+ )
272
+ normalized_wing_number = wing_number.upper()
273
+ normalized_room_number = room_number.upper()
274
+ room_entity_content = f"{normalized_building_number}_{normalized_wing_number}_{normalized_room_number}"
275
+ return generate_entity_id(
276
+ entity_type="Room", entity_content=room_entity_content
277
+ )
File without changes
@@ -0,0 +1,106 @@
1
+ from itertools import cycle
2
+ from typing import Any, Dict, List
3
+
4
+ from agentlib.core import Agent, AgentVariable, BaseModule, BaseModuleConfig
5
+ from pydantic import Field, ValidationError, field_validator
6
+
7
+ from juniconnlib_core.utils.logging import create_logger_for_module
8
+
9
+
10
+ class SendConfig(BaseModuleConfig):
11
+ delay: float = Field(
12
+ default=0,
13
+ ge=0,
14
+ description="Time delay to tick down before a value "
15
+ "should be send (again)",
16
+ )
17
+ values: List[dict[str, Any]] = Field(
18
+ None,
19
+ description="Variable to be send. Should have keys "
20
+ "'value' and 'alias'",
21
+ examples=[
22
+ '[{"value": ..., "alias": ...}, ' '{"value": ..., "alias": ...}]'
23
+ ],
24
+ )
25
+ repeat: bool = Field(
26
+ False,
27
+ description="Decision if messages should be send "
28
+ "repeatedly. Cycles through the list of "
29
+ "values.",
30
+ )
31
+
32
+ @field_validator("values")
33
+ def check_keys(cls, values):
34
+ for val in values:
35
+ if "value" not in val and "alias" not in val:
36
+ raise ValidationError(
37
+ "Provide 'value' and 'alias' for every "
38
+ "variable to be send"
39
+ )
40
+ return values
41
+
42
+
43
+ class DebugSender(BaseModule):
44
+ """General sending type module that can be used for debugging purposes.
45
+ It sends a list of `values` to the agent broker with a given `delay`"""
46
+
47
+ config: SendConfig
48
+
49
+ def __init__(self, config: Dict, agent: Agent):
50
+ super().__init__(config=config, agent=agent)
51
+ # overwrite the logger of agent liv modules, since it has a fixed formatter
52
+ self.logger = create_logger_for_module(self)
53
+
54
+ self.loop = True
55
+ if self.config.repeat:
56
+ self.vals = cycle(self.config.values)
57
+ else:
58
+ self.vals = iter(self.config.values)
59
+
60
+ def process(self):
61
+ while True:
62
+ # try getting a value. If the iterator is exhausted
63
+ try:
64
+ val = next(self.vals)
65
+ self.logger.debug(f"Sending {val}")
66
+ except StopIteration:
67
+ self.logger.debug("All values have been sent")
68
+ yield self.env.event()
69
+
70
+ var = AgentVariable(**val, name="DevVar", source=self.source)
71
+ self.agent.data_broker.send_variable(var)
72
+ yield self.env.timeout(5)
73
+
74
+ def terminate(self):
75
+ self.loop = False
76
+ self.logger.info(f"Terminated {self.__class__.__name__}")
77
+
78
+ def register_callbacks(self):
79
+ """No callback necessary."""
80
+ ...
81
+ # self.agent.data_broker.register_callback()
82
+
83
+
84
+ class DebugListener(BaseModule):
85
+ """This module ist meant for debugging and development purposes.
86
+ It logs any variable on the broker on level DEBUG"""
87
+
88
+ config: BaseModuleConfig
89
+
90
+ def __init__(self, config: Dict, agent: Agent):
91
+ super().__init__(config=config, agent=agent)
92
+ # overwrite the logger of agent liv modules, since it has a fixed formatter
93
+ self.logger = create_logger_for_module(self)
94
+
95
+ def process(self):
96
+ yield self.env.event()
97
+
98
+ def terminate(self):
99
+ self.logger.info(f"Terminated {self.__class__.__name__}")
100
+
101
+ def register_callbacks(self):
102
+ """Registers a simple callback that logs ALL variables on the broker as
103
+ debug message."""
104
+ self.agent.data_broker.register_callback(
105
+ callback=lambda var: self.logger.debug(var)
106
+ )