Skip to content
3 changes: 3 additions & 0 deletions homeassistant/components/bosch_shc/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@
"child_lock": {
"name": "Child lock"
},
"pet_immunity_enabled": {
"name": "Pet immunity"
},
"routing": {
"name": "Range extension"
}
Expand Down
21 changes: 21 additions & 0 deletions homeassistant/components/bosch_shc/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,15 @@ class SHCSwitchEntityDescription(SwitchEntityDescription):
on_value=BypassService.State.BYPASS_ACTIVE,
should_poll=False,
),
"pet_immunity_enabled": SHCSwitchEntityDescription(
key="pet_immunity_enabled",
translation_key="pet_immunity_enabled",
device_class=SwitchDeviceClass.SWITCH,
entity_category=EntityCategory.CONFIG,
on_key="pet_immunity_enabled",
on_value=True,
should_poll=False,
),
}


Expand Down Expand Up @@ -257,6 +266,18 @@ async def async_setup_entry(
for switch in session.device_helper.shutter_contacts2
)

entities.extend(
SHCSwitch(
hass=hass,
device=switch,
parent_id=shc_info.unique_id,
entry_id=config_entry.entry_id,
description=SWITCH_TYPES["pet_immunity_enabled"],
unique_id_suffix="pet_immunity",
)
for switch in session.device_helper.motion_detectors2
)

async_add_entities(entities)


Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/homeworks/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ def _try_connect(host: str, port: int) -> None:
_LOGGER.debug("Caught HomeworksNoCredentialsProvided")
raise SchemaFlowError("credentials_needed") from err
except Exception as err:
_LOGGER.exception("Caught unexpected exception %s")
_LOGGER.exception("Caught unexpected exception")
raise SchemaFlowError("unknown_error") from err


Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/indi_allsky/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ class IndiAllSkySensorEntityDescription(SensorEntityDescription):
value_fn=lambda data: data.exposure.stars if data.exposure else None,
),
IndiAllSkySensorEntityDescription(
key="temperature",
key="camera_sensor_temp",
translation_key="camera_sensor_temp",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/indi_allsky/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
"binmode": {
"name": "Binning mode"
},
"camera_sensor_temp": {
"name": "Camera sensor temperature"
},
"exposure": {
"name": "Exposure time"
},
Expand Down
31 changes: 5 additions & 26 deletions homeassistant/components/keba/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
import probatio

from homeassistant.const import CONF_HOST, Platform
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv, discovery
from homeassistant.helpers.typing import ConfigType

from .const import DOMAIN
from .services import async_setup_services

_LOGGER = logging.getLogger(__name__)

DOMAIN = "keba"
PLATFORMS = (Platform.BINARY_SENSOR, Platform.SENSOR, Platform.LOCK, Platform.NOTIFY)

CONF_RFID = "rfid"
Expand Down Expand Up @@ -43,17 +45,6 @@
extra=probatio.ALLOW_EXTRA,
)

_SERVICE_MAP = {
"request_data": "async_request_data",
"set_energy": "async_set_energy",
"set_current": "async_set_current",
"authorize": "async_start",
"deauthorize": "async_stop",
"enable": "async_enable_ev",
"disable": "async_disable_ev",
"set_failsafe": "async_set_failsafe",
}


async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Check connectivity and version of KEBA charging station."""
Expand All @@ -78,19 +69,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
except ValueError as ex:
_LOGGER.warning("Could not set failsafe mode %s", ex)

# Register services to hass
async def execute_service(call: ServiceCall) -> None:
"""Execute a service to KEBA charging station.

This must be a member function as we need access to the keba
object here.
"""
function_name = _SERVICE_MAP[call.service]
function_call = getattr(keba, function_name)
await function_call(call.data)

for service in _SERVICE_MAP:
hass.services.async_register(DOMAIN, service, execute_service)
async_setup_services(hass)

# Load components
for platform in PLATFORMS:
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/keba/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Constants for the KEBA charging station integration."""

