Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion homeassistant/components/tasmota/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ async def _discover_entity(tasmota_entity_config, discovery_hash, platform):
tasmota_entity_config,
)
else:
tasmota_entity = tasmota_get_entity(tasmota_entity_config, tasmota_mqtt)
tasmota_entity = tasmota_get_entity(
tasmota_entity_config, tasmota_mqtt, hass.async_create_task
Comment thread
MartinHjelmare marked this conversation as resolved.
Outdated
)
_LOGGER.debug(
"Adding new entity: %s %s %s",
platform,
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/tasmota/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "Tasmota (beta)",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/tasmota",
"requirements": ["hatasmota==0.0.27"],
"requirements": ["hatasmota==0.0.28"],
"dependencies": ["mqtt"],
"mqtt": ["tasmota/discovery/#"],
"codeowners": ["@emontnemery"]
Expand Down
3 changes: 1 addition & 2 deletions homeassistant/components/tasmota/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,7 @@ async def async_added_to_hass(self) -> None:
@callback
def availability_updated(self, available: bool) -> None:
"""Handle updated availability."""
if available and not self._available:
self._tasmota_entity.poll_status()
self._tasmota_entity.poll_status()
self._available = available
self.async_write_ha_state()

Expand Down
80 changes: 75 additions & 5 deletions homeassistant/components/tasmota/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@

from hatasmota import status_sensor
from hatasmota.const import (

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol at all these renames. You can also do from hatasmota import const as hc

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_APPARENT_POWERUSAGE,
SENSOR_BATTERY,
Expand Down Expand Up @@ -35,12 +48,12 @@
SENSOR_PROXIMITY,
SENSOR_REACTIVE_POWERUSAGE,
SENSOR_STATUS_IP,
SENSOR_STATUS_LAST_RESTART_TIME,
SENSOR_STATUS_LINK_COUNT,
SENSOR_STATUS_MQTT_COUNT,
SENSOR_STATUS_RESTART,
SENSOR_STATUS_RESTART_REASON,
SENSOR_STATUS_RSSI,
SENSOR_STATUS_SIGNAL,
SENSOR_STATUS_UPTIME,
SENSOR_TEMPERATURE,
SENSOR_TODAY,
SENSOR_TOTAL,
Expand All @@ -49,21 +62,52 @@
SENSOR_VOLTAGE,
SENSOR_WEIGHT,
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.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_POWER,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_SIGNAL_STRENGTH,
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.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
import homeassistant.util.dt as dt_util

from .const import DATA_REMOVE_DISCOVER_COMPONENT, DOMAIN as TASMOTA_DOMAIN
from .discovery import TASMOTA_DISCOVERY_ENTITY_NEW
Expand Down Expand Up @@ -108,10 +152,10 @@
SENSOR_PRESSUREATSEALEVEL: {DEVICE_CLASS: DEVICE_CLASS_PRESSURE},
SENSOR_PROXIMITY: {ICON: "mdi:ruler"},
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_RSSI: {ICON: "mdi:access-point"},
SENSOR_STATUS_UPTIME: {ICON: "mdi:progress-clock"},
SENSOR_TEMPERATURE: {DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE},
SENSOR_TODAY: {DEVICE_CLASS: DEVICE_CLASS_POWER},
SENSOR_TOTAL: {DEVICE_CLASS: DEVICE_CLASS_POWER},
Expand All @@ -122,6 +166,30 @@
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):
"""Set up Tasmota sensor dynamically through discovery."""
Expand Down Expand Up @@ -190,9 +258,11 @@ def icon(self):
@property
def state(self):
"""Return the state of the entity."""
if self._state and self.device_class == DEVICE_CLASS_TIMESTAMP:
return dt_util.as_local(self._state)
Comment thread
MartinHjelmare marked this conversation as resolved.
Outdated
return self._state

@property
def unit_of_measurement(self):
"""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)
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -738,7 +738,7 @@ hass-nabucasa==0.37.1
hass_splunk==0.1.1

# homeassistant.components.tasmota
hatasmota==0.0.27
hatasmota==0.0.28

# homeassistant.components.jewish_calendar
hdate==0.9.12
Expand Down
2 changes: 1 addition & 1 deletion requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ hangups==0.4.11
hass-nabucasa==0.37.1

# homeassistant.components.tasmota
hatasmota==0.0.27
hatasmota==0.0.28

# homeassistant.components.jewish_calendar
hdate==0.9.12
Expand Down
48 changes: 48 additions & 0 deletions tests/components/tasmota/test_binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,54 @@ async def test_controlling_state_via_mqtt(hass, mqtt_mock, setup_tasmota):
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):
"""Test state update via MQTT."""
config = copy.deepcopy(DEFAULT_CONFIG)
Expand Down
87 changes: 85 additions & 2 deletions tests/components/tasmota/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ async def test_status_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota):
async_fire_mqtt_message(
hass, "tasmota_49A3BC/tele/STATE", '{"Wifi":{"Signal":20.5}}'
)
await hass.async_block_till_done()
state = hass.states.get("sensor.tasmota_status")
assert state.state == "20.5"

Expand All @@ -268,10 +269,92 @@ async def test_status_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota):
"tasmota_49A3BC/stat/STATUS11",
'{"StatusSTS":{"Wifi":{"Signal":20.0}}}',
)
await hass.async_block_till_done()
state = hass.states.get("sensor.tasmota_status")
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"


async def test_attributes(hass, mqtt_mock, setup_tasmota):
"""Test correct attributes for sensors."""
config = copy.deepcopy(DEFAULT_CONFIG)
Expand Down Expand Up @@ -301,7 +384,7 @@ async def test_attributes(hass, mqtt_mock, setup_tasmota):
assert state.attributes.get("device_class") == "temperature"
assert state.attributes.get("friendly_name") == "Tasmota DHT11 Temperature"
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")
assert state.attributes.get("device_class") is None
Expand Down Expand Up @@ -371,7 +454,7 @@ async def test_indexed_sensor_attributes(hass, mqtt_mock, setup_tasmota):
assert state.attributes.get("device_class") == "temperature"
assert state.attributes.get("friendly_name") == "Tasmota Dummy1 Temperature 0"
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")
assert state.attributes.get("device_class") is None
Expand Down