Pairlink BLE протокол Ergostol

Протокол управления стоячим столом Ergostol через BLE. Reverse-engineered из Android APK com.pairlink.ergostol v1.2.9.7 (jadx decompilation, март 2026).

Это не стандартный JIECANG протокол (который использует FFE1/FFE2 и фреймы F1 F1). Ergostol использует кастомный протокол Pairlink (normalLib SDK, тип LIB_DIRECT_DING).

BLE Connection

ПараметрЗначение
Device nameStruchkovErgostol (настраивается через FF06)
Device UUID47E28F21-C8A7-BDAA-0563-85A64B7CF23B
Service UUIDFF12
Write characteristicFF01 (Write, WriteWithoutResponse)
Notify characteristicFF02 (Notify)
Name characteristicFF06 (Read/Write, ASCII)

Полные UUID характеристик:

  • Write: 0000ff01-0000-1000-8000-00805f9b34fb
  • Notify: 0000ff02-0000-1000-8000-00805f9b34fb

Дополнительные сервисы (не для управления)

ServiceCharacteristicsНазначение
00006287-3C17-D293-8E48-14FE2E4DA2126387 (Wn), 6487 (W,N)OTA (предположительно)
0000D0FF-3C17-D293-8E48-14FE2E4DA212FFD1, EFD2, FFD3, FFD4, FFD5, FFD8, FFF1, FFF2, FFE0Device info/config

Static reads при подключении

CharacteristicЗначениеОписание
FF0653 74 72 75 63 68 6B 6F 76 45 72 67 6F 73 74 6F 6C”StruchkovErgostol”
EFD2EC C5 7F 63 66 E1MAC address (reversed)
FFD301 00 24 00Unknown config
FFD404 08 01 02 01Unknown config
FFF105 01 00 04 00 08 00 00 00 0F 00 00Device capabilities
FFE001 00 24 00 01 40 00 00Unknown config

Формат пакета

Все пакеты — 6 байт:

[cmd] [param] [data_hi] [data_lo] [crc_lo] [crc_hi]

CRC16

Отправка (app → стол): CRC по [0x04, 0xFC, 0x42, 0x06, cmd, param, data_hi, data_lo]

Приём (стол → app): CRC по [0x01, 0xFC, 0x41, 0x06, cmd, param, data_hi, data_lo, crc_lo, crc_hi] = 0

Lookup table:

[0, 52225, 55297, 5120, 61441, 15360, 10240, 58369, 40961, 27648, 30720, 46081, 20480, 39937, 34817, 17408]

Алгоритм:

crc = 0xFFFF
for byte in data:
    nibble_lo = (crc & 0x0F) ^ (byte & 0x0F) & 0x7F
    crc = ((crc >> 4) ^ table[nibble_lo]) & 0xFFFF
    nibble_hi = (crc & 0x0F) ^ ((byte >> 4) & 0x0F) & 0x7F
    crc = ((crc >> 4) ^ table[nibble_hi]) & 0xFFFF

Источник: com.pairlink.ergostol.utils.c, метод a(byte[], int), строка 59-68.

Команды (отправка)

cmdparamОписаниеИсточник
11Движение ВНИЗControlFragment.onTouch
21Движение ВВЕРХControlFragment.onTouch
31Пресет STANDControlFragment.onClick
41Пресет MIDDLEControlFragment.onClick
51Пресет SITControlFragment.onClick
61/2/3Сохранить позицию (1=sit, 2=stand, 3=middle)CustomSetupFrg
64Переключить cm/inch
71-11Запрос init данных (последовательно)Init handshake
81Запрос текущей высотыHeartbeat/poll
91СТОПControlFragment.stop_run
10variesНастройка чувствительности
11variesРежим handset
12variesЗапрос reminder mode

Special case: cmd=1 (DOWN) при ошибке

Когда код ошибки 18 (E02) или 258 (E102), data_lo устанавливается в 1 вместо 0. Это разрешает движение только вниз при ошибке anti-collision.

Ответы (приём)

cmd=7: Init данные

param определяет тип данных:

