Детектив Зубочистка Раскрывает Тайну Радиопротокола

Это небольшой набросок рассказа о " Комфортный дом Просто иллюстрация того, что даже имея не слишком большие знания и опыт, можно чего-то добиться.

Другими словами, достаточно настойчивый дятел убьет любое дерево.

Все началось с простого желания управлять светом в доме с помощью Arduino. Включая переключатели Ливоло , купил еще до этой безумной идеи с домашней автоматикой.

Но, в отличие от радиорозеток, «щелкнуть» их с помощью моей любимой библиотеки RC-Switch не получилось, а поиск других готовых решений показал их полное отсутствие.

А китайские производители и продавцы на вопрос о протоколе ответили, что эта штука работает на частоте 433 МГц.

Не очень полезная информация.

Однако я не буду претендовать на святую невинность.

Ведь вместе с Ардуино я купил еще пару блоков по четыре реле, чтобы в случае чего тривиально замкнуть выбранные кнопки пульта.

И это, кстати, достаточно популярное решение, потому что быстрое, относительно дешевое и очень сердито.

Но в душе я стремился к красоте.

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



Эпические неудачи
Однако сначала я не подумал о зубочистках.

Но в процессе чтения чего-либо более-менее простого о декодировании радиопротоколов я наткнулся на замечательный ресурс NetHome. И там автор публикует, во-первых, схема делителя , позволяющая записывать демодулированный сигнал с ресивера на компьютер через обычный микрофонный вход и одновременно простую утилиту Анализатор протоколов для записи и анализа сигнала.

.

виновники торжества

Детектив Зубочистка раскрывает тайну радиопротокола



Детектив Зубочистка раскрывает тайну радиопротокола

Итак, я собрал делитель, подключил его к ноуту, нажал кнопку пульта и начал смотреть результаты – благо модуляция (амплитудная) пульта совпала с модуляцией приёмника.

Анализатор протоколов на самом деле довольно крутая штука.

Программа определяет наиболее популярные протоколы, и если встретит неизвестный, можно просмотреть «осциллограмму» с разбивкой по импульсам.

К сожалению, она не знала протокола Ливоло.

И это меня даже немного смутило, так как не показывало четко истинную форму сигнала с пульта Livolo. Это оказалось случайностью, когда мне пришло в голову посмотреть сигнал еще и в Мужество .

Здесь импульсы стали отчетливо видны и, мне кажется, причина бед «Анализатора протоколов» очевидна: крайне малая длительность этих самых импульсов — от 100 до 500 микросекунд. В том же редакторе я решил пойти по простому пути — записать полученный сигнал в WAV, а затем воспроизвести его с помощью Arduino на пине, к которому подключен передатчик.

Ведь у меня был Ethernet шилд со слотом microSD, вполне подходящий для «плеера».

Немного поискав и я нашел «музыкальную» библиотеку ТМРпкм .

.

вот что показал анализатор протоколов

Детектив Зубочистка раскрывает тайну радиопротокола

.

сравни с Аудасити

Детектив Зубочистка раскрывает тайну радиопротокола

Идея основывалась на том, что схожие по сути ИК-протоколы можно успешно и довольно просто смоделировать, подключив ИК-светодиод к аудиовыходу компьютера или другого гаджета, способного воспроизводить звук с подходящими параметрами.

Сказано - сделано.

И отложим: переключатели не поняли шутки, а просмотр излучаемого таким образом сигнала в Audacity показал, что форма импульсов слишком искажена.



Повторение формы
Тогда я решил пойти на крайние меры.

А именно, глупо повторять форму сигнала, не выходя на логический уровень.

Для этого необходимо и довольно сложно закодировать сообщения кода в скетче Arduino. И здесь мне очень повезло.

Если посмотреть кодовое сообщение пульта Livolo, то можно заметить, что оно состоит из множества (около 100) многократно повторяющихся пакетов импульсов.

