1 """Support for the Fibaro devices."""
3 from __future__
import annotations
5 from collections
import defaultdict
6 from collections.abc
import Callable, Mapping
10 from pyfibaro.fibaro_client
import FibaroClient
11 from pyfibaro.fibaro_device
import DeviceModel
12 from pyfibaro.fibaro_room
import RoomModel
13 from pyfibaro.fibaro_scene
import SceneModel
14 from pyfibaro.fibaro_state_resolver
import FibaroEvent, FibaroStateResolver
15 from requests.exceptions
import HTTPError
21 ConfigEntryAuthFailed,
29 from .const
import CONF_IMPORT_PLUGINS, DOMAIN
31 _LOGGER = logging.getLogger(__name__)
35 Platform.BINARY_SENSOR,
47 "com.fibaro.multilevelSensor": Platform.SENSOR,
48 "com.fibaro.binarySwitch": Platform.SWITCH,
49 "com.fibaro.multilevelSwitch": Platform.SWITCH,
50 "com.fibaro.FGD212": Platform.LIGHT,
51 "com.fibaro.FGR": Platform.COVER,
52 "com.fibaro.doorSensor": Platform.BINARY_SENSOR,
53 "com.fibaro.doorWindowSensor": Platform.BINARY_SENSOR,
54 "com.fibaro.FGMS001": Platform.BINARY_SENSOR,
55 "com.fibaro.heatDetector": Platform.BINARY_SENSOR,
56 "com.fibaro.lifeDangerSensor": Platform.BINARY_SENSOR,
57 "com.fibaro.smokeSensor": Platform.BINARY_SENSOR,
58 "com.fibaro.remoteSwitch": Platform.SWITCH,
59 "com.fibaro.sensor": Platform.SENSOR,
60 "com.fibaro.colorController": Platform.LIGHT,
61 "com.fibaro.securitySensor": Platform.BINARY_SENSOR,
62 "com.fibaro.hvac": Platform.CLIMATE,
63 "com.fibaro.hvacSystem": Platform.CLIMATE,
64 "com.fibaro.setpoint": Platform.CLIMATE,
65 "com.fibaro.FGT001": Platform.CLIMATE,
66 "com.fibaro.thermostatDanfoss": Platform.CLIMATE,
67 "com.fibaro.doorLock": Platform.LOCK,
68 "com.fibaro.binarySensor": Platform.BINARY_SENSOR,
69 "com.fibaro.accelerometer": Platform.BINARY_SENSOR,
74 """Initiate Fibaro Controller Class."""
76 def __init__(self, config: Mapping[str, Any]) ->
None:
77 """Initialize the Fibaro controller."""
80 self.
_client_client = FibaroClient(config[CONF_URL])
81 self.
_client_client.set_authentication(config[CONF_USERNAME], config[CONF_PASSWORD])
85 self.
_room_map_room_map: dict[int, RoomModel]
87 self.fibaro_devices: dict[Platform, list[DeviceModel]] = defaultdict(
91 self.
_scenes_scenes: list[SceneModel] = []
92 self._callbacks: dict[int, list[Any]] = {}
94 self._event_callbacks: dict[int, list[Callable[[FibaroEvent],
None]]] = {}
99 self.hub_api_url: str = config[CONF_URL]
101 self._device_infos: dict[int, DeviceInfo] = {}
104 """Start the communication with the Fibaro controller."""
109 info = self.
_client_client.read_info()
115 self.
_room_map_room_map = {room.fibaro_id: room
for room
in self.
_client_client.read_rooms()}
120 """Translate connect errors to easily differentiate auth and connect failures.
122 When there is a better error handling in the used library this can be improved.
126 except HTTPError
as http_ex:
127 if http_ex.response.status_code == 403:
128 raise FibaroAuthFailed
from http_ex
130 raise FibaroConnectFailed
from http_ex
131 except Exception
as ex:
132 raise FibaroConnectFailed
from ex
135 """Start StateHandler thread for monitoring updates."""
139 """Stop StateHandler thread used for monitoring updates."""
140 self.
_client_client.unregister_update_handler()
143 """Handle change report received from the HomeCenter."""
145 for change
in state.get(
"changes", []):
147 dev_id = change.pop(
"id")
151 for property_name, value
in change.items():
152 if property_name ==
"log":
153 if value
and value !=
"transfer OK":
154 _LOGGER.debug(
"LOG %s: %s", device.friendly_name, value)
156 if property_name ==
"logTemp":
158 if property_name
in device.properties:
159 device.properties[property_name] = value
161 "<- %s.%s = %s", device.ha_id, property_name,
str(value)
164 _LOGGER.warning(
"%s.%s not found", device.ha_id, property_name)
165 if dev_id
in self._callbacks:
166 callback_set.add(dev_id)
167 except (ValueError, KeyError):
169 for item
in callback_set:
170 for callback
in self._callbacks[item]:
173 resolver = FibaroStateResolver(state)
174 for event
in resolver.get_events():
178 event.event_type.lower() ==
"centralsceneevent"
179 and event.fibaro_id
in self._event_callbacks
181 for callback
in self._event_callbacks[event.fibaro_id]:
184 def register(self, device_id: int, callback: Any) ->
None:
185 """Register device with a callback for updates."""
186 device_callbacks = self._callbacks.setdefault(device_id, [])
187 device_callbacks.append(callback)
190 self, device_id: int, callback: Callable[[FibaroEvent],
None]
192 """Register device with a callback for central scene events.
194 The callback receives one parameter with the event.
196 device_callbacks = self._event_callbacks.setdefault(device_id, [])
197 device_callbacks.append(callback)
200 """Get a list of child devices."""
204 if device.parent_fibaro_id == device_id
207 def get_children2(self, device_id: int, endpoint_id: int) -> list[DeviceModel]:
208 """Get a list of child devices for the same endpoint."""
212 if device.parent_fibaro_id == device_id
213 and (
not device.has_endpoint_id
or device.endpoint_id == endpoint_id)
217 """Get the siblings of a device."""
218 if device.has_endpoint_id:
219 return self.
get_children2get_children2(device.parent_fibaro_id, device.endpoint_id)
220 return self.
get_childrenget_children(device.parent_fibaro_id)
224 """Map device to HA device type."""
226 platform: Platform |
None =
None
228 platform = FIBARO_TYPEMAP.get(device.type)
229 if platform
is None and device.base_type:
230 platform = FIBARO_TYPEMAP.get(device.base_type)
234 if "setBrightness" in device.actions:
235 platform = Platform.LIGHT
236 elif "turnOn" in device.actions:
237 platform = Platform.SWITCH
238 elif "open" in device.actions:
239 platform = Platform.COVER
240 elif "secure" in device.actions:
241 platform = Platform.LOCK
242 elif device.has_central_scene_event:
243 platform = Platform.EVENT
244 elif device.value.has_value
and device.value.is_bool_value:
245 platform = Platform.BINARY_SENSOR
247 device.value.has_value
248 or "power" in device.properties
249 or "energy" in device.properties
251 platform = Platform.SENSOR
254 if platform == Platform.SWITCH
and device.properties.get(
"isLight",
False):
255 platform = Platform.LIGHT
259 self, device: DeviceModel, devices: list[DeviceModel]
261 """Create the device info. Unrooted entities are directly shown below the home center."""
264 if device.parent_fibaro_id <= 1:
267 master_entity: DeviceModel |
None =
None
268 if device.parent_fibaro_id == 1:
269 master_entity = device
271 for parent
in devices:
272 if parent.fibaro_id == device.parent_fibaro_id:
273 master_entity = parent
274 if master_entity
is None:
275 _LOGGER.error(
"Parent with id %s not found", device.parent_fibaro_id)
278 if "zwaveCompany" in master_entity.properties:
279 manufacturer = master_entity.properties.get(
"zwaveCompany")
283 self._device_infos[master_entity.fibaro_id] =
DeviceInfo(
284 identifiers={(DOMAIN, master_entity.fibaro_id)},
285 manufacturer=manufacturer,
286 name=master_entity.name,
287 via_device=(DOMAIN, self.
hub_serialhub_serial),
291 """Get the device info by fibaro device id."""
292 if device.fibaro_id
in self._device_infos:
293 return self._device_infos[device.fibaro_id]
294 if device.parent_fibaro_id
in self._device_infos:
295 return self._device_infos[device.parent_fibaro_id]
299 """Get all identifiers of fibaro integration."""
300 return [device[
"identifiers"]
for device
in self._device_infos.values()]
303 """Get the room name by room id."""
306 return room.name
if room
else None
309 """Return list of scenes."""
313 """Read and process the device list."""
314 devices = self.
_client_client.read_devices()
316 last_climate_parent =
None
318 for device
in devices:
320 device.fibaro_controller = self
321 if device.room_id == 0:
322 room_name =
"Unknown"
324 room_name = self.
_room_map_room_map[device.room_id].name
325 device.room_name = room_name
326 device.friendly_name = f
"{room_name} {device.name}"
328 f
"{slugify(room_name)}_{slugify(device.name)}_{device.fibaro_id}"
330 if device.enabled
and (
not device.is_plugin
or self.
_import_plugins_import_plugins):
333 device.mapped_platform =
None
334 if (platform := device.mapped_platform)
is None:
336 device.unique_id_str = f
"{slugify(self.hub_serial)}.{device.fibaro_id}"
338 self.
_device_map_device_map[device.fibaro_id] = device
340 "%s (%s, %s) -> %s %s",
347 if platform != Platform.CLIMATE:
348 self.fibaro_devices[platform].append(device)
352 if device.has_endpoint_id:
354 "climate device: %s, endPointId: %s",
359 _LOGGER.debug(
"climate device: %s, no endPointId", device.ha_id)
364 if last_climate_parent != device.parent_fibaro_id
or (
365 device.has_endpoint_id
and last_endpoint != device.endpoint_id
367 _LOGGER.debug(
"Handle separately")
368 self.fibaro_devices[platform].append(device)
369 last_climate_parent = device.parent_fibaro_id
370 last_endpoint = device.endpoint_id
372 _LOGGER.debug(
"not handling separately")
373 except (KeyError, ValueError):
378 """Validate the user input allows us to connect to fibaro."""
380 controller.connect_with_error_handling()
385 """Set up the Fibaro Component.
387 The unique id of the config entry is the serial number of the home center.
390 controller = await hass.async_add_executor_job(init_controller, entry.data)
391 except FibaroConnectFailed
as connect_ex:
393 f
"Could not connect to controller at {entry.data[CONF_URL]}"
395 except FibaroAuthFailed
as auth_ex:
396 raise ConfigEntryAuthFailed
from auth_ex
398 hass.data.setdefault(DOMAIN, {})[entry.entry_id] = controller
401 device_registry = dr.async_get(hass)
402 device_registry.async_get_or_create(
403 config_entry_id=entry.entry_id,
404 identifiers={(DOMAIN, controller.hub_serial)},
405 serial_number=controller.hub_serial,
406 manufacturer=
"Fibaro",
407 name=controller.hub_name,
408 model=controller.hub_model,
409 sw_version=controller.hub_software_version,
410 configuration_url=controller.hub_api_url.removesuffix(
"/api/"),
413 await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
415 controller.enable_state_handler()
421 """Unload a config entry."""
422 _LOGGER.debug(
"Shutting down Fibaro connection")
423 unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
425 hass.data[DOMAIN][entry.entry_id].disable_state_handler()
426 hass.data[DOMAIN].pop(entry.entry_id)
432 hass: HomeAssistant, config_entry: ConfigEntry, device_entry: DeviceEntry
434 """Remove a device entry from fibaro integration.
436 Only removing devices which are not present anymore are eligible to be removed.
438 controller: FibaroController = hass.data[DOMAIN][config_entry.entry_id]
439 for identifiers
in controller.get_all_device_identifiers():
440 if device_entry.identifiers == identifiers:
449 """Error to indicate we cannot connect to fibaro home center."""
452 class FibaroAuthFailed(HomeAssistantError):
453 """Error to indicate that authentication failed on fibaro home center."""
None __init__(self, Mapping[str, Any] config)
str|None get_room_name(self, int room_id)
list[DeviceModel] get_siblings(self, DeviceModel device)
DeviceInfo get_device_info(self, DeviceModel device)
None register_event(self, int device_id, Callable[[FibaroEvent], None] callback)
None connect_with_error_handling(self)
None enable_state_handler(self)
list[set[tuple[str, str]]] get_all_device_identifiers(self)
list[DeviceModel] get_children2(self, int device_id, int endpoint_id)
None disable_state_handler(self)
None _on_state_change(self, Any state)
None register(self, int device_id, Any callback)
list[DeviceModel] get_children(self, int device_id)
Platform|None _map_device_to_platform(DeviceModel device)
None _create_device_info(self, DeviceModel device, list[DeviceModel] devices)
list[SceneModel] read_scenes(self)
web.Response get(self, web.Request request, str config_key)
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
bool async_remove_config_entry_device(HomeAssistant hass, ConfigEntry config_entry, DeviceEntry device_entry)
FibaroController init_controller(Mapping[str, Any] data)
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)