1 """Support for HomeMatic devices."""
3 from datetime
import datetime
4 from functools
import partial
7 from pyhomematic
import HMConnection
8 import voluptuous
as vol
24 EVENT_HOMEASSISTANT_STOP,
35 ATTR_DISCOVER_DEVICES,
56 CONF_RESOLVENAMES_OPTIONS,
61 DISCOVER_BINARY_SENSORS,
73 HM_IGNORE_DISCOVERY_NODE,
74 HM_IGNORE_DISCOVERY_NODE_EXCEPTIONS,
79 SERVICE_SET_DEVICE_VALUE,
80 SERVICE_SET_INSTALL_MODE,
81 SERVICE_SET_VARIABLE_VALUE,
84 from .entity
import HMHub
86 _LOGGER = logging.getLogger(__name__)
88 DEFAULT_LOCAL_IP =
"0.0.0.0"
89 DEFAULT_LOCAL_PORT = 0
90 DEFAULT_RESOLVENAMES =
False
94 DEFAULT_USERNAME =
"Admin"
97 DEFAULT_VERIFY_SSL =
False
101 DEVICE_SCHEMA = vol.Schema(
103 vol.Required(CONF_PLATFORM):
"homematic",
104 vol.Required(ATTR_NAME): cv.string,
105 vol.Required(ATTR_ADDRESS): cv.string,
106 vol.Required(ATTR_INTERFACE): cv.string,
107 vol.Optional(ATTR_DEVICE_TYPE): cv.string,
108 vol.Optional(ATTR_CHANNEL, default=DEFAULT_CHANNEL): vol.Coerce(int),
109 vol.Optional(ATTR_PARAM): cv.string,
110 vol.Optional(ATTR_UNIQUE_ID): cv.string,
114 CONFIG_SCHEMA = vol.Schema(
118 vol.Optional(CONF_INTERFACES, default={}): {
120 vol.Required(CONF_HOST): cv.string,
121 vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
122 vol.Optional(CONF_PATH, default=DEFAULT_PATH): cv.string,
124 CONF_RESOLVENAMES, default=DEFAULT_RESOLVENAMES
125 ): vol.In(CONF_RESOLVENAMES_OPTIONS),
126 vol.Optional(CONF_JSONPORT, default=DEFAULT_JSONPORT): cv.port,
128 CONF_USERNAME, default=DEFAULT_USERNAME
131 CONF_PASSWORD, default=DEFAULT_PASSWORD
133 vol.Optional(CONF_CALLBACK_IP): cv.string,
134 vol.Optional(CONF_CALLBACK_PORT): cv.port,
135 vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
137 CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL
141 vol.Optional(CONF_HOSTS, default={}): {
143 vol.Required(CONF_HOST): cv.string,
144 vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
146 CONF_USERNAME, default=DEFAULT_USERNAME
149 CONF_PASSWORD, default=DEFAULT_PASSWORD
153 vol.Optional(CONF_LOCAL_IP, default=DEFAULT_LOCAL_IP): cv.string,
154 vol.Optional(CONF_LOCAL_PORT): cv.port,
158 extra=vol.ALLOW_EXTRA,
161 SCHEMA_SERVICE_VIRTUALKEY = vol.Schema(
163 vol.Required(ATTR_ADDRESS): vol.All(cv.string, vol.Upper),
164 vol.Required(ATTR_CHANNEL): vol.Coerce(int),
165 vol.Required(ATTR_PARAM): cv.string,
166 vol.Optional(ATTR_INTERFACE): cv.string,
170 SCHEMA_SERVICE_SET_VARIABLE_VALUE = vol.Schema(
172 vol.Required(ATTR_NAME): cv.string,
173 vol.Required(ATTR_VALUE): cv.match_all,
174 vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
178 SCHEMA_SERVICE_SET_DEVICE_VALUE = vol.Schema(
180 vol.Required(ATTR_ADDRESS): vol.All(cv.string, vol.Upper),
181 vol.Required(ATTR_CHANNEL): vol.Coerce(int),
182 vol.Required(ATTR_PARAM): vol.All(cv.string, vol.Upper),
183 vol.Required(ATTR_VALUE): cv.match_all,
184 vol.Optional(ATTR_VALUE_TYPE): vol.In(
185 [
"boolean",
"dateTime.iso8601",
"double",
"int",
"string"]
187 vol.Optional(ATTR_INTERFACE): cv.string,
191 SCHEMA_SERVICE_RECONNECT = vol.Schema({})
193 SCHEMA_SERVICE_SET_INSTALL_MODE = vol.Schema(
195 vol.Required(ATTR_INTERFACE): cv.string,
196 vol.Optional(ATTR_TIME, default=60): cv.positive_int,
197 vol.Optional(ATTR_MODE, default=1): vol.All(vol.Coerce(int), vol.In([1, 2])),
198 vol.Optional(ATTR_ADDRESS): vol.All(cv.string, vol.Upper),
202 SCHEMA_SERVICE_PUT_PARAMSET = vol.Schema(
204 vol.Required(ATTR_INTERFACE): cv.string,
205 vol.Required(ATTR_ADDRESS): vol.All(cv.string, vol.Upper),
206 vol.Required(ATTR_PARAMSET_KEY): vol.All(cv.string, vol.Upper),
207 vol.Required(ATTR_PARAMSET): dict,
208 vol.Optional(ATTR_RX_MODE): vol.All(cv.string, vol.Upper),
213 def setup(hass: HomeAssistant, config: ConfigType) -> bool:
214 """Set up the Homematic component."""
215 conf = config[DOMAIN]
216 hass.data[DATA_CONF] = remotes = {}
217 hass.data[DATA_STORE] = set()
220 for rname, rconfig
in conf[CONF_INTERFACES].items():
222 "ip": rconfig.get(CONF_HOST),
223 "port": rconfig.get(CONF_PORT),
224 "path": rconfig.get(CONF_PATH),
225 "resolvenames": rconfig.get(CONF_RESOLVENAMES),
226 "jsonport": rconfig.get(CONF_JSONPORT),
227 "username": rconfig.get(CONF_USERNAME),
228 "password": rconfig.get(CONF_PASSWORD),
229 "callbackip": rconfig.get(CONF_CALLBACK_IP),
230 "callbackport": rconfig.get(CONF_CALLBACK_PORT),
231 "ssl": rconfig[CONF_SSL],
232 "verify_ssl": rconfig.get(CONF_VERIFY_SSL),
236 for sname, sconfig
in conf[CONF_HOSTS].items():
238 "ip": sconfig.get(CONF_HOST),
239 "port": sconfig[CONF_PORT],
240 "username": sconfig.get(CONF_USERNAME),
241 "password": sconfig.get(CONF_PASSWORD),
246 bound_system_callback = partial(_system_callback_handler, hass, config)
247 hass.data[DATA_HOMEMATIC] = homematic = HMConnection(
248 local=config[DOMAIN].
get(CONF_LOCAL_IP),
249 localport=config[DOMAIN].
get(CONF_LOCAL_PORT, DEFAULT_LOCAL_PORT),
251 systemcallback=bound_system_callback,
252 interface_id=
"homeassistant",
259 hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, hass.data[DATA_HOMEMATIC].stop)
262 entity_hubs = [
HMHub(hass, homematic, hub_name)
for hub_name
in conf[CONF_HOSTS]]
264 def _hm_service_virtualkey(service: ServiceCall) ->
None:
265 """Service to handle virtualkey servicecalls."""
266 address = service.data.get(ATTR_ADDRESS)
267 channel = service.data.get(ATTR_CHANNEL)
268 param = service.data.get(ATTR_PARAM)
273 _LOGGER.error(
"%s not found for service virtualkey!", address)
277 if param
not in hmdevice.ACTIONNODE:
278 _LOGGER.error(
"%s not datapoint in hm device %s", param, address)
282 if channel
not in hmdevice.ACTIONNODE[param]:
283 _LOGGER.error(
"%i is not a channel in hm device %s", channel, address)
287 hmdevice.actionNodeData(param,
True, channel)
289 hass.services.register(
292 _hm_service_virtualkey,
293 schema=SCHEMA_SERVICE_VIRTUALKEY,
296 def _service_handle_value(service: ServiceCall) ->
None:
297 """Service to call setValue method for HomeMatic system variable."""
298 entity_ids = service.data.get(ATTR_ENTITY_ID)
299 name = service.data[ATTR_NAME]
300 value = service.data[ATTR_VALUE]
304 entity
for entity
in entity_hubs
if entity.entity_id
in entity_ids
307 entities = entity_hubs
310 _LOGGER.error(
"No HomeMatic hubs available")
314 hub.hm_set_variable(name, value)
316 hass.services.register(
318 SERVICE_SET_VARIABLE_VALUE,
319 _service_handle_value,
320 schema=SCHEMA_SERVICE_SET_VARIABLE_VALUE,
323 def _service_handle_reconnect(service: ServiceCall) ->
None:
324 """Service to reconnect all HomeMatic hubs."""
325 homematic.reconnect()
327 hass.services.register(
330 _service_handle_reconnect,
331 schema=SCHEMA_SERVICE_RECONNECT,
334 def _service_handle_device(service: ServiceCall) ->
None:
335 """Service to call setValue method for HomeMatic devices."""
336 address = service.data[ATTR_ADDRESS]
337 channel = service.data[ATTR_CHANNEL]
338 param = service.data[ATTR_PARAM]
339 value = service.data[ATTR_VALUE]
340 value_type = service.data.get(ATTR_VALUE_TYPE)
345 if value_type ==
"int":
347 elif value_type ==
"double":
349 elif value_type ==
"boolean":
351 elif value_type ==
"dateTime.iso8601":
352 value = datetime.strptime(value,
"%Y%m%dT%H:%M:%S")
360 _LOGGER.error(
"%s not found!", address)
363 hmdevice.setValue(param, value, channel)
365 hass.services.register(
367 SERVICE_SET_DEVICE_VALUE,
368 _service_handle_device,
369 schema=SCHEMA_SERVICE_SET_DEVICE_VALUE,
372 def _service_handle_install_mode(service: ServiceCall) ->
None:
373 """Service to set interface into install mode."""
374 interface = service.data.get(ATTR_INTERFACE)
375 mode = service.data.get(ATTR_MODE)
376 time = service.data.get(ATTR_TIME)
377 address = service.data.get(ATTR_ADDRESS)
379 homematic.setInstallMode(interface, t=time, mode=mode, address=address)
381 hass.services.register(
383 SERVICE_SET_INSTALL_MODE,
384 _service_handle_install_mode,
385 schema=SCHEMA_SERVICE_SET_INSTALL_MODE,
388 def _service_put_paramset(service: ServiceCall) ->
None:
389 """Service to call the putParamset method on a HomeMatic connection."""
390 interface = service.data[ATTR_INTERFACE]
391 address = service.data[ATTR_ADDRESS]
392 paramset_key = service.data[ATTR_PARAMSET_KEY]
396 paramset =
dict(service.data[ATTR_PARAMSET])
397 rx_mode = service.data.get(ATTR_RX_MODE)
400 "Calling putParamset: %s, %s, %s, %s, %s",
407 homematic.putParamset(interface, address, paramset_key, paramset, rx_mode)
409 hass.services.register(
411 SERVICE_PUT_PARAMSET,
412 _service_put_paramset,
413 schema=SCHEMA_SERVICE_PUT_PARAMSET,
420 """System callback handler."""
422 if src ==
"newDevices":
423 (interface_id, dev_descriptions) = args
424 interface = interface_id.split(
"-")[-1]
427 if not hass.data[DATA_CONF][interface][
"connect"]:
431 for dev
in dev_descriptions:
432 address = dev[
"ADDRESS"].split(
":")[0]
433 if address
not in hass.data[DATA_STORE]:
434 hass.data[DATA_STORE].
add(address)
435 addresses.append(address)
439 bound_event_callback = partial(_hm_event_handler, hass, interface)
440 for dev
in addresses:
441 hmdevice = hass.data[DATA_HOMEMATIC].devices[interface].
get(dev)
443 if hmdevice.EVENTNODE:
444 hmdevice.setEventCallback(callback=bound_event_callback, bequeath=
True)
448 for component_name, discovery_type
in (
449 (
"switch", DISCOVER_SWITCHES),
450 (
"light", DISCOVER_LIGHTS),
451 (
"cover", DISCOVER_COVER),
452 (
"binary_sensor", DISCOVER_BINARY_SENSORS),
453 (
"sensor", DISCOVER_SENSORS),
454 (
"climate", DISCOVER_CLIMATE),
455 (
"lock", DISCOVER_LOCKS),
456 (
"binary_sensor", DISCOVER_BATTERY),
459 found_devices =
_get_devices(hass, discovery_type, addresses, interface)
464 discovery.load_platform(
469 ATTR_DISCOVER_DEVICES: found_devices,
470 ATTR_DISCOVERY_TYPE: discovery_type,
477 _LOGGER.error(
"Error: %s", args)
478 (interface_id, errorcode, message) = args
479 hass.bus.fire(EVENT_ERROR, {ATTR_ERRORCODE: errorcode, ATTR_MESSAGE: message})
483 """Get the HomeMatic devices for given discovery_type."""
487 device = hass.data[DATA_HOMEMATIC].devices[interface][key]
488 class_name = device.__class__.__name__
493 discovery_type != DISCOVER_BATTERY
494 and class_name
not in HM_DEVICE_TYPES[discovery_type]
499 if discovery_type == DISCOVER_SENSORS:
500 metadata.update(device.SENSORNODE)
501 elif discovery_type == DISCOVER_BINARY_SENSORS:
502 metadata.update(device.BINARYNODE)
503 elif discovery_type == DISCOVER_BATTERY:
504 if ATTR_LOWBAT
in device.ATTRIBUTENODE:
505 metadata.update({ATTR_LOWBAT: device.ATTRIBUTENODE[ATTR_LOWBAT]})
506 elif ATTR_LOW_BAT
in device.ATTRIBUTENODE:
507 metadata.update({ATTR_LOW_BAT: device.ATTRIBUTENODE[ATTR_LOW_BAT]})
511 metadata.update({
None: device.ELEMENT})
514 for param, channels
in metadata.items():
516 param
in HM_IGNORE_DISCOVERY_NODE
517 and class_name
not in HM_IGNORE_DISCOVERY_NODE_EXCEPTIONS.get(param, [])
520 if discovery_type == DISCOVER_SWITCHES
and class_name ==
"IPKeySwitchLevel":
523 if discovery_type == DISCOVER_LIGHTS
and class_name ==
"IPKeySwitchLevel":
528 "%s: Handling %s: %s: %s", discovery_type, key, param, channels
530 for channel
in channels:
532 name=device.NAME, channel=channel, param=param, count=len(channels)
535 name=key, channel=channel, param=param, count=len(channels)
538 CONF_PLATFORM:
"homematic",
540 ATTR_INTERFACE: interface,
542 ATTR_DEVICE_TYPE: class_name,
543 ATTR_CHANNEL: channel,
544 ATTR_UNIQUE_ID: unique_id,
546 if param
is not None:
547 device_dict[ATTR_PARAM] = param
552 device_arr.append(device_dict)
553 except vol.MultipleInvalid
as err:
554 _LOGGER.error(
"Invalid device config: %s",
str(err))
559 """Generate a unique entity id."""
561 if count == 1
and param
is None:
565 if count > 1
and param
is None:
566 return f
"{name} {channel}"
569 if count == 1
and param
is not None:
570 return f
"{name} {param}"
573 if count > 1
and param
is not None:
574 return f
"{name} {channel} {param}"
576 raise ValueError(f
"Unable to create unique id for count:{count} and param:{param}")
580 """Handle all pyhomematic device events."""
582 channel =
int(device.split(
":")[1])
583 address = device.split(
":")[0]
584 hmdevice = hass.data[DATA_HOMEMATIC].devices[interface].
get(address)
585 except (TypeError, ValueError):
586 _LOGGER.error(
"Event handling channel convert error!")
590 if attribute
not in hmdevice.EVENTNODE:
593 _LOGGER.debug(
"Event %s for %s channel %i", attribute, hmdevice.NAME, channel)
596 if attribute
in HM_PRESS_EVENTS:
599 {ATTR_NAME: hmdevice.NAME, ATTR_PARAM: attribute, ATTR_CHANNEL: channel},
604 if attribute
in HM_IMPULSE_EVENTS:
605 hass.bus.fire(EVENT_IMPULSE, {ATTR_NAME: hmdevice.NAME, ATTR_CHANNEL: channel})
608 _LOGGER.warning(
"Event is unknown and not forwarded")
612 """Extract HomeMatic device from service call."""
613 address = service.data.get(ATTR_ADDRESS)
614 interface = service.data.get(ATTR_INTERFACE)
615 if address ==
"BIDCOS-RF":
616 address =
"BidCoS-RF"
617 if address ==
"HMIP-RCV-1":
618 address =
"HmIP-RCV-1"
621 return hass.data[DATA_HOMEMATIC].devices[interface].
get(address)
623 for devices
in hass.data[DATA_HOMEMATIC].devices.values():
624 if address
in devices:
625 return devices[address]
bool add(self, _T matcher)
web.Response get(self, web.Request request, str config_key)
def _hm_event_handler(hass, interface, device, caller, attribute, value)
bool setup(HomeAssistant hass, ConfigType config)
def _system_callback_handler(hass, config, src, *args)
def _device_from_servicecall(hass, service)
def _get_devices(hass, discovery_type, keys, interface)
def _create_ha_id(name, channel, param, count)