diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 84452839ad..3aebca81b8 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -891,11 +891,20 @@ def validate_bytes(value): def hostname(value): value = string(value) + warned_underscore = False if len(value) > 63: raise Invalid("Hostnames can only be 63 characters long") for c in value: - if not (c.isalnum() or c in "_-"): - raise Invalid("Hostname can only have alphanumeric characters and _ or -") + if not (c.isalnum() or c in "-_"): + raise Invalid("Hostname can only have alphanumeric characters and -") + if c in "_" and not warned_underscore: + _LOGGER.warning( + "'%s': Using the '_' (underscore) character in the hostname is discouraged " + "as it can cause problems with some DHCP and local name services. " + "For more information, see https://esphome.io/guides/faq.html#why-shouldn-t-i-use-underscores-in-my-device-name", + value, + ) + warned_underscore = True return value diff --git a/esphome/core/config.py b/esphome/core/config.py index 97748d71d8..45b8809f1e 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -158,7 +158,7 @@ def valid_project_name(value: str): CONFIG_SCHEMA = cv.Schema( { - cv.Required(CONF_NAME): cv.valid_name, + cv.Required(CONF_NAME): cv.hostname, cv.Required(CONF_PLATFORM): cv.one_of("ESP8266", "ESP32", upper=True), cv.Required(CONF_BOARD): validate_board, cv.Optional(CONF_COMMENT): cv.string, diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py index 16cfb16e94..e34c7064fa 100644 --- a/tests/unit_tests/test_config_validation.py +++ b/tests/unit_tests/test_config_validation.py @@ -40,6 +40,28 @@ def test_valid_name__invalid(value): config_validation.valid_name(value) +@pytest.mark.parametrize("value", ("foo", "bar123", "foo-bar")) +def test_hostname__valid(value): + actual = config_validation.hostname(value) + + assert actual == value + + +@pytest.mark.parametrize("value", ("foo bar", "foobar ", "foo#bar")) +def test_hostname__invalid(value): + with pytest.raises(Invalid): + config_validation.hostname(value) + + +def test_hostname__warning(caplog): + actual = config_validation.hostname("foo_bar") + assert actual == "foo_bar" + assert ( + "Using the '_' (underscore) character in the hostname is discouraged" + in caplog.text + ) + + @given(one_of(integers(), text())) def test_string__valid(value): actual = config_validation.string(value)