10.04.2015

Эксперименты выходного дня с GNU Radio

image

У нашего местного дискаунтера «ALDI» я купил несколько розеток модели «Easy Home» с дистанционным управлением. В комплект входит 4 розетки плюс (всего лишь) один пульт. Кроме того, в интернете я поискал подходящий передатчик с USB-разъемом, который бы смог управлять розетками, и нашел очень дешевую модель «Home Easy». Вы, вероятно, заметили, что словосочетания «Easy Home» и «Home Easy» несколько отличаются. Я тоже обратил внимание на это различие, но все же надеялся, что передатчик будет работать без дополнительных танцев с бубном.

Автор: Sven Tantau

Home easy или easy home?

У нашего местного дискаунтера «ALDI» я купил несколько розеток модели «Easy Home» с дистанционным управлением. В комплект входит 4 розетки плюс (всего лишь) один пульт. Кроме того, в интернете я поискал подходящий передатчик с USB-разъемом, который бы смог управлять розетками, и нашел очень дешевую модель «Home Easy». Вы, вероятно, заметили, что словосочетания «Easy Home» и «Home Easy» несколько отличаются. Я тоже обратил внимание на это различие, но все же надеялся, что передатчик будет работать без дополнительных танцев с бубном.

Я ошибался.

Оборудование для экспериментов


Рисунок: Перечень оборудования

Easy Home (Розетки + Пульт)

10-ти кнопочный пульт позволяет включать/выключать розетки. Еще существует так называемый мастер-режим для выключения/выключения одновременно всех 4-х розеток. Во время подачи питания розетки находятся в «режиме обучения». В течение 30 секунд розетка ожидает сигнал на включение, который затем считается корректным, и запоминает некоторый идентификатор. Если в течение 30 секунд никаких сигналов не поступало, розетка использует сохраненный ранее идентификатор. Розетку можно «научить» принимать сигналы включения/отключения от нескольких кнопок пульта. Если во время режима обучения поступил сигнал выключения, эта кнопка больше не сможет управлять данной розеткой. Если во время режима обучения используется мастер-режим на выключение, удаляются все сохраненные ранее идентификаторы.

Производитель: Globaltronics GmbH & Co KG
Модель розетки: GT-FSI-07
Модель пульта: GT-9000

RTL-SDR (USB DVB-T приемник)

DVB-T приемник с USB-разъемом и тюнером Elonics E4000 (TV-приемник), функционирующий как программно-определяемая радиосистема, предназначен для анализа сигнала. Программное обеспечение с открытым исходным кодом позволяет устанавливать частоту, полосу пропускания, периодичность выборки и на выходе получать I/Q сигнал.

Home Easy (USB-RF-передатчик)

USB-передатчик, работающий на частоте 433.92 МГц.

Поставщик: ROOS Electronics bv.
Модель: ELRO AB600USB
idVendor=04d9, idProduct=1357
Производитель: ABLOCK

Размышления, догадки и немного удачи

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

В своих исследованиях я использовал утилиту baudline в связке с rtl_sdr:

sudo rtl_sdr -s 2e6 -f 433.92e6 - | baudline -reset -stdin
# This invokes rtl_sdr
# -s 2e6 sets the sample rate to 2000000 samples per second
# -f 433.92e6 sets the frequency to 433.92 MHz
# and then pipes the output into baudline


Рисунок 1: Выходной сигнал при нажатии кнопки на пульте

Мои предположения подтвердились. Как только я нажал на кнопку пульта, то увидел «содержимое» на диаграмме частот в baudline.

Подсказка: вы можете остановить обработку новых данных, если кликнете на правую кнопку мыши и выберете «Pause».

В baudline я обнаружил следующую опцию:

-quadrature Анализ комплексного I/Q сигнала.

Поскольку на выходе утилиты I/Q сигнал, я решил попробовать опцию –quadrature. Также я решил установить входной формат как беззнаковый целый, а затем увеличил периодичность выборки до 1e6.

sudo rtl_sdr -s 2e6 -f 433.92e6 - | baudline -reset -samplerate 1000000 -format u8 -quadrature -stdin
# This invokes rtl_sdr
# -s 2e6 (sets the sample rate to 2000000 samples per second)
# -f 433.92e6 (sets the frequency to 433.92 MHz)
# and then pipes the output into baudline
# -reset (use defaults)
# -samplerate 1000000 (guess what)
# -format u8 (unsigned integer as input)
# -quadrature (I/Q data is coming)
# -stdin (take input from stdin)


Рисунок 2: В анализаторе изменен формат входного сигнала и периодичность выборки

Чтобы лучше рассмотреть сигнал, я переключился в режим waveform (кликните правой кнопкой мыши, выберите displays и далее waveform). Комбинация клавиш ALT+(Левая/Правая стрелка) позволяет масштабировать время и найти искомую информацию.


Рисунок 3: Сигнал без масштабирования




Рисунок 4: Масштабированный сигнал

Глядя на сигнал в масштабированном виде, я предположил, что передаются бинарные последовательности. Предполагая, что при помощи длинного импульса передается единица, а при помощи короткого – ноль, я смог выписать бинарные последовательности, передаваемые при нажатии кнопок на моем пульте.

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

Одна из последовательностей, которую я обнаружил: 110100000111010000110000.

На данном этапе я не стал вдаваться в подробности и выяснять, что означает каждый конкретный бит. Я лишь выяснил, что везде присутствует префикс 1101, возможно кодирующий модель розетки, и что последние 4 бита кодируют один из 5 наборов кнопок пульта (каждый набор состоит из пары кнопок включения/выключения розетки). Также я заметил, что существуют многократно повторяемые коды для одной и той же кнопки.

Я не знал, какое количество кодов используется для каждой из 10 кнопок, и не стал далее анализировать сигнал вручную. Намного интереснее было поиграться с передатчиком (модель Home Easy).

Я сделал некоторые пометки относительно длительности сигналов, поскольку эта информация могла пригодиться в дальнейшем.


Рисунок 5: Еще один участок масштабированного сигнала

Еще одна подсказка: не используйте функцию копирования+вставки в baudline. Я не вдавался в подробности, но, по-видимому, существует различие между реальными данными и тем, что хранит baudline. Если хотите поработать с выборками, сохраните всю информацию в файл и используйте утилиту dd для того, чтобы отрезать нужные вам части.

Изучение, тестирование и первые успехи

Не удивительно, что программное обеспечение, идущее в комплекте с передатчиком, не работало с моими розетками. На мою удачу, я нашел утилиту he853-remote, написанную на C++, которая позволила мне отослать некоторые первоначальные сигналы при помощи передатчика.

Код утилиты не особо сложен для понимания.

Используется библиотека 'HIDAPI' (libusb). Вначале происходит инициализация обработчика устройства на основе имени поставщика и идентификатора продукта, а затем отсылаются байты передатчику.

Первоначальный код заполняет массив uint8_t rfCmdBuf[32] информацией, а потом происходит передача порциями по 8 байт контроллеру HE853Controller::sendOutputReport(uint8_t* buf). Контроллер добавляет еще один пустой байт, и далее 9 байт отсылаются передатчику. После пересылки 4 блоков, код отсылает еще один специальный контрольный блок (также состоящий из 9 байт), и устройство начинает передачу данных.

Изучив документацию и внеся небольшие изменения на основе того, что я наблюдал в baudline, мне удалось сделать новую версию rfCmdBuf, успешно управляющую моими розетками.

