feat: Add Somfy RTS integration#169920
Conversation
There was a problem hiding this comment.
When adding new integrations, limit included platforms to a single platform. Please reduce this PR to a single platform. See the review process for more details.
|
Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍 |
There was a problem hiding this comment.
Pull request overview
This PR introduces a new somfy_rts integration that controls Somfy RTS roller blinds over the radio_frequency transmitter platform using rf-protocols.
Changes:
- Adds a config flow to select an RF transmitter and configure a Somfy RTS remote address.
- Implements
coverandbuttonplatforms, including rolling-code persistence and a PROG pairing button. - Adds integration metadata/translations and a quality scale definition.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
homeassistant/components/somfy_rts/__init__.py |
Sets up config entries, initializes per-entry storage for rolling code, forwards platforms. |
homeassistant/components/somfy_rts/button.py |
Adds a PROG button entity to send pairing commands over RF. |
homeassistant/components/somfy_rts/config_flow.py |
Adds UI flow to pick a transmitter and enter a Somfy RTS remote address. |
homeassistant/components/somfy_rts/const.py |
Defines integration constants and storage version. |
homeassistant/components/somfy_rts/cover.py |
Adds a cover entity that sends UP/DOWN/MY commands and restores last known state. |
homeassistant/components/somfy_rts/entity.py |
Provides the base entity, transmitter availability tracking, and rolling-code command transmission. |
homeassistant/components/somfy_rts/manifest.json |
Declares integration metadata, dependencies, and the rf-protocols requirement. |
homeassistant/components/somfy_rts/quality_scale.yaml |
Declares quality scale rule statuses for the new integration. |
homeassistant/components/somfy_rts/strings.json |
Adds config flow strings and entity translation for the PROG button. |
| data = self._entry.runtime_data | ||
| data.rolling_code += 1 | ||
| await data.store.async_save({"rolling_code": data.rolling_code}) | ||
| command = SomfyRTSCommand( | ||
| address=self._address, |
| """Set up Somfy RTS from a config entry.""" | ||
| store = Store(hass, STORAGE_VERSION, f"{DOMAIN}/{entry.entry_id}") | ||
| stored = await store.async_load() | ||
| rolling_code = stored["rolling_code"] if stored is not None else 1 |
| async def async_step_user( | ||
| self, user_input: dict[str, Any] | None = None | ||
| ) -> ConfigFlowResult: | ||
| """Handle the initial step.""" | ||
| sample_command = SomfyRTSCommand( | ||
| address=0x1, rolling_code=0, button=SomfyRTSButton.MY | ||
| ) | ||
| try: | ||
| transmitters = async_get_transmitters( | ||
| self.hass, sample_command.frequency, sample_command.modulation | ||
| ) | ||
| except HomeAssistantError: |
That would leave the integration non-functional. I obviously need to use the cover platform to be able to expose the actual cover, but I also need the button platform to expose the prog button. Without that button, you cannot pair your "remote" to the cover and thus cannot control it. |
|
I am happy to see that this intergration is on it's way. I am willing to help with code initial review. |
Wouldn't it make more sense to have that in the config flow? |
You would also need the prog button to register or deregister a remote, so while maybe it would work initially in the config flow, you would not have the full functionality of a real somfy rts remote. |
Is that Broadlink also supported by the new RF integrations that was added in 2026.5? I only tested it with ESPHome and a CC1101. I left a comment here how basically the same code could be used directly in ESPHome. I'm using that implementation with my blinds for almost four years now, although I initially was using a Arduino lib for communication with the CC1101 that I later badly ported to ESP-IDF and now finally uses the native CC1101 component from ESPHome. I also had a really hard time when I created that setup a few years back as I had no SDR to sniff what I'm sending, so for me it was a lot of trial and error. One issue that I ran myself into multiple time was reusing an existing remote ID when I reflashed my ESP32 and thus the rolling code stored on the blind would not match the reset rolling code in the ESP32, but I guess this is not the case here. Edit: I read that the Broadlink device is also supported. I also read that the Broadlink maybe only supports 433.92 MHz, while the Somfy RTS needs 433.42 MHz. |
|
@astrandb out of curiosity I tuned my CC1101 to 433.92 MHz and I was still able to control my blinds but only if I bring my antenna really close to the roller blind. As soon as I move the antenna further away, the roller blinds will not react anymore, while they continue to work on 433.42 MHz from that distance. That somewhat matches my limited knowledge of RF that a device might still be able to retrieve information on a different sub-frequency, but with limited range/reliability as the non-matching frequency will be cutoff earlier. This is just very vague knowledge on that topic, but you might try to bring the Broadlink device really close to the blinds and see if you are able to pair it. |
That is used to register other remotes, right? If so, it is not needed for the basic functionality of the integration and could be added in a separate PR. |
Sure. I could implement that and add the entity in a later PR. The only risk that I see is that the config flow then is allowing you to pair a remote that is not saved yet. A Somfy RTS motor can have at most 12 remotes paired and if you have not persisted your remote ID and abort the config flow after paring the motor, you cannot unpair that specific remote. If the 12 slots are full with unknown IDs, you have no choice but to factory reset the motor, which is not that easy and involves repeatedly switching power for that motor for a specific time. For a fixed installation, this would mean switching the breaker in a certain pattern and hope that the motor resets. My first attempt of doing that had cost me several hours as nothing was working as expected, due to a battery backup on that motor. All in all an experience that should be avoided for end users. So maybe a future PR should move the prog button back to the entity later and remove it from the config flow as that seems to be the safer option, even if programming in the config flow might be a bit more intuitive. |
|
I looked through the config flow documentation and code and I couldn't really see a way to show a clickable button in a config flow and I also cannot remember that an integration has ever presented me something clickable that would trigger any kind of action. The only thing I could think of is loop over config pages, but maybe you have a better idea how that could be implemented. |
OK, it works when I put the Broadlink 30-50 cm from the awning motor. It is not a very practical solution in my case as the awning is on the outside and there is no natural place for a permanent installation. Anyway, It is good to see that the integration is working. I will try to find out why the device does not use the desired transmission frequency. Regarding the config flow, you could insert an extra step in the flow before creating the entry and send the PROG-signal when you press confirm/submit. My advice is to try to make a single platform design first, to get the review process running. It will not be any problems to add the button platform in a separate PR after the merge of the initial PR to dev branch and before the first release to the public. |
|
Somfy RTS codes included in #170301 |
balloob
left a comment
There was a problem hiding this comment.
Limit new integrations to a single platform.
Could this be fixed by having a deregister action that can deregister any remote based on it's ID and rolling code? Then, during configuration, it clearly states the ID it'll attempt to register with. |
I'm currently preparing all the requested changes, docs and so one. With the mentioned limitation of only one platform for new integrations I would just ignore the deregistration for now as I will simply add the prog button later and then anyone can deregister a remote the same way you would do it with a physical remote. |
|
I believe there's also a lifecycle method you can implement to run when the flow is being shut down. So if it aborts, you could send the unregister call |
|
That could work, however you might (unsure if this is only for registration) need 2 remotes (one active and one to be deregistered) to deregister a remote, so you'd need to manually put it back into program mode |
| entry_default = entry.data.get(CONF_ROLLING_CODE, 1) | ||
| rolling_code = stored.get("rolling_code", entry_default) if isinstance(stored, dict) else entry_default |
…config Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
| store = Store(hass, STORAGE_VERSION, f"{DOMAIN}/{entry.entry_id}") | ||
| stored = await store.async_load() | ||
| entry_default = entry.data.get(CONF_ROLLING_CODE, 1) | ||
| rolling_code = stored.get("rolling_code", entry_default) if isinstance(stored, dict) else entry_default |
There was a problem hiding this comment.
What is stored if it's not a dict? Can you just use or entry_default ?
There was a problem hiding this comment.
To be honest, I don't know as I don't know enough about internal error handling in HA. Initially that load was just a simple rolling_code = stored["rolling_code"] if stored is not None else 1 but then copilot commented that I should load a default to protect against missing or corrupt storage data, which seemed reasonable. Then when I implemented the suggestion, it suggested an even more defensive approach. I currently don't know how defensive the code has to be to be protected against all edge cases, but I'll find out how it is handled in other integrations.
| async def async_setup_entry(hass: HomeAssistant, entry: SomfyRTSConfigEntry) -> bool: | ||
| """Set up Somfy RTS from a config entry.""" | ||
| store = Store(hass, STORAGE_VERSION, f"{DOMAIN}/{entry.entry_id}") | ||
| stored = await store.async_load() |
There was a problem hiding this comment.
do we really need to load this at startup, can we delay this until first use? It will make startup slightly slower (loading 1 more file)
| data={ | ||
| CONF_ADDRESS: self._address, | ||
| CONF_TRANSMITTER: self._transmitter_id, | ||
| CONF_ROLLING_CODE: self._rolling_code, |
There was a problem hiding this comment.
rolling codes are stored in a Store
| data.rolling_code = rolling_code | ||
| await data.store.async_save({"rolling_code": data.rolling_code}) |
There was a problem hiding this comment.
Let's move this into the runtime data class. make it an async context handler.
async with data.get_rolling_code() as rolling_code:
...
Also, don't use async_save, but use delay function
| data = self._entry.runtime_data | ||
| async with data.lock: | ||
| rolling_code = data.rolling_code + 1 | ||
| command = SomfyRTSCommand( |
There was a problem hiding this comment.
You should be able to use button.to_command(...)
| from homeassistant.helpers.storage import Store | ||
|
|
||
|
|
||
| class SomfyRTSData: |
There was a problem hiding this comment.
bit weird this is in entity.py, it has nothing to do with it? Same for the confg entry type? that can be in const.
There was a problem hiding this comment.
I tried to follow the existing new RF integrations as closely as possible as I'm not fully familiar with the Home Assistant conventions. The Novy Cooking Hood was using that design either the entity and I followed that as the initial PR contained the cover and an additional button platform to expose the prog button and the entity.py contained the shared code. I had to remove the button platform as new components should only use one platform but the button platform should be re-added once the integration would be merged.
| "no_compatible_transmitters": "No radio frequency transmitter supports 433.42 MHz OOK transmissions. Please add a compatible transmitter first.", | ||
| "no_transmitters": "No radio frequency transmitters are available. Please set up a transmitter first." |
There was a problem hiding this comment.
use a reference to the honeywell integration translation.
There was a problem hiding this comment.
| "user": { | ||
| "data": { | ||
| "address": "Remote address", | ||
| "transmitter": "Radio frequency transmitter" |
There was a problem hiding this comment.
use a reference to the honeywell integration translation.
As suggested by @astrandb Co-authored-by: Åke Strandberg <ake@strandberg.eu>
| user_input or {}, | ||
| ), | ||
| errors=errors, | ||
| last_step = False, |
| "no_transmitters": "No radio frequency transmitters are available. Please set up a transmitter first." | ||
| }, | ||
| "error": { | ||
| "invalid_address": "Address must be a hexadecimal value between 000001 and FFFFFF.", |
I took a dive into the python-broadlink library to try to find out if it is possible to control the transmit frequency. As of now you can only select 315 MHz and 433 MHz. There are no signs of more fine-grained control. However, it is possible to set the exact receiver frequency when learning codes. The implementation in HA does not use this feature though. |
|
I just got confirmation from Broadlink that their RF-transmitters don't allow finegrained control of the transmission frequency. Perhaps you can add a note in the documentation (prerequisites and/or troubleshoooting) about this fact and hint the user that they might get an acceptable connection if they place the transmitter within 30-50 cm from the motor. |
|
If the broadlink transmitter does not support a frequency, it should not tell HAnit supports it so it won't be user selectable. |
|
Great work getting this off the ground! I've been maintaining a similar ESPHome-based Somfy RTS component (HarmEllis/esphome-somfy-cover-remote) for a while and would like to see position tracking included here also. Happy to help wherever needed. |
The issue here is that when the frequency is a little bit off, the max working distance is reduced. This may be fully acceptable for some users, while less acceptable for others. I suggest that we don't disqualify Broadcom devices from this integration but mention the potential problem in the docs - as already done. |
I would also add that many Somfy products have tilt functions in addition to the standard up, down, and stop of a cover. Tilt for Somfy louvres is implemented as an up or down command followed by a number of repeats. The number of repeats says how far to tilt. The most logical way to implement this is via seperate buttons. Likewise with the program button, a short (zero repeats) and long press (say 15 repeats) have different functions. Here are the buttons I have implemented in my esphome controller based on evgeni's code at https://github.com/evgeni/esphome-configs/tree/devel/components/somfy, where the second argument in the sendCC1101Command is the number of repeats. void program() { |
Breaking change
Proposed change
This PR adds support for Somfy RTS roller blinds via the new RF component. It requires home-assistant-libs/rf-protocols#8 to be merged.
For transparency: Claude Code was used to help creating this integration based on the new honeywell string lights integration. I could verify it working with my roller blinds and an ESPHome using the currently nightly with the
radio_frequencycomponent.Type of change
Additional information
Checklist
ruff format homeassistant tests)If user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
Updated and included derived files by running:
python3 -m script.hassfest.requirements_all.txt.Updated by running
python3 -m script.gen_requirements_all.To help with the load of incoming pull requests: