Skip to content

Commit

Permalink
Merge pull request #12 from pink88/dev
Browse files Browse the repository at this point in the history
Merge latest changes for STOP command
  • Loading branch information
pink88 authored Feb 28, 2024
2 parents 6569901 + fb93a72 commit 8728f35
Show file tree
Hide file tree
Showing 12 changed files with 273 additions and 132 deletions.
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"python.testing.pytestArgs": [
"custom_components"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ To get started you need to download and use the Tuiss Smartview app to set the u
- Set position
- Open
- Close
- Stop
- Battery State (through service)
- Blind position (through service)

Expand Down Expand Up @@ -66,7 +67,7 @@ The blind will not update its position within Home Assistant if controlled using


## Limitations ##
- Currently the opening and closing status of the blind are not included, it will only report on if the blind is Open and Closed, not while it is moving
- Setting the top and bottom thresholds of the blind. Currently, you still need to pair with and use the Tuiss app to set these values.


## Troubleshooting ##
Expand Down
54 changes: 29 additions & 25 deletions custom_components/tuiss2ha/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@

from homeassistant import config_entries, exceptions
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResult

from .const import DOMAIN
from .const import CONF_BLIND_MAC, CONF_BLIND_NAME, DOMAIN

_LOGGER = logging.getLogger(__name__)

DATA_SCHEMA = vol.Schema(
STEP_DATA_SCHEMA = vol.Schema(
{
vol.Required("host", default="XX:XX:XX:XX:XX:XX"): str,
vol.Required("name", default="Name for device"): str,
vol.Required(CONF_BLIND_MAC): str,
vol.Required(CONF_BLIND_NAME): str,
}
)

Expand All @@ -27,36 +28,39 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_ASSUMED

async def async_step_user(self, user_input=None):
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step."""

errors = {}
if user_input is not None:
try:
_title = await validate_input(self.hass, user_input)

return self.async_create_entry(title=_title, data=user_input)
except CannotConnect:
errors["name"] = "Cannot connect"
except InvalidHost:
errors[
"host"
] = "Your host should be a valid MAC address in the format XX:XX:XX:XX:XX:XX"
except InvalidName:
errors["name"] = "Your name must be longer than 0 characters"
except Exception:
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
if user_input is None:
return self.async_show_form(step_id="user", data_schema=STEP_DATA_SCHEMA)

try:
_title = await validate_input(self.hass, user_input)
except CannotConnect:
errors["name"] = "Cannot connect"
except InvalidMac:
errors["mac"] = "Your mac address must be in the format XX:XX:XX:XX:XX:XX"
except InvalidName:
errors["name"] = "Your name must be longer than 0 characters"
except Exception:
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
return self.async_create_entry(title=_title, data=user_input)

return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA, errors=errors
step_id="user", data_schema=STEP_DATA_SCHEMA, errors=errors
)


async def validate_input(hass: HomeAssistant, data: dict) -> dict[str, Any]:
"""Validate the user input allows us to connect."""

if len(data["host"]) < 17:
raise InvalidHost
if len(data["mac"]) < 17:
raise InvalidMac

if len(data["name"]) == 0:
raise InvalidName
Expand All @@ -68,7 +72,7 @@ class CannotConnect(exceptions.HomeAssistantError):
"""Error to indicate we cannot connect."""


class InvalidHost(exceptions.HomeAssistantError):
class InvalidMac(exceptions.HomeAssistantError):
"""Error to indicate there is an invalid hostname."""


Expand Down
8 changes: 7 additions & 1 deletion custom_components/tuiss2ha/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,11 @@

# name for the integration.
DOMAIN = "tuiss2ha"
BATTERY_NOTIFY_CHARACTERISTIC = "00010304-0405-0607-0809-0a0b0c0d1910"

CONF_BLIND_MAC = "mac"
CONF_BLIND_NAME = "name"


BLIND_NOTIFY_CHARACTERISTIC = "00010304-0405-0607-0809-0a0b0c0d1910"
CONNECTION_MESSAGE = "ff03030303787878787878"
UUID = "00010405-0405-0607-0809-0a0b0c0d1910"
71 changes: 53 additions & 18 deletions custom_components/tuiss2ha/cover.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
"""Platform for cover integration."""
from __future__ import annotations

import asyncio
import logging

from typing import Any

from homeassistant.components.cover import (
Expand All @@ -11,14 +14,15 @@
CoverEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import STATE_CLOSED, STATE_OPEN
from homeassistant.const import STATE_CLOSED, STATE_OPEN, STATE_OPENING, STATE_CLOSING
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_platform
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity

from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)

async def async_setup_entry(
hass: HomeAssistant,
Expand All @@ -39,7 +43,7 @@ async def async_setup_entry(
async def async_get_blind_position(entity, service_call):
"""Get the battery status when called by service."""
await entity._blind.get_blind_position()
entity._current_cover_position = entity._blind._current_cover_position
#entity._current_cover_position = entity._blind._current_cover_position
entity.schedule_update_ha_state()


Expand All @@ -52,13 +56,17 @@ def __init__(self, blind) -> None:
self._attr_unique_id = f"{self._blind._id}_cover"
self._attr_name = self._blind.name
self._state = None
self._current_cover_position: None
self._moving = 0
#self._current_cover_position: None


@property
def state(self):
"""Set state of object."""
if self._current_cover_position >= 25:
if self._blind._moving > 0:
self._state = STATE_OPENING
elif self._blind._moving < 0:
self._state = STATE_CLOSING
elif self._blind._moving == 0 and self._blind._current_cover_position >= 25:
self._state = STATE_OPEN
else:
self._state = STATE_CLOSED
Expand All @@ -82,16 +90,16 @@ def available(self) -> bool:
@property
def current_cover_position(self):
"""Return the current position of the cover."""
if self._current_cover_position is None:
if self._blind._current_cover_position is None:
return None
return self._current_cover_position
return self._blind._current_cover_position

@property
def is_closed(self) -> bool | None:
"""Return if the cover is closed or not."""
if self._current_cover_position is None:
if self._blind._current_cover_position is None:
return None
return self._current_cover_position == 0
return self._blind._current_cover_position == 0

@property
def supported_features(self):
Expand All @@ -100,6 +108,7 @@ def supported_features(self):
CoverEntityFeature.OPEN
| CoverEntityFeature.CLOSE
| CoverEntityFeature.SET_POSITION
| CoverEntityFeature.STOP
)

@property
Expand All @@ -121,9 +130,9 @@ async def async_added_to_hass(self) -> None:
"""Run when this Entity has been added to HA."""
last_state = await self.async_get_last_state()
if not last_state or ATTR_CURRENT_POSITION not in last_state.attributes:
self._current_cover_position = 0
self._blind._current_cover_position = 0
else:
self._current_cover_position = last_state.attributes.get(
self._blind._current_cover_position = last_state.attributes.get(
ATTR_CURRENT_POSITION
)
self._blind.register_callback(self.async_write_ha_state)
Expand All @@ -136,22 +145,48 @@ async def async_open_cover(self, **kwargs: Any) -> None:
"""Open the cover."""
await self._blind.attempt_connection()
if self._blind._client.is_connected:
self._blind._moving = 1
await self.async_scheduled_update_request()
await self._blind.set_position(0)
self._current_cover_position = 100
self.schedule_update_ha_state()
while self._blind._client.is_connected:
await asyncio.sleep(1)
await self._blind.check_connection()
await self.async_scheduled_update_request()


async def async_close_cover(self, **kwargs: Any) -> None:
"""Close the cover."""
await self._blind.attempt_connection()
if self._blind._client.is_connected:
self._blind._moving = -1
await self.async_scheduled_update_request()
await self._blind.set_position(100)
self._current_cover_position = 0
self.schedule_update_ha_state()
while self._blind._client.is_connected:
await asyncio.sleep(1)
await self._blind.check_connection()
await self.async_scheduled_update_request()


async def async_set_cover_position(self, **kwargs: Any) -> None:
"""Close the cover."""
"""Set the cover position."""
await self._blind.attempt_connection()
if self._blind._client.is_connected:
if (self._blind._current_cover_position <= kwargs[ATTR_POSITION]):
self._blind._moving = 1
else:
self._blind._moving = -1
await self.async_scheduled_update_request()
await self._blind.set_position(100 - kwargs[ATTR_POSITION])
self._current_cover_position = kwargs[ATTR_POSITION]
self.schedule_update_ha_state()

while self._blind._client.is_connected:
await asyncio.sleep(1)
await self._blind.check_connection()
await self.async_scheduled_update_request()


async def async_stop_cover(self, **kwargs: Any) -> None:
"""Stop the cover."""
await self._blind.stop()
while self._blind._client.is_connected:
await asyncio.sleep(1)
await self.async_scheduled_update_request()
Loading

0 comments on commit 8728f35

Please sign in to comment.