paramdata_hi:data_loЗначение
1hall valueПозиция Stand
2hall valueПозиция Middle
3hall valueПозиция Sit
40:valueНастройка cm/inch (1=cm, 2=inch)
5hall valueBase height (desk_base_h)
6hall valueMin height (desk_min_h)
7hall valueMax height (desk_max_h)
8hall valueТекущая высота (desk_cur_h)
90:versionMCU версия → определяет делитель (g.u)
100:stateСостояние handset
110:stateHandset running

cmd=8: Обновление высоты

paramЗначение
1Движение (высота в data)
2Отскок
3Движение завершено
5Отскок завершён
6Позиция сохранена
7Reboot reset complete

cmd=9: СТОП ACK

param=1, финальная высота в data.

cmd=11: Handset уведомление

Физические кнопки стола активны. Пример пакета:
0B 01 00 00 69 3D = cmd=11, param=1, data=0x0000, crc=0x3D69

Ошибка: param=0x80

Код ошибки в (data_hi << 8) | data_lo.

Кодирование высоты

hall_value = (data_hi << 8) | data_lo    // big-endian uint16
height_cm = hall_value / divisor + base_height_cm

Делитель зависит от MCU версии (определяется при init, param=9):

MCU verДелительbase_minbase_maxrange
129.333334176022001408
229.333334176022001320
311.0660825715
444.0264033002860
526.0156019501170
658.666668352044002698
729.8178822351430
826.0156019501248
927.5165020621787
1044.0264033002112
1122.0132016501430

Источник: com.pairlink.ergostol.utils.c, метод b(int), строка 319-433.

Init последовательность

Обязательна перед отправкой команд:

  1. Подключиться к BLE, подписаться на FF02
  2. Отправить cmd=7, param=1
  3. Стол отвечает — отправить cmd=7, param=2
  4. Продолжать до param=11 (каждый ответ триггерит следующий запрос: param=N+1)
  5. После param=9 (MCU version) устанавливается делитель (g.u)
  6. После param=11 → отправить cmd=7, param=32 (0x20) (init complete)
  7. Отправить cmd=8 для текущей высоты

Поток движения

  1. Отправить cmd=1 (вниз) или cmd=2 (вверх)
  2. Стол шлёт cmd=8 с текущей высотой
  3. Отправить cmd=9 для остановки
  4. Стол отвечает cmd=9 с финальной высотой

Пресеты: cmd=3/4/5 → стол движется → cmd=8 (param=3) когда достиг позиции.

Gotchas (реальное поведение)

Обнаружено при работе с реальным столом (MCU v4):

  • param=32 echo: Стол отвечает на cmd=7 param=32 (init complete signal) своим cmd=7 param=32. Обработчик init должен игнорировать params вне 1-11, иначе бесконечный цикл.
  • Нет height notifications при ручном движении: При cmd=1/2 (вверх/вниз) стол НЕ шлёт cmd=8 param=1. Необходим polling — отправлять cmd=8 каждые 100-200мс.
  • Height query response: На cmd=8 param=1 (query) стол отвечает cmd=8 param=6 (не param=1). Param=6 содержит актуальный hall value.
  • Command echo: Стол эхом отвечает на движение (cmd=1 → RX cmd=1, cmd=2 → RX cmd=2). Эти ответы не содержат полезных данных.
  • Стабилизация: Определять окончание движения по стабилизации hall value (5+ одинаковых показаний подряд), а не по param=3 (movement finished), который не всегда приходит.

Источники

  • APK: com.pairlink.ergostol v1.2.9.7, декомпилирован jadx

Ключевые классы

ФайлНазначение
com.pairlink.ergostol.utils.cФормирование пакетов, CRC, логика отправки
com.pairlink.ergostol.activity.MainActivityprocessPDU() — парсинг ответов, on_recv_dat()
com.pairlink.ergostol.fragment.ControlFragmentUI кнопки → команды
com.pairlink.normalLib.BLEDeviceManagerBLE соединение, discovery характеристик, write queue
com.pairlink.normalLib.BLEUtilHex конвертация, byte утилиты
com.pairlink.ergostol.utils.gГлобальные флаги состояния