mirror of
https://github.com/esphome/esphome.git
synced 2025-10-30 14:43:51 +00:00
add ble logger
This commit is contained in:
@@ -48,7 +48,13 @@ from esphome.util import (
|
|||||||
get_serial_ports,
|
get_serial_ports,
|
||||||
)
|
)
|
||||||
from esphome.log import color, setup_log, Fore
|
from esphome.log import color, setup_log, Fore
|
||||||
from .zephyr_tools import smpmgr_upload, smpmgr_scan
|
from .zephyr_tools import (
|
||||||
|
logger_connect,
|
||||||
|
smpmgr_upload,
|
||||||
|
is_mac_address,
|
||||||
|
logger_scan,
|
||||||
|
smpmgr_scan,
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -88,33 +94,49 @@ def choose_prompt(options, purpose: str = None):
|
|||||||
def choose_upload_log_host(
|
def choose_upload_log_host(
|
||||||
default, check_default, show_ota, show_mqtt, show_api, purpose: str = None
|
default, check_default, show_ota, show_mqtt, show_api, purpose: str = None
|
||||||
):
|
):
|
||||||
|
try:
|
||||||
|
mcuboot = CORE.config["nrf52"]["bootloader"] == "mcuboot"
|
||||||
|
except KeyError:
|
||||||
|
mcuboot = False
|
||||||
|
try:
|
||||||
|
ble_logger = CORE.config["zephyr_ble_nus"]["log"]
|
||||||
|
except KeyError:
|
||||||
|
ble_logger = False
|
||||||
|
ota = "ota" in CORE.config
|
||||||
options = []
|
options = []
|
||||||
for port in get_serial_ports():
|
if mcuboot and show_ota and ota:
|
||||||
options.append((f"{port.path} ({port.description})", port.path))
|
for port in get_serial_ports():
|
||||||
# if show_ota and CONF_FOTA in CORE.config:
|
options.append(
|
||||||
# options.append(
|
(f"mcumgr {port.path} ({port.description})", f"mcumgr {port.path}")
|
||||||
# (f"mcumgr {port.path} ({port.description})", f"mcumgr {port.path}")
|
)
|
||||||
# )
|
else:
|
||||||
|
for port in get_serial_ports():
|
||||||
|
options.append((f"{port.path} ({port.description})", port.path))
|
||||||
if default == "SERIAL":
|
if default == "SERIAL":
|
||||||
return choose_prompt(options, purpose=purpose)
|
return choose_prompt(options, purpose=purpose)
|
||||||
if default == "PYOCD":
|
if default == "PYOCD":
|
||||||
options = [("pyocd", "PYOCD")]
|
options = [("pyocd", "PYOCD")]
|
||||||
return choose_prompt(options, purpose=purpose)
|
return choose_prompt(options, purpose=purpose)
|
||||||
if CORE.target_platform in (PLATFORM_NRF52):
|
if mcuboot:
|
||||||
if (show_ota and "ota" in CORE.config) and default is None:
|
if show_ota and ota:
|
||||||
ble_devices = asyncio.run(smpmgr_scan())
|
if default:
|
||||||
if len(ble_devices) == 0:
|
|
||||||
_LOGGER.warning("No OTA service found!")
|
|
||||||
for device in ble_devices:
|
|
||||||
options.append(
|
options.append(
|
||||||
(
|
(f"OTA over Bluetooth LE ({default})", f"mcumgr {default}")
|
||||||
f"FOTA over Bluetooth LE({device.address}) {device.name}",
|
|
||||||
f"mcumgr {device.address}",
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return choose_prompt(options, purpose=purpose)
|
return choose_prompt(options, purpose=purpose)
|
||||||
|
else:
|
||||||
|
ble_devices = asyncio.run(smpmgr_scan(CORE.config["esphome"]["name"]))
|
||||||
|
if len(ble_devices) == 0:
|
||||||
|
_LOGGER.warning("No OTA over Bluetooth LE service found!")
|
||||||
|
for device in ble_devices:
|
||||||
|
options.append(
|
||||||
|
(
|
||||||
|
f"OTA over Bluetooth LE({device.address}) {device.name}",
|
||||||
|
f"mcumgr {device.address}",
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config):
|
if (show_ota and ota) or (show_api and "api" in CORE.config):
|
||||||
options.append((f"Over The Air ({CORE.address})", CORE.address))
|
options.append((f"Over The Air ({CORE.address})", CORE.address))
|
||||||
if default == "OTA":
|
if default == "OTA":
|
||||||
return CORE.address
|
return CORE.address
|
||||||
@@ -122,6 +144,12 @@ def choose_upload_log_host(
|
|||||||
options.append((f"MQTT ({CORE.config['mqtt'][CONF_BROKER]})", "MQTT"))
|
options.append((f"MQTT ({CORE.config['mqtt'][CONF_BROKER]})", "MQTT"))
|
||||||
if default == "OTA":
|
if default == "OTA":
|
||||||
return "MQTT"
|
return "MQTT"
|
||||||
|
if "logging" == purpose and ble_logger and default is None:
|
||||||
|
ble_device = asyncio.run(logger_scan(CORE.config["esphome"]["name"]))
|
||||||
|
if ble_device:
|
||||||
|
options.append((f"Bluetooth LE logger ({ble_device})", ble_device.address))
|
||||||
|
else:
|
||||||
|
_LOGGER.warning("No logger over Bluetooth LE service found!")
|
||||||
if default is not None:
|
if default is not None:
|
||||||
return default
|
return default
|
||||||
if check_default is not None and check_default in [opt[1] for opt in options]:
|
if check_default is not None and check_default in [opt[1] for opt in options]:
|
||||||
@@ -134,6 +162,8 @@ def get_port_type(port):
|
|||||||
return "SERIAL"
|
return "SERIAL"
|
||||||
if port == "MQTT":
|
if port == "MQTT":
|
||||||
return "MQTT"
|
return "MQTT"
|
||||||
|
if is_mac_address(port):
|
||||||
|
return "BLE"
|
||||||
return "NETWORK"
|
return "NETWORK"
|
||||||
|
|
||||||
|
|
||||||
@@ -403,6 +433,9 @@ def show_logs(config, args, port):
|
|||||||
config, args.topic, args.username, args.password, args.client_id
|
config, args.topic, args.username, args.password, args.client_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if get_port_type(port) == "BLE":
|
||||||
|
return asyncio.run(logger_connect(port))
|
||||||
|
|
||||||
raise EsphomeError("No remote or local logging method configured (api/mqtt/logger)")
|
raise EsphomeError("No remote or local logging method configured (api/mqtt/logger)")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ void BLENUS::setup() {
|
|||||||
if (logger::global_logger != nullptr && this->expose_log_) {
|
if (logger::global_logger != nullptr && this->expose_log_) {
|
||||||
logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) {
|
logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) {
|
||||||
this->write_array(reinterpret_cast<const uint8_t *>(message), strlen(message));
|
this->write_array(reinterpret_cast<const uint8_t *>(message), strlen(message));
|
||||||
|
const char c = '\n';
|
||||||
|
this->write_array(reinterpret_cast<const uint8_t *>(&c), 1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import logging
|
|||||||
import re
|
import re
|
||||||
from typing import Final
|
from typing import Final
|
||||||
from rich.pretty import pprint
|
from rich.pretty import pprint
|
||||||
from bleak import BleakScanner
|
from bleak import BleakScanner, BleakClient
|
||||||
from bleak.exc import BleakDeviceNotFoundError
|
from bleak.exc import BleakDeviceNotFoundError, BleakDBusError
|
||||||
from smpclient.transport.ble import SMPBLETransport
|
from smpclient.transport.ble import SMPBLETransport
|
||||||
from smpclient.transport.serial import SMPSerialTransport
|
from smpclient.transport.serial import SMPSerialTransport
|
||||||
from smpclient import SMPClient
|
from smpclient import SMPClient
|
||||||
@@ -16,6 +16,8 @@ from smpclient.generics import error, success
|
|||||||
from esphome.espota2 import ProgressBar
|
from esphome.espota2 import ProgressBar
|
||||||
|
|
||||||
SMP_SERVICE_UUID = "8D53DC1D-1DB7-4CD3-868B-8A527460AA84"
|
SMP_SERVICE_UUID = "8D53DC1D-1DB7-4CD3-868B-8A527460AA84"
|
||||||
|
NUS_SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
|
||||||
|
NUS_TX_CHAR_UUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
|
||||||
MAC_ADDRESS_PATTERN: Final = re.compile(
|
MAC_ADDRESS_PATTERN: Final = re.compile(
|
||||||
r"([0-9A-F]{2}[:]){5}[0-9A-F]{2}$", flags=re.IGNORECASE
|
r"([0-9A-F]{2}[:]){5}[0-9A-F]{2}$", flags=re.IGNORECASE
|
||||||
)
|
)
|
||||||
@@ -23,9 +25,42 @@ MAC_ADDRESS_PATTERN: Final = re.compile(
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def smpmgr_scan():
|
def is_mac_address(value):
|
||||||
_LOGGER.info("Scanning bluetooth...")
|
return MAC_ADDRESS_PATTERN.match(value)
|
||||||
devices = await BleakScanner.discover(service_uuids=[SMP_SERVICE_UUID])
|
|
||||||
|
|
||||||
|
async def logger_scan(name):
|
||||||
|
_LOGGER.info(f"Scanning bluetooth for {name}...")
|
||||||
|
device = await BleakScanner.find_device_by_name(name)
|
||||||
|
return device
|
||||||
|
|
||||||
|
|
||||||
|
async def logger_connect(host):
|
||||||
|
disconnected_event = asyncio.Event()
|
||||||
|
|
||||||
|
def handle_disconnect(client):
|
||||||
|
disconnected_event.set()
|
||||||
|
|
||||||
|
def handle_rx(_, data: bytearray):
|
||||||
|
print(data.decode("utf-8"), end="")
|
||||||
|
|
||||||
|
_LOGGER.info(f"Connecting {host}...")
|
||||||
|
async with BleakClient(host, disconnected_callback=handle_disconnect) as client:
|
||||||
|
_LOGGER.info(f"Connected {host}...")
|
||||||
|
try:
|
||||||
|
await client.start_notify(NUS_TX_CHAR_UUID, handle_rx)
|
||||||
|
except BleakDBusError as e:
|
||||||
|
_LOGGER.error(f"Bluetooth LE logger: {e}")
|
||||||
|
disconnected_event.set()
|
||||||
|
await disconnected_event.wait()
|
||||||
|
|
||||||
|
|
||||||
|
async def smpmgr_scan(name):
|
||||||
|
_LOGGER.info(f"Scanning bluetooth for {name}...")
|
||||||
|
devices = []
|
||||||
|
for device in await BleakScanner.discover(service_uuids=[SMP_SERVICE_UUID]):
|
||||||
|
if device.name == name:
|
||||||
|
devices += [device]
|
||||||
return devices
|
return devices
|
||||||
|
|
||||||
|
|
||||||
@@ -53,7 +88,7 @@ async def smpmgr_upload(config, host, firmware):
|
|||||||
if image_tlv_sha256 is None:
|
if image_tlv_sha256 is None:
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
if MAC_ADDRESS_PATTERN.match(host):
|
if is_mac_address(host):
|
||||||
smp_client = SMPClient(SMPBLETransport(), host)
|
smp_client = SMPClient(SMPBLETransport(), host)
|
||||||
else:
|
else:
|
||||||
smp_client = SMPClient(SMPSerialTransport(mtu=256), host)
|
smp_client = SMPClient(SMPSerialTransport(mtu=256), host)
|
||||||
|
|||||||
Reference in New Issue
Block a user