Files
ha-mysmartbike_ble/custom_components/mysmartbike_ble/const.py
T
Rene NulschandClaude Opus 5 7c1a23a8b4 Reconnect right after a dropped link instead of waiting out the poll
Measured on a real bike: after every unexpected disconnect the integration
sat idle for 26.5s before reconnecting, in a suspiciously tight band across
nine consecutive drops.

Two causes, both introduced with the restore work:

`async_set_updated_data` runs on every BLE notification, which reschedules
the coordinator's next poll to now + SCAN_INTERVAL. The last notification
lands just before the link dies, bleak reports the disconnect ~3.5s later,
so the poll that would reconnect is still ~26.5s out.

The advertisement watch was supposed to cover exactly this, but does not:
instrumenting the callback showed one advertisement in seven minutes across
five drop cycles. These bikes stop advertising after an unexpected link
loss, so the watch only helps when the bike is switched on fresh.

The disconnected callback now starts a reconnect itself. A link that did
not survive MIN_LINK_SECONDS_FOR_FAST_RECONNECT is left to the poll so a
bike that cannot hold a connection at all does not spin. Measured after
the change: 0.7s instead of 26.5s.

Also fixes an advertisement storm. Home Assistant invokes bluetooth
callbacks on every advertisement, and this bike advertises every 0.27s, so
while disconnected the callback queued a background task several times a
second - roughly a hundred per reconnect window - all serialising behind
_connect_lock, each running a full establish_connection retry cycle against
a proxy already struggling to hold the link. A claim guard reduces that to
one attempt in flight at a time; the test covers 50 advertisements.

Keeps the advertisement counter as debug output - it is what made the
second cause visible, and it is the first thing to look at when a bike
reconnects slowly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FyabWZzd7HoLpyBwEa5Zzh
2026-08-28 00:24:42 +02:00

61 lines
2.1 KiB
Python

"""Constants for the MySmartBike BLE integration."""
from typing import Final
DOMAIN: Final = "mysmartbike_ble"
# BLE UUIDs
WRITE_UUID: Final = "0000FFE2-0000-1000-8000-00805F9B34FB"
NOTIFY_UUID: Final = "0000FFD1-0000-1000-8000-00805F9B34FB"
# BLE Messages
VIN_REQUEST_MESSAGE: Final = bytearray([0x24, 0x53, 0x24, 0x56, 0x23, 0x40]) # $S$V#@
PROTOCOL_REQUEST_MESSAGE: Final = bytearray([0x24, 0x53, 0x24, 0x50, 0x23, 0x40]) # $S$P#@
CLOSE_MESSAGE: Final = bytearray([0x24, 0x44, 0x24, 0x49, 0x23, 0x40]) # $D$I#@
# Legacy alias
WAKEUP_MESSAGE: Final = VIN_REQUEST_MESSAGE
# Message lengths
BATTERY_MESSAGE_LENGTH: Final = 17
MOTOR_MESSAGE_LENGTH: Final = 18
EBM_MESSAGE_LENGTH: Final = 17
# Connection settings
MAX_CONNECT_ATTEMPTS: Final = 3
BLACKLIST_DURATION: Final = 300 # 5 minutes in seconds
CONNECTION_TIMEOUT: Final = 120 # seconds
SCAN_INTERVAL: Final = 30 # seconds
# Device info
MANUFACTURER: Final = "Mahle"
MODEL: Final = "iWoc BLE"
# Config entry keys
CONF_DEVICE_NAME: Final = "device_name"
CONF_DEVICE_ADDRESS: Final = "device_address"
# Options
CONF_LOG_BLE_MESSAGES: Final = "log_ble_messages"
# Persistence
STORAGE_VERSION: Final = 1
STORAGE_SAVE_DELAY: Final = 60 # seconds; BLE notifications arrive far too often to save eagerly
# Top-level parser state keys that survive a restart. "motor" and "assist" are
# deliberately absent: a restored speed or power reading would look like live
# data from a bike that is actually parked.
RESTORE_STATE_KEYS: Final = ("battery_primary", "battery_secondary", "ebm")
# Fields inside the restored dicts that describe an instantaneous condition and
# are therefore dropped (set to None) when reading the state back.
VOLATILE_FIELDS: Final[dict[str, tuple[str, ...]]] = {
"battery_primary": ("current", "is_charging"),
"battery_secondary": ("current", "is_charging"),
"ebm": ("status", "accel_y", "accel_z"),
}
# A link that survived at least this long is worth reconnecting immediately when
# it drops; anything shorter is left to the regular poll so a bike that cannot
# hold a connection does not spin in a reconnect loop.
MIN_LINK_SECONDS_FOR_FAST_RECONNECT: Final = 5.0