Skip to content

feat: Add Somfy RTS integration#169920

Draft
L-Henke wants to merge 14 commits into
home-assistant:devfrom
L-Henke:feat/add-somfy-rts-component
Draft

feat: Add Somfy RTS integration#169920
L-Henke wants to merge 14 commits into
home-assistant:devfrom
L-Henke:feat/add-somfy-rts-component

Conversation

@L-Henke
Copy link
Copy Markdown
Contributor

@L-Henke L-Henke commented May 6, 2026

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_frequency component.

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Deprecation (breaking change to happen in the future)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

Checklist

  • I understand the code I am submitting and can explain how it works.
  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • I have followed the perfect PR recommendations
  • The code has been formatted using Ruff (ruff format homeassistant tests)
  • Tests have been added to verify that the new code works.
  • Any generated code has been carefully reviewed for correctness and compliance with project standards.

If user exposed functionality or configuration variables are added/changed:

If the code communicates with devices, web services, or third-party tools:

  • The manifest file has all fields filled out correctly.
    Updated and included derived files by running: python3 -m script.hassfest.
  • New or updated dependencies have been added to requirements_all.txt.
    Updated by running python3 -m script.gen_requirements_all.
  • For the updated dependencies a diff between library versions and ideally a link to the changelog/release notes is added to the PR description.

To help with the load of incoming pull requests:

Copy link
Copy Markdown
Contributor

@home-assistant home-assistant Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@home-assistant home-assistant Bot marked this pull request as draft May 6, 2026 14:36
@home-assistant
Copy link
Copy Markdown
Contributor

home-assistant Bot commented May 6, 2026

Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍

Learn more about our pull request process.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 cover and button platforms, 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.

Comment on lines +98 to +102
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
Comment on lines +35 to +46
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:
@L-Henke
Copy link
Copy Markdown
Contributor Author

L-Henke commented May 6, 2026

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.

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.

@astrandb
Copy link
Copy Markdown
Contributor

astrandb commented May 7, 2026

I am happy to see that this intergration is on it's way. I am willing to help with code initial review.
But first I would like to see that it works with my Somfy awning and Telis Soliris RTS. I have a Broadlink RM4C Pro that works fine with other integrations. I have installed this integration and everything looks good and the Broadlink blinks nicely when buttons are pressed. But pairing fails. I enter pairing mode on the old remote and the awning jogs. Nothing happens when I press the PROG button in HA. The motor returns to normal mode after 2-3 minutes. Any hints how to proceed with debugging?

@joostlek
Copy link
Copy Markdown
Member

joostlek commented May 7, 2026

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.

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.

Wouldn't it make more sense to have that in the config flow?

@L-Henke
Copy link
Copy Markdown
Contributor Author

L-Henke commented May 8, 2026

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.

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.

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.

@L-Henke
Copy link
Copy Markdown
Contributor Author

L-Henke commented May 8, 2026

I am happy to see that this intergration is on it's way. I am willing to help with code initial review. But first I would like to see that it works with my Somfy awning and Telis Soliris RTS. I have a Broadlink RM4C Pro that works fine with other integrations. I have installed this integration and everything looks good and the Broadlink blinks nicely when buttons are pressed. But pairing fails. I enter pairing mode on the old remote and the awning jogs. Nothing happens when I press the PROG button in HA. The motor returns to normal mode after 2-3 minutes. Any hints how to proceed with debugging?

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.

@L-Henke
Copy link
Copy Markdown
Contributor Author

L-Henke commented May 8, 2026

@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.

@abmantis
Copy link
Copy Markdown
Member

abmantis commented May 8, 2026

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.

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.

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.

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.

@L-Henke
Copy link
Copy Markdown
Contributor Author

L-Henke commented May 8, 2026

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.

@L-Henke
Copy link
Copy Markdown
Contributor Author

L-Henke commented May 8, 2026

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.

@astrandb
Copy link
Copy Markdown
Contributor

astrandb commented May 8, 2026

you might try to bring the Broadlink device really close to the blinds and see if you are able to pair it.

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.
I think your comment is relevant regarding the risk for filling up the id slots. I have seen a repair guy climbing on a ladder doing multiple factory resets on 10 windows in my neighbour's house. It took him many hours to get everything in sync again... I recommend a clear caution message in the config flow form so that the user don't retries with different ids without understanding the consequences.

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.

@balloob
Copy link
Copy Markdown
Member

balloob commented May 11, 2026

Somfy RTS codes included in #170301

Copy link
Copy Markdown
Member

@balloob balloob left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Limit new integrations to a single platform.

@DamitusThyYeetus123
Copy link
Copy Markdown

DamitusThyYeetus123 commented May 11, 2026

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.

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.

@L-Henke
Copy link
Copy Markdown
Contributor Author

L-Henke commented May 11, 2026

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.

@joostlek
Copy link
Copy Markdown
Member

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

@DamitusThyYeetus123
Copy link
Copy Markdown

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

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Comment thread homeassistant/components/somfy_rts/config_flow.py Outdated
Comment on lines +17 to +18
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>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is stored if it's not a dict? Can you just use or entry_default ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rolling codes are stored in a Store

Comment thread homeassistant/components/somfy_rts/config_flow.py
Comment on lines +130 to +131
data.rolling_code = rolling_code
await data.store.async_save({"rolling_code": data.rolling_code})
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to use button.to_command(...)

from homeassistant.helpers.storage import Store


class SomfyRTSData:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +5 to +6
"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."
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use a reference to the honeywell integration translation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should that be done when the two newly featured RF integrations honeywell and novy don't link to each others strings? Shouldn't the no_transmitter and no_compatible_transmitter then be offered as a common key? Because that would make a lot of sense, but is outside of this PR.

"user": {
"data": {
"address": "Remote address",
"transmitter": "Radio frequency transmitter"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use a reference to the honeywell integration translation.

As suggested by @astrandb

Co-authored-by: Åke Strandberg <ake@strandberg.eu>
Copilot AI review requested due to automatic review settings May 11, 2026 19:06
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

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.",
@astrandb
Copy link
Copy Markdown
Contributor

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.

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 have sent a support question to Broadlink in China to try to get info on if and how the transmit frequency can be controlled. Let us hope for a positive answer.

@astrandb
Copy link
Copy Markdown
Contributor

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.

@balloob
Copy link
Copy Markdown
Member

balloob commented May 12, 2026

If the broadlink transmitter does not support a frequency, it should not tell HAnit supports it so it won't be user selectable.

@HarmEllis
Copy link
Copy Markdown

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.

@astrandb
Copy link
Copy Markdown
Contributor

If the broadlink transmitter does not support a frequency, it should not tell HAnit supports it so it won't be user selectable.

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.

@muddyfeet
Copy link
Copy Markdown

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.

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.

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.

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() {
ESP_LOGI(TAG, "PROG");
sendCC1101Command(Command::Prog,1);
}
void long_program() {
ESP_LOGI(TAG, "LONG_PROG");
sendCC1101Command(Command::Prog,15);
}
void tilt_up() {
ESP_LOGI("somfy", "TILT_UP");
sendCC1101Command(Command::Up,3);
}
void tilt_down() {
ESP_LOGI("somfy", "TILT_DOWN");
sendCC1101Command(Command::Down,3);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants