diff --git a/esphome/__main__.py b/esphome/__main__.py index 4b0fc2cec7..87abd7f796 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -1046,7 +1046,11 @@ def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int ): from esphome.components.api.client import run_logs - return run_logs(config, network_devices) + return run_logs( + config, + network_devices, + subscribe_states=not getattr(args, "no_states", False), + ) if port_type in (PortType.NETWORK, PortType.MQTT) and has_mqtt_logging(): from esphome import mqtt @@ -1664,6 +1668,11 @@ def parse_args(argv): help="Reset the device before starting serial logs.", default=os.getenv("ESPHOME_SERIAL_LOGGING_RESET"), ) + parser_logs.add_argument( + "--no-states", + action="store_true", + help="Do not show entity state changes in log output.", + ) parser_discover = subparsers.add_parser( "discover", diff --git a/esphome/components/api/client.py b/esphome/components/api/client.py index 0e71ad8fcb..0c6c569c7d 100644 --- a/esphome/components/api/client.py +++ b/esphome/components/api/client.py @@ -32,7 +32,11 @@ if TYPE_CHECKING: _LOGGER = logging.getLogger(__name__) -async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None: +async def async_run_logs( + config: dict[str, Any], + addresses: list[str], + subscribe_states: bool = True, +) -> None: """Run the logs command in the event loop.""" conf = config["api"] name = config["esphome"]["name"] @@ -89,14 +93,20 @@ async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None: config, raw_line, backtrace_state=backtrace_state ) - stop = await async_run(cli, on_log, name=name) + stop = await async_run(cli, on_log, name=name, subscribe_states=subscribe_states) try: await asyncio.Event().wait() finally: await stop() -def run_logs(config: dict[str, Any], addresses: list[str]) -> None: +def run_logs( + config: dict[str, Any], + addresses: list[str], + subscribe_states: bool = True, +) -> None: """Run the logs command.""" with contextlib.suppress(KeyboardInterrupt): - asyncio.run(async_run_logs(config, addresses)) + asyncio.run( + async_run_logs(config, addresses, subscribe_states=subscribe_states) + ) diff --git a/tests/unit_tests/test_main.py b/tests/unit_tests/test_main.py index 5e36c06bb3..115ce38c93 100644 --- a/tests/unit_tests/test_main.py +++ b/tests/unit_tests/test_main.py @@ -1762,7 +1762,34 @@ def test_show_logs_api( assert result == 0 mock_run_logs.assert_called_once_with( - CORE.config, ["192.168.1.100", "192.168.1.101"] + CORE.config, ["192.168.1.100", "192.168.1.101"], subscribe_states=True + ) + + +@patch("esphome.components.api.client.run_logs") +def test_show_logs_api_no_states( + mock_run_logs: Mock, +) -> None: + """Test show_logs with --no-states flag.""" + setup_core( + config={ + "logger": {}, + CONF_API: {}, + CONF_MDNS: {CONF_DISABLED: False}, + }, + platform=PLATFORM_ESP32, + ) + mock_run_logs.return_value = 0 + + args = MockArgs() + args.no_states = True + devices = ["192.168.1.100"] + + result = show_logs(CORE.config, args, devices) + + assert result == 0 + mock_run_logs.assert_called_once_with( + CORE.config, ["192.168.1.100"], subscribe_states=False ) @@ -1788,7 +1815,9 @@ def test_show_logs_api_with_fqdn_mdns_disabled( assert result == 0 # Should use the FQDN directly, not try MQTT lookup - mock_run_logs.assert_called_once_with(CORE.config, ["device.example.com"]) + mock_run_logs.assert_called_once_with( + CORE.config, ["device.example.com"], subscribe_states=True + ) @patch("esphome.components.api.client.run_logs") @@ -1816,7 +1845,9 @@ def test_show_logs_api_with_mqtt_fallback( assert result == 0 mock_mqtt_get_ip.assert_called_once_with(CORE.config, "user", "pass", "client") - mock_run_logs.assert_called_once_with(CORE.config, ["192.168.1.200"]) + mock_run_logs.assert_called_once_with( + CORE.config, ["192.168.1.200"], subscribe_states=True + ) @patch("esphome.mqtt.show_logs") @@ -2746,7 +2777,7 @@ def test_show_logs_api_static_ip_with_mqttip( # Verify run_logs was called with both IPs mock_run_logs.assert_called_once_with( - CORE.config, ["192.168.1.100", "192.168.2.50"] + CORE.config, ["192.168.1.100", "192.168.2.50"], subscribe_states=True ) @@ -2782,7 +2813,9 @@ def test_show_logs_api_multiple_mqttip_resolves_once( # Note: "MQTT" is a different magic string from "MQTTIP", but both trigger MQTT resolution # The _resolve_network_devices helper filters out both after first resolution mock_run_logs.assert_called_once_with( - CORE.config, ["192.168.2.50", "192.168.2.51", "192.168.1.100"] + CORE.config, + ["192.168.2.50", "192.168.2.51", "192.168.1.100"], + subscribe_states=True, ) @@ -2862,7 +2895,9 @@ def test_show_logs_api_mqtt_timeout_fallback( mock_mqtt_get_ip.assert_called_once_with(CORE.config, "user", "pass", "client") # Verify run_logs was called with only the static IP (MQTT failed) - mock_run_logs.assert_called_once_with(CORE.config, ["192.168.1.100"]) + mock_run_logs.assert_called_once_with( + CORE.config, ["192.168.1.100"], subscribe_states=True + ) def test_detect_external_components_no_external(