Весы, часы и заметки: цепочка, которая ведёт мой вес без меня
Задача
В начале года я взялся за программу по составу тела, и она держится на регулярных замерах: вес раз в неделю, воскресенье утром, натощак. Плюс дневная рутина – шаги, сон, пульс покоя, стресс. Плюс силовые тренировки с подходами.
Всё это я честно вносил руками ровно три недели. Потом пропустил одно воскресенье, потом ещё одно, и через месяц дневник превратился в набор дыр, по которому нельзя понять ничего.
Проблема была не в лени. Данные существовали – просто в трёх разных местах, и ни одно из них не разговаривало с двумя другими:
- умные весы 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, бэкап перед записью, явные границы своей территории. Три простых правила, каждое из которых стоит десять строк кода.
Автоматизация меняет не удобство, а вероятность. Ручной замер я не пропущу с вероятностью процентов семьдесят – и на длинной дистанции этого хватает, чтобы данные стали бесполезными. Автоматический не пропущу с вероятностью почти единица. Это разница не в комфорте, а в том, есть у меня данные или нет.