Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion bmslib/bt.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,17 @@ def _create_client(self, addr_or_device):
if adapter: # hci0, hci1 (BT adapter hardware)
self.logger.info('Using adapter %s to connect to %s (%s)', adapter, self.address, self.name)
kwargs['adapter'] = adapter
return BleakClient(addr_or_device,

# НАШЕ РЕШЕНИЕ: Подменяем стандартный клиент на сокеты ядра Linux (BlueK)
client_class = BleakClient
try:
import bluek
client_class = bluek.BleakClient
self.logger.info('Successfully forced native BlueK L2CAP sockets for %s (%s)', self.address, self.name)
except Exception as e:
self.logger.warning('Fallback to standard bleak. BlueK import failed: %s', e)

return client_class(addr_or_device,
handle_pairing=bool(self._psk),
disconnected_callback=self._on_disconnect,
**kwargs
Expand Down
15 changes: 15 additions & 0 deletions bmslib/models/jikong.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,23 @@ def _notification_handler(self, _sender, data):
# Non-protocol junk on the notify char, e.g. a JK-PB inverter flooding
# 'AT\r\n' on the shared UART (#370). Throttle so a flood cannot roll
# the log over before the real disconnect is captured.
try:
# SMART JUNK BUFFER FLUSH: Slice away AT\r\n flood prefixes, preserve valid protocol starts
if hasattr(self, '_buffer') and isinstance(self._buffer, bytearray) and len(self._buffer) > 0:
while len(self._buffer) > 0 and self._buffer[0] in (0x41, 0x54, 0x0D, 0x0A):
self._buffer.pop(0)

# Защита от переполнения: если буфер всё равно забит неизвестным шумом и раздулся > 512 байт
if len(self._buffer) > 512:
self.logger.error("%s: Buffer overflow protection triggered (>512 bytes). Flushing completely.", self.name)
self._buffer.clear()

except Exception:
pass

now = time.time()
self._junk_count += dropped

if now - self._junk_log_t >= self.JUNK_LOG_PERIOD:
self.logger.warning(
"%s discarded %d junk byte(s) between frames in %.0fs "
Expand Down
3 changes: 3 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ schema:

bt_power_cycle: "bool?"

bt_power_cycle_on_error: "bool?"
bms_cooldown_on_error: "bool?"

# "bleak" = stock BlueZ/D-Bus stack (default); "bumble" = bumble-bleak
# (pure-Python HCI, no BlueZ/D-Bus) which takes exclusive ownership of the
# adapter(s) it uses (brings them down), so they leave the HA Bluetooth pool;
Expand Down
46 changes: 46 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,52 @@ async def fn():
exceptions.append(ex)
if exceptions:
logger.error('%d exceptions occurred fetching BMSs', len(exceptions))

# 1. SOFT MODE: Flush Bleak cache and soft-close active connections
if len(exceptions) > 0 and user_config.get('bms_cooldown_on_error', False):
try:
logger.warning('BMS sampling error detected. Flushing GATT cache and closing connections...')
from bleak import BleakClient
import asyncio

if hasattr(BleakClient, '_gatt_cache'):
BleakClient._gatt_cache.clear()
logger.info('Bleak GATT cache successfully cleared.')

active_bms = locals().get('bms_list') or globals().get('bms_list') or []
for bms in active_bms:
if hasattr(bms, 'close'):
await bms.close()

logger.info('Soft programmatic flush completed.')
await asyncio.sleep(1)
except Exception as soft_err:
logger.error('Failed to execute soft programmatic flush: %s', soft_err)

# 2. HARD MODE: Total hardware restart of the configured hcieX chip via Linux kernel
if len(exceptions) > 0 and user_config.get('bt_power_cycle_on_error', False):
try:
logger.warning('Bluetooth stack freeze detected. Executing hot hardware power cycle...')
import bmslib.bt
import asyncio
import os

bmslib.bt.bt_power(False)
await asyncio.sleep(3)
try:
bmslib.bt.bt_power(True)
except Exception:
pass

# DYNAMIC ADAPTER SELECTION: Extract active interface from user config (e.g., hci0, hci1) to prevent hardcoding
adapter = user_config.get('bluetooth_adapter') or user_config.get('adapter') or 'hci0'
os.system(f"hciconfig {adapter} up >/dev/null 2>&1")

await asyncio.sleep(3)
logger.info('Hot Bluetooth power cycle completed successfully.')
except Exception as hw_err:
logger.error('Failed to execute hardware BT power cycle: %s', hw_err)

raise exceptions[0]

await fetch_loop(fn, period=sample_period, max_errors=max_errors)
Expand Down