Я ношу датчик непрерывного мониторинга глюкозы Sinocare iCan i6 (внутри приложения он называется t6). Новый датчик заработал странно: текущие измерения на телефон приходили, а архив он отдавать отказывался. Таймер прогрева на экране при этом давно закончился. Появилось подозрение, что при запуске приложение пропустило какой-то шаг, и датчик работает «наполовину». Дальше было расследование в духе того, что я делал с прошивкой саундбара Klipsch: разобрать, как устройство и приложение договариваются на самом деле, подобрать рабочий алгоритм и проверить каждый шаг на настоящем датчике.
Сразу о рамке. Это разбор моего собственного устройства, а не медицинский совет. Никакой из описанных шагов не делает датчик точнее и не заменяет глюкометр, а самодельную сборку приложения я не распространяю. Я участвую в разработке JugglucoNG, открытого приложения для таких датчиков, и этот разбор нужен в том числе для переноса поддержки iCan туда.
- Как устроен датчик и почему важен архив
- Главное правило: отправлено, доставлено и принято не одно и то же
- Читаем приложение, а не декомпилятор
- Попытка перезапуска: датчик сказал «нет»
- Заводские параметры отдельно от запуска
- Архив вернулся
- Чистая сборка: оригинал плюс инструменты
- Перенос на второй телефон
- Китайский оригинал и что дальше
- Что из этого применимо не только к датчикам
- Границы выводов
- Частые вопросы
- Почему датчик глюкозы iCan не отдавал архив?
- Можно ли просто перезапустить сессию датчика?
- Стал ли датчик точнее после загрузки параметров?
- Можно ли скачать вашу сборку приложения?
- Нужна консультация?
Как устроен датчик и почему важен архив

