Can you see an error in the Home Assistan log page or within the home-assistant.log file?
Log Details (ERROR)
Thu Dec 20 2018 17:28:06 GMT+0100 (CET)
Error handling request
Traceback (most recent call last):
File "/usr/local/lib/python3.6/site-packages/aiohttp/web_protocol.py", line 390, in start
resp = await self._request_handler(request)
File "/usr/local/lib/python3.6/site-packages/aiohttp/web_app.py", line 366, in _handle
resp = await handler(request)
File "/usr/local/lib/python3.6/site-packages/aiohttp/web_middlewares.py", line 106, in impl
return await handler(request)
File "/usr/local/lib/python3.6/site-packages/homeassistant/components/http/static.py", line 66, in staticresource_middleware
return await handler(request)
File "/usr/local/lib/python3.6/site-packages/homeassistant/components/http/real_ip.py", line 34, in real_ip_middleware
return await handler(request)
File "/usr/local/lib/python3.6/site-packages/homeassistant/components/http/ban.py", line 67, in ban_middleware
return await handler(request)
File "/usr/local/lib/python3.6/site-packages/homeassistant/components/http/auth.py", line 99, in auth_middleware
return await handler(request)
File "/usr/local/lib/python3.6/site-packages/homeassistant/components/http/view.py", line 115, in handle
result = handler(request, **request.match_info)
File "/config/custom_components/somfy/__init__.py", line 111, in get
self.request_token(str(request.url))
File "/config/deps/lib/python3.6/site-packages/pymfy/api/somfy_api.py", line 49, in request_token
client_secret=self.client_secret)
File "/usr/local/lib/python3.6/site-packages/requests_oauthlib/oauth2_session.py", line 187, in fetch_token
state=self._state)
File "/usr/local/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/web_application.py", line 174, in parse_request_uri_response
response = parse_authorization_code_response(uri, state=state)
File "/usr/local/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 221, in parse_authorization_code_response
raise InsecureTransportError()
oauthlib.oauth2.rfc6749.errors.InsecureTransportError: (insecure_transport) OAuth 2 MUST utilize https.
Apparently it seems you have configured a non https URL in your configuration or within the Somfy developper portal.
I have updated component on my repo, you can download the new version if you want.
### Somfy ###
somfy:
client_id: !secret somfy_id
client_secret: !secret somfy_secret
I have this in my config, do I need more?
To avoid noise on this topic, I sent you a PM.
Hi, I’ve added the support for the tilt position with my last changes: https://github.com/tetienne/home-assistant/commit/8b1231b9e238783b6aa3719f066002391938f34b. So once my component available you will be able to test There is one possible issue, home assistant use the value 100 when the blind is fully opened, I don’t know if it’s the same thing for Somfy. So if I’m lucky we are good, otherwise the command will be reverse.
Hello, can You Tell me, how can i install it to hassio with raspberry? I realy want try to tilt.
Thx to @gieljnssns I was able to fix an issue with my component related to the the caddy addon (reverse proxy).
@Bojkas For the moment you will have to create a custom component. Create a file __init__.py
at /config/custom_components/somfy
with this content and a file somfy.py
at /config/custom_components/cover
with this content.
On the Somfy API website, log in using your Tahoma/Conexoon account. Open the My Apps menu and Add a new App where the redirect URI is <your HASS URL>/auth/somfy/callback
Update your configuration.yaml file with
http:
base_url: <your HASS url>
somfy:
client_id: <Consumer Key>
client_secret: <Consumer Secret>
Reboot HASS. Back to the home page you will a notification message inviting you to click on a link. Once Somfy approval done, you will have access to your covers.
@pbavinck Can you have a look too?
You can follow the progress of this component looking at the pull request on home assistant repository.
Hello,
i do exactly what you wrote, but:
From log
Setup failed for somfy: Component not found.
Unable to find component somfy
In notification start screen
the following components and platforms could not be set up
I created file
/config/custom_components/somfy/somfy.py
and
/config/custom_components/cover/init.py
Hassio created pycache/sonoff.cpython-36.pyc
Configuration validation:
Component not found: somfy
Any idea?
You created the files at the wrong path. You switched them. Double check my post.
And you also have to changes this 2 lines
# from homeassistant.components.somfy import DOMAIN, SomfyEntity
from custom_components.somfy import DOMAIN, SomfyEntity
in the /config/custom_components/cover/somfy.py
file
I wrote bad. In folder somfy have init.py and in cover folder have somfy.py .
gieljnssns ok, i try IT.
And what homeassistant.components.cover import CoverDevice, ATTR_POSITION,
ATTR_TILT_POSITION is ok?
Edit: doesnt work, still component not found.
Hi everyone. First, many thanks @tetienne for your great work.
I read every post of this thread, trying to make this working on my config (homeassistant in Docker on Synology NAS).
I registered on Somy Dev site and made a new App (and tested it with the online check from Somfy)
I installed pymfy 0.4.3, i made the folders and files for the custom components, i changed the “from” section of Somfy.py …
but i’m in the same case of Bojkas
2018-12-26 16:23:05 ERROR (MainThread) [homeassistant.loader] Unable to find component somfy
2018-12-26 16:23:05 ERROR (MainThread) [homeassistant.setup] Setup failed for somfy: Component not found.
One thing is that my HomeAssistant is not publicly available, so the Callback URL is not resolvable from the internet side.
I would like to help you make this component perfect for somfy users.
Is there a way to have more detailed log in HA ?
Arnaud
PS : i’m also French @tetienne but for universality it seems obvious that we need to exchange in english
@koomik (Bonjour Arnaud )I’m glad to see your are interest by this component. If a lot of people use it, it will send to Somfy a good message and show them they did the good choice by creating this API. By the way, which devices do you have? Only cover?
About your question, the initial setup need your installation to be accessible from the outside. This is due to the Oauth2 protocol. But I can see a workaround. You will have to create a file .somfy in your configuration folder which look like.
{"scope": ["user.basic", "api.full", "oa.site", "oa.user", "oa.device", "oa.devicedefinition", "level.0"], "access_token": "xxxxxxx", "token_type": "bearer", "expires_in": 3600, "expires_at": 1545863003.3705792, "refresh_token": "yyyyyy"}
@koomik @Bojkas Can you show me the full path of the two files I gave you? I tested from scratch on a fresh home assistant installation without any issue. Did you apply the @gieljnssns’s remark?
config/custom_components/cover/somfy.py
config/custom_components/somfy/init.py
Config folder Is that with config.yaml
somfy.py
“”"
Support for Somfy Covers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.somfy/
"""
from homeassistant.components.cover import CoverDevice, ATTR_POSITION, \
ATTR_TILT_POSITION
from custom_components.somfy import DOMAIN, SomfyEntity, DEVICES
DEPENDENCIES = ['somfy']
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Somfy cover platform."""
from pymfy.api.devices.category import Category
categories = {Category.ROLLER_SHUTTER.value, Category.INTERIOR_BLIND.value,
Category.EXTERIOR_BLIND.value}
devices = hass.data[DOMAIN][DEVICES]
for cover in devices:
if categories & set(cover.categories):
add_entities([SomfyCover(cover, hass)])
class SomfyCover(SomfyEntity, CoverDevice):
"""Representation of a Somfy cover device."""
def close_cover(self, **kwargs):
"""Close the cover."""
from pymfy.api.devices.roller_shutter import RollerShutter
RollerShutter(self.device, self.api).close()
def open_cover(self, **kwargs):
"""Open the cover."""
from pymfy.api.devices.roller_shutter import RollerShutter
RollerShutter(self.device, self.api).open()
def stop_cover(self, **kwargs):
"""Stop the cover"""
from pymfy.api.devices.roller_shutter import RollerShutter
RollerShutter(self.device, self.api).stop()
def set_cover_position(self, **kwargs):
"""Move the cover shutter to a specific position."""
position = kwargs.get(ATTR_POSITION)
from pymfy.api.devices.roller_shutter import RollerShutter
RollerShutter(self.device, self.api).set_position(100 - position)
@property
def current_cover_position(self):
"""Return the current position of cover shutter."""
position = None
try:
from pymfy.api.devices.roller_shutter import RollerShutter
shutter = RollerShutter(self.device, self.api)
position = 100 - shutter.get_position()
except StopIteration:
pass
return position
@property
def is_closed(self):
"""Return if the cover is closed."""
is_closed = None
try:
from pymfy.api.devices.roller_shutter import RollerShutter
is_closed = RollerShutter(self.device, self.api).is_closed()
except StopIteration:
pass
return is_closed
@property
def current_cover_tilt_position(self):
"""Return current position of cover tilt.
None is unknown, 0 is closed, 100 is fully open.
"""
orientation = None
try:
from pymfy.api.devices.blind import Blind
orientation = Blind(self.device, self.api).orientation
except StopIteration:
pass
return orientation
def set_cover_tilt_position(self, **kwargs):
"""Move the cover tilt to a specific position."""
orientation = kwargs.get(ATTR_TILT_POSITION)
from pymfy.api.devices.blind import Blind
Blind(self.device, self.api).orientation = orientation
def open_cover_tilt(self, **kwargs):
"""Open the cover tilt."""
from pymfy.api.devices.blind import Blind
Blind(self.device, self.api).orientation = 100
def close_cover_tilt(self, **kwargs):
"""Close the cover tilt."""
from pymfy.api.devices.blind import Blind
Blind(self.device, self.api).orientation = 0
def stop_cover_tilt(self, **kwargs):
"""Stop the cover."""
from pymfy.api.devices.blind import Blind
Blind(self.device, self.api).stop()
init.py
“”"
Support for Somfy hubs.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/somfy/
"""
import logging
from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.http import HomeAssistantView
from homeassistant.core import callback
from homeassistant.helpers import discovery
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
API = 'api'
DEVICES = 'devices'
REQUIREMENTS = ['pymfy==0.4.3']
_LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10)
DOMAIN = 'somfy'
CONF_CLIENT_ID = 'client_id'
CONF_CLIENT_SECRET = 'client_secret'
NOTIFICATION_CB_ID = 'somfy_cb_notification'
NOTIFICATION_OK_ID = 'somfy_ok_notification'
NOTIFICATION_TITLE = 'Somfy Setup'
ATTR_ACCESS_TOKEN = 'access_token'
ATTR_REFRESH_TOKEN = 'refresh_token'
ATTR_CLIENT_ID = 'client_id'
ATTR_CLIENT_SECRET = 'client_secret'
SOMFY_AUTH_CALLBACK_PATH = '/auth/somfy/callback'
SOMFY_AUTH_START = '/auth/somfy'
DEFAULT_CACHE_PATH = '.somfy'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_CLIENT_ID): cv.string,
vol.Required(CONF_CLIENT_SECRET): cv.string
})
}, extra=vol.ALLOW_EXTRA)
SOMFY_COMPONENTS = ['cover']
def setup(hass, config):
"""Set up the Somfy component."""
from pymfy.api.somfy_api import SomfyApi
hass.data[DOMAIN] = {}
# This is called to create the redirect so the user can Authorize Home .
redirect_uri = '{}{}'.format(
hass.config.api.base_url, SOMFY_AUTH_CALLBACK_PATH)
conf = config[DOMAIN]
api = SomfyApi(conf.get(CONF_CLIENT_ID),
conf.get(CONF_CLIENT_SECRET),
redirect_uri, hass.config.path(DEFAULT_CACHE_PATH))
hass.data[DOMAIN][API] = api
if not api.token:
authorization_url, _ = api.get_authorization_url()
hass.components.persistent_notification.create(
'In order to authorize Home Assistant to view your Somfy devices'
' you must visit this <a href="{}" target="_blank">link</a>.'
.format(authorization_url),
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_CB_ID
)
hass.http.register_view(SomfyAuthCallbackView(config))
else:
update_all_devices(hass)
for component in SOMFY_COMPONENTS:
discovery.load_platform(hass, component, DOMAIN, {}, config)
return True
class SomfyAuthCallbackView(HomeAssistantView):
"""Handle OAuth finish callback requests."""
url = SOMFY_AUTH_CALLBACK_PATH
name = 'auth:somfy:callback'
requires_auth = False
def __init__(self, config):
"""Initialize the OAuth callback view."""
self.config = config
@callback
def get(self, request):
"""Finish OAuth callback request."""
from aiohttp import web
from oauthlib.oauth2 import MismatchingStateError
from oauthlib.oauth2 import InsecureTransportError
hass = request.app['hass']
response = web.HTTPFound('/')
try:
code = request.query.get('code')
hass.data[DOMAIN][API].request_token(code=code)
hass.async_add_job(setup, hass, self.config)
hass.components.persistent_notification.dismiss(NOTIFICATION_CB_ID)
hass.components.persistent_notification.create(
"Somfy has been successfully authorized!",
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_CB_ID
)
except MismatchingStateError:
_LOGGER.error("OAuth state not equal in request and response.",
exc_info=True)
except InsecureTransportError:
_LOGGER.error("Somfy redirect URI %s is insecure.", request.url,
exc_info=True)
return response
class SomfyEntity(Entity):
"""Representation of a generic Somfy device."""
def __init__(self, device, hass):
"""Initialize the Somfy device."""
self.hass = hass
self.device = device
self.api = hass.data[DOMAIN][API]
@property
def unique_id(self):
"""Return the unique id base on the id returned by Somfy."""
return self.device.id
@property
def name(self):
"""Return the name of the device."""
return self.device.name
def update(self):
"""Update the device with the latest data."""
update_all_devices(self.hass)
devices = self.hass.data[DOMAIN][DEVICES]
self.device = next((d for d in devices if d.id == self.device.id),
self.device)
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update_all_devices(hass):
"""Update all the devices."""
from requests import HTTPError
try:
data = hass.data[DOMAIN]
data[DEVICES] = data[API].get_devices()
except HTTPError:
_LOGGER.warning("Cannot update devices.", exc_info=True)
__init__.py
File must be init.py? No init.py?
Yeeeehaaaaa
So, many thanks @gieljnssns for pointing us that init.py need to be with 2 underscores before and after the “init”
@tetienne for information, i’ve put the internal URL of my HA setup in the callback URL and it seems to work.
I’ll test it now
That’s great. Sorry for my typo. I think I was tired by the Christmas meal I’ve updated my post.
Nice to see it can work also when your instance is offline. Tell me if you see any error in the log or a strange behavior. It can be due to my code or a bug on Somfy side.