-
-
Notifications
You must be signed in to change notification settings - Fork 30.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for Homekit battery service #14288
Conversation
b56628c
to
7900ba3
Compare
@cdce8p I'm not sure how to handle ChargingState characteristic, there's no standard Home Assistant constant attribute (yet). My image is that the constants below would eventually be moved to Home Assistant global constants and other components would be updated to use them. Any thoughts?
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which entities currently support a Charging State? The rule is that a new feature should be used at least once or it can't be added.
It might be necessary to harmonize the attribute first. That would start with a new issue in https://github.com/home-assistant/architecture.
Nevertheless I added a few first comments.
@@ -149,7 +149,8 @@ def get_accessory(hass, state, aid, config): | |||
return None | |||
|
|||
_LOGGER.debug('Add "%s" as "%s"', state.entity_id, a_type) | |||
return TYPES[a_type](hass, state.name, state.entity_id, aid, config=config) | |||
return TYPES[a_type](hass, state.name, state.entity_id, state.attributes, | |||
aid, config=config) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't pass state.attributes
. Fetch the new state from hass instead with hass.states.get(entity_id)
inside the HomeAccessory
.
serv_battery = self.add_preload_service(SERV_BATTERY_SERVICE) | ||
self.char_battery = serv_battery.configure_char( | ||
CHAR_BATTERY_LEVEL, | ||
value=0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can go on one line.
value=0) | ||
self.char_charging = serv_battery.configure_char( | ||
CHAR_CHARGING_STATE, | ||
value=HASS_TO_HOMEKIT[STATE_UNKNOWN]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
value=2
is enough. This should get overridden immediately after the driver is started.
@@ -93,9 +117,24 @@ def update_state_callback(self, entity_id=None, old_state=None, | |||
def update_state(self, new_state): | |||
"""Method called on state change to update HomeKit value. | |||
|
|||
Overridden by accessory types. | |||
Update battery service if available. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of using the update_state
method, why not add a new one update_battery_level
and call it before update_state
in update_state_callback
? That way you don't need to add super
calls to every accessory type.
"""Update battery service if available.""" | ||
battery_level = convert_to_float(new_state.attributes | ||
.get(ATTR_BATTERY_LEVEL)) | ||
if battery_level is not None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could reduce the indent level for the rest of the function by using
if battery_level is None:
return
if battery_level <=1:
# an so on
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, done.
battery_level = convert_to_float(new_state.attributes | ||
.get(ATTR_BATTERY_LEVEL)) | ||
if battery_level is not None: | ||
if battery_level <= 1: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this necessary? The battery_level should be reported in the same range by all components. If not the component/platform needs to be fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Valid point, I was trying to make it robust but I believe you're correct. Fixed.
ATTR_CHARGING_STATE = 'charging_state' | ||
ATTR_CHARGING_STATE_CHARGING = 'charging' | ||
ATTR_CHARGING_STATE_NOT_CHARGING = 'not_charging' | ||
ATTR_CHARGING_STATE_NOT_SUPPORTED = 'not_supported' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those should go inside homeassistant.const
to be accessible for other platforms and components.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed charging state tracking.
@cdce8p I agree that the charging state attribute architecture needs to be harmonized first and then affected components updated. So, I propose to set ChargingState characteristic to NotSupported as a fixed value for now. Once the charging state attribute is in place, then this can be updated to provide dynamic updates. |
@schmittx I would advice you to propose the final solution directly otherwise I don't see much chance in getting it through once something is implemented. |
b7700f3
to
a1caea0
Compare
I added an attribute in homeassistant/const.py as discussed in home-assistant/architecture#25 |
7e8ddb1
to
40eb66a
Compare
@@ -31,7 +31,8 @@ def cls(request): | |||
async def test_garage_door_open_close(hass, cls): | |||
"""Test if accessory and HA are updated accordingly.""" | |||
entity_id = 'cover.garage_door' | |||
|
|||
hass.states.async_set(entity_id, None, {}) | |||
await hass.async_block_till_done() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If have extracted those changes to #14416, since this might take a bit until it's merged.
6b3e6f1
to
cd59a0d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the time being we should set this PR to WIP
.
def update_battery_level(self, new_state): | ||
"""Update battery service if available.""" | ||
battery_level = convert_to_float(new_state.attributes | ||
.get(ATTR_BATTERY_LEVEL)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
battery_level = convert_to_float(
new_state.attributes.get(ATTR_BATTERY_LEVEL))
f463b6a
to
576c806
Compare
@@ -85,8 +100,32 @@ def update_state_callback(self, entity_id=None, old_state=None, | |||
_LOGGER.debug('New_state: %s', new_state) | |||
if new_state is None: | |||
return | |||
self.hass.async_add_job(self.update_battery_level, new_state) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't make sense to always call self.update_batter_level
only to exit moments later since it isn't support by that entity. Instead define a flag during __init__
and check if batter
is supported before calling it.
self.char_low_battery.set_value(battery_level < 20) | ||
_LOGGER.debug('%s: Updated battery level to %d', self.entity_id, | ||
battery_level) | ||
self.hass.async_add_job(self.update_battery_charging, new_state) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This won't work!
update_bettery_level
is a sync function and async_add_job
must be called from an async
context. If you still want to do it that way, use add_job
instead. Even better would be to just combine the to methods.
"""Update battery service if available.""" | ||
charging = new_state.attributes.get(ATTR_BATTERY_CHARGING) | ||
if charging is None: | ||
hk_charging = 2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If an entity is not chargeable the first time, it won't be any time later. Maybe another flag?
await hass.async_block_till_done() | ||
|
||
acc = HomeAccessory(hass, hk_driver, 'Battery Service', | ||
entity_id, 2, None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It fits on one line. Same below
@@ -69,12 +69,15 @@ def __init__(self, hass, driver, name, entity_id, aid, config, | |||
self.entity_id = entity_id | |||
self.hass = hass | |||
self.debounce = {} | |||
self._flag = {SERV_BATTERY_SERVICE: False, | |||
ATTR_BATTERY_CHARGING: True} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A dictionary for two values is probably a bit much.
What do you think of self._support_battery_level
and self._support_charging
?
"""Update battery service if available.""" | ||
battery_level = convert_to_float( | ||
new_state.attributes.get(ATTR_BATTERY_LEVEL)) | ||
if battery_level is None: | ||
return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
battery_level
could still be None
, e.g. if conversion fails. Might be better to leave it in.
self.char_charging.set_value(hk_charging) | ||
_LOGGER.debug('%s: Updated battery charging to %d', self.entity_id, | ||
hk_charging) | ||
if self._flag[ATTR_BATTERY_CHARGING]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of indenting all, just exit with return
if condition is false.
charging = new_state.attributes.get(ATTR_BATTERY_CHARGING) | ||
if charging is None: | ||
self._flag[ATTR_BATTERY_CHARGING] = False | ||
hk_charging = 2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here: Exit with return.
"""Add battery service if available""" | ||
battery_level = self.hass.states.get(self.entity_id).attributes \ | ||
.get(ATTR_BATTERY_LEVEL) | ||
if battery_level: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also here: Exit with return.
I did some final touches. Mainly changing the chars to |
Description:
Add battery service for entities with
battery_level
attribute.Related issue (if applicable): N/A
Pull request in home-assistant.github.io with documentation (if applicable): N/A
Example entry for
configuration.yaml
(if applicable):N/A
Checklist:
tox
. Your PR cannot be merged unless tests passIf user exposed functionality or configuration variables are added/changed:
If the code does not interact with devices: