Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: cdce8p/ha-core
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 440f5094f9085c945921808a678e7c914cf602fb
Choose a base ref
..
head repository: cdce8p/ha-core
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 3c438317af7b84287b86eb5201ba7a3a434a698f
Choose a head ref
Showing with 492 additions and 189 deletions.
  1. +1 −0 .coveragerc
  2. +1 −1 homeassistant/components/eufylife_ble/manifest.json
  3. +4 −4 homeassistant/components/fibaro/__init__.py
  4. +8 −8 homeassistant/components/fibaro/event.py
  5. +2 −2 homeassistant/components/flux_led/services.yaml
  6. +6 −3 homeassistant/components/group/services.yaml
  7. +1 −1 homeassistant/components/light/services.yaml
  8. +1 −1 homeassistant/components/matrix/manifest.json
  9. +27 −8 homeassistant/components/minecraft_server/__init__.py
  10. +134 −0 homeassistant/components/minecraft_server/api.py
  11. +4 −3 homeassistant/components/minecraft_server/binary_sensor.py
  12. +24 −40 homeassistant/components/minecraft_server/config_flow.py
  13. +8 −49 homeassistant/components/minecraft_server/coordinator.py
  14. +8 −3 homeassistant/components/minecraft_server/entity.py
  15. +70 −5 homeassistant/components/minecraft_server/sensor.py
  16. +10 −1 homeassistant/components/minecraft_server/strings.json
  17. +1 −1 homeassistant/components/mqtt/services.yaml
  18. +1 −1 homeassistant/components/ombi/strings.json
  19. +2 −1 homeassistant/components/scene/services.yaml
  20. +1 −1 homeassistant/components/slack/notify.py
  21. +4 −4 homeassistant/components/supla/cover.py
  22. +3 −3 homeassistant/components/supla/entity.py
  23. +1 −1 homeassistant/components/supla/manifest.json
  24. +3 −3 homeassistant/components/supla/switch.py
  25. +1 −1 homeassistant/components/yeelight/services.yaml
  26. +1 −1 homeassistant/generated/integrations.json
  27. +0 −4 pyproject.toml
  28. +2 −2 requirements_all.txt
  29. +2 −2 requirements_test_all.txt
  30. +40 −0 tests/components/minecraft_server/const.py
  31. +101 −18 tests/components/minecraft_server/test_config_flow.py
  32. +20 −17 tests/components/minecraft_server/test_init.py
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -745,6 +745,7 @@ omit =
homeassistant/components/mill/climate.py
homeassistant/components/mill/sensor.py
homeassistant/components/minecraft_server/__init__.py
homeassistant/components/minecraft_server/api.py
homeassistant/components/minecraft_server/binary_sensor.py
homeassistant/components/minecraft_server/coordinator.py
homeassistant/components/minecraft_server/entity.py
2 changes: 1 addition & 1 deletion homeassistant/components/eufylife_ble/manifest.json
Original file line number Diff line number Diff line change
@@ -24,5 +24,5 @@
"documentation": "https://www.home-assistant.io/integrations/eufylife_ble",
"integration_type": "device",
"iot_class": "local_push",
"requirements": ["eufylife-ble-client==0.1.7"]
"requirements": ["eufylife-ble-client==0.1.8"]
}
8 changes: 4 additions & 4 deletions homeassistant/components/fibaro/__init__.py
Original file line number Diff line number Diff line change
@@ -194,8 +194,8 @@ def _on_state_change(self, state: Any) -> None:

def register(self, device_id: int, callback: Any) -> None:
"""Register device with a callback for updates."""
self._callbacks.setdefault(device_id, [])
self._callbacks[device_id].append(callback)
device_callbacks = self._callbacks.setdefault(device_id, [])
device_callbacks.append(callback)

def register_event(
self, device_id: int, callback: Callable[[FibaroEvent], None]
@@ -204,8 +204,8 @@ def register_event(
The callback receives one parameter with the event.
"""
self._event_callbacks.setdefault(device_id, [])
self._event_callbacks[device_id].append(callback)
device_callbacks = self._event_callbacks.setdefault(device_id, [])
device_callbacks.append(callback)

def get_children(self, device_id: int) -> list[DeviceModel]:
"""Get a list of child devices."""
16 changes: 8 additions & 8 deletions homeassistant/components/fibaro/event.py
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from . import FibaroController, FibaroDevice
@@ -41,16 +41,17 @@ class FibaroEventEntity(FibaroDevice, EventEntity):
def __init__(self, fibaro_device: DeviceModel, scene_event: SceneEvent) -> None:
"""Initialize the Fibaro device."""
super().__init__(fibaro_device)
self.entity_id = ENTITY_ID_FORMAT.format(
f"{self.ha_id}_button_{scene_event.key_id}"
)

self._button = scene_event.key_id
key_id = scene_event.key_id

self.entity_id = ENTITY_ID_FORMAT.format(f"{self.ha_id}_button_{key_id}")

self._button = key_id

self._attr_name = f"{fibaro_device.friendly_name} Button {scene_event.key_id}"
self._attr_name = f"{fibaro_device.friendly_name} Button {key_id}"
self._attr_device_class = EventDeviceClass.BUTTON
self._attr_event_types = scene_event.key_event_types
self._attr_unique_id = f"{fibaro_device.unique_id_str}.{scene_event.key_id}"
self._attr_unique_id = f"{fibaro_device.unique_id_str}.{key_id}"

async def async_added_to_hass(self) -> None:
"""Call when entity is added to hass."""
@@ -61,7 +62,6 @@ async def async_added_to_hass(self) -> None:
self.fibaro_device.fibaro_id, self._event_callback
)

@callback
def _event_callback(self, event: FibaroEvent) -> None:
if event.key_id == self._button:
self._trigger_event(event.key_event_type)
4 changes: 2 additions & 2 deletions homeassistant/components/flux_led/services.yaml
Original file line number Diff line number Diff line change
@@ -113,9 +113,9 @@ set_music_mode:
example: "[255, 100, 100]"
required: false
selector:
object:
color_rgb:
background_color:
example: "[255, 100, 100]"
required: false
selector:
object:
color_rgb:
9 changes: 6 additions & 3 deletions homeassistant/components/group/services.yaml
Original file line number Diff line number Diff line change
@@ -18,15 +18,18 @@ set:
entities:
example: domain.entity_id1, domain.entity_id2
selector:
object:
entity:
multiple: true
add_entities:
example: domain.entity_id1, domain.entity_id2
selector:
object:
entity:
multiple: true
remove_entities:
example: domain.entity_id1, domain.entity_id2
selector:
object:
entity:
multiple: true
all:
selector:
boolean:
2 changes: 1 addition & 1 deletion homeassistant/components/light/services.yaml
Original file line number Diff line number Diff line change
@@ -422,7 +422,7 @@ toggle:
advanced: true
example: "[255, 100, 100]"
selector:
object:
color_rgb:
color_name:
filter:
attribute:
2 changes: 1 addition & 1 deletion homeassistant/components/matrix/manifest.json
Original file line number Diff line number Diff line change
@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/matrix",
"iot_class": "cloud_push",
"loggers": ["matrix_client"],
"requirements": ["matrix-nio==0.21.2", "Pillow==10.0.1"]
"requirements": ["matrix-nio==0.22.1", "Pillow==10.0.1"]
}
35 changes: 27 additions & 8 deletions homeassistant/components/minecraft_server/__init__.py
Original file line number Diff line number Diff line change
@@ -4,14 +4,21 @@
import logging
from typing import Any

from mcstatus import JavaServer

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ADDRESS, CONF_HOST, CONF_PORT, Platform
from homeassistant.const import (
CONF_ADDRESS,
CONF_HOST,
CONF_NAME,
CONF_PORT,
CONF_TYPE,
Platform,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryError
import homeassistant.helpers.device_registry as dr
import homeassistant.helpers.entity_registry as er

from .api import MinecraftServer, MinecraftServerAddressError, MinecraftServerType
from .const import DOMAIN, KEY_LATENCY, KEY_MOTD
from .coordinator import MinecraftServerCoordinator

@@ -23,8 +30,20 @@
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Minecraft Server from a config entry."""

# Check and create API instance.
try:
api = await hass.async_add_executor_job(
MinecraftServer,
entry.data.get(CONF_TYPE, MinecraftServerType.JAVA_EDITION),
entry.data[CONF_ADDRESS],
)
except MinecraftServerAddressError as error:
raise ConfigEntryError(
f"Server address in configuration entry is invalid (error: {error})"
) from error

# Create coordinator instance.
coordinator = MinecraftServerCoordinator(hass, entry)
coordinator = MinecraftServerCoordinator(hass, entry.data[CONF_NAME], api)
await coordinator.async_config_entry_first_refresh()

# Store coordinator instance.
@@ -85,9 +104,9 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
# Migrate config entry.
try:
address = config_data[CONF_HOST]
JavaServer.lookup(address)
MinecraftServer(MinecraftServerType.JAVA_EDITION, address)
host_only_lookup_success = True
except ValueError as error:
except MinecraftServerAddressError as error:
host_only_lookup_success = False
_LOGGER.debug(
"Hostname (without port) cannot be parsed (error: %s), trying again with port",
@@ -97,8 +116,8 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
if not host_only_lookup_success:
try:
address = f"{config_data[CONF_HOST]}:{config_data[CONF_PORT]}"
JavaServer.lookup(address)
except ValueError as error:
MinecraftServer(MinecraftServerType.JAVA_EDITION, address)
except MinecraftServerAddressError as error:
_LOGGER.exception(
"Can't migrate configuration entry due to error while parsing server address (error: %s), try again later",
error,
134 changes: 134 additions & 0 deletions homeassistant/components/minecraft_server/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
"""API for the Minecraft Server integration."""


from dataclasses import dataclass
from enum import StrEnum
import logging

from dns.resolver import LifetimeTimeout
from mcstatus import BedrockServer, JavaServer
from mcstatus.status_response import BedrockStatusResponse, JavaStatusResponse

_LOGGER = logging.getLogger(__name__)


@dataclass
class MinecraftServerData:
"""Representation of Minecraft Server data."""

# Common data
latency: float
motd: str
players_max: int
players_online: int
protocol_version: int
version: str

# Data available only in 'Java Edition'
players_list: list[str] | None = None

# Data available only in 'Bedrock Edition'
edition: str | None = None
game_mode: str | None = None
map_name: str | None = None


class MinecraftServerType(StrEnum):
"""Enumeration of Minecraft Server types."""

BEDROCK_EDITION = "Bedrock Edition"
JAVA_EDITION = "Java Edition"


class MinecraftServerAddressError(Exception):
"""Raised when the input address is invalid."""


class MinecraftServerConnectionError(Exception):
"""Raised when no data can be fechted from the server."""


class MinecraftServer:
"""Minecraft Server wrapper class for 3rd party library mcstatus."""

_server: BedrockServer | JavaServer

def __init__(self, server_type: MinecraftServerType, address: str) -> None:
"""Initialize server instance."""
try:
if server_type == MinecraftServerType.JAVA_EDITION:
self._server = JavaServer.lookup(address)
else:
self._server = BedrockServer.lookup(address)
except (ValueError, LifetimeTimeout) as error:
raise MinecraftServerAddressError(
f"{server_type} server address '{address}' is invalid (error: {error})"
) from error

_LOGGER.debug(
"%s server instance created with address '%s'", server_type, address
)

async def async_is_online(self) -> bool:
"""Check if the server is online, supporting both Java and Bedrock Edition servers."""
try:
await self.async_get_data()
except MinecraftServerConnectionError:
return False

return True

async def async_get_data(self) -> MinecraftServerData:
"""Get updated data from the server, supporting both Java and Bedrock Edition servers."""
status_response: BedrockStatusResponse | JavaStatusResponse

try:
status_response = await self._server.async_status()
except OSError as error:
raise MinecraftServerConnectionError(
f"Fetching data from the server failed (error: {error})"
) from error

if isinstance(status_response, JavaStatusResponse):
data = self._extract_java_data(status_response)
else:
data = self._extract_bedrock_data(status_response)

return data

def _extract_java_data(
self, status_response: JavaStatusResponse
) -> MinecraftServerData:
"""Extract Java Edition server data out of status response."""
players_list = []

if players := status_response.players.sample:
for player in players:
players_list.append(player.name)
players_list.sort()

return MinecraftServerData(
latency=status_response.latency,
motd=status_response.motd.to_plain(),
players_max=status_response.players.max,
players_online=status_response.players.online,
protocol_version=status_response.version.protocol,
version=status_response.version.name,
players_list=players_list,
)

def _extract_bedrock_data(
self, status_response: BedrockStatusResponse
) -> MinecraftServerData:
"""Extract Bedrock Edition server data out of status response."""
return MinecraftServerData(
latency=status_response.latency,
motd=status_response.motd.to_plain(),
players_max=status_response.players.max,
players_online=status_response.players.online,
protocol_version=status_response.version.protocol,
version=status_response.version.name,
edition=status_response.version.brand,
game_mode=status_response.gamemode,
map_name=status_response.map_name,
)
7 changes: 4 additions & 3 deletions homeassistant/components/minecraft_server/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -45,7 +45,7 @@ async def async_setup_entry(
# Add binary sensor entities.
async_add_entities(
[
MinecraftServerBinarySensorEntity(coordinator, description)
MinecraftServerBinarySensorEntity(coordinator, description, config_entry)
for description in BINARY_SENSOR_DESCRIPTIONS
]
)
@@ -60,11 +60,12 @@ def __init__(
self,
coordinator: MinecraftServerCoordinator,
description: MinecraftServerBinarySensorEntityDescription,
config_entry: ConfigEntry,
) -> None:
"""Initialize binary sensor base entity."""
super().__init__(coordinator)
super().__init__(coordinator, config_entry)
self.entity_description = description
self._attr_unique_id = f"{coordinator.unique_id}-{description.key}"
self._attr_unique_id = f"{config_entry.entry_id}-{description.key}"
self._attr_is_on = False

@property
Loading