diff --git a/homeassistant/components/nextdns/sensor.py b/homeassistant/components/nextdns/sensor.py index 174357864b3..168c0be8cd0 100644 --- a/homeassistant/components/nextdns/sensor.py +++ b/homeassistant/components/nextdns/sensor.py @@ -63,7 +63,7 @@ SENSORS: tuple[NextDnsSensorEntityDescription, ...] = ( icon="mdi:dns", name="DNS queries", native_unit_of_measurement="queries", - state_class=SensorStateClass.MEASUREMENT, + state_class=SensorStateClass.TOTAL, value=lambda data: data.all_queries, ), NextDnsSensorEntityDescription[AnalyticsStatus]( @@ -73,7 +73,7 @@ SENSORS: tuple[NextDnsSensorEntityDescription, ...] = ( icon="mdi:dns", name="DNS queries blocked", native_unit_of_measurement="queries", - state_class=SensorStateClass.MEASUREMENT, + state_class=SensorStateClass.TOTAL, value=lambda data: data.blocked_queries, ), NextDnsSensorEntityDescription[AnalyticsStatus]( @@ -83,7 +83,7 @@ SENSORS: tuple[NextDnsSensorEntityDescription, ...] = ( icon="mdi:dns", name="DNS queries relayed", native_unit_of_measurement="queries", - state_class=SensorStateClass.MEASUREMENT, + state_class=SensorStateClass.TOTAL, value=lambda data: data.relayed_queries, ), NextDnsSensorEntityDescription[AnalyticsStatus]( @@ -104,7 +104,7 @@ SENSORS: tuple[NextDnsSensorEntityDescription, ...] = ( icon="mdi:dns", name="DNS-over-HTTPS queries", native_unit_of_measurement="queries", - state_class=SensorStateClass.MEASUREMENT, + state_class=SensorStateClass.TOTAL, value=lambda data: data.doh_queries, ), NextDnsSensorEntityDescription[AnalyticsProtocols]( @@ -115,7 +115,7 @@ SENSORS: tuple[NextDnsSensorEntityDescription, ...] = ( icon="mdi:dns", name="DNS-over-TLS queries", native_unit_of_measurement="queries", - state_class=SensorStateClass.MEASUREMENT, + state_class=SensorStateClass.TOTAL, value=lambda data: data.dot_queries, ), NextDnsSensorEntityDescription[AnalyticsProtocols]( @@ -126,9 +126,20 @@ SENSORS: tuple[NextDnsSensorEntityDescription, ...] = ( icon="mdi:dns", name="DNS-over-QUIC queries", native_unit_of_measurement="queries", - state_class=SensorStateClass.MEASUREMENT, + state_class=SensorStateClass.TOTAL, value=lambda data: data.doq_queries, ), + NextDnsSensorEntityDescription[AnalyticsProtocols]( + key="tcp_queries", + coordinator_type=ATTR_PROTOCOLS, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + icon="mdi:dns", + name="TCP Queries", + native_unit_of_measurement="queries", + state_class=SensorStateClass.TOTAL, + value=lambda data: data.tcp_queries, + ), NextDnsSensorEntityDescription[AnalyticsProtocols]( key="udp_queries", coordinator_type=ATTR_PROTOCOLS, @@ -137,7 +148,7 @@ SENSORS: tuple[NextDnsSensorEntityDescription, ...] = ( icon="mdi:dns", name="UDP queries", native_unit_of_measurement="queries", - state_class=SensorStateClass.MEASUREMENT, + state_class=SensorStateClass.TOTAL, value=lambda data: data.udp_queries, ), NextDnsSensorEntityDescription[AnalyticsProtocols]( @@ -173,6 +184,17 @@ SENSORS: tuple[NextDnsSensorEntityDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, value=lambda data: data.doq_queries_ratio, ), + NextDnsSensorEntityDescription[AnalyticsProtocols]( + key="tcp_queries_ratio", + coordinator_type=ATTR_PROTOCOLS, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + icon="mdi:dns", + name="TCP Queries Ratio", + native_unit_of_measurement=PERCENTAGE, + state_class=SensorStateClass.MEASUREMENT, + value=lambda data: data.tcp_queries_ratio, + ), NextDnsSensorEntityDescription[AnalyticsProtocols]( key="udp_queries_ratio", coordinator_type=ATTR_PROTOCOLS, @@ -192,7 +214,7 @@ SENSORS: tuple[NextDnsSensorEntityDescription, ...] = ( icon="mdi:lock", name="Encrypted queries", native_unit_of_measurement="queries", - state_class=SensorStateClass.MEASUREMENT, + state_class=SensorStateClass.TOTAL, value=lambda data: data.encrypted_queries, ), NextDnsSensorEntityDescription[AnalyticsEncryption]( @@ -203,7 +225,7 @@ SENSORS: tuple[NextDnsSensorEntityDescription, ...] = ( icon="mdi:lock-open", name="Unencrypted queries", native_unit_of_measurement="queries", - state_class=SensorStateClass.MEASUREMENT, + state_class=SensorStateClass.TOTAL, value=lambda data: data.unencrypted_queries, ), NextDnsSensorEntityDescription[AnalyticsEncryption]( @@ -225,7 +247,7 @@ SENSORS: tuple[NextDnsSensorEntityDescription, ...] = ( icon="mdi:ip", name="IPv4 queries", native_unit_of_measurement="queries", - state_class=SensorStateClass.MEASUREMENT, + state_class=SensorStateClass.TOTAL, value=lambda data: data.ipv4_queries, ), NextDnsSensorEntityDescription[AnalyticsIpVersions]( @@ -236,7 +258,7 @@ SENSORS: tuple[NextDnsSensorEntityDescription, ...] = ( icon="mdi:ip", name="IPv6 queries", native_unit_of_measurement="queries", - state_class=SensorStateClass.MEASUREMENT, + state_class=SensorStateClass.TOTAL, value=lambda data: data.ipv6_queries, ), NextDnsSensorEntityDescription[AnalyticsIpVersions]( @@ -258,7 +280,7 @@ SENSORS: tuple[NextDnsSensorEntityDescription, ...] = ( icon="mdi:lock-check", name="DNSSEC validated queries", native_unit_of_measurement="queries", - state_class=SensorStateClass.MEASUREMENT, + state_class=SensorStateClass.TOTAL, value=lambda data: data.validated_queries, ), NextDnsSensorEntityDescription[AnalyticsDnssec]( @@ -269,7 +291,7 @@ SENSORS: tuple[NextDnsSensorEntityDescription, ...] = ( icon="mdi:lock-alert", name="DNSSEC not validated queries", native_unit_of_measurement="queries", - state_class=SensorStateClass.MEASUREMENT, + state_class=SensorStateClass.TOTAL, value=lambda data: data.not_validated_queries, ), NextDnsSensorEntityDescription[AnalyticsDnssec]( diff --git a/tests/components/nextdns/__init__.py b/tests/components/nextdns/__init__.py index 82c55f56bbb..6eb258ef2c4 100644 --- a/tests/components/nextdns/__init__.py +++ b/tests/components/nextdns/__init__.py @@ -24,7 +24,11 @@ DNSSEC = AnalyticsDnssec(not_validated_queries=25, validated_queries=75) ENCRYPTION = AnalyticsEncryption(encrypted_queries=60, unencrypted_queries=40) IP_VERSIONS = AnalyticsIpVersions(ipv4_queries=90, ipv6_queries=10) PROTOCOLS = AnalyticsProtocols( - doh_queries=20, doq_queries=10, dot_queries=30, udp_queries=40 + doh_queries=20, + doq_queries=10, + dot_queries=30, + tcp_queries=0, + udp_queries=40, ) SETTINGS = Settings( ai_threat_detection=True, diff --git a/tests/components/nextdns/test_sensor.py b/tests/components/nextdns/test_sensor.py index 8a7d13866f3..731f98203ab 100644 --- a/tests/components/nextdns/test_sensor.py +++ b/tests/components/nextdns/test_sensor.py @@ -121,6 +121,20 @@ async def test_sensor(hass): suggested_object_id="fake_profile_ipv6_queries_ratio", disabled_by=None, ) + registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + "xyz12_tcp_queries", + suggested_object_id="fake_profile_tcp_queries", + disabled_by=None, + ) + registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + "xyz12_tcp_queries_ratio", + suggested_object_id="fake_profile_tcp_queries_ratio", + disabled_by=None, + ) registry.async_get_or_create( SENSOR_DOMAIN, DOMAIN, @@ -148,7 +162,7 @@ async def test_sensor(hass): state = hass.states.get("sensor.fake_profile_dns_queries") assert state assert state.state == "100" - assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT + assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.TOTAL assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "queries" entry = registry.async_get("sensor.fake_profile_dns_queries") @@ -158,7 +172,7 @@ async def test_sensor(hass): state = hass.states.get("sensor.fake_profile_dns_queries_blocked") assert state assert state.state == "20" - assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT + assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.TOTAL assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "queries" entry = registry.async_get("sensor.fake_profile_dns_queries_blocked") @@ -178,7 +192,7 @@ async def test_sensor(hass): state = hass.states.get("sensor.fake_profile_dns_queries_relayed") assert state assert state.state == "10" - assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT + assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.TOTAL assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "queries" entry = registry.async_get("sensor.fake_profile_dns_queries_relayed") @@ -188,7 +202,7 @@ async def test_sensor(hass): state = hass.states.get("sensor.fake_profile_dns_over_https_queries") assert state assert state.state == "20" - assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT + assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.TOTAL assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "queries" entry = registry.async_get("sensor.fake_profile_dns_over_https_queries") @@ -208,7 +222,7 @@ async def test_sensor(hass): state = hass.states.get("sensor.fake_profile_dns_over_quic_queries") assert state assert state.state == "10" - assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT + assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.TOTAL assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "queries" entry = registry.async_get("sensor.fake_profile_dns_over_quic_queries") @@ -228,7 +242,7 @@ async def test_sensor(hass): state = hass.states.get("sensor.fake_profile_dns_over_tls_queries") assert state assert state.state == "30" - assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT + assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.TOTAL assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "queries" entry = registry.async_get("sensor.fake_profile_dns_over_tls_queries") @@ -248,7 +262,7 @@ async def test_sensor(hass): state = hass.states.get("sensor.fake_profile_dnssec_not_validated_queries") assert state assert state.state == "25" - assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT + assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.TOTAL assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "queries" entry = registry.async_get("sensor.fake_profile_dnssec_not_validated_queries") @@ -258,7 +272,7 @@ async def test_sensor(hass): state = hass.states.get("sensor.fake_profile_dnssec_validated_queries") assert state assert state.state == "75" - assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT + assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.TOTAL assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "queries" entry = registry.async_get("sensor.fake_profile_dnssec_validated_queries") @@ -278,7 +292,7 @@ async def test_sensor(hass): state = hass.states.get("sensor.fake_profile_encrypted_queries") assert state assert state.state == "60" - assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT + assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.TOTAL assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "queries" entry = registry.async_get("sensor.fake_profile_encrypted_queries") @@ -288,7 +302,7 @@ async def test_sensor(hass): state = hass.states.get("sensor.fake_profile_unencrypted_queries") assert state assert state.state == "40" - assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT + assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.TOTAL assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "queries" entry = registry.async_get("sensor.fake_profile_unencrypted_queries") @@ -308,7 +322,7 @@ async def test_sensor(hass): state = hass.states.get("sensor.fake_profile_ipv4_queries") assert state assert state.state == "90" - assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT + assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.TOTAL assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "queries" entry = registry.async_get("sensor.fake_profile_ipv4_queries") @@ -318,7 +332,7 @@ async def test_sensor(hass): state = hass.states.get("sensor.fake_profile_ipv6_queries") assert state assert state.state == "10" - assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT + assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.TOTAL assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "queries" entry = registry.async_get("sensor.fake_profile_ipv6_queries") @@ -335,10 +349,30 @@ async def test_sensor(hass): assert entry assert entry.unique_id == "xyz12_ipv6_queries_ratio" + state = hass.states.get("sensor.fake_profile_tcp_queries") + assert state + assert state.state == "0" + assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.TOTAL + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "queries" + + entry = registry.async_get("sensor.fake_profile_tcp_queries") + assert entry + assert entry.unique_id == "xyz12_tcp_queries" + + state = hass.states.get("sensor.fake_profile_tcp_queries_ratio") + assert state + assert state.state == "0.0" + assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PERCENTAGE + + entry = registry.async_get("sensor.fake_profile_tcp_queries_ratio") + assert entry + assert entry.unique_id == "xyz12_tcp_queries_ratio" + state = hass.states.get("sensor.fake_profile_udp_queries") assert state assert state.state == "40" - assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT + assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.TOTAL assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "queries" entry = registry.async_get("sensor.fake_profile_udp_queries")