mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 13:27:14 +00:00
[core] Clarify resolve error when a device has no network log/OTA transport
A device with a static IP, mDNS disabled, and no api: component failed logs with "All specified devices ['OTA'] could not be resolved" and a hint to set use_address; the hint is misleading since the static IP already resolves, the real gap is that network logs ride the native API. Name the missing transport instead: api: for logs, an ota: platform for uploads. The generic "could not be resolved" message stays for a genuinely unreachable address.
This commit is contained in:
@@ -268,6 +268,36 @@ def _ota_hostnames_for_default(purpose: Purpose) -> list[str]:
|
|||||||
return _resolve_with_cache(CORE.address, purpose)
|
return _resolve_with_cache(CORE.address, purpose)
|
||||||
|
|
||||||
|
|
||||||
|
def _unresolved_default_error(purpose: Purpose, defaults: list[str]) -> str:
|
||||||
|
"""Build the error when a default device target produced no usable host.
|
||||||
|
|
||||||
|
When the OTA default was requested and the address resolves but the config
|
||||||
|
lacks the transport the purpose needs (``api:`` for logs, an ``ota:``
|
||||||
|
platform for uploads), name that gap instead of the misleading
|
||||||
|
"could not be resolved" / set-use_address hint.
|
||||||
|
"""
|
||||||
|
if "OTA" in defaults and has_resolvable_address():
|
||||||
|
if purpose == Purpose.LOGGING and not has_api():
|
||||||
|
return (
|
||||||
|
"Cannot view logs over the network: no 'api:' component is "
|
||||||
|
"configured. Network log streaming requires the native API; add "
|
||||||
|
"an 'api:' component, enable MQTT logging, or view logs over USB."
|
||||||
|
)
|
||||||
|
if purpose == Purpose.UPLOADING and not has_ota():
|
||||||
|
return (
|
||||||
|
"Cannot upload over the network: no 'ota:' platform is "
|
||||||
|
"configured. Add an 'ota:' platform, or upload over USB."
|
||||||
|
)
|
||||||
|
if CORE.dashboard:
|
||||||
|
hint = "If you know the IP, set 'use_address' in your network config."
|
||||||
|
else:
|
||||||
|
hint = "If you know the IP, try --device <IP>"
|
||||||
|
return (
|
||||||
|
f"All specified devices {defaults} could not be resolved. "
|
||||||
|
f"Is the device connected to the network? {hint}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def choose_upload_log_host(
|
def choose_upload_log_host(
|
||||||
default: list[str] | str | None,
|
default: list[str] | str | None,
|
||||||
check_default: str | None,
|
check_default: str | None,
|
||||||
@@ -317,14 +347,7 @@ def choose_upload_log_host(
|
|||||||
else:
|
else:
|
||||||
resolved.append(device)
|
resolved.append(device)
|
||||||
if not resolved:
|
if not resolved:
|
||||||
if CORE.dashboard:
|
raise EsphomeError(_unresolved_default_error(purpose, defaults))
|
||||||
hint = "If you know the IP, set 'use_address' in your network config."
|
|
||||||
else:
|
|
||||||
hint = "If you know the IP, try --device <IP>"
|
|
||||||
raise EsphomeError(
|
|
||||||
f"All specified devices {defaults} could not be resolved. "
|
|
||||||
f"Is the device connected to the network? {hint}"
|
|
||||||
)
|
|
||||||
return resolved
|
return resolved
|
||||||
|
|
||||||
# No devices specified, show interactive chooser
|
# No devices specified, show interactive chooser
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ from esphome.__main__ import (
|
|||||||
_make_crystal_freq_callback,
|
_make_crystal_freq_callback,
|
||||||
_redact_with_legacy_fallback,
|
_redact_with_legacy_fallback,
|
||||||
_resolve_network_devices,
|
_resolve_network_devices,
|
||||||
|
_unresolved_default_error,
|
||||||
_validate_bootloader_binary,
|
_validate_bootloader_binary,
|
||||||
_validate_partition_table_binary,
|
_validate_partition_table_binary,
|
||||||
choose_upload_log_host,
|
choose_upload_log_host,
|
||||||
@@ -713,9 +714,7 @@ def test_choose_upload_log_host_with_ota_device_with_api_config() -> None:
|
|||||||
"""Test OTA device when API is configured (no upload without OTA in config)."""
|
"""Test OTA device when API is configured (no upload without OTA in config)."""
|
||||||
setup_core(config={CONF_API: {}}, address="192.168.1.100")
|
setup_core(config={CONF_API: {}}, address="192.168.1.100")
|
||||||
|
|
||||||
with pytest.raises(
|
with pytest.raises(EsphomeError, match="no 'ota:' platform is configured"):
|
||||||
EsphomeError, match="All specified devices .* could not be resolved"
|
|
||||||
):
|
|
||||||
choose_upload_log_host(
|
choose_upload_log_host(
|
||||||
default="OTA",
|
default="OTA",
|
||||||
check_default=None,
|
check_default=None,
|
||||||
@@ -735,6 +734,57 @@ def test_choose_upload_log_host_with_ota_device_with_api_config_logging() -> Non
|
|||||||
assert result == ["192.168.1.100"]
|
assert result == ["192.168.1.100"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_choose_upload_log_host_logging_without_api_reports_missing_api() -> None:
|
||||||
|
"""A resolvable device with only ota: fails logs with a missing-api message."""
|
||||||
|
setup_core(
|
||||||
|
config={CONF_OTA: [{CONF_PLATFORM: CONF_ESPHOME}]}, address="192.168.1.100"
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(EsphomeError, match="no 'api:' component is configured"):
|
||||||
|
choose_upload_log_host(
|
||||||
|
default="OTA",
|
||||||
|
check_default=None,
|
||||||
|
purpose=Purpose.LOGGING,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_choose_upload_log_host_logging_no_transport_reports_missing_api() -> None:
|
||||||
|
"""A resolvable device with neither api: nor MQTT logging fails clearly."""
|
||||||
|
setup_core(address="192.168.1.100")
|
||||||
|
|
||||||
|
with pytest.raises(EsphomeError, match="no 'api:' component is configured"):
|
||||||
|
choose_upload_log_host(
|
||||||
|
default="OTA",
|
||||||
|
check_default=None,
|
||||||
|
purpose=Purpose.LOGGING,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_unresolved_default_error_unresolvable_keeps_dashboard_hint() -> None:
|
||||||
|
"""A .local host with mDNS disabled and no cache keeps the dashboard hint."""
|
||||||
|
setup_core(
|
||||||
|
config={CONF_API: {}, CONF_MDNS: {CONF_DISABLED: True}},
|
||||||
|
address="esp32-a1s.local",
|
||||||
|
)
|
||||||
|
CORE.dashboard = True
|
||||||
|
|
||||||
|
msg = _unresolved_default_error(Purpose.LOGGING, ["OTA"])
|
||||||
|
assert "could not be resolved" in msg
|
||||||
|
assert "set 'use_address'" in msg
|
||||||
|
|
||||||
|
|
||||||
|
def test_unresolved_default_error_upload_with_ota_is_generic() -> None:
|
||||||
|
"""With ota: present the upload error stays generic, not transport-specific."""
|
||||||
|
setup_core(
|
||||||
|
config={CONF_OTA: [{CONF_PLATFORM: CONF_ESPHOME}]}, address="192.168.1.100"
|
||||||
|
)
|
||||||
|
CORE.dashboard = False
|
||||||
|
|
||||||
|
msg = _unresolved_default_error(Purpose.UPLOADING, ["OTA"])
|
||||||
|
assert "could not be resolved" in msg
|
||||||
|
assert "try --device <IP>" in msg
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("mock_has_mqtt_logging")
|
@pytest.mark.usefixtures("mock_has_mqtt_logging")
|
||||||
def test_choose_upload_log_host_with_ota_device_fallback_to_mqtt() -> None:
|
def test_choose_upload_log_host_with_ota_device_fallback_to_mqtt() -> None:
|
||||||
"""Test OTA device fallback to MQTT when no OTA/API config."""
|
"""Test OTA device fallback to MQTT when no OTA/API config."""
|
||||||
|
|||||||
Reference in New Issue
Block a user