Документация
Внешние устройства
Датчик давления в шине, самодельный экран и «Третий глаз»: что телефон умеет находить рядом с собой.
Кроме колеса, телефон умеет разговаривать ещё с тремя вещами рядом: с датчиком давления в шине, с самодельным экраном и с зеркалом «Третий глаз». Общего у них немного — каждое живёт на своём конце Bluetooth, и все трое настраиваются в разделе «Связь и датчики», — но ни одно не колесо, и потому собраны здесь.
Часы — тоже устройство на том же конце связи, но им отведён свой раздел: Часы.
Датчик давления в шине (TPMS)#
Раздел «Связь и датчики».
Датчик навинчивается на ниппель и раз в сколько-то секунд вещает в эфир давление, температуру и заряд своей батарейки. Приложение слушает этот эфир параллельно с колесом: подключаться к датчику не нужно, он никому не отвечает и ничего не принимает.
Поддержаны iBar и совместимые, Pecham/SYTPMS и Sysgration. Добавлены экспериментальные разборы TPMSII, Michelin TMS, WODHMIEY/ITPMS K и Salutica FOBO по исходникам открытого проекта tpms_ble; эти дополнительные форматы пока не проверены на живых датчиках. Порода определяется по рекламе, в том числе пакетам только с manufacturer data.
Привязка. Найденный датчик приложение предлагает привязать к текущему колесу: «Подключить» привязывает, «Не сейчас» откладывает до следующего раза, «Игнорировать» прячет этот датчик насовсем — чтобы чужое колесо на парковке не спрашивало каждый раз. Передумали — «Искать скрытые датчики» возвращает игнорированные в поиск. Сменили датчик — «Заменить TPMS».
Если датчик не находится. Он вещает редко и не всегда: покрутите колесо или стравите немного воздуха, чтобы датчик проснулся, и держите телефон рядом. Поиск ограничен по времени и честно говорит, что время вышло, — молчащий экран тут был бы хуже всего.
Давление и температура шины попадают на дашборд обычными метриками, и алерт на них ставится так же, как на любую другую. Заряд самого датчика показывается в вольтах у тех пород, которые сообщают его вольтами, — процент там был бы выдумкой.
Последнее давление остаётся на плитке, когда датчик молчит, без ограничения в 30 секунд. Это не свежее измерение: устаревшие показания исключаются из алертов. Между перезапусками приложения последнее измерение не сохраняется. На iPhone manufacturer-only датчики без сервисов FBB0/27A5 обнаруживаются только при открытом приложении; в фоне сохраняется прежний фильтр по сервисам.
Внешний экран#
Раздел «Связь и датчики». Android и iPhone.
Телеметрию можно отдавать самодельному экрану — например, на ESP32. Включите «Внешний экран», и телефон сам найдёт его в эфире, подключится и начнёт слать кадры. Экран при этом ничего не ищет и не требует кнопок для выбора телефона: рекламируется он, подключается телефон.
Под строкой выключателя видно, что происходит: идёт ли поиск, или экран найден и в него уже пишут. Ниже — список всех экранов, которые слышно в эфире: имя, если железка его рекламирует, и уровень сигнала. Телефон пишет в первый найденный, потому что экран у человека обычно один; если их несколько, нажмите на нужную строку, и дальше телефон будет писать только в неё. Выбор запоминается и первым попавшимся больше не подменяется — если выбранного экрана нет в эфире, телефон ждёт его, а не пишет в чужой.
Список живёт, пока открыт раздел настроек: держать поиск включённым всю поездку — это радио и батарея, а смотреть на список в это время всё равно некому.
Если экран остался дома, телефон продолжает его искать и больше ничего не делает: Android повторяет попытку раз в пять секунд, iPhone держит поиск включённым. Если экран пропал из эфира на ходу, телефон подключится к нему сам, как только тот вернётся.
Что должен уметь экран#
- рекламировать сервис
1d0f2f9c-6d1a-4d96-ae61-2a4fd97c2c01; - держать в нём характеристику
1d0f2f9c-6d1a-4d96-ae61-2a4fd97c2c02, доступную на запись, лучше без подтверждения; - принимать в неё кадр из двадцати байт, пять раз в секунду.
Двадцать байт — это дефолтный MTU за вычетом заголовка ATT: кадр долетает без согласования MTU вообще. Телефон пишет без подтверждения — на пяти кадрах в секунду подтверждение каждого стоит дороже, чем экономит.
Кадр#
Тот же компактный кадр, что уходит на часы по запасному каналу. Порядок байтов — big-endian, целые со знаком.
| Offset | Размер | Поле |
|---|---|---|
| 0 | 1 | 0x4C, магия L |
| 1 | 1 | 0x45, магия E |
| 2 | 1 | версия, сейчас 0x01 |
| 3 | 1 | счётчик кадров, растёт по модулю 256 |
| 4..5 | 2 | маска присутствия полей |
| 6..7 | 2 | скорость, км/ч ×10 |
| 8..9 | 2 | ШИМ, % ×10 |
| 10..11 | 2 | напряжение, В ×10 |
| 12..13 | 2 | заряд, % ×10 |
| 14..15 | 2 | температура, °C ×10 |
| 16..17 | 2 | пробег за поездку, км ×10 |
| 18..19 | 2 | предел ШИМ для тревоги, % ×10 |
Биты маски, от нулевого: скорость, ШИМ, напряжение, заряд, температура, пробег, предел ШИМ.
Сброшенный бит означает, что поля в этом кадре нет — читать его значение не нужно. В самих
двух байтах при этом лежит 0x8000 (Int16.MIN), так что экран, читающий поля напрямую,
тоже отличит «нет величины» от нуля.
Магию и версию приёмник обязан проверять и молча отбрасывать всё, что не 4C 45 01: версия
кадра — единственный способ его когда-нибудь расширить.
Счётчик кадров нужен, чтобы отличить свежий кадр от повтора: связь без подтверждения теряет пакеты молча, и экран, у которого счётчик не меняется несколько секунд, вправе показать, что данные устарели.
Если экран умеет больше#
Семь величин — это всё, что влезает в двадцать байт. Экран, которому нужно больше, держит
вторую характеристику 1d0f2f9c-6d1a-4d96-ae61-2a4fd97c2c07, тоже на запись, и получает в
неё раз в секунду всё: до тридцати трёх метрик колеса и итог текущей поездки.
Характеристика необязательная. Нет её — телефон шлёт только двадцатибайтовый кадр, ровно как раньше, и экран, собранный по этой странице год назад, ничего не заметит.
Как снимок доезжает#
Снимок весит пару сотен байт и в одну запись при обычном MTU не влезает, поэтому едет кусками. У каждого куска свой заголовок, а у первого — ещё длина и контрольная сумма всей передачи:
| Смещение | Размер | Поле |
|---|---|---|
| 0 | 1 | 0x4C |
| 1 | 1 | 0x45 |
| 2 | 1 | версия нарезки, 0x01 |
| 3 | 1 | флаги: бит 0 — последний кусок |
| 4..5 | 2 | номер куска, с нуля |
| 6..9 | 4 | только в куске 0: длина всей передачи |
| 10..13 | 4 | только в куске 0: CRC32 всей передачи |
| дальше | данные |
Собирать надо по номерам, а в конце проверять длину и CRC32 (обычный IEEE, полином
0xEDB88320). Без этой проверки потерянный кусок склеится в мусор, который выглядит как
данные. Пишет телефон с подтверждением: потеря любого куска роняет весь снимок, и раз в
секунду подтверждение себя окупает.
Размер куска телефон берёт от согласованного MTU минус три байта. Android просит 517 и берёт то, что дали; iPhone MTU не просит — его согласует система. Экран, который согласится на большой MTU, получит снимок одной-двумя записями вместо десятка.
Что внутри снимка#
| Смещение | Размер | Поле |
|---|---|---|
| 0 | 1 | 0x4C |
| 1 | 1 | 0x45 |
| 2 | 1 | версия, 0x03 |
| 3 | 1 | флаги: бит 0 — следом блок поездки |
| 4..11 | 8 | метка времени, мс |
| 12 | 1 | N — сколько метрик |
| 13.. | 5×N | записи: код метрики (1 байт) и значение (4 байта, со знаком) |
Дальше, если бит 0 стоит, блок поездки на 22 байта: длительность в мс (4), пробег (4), максимум скорости (4), максимум ШИМ (4), минимум заряда (4), сколько раз сработали алерты (2).
Все числа — big-endian. Значения метрик и поездки умножены на 1000, а не на 10, как в
компактном кадре: разбег ячеек меряется милливольтами, и одна шкала обязана вместить и его, и
стотысячный одометр. 0x80000000 (Int32.MIN) означает «телефон эту величину не знает».
Коды метрик раздаются один раз и больше не переиспользуются, поэтому незнакомый код надо пропускать, а не показывать под чужим именем: снимок от телефона новее вашего экрана — обычное дело.
| Код | Метрика | Код | Метрика |
|---|---|---|---|
| 1 | скорость, км/ч | 18 | температура АКБ макс., °C |
| 2 | ШИМ, % | 19 | тангаж, ° |
| 3 | напряжение пакета, В | 20 | крен, ° |
| 4 | среднее ячейки, В | 21 | угол защиты от падения, ° |
| 5 | перепад ячеек, В | 22 | опасная скорость, км/ч |
| 6 | ток АКБ, А | 23 | давление в шине, бар |
| 7 | ток шины, А | 24 | пробег поездки, км |
| 8 | фазный ток, А | 25 | одометр, км |
| 9 | мощность, Вт | 26 | напряжение зарядки, В |
| 10 | мощность мотора, Вт | 27 | ток зарядки, А |
| 11 | полная мощность, ВА | 28 | мощность зарядки, Вт |
| 12 | заряд, % | 29 | температура зарядника, °C |
| 13 | температура контроллера, °C | 30 | целевое напряжение, В |
| 14 | температура мосфетов, °C | 31 | залито, А·ч |
| 15 | температура мотора, °C | 32 | залито, Вт·ч |
| 16 | температура платы, °C | 33 | зарядка идёт (0 или 1) |
| 17 | температура АКБ мин., °C |
Метрики, которых ваше колесо не сообщает, в снимок не попадают вовсе — их там просто нет, и это не то же самое, что ноль.
Кнопки на экране#
Экран с кнопками может управлять колесом: включить и выключить фару, посигналить, выключить колесо, переключить автосвет телефона, автосвет самого колеса (Inmotion V12 и новее) и охрану (плагин парковочной сигнализации). Автосвет колеса с экрана пока переключается только с Android; остальное — и с Android, и с iPhone. Байты для колеса собирает телефон — тем же кодом, что и кнопки дашборда, — поэтому команды одинаковы для всех колёс: экрану не нужно знать, какое колесо на связи. Кнопки, которых у колеса нет, телефон экрану и не предлагает.
Выключение телефон выполняет только на стоящем колесе, медленнее 3 км/ч, как бы экран ни просил: погасить колесо под едущим — уронить человека.
Для этого экран держит ещё одну характеристику, 1d0f2f9c-6d1a-4d96-ae61-2a4fd97c2c08, с
уведомлениями и записью без подтверждения. Нет её — всё работает как раньше, только без
кнопок.
Нажатие экран шлёт уведомлением из семи байт: опкод и номер нажатия (шесть байт, big-endian). Номер нужен, чтобы повтор того же уведомления не выключил только что включённую фару; для нового нажатия — новый номер.
| Опкод | Команда |
|---|---|
0x10 |
фара — переключить |
0x11 |
автосвет колеса — переключить |
0x12 |
выключить колесо |
0x13 |
сигнал |
0x14 |
автосвет телефона — переключить |
0x15 |
охрана — снять или поставить |
Состояние телефон пишет в ту же характеристику кадром из пятнадцати байт — при каждой смене, сразу после нажатия и раз в секунду:
| Offset | Размер | Поле |
|---|---|---|
| 0..2 | 3 | 4C 45 20 — магия и версия |
| 3 | 1 | счётчик кадров |
| 4 | 1 | переключатели по два бита: 0-1 фара, 2-3 автосвет телефона, 4-5 охрана, 6-7 автосвет колеса |
| 5 | 1 | достоверность, по биту на переключатель в том же порядке |
| 6 | 1 | бит 0 — сигнал есть, бит 1 — выключение есть |
| 7 | 1 | опкод последнего нажатия, 0 — нажатий не было |
| 8 | 1 | чем оно кончилось: 1 выполнено, 2 колесо не умеет, 3 нет колеса, 4 повтор, 5 выключение на ходу отклонено |
| 9..14 | 6 | номер того нажатия |
Два бита переключателя: 00 — у колеса этого нет, кнопку не показывать; 01 — есть, но
неизвестно, включено ли; 10 — выключено; 11 — включено. У фары и автосвета колеса
состояния нет — они всегда 01: колесо его не сообщает, а угадывать по своим командам
телефон не берётся, фару могли включить кнопкой на самом колесе. Состояние есть у автосвета
телефона и у охраны, и у них стоит бит достоверности.
Экран, который подключается сам#
Только на Android. iPhone не умеет рекламироваться сам, поэтому там работает только первый порядок — телефон находит экран.
Есть и обратный порядок: экран сам находит телефон и подписывается на нотификации той же характеристики. Этот путь появился раньше и остаётся рабочим — тот же сервис, та же характеристика, тот же кадр, только читать его надо подпиской, а не ждать записи.
Тот же выключатель держит его живым. Раньше телефон переставал рекламироваться, как только видел часы с LoEUC, и владелец часов автоматически оставался без экрана; теперь включённый «Внешний экран» держит канал поднятым независимо от часов.
Третий глаз#
Раздел «Связь и датчики». Android и iPhone.
«Третий глаз» — маленькое зеркало-дисплей на руле, которое продаётся готовым. Своё приложение у него есть, но цифра на нём заметно отстаёт от происходящего; LoEUC пишет в то же зеркало и той же строкой, только чаще и без лишних задержек.
Включите «Третий глаз» — телефон сам найдёт зеркало в эфире (оно рекламируется именем,
начинающимся на ThirdEye_), подключится и начнёт писать. Под выключателем видно, идёт ли
поиск или зеркало уже принимает кадры, а ниже — список всех зеркал, которые слышно, с
уровнем сигнала. В первый раз телефон пишет в первое найденное зеркало и запоминает его, а
выбранное руками запоминается и не подменяется.
Выключатель живёт сам по себе, а не до конца поездки: включили один раз — и на следующей поездке телефон сам подключится к зеркалу, как только оно появится в эфире. Подключается он только к своему, тому, что было в прошлый раз: зеркало товарища в группе он не перехватит. Чтобы перейти на другое зеркало, выберите его в списке.
Пока колесо подключено, телефон ищет своё зеркало всю поездку: первые 10 минут раз в 10 секунд, дальше раз в 30. Зеркало, включённое посреди поездки или перезагрузившееся, подключится само в течение полуминуты.
У зеркала две короткие строки, и что в них показывать — ваше дело: в каждую годится любая метрика, которую отдаёт колесо, от скорости и заряда до ШИМ и напряжения ячейки. По умолчанию сверху скорость, снизу заряд — как в родном приложении. Любую строку можно выключить, тогда зеркало покажет одну.
Свой заряд зеркало сообщает само, раз в минуту. Он доступен как обычная метрика дашборда — поставьте её плиткой, если не хотите однажды обнаружить зеркало разряженным посреди поездки.
Ничего, кроме текста кадра, телефон зеркалу не шлёт и настройки его не меняет.