Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,7 @@ jobs:
env.HA_SHORT_VERSION }}-
- name: Install additional OS dependencies
if: steps.cache-venv.outputs.cache-hit != 'true'
timeout-minutes: 5
run: |
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get update
Expand Down Expand Up @@ -578,6 +579,7 @@ jobs:
- base
steps:
- name: Install additional OS dependencies
timeout-minutes: 5
run: |
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get update
Expand Down Expand Up @@ -877,6 +879,7 @@ jobs:
name: Split tests for full run
steps:
- name: Install additional OS dependencies
timeout-minutes: 5
run: |
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get update
Expand Down Expand Up @@ -937,6 +940,7 @@ jobs:
Run tests Python ${{ matrix.python-version }} (${{ matrix.group }})
steps:
- name: Install additional OS dependencies
timeout-minutes: 5
run: |
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get update
Expand Down Expand Up @@ -1070,6 +1074,7 @@ jobs:
Run ${{ matrix.mariadb-group }} tests Python ${{ matrix.python-version }}
steps:
- name: Install additional OS dependencies
timeout-minutes: 5
run: |
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get update
Expand Down Expand Up @@ -1210,6 +1215,7 @@ jobs:
Run ${{ matrix.postgresql-group }} tests Python ${{ matrix.python-version }}
steps:
- name: Install additional OS dependencies
timeout-minutes: 5
run: |
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get update
Expand Down Expand Up @@ -1371,6 +1377,7 @@ jobs:
Run tests Python ${{ matrix.python-version }} (${{ matrix.group }})
steps:
- name: Install additional OS dependencies
timeout-minutes: 5
run: |
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get update
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/fan/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
"toggle": "[%key:common::device_automation::action_type::toggle%]",
"turn_on": "[%key:common::device_automation::action_type::turn_on%]",
"turn_off": "[%key:common::device_automation::action_type::turn_off%]"
},
"extra_fields": {
"for": "[%key:common::device_automation::extra_fields::for%]"
}
},
"entity_component": {
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/frontend/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["home-assistant-frontend==20250903.2"]
"requirements": ["home-assistant-frontend==20250903.3"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/hue/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
"integration_type": "hub",
"iot_class": "local_push",
"loggers": ["aiohue"],
"requirements": ["aiohue==4.7.4"],
"requirements": ["aiohue==4.7.5"],
"zeroconf": ["_hue._tcp.local."]
}
3 changes: 2 additions & 1 deletion homeassistant/components/light/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
},
"extra_fields": {
"brightness_pct": "Brightness",
"flash": "Flash"
"flash": "Flash",
"for": "[%key:common::device_automation::extra_fields::for%]"
}
},
"entity_component": {
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/remote/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
"changed_states": "[%key:common::device_automation::trigger_type::changed_states%]",
"turned_on": "[%key:common::device_automation::trigger_type::turned_on%]",
"turned_off": "[%key:common::device_automation::trigger_type::turned_off%]"
},
"extra_fields": {
"for": "[%key:common::device_automation::extra_fields::for%]"
}
},
"entity_component": {
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/switch/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
"changed_states": "[%key:common::device_automation::trigger_type::changed_states%]",
"turned_on": "[%key:common::device_automation::trigger_type::turned_on%]",
"turned_off": "[%key:common::device_automation::trigger_type::turned_off%]"
},
"extra_fields": {
"for": "[%key:common::device_automation::extra_fields::for%]"
}
},
"entity_component": {
Expand Down
74 changes: 2 additions & 72 deletions homeassistant/components/tuya/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@
from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Any, NamedTuple
from urllib.parse import urlsplit
from typing import Any, NamedTuple

from tuya_sharing import (
CustomerDevice,
Manager,
SharingDeviceListener,
SharingTokenListener,
)
from tuya_sharing.mq import SharingMQ, SharingMQConfig

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
Expand Down Expand Up @@ -47,81 +45,13 @@ class HomeAssistantTuyaData(NamedTuple):
listener: SharingDeviceListener


if TYPE_CHECKING:
import paho.mqtt.client as mqtt


class ManagerCompat(Manager):
"""Extended Manager class from the Tuya device sharing SDK.

The extension ensures compatibility a paho-mqtt client version >= 2.1.0.
It overrides extend refresh_mq method to ensure correct paho.mqtt client calls.

This code can be removed when a version of tuya-device-sharing with
https://github.com/tuya/tuya-device-sharing-sdk/pull/25 is available.
"""

def refresh_mq(self):
"""Refresh the MQTT connection."""
if self.mq is not None:
self.mq.stop()
self.mq = None

home_ids = [home.id for home in self.user_homes]
device = [
device
for device in self.device_map.values()
if hasattr(device, "id") and getattr(device, "set_up", False)
]

sharing_mq = SharingMQCompat(self.customer_api, home_ids, device)
sharing_mq.start()
sharing_mq.add_message_listener(self.on_message)
self.mq = sharing_mq


class SharingMQCompat(SharingMQ):
"""Extended SharingMQ class from the Tuya device sharing SDK.

The extension ensures compatibility a paho-mqtt client version >= 2.1.0.
It overrides _start method to ensure correct paho.mqtt client calls.

This code can be removed when a version of tuya-device-sharing with
https://github.com/tuya/tuya-device-sharing-sdk/pull/25 is available.
"""

def _start(self, mq_config: SharingMQConfig) -> mqtt.Client:
"""Start the MQTT client."""
# We don't import on the top because some integrations
# should be able to optionally rely on MQTT.
import paho.mqtt.client as mqtt # noqa: PLC0415

mqttc = mqtt.Client(client_id=mq_config.client_id)
mqttc.username_pw_set(mq_config.username, mq_config.password)
mqttc.user_data_set({"mqConfig": mq_config})
mqttc.on_connect = self._on_connect
mqttc.on_message = self._on_message
mqttc.on_subscribe = self._on_subscribe
mqttc.on_log = self._on_log
mqttc.on_disconnect = self._on_disconnect

url = urlsplit(mq_config.url)
if url.scheme == "ssl":
mqttc.tls_set()

mqttc.connect(url.hostname, url.port)

mqttc.loop_start()
return mqttc


async def async_setup_entry(hass: HomeAssistant, entry: TuyaConfigEntry) -> bool:
"""Async setup hass config entry."""
if CONF_APP_TYPE in entry.data:
raise ConfigEntryAuthFailed("Authentication failed. Please re-authenticate.")

token_listener = TokenListener(hass, entry)
manager = ManagerCompat(
manager = Manager(
TUYA_CLIENT_ID,
entry.data[CONF_USER_CODE],
entry.data[CONF_TERMINAL_ID],
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/update/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
"changed_states": "{entity_name} update availability changed",
"turned_on": "{entity_name} got an update available",
"turned_off": "{entity_name} became up-to-date"
},
"extra_fields": {
"for": "[%key:common::device_automation::extra_fields::for%]"
}
},
"entity_component": {
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/package_constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ habluetooth==5.3.0
hass-nabucasa==1.1.0
hassil==3.2.0
home-assistant-bluetooth==1.13.1
home-assistant-frontend==20250903.2
home-assistant-frontend==20250903.3
home-assistant-intents==2025.9.3
httpx==0.28.1
ifaddr==0.2.0
Expand Down
4 changes: 2 additions & 2 deletions requirements_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions requirements_test_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 5 additions & 6 deletions tests/components/tuya/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
from typing import Any
from unittest.mock import patch

from tuya_sharing import CustomerDevice
from tuya_sharing import CustomerDevice, Manager

from homeassistant.components.tuya import DeviceListener, ManagerCompat
from homeassistant.components.tuya import DeviceListener
from homeassistant.core import HomeAssistant

from tests.common import MockConfigEntry
Expand Down Expand Up @@ -116,6 +116,7 @@
"dj_zputiamzanuk6yky", # https://github.com/home-assistant/core/issues/149704
"dlq_0tnvg2xaisqdadcf", # https://github.com/home-assistant/core/issues/102769
"dlq_cnpkf4xdmd9v49iq", # https://github.com/home-assistant/core/pull/149320
"dlq_dikb3dp6", # https://github.com/home-assistant/core/pull/151601
"dlq_jdj6ccklup7btq3a", # https://github.com/home-assistant/core/issues/143209
"dlq_kxdr6su0c55p7bbo", # https://github.com/home-assistant/core/issues/143499
"dlq_r9kg2g1uhhyicycb", # https://github.com/home-assistant/core/issues/149650
Expand Down Expand Up @@ -264,7 +265,7 @@ async def async_send_device_update(

async def initialize_entry(
hass: HomeAssistant,
mock_manager: ManagerCompat,
mock_manager: Manager,
mock_config_entry: MockConfigEntry,
mock_devices: CustomerDevice | list[CustomerDevice],
) -> None:
Expand All @@ -277,8 +278,6 @@ async def initialize_entry(
mock_config_entry.add_to_hass(hass)

# Initialize the component
with patch(
"homeassistant.components.tuya.ManagerCompat", return_value=mock_manager
):
with patch("homeassistant.components.tuya.Manager", return_value=mock_manager):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
21 changes: 12 additions & 9 deletions tests/components/tuya/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@
from unittest.mock import MagicMock, patch

import pytest
from tuya_sharing import CustomerApi, CustomerDevice, DeviceFunction, DeviceStatusRange
from tuya_sharing import (
CustomerApi,
CustomerDevice,
DeviceFunction,
DeviceStatusRange,
Manager,
)

from homeassistant.components.tuya import ManagerCompat
from homeassistant.components.tuya.const import (
CONF_APP_TYPE,
CONF_ENDPOINT,
Expand Down Expand Up @@ -56,7 +61,7 @@ def mock_config_entry() -> MockConfigEntry:
@pytest.fixture
async def mock_loaded_entry(
hass: HomeAssistant,
mock_manager: ManagerCompat,
mock_manager: Manager,
mock_config_entry: MockConfigEntry,
mock_device: CustomerDevice,
) -> MockConfigEntry:
Expand All @@ -69,7 +74,7 @@ async def mock_loaded_entry(

# Initialize the component
with (
patch("homeassistant.components.tuya.ManagerCompat", return_value=mock_manager),
patch("homeassistant.components.tuya.Manager", return_value=mock_manager),
):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
Expand Down Expand Up @@ -114,9 +119,9 @@ def mock_tuya_login_control() -> Generator[MagicMock]:


@pytest.fixture
def mock_manager() -> ManagerCompat:
def mock_manager() -> Manager:
"""Mock Tuya Manager."""
manager = MagicMock(spec=ManagerCompat)
manager = MagicMock(spec=Manager)
manager.device_map = {}
manager.mq = MagicMock()
manager.mq.client = MagicMock()
Expand Down Expand Up @@ -209,9 +214,7 @@ async def _create_device(hass: HomeAssistant, mock_device_code: str) -> Customer


@pytest.fixture
def mock_listener(
hass: HomeAssistant, mock_manager: ManagerCompat
) -> MockDeviceListener:
def mock_listener(hass: HomeAssistant, mock_manager: Manager) -> MockDeviceListener:
"""Create a DeviceListener for testing."""
listener = MockDeviceListener(hass, mock_manager)
mock_manager.add_device_listener(listener)
Expand Down
2 changes: 0 additions & 2 deletions tests/components/tuya/fixtures/dlq_dikb3dp6.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
{
"endpoint": "https://apigw.tuyaus.com",
"terminal_id": "1747852059900mCJdQO",
"mqtt_connected": true,
"disabled_by": null,
"disabled_polling": false,
"id": "eb40f2a309edea3892f0o2",
"name": "Medidor de Energia",
"category": "dlq",
"product_id": "dikb3dp6",
Expand Down
Loading
Loading