Skip to content

Commit

Permalink
- don't wait for BLE disconnect
Browse files Browse the repository at this point in the history
- adds Acaia BLE connected/disconnected messages
  • Loading branch information
MAKOMO committed Nov 1, 2024
1 parent 949c559 commit 76ad628
Show file tree
Hide file tree
Showing 7 changed files with 36 additions and 22 deletions.
22 changes: 19 additions & 3 deletions src/artisanlib/acaia.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import asyncio
import logging
from enum import IntEnum
from typing import Optional, Union, List, Tuple, Final, TYPE_CHECKING
from typing import Optional, Union, List, Tuple, Final, Callable, TYPE_CHECKING

if TYPE_CHECKING:
from bleak.backends.characteristic import BleakGATTCharacteristic # pylint: disable=unused-import
Expand Down Expand Up @@ -125,11 +125,17 @@ class AcaiaBLE(ClientBLE):

# NOTE: __slots__ are incompatible with multiple inheritance mixings in subclasses (as done below in class Acaia with QObject)
# __slots__ = [ '_read_queue', '_input_stream',
# 'id_sent', 'fast_notifications_sent', 'slow_notifications_sent', 'weight', 'battery', 'firmware', 'unit', 'max_weight' ]
# 'id_sent', 'fast_notifications_sent', 'slow_notifications_sent', 'weight', 'battery', 'firmware', 'unit', 'max_weight'
# '_connected_handler', '_disconnected_handler' ]

def __init__(self) -> None:
def __init__(self, connected_handler:Optional[Callable[[], None]] = None,
disconnected_handler:Optional[Callable[[], None]] = None):
super().__init__()

# handlers
self._connected_handler = connected_handler
self._disconnected_handler = disconnected_handler

# Protocol parser variables
self._read_queue : asyncio.Queue[bytes] = asyncio.Queue(maxsize=200)
self._input_stream = IteratorReader(AsyncIterable(self._read_queue))
Expand Down Expand Up @@ -184,9 +190,13 @@ def on_connect(self) -> None:
_log.debug('connected to Acaia Legacy Scale')
elif connected_service_UUID == self.ACAIA_SERVICE_UUID:
_log.debug('connected to Acaia Scale')
if self._connected_handler is not None:
self._connected_handler()

def on_disconnect(self) -> None:
_log.debug('disconnected')
if self._disconnected_handler is not None:
self._disconnected_handler()


##
Expand Down Expand Up @@ -505,6 +515,11 @@ class Acaia(QObject, AcaiaBLE): # pyright: ignore [reportGeneralTypeIssues] # Ar
battery_changed_signal = pyqtSignal(int) # delivers new batter level in %
disconnected_signal = pyqtSignal() # issued on disconnect

def __init__(self, connected_handler:Optional[Callable[[], None]] = None,
disconnected_handler:Optional[Callable[[], None]] = None):
QObject.__init__(self)
AcaiaBLE.__init__(self, connected_handler = connected_handler, disconnected_handler=disconnected_handler)


def weight_changed(self, new_value:int) -> None:
self.weight_changed_signal.emit(new_value)
Expand All @@ -514,3 +529,4 @@ def battery_changed(self, new_value:int) -> None:

def on_disconnect(self) -> None:
self.disconnected_signal.emit()
AcaiaBLE.on_disconnect(self)
15 changes: 4 additions & 11 deletions src/artisanlib/ble_port.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
# AUTHOR
# Marko Luther, 2024

import time
import asyncio
import logging
from bleak import BleakScanner, BleakClient
Expand Down Expand Up @@ -179,12 +178,8 @@ def scan_and_connect(self,

def disconnect(self, client:'BleakClient') -> bool:
if hasattr(self, '_asyncLoopThread') and self._asyncLoopThread is not None:
fut = asyncio.run_coroutine_threadsafe(client.disconnect(), self._asyncLoopThread.loop)
try:
return fut.result()
except Exception: # pylint: disable=broad-except
#raise fut.exception() from e # type: ignore[misc]
_log.error('exception in disconnect: %s', fut.exception())
# don't wait for completion not to block caller (note: ble device will not be discovered until fully disconnected)
asyncio.run_coroutine_threadsafe(client.disconnect(), self._asyncLoopThread.loop)
return False

def start_notify(self, client:BleakClient, uuid:str, callback: 'Callable[[BleakGATTCharacteristic, bytearray], Union[None, Awaitable[None]]]') -> None:
Expand Down Expand Up @@ -270,9 +265,6 @@ def stop_notifications(self) -> None:
def _disconnect(self) -> None:
if self._ble_client is not None and self._ble_client.is_connected:
ble.disconnect(self._ble_client)
# wait somewhat until disconnected
while self._ble_client is not None and self._ble_client.is_connected:
time.sleep(0.05)

# returns the service UUID connected to or None
def connected(self) -> Optional[str]:
Expand Down Expand Up @@ -398,6 +390,7 @@ def start(self, case_sensitive:bool=True, scan_timeout:float=10, connect_timeout
asyncio.run_coroutine_threadsafe(
self._connect_and_keep_alive(case_sensitive, scan_timeout, connect_timeout),
self._async_loop_thread.loop)
_log.debug('BLE client started')
self.on_start()
except Exception as e: # pylint: disable=broad-except
_log.exception(e)
Expand All @@ -414,8 +407,8 @@ def stop(self) -> None:
self._async_loop_thread = None
self._ble_client = None
self._connected_service_uuid = None
_log.debug('BLE client stopped')
self.on_stop()
_log.error('BLE client stopped')
else:
_log.error('BLE client not running')

Expand Down
2 changes: 1 addition & 1 deletion src/artisanlib/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -12048,7 +12048,7 @@ def OnMonitor(self) -> None:
connected_handler=lambda : self.aw.sendmessageSignal.emit(QApplication.translate('Message', '{} connected').format('Santoker R'),True,None),
disconnected_handler=lambda : self.aw.sendmessageSignal.emit(QApplication.translate('Message', '{} disconnected').format('Santoker R'),True,None))
self.aw.santokerR.setLogging(self.device_logging)
self.aw.santokerR.start()
self.aw.santokerR.start(case_sensitive=False)
elif self.device == 138:
# connect Kaleido
from artisanlib.kaleido import KaleidoPort
Expand Down
4 changes: 3 additions & 1 deletion src/artisanlib/comm.py
Original file line number Diff line number Diff line change
Expand Up @@ -1455,7 +1455,9 @@ def ColorTrackSerial(self) -> Tuple[float,float,float]:
def ColorTrackBT(self) -> Tuple[float,float,float]:
if self.colorTrackBT is None:
from artisanlib.colortrack import ColorTrackBLE
self.colorTrackBT = ColorTrackBLE()
self.colorTrackBT = ColorTrackBLE(
connected_handler=lambda : self.aw.sendmessageSignal.emit(QApplication.translate('Message', '{} connected').format('ColorTrack'),True,None),
disconnected_handler=lambda : self.aw.sendmessageSignal.emit(QApplication.translate('Message', '{} disconnected').format('ColorTrack'),True,None))
if self.colorTrackBT is not None:
self.colorTrackBT.start()
tx = self.aw.qmc.timeclock.elapsedMilli()
Expand Down
3 changes: 2 additions & 1 deletion src/artisanlib/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17679,7 +17679,8 @@ def settingsLoad(self, filename:Optional[str] = None, theme:bool = False, machin
_log.info('machine: %s (%s, %skg, %s)', self.qmc.machinesetup, self.qmc.roastertype_setup, self.qmc.roastersize_setup, ([''] + self.qmc.sourcenames)[self.qmc.roasterheating_setup])
_log.info('device: %s (%s extra devices)', (['Fuji PID']+self.qmc.devices)[self.qmc.device], len(self.qmc.extradevices))
_log.info('serial: %s @%s', self.ser.comport, self.ser.baudrate)
_log.info('MODBUS %s: %s, %s @%s', ['Serial RTU','Serial ASCII','Serial Binary','TCP','UDP'][self.modbus.type], self.modbus.host, self.modbus.comport, self.modbus.baudrate)
_log.info('MODBUS %s: %s, %s %s%s%s@%s', ['Serial RTU','Serial ASCII','Serial Binary','TCP','UDP'][self.modbus.type],
self.modbus.host, self.modbus.comport, self.modbus.bytesize, self.modbus.parity, self.modbus.stopbits, self.modbus.baudrate)
_log.info('S7: %s', self.s7.host)
_log.info('WebSocket: %s', self.ws.host)
except Exception as e: # pylint: disable=broad-except
Expand Down
4 changes: 3 additions & 1 deletion src/artisanlib/roast_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -1370,7 +1370,9 @@ def __init__(self, parent:QWidget, aw:'ApplicationWindow', activeTab:int = 0) ->
# BLE is not well supported under Windows versions before Windows 10
try:
from artisanlib.acaia import Acaia
self.acaia = Acaia()
self.acaia = Acaia(
connected_handler=lambda : self.aw.sendmessageSignal.emit(QApplication.translate('Message', '{} connected').format('Acaia'),True,None),
disconnected_handler=lambda : self.aw.sendmessageSignal.emit(QApplication.translate('Message', '{} disconnected').format('Acaia'),True,None))
self.acaia.weight_changed_signal.connect(self.ble_weight_changed)
self.acaia.battery_changed_signal.connect(self.ble_battery_changed)
self.acaia.disconnected_signal.connect(self.ble_disconnected)
Expand Down
8 changes: 4 additions & 4 deletions src/artisanlib/santoker.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ def create_msg(self, target:bytes, value: int) -> bytes:
return self.HEADER + target + data + crc + self.TAIL

def send_msg(self, target:bytes, value: int) -> None:
if self._connect_using_ble and self._ble_client is not None:
if self._connect_using_ble and hasattr(self, '_ble_client') and self._ble_client is not None:
# send via BLE
# _log.debug("send_msg(%s,%s): %s",target,value,self.create_msg(target, value))
self._ble_client.send(self.create_msg(target, value))
Expand All @@ -330,15 +330,15 @@ def send_msg(self, target:bytes, value: int) -> None:


def start(self, connect_timeout:float=5) -> None:
if self._connect_using_ble and self._ble_client is not None:
if self._connect_using_ble and hasattr(self, '_ble_client') and self._ble_client is not None:
self._ble_client.start(case_sensitive=False)
else:
super().start(connect_timeout)

def stop(self) -> None:
if self._connect_using_ble and self._ble_client is not None:
if self._connect_using_ble and hasattr(self, '_ble_client') and self._ble_client is not None:
self._ble_client.stop()
del self._ble_client
#del self._ble_client # on this level the released object should be automatically collected by the GC
self._ble_client = None
else:
super().stop()
Expand Down

0 comments on commit 76ad628

Please sign in to comment.