Реверс-инжениринг протокола китайского USB ИК трансивера


Попался мне китайский MicroUSB ИК трансивер, и возникло желание подключить его к компу с Windows. Трансивер представляет собой весьма компактный девайс с разъемом Micro USB. Единственный «официальный» вариант работы с ним — через Android приложение под названием ZaZaRemote.

При подключении к компу через переходник девайс определился как HID-совместимое устройство USB\VID_10C4&PID_8468. Гугление по этому ID не дало никаких результатов, и пришлось заняться реверсингом протокола.

Неправильный HID


Класс девайса определялся как USB\Class_03&SubClass_FF&Prot_FF. Class_03 — HID устройство, SubClass_FF — vendor specific. Системой был автоматически установлен драйвер hidusb.sys. С этим драйвером можно работать посредством HID API.

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

HidD_GetManufacturerString: "Tiqiaa"
HidD_GetProductString: "Tview"

HidP_GetCaps:
  Usage 1
  UsagePage ff00
  InputReportByteLength 61
  OutputReportByteLength 61
  FeatureReportByteLength 0
  NumberLinkCollectionNodes 1
  NumberInputButtonCaps 0
  NumberInputValueCaps 2
  NumberInputDataIndices 2
  NumberOutputButtonCaps 0
  NumberOutputValueCaps 2
  NumberOutputDataIndices 2
  NumberFeatureButtonCaps 0
  NumberFeatureValueCaps 0
  NumberFeatureDataIndices 0

Получается что обмен ведется блоками по 61 байту максимум, имеется 2 InputValue и 2 OutputValue интерфейса. Более подробную информацию по ним возвращает функция HidP_GetValueCaps:

HidP_GetValueCaps(HidP_Input):
    UsagePage ff00
    ReportID fe
    LinkUsage 1
    LinkUsagePage ff00
    BitSize 8
    ReportCount 8

    UsagePage ff00
    ReportID 1
    LinkUsage 1
    LinkUsagePage ff00
    BitSize 8
    ReportCount 60

HidP_GetValueCaps(HidP_Output):
    UsagePage ff00
    ReportID fd
    LinkUsage 1
    LinkUsagePage ff00
    BitSize 8
    ReportCount 8

    UsagePage ff00
    ReportID 2
    LinkUsage 1
    LinkUsagePage ff00
    BitSize 8
    ReportCount 60

Из этих данных наиболее интересными являются ReportID — ID report-а (фактически, пакета данных) и ReportCount — его размер. Данные можно отправлять и принимать с помощью функций HidD_SetOutputReport и HidD_GetInputReport соответственно. Поэкспериментировав с этими функциями, с разными ReportID и размером данных, я так и не смог добиться успешного обмена. Поснифав трафик по USB с помощью USBPcap, я обнаружил что данные даже не пытались передаваться. Появилось подозрение что это какой-то неправильный HID.


SET_REPORT Request остался без ответа

Реверсинг приложения ZaZaRemote


В APK файле этого приложения я обнаружил библиотеку libtiqiaa_dev_usb.so. Она экспортирует следующие функции:

Java_com_icontrol_dev_TiqiaaUsbController_usbOpen
Java_com_icontrol_dev_TiqiaaUsbController_usbClose
Java_com_icontrol_dev_TiqiaaUsbController_usbSendCmd
Java_com_icontrol_dev_TiqiaaUsbController_usbSendIR
Java_com_icontrol_dev_TiqiaaUsbController_d

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