// Frame number 1
rfCmdBuf[0*8+0] = 0x01;
// StartBit_HTime
rfCmdBuf[0*8+1] = (uint8_t) ((420 / 10) >> 8);
rfCmdBuf[0*8+2] = (uint8_t) (420 / 10);
// StartBit_LTime
rfCmdBuf[0*8+3] = (uint8_t) ((2200 / 10) >> 8);
rfCmdBuf[0*8+4] = (uint8_t) (2200 / 10);
// EndBit_HTime
rfCmdBuf[0*8+5] = (uint8_t) ((6200 / 10) >> 8 ); // set to 0 for learning mode
rfCmdBuf[0*8+6] = (uint8_t) (6200 / 10); // set to 0 for learning mode
// EndBit_LTime
rfCmdBuf[0*8+7] = 0x00;
// Frame number 2
rfCmdBuf[1*8+0] = 0x02;
// EndBit_LTime
rfCmdBuf[1*8+1] = 0x00;
// DataBit0_HTime
rfCmdBuf[1*8+2] = (uint8_t) (330 / 10); // 2250
// DataBit0_LTime
rfCmdBuf[1*8+3] = (uint8_t) (1190 / 10); // 750
// DataBit1_HTime
rfCmdBuf[1*8+4] = (uint8_t) (1190 / 10); // 750
// DataBit1_LTime
rfCmdBuf[1*8+5] = (uint8_t) (330 / 10); // 2250
// DataBit_Count
rfCmdBuf[1*8+6] = (uint8_t) 24; // how many bits to send
// Frame_Count
rfCmdBuf[1*8+7] = (uint8_t) 4;
// Frame number 3
rfCmdBuf[2*8+0] = 0x03;
// payload: 110100000111010000110100
rfCmdBuf[2*8+1] = 0xd0; // 11010000
rfCmdBuf[2*8+2] = 0x74; // 01110100
rfCmdBuf[2*8+3] = 0x34; // 00110000
rfCmdBuf[2*8+4] = 0x00;
rfCmdBuf[2*8+5] = 0x00;
rfCmdBuf[2*8+6] = 0x00;
rfCmdBuf[2*8+7] = 0x00;
// Frame number 4
rfCmdBuf[3*8+0] = 0x04;
rfCmdBuf[3*8+1] = 0x00;
rfCmdBuf[3*8+2] = 0x00;
rfCmdBuf[3*8+3] = 0x00;
rfCmdBuf[3*8+4] = 0x00;
rfCmdBuf[3*8+5] = 0x00;
rfCmdBuf[3*8+6] = 0x00;
rfCmdBuf[3*8+7] = 0x00;

В итоге получилась последовательность из 5*9 байтов:

00 01 00 2A 00 DC 02 6C 00
00 02 00 21 77 77 21 18 01
00 03 D0 74 34 00 00 00 00
00 04 00 00 00 00 00 00 00
00 05 00 00 00 00 00 00 00

Первоначальная задача решена. Теперь я могу управлять розетками при помощи устройства от стороннего поставщика. Однако решение не очень красивое, поскольку каждый раз приходится искать коды управления вручную. Я стал изучать, как в GNU Radio создаются новые модули.

Создание декодера сигнала от пульта

GNU Radio представляет собой набор библиотек для создания радиосистем под конкретные нужды. Для построения блок-схемы будущей системы предусмотрен очень удобный графический интерфейс GNU Radio Companion (GRC). После создания схемы происходит преобразование в код на python, который потом при необходимости можно доработать.

Существует множество руководств по установке и настройке gnuradion, большинство из которых рекомендуют использовать самую последнюю версию. Я бы порекомендовал вам то же самое. Компилируйте исходники. Поскольку вам нужна рабочая среда для написания собственных модулей, крайне важно иметь «нормальную» версию.

После того, как я потратил некоторое время на изучение документации, должен сказать, что мне не был понятен функционал большинства компонентов, поскольку я не знаком с основами радиотехники.

Однако моя страсть к экспериментам не ослабла.

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

После некоторых экспериментов мне удалось собрать простейшую работоспособную версию.


Рисунок 6: Первая работоспособная версия простейшей радиосистемы

Значение N равное 30 я поставил случайно. Во всех последующих версиях модуля число N также было в районе 30 (плюс минус).

Как было сказано ранее, измерительные приборы очень помогали мне при изучении компонентов.

Ниже показан осциллограф, подключенный к радиосистеме:


Рисунок 7: Показания осциллографа, подключенного ко всей радиосистеме

Осциллограф, подключенный к компоненту Complex to Mag^2:


Рисунок 8: Показания осциллографа, подключенного к компоненту Complex to Mag^2

Осциллограф, подключенный к компоненту Low Pass Filter (низкочастотный фильтр):


Рисунок 9: Показания осциллографа, подключенного к компоненту Low Pass Filter

Осциллограф, подключенный к компоненту Keep 1 of N:


Рисунок 10: Показания осциллографа, подключенного к компоненту Keep 1 of N

Код моего модуля socket_dump_f, написанного на Python, считает «потоки» нулей и единиц. Огромное множество единиц означает импульс, разделяющий битовые последовательности (обозначается символом «X»).

Множество единиц представляют собой бинарную единицу в исходящем сигнале.

Несколько единиц представляют собой бинарный ноль в исходящем сигнале.

В итоге получается строка следующего формата: бинарный код X бинарный код X бинарный код …

Если детектируется длинная последовательность поступающих нулей, предполагается, что пакет завершен (если это был пакет).

Если в итоговой строке есть какие-либо данные, я выбираю коды между символами «X». Не слишком красивое, но рабочее решение.

Код модуля:

#!/usr/bin/env python
import numpy
from gnuradio import gr

class socket_dump_f(gr.sync_block):
"""
docstring for block socket_dump_f
"""
def __init__(self):
self.zero_counter = 0
self.one_counter = 0
self.last = 0
self.output_buffer = ""
gr.sync_block.__init__(self,
name="socket_dump_f",
in_sig=[numpy.float32],
out_sig=None)

def work(self, input_items, output_items):
in0 = input_items[0]
for item in in0 :
if item < 1 :
if self.last >= 1 :
if self.one_counter > 50 :
self.output_buffer += "X"
elif self.one_counter > 20 :
self.output_buffer += "1"
else :
self.output_buffer += "0"
self.one_counter = 0
self.zero_counter += 1
if self.zero_counter > 1000 and len(self.output_buffer) > 10:
a = self.output_buffer.split('X')
if len(a) >= 2 :
print "Received Code: " + a[1]
self.output_buffer = ""
self.zero_counter = 500
else :
self.zero_counter = 0
self.one_counter += 1
self.last = item
return len(input_items[0])

Если захотите импортировать код в GNU Radio Companion, вам понадобится описание проекта в формате XML.

sockethack_socket_dump_f.xml:
<!--?xml version="1.0"?-->
<block>
<name>socket_dump_f</name>
<key>sockethack_socket_dump_f</key>
<category>sockethack</category>
<import>import sockethack</import>
<make>sockethack.socket_dump_f()</make>
<sink>
<name>in0</name>
<type>float</type>
</sink>
</block>

Последние штрихи и оставшиеся вопросы

Теперь я могу легко выгружать все коды, передаваемые моим пультом.

Каждая порция передаваемой битовой последовательности состоит из 24 байт:

В байтах [0-3] закодирован идентификатор продукта (эти байты всегда неизменны).

В байтах [4-19] передается непонятное содержимое.

В байтах [20-23] закодирован идентификатор набора кнопок (эти байты одинаковые для каждого набора кнопок включения/выключения розетки).

  • Общее количество неизвестных кодов – 8.
  • Все 8 неизвестных кодов можно разделить на 2 группы (A/B) по 4 кода в каждой.
  • Либо кнопка включения использует коды из группы A и кнопка выключения из группы B, либо наоборот.

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