Итак, все пакеты в кодовом пакете совершенно одинаковы – это своего рода защита от помех: чрезмерное количество пакетов гарантирует, во-первых, надежный захват сигнала АРУ приемника, а, во-вторых, прием самой команды.

.

.

Вот такая картина, если нажать кнопки подряд

Детектив Зубочистка раскрывает тайну радиопротокола

.

Понять, где сигнал, а где шум, довольно просто.

Здесь вы можете оценить масштаб катастрофы: сигнал — всего одна кнопка

Детектив Зубочистка раскрывает тайну радиопротокола

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

Livolo также использует систему фиксированных кодов, то есть одной кнопке пульта всегда соответствует один и тот же пакет импульсов.

Проверить это легко: достаточно нажать одну и ту же кнопку несколько раз и сравнить результаты.

В моем случае все они оказались совершенно идентичными.

.

повторение – не только мать учения, но и залог уверенного приема сигнала

Детектив Зубочистка раскрывает тайну радиопротокола

Это то, что я называю удачей: фиксированный код без каких-либо ухищрений.

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

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

И вот настал звездный час зубочистки.

На самом деле искусство подсчета импульсов одними глазами мне недоступно, но если подвигать указатель, то это очень даже неплохо.

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

При достаточном увеличении видно, что пакет состоит из пяти типов импульсов (условно: длинный вниз, короткий вверх, короткий вниз, средний вверх, средний вниз).

.



Детектив Зубочистка раскрывает тайну радиопротокола

Если увеличить масштаб еще больше, то можно на глазок оценить длину импульсов по линии Audacity, что я и сделал для всех пяти.

Кроме того, каждому импульсу был присвоен порядковый номер — это основано на использовании переменных байтового типа в целях экономии памяти Arduino. Я только сейчас подумал, что можно будет разделить на 10 и не заморачиваться насчет «сокращений».

.

Теоретические границы импульсов выделены синим и красным цветом, так как в идеале фронты должны быть вертикальными, но это если нет радиоканала

Детектив Зубочистка раскрывает тайну радиопротокола

Работа оказалась не столько интеллектуальной, сколько муторной.

Количество отдельных импульсов «переплывало» от кнопки к кнопке.

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

Я просто закодировал результат и опробовал его.

Ничего не получалось с первого раза.

Однако это было ожидаемо.

Чего я не ожидал, так это того, что со второго раза все получится.

Но дело оказалось в том, что при прямом кодировании (т.е.

если импульс направлен вверх, кодируем OUTPUT/HIGH) сигнал получился перевернутым - очевидно, это особенность передатчика.

Решить это было проще простого: инвертируем уровни в коде (т.е.

кодируем восходящий импульс OUTPUT/LOW).

Сравнение симуляции и оригинального сигнала (в Audactiy, на глазок) также показало небольшое несоответствие длины импульсов — это я тоже исправил.

Первая версия, великая и ужасная

  
  
  
  
   

int txPin = 9; // pin connected to RF transmitter int i; // counter to send command pulses int pulse; // count pulse repetitions int incomingByte = 0; // for incoming serial data // hard coded commands (see txButton): 1 - pulse start, 2 - zero, 3 - one, 4 - pause, 5 - low int button1[45]={44, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2}; int button2[43]={43, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2}; int button3[41]={40, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5, 3, 5, 3, 4, 2, 4, 2, 4, 2}; int button4[43]={42, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5, 3, 4, 2, 4, 2, 4, 2}; int button5[43]={42, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2}; int button6[43]={42, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5, 3, 4, 2, 4, 2, 4, 2, 4, 2}; int button7[41]={40, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5, 3, 4, 2, 5, 3, 4, 2, 4, 2}; int button8[43]={42, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5, 3, 4, 2, 4, 2}; int button9[43]={42, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2}; int button10[43]={42, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5, 2, 4, 3, 4, 2, 4, 2, 4, 2}; int button11[41]={40, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5, 3, 4, 2, 5, 2, 4, 3, 4, 2}; void setup () { pinMode(txPin, OUTPUT); Serial.begin(9600); Serial.println("Number = button; a to press 0; b to shut off all"); } void loop(){ if (Serial.available() > 0) { // read the incoming byte: incomingByte = Serial.read(); switch(incomingByte) { case 49: txButton(button1); Serial.println("Switching on 1"); break; case 50: txButton(button2); Serial.println("Switching on 2"); break; case 51: txButton(button3); Serial.println("Switching on 3"); break; case 52: txButton(button4); Serial.println("Switching on 4"); break; case 53: txButton(button5); Serial.println("Switching on 5"); break; case 54: txButton(button6); Serial.println("Switching on 6"); break; case 55: txButton(button7); Serial.println("Switching on 7"); break; case 56: txButton(button8); Serial.println("Switching on 8"); break; case 57: txButton(button9); Serial.println("Switching on 9"); break; case 97: txButton(button10); Serial.println("Switching on 0"); break; case 98: txButton(button11); Serial.println("Switching All off"); break; } } // end if serial available }// end void loop // transmit command. Due to transmitter (or something, I don't know) transmission code should be INVERTED. Ex: one is coded as LOW-delay->HIGH instead of HIGH-delay-LOW void txButton(int cmd[]) { Serial.print("Processing. Array size is "); Serial.println(cmd[0]); digitalWrite(txPin, HIGH); // not sure if its required, just an attempt to start transmission to enable AGC of the receiver delay(1000); for (pulse= 0; pulse <= 100; pulse=pulse+1) { // repeat command 100 times for (i = 1; i < cmd[0]+1; i = i + 1) { // transmit command switch(cmd[i]) { case 1: // start digitalWrite(txPin, HIGH); delayMicroseconds(550); digitalWrite(txPin, LOW); // Serial.print("s"); break; case 2: // "zero", that is short high spike digitalWrite(txPin, LOW); delayMicroseconds(110); digitalWrite(txPin, HIGH); // Serial.print("0"); break; case 3: // "one", that is long high spike digitalWrite(txPin, LOW); delayMicroseconds(303); digitalWrite(txPin, HIGH); // Serial.print("1"); break; case 4: // pause, that is short low spike digitalWrite(txPin, HIGH); delayMicroseconds(110); digitalWrite(txPin, LOW); // Serial.print("p"); break; case 5: // low, that is long low spike digitalWrite(txPin, HIGH); delayMicroseconds(290); digitalWrite(txPin, LOW); // Serial.print("l"); break; } } } }

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

В таком виде код продержался, по-моему, больше полугода, а потом мне надоело, и вообще мне снова захотелось чего-то красивого.

Вторая версия с PROGMEM

