Bump hatasmota to 0.0.29 (#43013)

This commit is contained in:
Erik Montnemery 2020-11-11 13:51:09 +01:00 committed by GitHub
parent 621a0a5dd2
commit 8cc7069323
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 266 additions and 13 deletions

View file

@ -3,7 +3,7 @@
"name": "Tasmota (beta)", "name": "Tasmota (beta)",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/tasmota", "documentation": "https://www.home-assistant.io/integrations/tasmota",
"requirements": ["hatasmota==0.0.27"], "requirements": ["hatasmota==0.0.29"],
"dependencies": ["mqtt"], "dependencies": ["mqtt"],
"mqtt": ["tasmota/discovery/#"], "mqtt": ["tasmota/discovery/#"],
"codeowners": ["@emontnemery"] "codeowners": ["@emontnemery"]

View file

@ -95,8 +95,7 @@ class TasmotaAvailability(TasmotaEntity):
@callback @callback
def availability_updated(self, available: bool) -> None: def availability_updated(self, available: bool) -> None:
"""Handle updated availability.""" """Handle updated availability."""
if available and not self._available: self._tasmota_entity.poll_status()
self._tasmota_entity.poll_status()
self._available = available self._available = available
self.async_write_ha_state() self.async_write_ha_state()

View file

@ -3,6 +3,19 @@ from typing import Optional
from hatasmota import status_sensor from hatasmota import status_sensor
from hatasmota.const import ( from hatasmota.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER as TASMOTA_CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_BILLION as TASMOTA_CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION as TASMOTA_CONCENTRATION_PARTS_PER_MILLION,
ELECTRICAL_CURRENT_AMPERE as TASMOTA_ELECTRICAL_CURRENT_AMPERE,
ELECTRICAL_VOLT_AMPERE as TASMOTA_ELECTRICAL_VOLT_AMPERE,
ENERGY_KILO_WATT_HOUR as TASMOTA_ENERGY_KILO_WATT_HOUR,
FREQUENCY_HERTZ as TASMOTA_FREQUENCY_HERTZ,
LENGTH_CENTIMETERS as TASMOTA_LENGTH_CENTIMETERS,
LIGHT_LUX as TASMOTA_LIGHT_LUX,
MASS_KILOGRAMS as TASMOTA_MASS_KILOGRAMS,
PERCENTAGE as TASMOTA_PERCENTAGE,
POWER_WATT as TASMOTA_POWER_WATT,
PRESSURE_HPA as TASMOTA_PRESSURE_HPA,
SENSOR_AMBIENT, SENSOR_AMBIENT,
SENSOR_APPARENT_POWERUSAGE, SENSOR_APPARENT_POWERUSAGE,
SENSOR_BATTERY, SENSOR_BATTERY,
@ -35,12 +48,13 @@ from hatasmota.const import (
SENSOR_PROXIMITY, SENSOR_PROXIMITY,
SENSOR_REACTIVE_POWERUSAGE, SENSOR_REACTIVE_POWERUSAGE,
SENSOR_STATUS_IP, SENSOR_STATUS_IP,
SENSOR_STATUS_LAST_RESTART_TIME,
SENSOR_STATUS_LINK_COUNT, SENSOR_STATUS_LINK_COUNT,
SENSOR_STATUS_MQTT_COUNT, SENSOR_STATUS_MQTT_COUNT,
SENSOR_STATUS_RESTART, SENSOR_STATUS_RESTART_REASON,
SENSOR_STATUS_RSSI, SENSOR_STATUS_RSSI,
SENSOR_STATUS_SIGNAL, SENSOR_STATUS_SIGNAL,
SENSOR_STATUS_UPTIME, SENSOR_STATUS_SSID,
SENSOR_TEMPERATURE, SENSOR_TEMPERATURE,
SENSOR_TODAY, SENSOR_TODAY,
SENSOR_TOTAL, SENSOR_TOTAL,
@ -49,10 +63,21 @@ from hatasmota.const import (
SENSOR_VOLTAGE, SENSOR_VOLTAGE,
SENSOR_WEIGHT, SENSOR_WEIGHT,
SENSOR_YESTERDAY, SENSOR_YESTERDAY,
SIGNAL_STRENGTH_DECIBELS as TASMOTA_SIGNAL_STRENGTH_DECIBELS,
SPEED_KILOMETERS_PER_HOUR as TASMOTA_SPEED_KILOMETERS_PER_HOUR,
SPEED_METERS_PER_SECOND as TASMOTA_SPEED_METERS_PER_SECOND,
SPEED_MILES_PER_HOUR as TASMOTA_SPEED_MILES_PER_HOUR,
TEMP_CELSIUS as TASMOTA_TEMP_CELSIUS,
TEMP_FAHRENHEIT as TASMOTA_TEMP_FAHRENHEIT,
TEMP_KELVIN as TASMOTA_TEMP_KELVIN,
VOLT as TASMOTA_VOLT,
) )
from homeassistant.components import sensor from homeassistant.components import sensor
from homeassistant.const import ( from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION,
DEVICE_CLASS_BATTERY, DEVICE_CLASS_BATTERY,
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_ILLUMINANCE,
@ -60,6 +85,25 @@ from homeassistant.const import (
DEVICE_CLASS_PRESSURE, DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_SIGNAL_STRENGTH,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_TIMESTAMP,
ELECTRICAL_CURRENT_AMPERE,
ELECTRICAL_VOLT_AMPERE,
ENERGY_KILO_WATT_HOUR,
FREQUENCY_HERTZ,
LENGTH_CENTIMETERS,
LIGHT_LUX,
MASS_KILOGRAMS,
PERCENTAGE,
POWER_WATT,
PRESSURE_HPA,
SIGNAL_STRENGTH_DECIBELS,
SPEED_KILOMETERS_PER_HOUR,
SPEED_METERS_PER_SECOND,
SPEED_MILES_PER_HOUR,
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
TEMP_KELVIN,
VOLT,
) )
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
@ -108,10 +152,11 @@ SENSOR_DEVICE_CLASS_ICON_MAP = {
SENSOR_PRESSUREATSEALEVEL: {DEVICE_CLASS: DEVICE_CLASS_PRESSURE}, SENSOR_PRESSUREATSEALEVEL: {DEVICE_CLASS: DEVICE_CLASS_PRESSURE},
SENSOR_PROXIMITY: {ICON: "mdi:ruler"}, SENSOR_PROXIMITY: {ICON: "mdi:ruler"},
SENSOR_REACTIVE_POWERUSAGE: {DEVICE_CLASS: DEVICE_CLASS_POWER}, SENSOR_REACTIVE_POWERUSAGE: {DEVICE_CLASS: DEVICE_CLASS_POWER},
SENSOR_STATUS_RESTART: {ICON: "mdi:information-outline"}, SENSOR_STATUS_LAST_RESTART_TIME: {DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP},
SENSOR_STATUS_RESTART_REASON: {ICON: "mdi:information-outline"},
SENSOR_STATUS_SIGNAL: {DEVICE_CLASS: DEVICE_CLASS_SIGNAL_STRENGTH}, SENSOR_STATUS_SIGNAL: {DEVICE_CLASS: DEVICE_CLASS_SIGNAL_STRENGTH},
SENSOR_STATUS_RSSI: {ICON: "mdi:access-point"}, SENSOR_STATUS_RSSI: {ICON: "mdi:access-point"},
SENSOR_STATUS_UPTIME: {ICON: "mdi:progress-clock"}, SENSOR_STATUS_SSID: {ICON: "mdi:access-point-network"},
SENSOR_TEMPERATURE: {DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE}, SENSOR_TEMPERATURE: {DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE},
SENSOR_TODAY: {DEVICE_CLASS: DEVICE_CLASS_POWER}, SENSOR_TODAY: {DEVICE_CLASS: DEVICE_CLASS_POWER},
SENSOR_TOTAL: {DEVICE_CLASS: DEVICE_CLASS_POWER}, SENSOR_TOTAL: {DEVICE_CLASS: DEVICE_CLASS_POWER},
@ -122,6 +167,30 @@ SENSOR_DEVICE_CLASS_ICON_MAP = {
SENSOR_YESTERDAY: {DEVICE_CLASS: DEVICE_CLASS_POWER}, SENSOR_YESTERDAY: {DEVICE_CLASS: DEVICE_CLASS_POWER},
} }
SENSOR_UNIT_MAP = {
TASMOTA_CONCENTRATION_MICROGRAMS_PER_CUBIC_METER: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
TASMOTA_CONCENTRATION_PARTS_PER_BILLION: CONCENTRATION_PARTS_PER_BILLION,
TASMOTA_CONCENTRATION_PARTS_PER_MILLION: CONCENTRATION_PARTS_PER_MILLION,
TASMOTA_ELECTRICAL_CURRENT_AMPERE: ELECTRICAL_CURRENT_AMPERE,
TASMOTA_ELECTRICAL_VOLT_AMPERE: ELECTRICAL_VOLT_AMPERE,
TASMOTA_ENERGY_KILO_WATT_HOUR: ENERGY_KILO_WATT_HOUR,
TASMOTA_FREQUENCY_HERTZ: FREQUENCY_HERTZ,
TASMOTA_LENGTH_CENTIMETERS: LENGTH_CENTIMETERS,
TASMOTA_LIGHT_LUX: LIGHT_LUX,
TASMOTA_MASS_KILOGRAMS: MASS_KILOGRAMS,
TASMOTA_PERCENTAGE: PERCENTAGE,
TASMOTA_POWER_WATT: POWER_WATT,
TASMOTA_PRESSURE_HPA: PRESSURE_HPA,
TASMOTA_SIGNAL_STRENGTH_DECIBELS: SIGNAL_STRENGTH_DECIBELS,
TASMOTA_SPEED_KILOMETERS_PER_HOUR: SPEED_KILOMETERS_PER_HOUR,
TASMOTA_SPEED_METERS_PER_SECOND: SPEED_METERS_PER_SECOND,
TASMOTA_SPEED_MILES_PER_HOUR: SPEED_MILES_PER_HOUR,
TASMOTA_TEMP_CELSIUS: TEMP_CELSIUS,
TASMOTA_TEMP_FAHRENHEIT: TEMP_FAHRENHEIT,
TASMOTA_TEMP_KELVIN: TEMP_KELVIN,
TASMOTA_VOLT: VOLT,
}
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Tasmota sensor dynamically through discovery.""" """Set up Tasmota sensor dynamically through discovery."""
@ -190,9 +259,11 @@ class TasmotaSensor(TasmotaAvailability, TasmotaDiscoveryUpdate, Entity):
@property @property
def state(self): def state(self):
"""Return the state of the entity.""" """Return the state of the entity."""
if self._state and self.device_class == DEVICE_CLASS_TIMESTAMP:
return self._state.isoformat()
return self._state return self._state
@property @property
def unit_of_measurement(self): def unit_of_measurement(self):
"""Return the unit this state is expressed in.""" """Return the unit this state is expressed in."""
return self._tasmota_entity.unit return SENSOR_UNIT_MAP.get(self._tasmota_entity.unit, self._tasmota_entity.unit)

View file

@ -741,7 +741,7 @@ hass-nabucasa==0.37.1
hass_splunk==0.1.1 hass_splunk==0.1.1
# homeassistant.components.tasmota # homeassistant.components.tasmota
hatasmota==0.0.27 hatasmota==0.0.29
# homeassistant.components.jewish_calendar # homeassistant.components.jewish_calendar
hdate==0.9.12 hdate==0.9.12

View file

@ -376,7 +376,7 @@ hangups==0.4.11
hass-nabucasa==0.37.1 hass-nabucasa==0.37.1
# homeassistant.components.tasmota # homeassistant.components.tasmota
hatasmota==0.0.27 hatasmota==0.0.29
# homeassistant.components.jewish_calendar # homeassistant.components.jewish_calendar
hdate==0.9.12 hdate==0.9.12

View file

@ -96,6 +96,54 @@ async def test_controlling_state_via_mqtt(hass, mqtt_mock, setup_tasmota):
assert state.state == STATE_OFF assert state.state == STATE_OFF
async def test_pushon_controlling_state_via_mqtt(hass, mqtt_mock, setup_tasmota):
"""Test state update via MQTT."""
config = copy.deepcopy(DEFAULT_CONFIG)
config["swc"][0] = 13
mac = config["mac"]
async_fire_mqtt_message(
hass,
f"{DEFAULT_PREFIX}/{mac}/config",
json.dumps(config),
)
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.test")
assert state.state == "unavailable"
assert not state.attributes.get(ATTR_ASSUMED_STATE)
async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online")
state = hass.states.get("binary_sensor.test")
assert state.state == STATE_OFF
assert not state.attributes.get(ATTR_ASSUMED_STATE)
# Test normal state update
async_fire_mqtt_message(
hass, "tasmota_49A3BC/stat/RESULT", '{"Switch1":{"Action":"ON"}}'
)
state = hass.states.get("binary_sensor.test")
assert state.state == STATE_ON
async_fire_mqtt_message(
hass, "tasmota_49A3BC/stat/RESULT", '{"Switch1":{"Action":"OFF"}}'
)
state = hass.states.get("binary_sensor.test")
assert state.state == STATE_OFF
# Test periodic state update is ignored
async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/SENSOR", '{"Switch1":"ON"}')
state = hass.states.get("binary_sensor.test")
assert state.state == STATE_OFF
# Test polled state update is ignored
async_fire_mqtt_message(
hass, "tasmota_49A3BC/stat/STATUS10", '{"StatusSNS":{"Switch1":"ON"}}'
)
state = hass.states.get("binary_sensor.test")
assert state.state == STATE_OFF
async def test_friendly_names(hass, mqtt_mock, setup_tasmota): async def test_friendly_names(hass, mqtt_mock, setup_tasmota):
"""Test state update via MQTT.""" """Test state update via MQTT."""
config = copy.deepcopy(DEFAULT_CONFIG) config = copy.deepcopy(DEFAULT_CONFIG)

View file

@ -1,8 +1,10 @@
"""The tests for the Tasmota sensor platform.""" """The tests for the Tasmota sensor platform."""
import copy import copy
import datetime
from datetime import timedelta from datetime import timedelta
import json import json
import hatasmota
from hatasmota.utils import ( from hatasmota.utils import (
get_topic_stat_status, get_topic_stat_status,
get_topic_tele_sensor, get_topic_tele_sensor,
@ -29,7 +31,7 @@ from .test_common import (
help_test_entity_id_update_subscriptions, help_test_entity_id_update_subscriptions,
) )
from tests.async_mock import patch from tests.async_mock import Mock, patch
from tests.common import async_fire_mqtt_message, async_fire_time_changed from tests.common import async_fire_mqtt_message, async_fire_time_changed
DEFAULT_SENSOR_CONFIG = { DEFAULT_SENSOR_CONFIG = {
@ -259,6 +261,7 @@ async def test_status_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota):
async_fire_mqtt_message( async_fire_mqtt_message(
hass, "tasmota_49A3BC/tele/STATE", '{"Wifi":{"Signal":20.5}}' hass, "tasmota_49A3BC/tele/STATE", '{"Wifi":{"Signal":20.5}}'
) )
await hass.async_block_till_done()
state = hass.states.get("sensor.tasmota_status") state = hass.states.get("sensor.tasmota_status")
assert state.state == "20.5" assert state.state == "20.5"
@ -268,10 +271,142 @@ async def test_status_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota):
"tasmota_49A3BC/stat/STATUS11", "tasmota_49A3BC/stat/STATUS11",
'{"StatusSTS":{"Wifi":{"Signal":20.0}}}', '{"StatusSTS":{"Wifi":{"Signal":20.0}}}',
) )
await hass.async_block_till_done()
state = hass.states.get("sensor.tasmota_status") state = hass.states.get("sensor.tasmota_status")
assert state.state == "20.0" assert state.state == "20.0"
@pytest.mark.parametrize("status_sensor_disabled", [False])
async def test_single_shot_status_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota):
"""Test state update via MQTT."""
entity_reg = await hass.helpers.entity_registry.async_get_registry()
# Pre-enable the status sensor
entity_reg.async_get_or_create(
sensor.DOMAIN,
"tasmota",
"00000049A3BC_status_sensor_status_sensor_status_restart_reason",
suggested_object_id="tasmota_status",
disabled_by=None,
)
config = copy.deepcopy(DEFAULT_CONFIG)
mac = config["mac"]
async_fire_mqtt_message(
hass,
f"{DEFAULT_PREFIX}/{mac}/config",
json.dumps(config),
)
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get("sensor.tasmota_status")
assert state.state == "unavailable"
assert not state.attributes.get(ATTR_ASSUMED_STATE)
async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online")
state = hass.states.get("sensor.tasmota_status")
assert state.state == STATE_UNKNOWN
assert not state.attributes.get(ATTR_ASSUMED_STATE)
# Test polled state update
async_fire_mqtt_message(
hass,
"tasmota_49A3BC/stat/STATUS1",
'{"StatusPRM":{"RestartReason":"Some reason"}}',
)
await hass.async_block_till_done()
state = hass.states.get("sensor.tasmota_status")
assert state.state == "Some reason"
# Test polled state update is ignored
async_fire_mqtt_message(
hass,
"tasmota_49A3BC/stat/STATUS1",
'{"StatusPRM":{"RestartReason":"Another reason"}}',
)
await hass.async_block_till_done()
state = hass.states.get("sensor.tasmota_status")
assert state.state == "Some reason"
# Device signals online again
async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online")
await hass.async_block_till_done()
state = hass.states.get("sensor.tasmota_status")
assert state.state == "Some reason"
# Test polled state update
async_fire_mqtt_message(
hass,
"tasmota_49A3BC/stat/STATUS1",
'{"StatusPRM":{"RestartReason":"Another reason"}}',
)
await hass.async_block_till_done()
state = hass.states.get("sensor.tasmota_status")
assert state.state == "Another reason"
# Test polled state update is ignored
async_fire_mqtt_message(
hass,
"tasmota_49A3BC/stat/STATUS1",
'{"StatusPRM":{"RestartReason":"Third reason"}}',
)
await hass.async_block_till_done()
state = hass.states.get("sensor.tasmota_status")
assert state.state == "Another reason"
@pytest.mark.parametrize("status_sensor_disabled", [False])
@patch.object(hatasmota.status_sensor, "datetime", Mock(wraps=datetime.datetime))
async def test_restart_time_status_sensor_state_via_mqtt(
hass, mqtt_mock, setup_tasmota
):
"""Test state update via MQTT."""
entity_reg = await hass.helpers.entity_registry.async_get_registry()
# Pre-enable the status sensor
entity_reg.async_get_or_create(
sensor.DOMAIN,
"tasmota",
"00000049A3BC_status_sensor_status_sensor_last_restart_time",
suggested_object_id="tasmota_status",
disabled_by=None,
)
config = copy.deepcopy(DEFAULT_CONFIG)
mac = config["mac"]
async_fire_mqtt_message(
hass,
f"{DEFAULT_PREFIX}/{mac}/config",
json.dumps(config),
)
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get("sensor.tasmota_status")
assert state.state == "unavailable"
assert not state.attributes.get(ATTR_ASSUMED_STATE)
async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online")
state = hass.states.get("sensor.tasmota_status")
assert state.state == STATE_UNKNOWN
assert not state.attributes.get(ATTR_ASSUMED_STATE)
# Test polled state update
utc_now = datetime.datetime(2020, 11, 11, 8, 0, 0, tzinfo=dt.UTC)
hatasmota.status_sensor.datetime.now.return_value = utc_now
async_fire_mqtt_message(
hass,
"tasmota_49A3BC/stat/STATUS11",
'{"StatusSTS":{"UptimeSec":"3600"}}',
)
await hass.async_block_till_done()
state = hass.states.get("sensor.tasmota_status")
assert state.state == "2020-11-11T07:00:00+00:00"
async def test_attributes(hass, mqtt_mock, setup_tasmota): async def test_attributes(hass, mqtt_mock, setup_tasmota):
"""Test correct attributes for sensors.""" """Test correct attributes for sensors."""
config = copy.deepcopy(DEFAULT_CONFIG) config = copy.deepcopy(DEFAULT_CONFIG)
@ -301,7 +436,7 @@ async def test_attributes(hass, mqtt_mock, setup_tasmota):
assert state.attributes.get("device_class") == "temperature" assert state.attributes.get("device_class") == "temperature"
assert state.attributes.get("friendly_name") == "Tasmota DHT11 Temperature" assert state.attributes.get("friendly_name") == "Tasmota DHT11 Temperature"
assert state.attributes.get("icon") is None assert state.attributes.get("icon") is None
assert state.attributes.get("unit_of_measurement") == "C" assert state.attributes.get("unit_of_measurement") == "°C"
state = hass.states.get("sensor.tasmota_beer_CarbonDioxide") state = hass.states.get("sensor.tasmota_beer_CarbonDioxide")
assert state.attributes.get("device_class") is None assert state.attributes.get("device_class") is None
@ -371,7 +506,7 @@ async def test_indexed_sensor_attributes(hass, mqtt_mock, setup_tasmota):
assert state.attributes.get("device_class") == "temperature" assert state.attributes.get("device_class") == "temperature"
assert state.attributes.get("friendly_name") == "Tasmota Dummy1 Temperature 0" assert state.attributes.get("friendly_name") == "Tasmota Dummy1 Temperature 0"
assert state.attributes.get("icon") is None assert state.attributes.get("icon") is None
assert state.attributes.get("unit_of_measurement") == "C" assert state.attributes.get("unit_of_measurement") == "°C"
state = hass.states.get("sensor.tasmota_dummy2_carbondioxide_1") state = hass.states.get("sensor.tasmota_dummy2_carbondioxide_1")
assert state.attributes.get("device_class") is None assert state.attributes.get("device_class") is None