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,16 @@
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
+
10
+ from flexCommunicator.clientLibraries.flcpy.utils.constants import *
11
+ from flexCommunicator.clientLibraries.flcpy.flexCloud.flexCloudVariable import flexCloudVariable
12
+
13
+
14
+ class flexCloudConfigParameter(flexCloudVariable):
15
+ def __init__(self, name, initial_value=None, syncType="onchange", sync_interval=None):
16
+ super().__init__(name, initial_value=initial_value, syncType=syncType, sync_interval=sync_interval,history=False,statusVariable=False,configVariable = True)
@@ -0,0 +1,162 @@
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 json
10
+ import time
11
+ import string
12
+ import random
13
+ import threading
14
+
15
+ from flexCommunicator.clientLibraries.flcpy.utils.constants import *
16
+
17
+ # Constants for configuration keys
18
+ KEY_KNOWLEDGE_TYPE = "knowledge_type"
19
+ KEY_HOST = "host"
20
+ KEY_PORT = "port"
21
+ KEY_DB = "db"
22
+ KEY_TOPIC = "topic"
23
+ KEY_BOOTSTRAP_SERVERS = "bootstrap_servers"
24
+
25
+ # Constants for backends
26
+ BACKEND_REDIS = "redis"
27
+ BACKEND_MEMCACHED = "memcached"
28
+ BACKEND_IGNITE = "ignite"
29
+ BACKEND_HAZELCAST = "hazelcast"
30
+ BACKEND_TARANTOOL = "tarantool"
31
+ BACKEND_AEROSPIKE = "aerospike"
32
+ BACKEND_KAFKA = "kafka"
33
+ BACKEND_SQLITE = "sqlite"
34
+
35
+ # Constants for SQLite queries and in-memory DB
36
+ SQLITE_MEMORY = ":memory:"
37
+ SQLITE_CREATE_TABLE = "CREATE TABLE kv (key TEXT PRIMARY KEY, value TEXT)"
38
+ SQLITE_INSERT_OR_REPLACE = "INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)"
39
+ SQLITE_SELECT_VALUE = "SELECT value FROM kv WHERE key=?"
40
+ SQLITE_DELETE_KEY = "DELETE FROM kv WHERE key=?"
41
+
42
+ # Other constants
43
+ ERROR_UNSUPPORTED_BACKEND = "Unsupported backend: {}"
44
+ DEFAULT_CACHE_NAME = "default"
45
+ AEROSPIKE_VALUE_FIELD = "value"
46
+
47
+ # Helper function to generate random strings (if needed)
48
+ def random_string(length=10):
49
+ return ''.join(random.choice(string.ascii_letters) for _ in range(length))
50
+
51
+
52
+ class flexCloudManager:
53
+ def __init__(self, config,autostart=False):
54
+ """
55
+ Initialize the flexCloudManager with a chosen backend.
56
+ Supported backends: redis
57
+
58
+ :param config: A dictionary containing all configuration parameters needed by the backend.
59
+ """
60
+ self.backend = config[KEY_KNOWLEDGE_TYPE]
61
+ #SUBSCRIPTION VARIABLES
62
+ self.subscribe_topics = []
63
+ self.event_callbacks = {} # Mapping: topic -> callback function
64
+ self._consumer_thread = None
65
+ #BACKEND CONFIGURATION
66
+ if self.backend == BACKEND_REDIS:
67
+ import redis
68
+ self.client = redis.Redis(host=config[KEY_HOST], port=config[KEY_PORT], db=config[KEY_DB])
69
+ self.pubsub = self.client.pubsub()
70
+ else:
71
+ raise ValueError(ERROR_UNSUPPORTED_BACKEND.format(self.backend))
72
+
73
+ #AUTO-START SUBSCRIPTON THREAD
74
+ if autostart:
75
+ self._consumer_thread = threading.Thread(target=self._listen, daemon=True)
76
+ self._consumer_thread.start()
77
+
78
+ def write(self, key, value):
79
+ """Write a key-value pair to the backend."""
80
+ if self.backend == BACKEND_REDIS:
81
+ return self.client.set(key, value)
82
+
83
+
84
+ def read(self, key, queueSize=1):
85
+ """Read the value for a given key from the backend."""
86
+ if self.backend == BACKEND_REDIS:
87
+ return self.client.get(key)
88
+
89
+ def write_history(self, key, value):
90
+ """
91
+ Append a value to the history list for `key` in Redis.
92
+ Returns the new length of the list.
93
+ """
94
+ if self.backend == BACKEND_REDIS:
95
+ # RPUSH adds to the tail of the list, creating it if needed
96
+ return self.client.rpush(key, value)
97
+
98
+ def read_history(self, key, history_size=1):
99
+ """
100
+ Read up to `history_size` most recent values for `key` from Redis.
101
+ Returns a list of values (oldest→newest within that window).
102
+ """
103
+ if self.backend == BACKEND_REDIS:
104
+ # Use a negative start index to grab the last `history_size` elements
105
+ start = -history_size
106
+ end = -1
107
+ return self.client.lrange(key, start, end)
108
+
109
+ def publish(self,key,value):
110
+ try:
111
+ data = json.dumps(value).encode('utf-8')
112
+ except:
113
+ data = value
114
+ self.client.publish(channel=key, message=data)
115
+
116
+ def delete(self, key):
117
+ """Delete the key-value pair identified by key from the backend."""
118
+ if self.backend == BACKEND_REDIS:
119
+ return self.client.delete(key)
120
+
121
+
122
+ def exists(self, key):
123
+ """Check whether a key exists in the backend."""
124
+ if self.backend == BACKEND_REDIS:
125
+ return self.client.exists(key)
126
+
127
+
128
+ def subscribe(self, topic, callback):
129
+ """Register a callback for the given topic."""
130
+ if topic not in self.subscribe_topics:
131
+ self.subscribe_topics.append(topic)
132
+ self.event_callbacks[topic] = callback
133
+
134
+ if self.backend == TRANSPORT_PROTOCOLS.PROTOCOL_REDIS and self.pubsub:
135
+ self.pubsub.subscribe(topic)
136
+ else:
137
+ raise ValueError(ERROR_UNSUPPORTED_BACKEND.format(self.backend))
138
+ def start(self):
139
+ """Start the listener thread."""
140
+ self._consumer_thread = threading.Thread(target=self._listen, daemon=True)
141
+ self._consumer_thread.start()
142
+
143
+ def stop(self):
144
+ """Stop the listener thread."""
145
+ self._consumer_thread.join(timeout=2)
146
+ self.pubsub.close()
147
+
148
+ #-----------------------helper functions------------------------------
149
+ def _listen(self):
150
+ try:
151
+ for message in self.pubsub.listen():
152
+ if message['type'] == 'message':
153
+ topic = message['channel'].decode('utf-8') #TODO: check if needed
154
+ if topic in self.event_callbacks:
155
+ message_decoded = message['data'].decode('utf-8')
156
+ self.event_callbacks[topic](message_decoded) #TODO: check if needed
157
+ except (ValueError, OSError) as e:
158
+ # Socket was likely closed during shutdown; exit gracefully.
159
+ print(FLEXCLOUD_ERRORS.ERR_REDIS_SUBSCRIPTION_CLOSED.format(e))
160
+
161
+
162
+
@@ -0,0 +1,248 @@
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
+ from __future__ import annotations
10
+
11
+ import time
12
+ import threading
13
+ from dataclasses import dataclass
14
+ from enum import Enum
15
+ from typing import Callable, Dict, Iterable, List, Optional, Sequence, Set, Tuple
16
+
17
+ import redis
18
+
19
+ from flexCommunicator.clientLibraries.flcpy.utils.constants import *
20
+
21
+ @dataclass(frozen=True)
22
+ class ComponentSpec:
23
+ """
24
+ name: unique component identifier (used for Redis keying)
25
+ spin: callable that starts the component
26
+ depends_on: optional list of component names that must be RUNNING first
27
+ """
28
+ name: str
29
+ spin: Callable[[], None]
30
+ depends_on: Tuple[str, ...] = ()
31
+
32
+
33
+ class StartupError(RuntimeError):
34
+ pass
35
+
36
+
37
+ class StartupOrchestrator:
38
+ """
39
+ Orchestrates startup of Python components that expose state in Redis.
40
+
41
+ Default Redis key pattern: "{name}:state" with value "RUNNING", etc.
42
+ """
43
+
44
+ def __init__(
45
+ self,
46
+ components: Sequence[ComponentSpec],
47
+ *,
48
+ # Redis connection settings (integrated)
49
+ redis_url: Optional[str] = None,
50
+ host: str = "localhost",
51
+ port: int = 6379,
52
+ db: int = 6,
53
+ username: Optional[str] = None,
54
+ password: Optional[str] = None,
55
+ ssl: bool = False,
56
+ socket_timeout_s: float = 2.0,
57
+ decode_responses: bool = True,
58
+ health_check_interval_s: int = 10,
59
+ # Orchestration settings
60
+ connectorID: str = "appName@projectName-key",
61
+ running_value: str = APPLICATION_STATUS.RUNNING,
62
+ poll_interval_s: float = 0.1,
63
+ spin_in_thread: bool = True,
64
+ ping_on_init: bool = True,
65
+ ) -> None:
66
+ self.components: Dict[str, ComponentSpec] = {c.name: c for c in components}
67
+
68
+ # Orchestration config
69
+ self.key_pattern = connectorID + ":applications:{name}:status"
70
+ self.running_value = running_value
71
+ self.poll_interval_s = poll_interval_s
72
+ self.spin_in_thread = spin_in_thread
73
+
74
+ # Build Redis client (integrated)
75
+ if redis_url:
76
+ self.r = redis.Redis.from_url(
77
+ redis_url,
78
+ socket_timeout=socket_timeout_s,
79
+ decode_responses=decode_responses,
80
+ health_check_interval=health_check_interval_s,
81
+ )
82
+ else:
83
+ self.r = redis.Redis(
84
+ host=host,
85
+ port=port,
86
+ db=db,
87
+ username=username,
88
+ password=password,
89
+ ssl=ssl,
90
+ socket_timeout=socket_timeout_s,
91
+ decode_responses=decode_responses,
92
+ health_check_interval=health_check_interval_s,
93
+ )
94
+
95
+ if ping_on_init:
96
+ try:
97
+ self.r.ping()
98
+ except Exception as e:
99
+ raise StartupError(f"Redis ping failed (check connection settings): {e}") from e
100
+
101
+ # Validate dependencies reference known components
102
+ for c in components:
103
+ for dep in c.depends_on:
104
+ if dep not in self.components:
105
+ raise ValueError(f"Component '{c.name}' depends on unknown component '{dep}'")
106
+
107
+ def close(self) -> None:
108
+ """
109
+ Close the Redis connection pool cleanly.
110
+ """
111
+ try:
112
+ self.r.close()
113
+ except Exception:
114
+ # redis-py versions differ; best-effort close
115
+ try:
116
+ self.r.connection_pool.disconnect()
117
+ except Exception:
118
+ pass
119
+
120
+ def _key_for(self, name: str) -> str:
121
+ return self.key_pattern.format(name=name)
122
+
123
+ def _read_state(self, name: str) -> str:
124
+ try:
125
+ val = self.r.get(self._key_for(name))
126
+ except Exception as e:
127
+ raise StartupError(f"Redis GET failed for '{self._key_for(name)}': {e}") from e
128
+
129
+ if val is None:
130
+ return APPLICATION_STATUS.UNKNOWN
131
+ # if decode_responses=False, val is bytes; else it's str
132
+ if isinstance(val, bytes):
133
+ return val.decode("utf-8", errors="replace")
134
+ return str(val)
135
+
136
+ def _wait_for_running(self, name: str, timeout_s: float) -> None:
137
+ print(f"Waiting for '{name}' to become {self.running_value}...")
138
+ deadline = time.time() + timeout_s
139
+ while True:
140
+ state = self._read_state(name)
141
+ if state == self.running_value:
142
+ print(f"'{name}' is now {self.running_value}.")
143
+ return
144
+ if state == APPLICATION_STATUS.ERROR:
145
+ raise StartupError(f"Component '{name}' entered ERROR state while starting")
146
+
147
+ if time.time() >= deadline:
148
+ raise StartupError(
149
+ f"Timeout waiting for '{name}' to become {self.running_value}. "
150
+ f"Last state: {state!r}, key: {self._key_for(name)!r}"
151
+ )
152
+ time.sleep(self.poll_interval_s)
153
+
154
+
155
+ def _topological_order(self, subset: Optional[Iterable[str]] = None) -> List[str]:
156
+ if subset is None:
157
+ nodes: Set[str] = set(self.components.keys())
158
+ else:
159
+ nodes = set(subset)
160
+
161
+ # Ensure requested subset includes its dependencies
162
+ missing_edges = []
163
+ for n in nodes:
164
+ for d in self.components[n].depends_on:
165
+ if d not in nodes:
166
+ missing_edges.append((n, d))
167
+ if missing_edges:
168
+ pairs = ", ".join([f"{n}->{d}" for n, d in sorted(missing_edges)])
169
+ raise ValueError("Subset does not include all dependencies. Missing edges: " + pairs)
170
+
171
+ indeg: Dict[str, int] = {n: 0 for n in nodes}
172
+ adj: Dict[str, List[str]] = {n: [] for n in nodes}
173
+
174
+ for n in nodes:
175
+ for d in self.components[n].depends_on:
176
+ adj[d].append(n) # d -> n
177
+ indeg[n] += 1
178
+
179
+ q = sorted([n for n, deg in indeg.items() if deg == 0])
180
+ out: List[str] = []
181
+
182
+ while q:
183
+ n = q.pop(0)
184
+ out.append(n)
185
+ for m in adj[n]:
186
+ indeg[m] -= 1
187
+ if indeg[m] == 0:
188
+ q.append(m)
189
+ q.sort()
190
+
191
+ if len(out) != len(nodes):
192
+ remaining = [n for n in nodes if n not in out]
193
+ raise ValueError(f"Dependency cycle detected among: {sorted(remaining)}")
194
+
195
+ return out
196
+
197
+ def _start_one(self, name: str) -> None:
198
+ spec = self.components[name]
199
+
200
+ def runner():
201
+ spec.spin()
202
+
203
+ if self.spin_in_thread:
204
+ threading.Thread(target=runner, name=f"spin:{name}", daemon=True).start()
205
+ else:
206
+ runner()
207
+
208
+ def startup(
209
+ self,
210
+ *,
211
+ order: Optional[Sequence[str]] = None,
212
+ subset: Optional[Iterable[str]] = None,
213
+ timeout_per_component_s: float = 30.0,
214
+ fail_fast: bool = True,
215
+ ensure_not_running: bool = False,
216
+ ) -> List[str]:
217
+ if order is None:
218
+ start_order = self._topological_order(subset=subset)
219
+ else:
220
+ start_order = list(order)
221
+
222
+ failures: List[str] = []
223
+
224
+ for name in start_order:
225
+ if name not in self.components:
226
+ raise ValueError(f"Unknown component in order: '{name}'")
227
+
228
+ current = self._read_state(name)
229
+ if ensure_not_running and current == self.running_value:
230
+ msg = f"Component '{name}' is already {self.running_value}"
231
+ if fail_fast:
232
+ raise StartupError(msg)
233
+ failures.append(msg)
234
+ continue
235
+
236
+ self._start_one(name)
237
+ try:
238
+ self._wait_for_running(name, timeout_s=timeout_per_component_s)
239
+ except Exception as e:
240
+ msg = f"Failed to start '{name}': {e}"
241
+ if fail_fast:
242
+ raise StartupError(msg) from e
243
+ failures.append(msg)
244
+
245
+ if failures:
246
+ raise StartupError("One or more components failed:\n- " + "\n- ".join(failures))
247
+
248
+ return start_order
@@ -0,0 +1,130 @@
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 threading
10
+ import json
11
+ import time
12
+
13
+ from flexCommunicator.clientLibraries.flcpy.utils.constants import *
14
+
15
+
16
+ class flexCloudVariable(object):
17
+ def __init__(self, name, initial_value=0, syncType="onchange", sync_interval=None,history=True,statusVariable=False,configVariable = False):
18
+ self._name = name
19
+ self._value = initial_value
20
+ self._lock = threading.Lock()
21
+
22
+
23
+ #flexCloud handler
24
+ self.history = history
25
+ self.flexCloud_manager = None
26
+ self.flexCloud_connectorID = None
27
+ self.flexCloud_applicationID = None
28
+
29
+ # flexCloud application status
30
+ self.statusVariable = statusVariable
31
+ if statusVariable:
32
+ self.history = False
33
+ # flexCloud config variable
34
+ self.configVariable = configVariable
35
+ if configVariable:
36
+ self.history = False
37
+
38
+
39
+ def _sync_to_redis(self):
40
+ if self.statusVariable:
41
+ key = self.flexCloud_connectorID + FLEXCLOUD_VARIABLES.APPLICATIONS + self.flexCloud_applicationID + ":"+ self._name
42
+ elif self.configVariable:
43
+ key = self.flexCloud_connectorID + FLEXCLOUD_VARIABLES.APPLICATIONS + self.flexCloud_applicationID + FLEXCLOUD_VARIABLES.CONFIGURATION + self._name
44
+ else:
45
+ key = self.flexCloud_connectorID + FLEXCLOUD_VARIABLES.APPLICATIONS + self.flexCloud_applicationID + FLEXCLOUD_VARIABLES.VARIABLES + self._name
46
+ self._write_knowledge(key=key, value=self._value)
47
+
48
+ def _retrieve_from_redis(self,queueSize=1):
49
+ if self.statusVariable:
50
+ key = self.flexCloud_connectorID + FLEXCLOUD_VARIABLES.APPLICATIONS + self.flexCloud_applicationID + ":"+ self._name
51
+ elif self.configVariable:
52
+ key = self.flexCloud_connectorID + FLEXCLOUD_VARIABLES.APPLICATIONS + self.flexCloud_applicationID + FLEXCLOUD_VARIABLES.CONFIGURATION + self._name
53
+ else:
54
+ key = self.flexCloud_connectorID + FLEXCLOUD_VARIABLES.APPLICATIONS + self.flexCloud_applicationID + FLEXCLOUD_VARIABLES.VARIABLES + self._name
55
+ return self._read_knowledge(key=key,queueSize=queueSize,decode=True) #TODO: assuming that variables are always stored raw
56
+
57
+ def initialize(self):
58
+ """ Initialize the flexCloud variable"""
59
+ self._sync_to_redis()
60
+
61
+ def set(self, value):
62
+ if value != self._value:
63
+ self._value = value
64
+ self._sync_to_redis()
65
+
66
+ def get(self,queueSize=1):
67
+ with self._lock:
68
+ # -- variable with history --
69
+ if self.history:
70
+ queue = self._retrieve_from_redis(queueSize=queueSize)
71
+ if len(queue) == 1:
72
+ self._value = queue[0] #AUTO-SET VALUE
73
+ return queue[0]
74
+ else:
75
+ return queue
76
+ # -- variable without history --
77
+ else:
78
+ value = self._retrieve_from_redis(queueSize=1)
79
+ self._value = value #AUTO-SET VALUE
80
+ return value
81
+
82
+ def fetch_from_redis(self): #TODO: this needs to be checked periodically for changes in the redis if variable is read/write!
83
+ value = self._read_knowledge(key=self._name)
84
+ with self._lock:
85
+ self._value = value
86
+ return value
87
+
88
+
89
+
90
+ # --------------------------- HELPER FUNCTIONS ------------------------
91
+ def _write_knowledge(self, key, value):
92
+ """Write a value to the knowledge Manager."""
93
+ if isinstance(key, str):
94
+ if isinstance(value, str):
95
+ value = str(value)
96
+ if self.history:
97
+ return self.flexCloud_manager.write_history(key, value)
98
+ else:
99
+ return self.flexCloud_manager.write(key, value)
100
+ else:
101
+ # Convert the class instance to a dictionary
102
+ class_dict = {}
103
+ for key_attr, attr_value in key.__dict__.items():
104
+ public_key = key_attr.lstrip('_')
105
+ class_dict[public_key] = attr_value
106
+ value = json.dumps(class_dict) # Serialize the dictionary to a JSON string
107
+ if self.history:
108
+ return self.flexCloud_manager.write_history(key.name, value)
109
+ else:
110
+ return self.flexCloud_manager.write(key.name, value)
111
+
112
+ def _read_knowledge(self, key, queueSize=1, decode=True):
113
+ """Read a value from the knowledge Manager."""
114
+ # -- variable with history --
115
+ if self.history:
116
+ value = self.flexCloud_manager.read_history(key, queueSize)
117
+ print(value)
118
+ if decode:
119
+ if value is not None:
120
+ # decode all values of array
121
+ for i in range (0,len(value),1):
122
+ value[i] = value[i].decode(ENCODING.ENCODING_UTF8)
123
+ return value
124
+
125
+ # -- variable without history --
126
+ else:
127
+ value = self.flexCloud_manager.read(key)
128
+ if decode:
129
+ value = value.decode(ENCODING.ENCODING_UTF8)
130
+ return value