#include <avr/pgmspace.h> // needed to use PROGMEM #define txPin 8 // pin connected to RF transmitter (pin 8) byte i; // command pulses counter for Livolo (0 - 100) byte pulse; // counter for command repeat // commands stored in PROGMEM arrays (see on PROGMEM use here: http://arduino.cc/forum/index.phpЭtopic=53240.0 ) // first array element is length of command const prog_uchar button1[45] PROGMEM ={44, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2}; const prog_uchar button2[43] PROGMEM ={42, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2}; const prog_uchar button3[41] PROGMEM ={40, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5, 3, 5, 3, 4, 2, 4, 2, 4, 2}; const prog_uchar button4[43] PROGMEM ={42, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5, 3, 4, 2, 4, 2, 4, 2}; const prog_uchar button5[43] PROGMEM ={42, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2}; const prog_uchar button7[41] PROGMEM ={40, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5, 3, 4, 2, 5, 3, 4, 2, 4, 2}; const prog_uchar button11[41] PROGMEM ={40, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5, 3, 4, 2, 5, 2, 4, 3, 4, 2}; // pointers to command arrays PROGMEM const prog_uchar *buttonPointer[] = {button1, button2, button3, button4, button5, button7, button11}; void setup() { // sipmle example: send button "button2" once. Note that array elements numbered starting from "0" (so button1 is 0, button2 is 1 and so on) txButton(1); } void loop() { } // transmitting part // zeroes and ones here are not actual 0 and 1. I just called these pulses for my own convenience. // also note that I had to invert pulses to get everything working // that said in actual command "start pulse" is long low; "zero" = short high; "one" = long high; "pause" is short low; "low" is long low. void txButton(byte cmd) { prog_uchar *currentPointer = (prog_uchar *)pgm_read_word(&buttonPointer[cmd]); // current pointer to command array passed as txButton(cmd) argument byte cmdCounter = pgm_read_byte(&currentPointer[0]); // read array length for (pulse= 0; pulse <= 180; pulse = pulse+1) { // how many times to transmit a command for (i = 1; i < cmdCounter+1; i = i + 1) { // counter for reading command array byte currentCmd = pgm_read_byte(&currentPointer[i]); // readpulse type from array switch(currentCmd) { // transmit pulse case 1: // start pulse digitalWrite(txPin, HIGH); delayMicroseconds(550); digitalWrite(txPin, LOW); break; case 2: // "zero" digitalWrite(txPin, LOW); delayMicroseconds(110); digitalWrite(txPin, HIGH); break; case 3: // "one" digitalWrite(txPin, LOW); delayMicroseconds(303); digitalWrite(txPin, HIGH); break; case 4: // "pause" digitalWrite(txPin, HIGH); delayMicroseconds(110); digitalWrite(txPin, LOW); break; case 5: // "low" digitalWrite(txPin, HIGH); delayMicroseconds(290); digitalWrite(txPin, LOW); break; } } } digitalWrite(txPin, LOW); }



Выделение общего
Еще раз просматривая WAV, я особо ни на что не надеялся.

Однако более тщательное, чем раньше, сравнение кодовых сообщений преподнесло приятный сюрприз.

Часть последовательности в начале каждого пакета импульсов оказалась общей для всех пакетов одного кодового сообщения, и для всех кнопок пульта в целом.

.

вырезаем разные пуговицы - и сразу видно, что часть упаковки не меняется

Детектив Зубочистка раскрывает тайну радиопротокола

До понимания протокола было еще далеко, но это небольшое открытие позволило еще больше сэкономить память контроллера.

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

Основная часть теперь обосновалась на отдельной территории.