LDR     R3, [R0]
LDR     R3, [R3,#0x18]
BLX     R3

Регистр R0 — первый аргумент этой функции. Эти функции вызываются только из Java кода classes.dex. Декомпилировав этот файл, получаем их тип:

private native boolean usbOpen(Context context);
private native void usbClose();
private native boolean usbSendCmd(UsbDeviceConnection usbdeviceconnection, UsbEndpoint usbendpoint, int j, int k);
private native boolean usbSendIR(Context context, UsbDeviceConnection usbdeviceconnection, UsbEndpoint usbendpoint, int j, byte abyte0[], int k);
private native IControlIRData d(byte abyte0[]);

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

Немного изучив декомпилированный Java код, я обнаружил следующие строки:

com.tiqiaa.icontrol.e.i.a("DeviceHolder", (new StringBuilder("send......cmdType=")).append(i1).append(",cmdId=").append(j1).toString());
boolean flag1 = a.b(i1, j1);

com.tiqiaa.icontrol.e.i.d("DeviceHolder", (new StringBuilder("send....................freq=")).append(i1).append(",cmdId=").append(j1).append(",buffer.length=").append(abyte0.length).append(" , device = ").append(a).toString());
boolean flag1 = a.a(i1, abyte0, j1);

Дебаг логи — хороший друг реверс-инженера.

Для вызова нативных методов из Java кода используется механизм Java Native Interface. Экспортируемая функция должна иметь вид:

extern "C" JNIEXPORT void JNICALL Java_ClassName_MethodName(JNIEnv *env, jobject obj, <java arguments>)

Теперь можно задать тип функций в IDA:

bool __cdecl Java_com_icontrol_dev_TiqiaaUsbController_usbOpen(JNIEnv *env, jobject obj, struct Context *context);
void __cdecl Java_com_icontrol_dev_TiqiaaUsbController_usbClose(JNIEnv *env, jobject obj);
bool __cdecl Java_com_icontrol_dev_TiqiaaUsbController_usbSendCmd(JNIEnv *env, jobject obj, struct UsbDeviceConnection *usbdeviceconnection, struct UsbEndpoint *usbendpoint, int cmdType, int cmdId);
bool __cdecl Java_com_icontrol_dev_TiqiaaUsbController_usbSendIR(JNIEnv *env, jobject obj, struct Context *context, struct UsbDeviceConnection *usbdeviceconnection, struct UsbEndpoint *usbendpoint, int freq, jbyte buffer, int cmdId);
struct IControlIRData *__cdecl Java_com_icontrol_dev_TiqiaaUsbController_d(JNIEnv *env, jobject obj, jbyteArray buffer);

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

v5 = ((int (*)(void))(*env)->FindClass)();

Результат декомпиляции функции usbSendCmd
bool __cdecl Java_com_icontrol_dev_TiqiaaUsbController_usbSendCmd(JNIEnv *env, jobject obj, struct UsbDeviceConnection *usbdeviceconnection, struct UsbEndpoint *usbendpoint, int cmdType, int cmdId)
{
  char v6; // r5@5
  bool result; // r0@12
  char data[24]; // [sp+8h] [bp-18h]@12

  dword_B57C = 0;
  if ( UsbEndpoint_bulkTransfer )
  {
    if ( usbdeviceconnection )
    {
      if ( usbendpoint )
      {
        switch ( cmdType )
        {
          case 0:
            v6 = 'L';
            goto LABEL_12;
          case 2:
            v6 = 'R';
            goto LABEL_12;
          case 3:
            v6 = 'H';
            goto LABEL_12;
          case 4:
            v6 = 'O';
            goto LABEL_12;
          case 6:
            v6 = 'C';
            goto LABEL_12;
          case 7:
            v6 = 'V';
            goto LABEL_12;
          case 1:
            v6 = 'S';
LABEL_12:
            data[0] = 'S';
            data[1] = 'T';
            data[3] = v6;
            data[2] = cmdId;
            data[4] = 'E';
            data[5] = 'N';
            result = sub_3528(env, usbdeviceconnection, usbendpoint, data, 6);
            break;
          default:
            result = 0;
            break;
        }
      }
      else
      {
        result = 0;
      }
    }
    else
    {
      result = 0;
    }
  }
  else
  {
    result = UsbEndpoint_bulkTransfer;
  }
  return result;
}

Код элементарный, формат сообщений очевиден: начинается с сигнатуры «ST», далее идет байт типа команды — один из символов {'L', 'R', 'H', 'O', 'C', 'V', 'S'}, далее байт cmdId (просто инкрементальный идентификатор чтобы сопоставить команду и ответ на нее) и заканчивается все сигнатурой «EN». Сформированное сообщение отправляется функцией sub_3528.

Код функции sub_3528
int __cdecl sub_3528(JNIEnv *env, struct UsbDeviceConnection *usbdeviceconnection, struct UsbEndpoint *usbendpoint, void *data, int size)
{
  JNIEnv v5; // r1@1
  JNIEnv *v6; // r4@1
  int result; // r0@2
  int v8; // r2@3
  int tsize; // r7@5
  size_t fragmSize; // r5@9
  int v11; // r0@11
  JNIEnv v12; // r3@11
  int v13; // r6@11
  int rdOffs; // [sp+10h] [bp-80h]@7
  int v15; // [sp+14h] [bp-7Ch]@15
  int jbyteArray; // [sp+18h] [bp-78h]@1
  int fragmCnt; // [sp+1Ch] [bp-74h]@7
  char *_data; // [sp+28h] [bp-68h]@1
  char buf[64]; // [sp+34h] [bp-5Ch]@7
  int _stack_chk_guard; // [sp+74h] [bp-1Ch]@1

  _data = (char *)data;
  v5 = *env;
  v6 = env;
  _stack_chk_guard = ::_stack_chk_guard;
  jbyteArray = ((int (*)(void))v5->NewByteArray)();
  if ( jbyteArray )
  {
    v8 = UsbPackCounter + 1;
    if ( UsbPackCounter + 1 > 15 )
      v8 = 1;
    tsize = size;
    UsbPackCounter = v8;
    if ( size > 1024 )
      tsize = 1024;
    j_j_memset(buf, 0, 0x40u);
    buf[0] = 2;
    buf[3] = (tsize / 56 & 0x7F)
           + ((((tsize + -56 * (tsize / 56 & 0x7F)) >> 31) - (tsize + -56 * (tsize / 56 & 0x7Fu))) >> 31);
    rdOffs = 0;
    fragmCnt = 0;
    buf[2] = UsbPackCounter;
    while ( 1 )
    {
      if ( rdOffs >= tsize )
        goto LABEL_25;
      fragmCnt = (fragmCnt + 1) & 0xFF;
      fragmSize = tsize - rdOffs;
      if ( tsize - rdOffs > 56 )
        fragmSize = 56;
      buf[1] = fragmSize + 3;
      buf[4] = fragmCnt;
      j_j_memcpy(&buf[5], &_data[rdOffs], fragmSize);
      ((void (__fastcall *)(JNIEnv *, int, _DWORD, signed int))(*v6)->SetByteArrayRegion)(v6, jbyteArray, 0, 61);
      v11 = ((int (__fastcall *)(JNIEnv *))(*v6)->ExceptionCheck)(v6);
      v12 = *v6;
      v13 = v11;
      if ( v11 )
      {
        ((void (__fastcall *)(JNIEnv *))v12->ExceptionClear)(v6);
        v13 = 0;
        goto return_r6_del;
      }
      if ( !dword_B2A4 )
      {
LABEL_25:
        v13 = 1;
        goto return_r6_del;
      }
      v15 = ((int (__fastcall *)(JNIEnv *))v12->CallIntMethod)(v6);
      if ( ((int (__fastcall *)(JNIEnv *))(*v6)->ExceptionCheck)(v6) )
      {
        ((void (__fastcall *)(JNIEnv *))(*v6)->ExceptionClear)(v6);
        goto return_r6_del;
      }
      if ( v15 < 0 )
        break;
      rdOffs += fragmSize;
    }
    v13 = 0;
return_r6_del:
    ((void (__fastcall *)(JNIEnv *, int))(*v6)->DeleteLocalRef)(v6, jbyteArray);
    result = v13;
  }
  else
  {
    ((void (__fastcall *)(JNIEnv *))(*v6)->ExceptionClear)(v6);
    result = 0;
  }
  if ( _stack_chk_guard != ::_stack_chk_guard )
    j_j___stack_chk_fail(result);
  return result;
}


Эта функция немного посложнее. Видно, что максимальная длинна отправляемого сообщения ограничивается 1024 байтами. Сообщение делится на фрагменты. Фрагмент состоит из 5 байт заголовка и максимум 56 байт данных — в сумме 61 байт. Структура заголовка:

  • buf[0] = 2 — константа. Помните ReportID 2? Похоже что это оно и есть. А ReportCount 60 — это размер оставшихся данных — тоже совпадает.
  • buf[1] = fragmSize + 3 — размер данных фрагмента + 3, т.е. размер считается от байта, следующего за этой переменной.
  • buf[2] = UsbPackCounter — просто счетчик, 1..15.
  • Значение buf[3], вычисляемое навороченным выражением, это просто число фрагментов, можно переписать как:

    buf[3] = tsize / 56;
    if (tsize % 56) buf[3]++;
  • buf[4] = fragmCnt — номер фрагмента, 1..buf[3].

Сформированные фрагменты отправляются через вызов CallIntMethod. Он имеет следующий тип:

jint (JNICALL *CallIntMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...);

Видно, что HexRays на этот раз не справился — в аргументах только v6 = JNIEnv *env. Однако в ассемблерном коде все на месте:

LDR     R2, [R2,#(dword_B2A4 - 0xB284)] ; jmethodID methodID

methodID хранится в переменной dword_B2A4. Посмотрим откуда он взялся:



Запись производится в функции usbOpen и usbClose. Очевидно, нас интересует usbOpen.

Нужный фрагмент:

v27 = ((int (__fastcall *)(JNIEnv *, int, const char *, const char *))(*v4)->GetMethodID)(
        v4,
        v26,
        "bulkTransfer",
        "(Landroid/hardware/usb/UsbEndpoint;[BII)I");
dword_B2A4 = v27;

Итак, это метод UsbEndpoint::bulkTransfer. И ничего — связанного с HID!

Теперь рассмотрим как передаются ИК коды — функция usbSendIR.

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

Фрагмент функции usbSendIR
  buf[0] = 'S';
  buf[1] = 'T';
  buf[2] = cmdId;
  buf[3] = 'D';
  if ( freq > 255 )
  {
    LOBYTE(v36) = 0;
    v37 = -1;
    v38 = 0;
    while ( 1 )
    {
      v39 = (freq - IrFreqTable[v38] + ((freq - IrFreqTable[v38]) >> 31)) ^ ((freq - IrFreqTable[v38]) >> 31);
      if ( v37 != -1 && v39 >= v37 )
      {
        v39 = v37;
      }
      else
      {
        LOBYTE(v36) = v38;
        if ( !v39 )
          break;
      }
      if ( ++v38 == 30 )
        break;
      v37 = v39;
    }
  }
  else
  {
    v36 = (unsigned int)(freq - 1) <= 0x1C ? freq : 0;
  }
  buf[4] = v36;
  v40 = &buf[v22];
  v40[5] = 'E';
  v40[6] = 'N';

Как и в случае с остальными командами, начинается все с «ST», затем следуют cmdId и код команды 'D', далее байт, определяющий частоту — если аргумент freq > 255 — он ищется в таблице частот IrFreqTable, иначе — копируется напрямую. Затем идут данные и завершается все на «EN».

Функция с обфусцированным именем «d» оказалась парсером принятых данных.

Смена драйвера и работа через bulkTransfer


Изучив HID API я выяснил, что оно в принципе не предоставляет возможности использовать bulkTransfer — значит придется менять драйвер. Для работы с этим устройством подходит драйвер WinUsb.
Написав inf файл, я сменил драйвер на WinUsb и попробовал отправлять команды. Все заработало и был получена реакция от девайса — в ответ на отправку команд (через WinUsb_WritePipe) приходил ответ аналогичного формата.

Дамп обмена с ZaZaRemote


Несмотря на предыдущие успехи, я пока что так и не смог добиться главного — заставить девайс передавать ИК команды. Приложение было слишком большим и запутанным и захотелось просто сдампить USB трафик. Однако как это сделать в случае Android-приложения? Вход был найден в виде Android-x86 на VirtualBox-e. Несмотря на то, что x86 ни разу не ARM, он тем не менее позволяет запускать нативные ARM бинарники через механизм NativeBridge. Установив и настроив необходимое ПО я смог заставить работать это приложение в VirtualBox.

Запрошенные разрешения определенно вызывают доверие к этой софтине


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

Выяснилось что девайс способен не только передавать, но и принимать ИК команды, однако прием работал весьма так себе — с расстояния 10 см, и то через раз.

Пример обмена
- Передача:

OUT:
0040 02 09 01 01 01 53 54 12 56 45 4e .....ST.VEN

IN:
0040 01 30 07 01 01 53 54 12 56 30 01 30 30 30 30 30 .0...ST.V0.00000
0050 30 30 30 2d 30 30 30 30 2d 30 30 30 30 2d 30 30 000-0000-0000-00
0060 30 30 2d 30 30 30 30 30 30 30 30 30 30 30 31 09 00-000000000001.
0070 45 4e ff ff ff df ff f9 ef ff df ff bf fb ff EN.............

OUT:
0040 02 09 02 01 01 53 54 13 53 45 4e .....ST.SEN

IN:
0040 01 0a 08 01 01 53 54 13 53 09 45 4e 30 30 30 30 .....ST.S.EN0000
0050 30 30 30 2d 30 30 30 30 2d 30 30 30 30 2d 30 30 000-0000-0000-00
0060 30 30 2d 30 30 30 30 30 30 30 30 30 30 30 31 09 00-000000000001.
0070 45 4e ff ff ff df ff f9 ef ff df ff bf fb ff EN.............

OUT:
0040 02 3b 03 02 01 53 54 14 44 00 ff ff ff ff b7 7f .;...ST.D.......
0050 7f 1b a3 23 a3 23 a3 69 a3 23 a3 23 a3 23 a3 23 ...#.#.i.#.#.#.#
0060 a3 23 a3 69 a3 69 a3 23 a3 69 a3 69 a3 69 a3 69 .#.i.i.#.i.i.i.i
0070 a3 69 a3 69 a3 23 a3 23 a3 23 a3 69 a3 .i.i.#.#.#.i.

OUT:
0040 02 2f 03 02 02 23 a3 23 a3 23 a3 23 a3 69 a3 69 ./...#.#.#.#.i.i
0050 a3 69 a3 23 a3 69 a3 69 a3 69 a3 7f 7f 7f 7f 7f .i.#.i.i.i......
0060 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 57 45 ..............WE
0070 4e N

IN:
0040 01 0a 09 01 01 53 54 14 4f 09 45 4e 30 30 30 30 .....ST.O.EN0000
0050 30 30 30 2d 30 30 30 30 2d 30 30 30 30 2d 30 30 000-0000-0000-00
0060 30 30 2d 30 30 30 30 30 30 30 30 30 30 30 31 09 00-000000000001.
0070 45 4e ff ff ff df ff f9 ef ff df ff bf fb ff EN.............

- Прием:

OUT:
0040 02 09 01 01 01 53 54 17 56 45 4e .....ST.VEN

IN:
0040 01 30 0c 01 01 53 54 17 56 30 01 30 30 30 30 30 .0...ST.V0.00000
0050 30 30 30 2d 30 30 30 30 2d 30 30 30 30 2d 30 30 000-0000-0000-00
0060 30 30 2d 30 30 30 30 30 30 30 30 30 30 30 31 09 00-000000000001.
0070 45 4e ff ff ff df ff f9 ef ff df ff bf fb ff EN.............

OUT:
0040 02 09 02 01 01 53 54 18 53 45 4e .....ST.SEN

0040 01 0a 0d 01 01 53 54 18 53 09 45 4e 30 30 30 30 .....ST.S.EN0000
0050 30 30 30 2d 30 30 30 30 2d 30 30 30 30 2d 30 30 000-0000-0000-00
0060 30 30 2d 30 30 30 30 30 30 30 30 30 30 30 31 09 00-000000000001.
0070 45 4e ff ff ff df ff f9 ef ff df ff bf fb ff EN.............

OUT:
0040 02 09 03 01 01 53 54 19 52 45 4e .....ST.REN

0040 01 0a 0e 01 01 53 54 19 52 13 45 4e 30 30 30 30 .....ST.R.EN0000
0050 30 30 30 2d 30 30 30 30 2d 30 30 30 30 2d 30 30 000-0000-0000-00
0060 30 30 2d 30 30 30 30 30 30 30 30 30 30 30 31 09 00-000000000001.
0070 45 4e ff ff ff df ff f9 ef ff df ff bf fb ff EN.............

OUT:
0040 02 09 04 01 01 53 54 1a 43 45 4e .....ST.CEN

IN:
0040 01 0a 0f 01 01 53 54 1a 43 13 45 4e 30 30 30 30 .....ST.C.EN0000
0050 30 30 30 2d 30 30 30 30 2d 30 30 30 30 2d 30 30 000-0000-0000-00
0060 30 30 2d 30 30 30 30 30 30 30 30 30 30 30 31 09 00-000000000001.
0070 45 4e ff ff ff df ff f9 ef ff df ff bf fb ff EN.............

OUT:
0040 02 09 05 01 01 53 54 1b 4f 45 4e .....ST.OEN

IN:
0040 01 3b 01 0e 01 53 54 00 44 ff ff ff ff ba 7f 7f .;...ST.D.......
0050 19 a4 21 a4 21 a4 68 a4 22 a4 21 a4 22 a4 22 a4 ..!.!.h.".!.".".
0060 22 a4 68 a4 68 a4 21 a4 68 a4 68 a4 68 a4 68 a4 ".h.h.!.h.h.h.h.
0070 68 a4 68 a4 22 a4 22 a4 22 a4 68 a4 22 fb ff h.h.".".".h."..
.....
0040 01 2f 01 0e 0e 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f ./..............
0050 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f ................
0060 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 3e 13 45 .............>.E
0070 4e 82 02 82 02 82 7f 7f 7f 7f 7f 7f 7f fb ff N..............

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

Изучение дампа и последующие эксперименты позволили узнать функции всех команд:

  • 'V' — Version — запрос версии — мой девайс выдает нулевой GUID;
  • 'L' — IdleMode — режим ожидания — в этом режиме девайс находится после подачи питания, либо переходит по этой команде;
  • 'S' — SendMode — режим передачи — используется для посылки ИК сигналов;
  • 'R' — RecvMode — режим приема — используется для приема ИК сигналов;
  • 'D' — Data — в режиме передачи — данные для передачи, в режиме приема — данные захваченной ИК посылки;
  • 'O' — Output — в режиме передачи — подтверждение передачи, в режиме приема — запрос на захват/выдачу данных;
  • 'C' — Cancel — в режиме приема — отмена приема, ранее запрошенного по 'O';
  • 'H' — Unknown — ответ на неизвестную команду.

Исследование формата ИК посылок


Получив дамп управляющих команд и ИК посылок я смог реализовать полноценное управление девайсом — прием и передачу ИК сигнала. Однако для того, чтобы синтезировать произвольный ИК сигнал необходимо было определить формат, в котором он кодируется. Для этого я подключил ИК фотоприемник к осциллографу и стал исследовать посылаемые сигналы. Путем экспериментов я выяснил формат кодирования: старший бит каждого байта определяет включен или нет передатчик, а младшие 7 бит — время. Единица времени оказалась равна 16 мксек. Например: 8A — передатчик включен на 160 мксек; 8A 05 FF 83 — 160 мксек включен, пауза 80 мксек, 2.08 мсек включен.

Когда передатчик включен, светодиод пульсирует с частотой ~36.64 кГц. Теоретически, эта частота должна определяться аргументом freq команды usbSendIR, однако эксперименты показали что девайс абсолютно никак не реагирует на этот аргумент. Тем не менее, имеющаяся у меня бытовая техника нормально принимала сигналы этого трансивера.
Формат данных, записываемых девайсом в режиме приема, оказался аналогичным.

Класс TiqiaaUsbIr и программа IR Control


Я реализовал управление трансивером в виде C++ класса TiqiaaUsbIr и написал простенькую программу CaptureIR на QT. Помимо функций приема и передачи ИК сигналов я реализовал синтезирование и декодирование сигналов по протоколу NEC. Этот протокол используется например в пультах телевизоров LG. Также я реализовал сохранение и загрузку ИК сигналов в исходном формате и формате LIRC. Была идея сделать модуль для WinLirc, однако там оказалось кривоватое и не до конца реализованное API, так что эту идею я пока отложил.

Исходники и скомпилированную программу можно скачать тут.

Пример использования класса TiqiaaUsbIr:

std::vector<std::string> IrDev;
TiqiaaUsbIr::EnumDevices(IrDev); //Получаем список устройств
TiqiaaUsbIr Ir;
Ir.Open(IrDev[0].c_str()); //Открываем первое устройство
Ir.SendNecSignal(0x0408); //Посылаем ИК код (LG POWER)
Ir.Close();

Захваченный сигнал включения питания:



Он же, синтезированный:



В процессе захвата что-то пошло не так:



Итоги


В процессе исследований был полностью восстановлен USB протокол ИК трансивера Tiqiaa Tview, написан inf файл драйвера и ПО для работы с ним.

Рассмотренный ИК трансивер — весьма дешевый, доступный и компактный (5$ на Али, габариты 15 x 10 x 5 мм) девайс для управления бытовой техникой и исследования ее ИК протоколов. К сожалению, управление частотой передатчика оказалось неработоспособным, что в моем случае не вызвало проблем, однако возможно что существует техника с более привередливыми приемниками.

Режим приема, в следствии мизерного радиуса и низкой надежности захвата, непригоден для использования в качестве полноценного ИК приемника — рекорд дальности успешного захвата ~30 см, при этом пульт нацелен точно на приемник, и даже в упор нормально захватываются далеко не все посланные сигналы. Тем не менее, он полезен для захвата сигналов и исследования протоколов ИК пультов.

Бонус


Интересные IR коды телевизоров LG:

POWER 0408
POWERON 04C4
POWEROFF 04C5
IN_STOP 04FA
IN_START 04FB
POWERONLY 04FE
EZ_ADJUST 04FF


P.S. Ищу информацию про LG AccessUSB, подробнее тут.
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 9

    +1
    Разве ида умеет автоматом JNI распознавать?
      +1
      Только декомпилятор HexRays, это плагин.
        0
        А по-моему ghidra может. И она бесплатная.
      +1
      Огромное спасибо!
      В свое время тоже пытался его исследовать, но терпения не хватило.
        +1
        А HID там не совсем неправильный и его вполне можно завести с родным драйвером.
        Краткая теория: HID предусматривает два варианта обмена — через EP0 control transfer (это и делают HidD_SetOutputReport/HidD_GetInputReport), либо через отдельные interrupt endpoints (посредством ReadFile/WriteFile с тем же самым device handle). Причём ОС опрашивают мыши/клавиатуры через interrupt transfers (более оптимальное использование шины), соответственно разработчики железа часто уделяют этому способу больше внимания.
        Китайцы видимо вообще не стали реализовывать работу через control transfer, сделав только interrupt. Сами пакеты — вполне нормальный HID (первый байт — Report ID, второй — длина, дальше — данные этой длины. Лишние данные, добивающие пакет до endpoint MaxPacketSize, как бы даже не часть стандарта).
        Это не единичный случай, когда-то натыкался на такое поведение у мышек A4Tech Bloody, тоже обескуражило.
          0
          Хм я пробовал и через WriteFile — результат был аналогичный HidD_SetOutputReport — т.е. никаких результатов. Дойдут руки — перепроверю, но врятли заработает. А еще с HID драйвером когда вытаскиваешь девайс из разъема — до драйвера доходило только секунд через 5. WinUsb драйвер реагирует мгновенно.
            0
            Проверил — WriteFile дает ERROR_GEN_FAILURE, как и HidD_SetOutputReport. Так что если нет какой-то хитрой настройки, без которой не работает, это таки неправильный HID. Возможно китайцы сначала сделали нормальный HID, но не смогли с ним работать под андроидом и переделали на BulkTransfer.
              0
              Скорее они изначально работали через bulk transfer, а где-то в HID-дескрипторах (которыми сами и не пользуются) допустили ошибки и HID-драйвер сходит с ума на попытке понять конфигурацию устройства (задержка обнаружения отсоединения опять же намекает). С корректно реализованным HID вполне можно было бы общаться и через bulk transfer без переделок (interrupt endpoint нормально понимает bulk пакеты, на уровне шины токены те же).
              Ну да у вас и так всё работает же, замена драйвера — мелочи.
              А внутрь устройства не заглядывали? Немного загадочно что приём такой слабый. Типовой трёхногий ИК-приёмник должен бы одинаково хорошо работать что в телевизоре, что в таком девайсе. Его там «глазом» вовнутрь не поставили часом?
                0
                Внутрь не заглядывал и сомневаюсь что его можно разобрать не разломав — зацепиться не за что, визуально девайс хрупкий, скорее всего на клею или защелках, которые сломаются при попытке разбора.
                Дальность приемника для него по видимому нормальная — родная софтина при захвате пишет чтоб поднесли пульт на 10 см.

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

          Самое читаемое