diff --git a/homeassistant/components/tasmota/const.py b/homeassistant/components/tasmota/const.py
index fe1f325e94c..f92b5ebe807 100644
--- a/homeassistant/components/tasmota/const.py
+++ b/homeassistant/components/tasmota/const.py
@@ -19,6 +19,7 @@ PLATFORMS = [
Platform.LIGHT,
Platform.SENSOR,
Platform.SWITCH,
+ Platform.UPDATE,
]
TASMOTA_EVENT = "tasmota_event"
diff --git a/homeassistant/components/tasmota/coordinator.py b/homeassistant/components/tasmota/coordinator.py
new file mode 100644
index 00000000000..61d90a01ec7
--- /dev/null
+++ b/homeassistant/components/tasmota/coordinator.py
@@ -0,0 +1,38 @@
+"""Data update coordinators for Tasmota."""
+
+from datetime import timedelta
+import logging
+
+from aiogithubapi import GitHubAPI, GitHubRatelimitException, GitHubReleaseModel
+from aiogithubapi.client import GitHubConnectionException
+
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.core import HomeAssistant
+from homeassistant.helpers.aiohttp_client import async_get_clientsession
+from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
+
+
+class TasmotaLatestReleaseUpdateCoordinator(DataUpdateCoordinator[GitHubReleaseModel]):
+ """Data update coordinator for Tasmota latest release info."""
+
+ def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
+ """Initialize the coordinator."""
+ self.client = GitHubAPI(session=async_get_clientsession(hass))
+ super().__init__(
+ hass,
+ logger=logging.getLogger(__name__),
+ config_entry=config_entry,
+ name="Tasmota latest release",
+ update_interval=timedelta(days=1),
+ )
+
+ async def _async_update_data(self) -> GitHubReleaseModel:
+ """Get new data."""
+ try:
+ response = await self.client.repos.releases.latest("arendst/Tasmota")
+ if response.data is None:
+ raise UpdateFailed("No data received")
+ except (GitHubConnectionException, GitHubRatelimitException) as ex:
+ raise UpdateFailed(ex) from ex
+ else:
+ return response.data
diff --git a/homeassistant/components/tasmota/manifest.json b/homeassistant/components/tasmota/manifest.json
index 6c2d7ee271b..cb068c07c44 100644
--- a/homeassistant/components/tasmota/manifest.json
+++ b/homeassistant/components/tasmota/manifest.json
@@ -8,5 +8,5 @@
"iot_class": "local_push",
"loggers": ["hatasmota"],
"mqtt": ["tasmota/discovery/#"],
- "requirements": ["HATasmota==0.10.1"]
+ "requirements": ["HATasmota==0.10.1", "aiogithubapi==26.0.0"]
}
diff --git a/homeassistant/components/tasmota/update.py b/homeassistant/components/tasmota/update.py
new file mode 100644
index 00000000000..6e7284c1f0e
--- /dev/null
+++ b/homeassistant/components/tasmota/update.py
@@ -0,0 +1,79 @@
+"""Update entity for Tasmota."""
+
+import re
+
+from homeassistant.components.update import (
+ UpdateDeviceClass,
+ UpdateEntity,
+ UpdateEntityFeature,
+)
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.core import HomeAssistant
+from homeassistant.helpers import device_registry as dr
+from homeassistant.helpers.device_registry import DeviceEntry
+from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
+
+from .coordinator import TasmotaLatestReleaseUpdateCoordinator
+
+
+async def async_setup_entry(
+ hass: HomeAssistant,
+ config_entry: ConfigEntry,
+ async_add_entities: AddConfigEntryEntitiesCallback,
+) -> None:
+ """Set up Tasmota update entities."""
+ coordinator = TasmotaLatestReleaseUpdateCoordinator(hass, config_entry)
+ await coordinator.async_config_entry_first_refresh()
+
+ device_registry = dr.async_get(hass)
+ devices = device_registry.devices.get_devices_for_config_entry_id(
+ config_entry.entry_id
+ )
+ async_add_entities(TasmotaUpdateEntity(coordinator, device) for device in devices)
+
+
+class TasmotaUpdateEntity(UpdateEntity):
+ """Representation of a Tasmota update entity."""
+
+ _attr_device_class = UpdateDeviceClass.FIRMWARE
+ _attr_name = "Firmware"
+ _attr_title = "Tasmota firmware"
+ _attr_supported_features = UpdateEntityFeature.RELEASE_NOTES
+
+ def __init__(
+ self,
+ coordinator: TasmotaLatestReleaseUpdateCoordinator,
+ device_entry: DeviceEntry,
+ ) -> None:
+ """Initialize the Tasmota update entity."""
+ self.coordinator = coordinator
+ self.device_entry = device_entry
+ self._attr_unique_id = f"{device_entry.id}_update"
+
+ @property
+ def installed_version(self) -> str | None:
+ """Return the installed version."""
+ return self.device_entry.sw_version # type:ignore[union-attr]
+
+ @property
+ def latest_version(self) -> str:
+ """Return the latest version."""
+ return self.coordinator.data.tag_name.removeprefix("v")
+
+ @property
+ def release_url(self) -> str:
+ """Return the release URL."""
+ return self.coordinator.data.html_url
+
+ @property
+ def release_summary(self) -> str:
+ """Return the release summary."""
+ return self.coordinator.data.name
+
+ def release_notes(self) -> str | None:
+ """Return the release notes."""
+ if not self.coordinator.data.body:
+ return None
+ return re.sub(
+ r"^.*?", "", self.coordinator.data.body, flags=re.DOTALL
+ )
diff --git a/requirements_all.txt b/requirements_all.txt
index ee89a230198..16d2b50505d 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -267,6 +267,7 @@ aioftp==0.21.3
aioghost==0.4.0
# homeassistant.components.github
+# homeassistant.components.tasmota
aiogithubapi==26.0.0
# homeassistant.components.guardian
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 96bf949f780..36afdaac6b0 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -255,6 +255,7 @@ aioflo==2021.11.0
aioghost==0.4.0
# homeassistant.components.github
+# homeassistant.components.tasmota
aiogithubapi==26.0.0
# homeassistant.components.guardian
diff --git a/tests/components/tasmota/test_update.py b/tests/components/tasmota/test_update.py
new file mode 100644
index 00000000000..af85a1a0dc1
--- /dev/null
+++ b/tests/components/tasmota/test_update.py
@@ -0,0 +1,65 @@
+"""Tests for the Tasmota update platform."""
+
+import copy
+import json
+
+from aiogithubapi import GitHubReleaseModel
+import pytest
+
+from homeassistant.components.tasmota.const import DEFAULT_PREFIX
+from homeassistant.core import HomeAssistant
+from homeassistant.helpers import device_registry as dr
+
+from .test_common import DEFAULT_CONFIG
+
+from tests.common import async_fire_mqtt_message
+from tests.typing import MqttMockHAClient
+
+
+@pytest.mark.parametrize(
+ ("candidate_version", "update_available"),
+ [
+ ("0.0.0", False),
+ (".".join(str(int(x) + 1) for x in DEFAULT_CONFIG["sw"].split(".")), True),
+ ],
+)
+async def test_update_state(
+ hass: HomeAssistant,
+ mqtt_mock: MqttMockHAClient,
+ device_registry: dr.DeviceRegistry,
+ setup_tasmota,
+ candidate_version: str,
+ update_available: bool,
+) -> None:
+ """Test setting up a device."""
+ 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()
+
+ device_entry = device_registry.async_get_device(
+ connections={(dr.CONNECTION_NETWORK_MAC, mac)}
+ )
+
+ # TODO mock coordinator.client.repos.releases.latest("arendst/Tasmota") to return this
+ data = GitHubReleaseModel(
+ tag_name=f"v{candidate_version}",
+ name=f"Tasmota v{candidate_version} Foo",
+ html_url=f"https://github.com/arendst/Tasmota/releases/tag/v{candidate_version}",
+ body="""\
+
+
+
+
+
+# RELEASE NOTES
+
+... """,
+ )
+
+ # TODO update_available test, device_entry.sw_version has the current version