Complete mysensors sensor coverage (#54471)

This commit is contained in:
Martin Hjelmare 2021-08-11 17:19:02 +02:00 committed by GitHub
parent 1e14b3a0ac
commit 028a3d3e53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 301 additions and 14 deletions

View File

@ -666,7 +666,6 @@ omit =
homeassistant/components/mysensors/helpers.py
homeassistant/components/mysensors/light.py
homeassistant/components/mysensors/notify.py
homeassistant/components/mysensors/sensor.py
homeassistant/components/mysensors/switch.py
homeassistant/components/mystrom/binary_sensor.py
homeassistant/components/mystrom/light.py

View File

@ -3,13 +3,14 @@ from __future__ import annotations
from collections.abc import AsyncGenerator, Generator
import json
from typing import Any
from typing import Any, Callable
from unittest.mock import MagicMock, patch
from mysensors.persistence import MySensorsJSONDecoder
from mysensors.sensor import Sensor
import pytest
from homeassistant.components.device_tracker.legacy import Device
from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN
from homeassistant.components.mysensors import CONF_VERSION, DEFAULT_BAUD_RATE
from homeassistant.components.mysensors.const import (
@ -27,14 +28,14 @@ from tests.common import MockConfigEntry, load_fixture
@pytest.fixture(autouse=True)
def device_tracker_storage(mock_device_tracker_conf):
def device_tracker_storage(mock_device_tracker_conf: list[Device]) -> list[Device]:
"""Mock out device tracker known devices storage."""
devices = mock_device_tracker_conf
return devices
@pytest.fixture(name="mqtt")
def mock_mqtt_fixture(hass) -> None:
def mock_mqtt_fixture(hass: HomeAssistant) -> None:
"""Mock the MQTT integration."""
hass.config.components.add(MQTT_DOMAIN)
@ -75,14 +76,14 @@ def mock_gateway_features(
) -> None:
"""Mock the gateway features."""
async def mock_start_persistence():
async def mock_start_persistence() -> None:
"""Load nodes from via persistence."""
gateway = transport_class.call_args[0][0]
gateway.sensors.update(nodes)
tasks.start_persistence.side_effect = mock_start_persistence
async def mock_start():
async def mock_start() -> None:
"""Mock the start method."""
gateway = transport_class.call_args[0][0]
gateway.on_conn_made(gateway)
@ -97,7 +98,7 @@ def transport_fixture(serial_transport: MagicMock) -> MagicMock:
@pytest.fixture(name="serial_entry")
async def serial_entry_fixture(hass) -> MockConfigEntry:
async def serial_entry_fixture(hass: HomeAssistant) -> MockConfigEntry:
"""Create a config entry for a serial gateway."""
entry = MockConfigEntry(
domain=DOMAIN,
@ -120,15 +121,25 @@ def config_entry_fixture(serial_entry: MockConfigEntry) -> MockConfigEntry:
@pytest.fixture
async def integration(
hass: HomeAssistant, transport: MagicMock, config_entry: MockConfigEntry
) -> AsyncGenerator[MockConfigEntry, None]:
) -> AsyncGenerator[tuple[MockConfigEntry, Callable[[str], None]], None]:
"""Set up the mysensors integration with a config entry."""
device = config_entry.data[CONF_DEVICE]
config: dict[str, Any] = {DOMAIN: {CONF_GATEWAYS: [{CONF_DEVICE: device}]}}
config_entry.add_to_hass(hass)
def receive_message(message_string: str) -> None:
"""Receive a message with the transport.
The message_string parameter is a string in the MySensors message format.
"""
gateway = transport.call_args[0][0]
# node_id;child_id;command;ack;type;payload\n
gateway.logic(message_string)
with patch("homeassistant.components.mysensors.device.UPDATE_DELAY", new=0):
await async_setup_component(hass, DOMAIN, config)
await hass.async_block_till_done()
yield config_entry
yield config_entry, receive_message
def load_nodes_state(fixture_path: str) -> dict:
@ -151,7 +162,7 @@ def gps_sensor_state_fixture() -> dict:
@pytest.fixture
def gps_sensor(gateway_nodes, gps_sensor_state) -> Sensor:
def gps_sensor(gateway_nodes: dict[int, Sensor], gps_sensor_state: dict) -> Sensor:
"""Load the gps sensor."""
nodes = update_gateway_nodes(gateway_nodes, gps_sensor_state)
node = nodes[1]
@ -165,8 +176,70 @@ def power_sensor_state_fixture() -> dict:
@pytest.fixture
def power_sensor(gateway_nodes, power_sensor_state) -> Sensor:
def power_sensor(gateway_nodes: dict[int, Sensor], power_sensor_state: dict) -> Sensor:
"""Load the power sensor."""
nodes = update_gateway_nodes(gateway_nodes, power_sensor_state)
node = nodes[1]
return node
@pytest.fixture(name="energy_sensor_state", scope="session")
def energy_sensor_state_fixture() -> dict:
"""Load the energy sensor state."""
return load_nodes_state("mysensors/energy_sensor_state.json")
@pytest.fixture
def energy_sensor(
gateway_nodes: dict[int, Sensor], energy_sensor_state: dict
) -> Sensor:
"""Load the energy sensor."""
nodes = update_gateway_nodes(gateway_nodes, energy_sensor_state)
node = nodes[1]
return node
@pytest.fixture(name="sound_sensor_state", scope="session")
def sound_sensor_state_fixture() -> dict:
"""Load the sound sensor state."""
return load_nodes_state("mysensors/sound_sensor_state.json")
@pytest.fixture
def sound_sensor(gateway_nodes: dict[int, Sensor], sound_sensor_state: dict) -> Sensor:
"""Load the sound sensor."""
nodes = update_gateway_nodes(gateway_nodes, sound_sensor_state)
node = nodes[1]
return node
@pytest.fixture(name="distance_sensor_state", scope="session")
def distance_sensor_state_fixture() -> dict:
"""Load the distance sensor state."""
return load_nodes_state("mysensors/distance_sensor_state.json")
@pytest.fixture
def distance_sensor(
gateway_nodes: dict[int, Sensor], distance_sensor_state: dict
) -> Sensor:
"""Load the distance sensor."""
nodes = update_gateway_nodes(gateway_nodes, distance_sensor_state)
node = nodes[1]
return node
@pytest.fixture(name="temperature_sensor_state", scope="session")
def temperature_sensor_state_fixture() -> dict:
"""Load the temperature sensor state."""
return load_nodes_state("mysensors/temperature_sensor_state.json")
@pytest.fixture
def temperature_sensor(
gateway_nodes: dict[int, Sensor], temperature_sensor_state: dict
) -> Sensor:
"""Load the temperature sensor."""
nodes = update_gateway_nodes(gateway_nodes, temperature_sensor_state)
node = nodes[1]
return node

View File

@ -1,31 +1,161 @@
"""Provide tests for mysensors sensor platform."""
from __future__ import annotations
from typing import Callable
from homeassistant.components.sensor import ATTR_STATE_CLASS, STATE_CLASS_MEASUREMENT
from mysensors.sensor import Sensor
import pytest
from homeassistant.components.sensor import (
ATTR_LAST_RESET,
ATTR_STATE_CLASS,
STATE_CLASS_MEASUREMENT,
)
from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_ICON,
ATTR_UNIT_OF_MEASUREMENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_TEMPERATURE,
ENERGY_KILO_WATT_HOUR,
POWER_WATT,
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
)
from homeassistant.core import HomeAssistant
from homeassistant.util.dt import utc_from_timestamp
from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM, UnitSystem
from tests.common import MockConfigEntry
async def test_gps_sensor(hass, gps_sensor, integration):
async def test_gps_sensor(
hass: HomeAssistant,
gps_sensor: Sensor,
integration: tuple[MockConfigEntry, Callable[[str], None]],
) -> None:
"""Test a gps sensor."""
entity_id = "sensor.gps_sensor_1_1"
_, receive_message = integration
state = hass.states.get(entity_id)
assert state
assert state.state == "40.741894,-73.989311,12"
altitude = 0
new_coords = "40.782,-73.965"
message_string = f"1;1;1;0;49;{new_coords},{altitude}\n"
async def test_power_sensor(hass, power_sensor, integration):
receive_message(message_string)
# the integration adds multiple jobs to do the update currently
await hass.async_block_till_done()
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state
assert state.state == f"{new_coords},{altitude}"
async def test_power_sensor(
hass: HomeAssistant,
power_sensor: Sensor,
integration: tuple[MockConfigEntry, Callable[[str], None]],
) -> None:
"""Test a power sensor."""
entity_id = "sensor.power_sensor_1_1"
state = hass.states.get(entity_id)
assert state
assert state.state == "1200"
assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_POWER
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == POWER_WATT
assert state.attributes[ATTR_STATE_CLASS] == STATE_CLASS_MEASUREMENT
assert ATTR_LAST_RESET not in state.attributes
async def test_energy_sensor(
hass: HomeAssistant,
energy_sensor: Sensor,
integration: tuple[MockConfigEntry, Callable[[str], None]],
) -> None:
"""Test an energy sensor."""
entity_id = "sensor.energy_sensor_1_1"
state = hass.states.get(entity_id)
assert state
assert state.state == "18000"
assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_ENERGY
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == ENERGY_KILO_WATT_HOUR
assert state.attributes[ATTR_STATE_CLASS] == STATE_CLASS_MEASUREMENT
assert state.attributes[ATTR_LAST_RESET] == utc_from_timestamp(0).isoformat()
async def test_sound_sensor(
hass: HomeAssistant,
sound_sensor: Sensor,
integration: tuple[MockConfigEntry, Callable[[str], None]],
) -> None:
"""Test a sound sensor."""
entity_id = "sensor.sound_sensor_1_1"
state = hass.states.get(entity_id)
assert state
assert state.state == "10"
assert state.attributes[ATTR_ICON] == "mdi:volume-high"
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == "dB"
async def test_distance_sensor(
hass: HomeAssistant,
distance_sensor: Sensor,
integration: tuple[MockConfigEntry, Callable[[str], None]],
) -> None:
"""Test a distance sensor."""
entity_id = "sensor.distance_sensor_1_1"
state = hass.states.get(entity_id)
assert state
assert state.state == "15"
assert state.attributes[ATTR_ICON] == "mdi:ruler"
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == "cm"
@pytest.mark.parametrize(
"unit_system, unit",
[(METRIC_SYSTEM, TEMP_CELSIUS), (IMPERIAL_SYSTEM, TEMP_FAHRENHEIT)],
)
async def test_temperature_sensor(
hass: HomeAssistant,
temperature_sensor: Sensor,
integration: tuple[MockConfigEntry, Callable[[str], None]],
unit_system: UnitSystem,
unit: str,
) -> None:
"""Test a temperature sensor."""
entity_id = "sensor.temperature_sensor_1_1"
hass.config.units = unit_system
_, receive_message = integration
temperature = "22.0"
message_string = f"1;1;1;0;0;{temperature}\n"
receive_message(message_string)
# the integration adds multiple jobs to do the update currently
await hass.async_block_till_done()
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state
assert state.state == temperature
assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_TEMPERATURE
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == unit
assert state.attributes[ATTR_STATE_CLASS] == STATE_CLASS_MEASUREMENT

View File

@ -0,0 +1,22 @@
{
"1": {
"sensor_id": 1,
"children": {
"1": {
"id": 1,
"type": 15,
"description": "",
"values": {
"13": "15",
"43": "cm"
}
}
},
"type": 17,
"sketch_name": "Distance Sensor",
"sketch_version": "1.0",
"battery_level": 0,
"protocol_version": "2.3.2",
"heartbeat": 0
}
}

View File

@ -0,0 +1,21 @@
{
"1": {
"sensor_id": 1,
"children": {
"1": {
"id": 1,
"type": 13,
"description": "",
"values": {
"18": "18000"
}
}
},
"type": 17,
"sketch_name": "Energy Sensor",
"sketch_version": "1.0",
"battery_level": 0,
"protocol_version": "2.3.2",
"heartbeat": 0
}
}

View File

@ -0,0 +1,21 @@
{
"1": {
"sensor_id": 1,
"children": {
"1": {
"id": 1,
"type": 33,
"description": "",
"values": {
"37": "10"
}
}
},
"type": 17,
"sketch_name": "Sound Sensor",
"sketch_version": "1.0",
"battery_level": 0,
"protocol_version": "2.3.2",
"heartbeat": 0
}
}

View File

@ -0,0 +1,21 @@
{
"1": {
"sensor_id": 1,
"children": {
"1": {
"id": 1,
"type": 6,
"description": "",
"values": {
"0": "20.0"
}
}
},
"type": 17,
"sketch_name": "Temperature Sensor",
"sketch_version": "1.0",
"battery_level": 0,
"protocol_version": "2.3.2",
"heartbeat": 0
}
}