Привет, Geektimes Habr.

У меня дома скопилось несколько беспроводных выключателей на 433МГц, стало интересно, можно ли их использовать для каких-либо задач, например для управления компьютером или для интегрирования в систему «умного дома».

Эти выключатели удобны своей дешевизной и стабильной работой, выглядят они примерно так:



Как это работает, и что с ними можно сделать (гусары молчать:), подробности под катом.

Теория


Скажу сразу — как работает такой выключатель, я не знаю, хотя и примерно догадываюсь. Значит нужно будет произвести небольшой reverse engineering.

Первым делом сигнал нужно принять, для чего используем многим уже известный RTL-SDR приемник, у радиолюбителей часто называемый просто «свисток». Этот девайс ценой всего в 10$ позволяет принимать радиосигналы в диапазоне примерно от 50 до 1250МГц, для нас то что нужно. Тема старая, но если кто не читал — читайте.

Делаем первый шаг анализа — внимательно смотрим на выключатель. Обнаруживаем что сзади на корпусе у него написано «Made in China» (кто бы мог подумать?) и, что более важно, указана частота 433МГц. Теперь можно подключить SDR-приемник, запустить SDR# и убедиться, что данные действительно передаются.



Симметрия сигнала на спектре подсказывает про наличие AM-модуляции. Кстати справа виден более слабый «чужой» сигнал — их тоже можно принимать и декодировать, про них будет подробнее сказано отдельно. Впрочем, вернемся к сигналу. Записываем его в формате обычного WAV и нажимаем кнопки на пульте — для примера я нажал кнопки ON и OFF на канале «1».

Открываем звуковой файл в любом аудиоредакторе, и воспользуемся для сравнения сигналов другим профессиональным инструментом аналитиков — программой Paint. Размещаем 2 сигнала c разных кнопок один над другим, чтобы увидеть разницу:



Нетрудно видеть, что мы имеем обычную битовую последовательность, отличие в которой как раз в одном бите, соответствующем кнопке ON или OFF. Пока кнопка нажата, выключатель просто циклически повторяет эту последовательность в эфир со скоростью 20 раз в секунду. Дешево и просто, даже если одна последовательность исказится при передаче, другая будет принята.

Из этого кстати можно сделать один важный вывод — сигналы таких выключателей (речь о дешевых моделях) передаются в эфир «как есть», без какой-либо аутентификации, защиты или шифрования. Такой выключатель или беспроводную розетку с таким выключателем не стоит использовать для каких-то ответственных функций, например для включения мощных обогревателей или тем более для открытия входной двери или гаража. Дело тут даже не в хакерах (шанс что кто-то будет взламывать мой дом беспроводным способом я оцениваю меньше чем шанс падения на мой дом МКС), а в том, что сосед может случайно купить такой же выключатель, и коды у них могут совпасть (впрочем на выключателе есть возможность выбора между 4 каналами). По моему опыту использования, 2-3 раза за год выключатель таки включался «сам», то ли помеха, то ли действительно принимался далекий сигнал от такой же модели.

Разумеется, это не относится к более сложным системам, таким как Lora или Philips Hue, там с шифрованием все впорядке.

Впрочем, вернемся к нашей задаче. Можно написать декодер таких сигналов самостоятельно, но к счастью, это уже сделали до нас, в проекте называемом «rtl_433». Изначально программа была создана для Linux, Windows-версию можно скачать по адресу Linux версию можно скачать с GitHub.

Запускаем программу из командной строки: «rtl_433.exe -F json»



Мы получили данные, осталось написать программу для их обработки.

Raspberry Pi


Первое, что интересно рассмотреть, это Raspberry Pi. Для установки rtl_433 на Raspbian распаковываем архив и выполняем следующие команды.

sudo apt-get install libtool libusb-1.0.0-dev librtlsdr-dev rtl-sdr build-essential autoconf cmake pkg-config
cd rtl_433/
autoreconf --install
./configure
make
make install

Вторым шагом, напишем программу которая будет получать эти данные, и в зависимости от них, выполнять нужные действия. Код на Python весьма несложный:

from __future__ import print_function

import os, sys, io
import json
import subprocess

print("RTLSDR listening started")
transmitter_name = "Waveman Switch Transmitter"
transmitter_channel = 1

