-
-
Notifications
You must be signed in to change notification settings - Fork 37.6k
Add valve platform support to google_assistant #106139
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,6 +29,7 @@ | |
| sensor, | ||
| switch, | ||
| vacuum, | ||
| valve, | ||
| water_heater, | ||
| ) | ||
| from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature | ||
|
|
@@ -41,6 +42,7 @@ | |
| from homeassistant.components.lock import STATE_JAMMED, STATE_UNLOCKING | ||
| from homeassistant.components.media_player import MediaPlayerEntityFeature, MediaType | ||
| from homeassistant.components.vacuum import VacuumEntityFeature | ||
| from homeassistant.components.valve import ValveEntityFeature | ||
| from homeassistant.components.water_heater import WaterHeaterEntityFeature | ||
| from homeassistant.const import ( | ||
| ATTR_ASSUMED_STATE, | ||
|
|
@@ -180,6 +182,57 @@ | |
|
|
||
| FAN_SPEED_MAX_SPEED_COUNT = 5 | ||
|
|
||
| COVER_VALVE_STATES = { | ||
| cover.DOMAIN: { | ||
| "closed": cover.STATE_CLOSED, | ||
| "closing": cover.STATE_CLOSING, | ||
| "open": cover.STATE_OPEN, | ||
| "opening": cover.STATE_OPENING, | ||
| }, | ||
| valve.DOMAIN: { | ||
| "closed": valve.STATE_CLOSED, | ||
| "closing": valve.STATE_CLOSING, | ||
| "open": valve.STATE_OPEN, | ||
| "opening": valve.STATE_OPENING, | ||
| }, | ||
| } | ||
|
|
||
| SERVICE_STOP_COVER_VALVE = { | ||
| cover.DOMAIN: cover.SERVICE_STOP_COVER, | ||
| valve.DOMAIN: valve.SERVICE_STOP_VALVE, | ||
| } | ||
| SERVICE_OPEN_COVER_VALVE = { | ||
| cover.DOMAIN: cover.SERVICE_OPEN_COVER, | ||
| valve.DOMAIN: valve.SERVICE_OPEN_VALVE, | ||
| } | ||
| SERVICE_CLOSE_COVER_VALVE = { | ||
| cover.DOMAIN: cover.SERVICE_CLOSE_COVER, | ||
| valve.DOMAIN: valve.SERVICE_CLOSE_VALVE, | ||
| } | ||
| SERVICE_SET_POSITION_COVER_VALVE = { | ||
| cover.DOMAIN: cover.SERVICE_SET_COVER_POSITION, | ||
| valve.DOMAIN: valve.SERVICE_SET_VALVE_POSITION, | ||
| } | ||
|
|
||
| COVER_VALVE_CURRENT_POSITION = { | ||
| cover.DOMAIN: cover.ATTR_CURRENT_POSITION, | ||
| valve.DOMAIN: valve.ATTR_CURRENT_POSITION, | ||
| } | ||
|
|
||
| COVER_VALVE_POSITION = { | ||
| cover.DOMAIN: cover.ATTR_POSITION, | ||
| valve.DOMAIN: valve.ATTR_POSITION, | ||
| } | ||
|
|
||
| COVER_VALVE_SET_POSITION_FEATURE = { | ||
| cover.DOMAIN: CoverEntityFeature.SET_POSITION, | ||
| valve.DOMAIN: ValveEntityFeature.SET_POSITION, | ||
| } | ||
|
|
||
| COVER_VALVE_DOMAINS = {cover.DOMAIN, valve.DOMAIN} | ||
|
|
||
| FRIENDLY_DOMAIN = {cover.DOMAIN: "Cover", valve.DOMAIN: "Valve"} | ||
|
|
||
| _TraitT = TypeVar("_TraitT", bound="_Trait") | ||
|
|
||
|
|
||
|
|
@@ -796,6 +849,9 @@ def supported(domain, features, device_class, _): | |
| if domain == cover.DOMAIN and features & CoverEntityFeature.STOP: | ||
| return True | ||
|
|
||
| if domain == valve.DOMAIN and features & ValveEntityFeature.STOP: | ||
| return True | ||
|
|
||
| return False | ||
|
|
||
| def sync_attributes(self): | ||
|
|
@@ -807,7 +863,7 @@ def sync_attributes(self): | |
| & VacuumEntityFeature.PAUSE | ||
| != 0 | ||
| } | ||
| if domain == cover.DOMAIN: | ||
| if domain in COVER_VALVE_DOMAINS: | ||
| return {} | ||
|
|
||
| def query_attributes(self): | ||
|
|
@@ -823,14 +879,16 @@ def query_attributes(self): | |
|
|
||
| if domain == cover.DOMAIN: | ||
| return {"isRunning": state in (cover.STATE_CLOSING, cover.STATE_OPENING)} | ||
| if domain == valve.DOMAIN: | ||
| return {"isRunning": True} | ||
|
jbouwh marked this conversation as resolved.
|
||
|
|
||
| async def execute(self, command, data, params, challenge): | ||
| """Execute a StartStop command.""" | ||
| domain = self.state.domain | ||
| if domain == vacuum.DOMAIN: | ||
| return await self._execute_vacuum(command, data, params, challenge) | ||
| if domain == cover.DOMAIN: | ||
| return await self._execute_cover(command, data, params, challenge) | ||
| if domain in COVER_VALVE_DOMAINS: | ||
| return await self._execute_cover_or_valve(command, data, params, challenge) | ||
|
|
||
| async def _execute_vacuum(self, command, data, params, challenge): | ||
| """Execute a StartStop command.""" | ||
|
|
@@ -869,28 +927,35 @@ async def _execute_vacuum(self, command, data, params, challenge): | |
| context=data.context, | ||
| ) | ||
|
|
||
| async def _execute_cover(self, command, data, params, challenge): | ||
| async def _execute_cover_or_valve(self, command, data, params, challenge): | ||
| """Execute a StartStop command.""" | ||
| domain = self.state.domain | ||
| if command == COMMAND_STARTSTOP: | ||
| if params["start"] is False: | ||
| if self.state.state in ( | ||
| cover.STATE_CLOSING, | ||
| cover.STATE_OPENING, | ||
| ) or self.state.attributes.get(ATTR_ASSUMED_STATE): | ||
| if ( | ||
| self.state.state | ||
| in ( | ||
| COVER_VALVE_STATES[domain]["closing"], | ||
| COVER_VALVE_STATES[domain]["opening"], | ||
| ) | ||
| or domain == valve.DOMAIN | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We want the stop function to be always available since we can not read determine from, the state if
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is that?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It will ensure that the |
||
| or self.state.attributes.get(ATTR_ASSUMED_STATE) | ||
| ): | ||
| await self.hass.services.async_call( | ||
| self.state.domain, | ||
| cover.SERVICE_STOP_COVER, | ||
| domain, | ||
| SERVICE_STOP_COVER_VALVE[domain], | ||
| {ATTR_ENTITY_ID: self.state.entity_id}, | ||
| blocking=not self.config.should_report_state, | ||
| context=data.context, | ||
| ) | ||
| else: | ||
| raise SmartHomeError( | ||
| ERR_ALREADY_STOPPED, "Cover is already stopped" | ||
| ERR_ALREADY_STOPPED, | ||
| f"{FRIENDLY_DOMAIN[domain]} is already stopped", | ||
| ) | ||
| else: | ||
| raise SmartHomeError( | ||
| ERR_NOT_SUPPORTED, "Starting a cover is not supported" | ||
| ERR_NOT_SUPPORTED, f"Starting a {domain} is not supported" | ||
| ) | ||
| else: | ||
| raise SmartHomeError( | ||
|
|
@@ -2081,7 +2146,7 @@ class OpenCloseTrait(_Trait): | |
| @staticmethod | ||
| def supported(domain, features, device_class, _): | ||
| """Test if state is supported.""" | ||
| if domain == cover.DOMAIN: | ||
| if domain in COVER_VALVE_DOMAINS: | ||
| return True | ||
|
|
||
| return domain == binary_sensor.DOMAIN and device_class in ( | ||
|
|
@@ -2116,6 +2181,17 @@ def sync_attributes(self): | |
| and features & CoverEntityFeature.CLOSE == 0 | ||
| ): | ||
| response["queryOnlyOpenClose"] = True | ||
| elif ( | ||
| self.state.domain == valve.DOMAIN | ||
| and features & ValveEntityFeature.SET_POSITION == 0 | ||
| ): | ||
| response["discreteOnlyOpenClose"] = True | ||
|
|
||
| if ( | ||
| features & ValveEntityFeature.OPEN == 0 | ||
| and features & ValveEntityFeature.CLOSE == 0 | ||
| ): | ||
| response["queryOnlyOpenClose"] = True | ||
|
|
||
| if self.state.attributes.get(ATTR_ASSUMED_STATE): | ||
| response["commandOnlyOpenClose"] = True | ||
|
|
@@ -2134,17 +2210,17 @@ def query_attributes(self): | |
| if self.state.attributes.get(ATTR_ASSUMED_STATE): | ||
| return response | ||
|
|
||
| if domain == cover.DOMAIN: | ||
| if domain in COVER_VALVE_DOMAINS: | ||
| if self.state.state == STATE_UNKNOWN: | ||
| raise SmartHomeError( | ||
| ERR_NOT_SUPPORTED, "Querying state is not supported" | ||
| ) | ||
|
|
||
| position = self.state.attributes.get(cover.ATTR_CURRENT_POSITION) | ||
| position = self.state.attributes.get(COVER_VALVE_CURRENT_POSITION[domain]) | ||
|
|
||
| if position is not None: | ||
| response["openPercent"] = position | ||
| elif self.state.state != cover.STATE_CLOSED: | ||
| elif self.state.state != COVER_VALVE_STATES[domain]["closed"]: | ||
| response["openPercent"] = 100 | ||
| else: | ||
| response["openPercent"] = 0 | ||
|
|
@@ -2162,11 +2238,13 @@ async def execute(self, command, data, params, challenge): | |
| domain = self.state.domain | ||
| features = self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) | ||
|
|
||
| if domain == cover.DOMAIN: | ||
| if domain in COVER_VALVE_DOMAINS: | ||
| svc_params = {ATTR_ENTITY_ID: self.state.entity_id} | ||
| should_verify = False | ||
| if command == COMMAND_OPENCLOSE_RELATIVE: | ||
| position = self.state.attributes.get(cover.ATTR_CURRENT_POSITION) | ||
| position = self.state.attributes.get( | ||
| COVER_VALVE_CURRENT_POSITION[domain] | ||
| ) | ||
| if position is None: | ||
| raise SmartHomeError( | ||
| ERR_NOT_SUPPORTED, | ||
|
|
@@ -2177,16 +2255,16 @@ async def execute(self, command, data, params, challenge): | |
| position = params["openPercent"] | ||
|
|
||
| if position == 0: | ||
| service = cover.SERVICE_CLOSE_COVER | ||
| service = SERVICE_CLOSE_COVER_VALVE[domain] | ||
| should_verify = False | ||
| elif position == 100: | ||
| service = cover.SERVICE_OPEN_COVER | ||
| service = SERVICE_OPEN_COVER_VALVE[domain] | ||
| should_verify = True | ||
| elif features & CoverEntityFeature.SET_POSITION: | ||
| service = cover.SERVICE_SET_COVER_POSITION | ||
| elif features & COVER_VALVE_SET_POSITION_FEATURE[domain]: | ||
| service = SERVICE_SET_POSITION_COVER_VALVE[domain] | ||
| if position > 0: | ||
| should_verify = True | ||
| svc_params[cover.ATTR_POSITION] = position | ||
| svc_params[COVER_VALVE_POSITION[domain]] = position | ||
| else: | ||
| raise SmartHomeError( | ||
| ERR_NOT_SUPPORTED, "No support for partial open close" | ||
|
|
@@ -2200,7 +2278,7 @@ async def execute(self, command, data, params, challenge): | |
| _verify_pin_challenge(data, self.state, challenge) | ||
|
|
||
| await self.hass.services.async_call( | ||
| cover.DOMAIN, | ||
| domain, | ||
| service, | ||
| svc_params, | ||
| blocking=not self.config.should_report_state, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -103,6 +103,7 @@ | |
| 'sensor', | ||
| 'switch', | ||
| 'vacuum', | ||
| 'valve', | ||
| 'water_heater', | ||
| ]), | ||
| 'project_id': '1234', | ||
|
|
||
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.
We return
True, so the state is alwaysrunningand theStopfunction will be always available (if 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.
Hm, why don't we rely on CLOSING + OPENING states like we do for cover?
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.
The reason is that not all valves support that, but they can support the stop function. Which should be always available and not depend on the reported state.
Futher the
Startbutton, that will pop up instead will not make any sense, hence I made this button to always showStop.To avoid this:
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.
I think that should be a UX fix. Replace Start button for open/close depending on the state. And isRunning uses the same logic as the cover.
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 is not something we can easily customize. It is how the start stop treat works. The integration can decide if a
stopcommand should be processed.Uh oh!
There was an error while loading. Please reload this page.
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.
BTW. the cover implentation ideed has the same issue:
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.
I suggest I open a separate PR to fix this for cover as well.
Uh oh!
There was an error while loading. Please reload this page.
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.
Another suggestion is to use
Startto toggle the valve (or cover) #106378