Недавно получил свой заказ с новой логической платой от Sensor Watch для вездесущих классических часов Casio F-91W. Модель F-91W не требует представления. Это наверняка самые популярные кварцевые часы в мире, которых в общей сложности было продано около 90 миллионов.
В купленной мной плате Sensor Watch оригинальный кварцевый механизм F-91W заменён новым
В устройстве нет Bluetooth, но комбинация легковесного, проверенного временем корпуса с долгоживущей батареей и функциональностью, которую без проблем можно воссоздать дома, на удивление великолепна. Где-то за час я смог: заменить плату, настроить двухфакторную аутентификацию (2FA) для своих аккаунтов Google и GitHub, чтобы получать наиболее часто используемые OTP-коды прямо на своё запястье, и написать циферблат-счётчик, который можно использовать для отсчёта шагов или взмахов при гребле на лодке.
Хакать такой девайс — одно удовольствие. Причём для него есть даже эмулятор на основе WASM, который упрощает тестирование на ПК и позволяет поиграться с различными кастомными сборками, например, с моей (в оригинале статьи доступна интерактивная версия часов, — прим. пер.):
Нажмите MODE один раз, чтобы перейти на экран 2FA-токенов. Сейчас ALARM выполняет переключение между токенами Google и GitHub. Не беспокойтесь, я заменил свои реальные секреты TOTP фиктивными значениями. Нажмите ещё раз MODE, чтобы перейти на разработанный мной циферблат со счётчиком. Теперь периодически нажимайте ALARM, чтобы измерять частоту в минуту или иной интересующий вас показатель. Среди прочих циферблатов в этой сборке присутствуют мировые часы, калькулятор времени восходов/закатов, индикатор лунных фаз, показания температурного датчика в реальном времени, установщик формата 24h и режим настройки даты/времени. В дереве исходного кода movement
репозитория Sensor Watch есть и много других крутых циферблатов, включая тонометр и модель Солнечной системы.
Весь процесс апгрейда модуля F-91W хорошо описан в блоге Джона Грэхама-Камминга — я даже заказал один экземпляр его крутых оранжевых часов, чтобы переставить туда новую плату.
Ниже я поделюсь о том, как перенести ваши секреты TOTP в сборку, и расскажу о своей разработке нового циферблата.
Циферблат TOTP
Этот циферблат генерирует одноразовые пароли с привязкой ко времени (двухфакторные коды аутентификации), позволяя безопасно авторизовываться на многих популярных сайтах (например, Google и GitHub). Механизм TOTP (time-based one-time password) представляет собой алгоритм, который генерирует одноразовый пароль (OTP), обеспечивая его уникальность на основе текущего времени.
Нажмите кнопку ALARM для переключения между настроенными сайтами/секретами TOTP.
Этот циферблат поддерживает несколько секретов сайтов, которые нужно извлекать из QR-кодов TOTP и добавлять в его исходный код следующим образом:
- Получать секрет TOTP или QR-код от сайта, для которого требуется сгенерировать коды.
- Если у вас есть только QR-код, сайт Штефана Сандина позволит извлечь его секрет в кодировке Base32 — это будет буквенно-цифровая строка из примерно 32 символов.
- Для добавления полученного секрета в код циферблата нужно преобразовать его в шестнадцатеричные байты. Сделать это можно на сайте cryptii.com. Обратите внимание, что TOTP-секрет нужно вводить в верхнем регистре.
- Наконец, вам нужно взять полученные шестнадцатеричные байты, добавить их в исходный код циферблата TOTP и заново скомпилировать
movement
:
▍ Редактирование totp_face.c
Вы можете решить удалить демо-ключи. Предположим, вы захотите добавить ключ в конец списка. В таком случае прибавьте единицу к числу в этой строке:
static const uint8_t num_keys = 2;
Добавьте шестнадцатеричные байты, полученные на шаге 3, в конец этого массива, разделив их запятыми и указав в начале каждого 0x
.
static uint8_t keys[] = {
// Добавьте шестнадцатеричные байты ключа
};
Добавьте в конец этого массива размер секрета (количество только что внесённых шестнадцатеричных байтов):
static const uint8_t key_sizes[] = {
Добавьте ещё одну запись 30 в конец этого массива.
static const uint32_t timesteps[] = {
Добавьте метку для секрета. Например, если он относится к аккаунту Google, можете указать в качестве неё символы { 'g', 'o' }
.
static const char labels[][2] = {
Вот и всё — наслаждайтесь всеми удобствами кодов TOTP на своём запястье!
▍ Написание собственного циферблата-счётчика
Весь код находится в этом пул-реквесте, который я отправил в основной проект.
Написать эту функциональность оказалось, на удивление, легко — практически вся реализация уместилась в одной основной функции.
bool ratemeter_face_loop(movement_event_t event,
movement_settings_t *settings,
void *context) {
(void) settings;
ratemeter_state_t *ratemeter_state = (ratemeter_state_t *)context;
char buf[14];
Эта функция обрабатывает все нужные вам события нажатий кнопок, а также каждый тик часов.
switch (event.event_type) {
При этом, если вас интересует измерение промежутков времени, обработка анимаций или нечто аналогичное, то циферблат может запрашивать частоту тактов.
В дереве movement
присутствует вспомогательная функция watch_display_string
, которая как умеет старается отрисовывать буквенно-цифровую строку для различных элементов дисплея Casio, состоящих из 7 и более сегментов. При попытке вывести на эту ограниченную поверхность произвольные строки возникают всяческие проблемы, но всё это достаточно хорошо описано в документации.
Итак, перечислю все состояния часов, которые нас интересуют.
При активации циферблата в индикаторе дней отображается RA.
case EVENT_ACTIVATE:
watch_display_string("ra ", 0);
break;
При нажатии MODE происходит переключение на следующий циферблат.
case EVENT_MODE_BUTTON_UP:
movement_move_to_next_face();
break;
При нажатии LIGHT включается подсветка.
case EVENT_LIGHT_BUTTON_DOWN:
movement_illuminate_led();
break;
А теперь самое важное… При нажатии ALARM:
- Обновляется вычисленная частота на основе промежутка между текущим нажатием кнопки и предыдущим.
- Сбрасывается счётчик тактов (часть специального состояния циферблата, который я написал).
- Сбрасывается частота быстрых тактов (эта константа определена как 1/16 секунды).
case EVENT_ALARM_BUTTON_DOWN:
if (ratemeter_state->ticks != 0) {
ratemeter_state->rate =
(int16_t)(60.0 /
((float)ratemeter_state->ticks /
(float)RATEMETER_FACE_FREQUENCY));
}
ratemeter_state->ticks = 0;
movement_request_tick_frequency(RATEMETER_FACE_FREQUENCY);
break;
И, наконец, при каждом тике… Обновляется дисплей, отображая текущую частоту или «Hi», если частота больше 500/минуту, либо «Lo», если она составляет 1/минуту. При этом также инкрементируется счётчик тактов.
case EVENT_TICK:
if (ratemeter_state->rate == 0) {
watch_display_string("ra ", 0);
} else {
if (ratemeter_state->rate > 500) {
watch_display_string("ra Hi", 0);
} else if (ratemeter_state->rate < 1) {
watch_display_string("ra Lo", 0);
} else {
sprintf(buf, "ra %-3d pn", ratemeter_state->rate);
watch_display_string(buf, 0);
}
}
ratemeter_state->ticks++;
break;
Вот и всё! Проект получился проще и интереснее, чем я ожидал.
Если вам понравился этот апгрейд, можете тоже заказать себе плату Sensor Watch с Oddly Specific Objects. Я с ними никак не аффилирован, просто считаю, что Джо реализовал действительно крутую идею.
Автор: Дмитрий Брайт