Датчик непрерывного мониторинга (CGM) крепится на тело и раз в несколько минут измеряет глюкозу в межклеточной жидкости, мой делает это раз в три минуты. Каждое измерение он нумерует и хранит у себя. Телефон получает данные по Bluetooth двумя путями: текущее измерение приходит само, сразу как появилось, а пропущенные, например пока телефон был далеко, приложение догружает из архива датчика отдельным запросом. Если архив не отвечает, любой разрыв связи превращается в дыру в истории.
У моего датчика текущие значения шли, а на каждый запрос истории приходил отказ. Штатное приложение производителя (локализованная сборка SelfCare) при этом показывало, что датчик настроен и прогрет. Вопрос был простой: это датчик неисправен или запуск прошёл не до конца?
Главное правило: отправлено, доставлено и принято не одно и то же
Прежде чем трогать датчик, я сохранил исходные установочные файлы приложения, его базу и журналы обмена по Bluetooth. И договорился сам с собой различать три разных события:
- Приложение вызвало отправку команды. Это намерение, а не действие.
- Bluetooth-стек Android подтвердил запись. Это значит только, что байты дошли до датчика.
- Датчик ответил, что выполнил команду. Это отдельный пакет от самого датчика, и только он говорит о результате.
Штатное приложение в ряде мест считает успехом второе: запись прошла, значит, всё хорошо. Отметка «настроено» в его базе и закончившийся таймер на экране не доказывают, что датчик принял параметры. Это та же ловушка, о которой я писал в статье о цикле OODA: гипотезу легко принять за факт, если не проверить её по первоисточнику.
Читаем приложение, а не декомпилятор
Чтобы понять, как приложение запускает датчик, я разобрал установочный файл. Здесь первая ловушка: популярный декомпилятор jadx не смог восстановить часть асинхронного кода (корутины Kotlin) и вставил на их место заглушки, которые выглядят как настоящие ошибки. Если читать такой Java-код, легко решить, что приложение вообще не выполняет нужную ветку. Поэтому источником истины я сделал дизассемблированный байт-код (smali), то есть то, что реально исполняется, и по нему восстановил порядок действий.
Штатный алгоритм запуска этой версии выглядит так:
- После подключения приложение читает данные устройства, по ним выбирает модель, сверяет серийный номер и проходит авторизацию (пара команд F3 и F4).
- Читает аппаратное время начала сессии и статус с минутным счётчиком. От этих двух ответов зависит всё дальнейшее.
- Если счётчик нулевой, отправляет команду запуска сессии (1A), ждёт и перечитывает статус. Время начала записывает, только если датчик вернул пустое или полностью нулевое значение.
- При определённых условиях строит заводской профиль для этой модели и передаёт его тремя шифрованными пакетами (F2). Есть и отдельный принудительный путь, который может повторить F2 для уже идущей сессии.
- Подписывается на текущие измерения и ответы истории, запрашивает начальный архив и переходит к обычному мониторингу.
Из этого следуют два важных вывода. Если счётчик уже ненулевой, приложение само не отправляет повторный запуск, а сохранённое время начала не перезаписывает. И отметка «настроено» ставится даже тогда, когда передача параметров в этом подключении была пропущена. По журналам видно, что в последних подключениях ни команды запуска, ни заводских пакетов не было, а журнала самого первого подключения не сохранилось. Поэтому версия «запуск прошёл не до конца» оставалась гипотезой.
Нашлась и более простая ошибка. Штатный разбор пакета читал все 16 бит слова глюкозы, тогда как значимы младшие 12, а старшие четыре бита, судя по китайской версии приложения, несут признак состояния, точный смысл которого в прошивке неизвестен. Из-за этого часть измерений превращалась в невозможные 43 ммоль/л, и приложение их отбрасывало. В диагностической сборке я читал только младшие 12 бит: значения стали появляться, но часть из них оказалась подозрительно низкой, а архив по-прежнему не отдавался. В итоговую сборку это исправление не вошло.
Попытка перезапуска: датчик сказал «нет»
Первый эксперимент был максимально узким: одно подключение, одна команда запуска и обязательная остановка при любом отказе. Bluetooth подтвердил запись, а датчик ответил кодом 06 вместо успеха 01. Последовательность остановилась сама: время и заводские параметры после отказа не записывались.
Код 06 здесь означает, что датчик ждёт от приложения калибровочные значения и без них не запущен. Повторять команду запуска поэтому не было смысла: датчику не хватало не её, а заводских параметров. JugglucoNG и китайская версия приложения считают успехом только 01, а штатное приложение ответ датчика здесь не проверяет вовсе, ему хватает подтверждения записи. Поэтому оно и не заметило, что датчик так и остался незапущенным.
Заводские параметры отдельно от запуска
Раз датчик ждал калибровочные значения, следующим шагом я их и передал: загрузил заводские параметры в уже идущую сессию, тем же путём, которым это делает штатное приложение, и с полным запретом на команду запуска и запись времени. Набор параметров содержит 15 числовых полей: профиль модели плюс значение из штрихкода датчика. Приложение шифрует их и отправляет тремя пакетами. Ни идентификатора владельца, ни времени начала сессии в этом наборе нет.
На каждый из трёх пакетов пришло подтверждение записи от Bluetooth и отдельный положительный ответ самого датчика. Время начала сессии и нумерация измерений сохранились. Здесь важно не обмануть себя: ответ датчика означает, что он сообщил о принятии команды. Это не чтение коэффициентов из его памяти, не доказательство завершённой калибровки и тем более не клиническая точность.
Архив вернулся
Решающая проверка была простой: тот же самый запрос истории до и после загрузки параметров. До неё датчик отвечал отказом. Сразу после он вернул 16 архивных точек и код успеха. Затем я запросил архив целиком и получил 131 точку подряд, с 120-го по 510-й номер, с шагом три минуты. Дальше база продолжила расти. Объяснение такое. Без заводского профиля у датчика нет калибровочных значений, и измерения он хранит в памяти без коррекции, то есть неверными. Такой архив он не отдаёт. Когда профиль записан, датчик применяет коррекцию к сохранённым измерениям и начинает выдавать историю.
Это видно по данным. Для 17 ранних измерений, которые раньше пришли как текущие, датчик теперь вернул в архиве другие значения: у них в старших битах стоял тот самый признак состояния, а в архиве он сменился на нормальный. Например, одно измерение было 2,56 ммоль/л, а в архиве оказалось 4,98. Я заменил в базе только поля самого измерения по полученному архиву, сохранив идентификаторы, время и всё остальное, и только после резервной копии. Это восстановление из источника, а не поправка, которую я сам прибавил к показаниям.
Почему архив начинается именно со 120-го номера. Номер измерения у этого датчика равен числу минут с начала сессии, так что 120 означает два часа. Для iCan i6 производитель заявляет прогрев 30 минут вместо двух часов у предыдущей модели, но первые 120 минут вшиты в прошивку датчика: в историю они не попадают, архив начинается со 120-й минуты. На это же рассчитаны оба приложения производителя. Локализованное помечает измерения с 3-й по 117-ю минуту как калибровочные и догружает историю только со 120-й, а китайское до 120-й минуты расшифровывает данные по отдельной схеме. Так что полноценная история у датчика начинается через два часа после старта, что бы ни было написано на коробке.
Чистая сборка: оригинал плюс инструменты
Во время экспериментов накопилось несколько диагностических версий приложения. Для повседневной работы я их выбросил и собрал приложение заново из сохранённых оригинальных установочных файлов, добавив поверх только инструменты. Штатная обработка пакетов, период запроса истории и переподключения остались как у производителя. По подписанному результату я проверил, что экспериментальные варианты протокола в него не попали.
- Отдельное приложение. Своё имя пакета и своя подпись. Root не нужен ни для установки, ни для работы. В настройках можно указать ID для авторизации с уже существующей привязкой датчика, владельца в памяти датчика это не меняет.
- Экспорт. Выгрузка в CSV и JSON, где исходные и скорректированные значения лежат в разных полях.
- Сравнение с глюкометром. Пары «датчик и глюкометр» записываются отдельно. Поправка отображения по умолчанию нулевая, требует минимум трёх пар и не меняет ни исходные записи, ни датчик. Это не калибровка.
- Тревоги. Исправлены ложные тревоги от старых архивных значений, которые приложение принимало за свежие.
- Диагностика. Показывает свежесть данных, аппаратные часы, ответы команд и историю, а открытие экрана не создаёт лишних запросов к датчику. Командный интерфейс для компьютера умеет читать, догружать архив и выгружать данные, но не отправляет датчику произвольные команды записи.
- Чего нет. Заряд батареи и температуру я убрал из инструментов и экспорта: достоверных значений получить не удалось.
Перенос на второй телефон
Последней проверкой стал перенос на другой телефон, складной Fold с Android 16, без root. На первом телефоне я остановил приложение и выключил Bluetooth. Второй прошёл авторизацию и увидел то же аппаратное начало сессии. Явный запрос истории с 120-го номера завершился успешно: в базе 377 точек до номера 1248 с шагом три минуты и без пропусков. Все 368 значений, которые были на обоих телефонах, совпали, а дальше пошли новые текущие измерения.
На Fold заработала запись в Health Connect, системное хранилище данных о здоровье в Android: и архив, и последующие фоновые обновления подтверждены через API. Повторный импорт пары сравнения не создал дубля. Переносить все сравнения автоматически я не стал: время на двух телефонах может немного расходиться, и пара могла бы привязаться не к тому измерению. Ещё несколько мелочей, которые проверены на устройстве:
- текущее значение числом в строке состояния;
- прозрачный прочерк, если свежего проверенного значения нет: устаревшее число не подставляется;
- возраст показания в уведомлении;
- пункты настроек переводятся вместе с языком приложения (на Fold проверен английский);
- ярлык Health Connect.
Китайский оригинал и что дальше
Для сравнения я взял китайскую версию приложения 2.5.0 из официального источника. Её код был защищён: пять защищённых модулей пришлось восстановить, прежде чем удалось выделить драйвер Bluetooth. Статическое сравнение показало отличия в обработке статусов, составе заводского профиля, разборе глюкозы и таймерах. Как китайский клиент на самом деле общается с этим датчиком, я ещё не проверял, поэтому не утверждаю, что протоколы одинаковы. Этот разбор нужен для следующих шагов: перенести рабочий алгоритм в JugglucoNG и разобраться с расходом батареи.
Что из этого применимо не только к датчикам
- Подтверждение доставки не равно результату. Это касается любой интеграции: платёжного шлюза, очереди сообщений, API подрядчика. Проверяйте ответ того, кто выполняет действие, а не того, кто передал байты.
- Сохраните оригинал до экспериментов. Исходные файлы, базу и журналы. Без них нельзя ни откатиться, ни доказать, что изменилось.
- Одна команда, одна транзакция, остановка на отказе. Если система ответила непонятным кодом, не повторяйте вслепую: повтор необратимой операции хуже, чем пауза.
- Источник истины это то, что исполняется. Декомпилятор, документация и экранный статус могут врать. Верить можно байт-коду, журналу обмена и фактически полученным данным.
- Отделяйте восстановление данных от их подгонки. Заменить значение по ответу источника нормально. Прибавить поправку, чтобы цифры выглядели правдоподобно, нельзя.
Границы выводов
Инженерный результат здесь такой: датчик принял заводские пакеты, архив вернулся, приложение работает на втором телефоне и проверенно передаёт данные в фоне. Совпадение отдельного измерения с глюкометром не доказывает точность во всех условиях, и точность датчика я не оценивал. Если вы пользуетесь таким датчиком, решения о лечении принимайте по показаниям сертифицированных устройств и вместе с врачом, а не по самодельной сборке приложения.
Частые вопросы
Почему датчик глюкозы iCan не отдавал архив?
Без заводского профиля у датчика нет калибровочных значений: измерения в его памяти хранятся без коррекции, то есть неверными, и такой архив он не отдаёт. Когда профиль записан, датчик применяет коррекцию и начинает выдавать историю. Это видно по данным: после трёх принятых пакетов параметров тот же запрос вернул архивные точки, а 17 ранних измерений пришли из архива уже исправленными, например 2,56 ммоль/л стало 4,98.
Можно ли просто перезапустить сессию датчика?
В моём случае это не помогло бы: на команду запуска датчик ответил кодом 06, то есть он ждал от приложения калибровочные значения и без них не был запущен. Помогла не повторная команда запуска, а загрузка заводских параметров в уже идущую сессию, без её перезапуска.
Стал ли датчик точнее после загрузки параметров?
Этого я не утверждаю. Ответ датчика подтверждает, что он принял команду, но это не чтение коэффициентов из его памяти и не проверка точности. Точность оценивается сравнением с глюкометром в разных условиях.
Можно ли скачать вашу сборку приложения?
Нет. Это личная сборка для моего устройства, я её не распространяю. Общие наработки по протоколу iCan я переношу в открытый JugglucoNG.
Нужна консультация?
Если у вас устройство или интеграция, которая «вроде работает», но вы не уверены, что она делает на самом деле, запишитесь на разговор. Начнём с того, где у вас подтверждение доставки выдаётся за результат.


