Весы, часы и заметки: цепочка, которая ведёт мой вес без меня

· 7 мин чтения · itsport

Задача

В начале года я взялся за программу по составу тела, и она держится на регулярных замерах: вес раз в неделю, воскресенье утром, натощак. Плюс дневная рутина – шаги, сон, пульс покоя, стресс. Плюс силовые тренировки с подходами.

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

Проблема была не в лени. Данные существовали – просто в трёх разных местах, и ни одно из них не разговаривало с двумя другими:

  • умные весы Mi Body Composition Scale 2 отдавали вес и состав тела в приложение Xiaomi;
  • часы Garmin копили тренировки, сон и пульс в Garmin Connect;
  • программа тренировок и таблица измерений жили в Obsidian у меня на маке.

Официального моста между ними нет ни одного. Xiaomi наружу данные не отдаёт, Garmin Connect про весы Xiaomi не знает, а Obsidian – это просто папка с текстовыми файлами.

Первое звено: весы к часам

Тут была развилка, на которой я сначала пошёл не туда. Логика подсказывала искать интеграцию между облаками: раз есть аккаунт Xiaomi и аккаунт Garmin, должен быть способ подружить их через API. Такого способа нет.

Правильная точка интеграции оказалась гораздо ниже. Весы не ждут облака, чтобы поделиться результатом: как только вы на них встали, они вещают измерение по Bluetooth LE в эфир. Телефон с приложением – всего лишь один из возможных слушателей, и совершенно не обязательно единственный.

Значит, слушателем может быть что угодно. У меня это домашний сервер: в него воткнут BLE-донгл, на нём поднята служба export2garmin, которая ловит рекламные пакеты весов по известному MAC-адресу, разбирает их, считает состав тела и заливает результат в Garmin Connect обычным взвешиванием.

Почему сервер, а не телефон, на котором и так есть Bluetooth: телефон уходит вместе со мной, а сервер стоит дома и слушает круглосуточно. Весы в ванной, сервер в пределах слышимости – измерение подхватывается всегда, даже если телефон в другой комнате или разряжен.

Само измерение прилетает не одним числом. Весы отдают вес и импеданс – сопротивление тела переменному току, которое они меряют через электроды в площадке. Всё остальное: процент жира, мышечная масса, вода, костная масса – считается уже из этих двух величин по формулам, где участвуют рост, возраст и пол. То есть «умные» весы не измеряют состав тела, они измеряют две величины и делают из них оценку.

Понимать это полезно по двум причинам. Во-первых, абсолютные цифры состава тела лучше не воспринимать всерьёз – формулы у всех производителей разные, и одно и то же тело на двух весах получит разный процент жира. Смысл есть только в динамике на одних и тех же весах при одинаковых условиях замера. Во-вторых, из-за этого замер и делается всегда одинаково: утром, натощак, после туалета, до воды и кофе. Импеданс отлично чует уровень гидратации, и стакан воды может «прибавить» вам процент мышц.

После этого звена Garmin стал единственным местом, где лежит вообще всё: и часы, и весы.

Второе звено: часы к заметкам

Токены Garmin лежат на сервере – там же, где export2garmin, чтобы не плодить копии учётных данных по машинам. Сервер отдаёт данные скриптом, который умеет только читать: сходить в Garmin, собрать нужное за N дней и напечатать JSON в stdout. Ничего не пишет, никуда не ходит, состояния не имеет.

Мак раз в несколько часов дёргает этот скрипт по SSH, получает JSON и раскладывает его по заметкам: тренировки с подходами – в дневник силы, дневная таблица – в трекер недели, воскресная строка веса – в таблицу измерений.

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

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

Как автоматика уживается с рукописным текстом

Дневник – это мои личные заметки. Там мои формулировки, мои пометки про самочувствие, про больное запястье и изжогу. Скрипт, который лезет в такой файл, обязан быть безопасным, и безопасность здесь складывается из трёх вещей.

Строго append-only. Скрипт умеет добавлять и не умеет удалять. Даже если он ошибётся, он ошибётся в сторону лишней строки, а не потерянного абзаца.

Бэкап перед каждой записью. Копия файла кладётся рядом до того, как что-то изменится, хранятся последние тридцать. За полгода я воспользовался этим дважды, и оба раза был очень рад, что не поленился.

Маркеры территории. Автоматический блок обёрнут в HTML-комментарии garmin:auto:start и garmin:auto:end. Внутри маркеров хозяйничает скрипт, снаружи – я. Так я могу дописать под таблицей что угодно своими словами, и следующий запуск это не тронет.

Отдельно пришлось повозиться со словарём. Garmin называет упражнения по-своему: BENCH_PRESS, LAT_PULLDOWN, HIP_RAISE. В моей программе они называются «Жим (гантели/штанга)», «Тяга верхнего блока», «Ягодичный мостик». Это перевод, и живёт он в обычном словаре в начале файла, а не размазан по коду. Появилось новое упражнение – дописал строчку. Часы не распознали движение и прислали UNKNOWN – так и пишем, «не распознано часами», потому что честная дырка лучше выдуманного значения.

Запускается всё через launchd дважды в сутки: в десять утра и в одиннадцать вечера. Утренний запуск подхватывает воскресное взвешивание и ночной сон, вечерний – тренировку сегодняшнего дня.

Что ломалось

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

Весы засыпают. Bluetooth LE у таких устройств – это не постоянное соединение, а короткие рекламные пакеты в момент измерения. Если слушателя в этот момент рядом не оказалось, измерение просто исчезает: весы его не хранят и не повторяют. Поэтому донгл должен быть не «где-то в доме», а в реальной слышимости, и это выяснилось не по логам, а по трём пропавшим воскресеньям подряд.

Тишина выглядит как норма. Отдельно неприятно, что сломанная цепочка внешне неотличима от недели, когда я просто не вставал на весы. Нет строки в таблице – и всё, никакой ошибки нигде не появляется. Пришлось завести отдельную проверку живости: сервер раз в сутки докладывает, что служба на месте и донгл виден. Это общее правило для любой домашней автоматики – система, которая сломалась молча, будет молчать неделями, и узнаете вы об этом в самый неподходящий момент.

Часы врут о подходах. Garmin неплохо считает повторы на базовых движениях и совершенно теряется на изоляции и на всём, что делается в тренажёре. Я сначала хотел «дочинить» распознавание словарём и эвристиками, а потом перестал: пусть пишет UNKNOWN, а я поправлю руками там, где это важно. Автоматика, которая уверенно врёт, хуже автоматики, которая честно расписывается в незнании – потому что первую вы перестанете перепроверять.

Часовые пояса. Сервер живёт в UTC, часы отдают локальное время, а «воскресенье утром» – понятие сугубо локальное. Пара замеров уехала в соседний день, пока я не привёл всё к одному поясу на входе, а не по месту использования.

Что в итоге

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

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

Выводы

Точка интеграции обычно ниже, чем кажется. Я потратил вечер на поиски моста между двумя облаками, которого не существует, хотя нужные данные всё это время лежали в открытом виде в эфире на расстоянии двух метров. Когда два сервиса не дружат – стоит спуститься на уровень ниже и посмотреть, что устройство отдаёт само.

Разделяйте читателя и писателя. Одна сторона только отдаёт данные, другая только пишет. Это снимает целый класс проблем – конфликты синхронизации, гонки, «кто последний, тот и прав» – не решением, а конструкцией.

Автоматика, которая правит ваши личные файлы, должна быть обратимой. Append-only, бэкап перед записью, явные границы своей территории. Три простых правила, каждое из которых стоит десять строк кода.

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