benchlab-pytools 3.0.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.
- benchlab/__init__.py +14 -0
- benchlab/__main__.py +21 -0
- benchlab/config/README.md +325 -0
- benchlab/config/__init__.py +9 -0
- benchlab/config/config_client.py +631 -0
- benchlab/config/config_manager.py +495 -0
- benchlab/config/config_tool.py +358 -0
- benchlab/config/diff.py +163 -0
- benchlab/config/examples/complete_config.json +56 -0
- benchlab/config/examples/rgb_lighting.json +24 -0
- benchlab/config/examples/simple_fan.json +32 -0
- benchlab/config/schema.py +138 -0
- benchlab/core/README.md +154 -0
- benchlab/core/__init__.py +78 -0
- benchlab/core/config.py +145 -0
- benchlab/core/datasource.py +1209 -0
- benchlab/core/datasource_manager.py +418 -0
- benchlab/core/device_registry.py +206 -0
- benchlab/core/discovery.py +92 -0
- benchlab/core/infrastructure.py +309 -0
- benchlab/core/process_manager.py +356 -0
- benchlab/core/retry.py +85 -0
- benchlab/core/shared_serial.py +235 -0
- benchlab/core/statistics.py +224 -0
- benchlab/csv_log/README.md +206 -0
- benchlab/csv_log/__init__.py +5 -0
- benchlab/csv_log/csv_logger_enhanced.py +416 -0
- benchlab/csv_log/message_batcher.py +361 -0
- benchlab/csv_log/requirements.txt +3 -0
- benchlab/csv_log/smart_retry.py +466 -0
- benchlab/graph/README.md +117 -0
- benchlab/graph/__init__.py +5 -0
- benchlab/graph/app.py +353 -0
- benchlab/graph/requirements.txt +2 -0
- benchlab/graph/runner.py +42 -0
- benchlab/graph/ui.py +288 -0
- benchlab/hwinfo/README.md +135 -0
- benchlab/hwinfo/__init__.py +5 -0
- benchlab/hwinfo/hwinfo_export.py +386 -0
- benchlab/hwinfo/requirements.txt +1 -0
- benchlab/launcher.py +387 -0
- benchlab/link/.env.EXAMPLE +10 -0
- benchlab/link/README.md +172 -0
- benchlab/link/__init__.py +1 -0
- benchlab/link/link_main.py +479 -0
- benchlab/main.py +462 -0
- benchlab/menu.py +729 -0
- benchlab/menu_classic.py +410 -0
- benchlab/mqtt/README.md +201 -0
- benchlab/mqtt/__init__.py +5 -0
- benchlab/mqtt/mqtt.config_template +2 -0
- benchlab/mqtt/mqtt_publisher.py +540 -0
- benchlab/mqtt/requirements.txt +3 -0
- benchlab/prefs.py +67 -0
- benchlab/restapi/.env.example +17 -0
- benchlab/restapi/__init__.py +5 -0
- benchlab/restapi/favicon.ico +0 -0
- benchlab/restapi/readme.md +202 -0
- benchlab/restapi/requirements.txt +5 -0
- benchlab/restapi/telemetry_api.py +808 -0
- benchlab/sources.py +515 -0
- benchlab/tools.py +147 -0
- benchlab/tui/README.md +266 -0
- benchlab/tui/__init__.py +5 -0
- benchlab/tui/config.py +477 -0
- benchlab/tui/requirements.txt +2 -0
- benchlab/tui/tui_core.py +1298 -0
- benchlab/tui/tui_main.py +418 -0
- benchlab/vu/README.md +64 -0
- benchlab/vu/VU-Server/NOTICE.md +13 -0
- benchlab/vu/VU-Server/README.md +80 -0
- benchlab/vu/VU-Server/Running_from_source_on_linux.md +97 -0
- benchlab/vu/VU-Server/__init__.py +0 -0
- benchlab/vu/VU-Server/assets/vu1_hello_world.png +0 -0
- benchlab/vu/VU-Server/community_applications.md +13 -0
- benchlab/vu/VU-Server/config.yaml +9 -0
- benchlab/vu/VU-Server/database.py +256 -0
- benchlab/vu/VU-Server/dial_driver.py +534 -0
- benchlab/vu/VU-Server/dials/Comms_Hub_Gauge.py +78 -0
- benchlab/vu/VU-Server/dials/Comms_Hub_Server.py +100 -0
- benchlab/vu/VU-Server/dials/__init__.py +0 -0
- benchlab/vu/VU-Server/dials/base_logger.py +100 -0
- benchlab/vu/VU-Server/installer/inc/icon.ico +0 -0
- benchlab/vu/VU-Server/installer/install.nsi +148 -0
- benchlab/vu/VU-Server/make_version.py +40 -0
- benchlab/vu/VU-Server/pylintrc +628 -0
- benchlab/vu/VU-Server/requirements.txt +9 -0
- benchlab/vu/VU-Server/serial_driver.py +244 -0
- benchlab/vu/VU-Server/server.py +670 -0
- benchlab/vu/VU-Server/server_config.py +255 -0
- benchlab/vu/VU-Server/server_dial_handler.py +334 -0
- benchlab/vu/VU-Server/vu_notifications.py +24 -0
- benchlab/vu/VU-Server/www/android-chrome-192x192.png +0 -0
- benchlab/vu/VU-Server/www/android-chrome-512x512.png +0 -0
- benchlab/vu/VU-Server/www/apple-touch-icon.png +0 -0
- benchlab/vu/VU-Server/www/assets/css/demo.css +291 -0
- benchlab/vu/VU-Server/www/assets/css/demo.min.css +9 -0
- benchlab/vu/VU-Server/www/assets/css/demo.rtl.css +276 -0
- benchlab/vu/VU-Server/www/assets/css/demo.rtl.min.css +9 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-flags.css +1084 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-flags.min.css +9 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-flags.rtl.css +1084 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-flags.rtl.min.css +9 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-payments.css +528 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-payments.min.css +9 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-payments.rtl.css +528 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-payments.rtl.min.css +9 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-social.css +10 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-social.min.css +8 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-social.rtl.css +10 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-social.rtl.min.css +8 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-vendors.css +1504 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-vendors.min.css +9 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-vendors.rtl.css +1504 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-vendors.rtl.min.css +9 -0
- benchlab/vu/VU-Server/www/assets/css/tabler.css +25813 -0
- benchlab/vu/VU-Server/www/assets/css/tabler.min.css +13 -0
- benchlab/vu/VU-Server/www/assets/css/tabler.rtl.css +25777 -0
- benchlab/vu/VU-Server/www/assets/css/tabler.rtl.min.css +13 -0
- benchlab/vu/VU-Server/www/assets/css/theme.min.css +261 -0
- benchlab/vu/VU-Server/www/assets/img/header.png +0 -0
- benchlab/vu/VU-Server/www/assets/img/logo.png +0 -0
- benchlab/vu/VU-Server/www/assets/js/dark-theme.js +35 -0
- benchlab/vu/VU-Server/www/assets/js/dark-theme.min.js +9 -0
- benchlab/vu/VU-Server/www/assets/js/demo.js +132 -0
- benchlab/vu/VU-Server/www/assets/js/demo.min.js +9 -0
- benchlab/vu/VU-Server/www/assets/js/jquery.min.js +2 -0
- benchlab/vu/VU-Server/www/assets/js/tabler.esm.js +7482 -0
- benchlab/vu/VU-Server/www/assets/js/tabler.esm.min.js +15 -0
- benchlab/vu/VU-Server/www/assets/js/tabler.js +7543 -0
- benchlab/vu/VU-Server/www/assets/js/tabler.min.js +15 -0
- benchlab/vu/VU-Server/www/assets/js/vu1_api.js +68 -0
- benchlab/vu/VU-Server/www/assets/js/vu1_gui_api_key_settings.js +167 -0
- benchlab/vu/VU-Server/www/assets/js/vu1_gui_api_keys.js +138 -0
- benchlab/vu/VU-Server/www/assets/js/vu1_gui_dial.js +179 -0
- benchlab/vu/VU-Server/www/assets/js/vu1_gui_home.js +60 -0
- benchlab/vu/VU-Server/www/assets/js/vu1_gui_root.js +125 -0
- benchlab/vu/VU-Server/www/favicon-16x16.png +0 -0
- benchlab/vu/VU-Server/www/favicon-32x32.png +0 -0
- benchlab/vu/VU-Server/www/favicon.ico +0 -0
- benchlab/vu/VU-Server/www/favicon.png +0 -0
- benchlab/vu/VU-Server/www/index.html +47 -0
- benchlab/vu/VU-Server/www/views/api_keys.html +151 -0
- benchlab/vu/VU-Server/www/views/dial.html +286 -0
- benchlab/vu/VU-Server/www/views/footer.html +27 -0
- benchlab/vu/VU-Server/www/views/header.html +86 -0
- benchlab/vu/VU-Server/www/views/key_settings.html +132 -0
- benchlab/vu/VU-Server/www/views/start.html +124 -0
- benchlab/vu/__init__.py +5 -0
- benchlab/vu/assets/Barlow-Bold.ttf +0 -0
- benchlab/vu/assets/bl_dial_144x200.png +0 -0
- benchlab/vu/assets/bl_logo_144x200.png +0 -0
- benchlab/vu/devices.py +166 -0
- benchlab/vu/requirements.txt +24 -0
- benchlab/vu/sensors.py +18 -0
- benchlab/vu/vu_dial.config_template +90 -0
- benchlab/vu/vu_logo_gen.py +97 -0
- benchlab/vu/vu_server.config_template +5 -0
- benchlab/vu/vu_server_config.py +40 -0
- benchlab/vu/vu_server_manager.py +192 -0
- benchlab/vu/vu_tui.py +919 -0
- benchlab/vu/vu_updater.py +540 -0
- benchlab/wigidash/README.md +182 -0
- benchlab/wigidash/__init__.py +5 -0
- benchlab/wigidash/assets/Inter-Bold.ttf +0 -0
- benchlab/wigidash/assets/Inter.ttf +0 -0
- benchlab/wigidash/assets/benchlab.png +0 -0
- benchlab/wigidash/benchlab_fleet.py +150 -0
- benchlab/wigidash/benchlab_graph.py +517 -0
- benchlab/wigidash/benchlab_overview.py +426 -0
- benchlab/wigidash/benchlab_telemetry.py +105 -0
- benchlab/wigidash/benchlab_ui.py +190 -0
- benchlab/wigidash/benchlab_utils.py +182 -0
- benchlab/wigidash/requirements.txt +9 -0
- benchlab/wigidash/wigidash_device.py +541 -0
- benchlab/wigidash/wigidash_manager.py +342 -0
- benchlab/wigidash/wigidash_session.py +355 -0
- benchlab/wigidash/wigidash_usb.py +247 -0
- benchlab/wigidash/wigidash_widget.py +88 -0
- benchlab_pytools-3.0.0.dist-info/METADATA +239 -0
- benchlab_pytools-3.0.0.dist-info/RECORD +184 -0
- benchlab_pytools-3.0.0.dist-info/WHEEL +4 -0
- benchlab_pytools-3.0.0.dist-info/entry_points.txt +2 -0
- benchlab_pytools-3.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import time
|
|
3
|
+
from threading import Lock
|
|
4
|
+
import serial as _serial
|
|
5
|
+
import serial.tools.list_ports as _lp
|
|
6
|
+
import serial.tools.list_ports_common as _lpc
|
|
7
|
+
from serial.tools.list_ports import comports
|
|
8
|
+
from dials.base_logger import logger
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SerialHardware(object):
|
|
12
|
+
def __init__( self,
|
|
13
|
+
port_info,
|
|
14
|
+
flush_on_write=True,
|
|
15
|
+
serialPrefix = '',
|
|
16
|
+
serialSuffix = '\r\n',
|
|
17
|
+
timeout=0.2,
|
|
18
|
+
debug_uart=False):
|
|
19
|
+
|
|
20
|
+
self.lock = Lock()
|
|
21
|
+
|
|
22
|
+
self.flush_on_write = flush_on_write
|
|
23
|
+
self.serialPrefix = serialPrefix
|
|
24
|
+
self.serialSuffix = serialSuffix
|
|
25
|
+
self.debug_uart = debug_uart
|
|
26
|
+
|
|
27
|
+
# If port is string, find port by name
|
|
28
|
+
if isinstance(port_info, str):
|
|
29
|
+
self.port_info = self._find_port_by_name(port_info)
|
|
30
|
+
else:
|
|
31
|
+
self.port_info = port_info
|
|
32
|
+
|
|
33
|
+
if not isinstance(self.port_info, _lpc.ListPortInfo):
|
|
34
|
+
raise TypeError("The port_info for {} must be of type {}".format(self.__class__, _lpc.ListPortInfo))
|
|
35
|
+
|
|
36
|
+
self.port = _serial.Serial(
|
|
37
|
+
port=self.port_info.device,
|
|
38
|
+
baudrate=115200,
|
|
39
|
+
bytesize=_serial.EIGHTBITS,
|
|
40
|
+
parity=_serial.PARITY_NONE,
|
|
41
|
+
stopbits=_serial.STOPBITS_ONE,
|
|
42
|
+
timeout=timeout, # seconds
|
|
43
|
+
write_timeout=timeout, # seconds
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if debug_uart:
|
|
48
|
+
logger.info("Debug UART is set. UART RX/TX will be printed to debug log")
|
|
49
|
+
|
|
50
|
+
logger.debug("Serial driver initialized with")
|
|
51
|
+
logger.debug("-- flush_on_write: '{}'".format(self.flush_on_write))
|
|
52
|
+
logger.debug("-- timeout: '{}'".format(timeout))
|
|
53
|
+
|
|
54
|
+
def __enter__(self):
|
|
55
|
+
self.open()
|
|
56
|
+
return self
|
|
57
|
+
|
|
58
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
59
|
+
self.close()
|
|
60
|
+
|
|
61
|
+
def open(self):
|
|
62
|
+
if self.is_open():
|
|
63
|
+
if self.debug_uart:
|
|
64
|
+
logger.debug("Port is already open")
|
|
65
|
+
return
|
|
66
|
+
self.port.open()
|
|
67
|
+
self.port.reset_input_buffer()
|
|
68
|
+
self.port.reset_output_buffer()
|
|
69
|
+
if self.debug_uart:
|
|
70
|
+
logger.debug("Port open and buffers flushed")
|
|
71
|
+
|
|
72
|
+
def close(self):
|
|
73
|
+
if not self.is_open():
|
|
74
|
+
return
|
|
75
|
+
self.port.reset_input_buffer()
|
|
76
|
+
self.port.reset_output_buffer()
|
|
77
|
+
self.port.close()
|
|
78
|
+
|
|
79
|
+
def is_open(self):
|
|
80
|
+
return self.port.is_open
|
|
81
|
+
|
|
82
|
+
def assert_open(self):
|
|
83
|
+
if not self.is_open():
|
|
84
|
+
raise _serial.SerialException("Serial port must be open. port: \"{}\" description \"{}\"".format(self.port_info.name, self.description()))
|
|
85
|
+
|
|
86
|
+
def get_port_info(self):
|
|
87
|
+
return self.port_info
|
|
88
|
+
|
|
89
|
+
def description(self):
|
|
90
|
+
return self.port_info.description
|
|
91
|
+
|
|
92
|
+
def _find_port_by_name(self, port_info):
|
|
93
|
+
availablePorts = comports()
|
|
94
|
+
logger.debug(f"Searching for COM port `{port_info}`")
|
|
95
|
+
for port in availablePorts:
|
|
96
|
+
logger.debug(f"{port.device}")
|
|
97
|
+
logger.debug(f"\tProduct: {port.product}")
|
|
98
|
+
logger.debug(f"\tDesc: {port.description}")
|
|
99
|
+
logger.debug(f"\tSN: {port.serial_number}")
|
|
100
|
+
logger.debug(f"\tVID:{port.vid} PID:{port.pid}")
|
|
101
|
+
logger.debug(f"\tLocation: {port.location}")
|
|
102
|
+
logger.debug(f"\tInterface: {port.interface}")
|
|
103
|
+
if port.device == port_info:
|
|
104
|
+
logger.debug("Using '{}' as GaugeHub COM port".format(port.device))
|
|
105
|
+
return port
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
def _read_until_re_match(self, status_re=None, timeout=2):
|
|
109
|
+
"""
|
|
110
|
+
Read lines from serial until either a line containing status_re is found or timeout
|
|
111
|
+
|
|
112
|
+
@param status_re regex, the status message you're waiting for
|
|
113
|
+
@param timeout time to wait for respone
|
|
114
|
+
@return tuple with True|False success|failure and the all the lines receiverd up
|
|
115
|
+
to the line containing the status_re match. Never returns lines if no match is found
|
|
116
|
+
"""
|
|
117
|
+
rx_lines = []
|
|
118
|
+
|
|
119
|
+
compiled_re = re.compile(status_re, flags=re.IGNORECASE)
|
|
120
|
+
|
|
121
|
+
timeout_timestmap = time.time() + timeout
|
|
122
|
+
while time.time() <= timeout_timestmap:
|
|
123
|
+
# Wait for new line
|
|
124
|
+
line = self.handle_serial_read()
|
|
125
|
+
if line:
|
|
126
|
+
if self.debug_uart:
|
|
127
|
+
logger.debug(f"_read_until_re_match:{line}")
|
|
128
|
+
rx_lines.append(line)
|
|
129
|
+
if compiled_re.match(line):
|
|
130
|
+
return True, rx_lines
|
|
131
|
+
return False, []
|
|
132
|
+
|
|
133
|
+
def wait_for_re_string(self, regexstr=r'', timeout=30, return_all=False):
|
|
134
|
+
status, lines = self._read_until_re_match(status_re=regexstr, timeout=timeout)
|
|
135
|
+
if status:
|
|
136
|
+
if return_all:
|
|
137
|
+
return lines
|
|
138
|
+
return True
|
|
139
|
+
|
|
140
|
+
if return_all:
|
|
141
|
+
return []
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
def read_until_response(self, timeout=5):
|
|
145
|
+
rx_lines = []
|
|
146
|
+
|
|
147
|
+
timeout_timestmap = time.time() + timeout
|
|
148
|
+
while time.time() <= timeout_timestmap:
|
|
149
|
+
# Wait for new line
|
|
150
|
+
line = self.handle_serial_read()
|
|
151
|
+
if line:
|
|
152
|
+
if self.debug_uart:
|
|
153
|
+
logger.debug(line)
|
|
154
|
+
rx_lines.append(line)
|
|
155
|
+
if line.startswith('<'):
|
|
156
|
+
break
|
|
157
|
+
if time.time() > timeout_timestmap:
|
|
158
|
+
logger.error(f"Timeout occured ({time.time()} > {timeout_timestmap})")
|
|
159
|
+
break
|
|
160
|
+
|
|
161
|
+
return rx_lines
|
|
162
|
+
|
|
163
|
+
def handle_serial_send(self, command):
|
|
164
|
+
"""
|
|
165
|
+
Basic implementations of serial write for the serial_transaction.
|
|
166
|
+
Sends the passed string, depending on the object settings, adds
|
|
167
|
+
a newline and/or flushes the in port before sending
|
|
168
|
+
|
|
169
|
+
@param command the command str
|
|
170
|
+
@return True if the command sent successfully
|
|
171
|
+
"""
|
|
172
|
+
self.assert_open()
|
|
173
|
+
command = self.serialPrefix + command + self.serialSuffix
|
|
174
|
+
|
|
175
|
+
if self.flush_on_write and self.port.in_waiting > 0:
|
|
176
|
+
logger.error("Warning: In bytes waiting. Discarded. Port: \"{}\" description \"{}\"".format(self.port_info.name, self.description()))
|
|
177
|
+
|
|
178
|
+
if self.flush_on_write:
|
|
179
|
+
self.port.reset_input_buffer()
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
if self.debug_uart:
|
|
183
|
+
logger.debug(f"Writting: '{command.encode()}'")
|
|
184
|
+
self.port.write(command.encode())
|
|
185
|
+
return True
|
|
186
|
+
except _serial.SerialTimeoutException:
|
|
187
|
+
logger.error("Warning: writing timed out. port: \"{}\" description \"{}\"".format(self.port_info.name, self.description()))
|
|
188
|
+
return False
|
|
189
|
+
|
|
190
|
+
def handle_serial_read(self):
|
|
191
|
+
"""
|
|
192
|
+
Basic read implementation. Reads 1 line and decodes as utf-8
|
|
193
|
+
|
|
194
|
+
@return the response str, None if the read fails or times out
|
|
195
|
+
"""
|
|
196
|
+
try:
|
|
197
|
+
response = self.port.readline()
|
|
198
|
+
try:
|
|
199
|
+
ret = response.decode("utf-8").strip()
|
|
200
|
+
except Exception as e:
|
|
201
|
+
logger.error(e)
|
|
202
|
+
ret = ""
|
|
203
|
+
return ret
|
|
204
|
+
except _serial.SerialTimeoutException:
|
|
205
|
+
logger.error("Warning: reading response timed out. port: \"{}\" description \"{}\"".format(self.port_info.name, self.description()))
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
def serial_transaction(self, payload, ignore_response=False):
|
|
211
|
+
"""
|
|
212
|
+
Wrapper to send a str payload to the serial port and get a response.
|
|
213
|
+
Acquires the lock and asserts that the port is open and then calls the handle_serial_send
|
|
214
|
+
If verify_response is set it reads until the response_re or default status_re is hit and return all lines up to that point or timeout
|
|
215
|
+
Otherwise returns a single line from handle_serial_read
|
|
216
|
+
handle_serial_read and handle_serial_send have default implementations that can be overridden
|
|
217
|
+
|
|
218
|
+
@param payload the string serial payload passed to handle_serial_send
|
|
219
|
+
@ignore_response don't read any response
|
|
220
|
+
@return the result from the handle_serial_read sub payload
|
|
221
|
+
"""
|
|
222
|
+
lines = []
|
|
223
|
+
try:
|
|
224
|
+
self.lock.acquire()
|
|
225
|
+
self.assert_open()
|
|
226
|
+
|
|
227
|
+
if not isinstance(payload, str) and not isinstance(payload, bytes) and not isinstance(payload, bytearray):
|
|
228
|
+
raise TypeError("Serial_transaction expects str/bytes/bytearray")
|
|
229
|
+
|
|
230
|
+
# Check if any messages were received
|
|
231
|
+
while self.port.in_waiting:
|
|
232
|
+
lines.append(self.handle_serial_read())
|
|
233
|
+
|
|
234
|
+
if not self.handle_serial_send(payload):
|
|
235
|
+
raise _serial.SerialException("Failed to send {}".format(payload))
|
|
236
|
+
|
|
237
|
+
if not ignore_response:
|
|
238
|
+
rx_lines = self.read_until_response()
|
|
239
|
+
|
|
240
|
+
lines = rx_lines + lines
|
|
241
|
+
|
|
242
|
+
return lines
|
|
243
|
+
finally:
|
|
244
|
+
self.lock.release()
|