Complete mysensors sensor coverage (#54471)
parent
1e14b3a0ac
commit
028a3d3e53
|
@ -666,7 +666,6 @@ omit =
|
||||||
homeassistant/components/mysensors/helpers.py
|
homeassistant/components/mysensors/helpers.py
|
||||||
homeassistant/components/mysensors/light.py
|
homeassistant/components/mysensors/light.py
|
||||||
homeassistant/components/mysensors/notify.py
|
homeassistant/components/mysensors/notify.py
|
||||||
homeassistant/components/mysensors/sensor.py
|
|
||||||
homeassistant/components/mysensors/switch.py
|
homeassistant/components/mysensors/switch.py
|
||||||
homeassistant/components/mystrom/binary_sensor.py
|
homeassistant/components/mystrom/binary_sensor.py
|
||||||
homeassistant/components/mystrom/light.py
|
homeassistant/components/mystrom/light.py
|
||||||
|
|
|
@ -3,13 +3,14 @@ from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import AsyncGenerator, Generator
|
from collections.abc import AsyncGenerator, Generator
|
||||||
import json
|
import json
|
||||||
from typing import Any
|
from typing import Any, Callable
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from mysensors.persistence import MySensorsJSONDecoder
|
from mysensors.persistence import MySensorsJSONDecoder
|
||||||
from mysensors.sensor import Sensor
|
from mysensors.sensor import Sensor
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.device_tracker.legacy import Device
|
||||||
from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN
|
from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN
|
||||||
from homeassistant.components.mysensors import CONF_VERSION, DEFAULT_BAUD_RATE
|
from homeassistant.components.mysensors import CONF_VERSION, DEFAULT_BAUD_RATE
|
||||||
from homeassistant.components.mysensors.const import (
|
from homeassistant.components.mysensors.const import (
|
||||||
|
@ -27,14 +28,14 @@ from tests.common import MockConfigEntry, load_fixture
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@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."""
|
"""Mock out device tracker known devices storage."""
|
||||||
devices = mock_device_tracker_conf
|
devices = mock_device_tracker_conf
|
||||||
return devices
|
return devices
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="mqtt")
|
@pytest.fixture(name="mqtt")
|
||||||
def mock_mqtt_fixture(hass) -> None:
|
def mock_mqtt_fixture(hass: HomeAssistant) -> None:
|
||||||
"""Mock the MQTT integration."""
|
"""Mock the MQTT integration."""
|
||||||
hass.config.components.add(MQTT_DOMAIN)
|
hass.config.components.add(MQTT_DOMAIN)
|
||||||
|
|
||||||
|
@ -75,14 +76,14 @@ def mock_gateway_features(
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Mock the gateway features."""
|
"""Mock the gateway features."""
|
||||||
|
|
||||||
async def mock_start_persistence():
|
async def mock_start_persistence() -> None:
|
||||||
"""Load nodes from via persistence."""
|
"""Load nodes from via persistence."""
|
||||||
gateway = transport_class.call_args[0][0]
|
gateway = transport_class.call_args[0][0]
|
||||||
gateway.sensors.update(nodes)
|
gateway.sensors.update(nodes)
|
||||||
|
|
||||||
tasks.start_persistence.side_effect = mock_start_persistence
|
tasks.start_persistence.side_effect = mock_start_persistence
|
||||||
|
|
||||||
async def mock_start():
|
async def mock_start() -> None:
|
||||||
"""Mock the start method."""
|
"""Mock the start method."""
|
||||||
gateway = transport_class.call_args[0][0]
|
gateway = transport_class.call_args[0][0]
|
||||||
gateway.on_conn_made(gateway)
|
gateway.on_conn_made(gateway)
|
||||||
|
@ -97,7 +98,7 @@ def transport_fixture(serial_transport: MagicMock) -> MagicMock:
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="serial_entry")
|
@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."""
|
"""Create a config entry for a serial gateway."""
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
|
@ -120,15 +121,25 @@ def config_entry_fixture(serial_entry: MockConfigEntry) -> MockConfigEntry:
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
async def integration(
|
async def integration(
|
||||||
hass: HomeAssistant, transport: MagicMock, config_entry: MockConfigEntry
|
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."""
|
"""Set up the mysensors integration with a config entry."""
|
||||||
device = config_entry.data[CONF_DEVICE]
|
device = config_entry.data[CONF_DEVICE]
|
||||||
config: dict[str, Any] = {DOMAIN: {CONF_GATEWAYS: [{CONF_DEVICE: device}]}}
|
config: dict[str, Any] = {DOMAIN: {CONF_GATEWAYS: [{CONF_DEVICE: device}]}}
|
||||||
config_entry.add_to_hass(hass)
|
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):
|
with patch("homeassistant.components.mysensors.device.UPDATE_DELAY", new=0):
|
||||||
await async_setup_component(hass, DOMAIN, config)
|
await async_setup_component(hass, DOMAIN, config)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
yield config_entry
|
yield config_entry, receive_message
|
||||||
|
|
||||||
|
|
||||||
def load_nodes_state(fixture_path: str) -> dict:
|
def load_nodes_state(fixture_path: str) -> dict:
|
||||||
|
@ -151,7 +162,7 @@ def gps_sensor_state_fixture() -> dict:
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@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."""
|
"""Load the gps sensor."""
|
||||||
nodes = update_gateway_nodes(gateway_nodes, gps_sensor_state)
|
nodes = update_gateway_nodes(gateway_nodes, gps_sensor_state)
|
||||||
node = nodes[1]
|
node = nodes[1]
|
||||||
|
@ -165,8 +176,70 @@ def power_sensor_state_fixture() -> dict:
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@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."""
|
"""Load the power sensor."""
|
||||||
nodes = update_gateway_nodes(gateway_nodes, power_sensor_state)
|
nodes = update_gateway_nodes(gateway_nodes, power_sensor_state)
|
||||||
node = nodes[1]
|
node = nodes[1]
|
||||||
return node
|
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
|
||||||
|
|
|
@ -1,31 +1,161 @@
|
||||||
"""Provide tests for mysensors sensor platform."""
|
"""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 (
|
from homeassistant.const import (
|
||||||
ATTR_DEVICE_CLASS,
|
ATTR_DEVICE_CLASS,
|
||||||
|
ATTR_ICON,
|
||||||
ATTR_UNIT_OF_MEASUREMENT,
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
|
DEVICE_CLASS_ENERGY,
|
||||||
DEVICE_CLASS_POWER,
|
DEVICE_CLASS_POWER,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
ENERGY_KILO_WATT_HOUR,
|
||||||
POWER_WATT,
|
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."""
|
"""Test a gps sensor."""
|
||||||
entity_id = "sensor.gps_sensor_1_1"
|
entity_id = "sensor.gps_sensor_1_1"
|
||||||
|
_, receive_message = integration
|
||||||
|
|
||||||
state = hass.states.get(entity_id)
|
state = hass.states.get(entity_id)
|
||||||
|
|
||||||
|
assert state
|
||||||
assert state.state == "40.741894,-73.989311,12"
|
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."""
|
"""Test a power sensor."""
|
||||||
entity_id = "sensor.power_sensor_1_1"
|
entity_id = "sensor.power_sensor_1_1"
|
||||||
|
|
||||||
state = hass.states.get(entity_id)
|
state = hass.states.get(entity_id)
|
||||||
|
|
||||||
|
assert state
|
||||||
assert state.state == "1200"
|
assert state.state == "1200"
|
||||||
assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_POWER
|
assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_POWER
|
||||||
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == POWER_WATT
|
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == POWER_WATT
|
||||||
assert state.attributes[ATTR_STATE_CLASS] == STATE_CLASS_MEASUREMENT
|
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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue