Это история о том, как я превратила свою бывалую Kindle Touch в инструмент мониторинга времени прибытия автобусов с ежеминутным обновлением экрана и возможностью выйти из режима дашборда через кнопку меню. По сути, у меня получился TRMNL, только без ценника в $140.

Весь процесс состоял из пяти этапов:

  • Взлом Kindle.

  • Установка KUAL & MRPI.

  • Настройка SSH.

  • Запуск сервера с доступом через интернет (или локальным) для предоставления Kindle изображений.

  • Создание приложения KUAL.

1. Взлом Kindle

Вот вам талмуд по взлому Kindle для этапов 1 — 3. Потребуется выяснить версию самого устройства, версию его прошивки (ищите аббревиатуру FW в руководствах с форумов и readme-файлах), скачать подходящий tar-файл и следовать инструкциям.

После успешного взлома нужно будет установить кое-какое ПО.

2. Установка KUAL & MRPI

KUAL — это кастомный лаунчер приложений Kindle, а MRPI позволяет устанавливать пользовательские приложения (если у вас достаточно новая версия Kindle, вам MRPI может не потребоваться). Этот этап оказался утомительным — перечитывание обсуждений на форумах вызывает у меня головную боль. Самым полезным ресурсом оказался Kindle Modding Wiki. Возможно, это только я такая невнимательная, но мне потребовалось полдня, чтобы заметить кнопку «Next Step» для перехода к следующему шагу инструкций.

Меня сбило с толку то, что сначала нужно было выполнить инструкции из «Setting up a Hotfix» и только потом устанавливать KUAL и MRPI.

После успешной установки этих инструментов я также решила отключить обновления OTA. Остальные инструкции мне показались ненужными.

3. Настройка SSH

Это делается с помощью расширения KUAL под названием USBNetwork (можно скачать из того же талмуда по взлому), которое позволяет подключаться к устройству по SSH как к обычному серверу.

Тем не менее ни на одном форуме я не нашла информацию о том, как вообще устанавливать расширение KUAL с помощью MRPI. В конечном итоге меня выручил этот пост по настройке SSH для Kindle. Я выполнила все шаги, где объяснялось, как устанавливать расширение и настраивать SSH через USB. Остальные инструкции я пропустила, так как не планирую добавлять пароль или налаживать работу SSH по WiFi.

Если вы успешно настроили SSH, то при подключении Kindle к ПК в сетевых подключениях должен появиться новый элемент в состоянии «Connected».

Вот сетевые характеристики моей Kindle после удачного подключения:

Поздравляю! Теперь ваша Kindle готова работать с кастомным кодом.

4. Запуск сервера, генерирующего изображение для Kindle

Для показа кастомных данных на дисплее Kindle нужно создать png-файл, который будет соответствовать его разрешению, после чего отправить эту картинку на Kindle.

Я живу в Нью-Джерси, поэтому мне хотелось видеть время прибытия автобусов, предоставляемое ресурсом NJTransit. К моему везению, у этого сервиса есть публичный сервер GraphQL, который сообщает время прибытия автобуса на любую указанную остановку по её номеру.

Получение данных с NJ Transit

Пошерстив сетевую вкладку сайта NJ Transit Bus, я нашла тот самый запрос к GraphQL, на который возвращается номер автобуса, текущая заполненность, а также время до его отправления и прибытия в минутах:

query BusArrivalsByStopID($stopID: ID!) {
    getBusArrivalsByStopID(stopID: $stopID) {
      departingIn
      destination
      route
      time
      capacity
      __typename
    }
  }

Если вы тоже живёте в Джерси, то вот вам curl для получения ближайшего времени прибытия автобусов (не забудьте заменить YOUR_STOP_NUMBER):

curl 'https://www.njtransit.com/api/graphql/graphql' \
  -H 'accept: */*' \
  -H 'accept-language: en-US,en;q=0.9' \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/json' \
  -H 'pragma: no-cache' \
  -H 'priority: u=1, i' \
  --data-raw $'{"operationName":"BusArrivalsByStopID","variables":{"stopID":"YOUR_STOP_NUMBER"},"query":"query BusArrivalsByStopID($stopID: ID\u0021) {\\n  getBusArrivalsByStopID(stopID: $stopID) {\\n    departingIn\\n    destination\\n    busID\\n    route\\n    time\\n    vehicleID\\n    capacity\\n    __typename\\n  }\\n}"}'

Создание сервера

В большинстве руководств, которые я успела изучить (два самых полезных: Matt Healy’s Kindle Dashboard guide и Hemant’s Kindle Dashboard guide) для конвертации HTML в png авторы использовали библиотеку puppeteer. Но мне такой вариант не подходит, так как я экономлю и для всех своих пет-проектов использую единственный сервер за $6. Как только я запускаю на нём puppeteer, он начинает жутко тормозить.

Вместо этого я создала эндпоинт для форматирования данных в HTML и настроила контейнер Docker, запустив на нём задачу cron, которая каждые три минуты выполняет команду wkhtmltoimage для генерации нового изображения на основе этого эндпоинта. После генерации сервер отправляет файл на другой эндпоинт.

Вот как выглядят эти две конечные точки для моей Kindle:

  • Первая, на основе которой wkhtmltoimage генерирует изображение.

  • Вторая, которую Kindle использует для получения изображения.

Весь код сервера — Dockerfile, скрипты и сам сервер — лежит в каталоге server моего репозитория Kindle hax. Он написан на Node, так как сначала я использовала puppeteer, пока не столкнулась с проблемами производительности. Хотя будет интересно поупражняться в оптимизации, переписав его на Go.

Генерация изображения

Главный нюанс в том, что изображение должно соответствовать разрешению экрана вашей Kindle. Чтобы это разрешение узнать, подключитесь к книге по SSH и выполните eips -i . С помощью этой же команды вы будете выводить изображение на дисплей. Вот полезное руководство по её использованию.

В ответ вы увидите что-то вроде:

Моей Kindle нужны картинки 600x800, и их надо заранее поворачивать. Если при генерации изображения не выполнить команду rotate, оно в итоге искажается:

Но даже после поворота информацию можно было читать только горизонтально, а я хотела расположить книгу вертикально. То есть мне нужно было повернуть сам HTML. Но если сначала поворачивать изображение, а потом делать снимок, то из-за того, что поворот происходит вокруг центра экрана, wkhtmltoimage обрезает область, где указано время до прибытия автобуса. Нужного результата мне удалось добиться, совместив rotate с translate, чтобы повёрнутое изображение выравнивалось по верхнему левому краю дисплея:

transform: rotate(90deg) translateX(-100px) translateY(-100px);

Настроив сервер и эндпоинт для получения изображения, можно переходить к последнему этапу.

5. Создание приложения KUAL

На этом этапе я преследовала две задачи: наладить простой способ выхода из режима дашборда и иметь относительно свежее расписание автобусов. Во всех инструкциях, которые я к этому моменту изучила, авторы запускали на своих Kindle задачу cron, которая обращалась к эндпоинту через установленный интервал времени. Но мне такой подход не понравился, так как я не хотела, чтобы после перезапуска Kindle каждый раз включался дашборд. Поэтому, чтобы иметь возможность управлять его включением, мне потребовалось создать собственное приложение KUAL.

Вот его общая структура:

bin/ # Исполняемые скрипты.
menu.json # Управление элементами меню в панели KUAL.
config.xml # Понятия не имею, что здесь.

Подключившись к Kindle по SSH, разместите свой каталог расширений по адресу /mnt/us/extensions/. Если вы использовали мой код custom-dash, то после перезагрузки Kindle и запуска KUAL увидите своё расширение, в котором будет кнопка «Start dashboard».

Скрипт запуска дашборда

Нажав «Start Dashboard», вы увидите, что в menu.json выполняется bin/start.sh. В скрипте запуска есть комментарии, объясняющие, что он конкретно делает. Вот некоторые интересные моменты, с которыми я раньше не сталкивалась:

# Игнорирует HUP, так как kual после запуска дашборда закроется, а это убьёт наш длительный скрипт.
trap '' HUP
...
# Игнорирует term, так как из-за связи kaul с GUI при остановке framework/gui нашему скрипту будет отправлен сигнал TERM.
trap '' TERM
...
trap - TERM

Вот хорошая статья, где подробно описывается команда trap в bash. Если коротко, то без игнорирования определённых сигналов скрипт будет завершаться раньше времени.

Заставить работать rtcwake тоже было непросто. У меня не получалось вызвать rtcwake для дефолтного устройства (без флага -d). Пришлось вывести список доступных и уже из него выбрать нужное. В моём случае на команду rtcwake реагировало rtc1.

do_night_suspend() {
  sync
  rtcwake -d rtc1 -m mem -s "$WAKE_IN_SECONDS"
}

Далее важная функция, refresh_screen. Собственно, ради неё мы и занимались всей этой настройкой сервера и генерации изображений. Она получает снимок с эндпоинта, дважды очищает экран, отрисовывает изображение и размещает его так, чтобы сверху осталось место для строки состояния. В завершении выводится дата/время, статус WiFi и остаток заряда батареи.

refresh_screen() {
  curl -k "$SCREEN_URL" -o "$DIR/screen.png"
  eips -c
  eips -c
  eips -g "$DIR/screen.png" -x 0 -y 30 -w gc16
  # Размещает дату/время и индикатор батареи в верху (eips не умеет выводить %, поэтому мы убираем его из вывода gasgauge-info -c)
  eips 1 1 "$(TZ=EST5EDT date '+%Y-%m-%d %I:%M %p') - wifi $(cat /sys/class/net/wlan0/operstate 2>/dev/null || echo '?') - battery: $(gasgauge-info -c 2>/dev/null | sed 's/%//g' || echo '?')"
}

Эта часть скрипта отслеживает нажатие кл��виши меню.

script -q -c "evtest /dev/input/event2 2>&1" /dev/null | grep -m 1 -q "code 102 (Home), value 1" && "$DIR/stop.sh"

Для прослушивания входящих событий на указанном устройстве я использовала команду evtest . В моём случае при каждом нажатии кнопки меню эта команда выводила code 102 (Home), value 1.

Когда пользователь тапает кнопку меню, вызывается скрипт stop.sh, который закрывает дашборд, очищает экран и перезапускает UI, возвращая книгу в штатный режим.

Выводы

Я уже пользуюсь своим обновлённым Kindle больше двух месяцев и хочу отметить два момента.

Размытость цвета

Несмотря на то, что я дважды чищу экран перед отрисовкой нового изображения, размытие всё равно остаётся. Даже после работы в течение пары дней. Я предполагаю, что если перед выключением Kindle на ночь сначала залить весь экран чёрным, а потом белым, это решит проблему. Но пока не пробовала.

Батарея

Сейчас её хватает до 5 дней работы без подзарядки. Мне бы хотелось растянуть это время где-нибудь до двух недель. Выключение книги на 10 часов перед сном помогло продлить период работы на ещё два дня, но до двух недель пока далеко. Предполагаю, что можно увеличить интервал обновления экрана, так как сейчас он составляет одну минуту. Но мне всё же хочется, чтобы обновление происходило достаточно активно, поэтому оставлю эту меру на крайний случай.

А так результат получился отпадный! Пожалуй, один из самых интересных проектов за последнее время. Теперь мы пользуемся новой функцией каждый день перед выходом из дома. И это намного проще, чем отправлять SMS с номером остановки сервису NJ Transit. Теперь я вижу на экране книги кучу полезной информации — календарь, погоду, ежедневные задачи… В общем, предел только в нашем воображении.