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
File without changes
File without changes
File without changes
@@ -0,0 +1,85 @@
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 action(object):
11
+ def __init__(self, name="tbd", request=None, response=None,feedback=None,callback=None,requestProperty=None, responseProperty=None,feedbackProperty=None):
12
+ self._name = name
13
+ self._request = request
14
+ self._response = response
15
+ self._feedback = feedback
16
+ self._requestProperty = requestProperty
17
+ self._responseProperty = responseProperty
18
+ self._feedbackProperty = feedbackProperty
19
+ self._callback = callback
20
+
21
+ @property
22
+ def name(self):
23
+ return self._name
24
+
25
+ @name.setter
26
+ def name(self, value):
27
+ self._name = value
28
+
29
+ @property
30
+ def request(self):
31
+ return self._request
32
+
33
+ @request.setter
34
+ def request(self, value):
35
+ self._request = value
36
+
37
+ @property
38
+ def response(self):
39
+ return self._response
40
+
41
+ @response.setter
42
+ def response(self, value):
43
+ self._response = value
44
+
45
+ @property
46
+ def feedback(self):
47
+ return self._feedback
48
+
49
+ @feedback.setter
50
+ def feedback(self, value):
51
+ self._feedback = value
52
+
53
+ @property
54
+ def requestProperty(self):
55
+ return self._requestProperty
56
+
57
+ @requestProperty.setter
58
+ def requestProperty(self, value):
59
+ self._requestProperty = value
60
+
61
+ @property
62
+ def responseProperty(self):
63
+ return self._responseProperty
64
+
65
+ @responseProperty.setter
66
+ def responseProperty(self, value):
67
+ self._responseProperty = value
68
+
69
+ @property
70
+ def feedbackProperty(self):
71
+ return self._feedbackProperty
72
+
73
+ @feedbackProperty.setter
74
+ def feedbackProperty(self, value):
75
+ self._feedbackProperty = value
76
+
77
+ @property
78
+ def callback(self):
79
+ return self._callback
80
+
81
+ @callback.setter
82
+ def callback(self, value):
83
+ self._callback = value
84
+
85
+
@@ -0,0 +1,70 @@
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 ation_client(object):
12
+ def __init__(self, name="tbd", action=None,logger=None,set_data=None):
13
+ self._name = name
14
+ self._action = action
15
+ self._isWaitingforAction = False
16
+ self._cv = threading.Condition()
17
+ self._logger = logger
18
+ self._set_data = set_data
19
+ self._done_callback = None
20
+ self._feedback_callback = None
21
+
22
+ @property
23
+ def action(self):
24
+ return self._action
25
+
26
+ @action.setter
27
+ def action(self, value):
28
+ self._action = value
29
+
30
+ def call_async(self,request):
31
+ self._logger.info("Async action <"+self.action.name+"> called.")
32
+ self._set_data(data_name=self._action.requestProperty, data=request)
33
+ self._isWaitingforAction = True
34
+
35
+ def spin_until_future_complete(self, timeout=5.0):
36
+ # Wait for response or timeout
37
+ with self._cv:
38
+ if not self._cv.wait_for(lambda: not self._isWaitingforAction, timeout=timeout):
39
+ self._isWaitingforAction = False
40
+ self._logger.info("Async action <" + self.action.name + "> called failed, no response received within timeout ["+timeout.__str__()+" sec.].")
41
+ return -1
42
+
43
+ self._isWaitingforAction = False
44
+ return self._action.response
45
+
46
+
47
+ def response_callback(self,topic,response):
48
+ #set response
49
+ self.action.response.instantiate(json_str=response)
50
+ self._isWaitingforAction = False
51
+
52
+ #DONE CALLBACK
53
+ if self._done_callback is not None:
54
+ self._done_callback(self.action.response)
55
+
56
+ def feedback_callback(self,topic,feedback):
57
+ #set response
58
+ self.action.feedback.instantiate(json_str=feedback)
59
+
60
+ #DONE CALLBACK
61
+ if self._feedback_callback is not None:
62
+ self._feedback_callback(self.action.feedback)
63
+
64
+ def add_done_callback(self,callback=None):
65
+ self._done_callback = callback
66
+
67
+ def add_feedback_callback(self,callback=None):
68
+ self._feedback_callback = callback
69
+
70
+
@@ -0,0 +1,50 @@
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.utils.constants import *
10
+ import jsonpickle
11
+ class goal_handle(object):
12
+ def __init__(self, name="tbd", action=None,logger=None):
13
+ self._name = name
14
+ self._action = action
15
+ self._logger = logger
16
+
17
+ @property
18
+ def action(self):
19
+ return self._action
20
+
21
+ @action.setter
22
+ def action(self, value):
23
+ self._action = value
24
+
25
+ @property
26
+ def request(self):
27
+ return self._action.request
28
+
29
+ @request.setter
30
+ def request(self, value):
31
+ self._action.request = value
32
+
33
+ @property
34
+ def response(self):
35
+ return self._action.response
36
+
37
+ @response.setter
38
+ def response(self, value):
39
+ self._action.response = value
40
+
41
+ @property
42
+ def feedback(self):
43
+ return self._action.feedback
44
+
45
+ @feedback.setter
46
+ def feedback(self,value):
47
+ self._action.feedback = value
48
+
49
+
50
+
@@ -0,0 +1,227 @@
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 base64
10
+ import json
11
+ from dataclasses import dataclass
12
+ from typing import Any, Dict, Optional, Union
13
+ import numpy as np
14
+ import cv2
15
+
16
+ @dataclass
17
+ class DecodedImageMessage:
18
+ frame: Optional[np.ndarray]
19
+ encoding: str
20
+ metadata: Dict[str, Any]
21
+ stamp_sec: Optional[int]
22
+ stamp_nanosec: Optional[int]
23
+ frame_id: Optional[str]
24
+ height: Optional[int]
25
+ width: Optional[int]
26
+ ros_encoding: Optional[str]
27
+ is_bigendian: Optional[int]
28
+ step: Optional[int]
29
+ raw_bytes: bytes
30
+
31
+
32
+ class ROS2ImageDecoder:
33
+ def decode_message(
34
+ self,
35
+ payload: Union[str, bytes, Dict[str, Any]],
36
+ ) -> Optional[DecodedImageMessage]:
37
+
38
+ data = self._parse_payload(payload)
39
+ if data is None:
40
+ return None
41
+
42
+ payload_obj = self._extract_payload_obj(data)
43
+ if not payload_obj:
44
+ return None
45
+
46
+ encoding = payload_obj.get("encoding", "")
47
+ raw_b64 = payload_obj.get("data", "")
48
+ metadata = payload_obj.get("metadata", {}) or {}
49
+
50
+ if not raw_b64:
51
+ return None
52
+
53
+ try:
54
+ raw_bytes = base64.b64decode(raw_b64)
55
+ except Exception as exc:
56
+ print(f"Failed to base64-decode image payload: {exc}")
57
+ return None
58
+
59
+ frame = self._decode_image_bytes(
60
+ raw_bytes=raw_bytes,
61
+ payload_encoding=encoding,
62
+ metadata=metadata,
63
+ )
64
+
65
+ stamp = metadata.get("stamp", {}) or {}
66
+
67
+ return DecodedImageMessage(
68
+ frame=frame,
69
+ encoding=encoding,
70
+ metadata=metadata,
71
+ stamp_sec=stamp.get("sec"),
72
+ stamp_nanosec=stamp.get("nanosec"),
73
+ frame_id=metadata.get("frame_id"),
74
+ height=metadata.get("height"),
75
+ width=metadata.get("width"),
76
+ ros_encoding=metadata.get("encoding"),
77
+ is_bigendian=metadata.get("is_bigendian"),
78
+ step=metadata.get("step"),
79
+ raw_bytes=raw_bytes,
80
+ )
81
+
82
+ def _parse_payload(
83
+ self,
84
+ payload: Union[str, bytes, Dict[str, Any]],
85
+ ) -> Optional[Dict[str, Any]]:
86
+
87
+ if isinstance(payload, dict):
88
+ return payload
89
+
90
+ if isinstance(payload, bytes):
91
+ try:
92
+ payload = payload.decode("utf-8")
93
+ except UnicodeDecodeError as exc:
94
+ print(f"Failed to decode MQTT payload as UTF-8: {exc}")
95
+ return None
96
+
97
+ if isinstance(payload, str):
98
+ try:
99
+ return json.loads(payload)
100
+ except json.JSONDecodeError as exc:
101
+ print(f"Failed to parse JSON payload: {exc}")
102
+ return None
103
+
104
+ return None
105
+
106
+ def _extract_payload_obj(self, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
107
+ """
108
+ Supports:
109
+
110
+ 1. {"_payload": {...}}
111
+ 2. {"payload": {"_payload": {...}}}
112
+ 3. {"payload": {...}}
113
+ 4. {"encoding": "...", "data": "..."}
114
+ """
115
+
116
+ if "_payload" in data and isinstance(data["_payload"], dict):
117
+ return data["_payload"]
118
+
119
+ if "payload" in data and isinstance(data["payload"], dict):
120
+ nested = data["payload"]
121
+
122
+ if "_payload" in nested and isinstance(nested["_payload"], dict):
123
+ return nested["_payload"]
124
+
125
+ if "encoding" in nested and "data" in nested:
126
+ return nested
127
+
128
+ if "encoding" in data and "data" in data:
129
+ return data
130
+
131
+ return None
132
+
133
+ def _decode_image_bytes(
134
+ self,
135
+ raw_bytes: bytes,
136
+ payload_encoding: str,
137
+ metadata: Dict[str, Any],
138
+ ) -> Optional[np.ndarray]:
139
+
140
+ if payload_encoding in (
141
+ "image_jpeg_base64",
142
+ "image_jpg_base64",
143
+ "image_png_base64",
144
+ ):
145
+ arr = np.frombuffer(raw_bytes, dtype=np.uint8)
146
+ frame_bgr = cv2.imdecode(arr, cv2.IMREAD_COLOR)
147
+
148
+ if frame_bgr is None:
149
+ print("OpenCV failed to decode compressed image.")
150
+ return None
151
+
152
+ return frame_bgr
153
+
154
+ if payload_encoding == "image_raw_base64":
155
+ return self._decode_raw_image(raw_bytes, metadata)
156
+
157
+ print(f"Unsupported payload encoding: {payload_encoding}")
158
+ return None
159
+
160
+ def _decode_raw_image(
161
+ self,
162
+ raw_bytes: bytes,
163
+ metadata: Dict[str, Any],
164
+ ) -> Optional[np.ndarray]:
165
+
166
+ height = metadata.get("height")
167
+ width = metadata.get("width")
168
+ ros_encoding = metadata.get("encoding")
169
+ step = metadata.get("step")
170
+
171
+ if not height or not width or not ros_encoding:
172
+ print("Missing raw image metadata: height, width, or encoding.")
173
+ return None
174
+
175
+ channels_by_encoding = {
176
+ "mono8": 1,
177
+ "rgb8": 3,
178
+ "bgr8": 3,
179
+ "rgba8": 4,
180
+ "bgra8": 4,
181
+ }
182
+
183
+ channels = channels_by_encoding.get(ros_encoding)
184
+
185
+ if channels is None:
186
+ print(f"Unsupported raw ROS image encoding: {ros_encoding}")
187
+ return None
188
+
189
+ arr = np.frombuffer(raw_bytes, dtype=np.uint8)
190
+
191
+ expected_row_bytes = width * channels
192
+
193
+ if step is None:
194
+ step = expected_row_bytes
195
+
196
+ expected_total_bytes = height * step
197
+
198
+ if arr.size < expected_total_bytes:
199
+ print(
200
+ f"Raw image buffer too small: got {arr.size} bytes, "
201
+ f"expected at least {expected_total_bytes} bytes."
202
+ )
203
+ return None
204
+
205
+ arr = arr[:expected_total_bytes]
206
+
207
+ rows = arr.reshape((height, step))
208
+ image_data = rows[:, :expected_row_bytes]
209
+
210
+ if channels == 1:
211
+ return image_data.reshape((height, width))
212
+
213
+ frame = image_data.reshape((height, width, channels))
214
+
215
+ if ros_encoding == "rgb8":
216
+ return cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
217
+
218
+ if ros_encoding == "bgr8":
219
+ return frame
220
+
221
+ if ros_encoding == "rgba8":
222
+ return cv2.cvtColor(frame, cv2.COLOR_RGBA2BGR)
223
+
224
+ if ros_encoding == "bgra8":
225
+ return cv2.cvtColor(frame, cv2.COLOR_BGRA2BGR)
226
+
227
+ return None
@@ -0,0 +1,113 @@
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.security.security import verify_signature
10
+ from flexCommunicator.clientLibraries.flcpy.utils.constants import *
11
+ import threading
12
+ import json
13
+ class commandProcessor(object):
14
+ def __init__(self,name="cmdProcessor",logger=None,verbose=False):
15
+ """Initialize a Logger component.
16
+
17
+ Parameters
18
+ ----------
19
+ name : string
20
+ name of the property components
21
+
22
+ verbose : bool
23
+ component verbose execution
24
+
25
+ See Also
26
+ --------
27
+ ..
28
+
29
+ Examples
30
+ --------
31
+ >> cmdProcessor = commandProcessor(name="customProcessor",verbose=False)
32
+
33
+ """
34
+
35
+ # --- logger configuration ---
36
+ self._name = name
37
+ self._verbose = verbose
38
+ self._logger = logger
39
+ # Set to store received command IDs.
40
+ self._received_command_ids = set()
41
+ self._command_ids_lock = threading.Lock()
42
+
43
+ self._logger.info("Command processor initialized")
44
+
45
+
46
+ def process_command(self,command):
47
+ """Process a command """
48
+ action = None
49
+ data = {}
50
+
51
+ # DECODE INCOMMING COMMAND
52
+ try:
53
+ command = json.loads(command)
54
+ except:
55
+ command = GENERIC_STATUS.INCORRECT_CMD
56
+
57
+ # CHECK COMMAND CORRECTNESS (SIGNATURE)
58
+ signature_valid = verify_signature(command=command)
59
+
60
+ try:
61
+ #CHECK IF COMMAND IS ALREADY RECEIVED
62
+ if not self.is_command_already_received(command):
63
+ if command["cmd"] == FLEXCLOUD_COMMANDS.UNKNOWN:
64
+ if self._logger is not None: self._logger.error(COMMAND_TYPE.UNKNOWN+" command received. Please verify the command type")
65
+ return action,data
66
+ elif command["cmd"] == FLEXCLOUD_COMMANDS.PING:
67
+ if self._logger is not None: self._logger.info(COMMAND_TYPE.PING+" command received.")
68
+ if self._verbose: print(FLEXCLOUD_COMMANDS.PING+" command received.")
69
+ action = FLEXCLOUD_COMMANDS.PING
70
+ data = {}
71
+ return signature_valid,action,data
72
+ elif command["cmd"] == FLEXCLOUD_COMMANDS.UPDATE_TRANSPORT:
73
+ if self._logger is not None: self._logger.info(FLEXCLOUD_COMMANDS.UPDATE_TRANSPORT+" command received.")
74
+ if self._verbose: print(FLEXCLOUD_COMMANDS.UPDATE_TRANSPORT+" command received.")
75
+ action = FLEXCLOUD_COMMANDS.UPDATE_TRANSPORT
76
+ data = command["params"]
77
+ return signature_valid,action,data
78
+ else:
79
+ if self._logger is not None: self._logger.warning(FLEXCLOUD_WARNINGS.WARN_UNKNOWN_FLEXCLOUD_COMMAND)
80
+ return signature_valid,action,data
81
+
82
+ #TODO: extend with other accepted commands
83
+
84
+ except Exception as e:
85
+ if self._logger is not None: self._logger.error("Failed to process command.")
86
+
87
+
88
+ def is_command_already_received(self,command):
89
+ """
90
+ Checks if a command has already been received by inspecting its unique 'id' field.
91
+
92
+ This function uses a global set to keep track of command IDs that have already been processed.
93
+ If the command has not been received before, its ID is added to the set.
94
+
95
+ Args:
96
+ command (dict): The command dictionary. Must include an 'id' key.
97
+
98
+ Returns:
99
+ bool: True if the command was already received, False if it is new.
100
+
101
+ Raises:
102
+ ValueError: If the command does not contain an 'id' field.
103
+ """
104
+ command_id = command.get("id")
105
+ if command_id is None:
106
+ raise ValueError("Command does not contain an 'id' field.")
107
+
108
+ with self._command_ids_lock:
109
+ if command_id in self._received_command_ids:
110
+ return True
111
+ else:
112
+ self._received_command_ids.add(command_id)
113
+ return False
@@ -0,0 +1,25 @@
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 uuid
11
+ from flexCommunicator.clientLibraries.flcpy.security.security import *
12
+ def compose_command(cmd_type, params, priority=5, expiry_seconds=60):
13
+ current_time = int(time.time())
14
+
15
+ _command = {
16
+ "id": str(uuid.uuid4()),
17
+ "cmd": cmd_type,
18
+ "priority": priority, # Add command priority (e.g., 1=highest, 10=lowest)
19
+ "expiry": current_time + expiry_seconds, # Command expiry timestamp
20
+ "params": params,
21
+ "timestamp": current_time,
22
+ "nonce": uuid.uuid4().hex
23
+ }
24
+ _command["signature"] = compute_signature(_command)
25
+ return _command