Connecting Somfy blinds to Alexa with a Raspberry Pi

I thought it would be nice to be able to control the blinds using Alexa, but unfortunately my blinds are too old and not readily compatible with Alexa.

In this article I will show how I used a Raspberry Pi together with a CC1101 433 MHz transmitter module to emulate the remote of my dumb blinds, and integrate it with Alexa so I can open and close the blinds using voice commands.

In a previous article, I analyzed the 433 MHz signal emitted by the remote of my Somfy blinds, and built a synthetic version of the signal I could replay using HackRF. We'll use that knowledge here.

Overview of the setup

We're going to have a Raspberry Pi connected to the internet, with a CC1101 module attached to it, which will give it the ability to transmit 433 MHz signals.

On the other hand, we'll configure Alexa to communicate with the Raspberry Pi over the internet when it receives voice commands for opening and closing the blinds.

For the communication part, we'll set up a tiny web server which both Alexa and the Raspberry Pi will connect to. This web server needs to be publicly accessible from the Internet, so Alexa can reach it. We'll use an Alexa plugin called URL Switch which someone already implemented: with this, we can easily configure Alexa to send a POST request to a URL on our web server on specific voice commands.

This is what the entire setup looks like:

Setup overview

You may be wondering: why can't URL Switch talk to the Raspberry Pi directly? In other words, why can't the Raspberry Pi be a Web Server itself? That would be a valid design, and probably simpler too. However, in my particular setup, the Raspberry Pi is behind a router which I don't control, and I am unable to open/redirect ports as needed to publicly expose a Web Server.

Hardware needed

This is the full list of hardware I used for this project:

Connecting the Raspberry Pi to the C1101 module

The CC1101 module uses SPI for communication, and it needs to connect to the corresponding SPI pins of the Raspberry Pi. No matter which version you have, they all have the same 40-pin layout.

This is how I made the connections:

CC1101 PinCC1101 SignalRaspberry Pi PinRaspberry Pi Signal
Pin 1GNDPin 20GND
Pin 2VCC (1.8V - 3.6V)Pin 173v3 Power
Pin 3GDO0Pin 18GPIO 24
Pin 4CSNPin 24GPIO 8 / SMI SD0
Pin 5SCKPin 23GPIO 11 / SMI SD3
Pin 6MOSIPin 19GPIO 10 / SMI SD2
Pin 7MISO / GDO1Pin 21GPIO 9 / SPI0 MISO
Pin 8GDO2Pin 22GPIO 25

I've seen 2 slightly different versions of the CC1101 module, one with 8 pins and one with 10 pins. They're almost the same, the 10-pin one just has duplicate VCC and GND. Mine is like this one and it has 8 pins:

This is what the connection ended up looking like:

Connection between Raspberry Pi and CC1101

Transmitting the RF signal using Python

After some research of different options in C/C++ and Python, I ended up using a library appropriately called python-cc1101. It worked great, except for the very annoying fact that there isn't a clean way of waiting for a transmission to finish before starting another one. As a workaround, I implemented a hacky method that waits for "idle" status.

This is how I put it together:

import cc1101
import time

frequency = 433410000 # 433.410 Mhz
pulse_duration = 642e-6
baud_rate = 1 / pulse_duration
transmit_power = 56
repeat = 5
pause = 0.02 # 20 ms

def wait_for_idle(transceiver):
    while True:
        try:
            state = transceiver.get_main_radio_control_state_machine_state()
        except ValueError:
            continue

        if state == cc1101.MainRadioControlStateMachineState.IDLE:
            break


def transmit_data(data: bytes):
    with cc1101.CC1101() as transceiver:
        transceiver.set_base_frequency_hertz(frequency)
        transceiver.set_symbol_rate_baud(baud_rate)
        transceiver.set_sync_mode(cc1101.SyncMode.NO_PREAMBLE_AND_SYNC_WORD)
        transceiver.set_packet_length_mode(cc1101.PacketLengthMode.FIXED)
        transceiver.set_packet_length_bytes(4)
        transceiver.disable_checksum()
        transceiver.set_output_power((0, transmit_power))
        transceiver.set_packet_length_bytes(len(data))

        for _ in range(repeat):
            transceiver.transmit(data)
            wait_for_idle(transceiver)
            time.sleep(pause)

Triggering the transmission from server-sent events

Continuation of the previous script, but now we add logic for listening to SSE from our Web Server:

import time
import logging
import requests
import sseclient

events_url = "https://example.com/my-sse-endpoint"

preamble = [
  1,1,1,1,0,0,0,0,
  1,1,1,1,0,0,0,0,
  1,1,1,1,0,0,0,0,
  1,1,1,1,0,0,0,0,
  1,1,1,1,0,0,0,0,
  1,1,1,1,0,0,0,0,
  1,1,1,1,0,0,0,0,
  1,1,1,1,1,1,1,1,
]
payload_up = [...]
payload_down = [...]

data_up = preamble + payload_up
data_down = preamble + payload_down

event_data = {
    'blinds-up': data_up,
    'blinds-down': data_down,
}

while True:
  try:
    with requests.get(events_url, stream=True, timeout=30) as r:
      client = sseclient.SSEClient(r)
      for event in client.events():
        name = event.event
        bits = event_data.get(name)
        if bits is not None:
            logging.info(f"Event: {name}")
            data = bits_to_bytes(bits)
            transmit_data(data)
  except Exception as e:
    logging.error(f"Error: {e}")
    time.sleep(1)

You may download the full script here: blinds-daemon.py

Setting up the Web Server

I created a simple web server using NextJS. In particular, I created 2 routes:

When the first endpoint gets called, the second one will publish a new event immediately. There are many ways of doing this and I won't go into detail.

Configuring URL Switch

URL Switch is easy peasy to set up. You have to enable the plugin from the Alexa app, and then create a virtual lightbulb that connects to your URL. Just follow the instructions on the website.