DOMAIN = "keba"
30 changes: 30 additions & 0 deletions homeassistant/components/keba/services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Support for KEBA charging station services."""

from homeassistant.core import HomeAssistant, ServiceCall, callback

from .const import DOMAIN

_SERVICE_MAP = {
"request_data": "async_request_data",
"set_energy": "async_set_energy",
"set_current": "async_set_current",
"authorize": "async_start",
"deauthorize": "async_stop",
"enable": "async_enable_ev",
"disable": "async_disable_ev",
"set_failsafe": "async_set_failsafe",
}


async def _async_execute_service(call: ServiceCall) -> None:
"""Execute a service to KEBA charging station."""
keba = call.hass.data[DOMAIN]
function_call = getattr(keba, _SERVICE_MAP[call.service])
await function_call(call.data)


@callback
def async_setup_services(hass: HomeAssistant) -> None:
"""Register the KEBA services."""
for service in _SERVICE_MAP:
hass.services.async_register(DOMAIN, service, _async_execute_service)
6 changes: 4 additions & 2 deletions homeassistant/components/litellm/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ def _format_tool(
custom_serializer: Callable[[Any], Any] | None,
) -> ChatCompletionFunctionToolParam:
"""Format tool specification."""
unsupported_keys = {"oneOf", "anyOf", "allOf"}
schema = to_openapi(tool.parameters, custom_serializer=custom_serializer)
unsupported_keys = {"oneOf", "anyOf", "allOf", "enum", "not"}
schema = to_openapi(
tool.parameters, custom_serializer=custom_serializer, openapi_version="3.1.0"
)
schema = {k: v for k, v in schema.items() if k not in unsupported_keys}

tool_spec = FunctionDefinition(
Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/mqtt/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,8 @@ def attributes_prepare_discovery_update(self, config: DiscoveryInfoType) -> None
_LOGGER.info(
"Group member update received for entity %s, "
"but this entity was not initialized with the `group` option. "
"Reload the MQTT integration or restart Home Assistant to activate"
"Reload the MQTT integration or restart Home Assistant to activate",
self.entity_id,
)

self._attributes_config = config
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/sisyphus/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,14 @@ def brightness(self) -> int:
async def async_turn_off(self, **kwargs: Any) -> None:
"""Put the table to sleep."""
await self._table.sleep()
_LOGGER.debug("Sisyphus table %s: sleep")
_LOGGER.debug("Sisyphus table %s: sleep", self._name)

@override
async def async_turn_on(self, **kwargs: Any) -> None:
"""Wake up the table if necessary, optionally changes brightness."""
if not self.is_on:
await self._table.wakeup()
_LOGGER.debug("Sisyphus table %s: wakeup")
_LOGGER.debug("Sisyphus table %s: wakeup", self._name)

if "brightness" in kwargs:
await self._table.set_brightness(kwargs["brightness"] / 255.0)
2 changes: 1 addition & 1 deletion homeassistant/components/snoo/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ async def async_step_user(
except InvalidSnooAuth:
errors["base"] = "invalid_auth"
except Exception:
_LOGGER.exception("Unexpected exception %s")
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
user_uuid = jwt.decode(
Expand Down
22 changes: 22 additions & 0 deletions tests/components/bosch_shc/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
SHCLightSwitchBSM,
SHCMicromoduleBlinds,
SHCMicromoduleRelay,
SHCMotionDetector2,
SHCPresenceSimulationSystem,
SHCShutterContact2,
SHCShutterControl,
Expand Down Expand Up @@ -73,6 +74,7 @@ def mock_config_entry() -> MockConfigEntry:
"micromodule_relays",
"micromodule_shutter_controls",
"motion_detectors",
"motion_detectors2",
"roomthermostats",
"shutter_contacts",
"shutter_contacts2",
Expand Down Expand Up @@ -332,3 +334,23 @@ def shutter_contact2_device(
device.bypass = bypass
device.bypass_infinite = bypass_infinite
return device


def motion_detector2_device(
device_id: str = "hdm:ZigBee:motiondetector1",
name: str = "Motion Detector",
pet_immunity_enabled: bool = False,
) -> SHCMotionDetector2:
"""Build a minimal device double for the motion_detectors2 bucket."""
device = create_autospec(SHCMotionDetector2, instance=True, spec_set=True)
device.name = name
device.id = device_id
device.root_device_id = "test-mac"
device.serial = f"serial-{device_id}"
device.manufacturer = "Bosch"
device.device_model = "MD2"
device.device_services = []
device.deleted = False
device.status = "AVAILABLE"
device.pet_immunity_enabled = pet_immunity_enabled
return device
37 changes: 37 additions & 0 deletions tests/components/bosch_shc/test_switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from .conftest import (
light_switch_bsm_device,
micromodule_relay_device,
motion_detector2_device,
presence_simulation_system_device,
setup_integration,
shutter_contact2_device,
Expand Down Expand Up @@ -317,3 +318,39 @@ async def test_shutter_contact2_bypass_unique_id(
assert bypass_entry is not None
assert bypass_infinite_entry is not None
assert bypass_entry.unique_id != bypass_infinite_entry.unique_id


@pytest.mark.parametrize(
"device_buckets",
[{"motion_detectors2": [motion_detector2_device(pet_immunity_enabled=False)]}],
indirect=True,
)
@pytest.mark.usefixtures("mock_session")
async def test_motion_detector2_pet_immunity(
hass: HomeAssistant,
mock_session: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""A Motion Detector 2's pet immunity setting is exposed and controllable."""
await setup_integration(hass, mock_config_entry)
device = mock_session.device_helper.motion_detectors2[0]

state = hass.states.get("switch.motion_detector_pet_immunity")
assert state is not None
assert state.state == "off"

await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: "switch.motion_detector_pet_immunity"},
blocking=True,
)
assert device.pet_immunity_enabled is True

await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: "switch.motion_detector_pet_immunity"},
blocking=True,
)
assert device.pet_immunity_enabled is False
Loading
Loading