Skip to content

Commit 8a0f12d

Browse files
committed
- improves WebSocket performance for slow servers
- fixes settings issue related to scales configurations (Issue #1847)
1 parent a138fc3 commit 8a0f12d

38 files changed

+21579
-21500
lines changed

src/Info.plist

+2-2
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@
120120
<key>CFBundlePackageType</key>
121121
<string>APPL</string>
122122
<key>CFBundleShortVersionString</key>
123-
<string>3.1.4</string>
123+
<string>3.1.5</string>
124124
<key>CFBundleURLTypes</key>
125125
<array>
126126
<dict>
@@ -131,7 +131,7 @@
131131
</dict>
132132
</array>
133133
<key>CFBundleVersion</key>
134-
<string>Artisan 3.1.4</string>
134+
<string>Artisan 3.1.5</string>
135135
<key>LSApplicationCategoryType</key>
136136
<string>public.app-category.productivity</string>
137137
<key>LSArchitecturePriority</key>

src/artisanlib/kaleido.py

+8
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ async def ws_write(self, websocket: 'ClientConnection', message: str) -> None:
235235
if self._logging:
236236
_log.info('write: %s',message)
237237
await websocket.send(message)
238+
await asyncio.sleep(0.1) # yield control to the event loop
238239

239240
async def ws_handle_writes(self, websocket: 'ClientConnection', queue: 'asyncio.Queue[str]') -> None:
240241
message = await queue.get()
@@ -305,7 +306,14 @@ async def ws_connect(self, mode:str, host:str, port:int, path:str,
305306
done, pending = await asyncio.wait([read_handler, write_handler], return_when=asyncio.FIRST_COMPLETED)
306307

307308
_log.debug('disconnected')
309+
for task in pending:
310+
task.cancel()
311+
for task in done:
312+
exception = task.exception()
313+
if isinstance(exception, Exception):
314+
raise exception
308315
except websockets.ConnectionClosed:
316+
_log.debug('reconnecting')
309317
continue
310318
finally:
311319
for task in pending:

src/artisanlib/main.py

+31-7
Original file line numberDiff line numberDiff line change
@@ -17578,7 +17578,7 @@ def settingsLoad(self, filename:Optional[str] = None, theme:bool = False, machin
1757817578
self.qmc.phasesLCDmode = toInt(settings.value('phasesLCDmode',self.qmc.phasesLCDmode))
1757917579
if settings.contains('step100temp'):
1758017580
try:
17581-
self.qmc.step100temp = int(settings.value('step100temp',self.qmc.step100temp))
17581+
self.qmc.step100temp = toInt(settings.value('step100temp',self.qmc.step100temp))
1758217582
except Exception: # pylint: disable=broad-except
1758317583
self.qmc.step100temp = None
1758417584
# Important - this must come after the code that restores phasesLCDmode
@@ -18643,13 +18643,37 @@ def settingsLoad(self, filename:Optional[str] = None, theme:bool = False, machin
1864318643
#--- BEGIN GROUP Scales
1864418644
# Scales
1864518645
settings.beginGroup('Scales')
18646-
self.scale1_model = settings.value('scale1_model',self.scale1_model)
18647-
self.scale1_name = settings.value('scale1_name',self.scale1_name)
18648-
self.scale1_id = settings.value('scale1_id',self.scale1_id)
18646+
if settings.contains('scale1_model'):
18647+
try:
18648+
self.scale1_model = toInt(settings.value('scale1_model',self.scale1_model))
18649+
except Exception: # pylint: disable=broad-except
18650+
self.scale1_model = None
18651+
if settings.contains('scale1_name'):
18652+
try:
18653+
self.scale1_name = toString(settings.value('scale1_name',self.scale1_name))
18654+
except Exception: # pylint: disable=broad-except
18655+
self.scale1_name = None
18656+
if settings.contains('scale1_id'):
18657+
try:
18658+
self.scale1_id = settings.value('scale1_id',self.scale1_id)
18659+
except Exception: # pylint: disable=broad-except
18660+
self.scale1_id = None
1864918661
self.container1_idx = toInt(settings.value('container1_idx',int(self.container1_idx)))
18650-
self.scale2_model = settings.value('scale2_model',self.scale2_model)
18651-
self.scale2_name = settings.value('scale2_name',self.scale2_name)
18652-
self.scale2_id = settings.value('scale2_id',self.scale2_id)
18662+
if settings.contains('scale2_model'):
18663+
try:
18664+
self.scale2_model = toInt(settings.value('scale2_model',self.scale2_model))
18665+
except Exception: # pylint: disable=broad-except
18666+
self.scale2_model = None
18667+
if settings.contains('scale2_name'):
18668+
try:
18669+
self.scale2_name = toString(settings.value('scale2_name',self.scale2_name))
18670+
except Exception: # pylint: disable=broad-except
18671+
self.scale2_name = None
18672+
if settings.contains('scale2_id'):
18673+
try:
18674+
self.scale2_id = settings.value('scale2_id',self.scale2_id)
18675+
except Exception: # pylint: disable=broad-except
18676+
self.scale2_id = None
1865318677
self.container2_idx = toInt(settings.value('container2_idx',int(self.container2_idx)))
1865418678
settings.endGroup()
1865518679
#--- END GROUP Scales

src/artisanlib/wsport.py

+25-8
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class wsport:
4545

4646
__slots__ = [ 'aw', '_loop', '_thread', '_write_queue', 'default_host', 'host', 'port', 'path', 'machineID', 'lastReadResult', 'channels', 'readings', 'tx',
4747
'channel_requests', 'channel_nodes', 'channel_modes', 'connect_timeout', 'request_timeout', 'compression',
48-
'reconnect_interval', 'ping_interval', 'ping_timeout', 'id_node', 'machine_node',
48+
'reconnect_interval', '_ping_interval', '_ping_timeout', 'id_node', 'machine_node',
4949
'command_node', 'data_node', 'pushMessage_node', 'request_data_command', 'charge_message', 'drop_message', 'addEvent_message', 'event_node',
5050
'DRY_node', 'FCs_node', 'FCe_node', 'SCs_node', 'SCe_node', 'STARTonCHARGE', 'OFFonDROP', 'open_event', 'pending_events',
5151
'ws', 'wst' ]
@@ -79,12 +79,12 @@ def __init__(self, aw:'ApplicationWindow') -> None:
7979
self.channel_modes:List[int] = [0]*self.channels # temp mode is an int here, 0:__,1:C,2:F
8080

8181
# configurable via the UI:
82-
self.connect_timeout:float = 4 # in seconds
83-
self.request_timeout:float = 0.5 # in seconds
84-
self.reconnect_interval:float = 2 # in seconds # not used for now
82+
self.connect_timeout:float = 4 # in seconds (websockets default is 10)
83+
self.request_timeout:float = 0.5 # in seconds
84+
self.reconnect_interval:float = 0.2 # in seconds # not used for now (reconnect delay)
8585
# not configurable via the UI:
86-
self.ping_interval:float = 0 # in seconds; if 0 pings are not send automatically
87-
self.ping_timeout:Optional[float] = None # in seconds
86+
self._ping_interval:Optional[float] = 20 # in seconds; None disables keepalive (default is 20)
87+
self._ping_timeout:Optional[float] = 20 # in seconds; None disables timeouts (default is 20)
8888

8989
# JSON nodes
9090
self.id_node:str = 'id'
@@ -256,6 +256,7 @@ async def producer_handler(self, websocket:'ClientConnection') -> None:
256256
message = await self.producer()
257257
if message is not None:
258258
await websocket.send(message)
259+
await asyncio.sleep(0.1) # yield control to the event loop
259260

260261

261262
# if serial settings are given, host/port are ignore and communication is handled by the given serial port
@@ -273,8 +274,11 @@ async def connect(self) -> None:
273274
hostport = f'{self.host}:{self.port}'
274275
async for websocket in websockets.connect(
275276
f'ws://{hostport}/{self.path}',
276-
compression=('deflate' if self.compression else None),
277-
origin=websockets.Origin(f'http://{socket.gethostname()}'),
277+
open_timeout = self.connect_timeout,
278+
ping_interval = self._ping_interval,
279+
ping_timeout = self._ping_timeout,
280+
compression = ('deflate' if self.compression else None),
281+
origin = websockets.Origin(f'http://{socket.gethostname()}'),
278282
user_agent_header = f'Artisan/{__version__} websockets'):
279283
done: Set[asyncio.Task[Any]] = set()
280284
pending: Set[asyncio.Task[Any]] = set()
@@ -288,15 +292,28 @@ async def connect(self) -> None:
288292
[consumer_task, producer_task],
289293
return_when=asyncio.FIRST_COMPLETED,
290294
)
295+
_log.debug('disconnected')
296+
for task in pending:
297+
task.cancel()
298+
for task in done:
299+
exception = task.exception()
300+
if isinstance(exception, Exception):
301+
raise exception
291302
except websockets.ConnectionClosed:
303+
_log.debug('ConnectionClosed exception')
292304
continue
305+
except Exception as e: # pylint: disable=broad-except
306+
_log.exception(e)
293307
finally:
294308
for task in pending:
295309
task.cancel()
296310
for task in done:
297311
exception = task.exception()
298312
if isinstance(exception, Exception):
299313
raise exception
314+
_log.debug('reconnecting')
315+
self.aw.sendmessageSignal.emit(QApplication.translate('Message', '{} disconnected').format('WebSocket'),True,None)
316+
await asyncio.sleep(0.1)
300317
except asyncio.TimeoutError:
301318
_log.info('connection timeout')
302319
except Exception as e: # pylint: disable=broad-except

src/requirements-dev.txt

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ types-docutils>=0.21.0.20241128
1313
lxml-stubs>=0.5.1
1414
mypy==1.15.0
1515
pyright==1.1.400
16-
ruff>=0.11.7
17-
pylint==3.3.6
16+
ruff>=0.11.8
17+
pylint==3.3.7
1818
pre-commit>=4.2.0
1919
pytest>=8.3.5
2020
pytest-cov==6.1.1

src/requirements.txt

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ python-snap7==2.0.2; python_version >= '3.10'
3636
Phidget22==1.22.20250422
3737
Unidecode==1.3.8
3838
qrcode==7.4.2; python_version < '3.9' # last Python 3.8 release
39-
qrcode==8.1; python_version >= '3.9'
39+
qrcode==8.2; python_version >= '3.9'
4040
requests==2.32.3
4141
requests-file==2.1.0
4242
pyusb==1.2.1; python_version < '3.9' # last Python 3.8 release
@@ -85,7 +85,7 @@ bleak==0.22.3
8585
#
8686
### yoctopuce 1.10.42060 on macOS 10.13
8787
yoctopuce==1.10.42060; sys_platform=='darwin' and platform_release<'20.0' # last version supporting macOS 10.13
88-
yoctopuce==2.1.5971; sys_platform!='darwin' or (sys_platform=='darwin' and platform_release>='20.0')
88+
yoctopuce==2.1.6320; sys_platform!='darwin' or (sys_platform=='darwin' and platform_release>='20.0')
8989
# last 1.x yoctopuce lib: 1.10.57762
9090
# 2.0.59414 is no longer universal2 lacking arm64 support; 2.0.59503 should fix this
9191
##

0 commit comments

Comments
 (0)