cgse-core 0.17.2__tar.gz → 0.17.3__tar.gz
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.
- {cgse_core-0.17.2 → cgse_core-0.17.3}/PKG-INFO +1 -1
- {cgse_core-0.17.2 → cgse_core-0.17.3}/pyproject.toml +1 -1
- cgse_core-0.17.3/src/egse/connect.py +55 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/dummy.py +3 -19
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/logger/__init__.py +2 -2
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/notifyhub/server.py +1 -3
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/registry/client.py +3 -9
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/registry/server.py +3 -11
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/registry/service.py +5 -11
- cgse_core-0.17.2/src/egse/async_control.py +0 -1085
- cgse_core-0.17.2/src/egse/async_control_claude.py +0 -807
- cgse_core-0.17.2/src/egse/confman/confman_acs.py +0 -35
- cgse_core-0.17.2/src/egse/connect.py +0 -528
- cgse_core-0.17.2/src/egse/metricshub/__init__.py +0 -0
- cgse_core-0.17.2/src/egse/metricshub/server.py +0 -271
- cgse_core-0.17.2/src/egse/notifyhub/test.py +0 -303
- {cgse_core-0.17.2 → cgse_core-0.17.3}/.gitignore +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/README.md +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/cgse_core/__init__.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/cgse_core/_start.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/cgse_core/_status.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/cgse_core/_stop.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/cgse_core/cgse_explore.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/cgse_core/services.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/cgse_core/settings.yaml +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/_setup_core.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/command.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/confman/__init__.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/confman/__main__.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/confman/confman.yaml +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/confman/confman_cs.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/control.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/icons/busy.svg +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/icons/operational-mode.svg +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/icons/pm_ui.svg +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/icons/simulator-mode.svg +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/icons/start-process-button.svg +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/icons/stop-process-button.svg +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/icons/user-interface.svg +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/listener.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/logger/__main__.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/logger/log_cs.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/mixin.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/monitoring.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/notifyhub/__init__.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/notifyhub/client.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/notifyhub/event.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/notifyhub/services.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/procman/__init__.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/procman/procman.yaml +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/procman/procman_cs.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/procman/procman_protocol.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/procman/procman_ui.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/protocol.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/proxy.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/registry/__init__.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/registry/backend.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/services.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/services.yaml +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/storage/__init__.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/storage/__main__.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/storage/persistence.py +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/storage/storage.yaml +0 -0
- {cgse_core-0.17.2 → cgse_core-0.17.3}/src/egse/storage/storage_cs.py +0 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from egse.env import bool_env
|
|
2
|
+
from egse.log import logging
|
|
3
|
+
from egse.zmq_ser import connect_address
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger("egse.connect")
|
|
6
|
+
|
|
7
|
+
# random.seed(time.monotonic()) # uncomment for testing only, main application should set a seed.
|
|
8
|
+
|
|
9
|
+
VERBOSE_DEBUG = bool_env("VERBOSE_DEBUG")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_endpoint(
|
|
13
|
+
service_type: str,
|
|
14
|
+
protocol: str = "tcp",
|
|
15
|
+
hostname: str = "localhost",
|
|
16
|
+
port: int = 0,
|
|
17
|
+
):
|
|
18
|
+
"""
|
|
19
|
+
Returns the endpoint for a service, either from the registry or by constructing
|
|
20
|
+
it from protocol, hostname and port.
|
|
21
|
+
|
|
22
|
+
If port is 0 (the default), attempt to retrieve the endpoint from the service registry.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
service_type: The service type to look up in the registry.
|
|
26
|
+
protocol: Protocol to use if constructing the endpoint, defaults to tcp.
|
|
27
|
+
hostname: Hostname to use if constructing the endpoint, defaults to localhost.
|
|
28
|
+
port: Port to use if constructing the endpoint, defaults to 0.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
The endpoint string.
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
RuntimeError: If no endpoint can be determined.
|
|
35
|
+
"""
|
|
36
|
+
endpoint = None
|
|
37
|
+
from egse.registry.client import RegistryClient
|
|
38
|
+
|
|
39
|
+
if port == 0:
|
|
40
|
+
with RegistryClient() as reg:
|
|
41
|
+
endpoint = reg.get_endpoint(service_type)
|
|
42
|
+
if endpoint:
|
|
43
|
+
if VERBOSE_DEBUG:
|
|
44
|
+
logger.debug(f"Endpoint for {service_type} found in registry: {endpoint}")
|
|
45
|
+
else:
|
|
46
|
+
logger.warning(f"No endpoint for {service_type} found in registry.")
|
|
47
|
+
|
|
48
|
+
if not endpoint:
|
|
49
|
+
if port == 0:
|
|
50
|
+
raise RuntimeError(f"No service registered as {service_type} and no port provided.")
|
|
51
|
+
endpoint = connect_address(protocol, hostname, port)
|
|
52
|
+
if VERBOSE_DEBUG:
|
|
53
|
+
logger.debug(f"Endpoint constructed from protocol/hostname/port: {endpoint}")
|
|
54
|
+
|
|
55
|
+
return endpoint
|
|
@@ -35,7 +35,6 @@ and stopped with:
|
|
|
35
35
|
|
|
36
36
|
from __future__ import annotations
|
|
37
37
|
|
|
38
|
-
import contextlib
|
|
39
38
|
import multiprocessing
|
|
40
39
|
import random
|
|
41
40
|
import select
|
|
@@ -53,14 +52,12 @@ from egse.device import DeviceConnectionError
|
|
|
53
52
|
from egse.device import DeviceConnectionInterface
|
|
54
53
|
from egse.device import DeviceTimeoutError
|
|
55
54
|
from egse.device import DeviceTransport
|
|
56
|
-
from egse.env import bool_env
|
|
57
55
|
from egse.log import logger
|
|
58
56
|
from egse.protocol import CommandProtocol
|
|
59
57
|
from egse.proxy import Proxy
|
|
60
58
|
from egse.system import SignalCatcher
|
|
61
59
|
from egse.system import attrdict
|
|
62
60
|
from egse.system import format_datetime
|
|
63
|
-
from egse.system import type_name
|
|
64
61
|
from egse.zmq_ser import bind_address
|
|
65
62
|
from egse.zmq_ser import connect_address
|
|
66
63
|
|
|
@@ -80,9 +77,6 @@ WRITE_TIMEOUT = 1.0
|
|
|
80
77
|
CONNECT_TIMEOUT = 3.0
|
|
81
78
|
"""The maximum time in seconds to wait for establishing a socket connect."""
|
|
82
79
|
|
|
83
|
-
|
|
84
|
-
VERBOSE_DEBUG = bool_env("VERBOSE_DEBUG", default=False)
|
|
85
|
-
|
|
86
80
|
# Especially DummyCommand and DummyController need to be defined in a known module
|
|
87
81
|
# because those objects are pickled and when de-pickled at the clients side the class
|
|
88
82
|
# definition must be known.
|
|
@@ -122,17 +116,14 @@ def is_dummy_cs_active() -> bool:
|
|
|
122
116
|
|
|
123
117
|
|
|
124
118
|
def is_dummy_dev_active() -> bool:
|
|
125
|
-
if VERBOSE_DEBUG:
|
|
126
|
-
logger.debug("Checking if dummy device is active...")
|
|
127
119
|
try:
|
|
128
120
|
dev = DummyDeviceEthernetInterface(DEV_HOST, DEV_PORT)
|
|
129
121
|
dev.connect()
|
|
130
122
|
rc = dev.trans("ping\n")
|
|
131
123
|
dev.disconnect()
|
|
132
124
|
return rc.decode().strip() == "pong"
|
|
133
|
-
except
|
|
134
|
-
|
|
135
|
-
logger.debug(f"Caught {type_name(exc)}: {exc} - returning False")
|
|
125
|
+
except DeviceConnectionError as exc:
|
|
126
|
+
# logger.error(f"Caught {type_name(exc)}: {exc}")
|
|
136
127
|
return False
|
|
137
128
|
|
|
138
129
|
|
|
@@ -589,18 +580,11 @@ def start_dev():
|
|
|
589
580
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
590
581
|
s.bind((DEV_HOST, DEV_PORT))
|
|
591
582
|
s.listen()
|
|
592
|
-
s.settimeout(CONNECT_TIMEOUT)
|
|
593
583
|
logger.info(f"Ready to accept connection on {DEV_HOST}:{DEV_PORT}...")
|
|
594
|
-
|
|
595
|
-
with contextlib.suppress(socket.timeout):
|
|
596
|
-
conn, addr = s.accept()
|
|
597
|
-
break
|
|
598
|
-
if killer.term_signal_received:
|
|
599
|
-
return
|
|
584
|
+
conn, addr = s.accept()
|
|
600
585
|
with conn:
|
|
601
586
|
logger.info(f"Accepted connection from {addr}")
|
|
602
587
|
conn.sendall(f"Dummy Device {__version__}".encode())
|
|
603
|
-
conn.settimeout(READ_TIMEOUT)
|
|
604
588
|
try:
|
|
605
589
|
while True:
|
|
606
590
|
error_msg = ""
|
|
@@ -57,7 +57,7 @@ COMMANDER_PORT = settings.get("COMMANDER_PORT", 0) # dynamically assigned by th
|
|
|
57
57
|
_initialised = False # will be set to True in the setup_logging() function
|
|
58
58
|
|
|
59
59
|
|
|
60
|
-
def get_log_file_name()
|
|
60
|
+
def get_log_file_name():
|
|
61
61
|
"""
|
|
62
62
|
Returns the filename of the log file as defined in the Settings or return the default name 'general.log'.
|
|
63
63
|
"""
|
|
@@ -315,7 +315,7 @@ def send_request(command_request: str):
|
|
|
315
315
|
"""Sends a request to the Logger Control Server and waits for a response."""
|
|
316
316
|
|
|
317
317
|
if COMMANDER_PORT == 0:
|
|
318
|
-
endpoint = get_endpoint_from_registry(
|
|
318
|
+
endpoint = get_endpoint_from_registry()
|
|
319
319
|
else:
|
|
320
320
|
endpoint = f"{PROTOCOL}://{HOSTNAME}:{COMMANDER_PORT}"
|
|
321
321
|
|
|
@@ -23,12 +23,10 @@ from egse.notifyhub import SERVICE_TYPE
|
|
|
23
23
|
from egse.notifyhub import STATS_INTERVAL
|
|
24
24
|
from egse.notifyhub.client import AsyncNotificationHubClient
|
|
25
25
|
from egse.registry import MessageType
|
|
26
|
-
from egse.registry.client import REQUEST_TIMEOUT
|
|
27
|
-
from egse.registry.client import AsyncRegistryClient
|
|
26
|
+
from egse.registry.client import AsyncRegistryClient, REQUEST_TIMEOUT
|
|
28
27
|
from egse.system import TyperAsyncCommand
|
|
29
28
|
from egse.system import get_host_ip
|
|
30
29
|
from egse.zmq_ser import get_port_number
|
|
31
|
-
|
|
32
30
|
from .event import NotificationEvent
|
|
33
31
|
|
|
34
32
|
REQUEST_POLL_TIMEOUT = 1.0
|
|
@@ -639,8 +639,6 @@ class AsyncRegistryClient:
|
|
|
639
639
|
The response from the registry as a dictionary.
|
|
640
640
|
"""
|
|
641
641
|
|
|
642
|
-
assert self.req_socket is not None, "REQ socket is not connected, cannot send request."
|
|
643
|
-
|
|
644
642
|
timeout = timeout or self.timeout
|
|
645
643
|
try:
|
|
646
644
|
self.logger.debug(f"Sending request: {request}")
|
|
@@ -689,8 +687,6 @@ class AsyncRegistryClient:
|
|
|
689
687
|
The response from the registry as a dictionary.
|
|
690
688
|
"""
|
|
691
689
|
|
|
692
|
-
assert self.hb_socket is not None, "HB socket is not connected, cannot send heartbeat request."
|
|
693
|
-
|
|
694
690
|
try:
|
|
695
691
|
self.logger.debug(f"Sending heartbeat request: {request}")
|
|
696
692
|
await self.hb_socket.send_string(json.dumps(request))
|
|
@@ -871,8 +867,7 @@ class AsyncRegistryClient:
|
|
|
871
867
|
await self.reregister()
|
|
872
868
|
|
|
873
869
|
else:
|
|
874
|
-
|
|
875
|
-
self.logger.debug(f"Heartbeat succeeded: {response.get('message')}")
|
|
870
|
+
VERBOSE_DEBUG and self.logger.debug(f"Heartbeat succeeded: {response.get('message')}")
|
|
876
871
|
|
|
877
872
|
except Exception as exc:
|
|
878
873
|
self.logger.error(f"Error in heartbeat loop: {exc}", exc_info=True)
|
|
@@ -905,7 +900,6 @@ class AsyncRegistryClient:
|
|
|
905
900
|
try:
|
|
906
901
|
await self._heartbeat_task
|
|
907
902
|
except asyncio.CancelledError:
|
|
908
|
-
self.logger.info("Heartbeat task cancelled")
|
|
909
903
|
pass
|
|
910
904
|
self._tasks.discard(self._heartbeat_task)
|
|
911
905
|
self._heartbeat_task = None
|
|
@@ -1152,8 +1146,8 @@ class AsyncRegistryClient:
|
|
|
1152
1146
|
if hasattr(self, "context") and self.context:
|
|
1153
1147
|
self.logger.info(f"{self.context = !r}")
|
|
1154
1148
|
self.logger.info(f"{self.context._sockets = !r}")
|
|
1155
|
-
|
|
1156
|
-
|
|
1149
|
+
if not self.context.closed:
|
|
1150
|
+
self.context.term()
|
|
1157
1151
|
except Exception as exc:
|
|
1158
1152
|
self.logger.error(f"Error during cleanup: {exc}")
|
|
1159
1153
|
|
|
@@ -218,18 +218,16 @@ class AsyncRegistryServer:
|
|
|
218
218
|
"""Task that handles incoming requests."""
|
|
219
219
|
self.logger.info("Started request handler task")
|
|
220
220
|
|
|
221
|
-
assert self.req_socket is not None, "REQ socket is not connected, cannot handle requests."
|
|
222
|
-
|
|
223
221
|
try:
|
|
224
222
|
message_parts = None
|
|
225
223
|
while self._running:
|
|
226
224
|
try:
|
|
227
225
|
# Wait for a request with timeout to allow checking if still running
|
|
228
226
|
try:
|
|
229
|
-
self.logger.info("Waiting for a request with 1s timeout...")
|
|
227
|
+
# self.logger.info("Waiting for a request with 1s timeout...")
|
|
230
228
|
message_parts = await asyncio.wait_for(self.req_socket.recv_multipart(), timeout=1.0)
|
|
231
229
|
except asyncio.TimeoutError:
|
|
232
|
-
self.logger.debug("waiting for command request...")
|
|
230
|
+
# self.logger.debug("waiting for command request...")
|
|
233
231
|
continue
|
|
234
232
|
|
|
235
233
|
if len(message_parts) >= 3:
|
|
@@ -243,9 +241,6 @@ class AsyncRegistryServer:
|
|
|
243
241
|
response = await self._process_request(message_data)
|
|
244
242
|
|
|
245
243
|
await self._send_response(client_id, message_type, response)
|
|
246
|
-
else:
|
|
247
|
-
self.logger.warning("Request handler: message corrupted, check debug messages.")
|
|
248
|
-
self.logger.debug(f"{message_parts=}")
|
|
249
244
|
|
|
250
245
|
except zmq.ZMQError as exc:
|
|
251
246
|
self.logger.error(f"ZMQ error: {exc}", exc_info=True)
|
|
@@ -402,8 +397,6 @@ class AsyncRegistryServer:
|
|
|
402
397
|
"""Task that handles heartbeat messages."""
|
|
403
398
|
self.logger.info("Started heartbeats handler task")
|
|
404
399
|
|
|
405
|
-
assert self.hb_socket is not None, "HB socket is not connected, cannot handle heartbeat messages."
|
|
406
|
-
|
|
407
400
|
try:
|
|
408
401
|
message_parts = None
|
|
409
402
|
while self._running:
|
|
@@ -432,8 +425,7 @@ class AsyncRegistryServer:
|
|
|
432
425
|
self.logger.warning("Heartbeat request: message corrupted, check debug messages.")
|
|
433
426
|
|
|
434
427
|
except asyncio.TimeoutError:
|
|
435
|
-
|
|
436
|
-
self.logger.debug("waiting for heartbeat...")
|
|
428
|
+
VERBOSE_DEBUG and self.logger.debug("waiting for heartbeat...")
|
|
437
429
|
continue
|
|
438
430
|
|
|
439
431
|
except Exception as exc:
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import json
|
|
5
|
+
import logging
|
|
5
6
|
import time
|
|
6
7
|
from typing import Any
|
|
7
8
|
from typing import Callable
|
|
@@ -9,14 +10,13 @@ from typing import Callable
|
|
|
9
10
|
import zmq
|
|
10
11
|
import zmq.asyncio
|
|
11
12
|
|
|
12
|
-
from egse.log import logging
|
|
13
13
|
from egse.registry import DEFAULT_RS_PUB_PORT
|
|
14
14
|
from egse.registry import DEFAULT_RS_REQ_PORT
|
|
15
15
|
from egse.registry.client import AsyncRegistryClient
|
|
16
16
|
from egse.system import get_host_ip
|
|
17
17
|
from egse.zmq_ser import get_port_number
|
|
18
18
|
|
|
19
|
-
module_module_logger_name = "
|
|
19
|
+
module_module_logger_name = "async_microservice"
|
|
20
20
|
module_logger = logging.getLogger(module_module_logger_name)
|
|
21
21
|
|
|
22
22
|
|
|
@@ -64,7 +64,7 @@ class ZMQMicroservice:
|
|
|
64
64
|
self.registry_sub_endpoint = registry_sub_endpoint or f"tcp://localhost:{DEFAULT_RS_PUB_PORT}"
|
|
65
65
|
self.metadata = metadata or {}
|
|
66
66
|
|
|
67
|
-
self.host_ip = get_host_ip()
|
|
67
|
+
self.host_ip = get_host_ip()
|
|
68
68
|
|
|
69
69
|
# Service ID will be set when registered
|
|
70
70
|
self.service_id = None
|
|
@@ -164,7 +164,6 @@ class ZMQMicroservice:
|
|
|
164
164
|
|
|
165
165
|
if not self.service_id:
|
|
166
166
|
module_logger.error("Failed to register with the service registry")
|
|
167
|
-
await self._cleanup()
|
|
168
167
|
return True
|
|
169
168
|
|
|
170
169
|
module_logger.info(f"Registered with service ID: {self.service_id}")
|
|
@@ -176,17 +175,12 @@ class ZMQMicroservice:
|
|
|
176
175
|
# Start request handler
|
|
177
176
|
request_task = asyncio.create_task(self._handle_requests())
|
|
178
177
|
self._tasks.add(request_task)
|
|
179
|
-
|
|
178
|
+
request_task.add_done_callback(self._tasks.discard)
|
|
180
179
|
|
|
181
180
|
# Wait for shutdown signal
|
|
182
181
|
await self._shutdown.wait()
|
|
183
182
|
|
|
184
|
-
#
|
|
185
|
-
# try:
|
|
186
|
-
# await request_task
|
|
187
|
-
# except asyncio.CancelledError:
|
|
188
|
-
# module_logger.info("Request handler task cancelled during shutdown")
|
|
189
|
-
|
|
183
|
+
# Clean shutdown
|
|
190
184
|
await self._cleanup()
|
|
191
185
|
|
|
192
186
|
return False
|