proc = subprocess.Popen(["rtl_433 -F json"], stdout=subprocess.PIPE, shell=True)
while True:
    try:
        line = proc.stdout.readline().encode('ascii','ignore')
        proc.poll()
        data = json.loads(line)
        print(data)
        m,st,ch,btn= data['model'],data['state'],data['channel'],data['button']
        if m==transmitter_name and ch==transmitter_channel and btn==1 and st=='on':
            print("ON")
        elif m==transmitter_name and ch==transmitter_channel and btn==1 and st=='off':
            print("OFF")
    except KeyboardInterrupt:
        break
    except:
        pass

print("RTLSDR listening done")

Чтобы запустить код, нужно сохранить его в файле (например rtl_listen.py) и запустить командой «python rtl_listen.py».

Как можно видеть, программа запускает процесс с помощью subprocess.Popen и читает из него данные. Дальше все просто, код вполне читабельный, и внести изменения будет не сложно. В данном примере, при нажатии кнопки «1» выводится сообщение print(«ON»), вместо этого можно делать что-то другое, например, активировать пин GPIO, включать реле, посылать данные на сервер и пр. Перед использованием будет необходимо заодно поменять имя transmitter_name на название той модели пульта, который будет использоваться.

Кстати, сам RTL-SDR-приемник по сравнению с Raspberry Pi выглядит так:



Windows


К сожалению, под Windows 10 вышеприведенный код не заработал. Но как подсказал поиск на github, работает асинхронное чтение данных из отдельного потока. Почему так, выяснять было лень, просто приведу под спойлером работающий код.

Исходный код
from __future__ import print_function

import os, sys
import subprocess
import time
import threading
import Queue
import json

class AsynchronousFileReader(threading.Thread):
    # Helper class to implement asynchronous reading
    def __init__(self, fd, queue):
        assert isinstance(queue, Queue.Queue)
        assert callable(fd.readline)
        threading.Thread.__init__(self)
        self._fd = fd
        self._queue = queue

    def run(self):
        # The body of the tread: read lines and put them on the queue.
        for line in iter(self._fd.readline, ''):
            self._queue.put(line)

    def eof(self):
        # Check whether there is no more content to expect
        return not self.is_alive() and self._queue.empty()

def replace(string):
    while '  ' in string:
        string = string.replace('  ', ' ')
    return string


