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.
- flexCommunicator/__init__.py +0 -0
- flexCommunicator/clientLibraries/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/action/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/action/action.py +85 -0
- flexCommunicator/clientLibraries/flcpy/action/action_client.py +70 -0
- flexCommunicator/clientLibraries/flcpy/action/goal_handle.py +50 -0
- flexCommunicator/clientLibraries/flcpy/codec/ROS2Codec.py +227 -0
- flexCommunicator/clientLibraries/flcpy/codec/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/commands/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/commands/commandProcessor.py +113 -0
- flexCommunicator/clientLibraries/flcpy/commands/composer.py +25 -0
- flexCommunicator/clientLibraries/flcpy/communication/CommunicationManager.py +579 -0
- flexCommunicator/clientLibraries/flcpy/communication/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/contracts/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/contracts/contractVariable.py +93 -0
- flexCommunicator/clientLibraries/flcpy/flexCloud/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/flexCloud/flexCloudConfiguration.py +16 -0
- flexCommunicator/clientLibraries/flcpy/flexCloud/flexCloudManager.py +162 -0
- flexCommunicator/clientLibraries/flcpy/flexCloud/flexCloudOrchestrator.py +248 -0
- flexCommunicator/clientLibraries/flcpy/flexCloud/flexCloudVariable.py +130 -0
- flexCommunicator/clientLibraries/flcpy/flexNode.py +892 -0
- flexCommunicator/clientLibraries/flcpy/fmi/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/fmi/fmuNode.py +166 -0
- flexCommunicator/clientLibraries/flcpy/fmi/node.py +360 -0
- flexCommunicator/clientLibraries/flcpy/knowledge/KnowledgeManager.py +288 -0
- flexCommunicator/clientLibraries/flcpy/knowledge/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/logging/LoggingAndTracking.py +159 -0
- flexCommunicator/clientLibraries/flcpy/logging/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/messages/StandardizedMessages.py +64 -0
- flexCommunicator/clientLibraries/flcpy/messages/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/node.py +486 -0
- flexCommunicator/clientLibraries/flcpy/security/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/security/security.py +189 -0
- flexCommunicator/clientLibraries/flcpy/service/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/service/client.py +58 -0
- flexCommunicator/clientLibraries/flcpy/service/service.py +67 -0
- flexCommunicator/clientLibraries/flcpy/transport/PropertyBinding.py +108 -0
- flexCommunicator/clientLibraries/flcpy/transport/PropertyRegistry.py +142 -0
- flexCommunicator/clientLibraries/flcpy/transport/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/utils/Timer.py +22 -0
- flexCommunicator/clientLibraries/flcpy/utils/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/utils/auxiliary.py +261 -0
- flexCommunicator/clientLibraries/flcpy/utils/constants.py +276 -0
- flexCommunicator/clientLibraries/flcpy/utils/decorators.py +70 -0
- flexCommunicator/clientLibraries/flcpy/validityFrame/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/validityFrame/runtimeMonitor.py +101 -0
- flexCommunicator/clientLibraries/flcpy/validityFrame/validityFrameManager.py +958 -0
- flexcommunicator-0.1.0.dist-info/METADATA +568 -0
- flexcommunicator-0.1.0.dist-info/RECORD +53 -0
- flexcommunicator-0.1.0.dist-info/WHEEL +5 -0
- flexcommunicator-0.1.0.dist-info/licenses/LICENSE +201 -0
- flexcommunicator-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,58 @@
|
|
|
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 threading
|
|
11
|
+
class client(object):
|
|
12
|
+
def __init__(self, name="tbd", service=None,logger=None,set_data=None):
|
|
13
|
+
self._name = name
|
|
14
|
+
self._service = service
|
|
15
|
+
self._isWaitingforService = False
|
|
16
|
+
self._cv = threading.Condition()
|
|
17
|
+
self._logger = logger
|
|
18
|
+
self._set_data = set_data
|
|
19
|
+
self._done_callback = None
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def service(self):
|
|
23
|
+
return self._service
|
|
24
|
+
|
|
25
|
+
@service.setter
|
|
26
|
+
def service(self, value):
|
|
27
|
+
self._service = value
|
|
28
|
+
|
|
29
|
+
def call_async(self,request):
|
|
30
|
+
self._logger.info("Async service <"+self.service.name+"> called.")
|
|
31
|
+
self._set_data(data_name=self._service.requestProperty, data=request)
|
|
32
|
+
self._isWaitingforService = True
|
|
33
|
+
|
|
34
|
+
def spin_until_future_complete(self, timeout=5.0):
|
|
35
|
+
# Wait for response or timeout
|
|
36
|
+
with self._cv:
|
|
37
|
+
if not self._cv.wait_for(lambda: not self._isWaitingforService, timeout=timeout):
|
|
38
|
+
self._isWaitingForService = False
|
|
39
|
+
self._logger.info("Async service <" + self.service.name + "> called failed, no response received within timeout ["+timeout.__str__()+" sec.].")
|
|
40
|
+
return -1
|
|
41
|
+
|
|
42
|
+
self._isWaitingForService = False
|
|
43
|
+
return self._service.response
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def response_callback(self,request,response):
|
|
47
|
+
#set response
|
|
48
|
+
self.service.response.instantiate(json_str=response)
|
|
49
|
+
self._isWaitingforService = False
|
|
50
|
+
|
|
51
|
+
#DONE CALLBACK
|
|
52
|
+
if self._done_callback is not None:
|
|
53
|
+
self._done_callback(self.service.response)
|
|
54
|
+
|
|
55
|
+
def add_done_callback(self,callback=None):
|
|
56
|
+
self._done_callback = callback
|
|
57
|
+
|
|
58
|
+
|
|
@@ -0,0 +1,67 @@
|
|
|
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
|
+
class service(object):
|
|
11
|
+
def __init__(self, name="tbd", request=None, response=None,callback=None,requestProperty=None, responseProperty=None):
|
|
12
|
+
self._name = name
|
|
13
|
+
self._request = request
|
|
14
|
+
self._response = response
|
|
15
|
+
self._requestProperty = requestProperty
|
|
16
|
+
self._responseProperty = responseProperty
|
|
17
|
+
self._callback = callback
|
|
18
|
+
|
|
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
|
+
@property
|
|
28
|
+
def request(self):
|
|
29
|
+
return self._request
|
|
30
|
+
|
|
31
|
+
@request.setter
|
|
32
|
+
def request(self, value):
|
|
33
|
+
self._request = value
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def response(self):
|
|
37
|
+
return self._response
|
|
38
|
+
|
|
39
|
+
@response.setter
|
|
40
|
+
def response(self, value):
|
|
41
|
+
self._response = value
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def requestProperty(self):
|
|
45
|
+
return self._requestProperty
|
|
46
|
+
|
|
47
|
+
@requestProperty.setter
|
|
48
|
+
def requestProperty(self, value):
|
|
49
|
+
self._requestProperty = value
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def responseProperty(self):
|
|
53
|
+
return self._responseProperty
|
|
54
|
+
|
|
55
|
+
@responseProperty.setter
|
|
56
|
+
def responseProperty(self, value):
|
|
57
|
+
self._responseProperty = value
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def callback(self):
|
|
61
|
+
return self._callback
|
|
62
|
+
|
|
63
|
+
@callback.setter
|
|
64
|
+
def callback(self, value):
|
|
65
|
+
self._callback = value
|
|
66
|
+
|
|
67
|
+
|
|
@@ -0,0 +1,108 @@
|
|
|
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 enum import Enum
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Direction(Enum):
|
|
14
|
+
"""Direction of data flow for a property."""
|
|
15
|
+
IN = "in"
|
|
16
|
+
OUT = "out"
|
|
17
|
+
INOUT = "inout"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MqttBinding:
|
|
21
|
+
"""Binding configuration for MQTT transport."""
|
|
22
|
+
def __init__(self, topic: str, QoS: Optional[int] = 10):
|
|
23
|
+
self.topic = topic
|
|
24
|
+
self.QoS = QoS
|
|
25
|
+
self.generic_endpoint = topic
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class RedisBinding:
|
|
29
|
+
"""Binding configuration for Redis transport."""
|
|
30
|
+
def __init__(self, key: str):
|
|
31
|
+
self.key = key
|
|
32
|
+
self.generic_endpoint = key
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class OpcUaBinding:
|
|
36
|
+
"""Binding configuration for OPC-UA transport."""
|
|
37
|
+
def __init__(self, node_id: Optional[str] = None, endpoint: Optional[str] = None):
|
|
38
|
+
self.node_id = node_id
|
|
39
|
+
self.endpoint = endpoint
|
|
40
|
+
self.generic_endpoint = endpoint
|
|
41
|
+
|
|
42
|
+
class TwincatAdsBinding:
|
|
43
|
+
"""Binding configuration for TWINCAT-ADS transport."""
|
|
44
|
+
def __init__(self, data_name: Optional[str] = None):
|
|
45
|
+
self.data_name = data_name
|
|
46
|
+
self.generic_endpoint = data_name
|
|
47
|
+
|
|
48
|
+
class RabbitMqBinding:
|
|
49
|
+
"""Binding configuration for RabbitMQ transport."""
|
|
50
|
+
def __init__(self, exchange: str, routing_key: str):
|
|
51
|
+
self.exchange = exchange
|
|
52
|
+
self.routing_key = routing_key
|
|
53
|
+
self.generic_endpoint = routing_key
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class KafkaBinding:
|
|
57
|
+
"""Binding configuration for Kafka transport."""
|
|
58
|
+
def __init__(self, topic: str, partition: Optional[int] = None):
|
|
59
|
+
self.topic = topic
|
|
60
|
+
self.partition = partition
|
|
61
|
+
self.generic_endpoint = topic
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ZenohBinding:
|
|
65
|
+
"""Binding configuration for Zenoh transport."""
|
|
66
|
+
def __init__(self, resource_key: str):
|
|
67
|
+
self.resource_key = resource_key
|
|
68
|
+
self.generic_endpoint = resource_key
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class PropertyBinding:
|
|
72
|
+
"""
|
|
73
|
+
Represents a property with metadata and its transport-specific bindings.
|
|
74
|
+
|
|
75
|
+
Attributes:
|
|
76
|
+
name (str): Logical name of the property.
|
|
77
|
+
description (Optional[str]): Human-readable description.
|
|
78
|
+
direction (Direction): Data flow direction (in, out, or inout).
|
|
79
|
+
mqtt (Optional[MqttBinding]): MQTT binding.
|
|
80
|
+
redis (Optional[RedisBinding]): Redis binding.
|
|
81
|
+
opc_ua (Optional[OpcUaBinding]): OPC-UA binding.
|
|
82
|
+
rabbitmq (Optional[RabbitMqBinding]): RabbitMQ binding.
|
|
83
|
+
kafka (Optional[KafkaBinding]): Kafka binding.
|
|
84
|
+
zenoh (Optional[ZenohBinding]): Zenoh binding.
|
|
85
|
+
"""
|
|
86
|
+
def __init__(
|
|
87
|
+
self,
|
|
88
|
+
name: str,
|
|
89
|
+
description: Optional[str],
|
|
90
|
+
protocol: str,
|
|
91
|
+
mqtt: Optional[MqttBinding] = None,
|
|
92
|
+
redis: Optional[RedisBinding] = None,
|
|
93
|
+
opc_ua: Optional[OpcUaBinding] = None,
|
|
94
|
+
rabbitmq: Optional[RabbitMqBinding] = None,
|
|
95
|
+
kafka: Optional[KafkaBinding] = None,
|
|
96
|
+
zenoh: Optional[ZenohBinding] = None,
|
|
97
|
+
twincat_ads: Optional[TwincatAdsBinding] = None,
|
|
98
|
+
):
|
|
99
|
+
self.name = name
|
|
100
|
+
self.description = description
|
|
101
|
+
self.protocol = protocol
|
|
102
|
+
self.mqtt = mqtt
|
|
103
|
+
self.redis = redis
|
|
104
|
+
self.opc_ua = opc_ua
|
|
105
|
+
self.rabbitmq = rabbitmq
|
|
106
|
+
self.kafka = kafka
|
|
107
|
+
self.zenoh = zenoh
|
|
108
|
+
self.twincat_ads = twincat_ads
|
|
@@ -0,0 +1,142 @@
|
|
|
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 flexCommunicator.clientLibraries.flcpy.transport.PropertyBinding import *
|
|
10
|
+
from typing import Optional, Dict
|
|
11
|
+
import yaml
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
KEY_Property_Registry = "Communication_Matrix"
|
|
15
|
+
|
|
16
|
+
class PropertyRegistry:
|
|
17
|
+
"""
|
|
18
|
+
Loads and stores PropertyBinding instances from a YAML config.
|
|
19
|
+
|
|
20
|
+
Methods:
|
|
21
|
+
get_binding(name: str, transport: str) -> object
|
|
22
|
+
Return the binding object for the given property name and transport type.
|
|
23
|
+
"""
|
|
24
|
+
def __init__(self, config_path: str):
|
|
25
|
+
with open(config_path, 'r') as f:
|
|
26
|
+
self.config = yaml.safe_load(f)
|
|
27
|
+
|
|
28
|
+
self._registry: Dict[str, PropertyBinding] = {}
|
|
29
|
+
commMatrix = self.config[KEY_Property_Registry]
|
|
30
|
+
for entry in commMatrix.get('properties', []):
|
|
31
|
+
mqtt_binding = entry['property'].get('mqtt')
|
|
32
|
+
redis_binding = entry['property'].get('redis')
|
|
33
|
+
opc_ua_binding = entry['property'].get('opc_ua')
|
|
34
|
+
rabbitmq_binding = entry['property'].get('rabbitmq')
|
|
35
|
+
kafka_binding = entry['property'].get('kafka')
|
|
36
|
+
zenoh_binding = entry['property'].get('zenoh')
|
|
37
|
+
twincat_ads_binding = entry['property'].get('twincat_ads')
|
|
38
|
+
|
|
39
|
+
pb = PropertyBinding(
|
|
40
|
+
name=entry['property']['name'],
|
|
41
|
+
description=entry['property'].get('description'),
|
|
42
|
+
protocol=entry['property'].get('protocol'),
|
|
43
|
+
mqtt=MqttBinding(**mqtt_binding) if mqtt_binding else None,
|
|
44
|
+
redis=RedisBinding(**redis_binding) if redis_binding else None,
|
|
45
|
+
opc_ua=OpcUaBinding(**opc_ua_binding) if opc_ua_binding else None,
|
|
46
|
+
rabbitmq=RabbitMqBinding(**rabbitmq_binding) if rabbitmq_binding else None,
|
|
47
|
+
kafka=KafkaBinding(**kafka_binding) if kafka_binding else None,
|
|
48
|
+
zenoh=ZenohBinding(**zenoh_binding) if zenoh_binding else None,
|
|
49
|
+
twincat_ads=TwincatAdsBinding(**twincat_ads_binding) if twincat_ads_binding else None
|
|
50
|
+
)
|
|
51
|
+
self._registry[pb.name] = pb
|
|
52
|
+
|
|
53
|
+
def get_binding(self, name: str, transport: str):
|
|
54
|
+
"""
|
|
55
|
+
Retrieve the binding configuration for a property by name and transport.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
name (str): The property name.
|
|
59
|
+
transport (str): One of 'mqtt', 'redis', 'opc_ua', 'rabbitmq', 'kafka', 'zenoh'.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
The corresponding binding object.
|
|
63
|
+
|
|
64
|
+
Raises:
|
|
65
|
+
KeyError: If the property or transport binding does not exist.
|
|
66
|
+
"""
|
|
67
|
+
pb = self._registry.get(name)
|
|
68
|
+
if not pb:
|
|
69
|
+
raise KeyError(f"No property with name '{name}' found.")
|
|
70
|
+
|
|
71
|
+
transport = transport.lower()
|
|
72
|
+
binding = getattr(pb, transport, None)
|
|
73
|
+
if not binding:
|
|
74
|
+
raise KeyError(f"No binding for transport '{transport}' on property '{name}'.")
|
|
75
|
+
return binding
|
|
76
|
+
|
|
77
|
+
def get_transport(self, name: str):
|
|
78
|
+
"""
|
|
79
|
+
Retrieve the transport protocol for a property by name .
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
name (str): The property name.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
The corresponding binding object.
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
KeyError: If the property or transport binding does not exist.
|
|
89
|
+
"""
|
|
90
|
+
pb = self._registry.get(name)
|
|
91
|
+
if not pb:
|
|
92
|
+
raise KeyError(f"No property with name '{name}' found.")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
protocol = getattr(pb, "protocol", None)
|
|
96
|
+
if not protocol:
|
|
97
|
+
raise KeyError(f"No transport protocol defined for property '{name}'.")
|
|
98
|
+
return protocol
|
|
99
|
+
|
|
100
|
+
def update_transport(self, name: str, transport: str):
|
|
101
|
+
""" Update current property transport"""
|
|
102
|
+
pb = self._registry.get(name)
|
|
103
|
+
if not pb:
|
|
104
|
+
raise KeyError(f"No property with name '{name}' found.")
|
|
105
|
+
try:
|
|
106
|
+
setattr(pb, "protocol", transport)
|
|
107
|
+
return True
|
|
108
|
+
except:
|
|
109
|
+
raise Exception(f"Transport could not be changed for property '{name}'.")
|
|
110
|
+
|
|
111
|
+
def update_endpoint(self, name: str, endpoint: str):
|
|
112
|
+
""" Update current property endpoint"""
|
|
113
|
+
pb = self._registry.get(name)
|
|
114
|
+
if not pb:
|
|
115
|
+
raise KeyError(f"No property with name '{name}' found.")
|
|
116
|
+
try:
|
|
117
|
+
if pb.protocol == "mqtt": #TODO: make this more generic and less hardcoded
|
|
118
|
+
pb.mqtt.generic_endpoint = endpoint
|
|
119
|
+
pb.mqtt.topic = endpoint
|
|
120
|
+
else:
|
|
121
|
+
print(f"Endpoint update for transport '{pb.protocol}' is not implemented yet.")
|
|
122
|
+
return False
|
|
123
|
+
return True
|
|
124
|
+
except:
|
|
125
|
+
raise Exception(f"Endpoint could not be changed for property '{name}'.")
|
|
126
|
+
|
|
127
|
+
def add_property(self,name="TBD",description="TBD",protocol=None,mqtt=None,redis=None,opc_ua=None,rabbitmq=None,kafka=None,zenoh=None,twincat_ads=None):
|
|
128
|
+
""" Manual add property to transport"""
|
|
129
|
+
pb = PropertyBinding(
|
|
130
|
+
name=name,
|
|
131
|
+
description=description,
|
|
132
|
+
protocol=protocol,
|
|
133
|
+
mqtt=MqttBinding(topic=mqtt,QoS=2) if mqtt else None,
|
|
134
|
+
redis=RedisBinding(key=redis) if redis else None,
|
|
135
|
+
opc_ua=OpcUaBinding(node_id=opc_ua) if opc_ua else None,
|
|
136
|
+
rabbitmq=RabbitMqBinding(routing_key=rabbitmq) if rabbitmq else None,
|
|
137
|
+
kafka=KafkaBinding(topic=kafka) if kafka else None,
|
|
138
|
+
zenoh=ZenohBinding(resource_key=zenoh) if zenoh else None,
|
|
139
|
+
twincat_ads=TwincatAdsBinding(data_name=twincat_ads) if twincat_ads else None
|
|
140
|
+
)
|
|
141
|
+
self._registry[pb.name] = pb
|
|
142
|
+
|
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
class Timer:
|
|
5
|
+
def __init__(self, period_sec, callback):
|
|
6
|
+
self._period = period_sec
|
|
7
|
+
self._callback = callback
|
|
8
|
+
self._running = False
|
|
9
|
+
self._thread = threading.Thread(target=self._run)
|
|
10
|
+
|
|
11
|
+
def _run(self):
|
|
12
|
+
while self._running:
|
|
13
|
+
time.sleep(self._period)
|
|
14
|
+
self._callback()
|
|
15
|
+
|
|
16
|
+
def start(self):
|
|
17
|
+
self._running = True
|
|
18
|
+
self._thread.start()
|
|
19
|
+
|
|
20
|
+
def stop(self):
|
|
21
|
+
self._running = False
|
|
22
|
+
self._thread.join()
|
|
File without changes
|
|
@@ -0,0 +1,261 @@
|
|
|
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 threading import Timer
|
|
10
|
+
import yaml
|
|
11
|
+
import configparser
|
|
12
|
+
import random
|
|
13
|
+
import uuid
|
|
14
|
+
import subprocess
|
|
15
|
+
import os
|
|
16
|
+
import json
|
|
17
|
+
import datetime
|
|
18
|
+
class perpetualTimer():
|
|
19
|
+
|
|
20
|
+
def __init__(self,t,hFunction):
|
|
21
|
+
self.t=t
|
|
22
|
+
self.hFunction = hFunction
|
|
23
|
+
#self.thread = Timer(self.t,self.handle_function)
|
|
24
|
+
self.thread = Timer(self.t, self.handle_function)
|
|
25
|
+
x=1
|
|
26
|
+
|
|
27
|
+
def handle_function(self):
|
|
28
|
+
self.hFunction()
|
|
29
|
+
self.thread = Timer(self.t,self.handle_function)
|
|
30
|
+
self.thread.start()
|
|
31
|
+
|
|
32
|
+
def start(self):
|
|
33
|
+
self.thread.start()
|
|
34
|
+
|
|
35
|
+
def cancel(self):
|
|
36
|
+
self.thread.cancel()
|
|
37
|
+
|
|
38
|
+
def isAlive(self):
|
|
39
|
+
return self.thread.is_alive()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_timestamp():
|
|
43
|
+
return datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
|
|
44
|
+
|
|
45
|
+
def load_config(config_file):
|
|
46
|
+
with open(config_file, 'r') as file:
|
|
47
|
+
return yaml.safe_load(file)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def load_flexia_config(config_file):
|
|
51
|
+
# read the configuration file
|
|
52
|
+
config = configparser.ConfigParser()
|
|
53
|
+
config.optionxform = str # preserve case of keys
|
|
54
|
+
config.read(config_file)
|
|
55
|
+
|
|
56
|
+
data = {}
|
|
57
|
+
|
|
58
|
+
# Process all sections
|
|
59
|
+
for section in config.sections():
|
|
60
|
+
if ":" in section:
|
|
61
|
+
# Split sections like 'build:Oberon-5' into prefix and variable name
|
|
62
|
+
prefix, variable = section.split(":", 1)
|
|
63
|
+
if prefix not in data:
|
|
64
|
+
data[prefix] = {}
|
|
65
|
+
data[prefix][variable] = dict(config[section])
|
|
66
|
+
else:
|
|
67
|
+
data[section] = dict(config[section])
|
|
68
|
+
|
|
69
|
+
return data
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def generate_random_name(separator="_"):
|
|
73
|
+
"""
|
|
74
|
+
Generates a random name similar to Docker's naming scheme, with an optional UUID.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
separator (str): The character to separate the adjective and the noun (default: "_").
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
str: A randomly generated name with a UUID.
|
|
81
|
+
"""
|
|
82
|
+
adjectives = [
|
|
83
|
+
"adoring", "brave", "clever", "daring", "eager", "frosty",
|
|
84
|
+
"gentle", "happy", "jolly", "kind", "lively", "mighty",
|
|
85
|
+
"noble", "optimistic", "peaceful", "quirky", "radiant",
|
|
86
|
+
"spirited", "trusty", "upbeat", "vivid", "witty", "youthful",
|
|
87
|
+
"zesty", "dynamic", "bold", "charming", "elegant", "fiery",
|
|
88
|
+
"graceful", "heroic", "inventive", "joyful", "keen", "legendary",
|
|
89
|
+
"marvelous", "nimble", "outgoing", "plucky", "quick", "resilient",
|
|
90
|
+
"sturdy", "tenacious", "vibrant", "whimsical", "zealous"
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
nouns = [
|
|
94
|
+
"einstein", "curie", "turing", "lovelace", "newton", "galileo",
|
|
95
|
+
"tesla", "hopper", "feynman", "darwin", "goodall", "carson",
|
|
96
|
+
"babbage", "franklin", "leonardo", "plato", "aristotle", "shakespeare",
|
|
97
|
+
"copernicus", "fermat", "noether", "gauss", "archimedes", "pythagoras",
|
|
98
|
+
"pasteur", "edison", "faraday", "kepler", "bohr", "planck",
|
|
99
|
+
"heisenberg", "ramanujan", "meitner", "daguerre", "volta", "da_vinci",
|
|
100
|
+
"marconi", "brunel", "darwin", "maxwell", "cavendish", "hawking",
|
|
101
|
+
"lamarr", "carver", "knuth", "von_neumann", "mendel", "sagan"
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
# Choose a random adjective and noun
|
|
105
|
+
adjective = random.choice(adjectives)
|
|
106
|
+
noun = random.choice(nouns)
|
|
107
|
+
|
|
108
|
+
# Generate a random UUID
|
|
109
|
+
unique_id = uuid.uuid4().hex[:4] # Shorten the UUID to 8 characters
|
|
110
|
+
|
|
111
|
+
# Combine with the separator and return
|
|
112
|
+
return f"{adjective}{separator}{noun}{separator}{unique_id}"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def generate_key(length=4):
|
|
116
|
+
return uuid.uuid4().hex[:length]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
import yaml
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def generate_config(name, platformID, primaryHardwareID, projectID, key, shared_secret, filename="communicationMatrix.yaml"):
|
|
123
|
+
config = {
|
|
124
|
+
"logging": {
|
|
125
|
+
"level": "DEBUG",
|
|
126
|
+
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
127
|
+
"file": "logs/syslog_local.log",
|
|
128
|
+
"remote": True
|
|
129
|
+
},
|
|
130
|
+
"flexCloud": {
|
|
131
|
+
"storage_type": "global",
|
|
132
|
+
"redis_host": "localhost",
|
|
133
|
+
"redis_port": 6379,
|
|
134
|
+
"redis_db": 6
|
|
135
|
+
},
|
|
136
|
+
"deviceCredentials": {
|
|
137
|
+
"name": name,
|
|
138
|
+
"platformID": platformID,
|
|
139
|
+
"primaryHardwareID": primaryHardwareID,
|
|
140
|
+
"projectID": projectID,
|
|
141
|
+
"key": key,
|
|
142
|
+
"shared_secret": shared_secret
|
|
143
|
+
},
|
|
144
|
+
"heartbeat": {
|
|
145
|
+
"heartbeat_interval": 10,
|
|
146
|
+
"ttl": 12
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if filename is not None:
|
|
150
|
+
with open(filename, "w") as file:
|
|
151
|
+
yaml.dump(config, file, default_flow_style=False, sort_keys=False)
|
|
152
|
+
else:
|
|
153
|
+
print(config)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def get_git_remote_url(folder: str) -> str:
|
|
157
|
+
"""
|
|
158
|
+
Determines the Git remote URL from a given folder.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
folder (str): The path to the folder.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
str: The Git remote URL if found, otherwise an empty string.
|
|
165
|
+
"""
|
|
166
|
+
if not os.path.isdir(folder):
|
|
167
|
+
raise ValueError(f"The provided path '{folder}' is not a valid directory.")
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
# Navigate to the folder and check if it's a git repository
|
|
171
|
+
git_dir = os.path.join(folder, ".git")
|
|
172
|
+
if not os.path.isdir(git_dir):
|
|
173
|
+
raise ValueError(f"The provided folder '{folder}' is not a Git repository.")
|
|
174
|
+
|
|
175
|
+
# Get the remote URL
|
|
176
|
+
result = subprocess.run(
|
|
177
|
+
["git", "config", "--get", "remote.origin.url"],
|
|
178
|
+
cwd=folder,
|
|
179
|
+
capture_output=True,
|
|
180
|
+
text=True,
|
|
181
|
+
check=True
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
return result.stdout.strip() if result.stdout else ""
|
|
185
|
+
|
|
186
|
+
except subprocess.CalledProcessError:
|
|
187
|
+
return ""
|
|
188
|
+
|
|
189
|
+
def yaml_to_json(yaml_path: str, json_path: str = None, *, sort_keys: bool = False, indent: int = 2) -> str:
|
|
190
|
+
"""
|
|
191
|
+
Load a YAML file and convert it to JSON.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
yaml_path: Path to the input YAML file.
|
|
195
|
+
json_path: Optional path to write the JSON output. If None, won’t write to disk.
|
|
196
|
+
sort_keys: Whether to sort the keys in the output JSON.
|
|
197
|
+
indent: Number of spaces to use for JSON indentation.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
The JSON string representation of the YAML content.
|
|
201
|
+
"""
|
|
202
|
+
# Read YAML
|
|
203
|
+
with open(yaml_path, 'r', encoding='utf-8') as yf:
|
|
204
|
+
data = yaml.safe_load(yf)
|
|
205
|
+
|
|
206
|
+
# Convert to JSON string
|
|
207
|
+
json_str = json.dumps(data, sort_keys=sort_keys, indent=indent)
|
|
208
|
+
|
|
209
|
+
# Optionally write to file
|
|
210
|
+
if json_path:
|
|
211
|
+
with open(json_path, 'w', encoding='utf-8') as jf:
|
|
212
|
+
jf.write(json_str)
|
|
213
|
+
|
|
214
|
+
return json_str
|
|
215
|
+
|
|
216
|
+
import json
|
|
217
|
+
import yaml
|
|
218
|
+
|
|
219
|
+
def json_to_yaml(
|
|
220
|
+
json_str,
|
|
221
|
+
yaml_path: str = None,
|
|
222
|
+
*,
|
|
223
|
+
sort_keys: bool = False,
|
|
224
|
+
default_flow_style: bool = False,
|
|
225
|
+
indent: int = 2
|
|
226
|
+
) -> str:
|
|
227
|
+
"""
|
|
228
|
+
Load a JSON file and convert it to YAML.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
json_path: Path to the input JSON file.
|
|
232
|
+
yaml_path: Optional path to write the YAML output. If None, won’t write to disk.
|
|
233
|
+
sort_keys: Whether to sort the keys in the output YAML.
|
|
234
|
+
default_flow_style: If True, uses block style; if False, uses flow style for collections.
|
|
235
|
+
indent: Number of spaces to use for YAML indentation.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
The YAML string representation of the JSON content.
|
|
239
|
+
"""
|
|
240
|
+
# Read JSON
|
|
241
|
+
#with open(json_path, 'r', encoding='utf-8') as jf:
|
|
242
|
+
# data = json.load(jf)
|
|
243
|
+
try:
|
|
244
|
+
data = json.load(json_str)
|
|
245
|
+
except:
|
|
246
|
+
data = json_str
|
|
247
|
+
|
|
248
|
+
# Convert to YAML string
|
|
249
|
+
yaml_str = yaml.safe_dump(
|
|
250
|
+
data,
|
|
251
|
+
sort_keys=sort_keys,
|
|
252
|
+
default_flow_style=default_flow_style,
|
|
253
|
+
indent=indent
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Optionally write to file
|
|
257
|
+
if yaml_path:
|
|
258
|
+
with open(yaml_path, 'w', encoding='utf-8') as yf:
|
|
259
|
+
yf.write(yaml_str)
|
|
260
|
+
|
|
261
|
+
return yaml_str
|