flexCommunicator 0.1.0__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.
Files changed (53) hide show
  1. flexCommunicator/__init__.py +0 -0
  2. flexCommunicator/clientLibraries/__init__.py +0 -0
  3. flexCommunicator/clientLibraries/flcpy/__init__.py +0 -0
  4. flexCommunicator/clientLibraries/flcpy/action/__init__.py +0 -0
  5. flexCommunicator/clientLibraries/flcpy/action/action.py +85 -0
  6. flexCommunicator/clientLibraries/flcpy/action/action_client.py +70 -0
  7. flexCommunicator/clientLibraries/flcpy/action/goal_handle.py +50 -0
  8. flexCommunicator/clientLibraries/flcpy/codec/ROS2Codec.py +227 -0
  9. flexCommunicator/clientLibraries/flcpy/codec/__init__.py +0 -0
  10. flexCommunicator/clientLibraries/flcpy/commands/__init__.py +0 -0
  11. flexCommunicator/clientLibraries/flcpy/commands/commandProcessor.py +113 -0
  12. flexCommunicator/clientLibraries/flcpy/commands/composer.py +25 -0
  13. flexCommunicator/clientLibraries/flcpy/communication/CommunicationManager.py +579 -0
  14. flexCommunicator/clientLibraries/flcpy/communication/__init__.py +0 -0
  15. flexCommunicator/clientLibraries/flcpy/contracts/__init__.py +0 -0
  16. flexCommunicator/clientLibraries/flcpy/contracts/contractVariable.py +93 -0
  17. flexCommunicator/clientLibraries/flcpy/flexCloud/__init__.py +0 -0
  18. flexCommunicator/clientLibraries/flcpy/flexCloud/flexCloudConfiguration.py +16 -0
  19. flexCommunicator/clientLibraries/flcpy/flexCloud/flexCloudManager.py +162 -0
  20. flexCommunicator/clientLibraries/flcpy/flexCloud/flexCloudOrchestrator.py +248 -0
  21. flexCommunicator/clientLibraries/flcpy/flexCloud/flexCloudVariable.py +130 -0
  22. flexCommunicator/clientLibraries/flcpy/flexNode.py +892 -0
  23. flexCommunicator/clientLibraries/flcpy/fmi/__init__.py +0 -0
  24. flexCommunicator/clientLibraries/flcpy/fmi/fmuNode.py +166 -0
  25. flexCommunicator/clientLibraries/flcpy/fmi/node.py +360 -0
  26. flexCommunicator/clientLibraries/flcpy/knowledge/KnowledgeManager.py +288 -0
  27. flexCommunicator/clientLibraries/flcpy/knowledge/__init__.py +0 -0
  28. flexCommunicator/clientLibraries/flcpy/logging/LoggingAndTracking.py +159 -0
  29. flexCommunicator/clientLibraries/flcpy/logging/__init__.py +0 -0
  30. flexCommunicator/clientLibraries/flcpy/messages/StandardizedMessages.py +64 -0
  31. flexCommunicator/clientLibraries/flcpy/messages/__init__.py +0 -0
  32. flexCommunicator/clientLibraries/flcpy/node.py +486 -0
  33. flexCommunicator/clientLibraries/flcpy/security/__init__.py +0 -0
  34. flexCommunicator/clientLibraries/flcpy/security/security.py +189 -0
  35. flexCommunicator/clientLibraries/flcpy/service/__init__.py +0 -0
  36. flexCommunicator/clientLibraries/flcpy/service/client.py +58 -0
  37. flexCommunicator/clientLibraries/flcpy/service/service.py +67 -0
  38. flexCommunicator/clientLibraries/flcpy/transport/PropertyBinding.py +108 -0
  39. flexCommunicator/clientLibraries/flcpy/transport/PropertyRegistry.py +142 -0
  40. flexCommunicator/clientLibraries/flcpy/transport/__init__.py +0 -0
  41. flexCommunicator/clientLibraries/flcpy/utils/Timer.py +22 -0
  42. flexCommunicator/clientLibraries/flcpy/utils/__init__.py +0 -0
  43. flexCommunicator/clientLibraries/flcpy/utils/auxiliary.py +261 -0
  44. flexCommunicator/clientLibraries/flcpy/utils/constants.py +276 -0
  45. flexCommunicator/clientLibraries/flcpy/utils/decorators.py +70 -0
  46. flexCommunicator/clientLibraries/flcpy/validityFrame/__init__.py +0 -0
  47. flexCommunicator/clientLibraries/flcpy/validityFrame/runtimeMonitor.py +101 -0
  48. flexCommunicator/clientLibraries/flcpy/validityFrame/validityFrameManager.py +958 -0
  49. flexcommunicator-0.1.0.dist-info/METADATA +568 -0
  50. flexcommunicator-0.1.0.dist-info/RECORD +53 -0
  51. flexcommunicator-0.1.0.dist-info/WHEEL +5 -0
  52. flexcommunicator-0.1.0.dist-info/licenses/LICENSE +201 -0
  53. flexcommunicator-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,288 @@