def read_rtl_data():
    process = subprocess.Popen(["rtl_433.exe", "-F", "json"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    # Launch the asynchronous readers of stdout and stderr.
    stdout_queue = Queue.Queue()
    stdout_reader = AsynchronousFileReader(process.stdout, stdout_queue)
    stdout_reader.start()
    stderr_queue = Queue.Queue()
    stderr_reader = AsynchronousFileReader(process.stderr, stderr_queue)
    stderr_reader.start()

    transmitter_name = "Waveman Switch Transmitter"
    transmitter_channel = 1

    # Check the queues if we received some output
    while not stdout_reader.eof() or not stderr_reader.eof():
        # Show what we received from standard output.
        while not stdout_queue.empty():
           line = stdout_queue.get()
           print("Line1:", repr(line))
           data = json.loads(line)
           # print("Data:", repr(line))
           m,st,ch,btn= data['model'],data['state'],data['channel'],data['button']
           if m==transmitter_name and ch==transmitter_channel and btn==1 and st=='on':
               print("ON")
           elif m==transmitter_name and ch==transmitter_channel and btn==1 and st=='off':
               print("OFF")

        # Show what we received from standard error.
        while not stderr_queue.empty():
            line = replace(stderr_queue.get())
            print("Line2:", line)

        # Sleep a bit before asking the readers again.
        time.sleep(0.1)

    stdout_reader.join()
    stderr_reader.join()

    # Close subprocess' file descriptors.
    process.stdout.close()
    process.stderr.close()

if __name__ == '__main__':
    print("RTLSDR listening started")
    
    read_rtl_data()
    
    print("RTLSDR listening done")


С этим кодом мы можем использовать любые действия в обработчике, логика такая же как и в коде на Raspberry Pi.

Пример: допустим, у нас есть компьютер, выделенный под домашний кинотеатр, и мы хотим выключать его нажатием кнопки с пульта. Заменяем код 'print(«OFF»)' на

               os.system('shutdown -s')
               sys.exit(0)

После чего, компьютер будет выключаться по нажатию соответствующей кнопки. Разумеется, кроме «shutdown -s» можно использовать любую другую команду Windows, стоит лишь учитывать что команды будут посылаться многократно, пока кнопка пульта нажата, чтобы избежать такого дублирования, нужно усовершенствовать код.

Заключение


Как можно видеть, все довольно-таки просто, и есть место для экспериментов. Наконец, небольшой бонус для тех кто дочитал до сюда. На 433МГц работает большое количество разных устройств, которые rtl_433 может декодировать, можно просто оставить программу работать несколько часов, и посмотреть что «поймается». Под спойлером пример такого лога, записанного ранее:

Лог
2018-01-10 21:15:17 : Prologue sensor : 5 : 15
Channel: 1
Battery: OK
Button: 0
Temperature: 6.00 C
Humidity: 11 %

2018-01-10 21:15:28 : inFactory sensor
ID: 71
Temperature: 6.67 °C
Humidity: 99 %

2018-01-10 21:16:07 : Toyota : TPMS : 61511475 : 60e5006b : CRC

2018-01-10 21:20:33 : Prologue sensor : 5 : 15
Channel: 1
Battery: OK
Button: 0
Temperature: 6.00 C
Humidity: 11 %
: Waveman Switch Transmitter
id: A
channel: 2
button: 1
state: on
: Waveman Switch Transmitter
id: A
channel: 2
button: 1
state: on
: Waveman Switch Transmitter
id: A
channel: 2
button: 1
state: on

2018-01-10 21:21:21 : Akhan 100F14 remote keyless entry
ID (20bit): 0x41
Data (4bit): 0x4 (Mute)
: Waveman Switch Transmitter
id: A
channel: 2
button: 1
state: off

2018-01-10 21:32:31 : Ford : TPMS : 00268b1f : a34a0e : CHECKSUM
2018-01-10 21:32:32 : Ford : TPMS : 00268a5c : 9c440e : CHECKSUM
2018-01-10 21:32:37 : Ford : TPMS : 016dbfce : 99430e : CHECKSUM
2018-01-10 21:32:39 : Ford : TPMS : 002671a0 : 9c4a0e : CHECKSUM


Есть интересные данные, например давление в шинах у соседского автомобиля (TPMS, tire-pressure monitoring system), или наружняя температура +6 с чьего-то датчика. Это позволяет например, выводить наружнюю температуру, если у соседей случайно окажется совместимая с этим протоколом метеостанция.

Всем удачных экспериментов.

Disclaimer: Очевидно, что использование SDR и цифровой обработки для чтения сигналов OOK-модуляции — это по сути, стрельба из пушки по воробьям. Возможно, на aliexpress существуют готовые приемники за 1-2$, которые делают то же самое, с меньшей ценой и меньшим энергопотреблением. Если кто знает такие модели, напишите в комментариях.

Комментарии (18)


  1. GennPen
    16.06.2018 16:15

    Обычно все подобные пульты на 433 МГц прекрасно ловятся на подобные приемники, на выходе получается двоичный сигнал, который легко расшифровывается программой.


    1. DmitrySpb79 Автор
      16.06.2018 16:37

      Спасибо, как-нибудь попробую к ардуине прикрутить.


      1. GennPen
        16.06.2018 16:43

        Если будете пробовать — берите как минимум на супергетеродине(PT4303-S). На Алике есть еще дешевле модули их категорически не советую, с ними одни проблемы.


    1. Wan-Derer
      19.06.2018 14:04

      А можно ли использовать такие модули как удлинитель UART? До какой скорости?


      1. AlexIsmagilov
        19.06.2018 14:16

        Я сейчас проект делаю на вот таких модулях:
        E32-433T20S2T (http://www.cdebyte.com/en/product-view-news.aspx?id=227)
        В него чистый UART с микроконтроллера входит и выходит. Дальность передачи до 3 км.
        Протокол с распределенным спектром (LoRa), помехозащищенность очень высокая, есть избыточность передачи для коррекции ошибок. Сам тестировал в поселке на 1 км — все ок (больше не нужно было).
        В общем, очень вкусный модулечек, только цена у него дороже, зато разработка быстрее — не надо вкуривать весь мануал на SX1278. Там этим мелкая STM8 занимается.


      1. GennPen
        19.06.2018 14:25

        Теоретически конечно можно, но не рекомендуется, т.к. канал общий на все устройства, будет забиваться, а все пульты передают короткие пакеты, которые почти не занимают канал. Можно сделать на NRF24, они могут использовать разные каналы, есть режим подтверждения для гарантированной доставки пакетов.


      1. DmitrySpb79 Автор
        19.06.2018 14:30

        Есть готовые радиомодемы на 433 или 866МГц, специально для UART, их часто на квадрокоптерах используют, типа таких www.dx.com/p/433mhz-single-ttl-3d-robotics-3dr-radio-telemetry-kit-for-apm-apm2-blue-green-235604

        Скорость 9600 если память не изменяет.


  1. One_Touch
    16.06.2018 22:45

    Почитайте о проекте rflink на Arduino mega.
    http://www.rflink.nl/blog2/wiring
    Я его в связке с domoticz использую для управления парой релешек 433МГц.
    Единственный минус — ловит много лишнего (например автосигнализации), чем забивает список девайсов.


  1. Squoworode
    17.06.2018 13:58

    Если не ошибаюсь, с RTL SDR работает ещё и GNURadio, поддерживающий подключение плагинов, среди которых наверняка можно найти бинарный декодер.


    1. DmitrySpb79 Автор
      17.06.2018 14:06

      Да, есть основанные на gnuradio декодеры, например для метеостанций Oregon — github.com/kevinmehall/rtlsdr-433m-sensor

      Готовых блоков именно для пультов (или даже просто чтобы возвращал бинарный поток с синхронизацией по началу пакета) не нашел.


  1. Oskal174
    17.06.2018 19:15

    Китайские пульты в частоту шлют сразу json строку?


    1. DmitrySpb79 Автор
      17.06.2018 19:15

      Нет конечно, параметр «rtl_433 -F json» задается в декодере. Декодер внутри уже имеет образцы сигналов наиболее популярных пультов.


  1. Mixalych
    17.06.2018 22:08

    Брал из «этой же серии», аля китайской, дешевый звонок (входной на дверь) и использовал как радиокнопку. Принесли мне старые часы «Электроника» (модель не помню, на газоразрядных индикаторах), 1985 года. Дедушке нужно было удаленно отключать будильник на них. Так вот, нашел в звонке, где приемник выдает лог. 1 на вход звуковой микросхемы при получении сигнала с кнопки, повесил на эту линию триггер шмитта с реле — и все этот в разрыв копки включения будильника в часах (разрывала просто выход на динамик). Результат достигнут, дешево и работает уже пару лет. Часы, конечно, на лампах — тема…


  1. mmMike
    18.06.2018 07:45

    Вот это да…
    А вскрыть корпус и посмотреть начинку (маркировку микросхемы кодера) — это не спортивно?


    Китайский производители обычно пихают в такие розетки и пультики ширпотребовские кодеры/декодеры PT2262/PT2272 + отдельные стандартные модули передатчика/приемника. Анализировать протокол на уровне радиосигнала для стандартного ширпотреба с доступными спецификациями — это какое то извращенное удовольствие.


  1. mavrikk
    18.06.2018 08:40
    +1

    Из готовых решений можно посмотреть в сторону sonoff rf bridge (не реклама). Умеет как получать, так и передавать сигналы 433 МГц. Плюс имеет WiFi. Сделана на основе популярного ESP8266. Можно подключать к различным системам умного дома. А обилие прошивок для ESP8266 позволяет расширить функционал и отойти от китайских проприетарных облачных сервисов


  1. Doktorov
    18.06.2018 08:43

    Я развернул OpenHub + Esp8266 + Передатчик 433.


  1. Wan-Derer
    19.06.2018 14:33

    Интересны приёмничек. А вот это тоже оно? Или даже вот это?


    1. DmitrySpb79 Автор
      19.06.2018 14:36

      Этот тоже заработает. Но самая последняя версия вроде эта — www.rtl-sdr.com/product/rtl-sdr-blog-v3-r820t2-rtl2832u-1ppm-tcxo-sma-software-defined-radio-dongle-only

      (или искать по ключевым словам RTL SDR V3)