Skip to content

Commit

Permalink
Add blind and curtain control for v1.3.2
Browse files Browse the repository at this point in the history
Updates the back end software to include tilt control for blinds.
  • Loading branch information
rstrouse committed Mar 4, 2023
1 parent 3581def commit eefe782
Show file tree
Hide file tree
Showing 12 changed files with 140 additions and 10 deletions.
4 changes: 2 additions & 2 deletions custom_components/espsomfy_rts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ async def _async_ws_close(_: Event) -> None:
entry.async_on_unload(
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_ws_close)
)

hass.config_entries.async_setup_platforms(entry, PLATFORMS)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
# hass.config_entries.async_setup_platforms(entry, PLATFORMS)
await controller.ws_connect()

return True
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1 change: 1 addition & 0 deletions custom_components/espsomfy_rts/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
API_CONTROLLER = "/controller"
API_SHADES = "/shades"
API_SHADECOMMAND = "/shadeCommand"
API_TILTCOMMAND = "/tiltCommand"
API_DISCOVERY = "/discovery"
EVT_CONTROLLER = "controller"
EVT_SHADESTATE = "shadeState"
Expand Down
50 changes: 45 additions & 5 deletions custom_components/espsomfy_rts/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import aiohttp
import websocket


from homeassistant.components.cover import CoverDeviceClass, CoverEntityFeature
from homeassistant.const import CONF_HOST, Platform
from homeassistant.core import HomeAssistant
Expand All @@ -23,6 +24,7 @@
from .const import (
API_DISCOVERY,
API_SHADECOMMAND,
API_TILTCOMMAND,
API_SHADES,
DOMAIN,
EVT_CONNECTED,
Expand Down Expand Up @@ -220,21 +222,36 @@ def ensure_shade_configured(self, data):
for entity in async_entries_for_config_entry(entities, self.config_entry_id):
if entity.unique_id == uuid:
return
dev_features = (CoverEntityFeature.OPEN
| CoverEntityFeature.CLOSE
| CoverEntityFeature.STOP
| CoverEntityFeature.SET_POSITION)

dev_class = CoverDeviceClass.SHADE
if "shadeType" in data:
match int(data["shadeType"]):
case 1:
dev_class = CoverDeviceClass.BLIND
if "hasTilt" in data and data["hasTilt"] is True:
dev_features |= (CoverEntityFeature.OPEN_TILT | CoverEntityFeature.CLOSE_TILT | CoverEntityFeature.SET_TILT_POSITION)
case 2:
dev_class = CoverDeviceClass.CURTAIN
case _:
dev_class = CoverDeviceClass.SHADE


# Reload all the shades
# self.api.load_shades()
# I have no idea whether this reloads the devices or not.
entities.async_get_or_create(
domain=DOMAIN,
platform=Platform.COVER,
original_device_class=CoverDeviceClass.SHADE,
original_device_class=dev_class,
unique_id=uuid,
device_id=device.id,
original_name=data["name"],
suggested_object_id=f"{str(data['name']).lower().replace(' ', '_')}",
supported_features=CoverEntityFeature.OPEN
| CoverEntityFeature.CLOSE
| CoverEntityFeature.STOP
| CoverEntityFeature.SET_POSITION,
supported_features=dev_features,
)
print(f"Shade not found {uuid} and one was added")

Expand Down Expand Up @@ -333,6 +350,19 @@ async def load_shades(self) -> Any | None:
else:
_LOGGER.error(await resp.text())

async def tilt_open(self, shade_id: int):
"""Send the command to open the tilt"""
await self.tilt_command({"shadeId": shade_id, "command": "up"})

async def tilt_close(self, shade_id: int):
"""Send the command to close the tilt"""
await self.tilt_command({"shadeId": shade_id, "command": "down"})

async def position_tilt(self, shade_id: int, position: int):
"""Send the command to position the shade"""
print(f"Setting tilt position to {position}")
await self.tilt_command({"shadeId": shade_id, "target": position})

async def open_shade(self, shade_id: int):
"""Send the command to open the shade"""
await self.shade_command({"shadeId": shade_id, "command": "up"})
Expand All @@ -359,6 +389,16 @@ async def shade_command(self, data):
else:
_LOGGER.error(await resp.text())

async def tilt_command(self, data):
"""Send commands to ESPSomfyRTS via PUT request"""
async with self._session.put(
f"{self._api_url}{API_TILTCOMMAND}", json=data
) as resp:
if resp.status == 200:
pass
else:
_LOGGER.error(await resp.text())

async def get_initial(self):
"""Get the initial config from nodejs-PoolController."""
try:
Expand Down
62 changes: 60 additions & 2 deletions custom_components/espsomfy_rts/cover.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Support for ESPSomfy RTS Shades."""
"""Support for ESPSomfy RTS Shades and Blinds."""
from __future__ import annotations

from typing import Any
Expand All @@ -7,6 +7,7 @@

from homeassistant.components.cover import (
ATTR_POSITION,
ATTR_TILT_POSITION,
CoverDeviceClass,
CoverEntity,
CoverEntityFeature,
Expand All @@ -25,6 +26,9 @@
SVC_CLOSE_SHADE = "close_shade"
SVC_STOP_SHADE = "stop_shade"
SVC_SET_SHADE_POS = "set_shade_position"
SVC_TILT_OPEN = "tilt_open"
SVC_TILT_CLOSE = "tilt_close"
SVC_SET_TILT_POS = "set_tilt_position"


async def async_setup_entry(
Expand All @@ -49,9 +53,16 @@ async def async_setup_entry(
{vol.Required(ATTR_POSITION): cv.string},
"async_set_cover_position",
)
platform.async_register_entity_service(
SVC_SET_TILT_POS,
{vol.Required(ATTR_POSITION): cv.string},
"async_set_cover_tilt_position",
)
platform.async_register_entity_service(SVC_OPEN_SHADE, {}, "async_open_cover")
platform.async_register_entity_service(SVC_CLOSE_SHADE, {}, "async_close_cover")
platform.async_register_entity_service(SVC_STOP_SHADE, {}, "async_stop_cover")
platform.async_register_entity_service(SVC_TILT_OPEN, {}, "async_tilt_open")
platform.async_register_entity_service(SVC_TILT_CLOSE, {}, "async_tilt_close")


class ESPSomfyShade(ESPSomfyEntity, CoverEntity):
Expand All @@ -62,10 +73,13 @@ def __init__(self, controller: ESPSomfyController, data):
self._controller = controller
self._shade_id = data["shadeId"]
self._position = data["position"]
self._tilt_postition = 100
self._tilt_direction = 0
self._attr_unique_id = f"{controller.unique_id}_{self._shade_id}"
self._attr_name = data["name"]
self._direction = 0
self._available = True
self._has_tilt = False

self._attr_device_class = CoverDeviceClass.SHADE
self._attr_supported_features = (
Expand All @@ -74,6 +88,24 @@ def __init__(self, controller: ESPSomfyController, data):
| CoverEntityFeature.STOP
| CoverEntityFeature.SET_POSITION
)
if "hasTilt" in data and data["hasTilt"] is True:
self._attr_supported_features |= (
CoverEntityFeature.OPEN_TILT
| CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.SET_TILT_POSITION
)
self._has_tilt = True
self._tilt_postition = data["tiltPosition"] if "tiltPosition" in data else 100
self._tilt_direction = data["tiltDirection"] if "tiltDirecion" in data else 0
if "shadeType" in data:
match data["shadeType"]:
case 1:
self._attr_device_class = CoverDeviceClass.BLIND
case 2:
self._attr_device_class = CoverDeviceClass.CURTAIN
case _:
self._attr_device_class = CoverDeviceClass.SHADE


self._attr_is_closed: bool = False
# print(f"Set up shade {self._attr_unique_id} - {self._attr_name}")
Expand All @@ -87,6 +119,13 @@ def _handle_coordinator_update(self) -> None:
self._position = int(self._controller.data["position"])
if "direction" in self._controller.data:
self._direction = int(self._controller.data["direction"])
if "hasTilt" in self._controller.data:
self._has_tilt = self._controller.data["hasTilt"]
if self._has_tilt is True:
if "tiltDirection" in self._controller.data:
self._tilt_direction = int(self._controller.data["tiltDirection"])
if "tiltPosition" in self._controller.data:
self._tilt_postition = int(self._controller.data["tiltPosition"])
self._available = True
elif self._controller.data["event"] == EVT_SHADEREMOVED:
self._available = False
Expand All @@ -109,9 +148,15 @@ def should_poll(self) -> bool:
return False

@property
def current_cover_position(self) -> int:
def current_cover_position(self) -> int | None:
"""Return the current position of the shade."""
return 100 - self._position
@property
def current_cover_tilt_position(self) -> int | None:
"""Return current position of cover tilt. 0 is closed, 100 is open."""
if not self._has_tilt:
return None
return 100 - self._tilt_postition

@property
def is_opening(self) -> bool:
Expand All @@ -133,6 +178,19 @@ def is_open(self) -> bool:
"""Return true if cover is closed."""
return self._position == 0

async def async_set_cover_tilt_position(self, **kwargs: Any) -> None:
"""Set the tilt postion"""
await self._controller.api.position_tilt(
self._shade_id, 100 - kwargs[ATTR_TILT_POSITION]
)
async def async_open_cover_tilt(self, **kwargs: Any) -> None:
"""Open the tilt position"""
await self._controller.api.tilt_open(self._shade_id)

async def async_close_cover_tilt(self, **kwargs: Any) -> None:
"""Close the tilt position"""
await self._controller.api.tilt_close(self._shade_id)

async def async_set_cover_position(self, **kwargs: Any) -> None:
"""Set the cover position."""
await self._controller.api.position_shade(
Expand Down
2 changes: 1 addition & 1 deletion custom_components/espsomfy_rts/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@
"dependencies": [],
"codeowners": ["@rstrouse"],
"iot_class": "local_push",
"version": "1.0.0"
"version": "1.0.8"
}
31 changes: 31 additions & 0 deletions custom_components/espsomfy_rts/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,37 @@ set_shade_postion:
integration: espsomfy_rts
domain: cover

tilt_open:
name: Tilt Open
description: Tilts the slats open
target:
integration: espsomfy_rts
domain: cover

tilt_close:
name: Tilt Close
description: Tilts the slats closed
target:
integration: espsomfy_rts
domain: cover

set_tilt_postion:
name: Set Position
description: Sets the tilt position to % of open
fields:
position:
name: position
description: Percentage of open for the shade slats
required: true
example: 50
selector:
text:
target:
entity:
integration: espsomfy_rts
domain: cover





0 comments on commit eefe782

Please sign in to comment.