1
+ #**************************************************************************
2
+ # * Copyright (C) 2025-present Bert Van Acker (B.MKR) <bva.bmkr@gmail.com>
3
+ # *
4
+ # * This file is part of the flexIA project.
5
+ # *
6
+ # * flexIA can not be copied and/or distributed without the express
7
+ # * permission of Bert Van Acker
8
+ # *************************************************************************
9
+ import time
10
+ import string
11
+ import random
12
+ import warnings
13
+
14
+ # Constants for configuration keys
15
+ KEY_KNOWLEDGE_TYPE = "knowledge_type"
16
+ KEY_HOST = "host"
17
+ KEY_PORT = "port"
18
+ KEY_DB = "db"
19
+ KEY_TOPIC = "topic"
20
+ KEY_BOOTSTRAP_SERVERS = "bootstrap_servers"
21
+
22
+ # Constants for backends
23
+ BACKEND_REDIS = "redis"
24
+ BACKEND_MEMCACHED = "memcached"
25
+ BACKEND_IGNITE = "ignite"
26
+ BACKEND_HAZELCAST = "hazelcast"
27
+ BACKEND_TARANTOOL = "tarantool"
28
+ BACKEND_AEROSPIKE = "aerospike"
29
+ BACKEND_KAFKA = "kafka"
30
+ BACKEND_SQLITE = "sqlite"
31
+ BACKEND_OPCUA = "opc_ua"
32
+
33
+ # Constants for SQLite queries and in-memory DB
34
+ SQLITE_MEMORY = ":memory:"
35
+ SQLITE_CREATE_TABLE = "CREATE TABLE kv (key TEXT PRIMARY KEY, value TEXT)"
36
+ SQLITE_INSERT_OR_REPLACE = "INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)"
37
+ SQLITE_SELECT_VALUE = "SELECT value FROM kv WHERE key=?"
38
+ SQLITE_DELETE_KEY = "DELETE FROM kv WHERE key=?"
39
+
40
+ # Other constants
41
+ ERROR_UNSUPPORTED_BACKEND = "Unsupported backend: {}"
42
+ DEFAULT_CACHE_NAME = "default"
43
+ AEROSPIKE_VALUE_FIELD = "value"
44
+
45
+ # Helper function to generate random strings (if needed)
46
+ def random_string(length=10):
47
+ return ''.join(random.choice(string.ascii_letters) for _ in range(length))
48
+
49
+
50
+ class KnowledgeManager:
51
+ def __init__(self, config):
52
+ """
53
+ Initialize the KnowledgeManager with a chosen backend.
54
+ Supported backends: redis, memcached, ignite, hazelcast, tarantool, aerospike, kafka, sqlite
55
+
56
+ :param config: A dictionary containing all configuration parameters needed by the backend.
57
+ """
58
+ self.backend = config[KEY_KNOWLEDGE_TYPE]
59
+ if self.backend == BACKEND_REDIS:
60
+ import redis
61
+ self.client = redis.Redis(host=config[KEY_HOST], port=config[KEY_PORT], db=config[KEY_DB])
62
+ elif self.backend == BACKEND_MEMCACHED:
63
+ import memcache
64
+ self.client = memcache.Client([(config[KEY_HOST], config[KEY_PORT])])
65
+ elif self.backend == BACKEND_IGNITE:
66
+ from pyignite import Client
67
+ self.client = Client()
68
+ self.client.connect(config[KEY_HOST], config[KEY_PORT])
69
+ elif self.backend == BACKEND_HAZELCAST:
70
+ import hazelcast
71
+ # Configuration for hazelcast client is passed via config.
72
+ self.client = hazelcast.HazelcastClient(**config)
73
+ elif self.backend == BACKEND_TARANTOOL:
74
+ import tarantool
75
+ self.client = tarantool.Connection(config[KEY_HOST], config[KEY_PORT])
76
+ elif self.backend == BACKEND_AEROSPIKE:
77
+ import aerospike
78
+ cfg = {'hosts': [(config[KEY_HOST], config[KEY_PORT])]}
79
+ self.client = aerospike.client(cfg).connect()
80
+ elif self.backend == BACKEND_KAFKA:
81
+ from kafka import KafkaProducer, KafkaConsumer
82
+ self.topic = config[KEY_TOPIC]
83
+ self.producer = KafkaProducer(bootstrap_servers=config[KEY_BOOTSTRAP_SERVERS])
84
+ self.consumer = KafkaConsumer(
85
+ self.topic,
86
+ bootstrap_servers=config[KEY_BOOTSTRAP_SERVERS],
87
+ auto_offset_reset='earliest',
88
+ consumer_timeout_ms=1000
89
+ )
90
+ elif self.backend == BACKEND_SQLITE:
91
+ import sqlite3
92
+ self.conn = sqlite3.connect(SQLITE_MEMORY)
93
+ self.cursor = self.conn.cursor()
94
+ self.cursor.execute(SQLITE_CREATE_TABLE)
95
+ else:
96
+ raise ValueError(ERROR_UNSUPPORTED_BACKEND.format(self.backend))
97
+
98
+ def write(self, key, value):
99
+ """Write a key-value pair to the backend."""
100
+ if self.backend == BACKEND_REDIS:
101
+ return self.client.set(key, value)
102
+ elif self.backend == BACKEND_MEMCACHED:
103
+ return self.client.set(key, value)
104
+ elif self.backend == BACKEND_IGNITE:
105
+ cache = self.client.get_or_create_cache(DEFAULT_CACHE_NAME)
106
+ return cache.put(key, value)
107
+ elif self.backend == BACKEND_HAZELCAST:
108
+ hz_map = self.client.get_map(DEFAULT_CACHE_NAME).blocking()
109
+ return hz_map.put(key, value)
110
+ elif self.backend == BACKEND_TARANTOOL:
111
+ # Assumes a space 'kv' exists with schema (key, value)
112
+ return self.client.insert('kv', (key, value))
113
+ elif self.backend == BACKEND_AEROSPIKE:
114
+ # Note: In Aerospike, the key should be a tuple; this is a simplified example.
115
+ return self.client.put(key, {AEROSPIKE_VALUE_FIELD: value})
116
+ elif self.backend == BACKEND_KAFKA:
117
+ # Kafka is a streaming system; here, writing means sending a message.
118
+ future = self.producer.send(self.topic, key=key.encode(), value=value.encode())
119
+ return future.get(timeout=10)
120
+ elif self.backend == BACKEND_SQLITE:
121
+ try:
122
+ self.cursor.execute(SQLITE_INSERT_OR_REPLACE, (key, value))
123
+ self.conn.commit()
124
+ return True
125
+ except Exception as e:
126
+ self.conn.rollback()
127
+ return False
128
+
129
+ def write_history(self, key, value):
130
+ """Write a key-value pair to the backend, push to list."""
131
+ try:
132
+ if self.backend == BACKEND_REDIS:
133
+ return self.client.rpush(key, value)
134
+ warnings.warn(
135
+ "write_history currently only supports the Redis backend.",
136
+ RuntimeWarning,
137
+ stacklevel=2,
138
+ )
139
+ except Exception as e:
140
+ warnings.warn(
141
+ f"Failed to write history for key '{key}': {e}",
142
+ RuntimeWarning,
143
+ stacklevel=2,
144
+ )
145
+
146
+ def read(self, key, queueSize=1):
147
+ """Read the value for a given key from the backend."""
148
+ if self.backend == BACKEND_REDIS:
149
+ return self.client.get(key)
150
+ elif self.backend == BACKEND_MEMCACHED:
151
+ return self.client.get(key)
152
+ elif self.backend == BACKEND_IGNITE:
153
+ cache = self.client.get_or_create_cache(DEFAULT_CACHE_NAME)
154
+ return cache.get(key)
155
+ elif self.backend == BACKEND_HAZELCAST:
156
+ hz_map = self.client.get_map(DEFAULT_CACHE_NAME).blocking()
157
+ return hz_map.get(key)
158
+ elif self.backend == BACKEND_TARANTOOL:
159
+ result = self.client.select('kv', 0, 1, 0, key)
160
+ return result[0][1] if result else None
161
+ elif self.backend == BACKEND_AEROSPIKE:
162
+ try:
163
+ (key_rec, metadata, record) = self.client.get(key)
164
+ return record.get(AEROSPIKE_VALUE_FIELD)
165
+ except Exception as e:
166
+ return None
167
+ elif self.backend == BACKEND_KAFKA:
168
+ # For Kafka, simulate reading by consuming one message from the topic.
169
+ for message in self.consumer:
170
+ return message.value.decode()
171
+ return None
172
+ elif self.backend == BACKEND_SQLITE:
173
+ self.cursor.execute(SQLITE_SELECT_VALUE, (key,))
174
+ row = self.cursor.fetchone()
175
+ return row[0] if row else None
176
+
177
+ def delete(self, key):
178
+ """Delete the key-value pair identified by key from the backend."""
179
+ if self.backend == BACKEND_REDIS:
180
+ return self.client.delete(key)
181
+ elif self.backend == BACKEND_MEMCACHED:
182
+ return self.client.delete(key)
183
+ elif self.backend == BACKEND_IGNITE:
184
+ cache = self.client.get_or_create_cache(DEFAULT_CACHE_NAME)
185
+ return cache.remove(key)
186
+ elif self.backend == BACKEND_HAZELCAST:
187
+ hz_map = self.client.get_map(DEFAULT_CACHE_NAME).blocking()
188
+ return hz_map.remove(key)
189
+ elif self.backend == BACKEND_TARANTOOL:
190
+ return self.client.delete('kv', key)
191
+ elif self.backend == BACKEND_AEROSPIKE:
192
+ try:
193
+ return self.client.remove(key)
194
+ except Exception as e:
195
+ return None
196
+ elif self.backend == BACKEND_KAFKA:
197
+ # Kafka does not support deleting individual messages.
198
+ return None
199
+ elif self.backend == BACKEND_SQLITE:
200
+ self.cursor.execute(SQLITE_DELETE_KEY, (key,))
201
+ self.conn.commit()
202
+ return True
203
+
204
+ def exists(self, key):
205
+ """Check whether a key exists in the backend."""
206
+ if self.backend == BACKEND_REDIS:
207
+ return self.client.exists(key)
208
+ elif self.backend == BACKEND_MEMCACHED:
209
+ return self.client.get(key) is not None
210
+ elif self.backend == BACKEND_IGNITE:
211
+ cache = self.client.get_or_create_cache(DEFAULT_CACHE_NAME)
212
+ return cache.get(key) is not None
213
+ elif self.backend == BACKEND_HAZELCAST:
214
+ hz_map = self.client.get_map(DEFAULT_CACHE_NAME).blocking()
215
+ return hz_map.contains_key(key)
216
+ elif self.backend == BACKEND_TARANTOOL:
217
+ result = self.client.select('kv', 0, 1, 0, key)
218
+ return bool(result)
219
+ elif self.backend == BACKEND_AEROSPIKE:
220
+ try:
221
+ self.client.get(key)
222
+ return True
223
+ except Exception as e:
224
+ return False
225
+ elif self.backend == BACKEND_KAFKA:
226
+ # Kafka does not provide a means to check for existence of a message by key.
227
+ return None
228
+ elif self.backend == BACKEND_SQLITE:
229
+ self.cursor.execute('SELECT 1 FROM kv WHERE key=?', (key,))
230
+ return self.cursor.fetchone() is not None
231
+
232
+
233
+ def benchmark_operations(backend, config, num_operations=1000):
234
+ print(f"\nBenchmarking backend: {backend.upper()} with {num_operations} operations")
235
+ try:
236
+ km = KnowledgeManager(config)
237
+ except Exception as e:
238
+ print(f"Could not initialize backend '{backend}': {e}")
239
+ return
240
+
241
+ keys = [f'key_{i}' for i in range(num_operations)]
242
+ value = "sample_value"
243
+
244
+ # Write benchmark
245
+ start_time = time.time()
246
+ for key in keys:
247
+ km.write(key, value)
248
+ write_time = time.time() - start_time
249
+ print(f"Write: {write_time:.6f} sec total, {(write_time/num_operations)*1e6:.2f} µs/op")
250
+
251
+ # Read benchmark
252
+ start_time = time.time()
253
+ for key in keys:
254
+ km.read(key)
255
+ read_time = time.time() - start_time
256
+ print(f"Read: {read_time:.6f} sec total, {(read_time/num_operations)*1e6:.2f} µs/op")
257
+
258
+ # Exists benchmark
259
+ start_time = time.time()
260
+ for key in keys:
261
+ km.exists(key)
262
+ exists_time = time.time() - start_time
263
+ print(f"Exists: {exists_time:.6f} sec total, {(exists_time/num_operations)*1e6:.2f} µs/op")
264
+
265
+ # Delete benchmark
266
+ start_time = time.time()
267
+ for key in keys:
268
+ km.delete(key)
269
+ delete_time = time.time() - start_time
270
+ print(f"Delete: {delete_time:.6f} sec total, {(delete_time/num_operations)*1e6:.2f} µs/op")
271
+
272
+
273
+ if __name__ == '__main__':
274
+ # Define backend configurations here. Adjust values as needed for your environment.
275
+ backend_configs = {
276
+ 'redis': {"host": "localhost", "port": 6379, "db": 0},
277
+ # 'memcached': {"host": "127.0.0.1", "port": 11211},
278
+ # 'ignite': {"host": "127.0.0.1", "port": 10800}, #TODO:Test this
279
+ # 'hazelcast': {}, # Provide Hazelcast-specific configuration if needed. #TODO:Test this
280
+ # 'tarantool': {"host": "127.0.0.1", "port": 3301}, #TODO:Test this
281
+ # 'aerospike': {"host": "127.0.0.1", "port": 3000}, #TODO:Test this
282
+ # 'kafka': {"bootstrap_servers": "localhost:9092", "topic": "knowledge"}, #TODO:Test this
283
+ # 'sqlite': {} # No configuration required for in-memory SQLite.
284
+ }
285
+
286
+ # Benchmark each backend.
287
+ for backend, config in backend_configs.items():
288
+ benchmark_operations(backend, config, num_operations=1000)
@@ -0,0 +1,159 @@
1
+ #**************************************************************************
2
+ # * Copyright (C) 2025-present Bert Van Acker (B.MKR) <bva.bmkr@gmail.com>
3
+ # *
4
+ # * This file is part of the flexIA project.
5
+ # *
6
+ # * flexIA can not be copied and/or distributed without the express
7
+ # * permission of Bert Van Acker
8
+ # *************************************************************************
9
+ import logging
10
+
11
+ from flexCommunicator.clientLibraries.flcpy.utils.constants import FLEXCLOUD_VARIABLES
12
+
13
+
14
+ # Custom logging handler for MQTT.
15
+ class MQTTLoggingHandler(logging.Handler):
16
+ def __init__(self, broker='localhost', port=1883, topic='logs'):
17
+ super().__init__()
18
+ import paho.mqtt.client as mqtt
19
+ self.topic = topic
20
+ self.client = mqtt.Client()
21
+ # Connect to the MQTT broker.
22
+ self.client.connect(broker, port)
23
+ # Start a background network loop.
24
+ self.client.loop_start()
25
+
26
+ def emit(self, record):
27
+ try:
28
+ log_entry = self.format(record)
29
+ self.client.publish(self.topic, log_entry)
30
+ except Exception:
31
+ self.handleError(record)
32
+
33
+ # Custom logging handler for Redis.
34
+ class RedisLoggingHandler(logging.Handler):
35
+ def __init__(self, host='localhost', port=6379, db=6, channel='logs',flexCloud=False):
36
+ super().__init__()
37
+ import redis
38
+ self.redis_client = redis.Redis(host=host, port=port,db=db, decode_responses=True)
39
+ self.channel = channel
40
+ self.flexCloud=flexCloud
41
+
42
+ def emit(self, record):
43
+ try:
44
+ log_entry = self.format(record)
45
+ if self.flexCloud:
46
+ self.redis_client.rpush(self.channel, log_entry)
47
+ else:
48
+ self.redis_client.publish(self.channel, log_entry)
49
+
50
+ except Exception:
51
+ self.handleError(record)
52
+
53
+ # Logging and Tracking Handler that wraps one active handler based on configuration.
54
+ class LoggingAndTrackingHandler(logging.Handler):
55
+ def __init__(self, config,applicationDescriptor=None):
56
+ """
57
+ Initialize the handler based on the provided configuration.
58
+
59
+ :param config: Dictionary with logging configuration.
60
+ Example:
61
+ {
62
+ 'log_level': 'DEBUG',
63
+ 'logger_type': 'file', # Options: 'terminal', 'file', 'mqtt', 'redis'
64
+ 'terminal': {},
65
+ 'file': {'filename': 'app.log'},
66
+ 'mqtt': {'broker': 'localhost', 'port': 1883, 'topic': 'logs'},
67
+ 'redis': {'host': 'localhost', 'port': 6379, 'channel': 'logs'}
68
+ }
69
+ """
70
+ super().__init__()
71
+ self.config = config
72
+ self.level = getattr(logging, config.get('log_level', 'INFO').upper(), logging.INFO)
73
+ self.applicationDescriptor = applicationDescriptor
74
+ self.active_handler = self._setup_handler()
75
+
76
+ def _setup_handler(self):
77
+ active_type = self.config.get('logger_type', 'terminal').lower()
78
+ if active_type == 'terminal':
79
+ handler = logging.StreamHandler()
80
+ elif active_type == 'file':
81
+ file_config = self.config.get('file', {})
82
+ if 'filename' not in file_config:
83
+ raise ValueError("File handler selected but no filename provided in config.")
84
+ handler = logging.FileHandler(file_config['filename'])
85
+ elif active_type == 'mqtt':
86
+ mqtt_config = self.config.get('mqtt', {})
87
+ handler = MQTTLoggingHandler(
88
+ broker=mqtt_config.get('broker', 'localhost'),
89
+ port=mqtt_config.get('port', 1883),
90
+ topic=mqtt_config.get('topic', 'logs')
91
+ )
92
+ elif active_type == 'redis':
93
+ redis_config = self.config.get('redis', {})
94
+ handler = RedisLoggingHandler(
95
+ host=redis_config.get('host', 'localhost'),
96
+ port=redis_config.get('port', 6379),
97
+ db=redis_config.get('db', 0),
98
+ channel=redis_config.get('channel', 'logs')
99
+ )
100
+ elif active_type == 'flexcloud':
101
+ channel = self._get_log_channel()
102
+ redis_config = self.config.get('redis', {})
103
+ handler = RedisLoggingHandler(
104
+ host=redis_config.get('host', 'localhost'),
105
+ port=redis_config.get('port', 6379),
106
+ db=redis_config.get('db', 0),
107
+ channel=channel,
108
+ flexCloud=True
109
+ )
110
+
111
+ else:
112
+ raise ValueError(f"Unknown logger_type specified: {active_type}")
113
+ return handler
114
+
115
+ def setFormatter(self, fmt):
116
+ """Override setFormatter to also set the formatter on the active handler."""
117
+ super().setFormatter(fmt)
118
+ self.active_handler.setFormatter(fmt)
119
+
120
+ def emit(self, record):
121
+ """Delegate emit to the wrapped active handler."""
122
+ self.active_handler.emit(record)
123
+
124
+ def _get_log_channel(self):
125
+ channel = self.applicationDescriptor["connectorID"] + FLEXCLOUD_VARIABLES.APPLICATIONS + self.applicationDescriptor["id"] + ":" + FLEXCLOUD_VARIABLES.LOGS
126
+ return channel
127
+
128
+
129
+ # Example usage:
130
+ if __name__ == '__main__':
131
+ config = {
132
+ 'log_level': 'DEBUG',
133
+ 'logger_type': 'redis', # Change to 'terminal', 'file', 'mqtt', or 'redis' as needed.
134
+ 'terminal': {},
135
+ 'file': {'filename': 'app.log'},
136
+ 'mqtt': {'broker': 'localhost', 'port': 1883, 'topic': 'logs'},
137
+ 'redis': {'host': 'localhost', 'port': 6379, 'channel': 'logs'}
138
+ }
139
+
140
+ # Create the main logger.
141
+ logger = logging.getLogger("main")
142
+ logger.setLevel(logging.DEBUG)
143
+
144
+ # Instantiate the custom logging handler.
145
+ custom_handler = LoggingAndTrackingHandler(config)
146
+
147
+ # Set the formatter here in main (instead of inside the class).
148
+ log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
149
+ formatter = logging.Formatter(log_format)
150
+ custom_handler.setFormatter(formatter)
151
+
152
+ logger.addHandler(custom_handler)
153
+
154
+ # Log some test messages.
155
+ logger.debug("This is a debug message.")
156
+ logger.info("This is an info message.")
157
+ logger.warning("This is a warning message.")
158
+ logger.error("This is an error message.")
159
+ logger.critical("This is a critical message.")
@@ -0,0 +1,64 @@
1
+ #**************************************************************************
2
+ # * Copyright (C) 2025-present Bert Van Acker (B.MKR) <bva.bmkr@gmail.com>
3
+ # *
4
+ # * This file is part of the flexIA project.
5
+ # *
6
+ # * flexIA can not be copied and/or distributed without the express
7
+ # * permission of Bert Van Acker
8
+ # *************************************************************************
9
+ import jsonpickle
10
+
11
+ class DataMessage(object):
12
+ def __init__(self):
13
+ self._name = "CustomMessage"
14
+ self._dataType = "Float"
15
+ self._dataUnit = "mm"
16
+ self._value = None
17
+
18
+ # Getter and setter for name
19
+ @property
20
+ def name(self):
21
+ return self._name
22
+
23
+ @name.setter
24
+ def name(self, value):
25
+ self._name = value
26
+
27
+ # Getter and setter for dataType
28
+ @property
29
+ def dataType(self):
30
+ return self._dataType
31
+
32
+ @dataType.setter
33
+ def dataType(self, value):
34
+ self._dataType = value
35
+
36
+ # Getter and setter for dataUnit
37
+ @property
38
+ def dataUnit(self):
39
+ return self._dataUnit
40
+
41
+ @dataUnit.setter
42
+ def dataUnit(self, value):
43
+ self._dataUnit = value
44
+
45
+ # Getter and setter for value
46
+ @property
47
+ def value(self):
48
+ return self._value
49
+
50
+ @value.setter
51
+ def value(self, cmp):
52
+ self._value = cmp
53
+
54
+ def instantiate(self, json_str):
55
+ """
56
+ Reconstruct the object from a JSON string using jsonpickle.
57
+ """
58
+ decoded = jsonpickle.decode(json_str)
59
+
60
+ # Update only if the attributes exist
61
+ self._name = getattr(decoded, "_name", self._name)
62
+ self._dataType = getattr(decoded, "_dataType", self._dataType)
63
+ self._dataUnit = getattr(decoded, "_dataUnit", self._dataUnit)
64
+ self._value = getattr(decoded, "_value", self._value)