#include <avr/pgmspace.h> // needed to use PROGMEM #define txPin 8 // pin connected to RF transmitter (pin 8) byte i; // command pulses counter for Livolo (0 - 100) byte pulse; // counter for command repeat // commands stored in PROGMEM arrays (see on PROGMEM use here: http://arduino.cc/forum/index.phpЭtopic=53240.0 ) // first array element is length of command const prog_uchar start[30] PROGMEM = {1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2}; // remote ID - no need to store it with each command const prog_uchar button1[15] PROGMEM ={14, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2}; // only command bits const prog_uchar button2[13] PROGMEM ={12, 5, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2}; const prog_uchar button3[11] PROGMEM ={10, 5, 3, 5, 3, 4, 2, 4, 2, 4, 2}; const prog_uchar button4[13] PROGMEM ={12, 4, 2, 4, 2, 5, 3, 4, 2, 4, 2, 4, 2}; const prog_uchar button5[13] PROGMEM ={12, 5, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2}; const prog_uchar button7[11] PROGMEM ={10, 5, 3, 4, 2, 5, 3, 4, 2, 4, 2}; const prog_uchar button11[11] PROGMEM ={10, 5, 3, 4, 2, 5, 2, 4, 3, 4, 2}; // pointers to command arrays PROGMEM const prog_uchar *buttonPointer[] = {start, button1, button2, button3, button4, button5, button7, button11}; void setup() { // sipmle example: send button "button2" once. Note that array elements numbered starting from "0" (so button1 is 0, button2 is 1 and so on) // Serial.begin(9600); } void loop() { txButton(3); delay(1000); } // transmitting part // zeroes and ones here are not actual 0 and 1. I just called these pulses for my own convenience. // also note that I had to invert pulses to get everything working // that said in actual command "start pulse" is long low; "zero" = short high; "one" = long high; "pause" is short low; "low" is long low. void txButton(byte cmd) { prog_uchar *currentPointer = (prog_uchar *)pgm_read_word(&buttonPointer[cmd]); // current pointer to command array passed as txButton(cmd) argument byte cmdCounter = pgm_read_byte(&currentPointer[0]); // read array length prog_uchar *currentPointerStart = (prog_uchar *)pgm_read_word(&buttonPointer[0]); // current pointer to start command array for (pulse= 0; pulse <= 180; pulse = pulse+1) { // how many times to transmit a command for (i = 0; i<30; i=i+1) { byte currentCmd = pgm_read_byte(&currentPointerStart[i]); sendPulse(currentCmd); // Serial.print(currentCmd); // Serial.print(", "); } for (i = 1; i < cmdCounter+1; i = i + 1) { // counter for reading command array byte currentCmd = pgm_read_byte(&currentPointer[i]); // readpulse type from array sendPulse(currentCmd); // Serial.print(currentCmd); // Serial.print(", "); } } } void sendPulse(byte txPulse) { switch(txPulse) { // transmit pulse case 1: // start pulse digitalWrite(txPin, HIGH); delayMicroseconds(550); digitalWrite(txPin, LOW); break; case 2: // "zero" digitalWrite(txPin, LOW); delayMicroseconds(110); digitalWrite(txPin, HIGH); break; case 3: // "one" digitalWrite(txPin, LOW); delayMicroseconds(303); digitalWrite(txPin, HIGH); break; case 4: // "pause" digitalWrite(txPin, HIGH); delayMicroseconds(110); digitalWrite(txPin, LOW); break; case 5: // "low" digitalWrite(txPin, HIGH); delayMicroseconds(290); digitalWrite(txPin, LOW); break; } digitalWrite(txPin, LOW); }

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

С одной стороны, ничто не мешало протоколу иметь такую особенность, с другой стороны, под рукой было множество примеров подобных протоколов (от сокетов, например, и метеостанций), где прослеживалась тенденция к 24-битности.

отправка.



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

С самого начала мне было удобнее думать, что короткие импульсы означают «0», а средней длительности — «1».

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

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

Процесс завершился следующими выводами: 1. Существует четкое правило последовательности импульсов: за импульсом «вверх» всегда следует импульс «вниз», независимо от длительности импульса.

2. Два коротких импульса подряд в моей системе координат означают «0».

3. Аналогично, каждый импульс средней длительности означает «1».

4. Самый длинный импульс отправки – это старт или стоп, который не имеет значения и зависит только от точки зрения.

Если применить эти правила к импульсному пакету, то станет ясно, что его общая длина всегда равна 24 битам, включая старт-стоп.

Из них 16 бит — это обнаруженная ранее «фиксированная» часть и 7 бит — уникальная часть для каждой цифровой кнопки пульта дистанционного управления.

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

.

по всем правилам

Детектив Зубочистка раскрывает тайну радиопротокола

Из «формата» пакета естественным образом следовало, что 16-битный фрагмент, скорее всего, является идентификатором пульта, позволяющим использовать несколько пультов в одной квартире или не беспокоить соседей, если у них одинаковые выключатели.

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

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

Главное соблюдать правило: 16 бит — это идентификатор пульта, и использовать либо известные кнопки, либо генерировать их по принципу «кнопки» из 7 бит. Однако практика показала, что не все 16-битные идентификаторы пульта подходят. Но это не так уж и страшно: согласно той же практике, найти подходящий идентификатор не составляет особой сложности.

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

И вот результат

#define txPin 8 // pin connected to RF transmitter (pin 8) byte i; // just a counter byte pulse; // counter for command repeat boolean high = true; // pulse "sign" // keycodes #1: 0, #2: 96, #3: 120, #4: 24, #5: 80, #6: 48, #7: 108, #8: 12, #9: 72; #10: 40, #OFF: 106 // real remote IDs: 6400; 19303 // tested "virtual" remote ID: 8500, other IDs could work too, as long as they do not exceed 16 bit // known issue: not all 16 bit remote ID are valid // have not tested other buttons, but as there is dimmer control, some keycodes could be strictly system // use: sendButton(remoteID, keycode); // see void loop for an example of use void setup() { } void loop() { sendButton(6400, 120); // blink button #3 every 3 seconds using remote with remoteID #6400 delay(3000); } void sendButton(unsigned int remoteID, byte keycode) { for (pulse= 0; pulse <= 180; pulse = pulse+1) { // how many times to transmit a command sendPulse(1); // Start high = true; // first pulse is always high for (i = 16; i>0; i--) { // transmit remoteID byte txPulse=bitRead(remoteID, i-1); // read bits from remote ID selectPulse(txPulse); } for (i = 7; i>0; i--) { // transmit keycode byte txPulse=bitRead(keycode, i-1); // read bits from keycode selectPulse(txPulse); } } digitalWrite(txPin, LOW); } // build transmit sequence so that every high pulse is followed by low and vice versa void selectPulse(byte inBit) { switch (inBit) { case 0: for (byte ii=1; ii<3; ii++) { if (high == true) { // if current pulse should be high, send High Zero sendPulse(2); } else { // else send Low Zero sendPulse(4); } high=!high; // invert next pulse } break; case 1: // if current pulse should be high, send High One if (high == true) { sendPulse(3); } else { // else send Low One sendPulse(5); } high=!high; // invert next pulse break; } } // transmit pulses // slightly corrected pulse length, use old (commented out) values if these not working for you void sendPulse(byte txPulse) { switch(txPulse) { // transmit pulse case 1: // Start digitalWrite(txPin, HIGH); delayMicroseconds(500); // 550 digitalWrite(txPin, LOW); break; case 2: // "High Zero" digitalWrite(txPin, LOW); delayMicroseconds(100); // 110 digitalWrite(txPin, HIGH); break; case 3: // "High One" digitalWrite(txPin, LOW); delayMicroseconds(300); // 303 digitalWrite(txPin, HIGH); break; case 4: // "Low Zero" digitalWrite(txPin, HIGH); delayMicroseconds(100); // 110 digitalWrite(txPin, LOW); break; case 5: // "Low One" digitalWrite(txPin, HIGH); delayMicroseconds(300); // 290 digitalWrite(txPin, LOW); break; } }



Мы жертвуем библиотеке
В принципе, можно было бы остановиться на новом коде, но он все равно загромождал основную программу, и другим желающим его использовать пришлось бы прибегать к ненужному копипасту.

Поэтому я решил немного потренироваться «на котах» и превратить это в отдельную библиотеку.

В этом процессе неоценимую помощь оказали инструкции на сайте Arduino.cc .

На русском инструкция опубликована на Arduino.ru .

Получилось точно по рецепту (ни шагов в сторону, ни прыжков на месте).

Файл h, cpp, readme и небольшой пример, показывающий, как всем этим счастьем пользоваться.

Лилволо.

h

/* Livolo.h - Library for Livolo wireless switches. Created by Sergey Chernov, October 25, 2013. Released into the public domain. */ #ifndef Livolo_h #define Livolo_h #include "Arduino.h" class Livolo { public: Livolo(byte pin); void sendButton(unsigned int remoteID, byte keycode); private: byte txPin;

Теги: #Сделай сам или Сделай сам #сделай сам #программирование #Умный дом #arduino

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.