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,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
+
@@ -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()
@@ -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