Skip to content
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

Merged
merged 4 commits into from
Jun 17, 2018

Conversation

schmittx
Copy link
Contributor

@schmittx schmittx commented May 4, 2018

Description:

Add battery service for entities with battery_level attribute.

fullsizeoutput_40b7

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:

  • The code change is tested and works locally.
  • Local tests pass with tox. Your PR cannot be merged unless tests pass

If user exposed functionality or configuration variables are added/changed:

If the code does not interact with devices:

  • Tests have been added to verify that the new code works.

@schmittx
Copy link
Contributor Author

schmittx commented May 4, 2018

@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?

# #### BATTERY SERVICE ####
ATTR_CHARGING_STATE = 'charging_state'
ATTR_CHARGING_STATE_CHARGING = 'charging'
ATTR_CHARGING_STATE_NOT_CHARGING = 'not_charging'
ATTR_CHARGING_STATE_NOT_SUPPORTED = 'not_supported'

@cdce8p cdce8p self-assigned this May 4, 2018
Copy link
Member

@cdce8p cdce8p left a 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)
Copy link
Member

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)
Copy link
Member

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])
Copy link
Member

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.
Copy link
Member

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:
Copy link
Member

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

Copy link
Contributor Author

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:
Copy link
Member

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.

Copy link
Contributor Author

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'
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed charging state tracking.

@schmittx
Copy link
Contributor Author

schmittx commented May 6, 2018

@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.

@cdce8p
Copy link
Member

cdce8p commented May 6, 2018

@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.

@schmittx
Copy link
Contributor Author

I added an attribute in homeassistant/const.py as discussed in home-assistant/architecture#25

@schmittx schmittx changed the title [WIP] Homekit battery service Homekit battery service May 11, 2018
@@ -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()
Copy link
Member

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.

Copy link
Member

@cdce8p cdce8p left a 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))
Copy link
Member

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))

@schmittx schmittx changed the title Homekit battery service [WIP] Homekit battery service May 20, 2018
@schmittx schmittx closed this Jun 12, 2018
@schmittx
Copy link
Contributor Author

@cdce8p Sorry for the long delay on this. We finally got the dev docs updated to define standard attributes so this should be ready for final review(s). The sister PR to define ATTR_BATTERY_CHARGING in dev docs is here.

@schmittx schmittx changed the title [WIP] Homekit battery service Add support for Homekit battery service Jun 12, 2018
@@ -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)
Copy link
Member

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)
Copy link
Member

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
Copy link
Member

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)
Copy link
Member

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}
Copy link
Member

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
Copy link
Member

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]:
Copy link
Member

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
Copy link
Member

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:
Copy link
Member

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.

@cdce8p
Copy link
Member

cdce8p commented Jun 17, 2018

I did some final touches. Mainly changing the chars to private. Although this doesn't effect anything, it might be warning not to use them. Will merge it, once tests pass.

@cdce8p cdce8p merged commit 1533bc1 into home-assistant:dev Jun 17, 2018
@ghost ghost removed the in progress label Jun 17, 2018
@schmittx schmittx deleted the homekit-battery-service branch July 5, 2018 15:36
@balloob balloob mentioned this pull request Jul 6, 2018
@home-assistant home-assistant locked and limited conversation to collaborators Dec 10, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants