Как я узнаю ежедневные новости с помощью матричного принтера

в 16:01, , рубрики: dot matrix printer, php, raspberry pi zero w

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

Недавно я приобрел матричный принтер на Ebay и подумал, что это прекрасная возможность создать собственную сводку новостей, отпечатанную и готовую к прочтению. Это я и сделал!

ASMR‑звуки принтера ниже 👇

В этой статье я покажу, что было использовано, как все настроить, а также PHP‑скрипт, который всем этим управляет.

«Полный код доступен в репозитории на Github»

Приобретение железа

Список необходимого для данного проекта сравнительно небольшой и, за исключением самого принтера, большая часть может быть найдена на Amazon или в других онлайн‑магазинах.

Принтером, который я приобрел, стал Star NP-10 из середины 80-х. Я не могу дать 100% гарантию, но по идее любой матричный принтер с последовательным портом должен сработать. Цены на них варьируются от 80 до 120 долларов, но я смог ухватить этот за примерно половину этой стоимости, потому что он был помечен как «не уверен, что работает».

Конечно, потребовалось почистить его и настроить картридж с красящей лентой (прямо как в печатной машинке, разве это не здорово?), но после этого он сразу запустился и отпечатал тестовую страницу.

Затем я подключил все друг к другу. Raspberry Pi подключена к WiFi и к последовательному порту принтера с помощью переходника с USB. После включения принтера и подключения к Pi по ssh, можно убедиться, что принтер доступен как /dev/usb/lp0.

Итак, как же печатать на этой штуке?

Разбираемся с кодом принтера

Так как принтер доступен как lp0, я хотел проверить, напечатается ли что‑либо, если просто отправить текст на него с помощью echo. Я выполнил следующую команду:

echo "Hello, world!" > /dev/usb/lp0

Что выдало ошибку о том, что файл недоступен. Вот ведь, ошибка прав доступа. Это можно легко исправить с помощью chmod:

sudo chmod 666 /dev/usb/lp0

Возможно, есть более корректный вариант решения вопроса, но после выполнения команды мне удалось отправить echo и текст успешно напечатался на принтере. Итак, мы можем отправлять сырые данные на принтер через этот файл, так что давайте попробуем пойти дальше.

Я использую php для повседневных задач, и эта — не исключение. Я написал простенький скрипт, который открывает файл с помощью fopen() и записывает в него текст. Я попробовал отправить несколько предложений, несколько пустых строк, а также Unicode‑арт, но быстро обнаружил, что поддержка символов на принтере гораздо меньше, чем ожидалось.

Как я узнаю ежедневные новости с помощью матричного принтера - 1

Похоже, пришло время разобраться, как он работает на самом деле. Благодаря усилиям людей, собирающих различную информацию, мне удалось найти полный мануал к принтеру в pdf‑формате.

Оказалось, что либо из‑за веяний времени, либо из‑за определенных решений при производстве, у этого принтера очень специфический набор поддерживаемых символов. Примерно опираясь на Code Page 437 для IBM PC, он состоит по большей мере из стандартных цифр и букв, но также имеет небольшой набор спецсимволов, линий и блоков. Отлично!

Отправлять данные символы достаточно просто, можно просто использовать hex‑значения с помощью PHP следующим образом:

<?php
$horizontalDouble = "xCD";
$deg = "xF8";
 
echo str_repeat($horizontalDouble, 24);
echo '78' . $deg . 'F' . PHP_EOL;

Итак, мы разобрались, как печатать текст на нашем принтере, добавлять спецсимволы и оформлять текст. Теперь надо разобраться, что именно я хочу читать по утрам.

Сбор данных

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

Также, поскольку проект экспериментальный, я хотел избежать излишних трат при получении информации, а в идеале — избежать их совсем. К счастью, есть прекрасный репозиторий на Github со списком бесплатных и публичных API, так что я прошелся по нему и отобрал нужные.

  • Погода подтягивается с Open‑Meteo, API‑ключ не требуется.

  • Данные по состоянию акций можно получить с twelvedata благодаря бесплатному плану.

  • Заголовки новостей получаются с NYTimes, бесплатного плана которого достаточно для данного проекта.

  • Посты на reddit подтягиваются из бесплатного Reddit JSON (но пришлось заспуфить User‑Agent).

Для каждой из секций я написал простой PHP‑код для получения пэйлоада с эндпоинта API и сбора нужных данных в общий массив. Мне нужна была информация только для определенных акций, типов заголовков и сабреддитов. Если у какой‑либо из секций не было данных — скрипт просто завершается и перезапускается позже.

Это можно увидеть в сниппете получения заголовков новостей:

<?php
// Get news headlines data
echo "Fetching news headlines data..." . PHP_EOL;
$newsUrl = NEWS . "?api-key=" . NEWSKEY;
$newsData = [];
$newsAmount = 0;
 
$data = json_decode(file_get_contents($newsUrl), true);
 
if (!isset($data['results'])) {
    die("Unable to retrieve news data");
}
 
foreach ($data['results'] as $article) {
    if (
        ($article['type'] === 'Article') &&
        (in_array($article['section'], ['U.S.', 'World', 'Weather', 'Arts'])) &&
        ($newsAmount < MAXNEWS)
    ) {
        $newsData[] = $article;
        $newsAmount++;
    }
}

Константы NEWS, NEWSKEY и MAXNEWS инициализируются в начале скрипта для упрощения редактирования.

При запуске скрипта собирается вся информация, которую я хочу увидеть на бумаге, но теперь нам нужно разобраться с форматированием и отправлением в печать.

Печать ежедневной сводки

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

Потребовалось немного вычислений, но мне удалось заставить все работать, используя комбинацию hex‑значений, упомянутых ранее, str_repeat и знания того, что ширина страницы данного принтера — 80 символов.

Теперь мы просто проходим по каждой из секций и печатаем небольшой заголовок:

<?php
str_repeat($horizontalSingle, 3) . " WEATHER " . str_repeat($horizontalSingle, (PAGEWIDTH - 9)) . "n";

И затем печатаем нужные для секции данные:

<?php
"   " . round(($weatherData['daily']['daylight_duration'][0] / 3600), 2) . "h of Sunlight  -  Sunrise: " . date('g:ia', strtotime($weatherData['daily']['sunrise'][0])) . "  -  Sunset: " . date('g:ia', strtotime($weatherData['daily']['sunset'][0])) . "n";

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

Если просто передавать длинную строку принтеру, он достаточно умен, чтобы разделить её и продолжить печать на следующей строке. Но я не хотел, чтобы он разрезал слово посередине и переносил его часть на новую строку, так что я написал небольшую функцию для обработки длины строки и возвращения массива строк с длиной, не превышающей ширину страницы.

<?php
function splitString($string, $maxLength = PAGEWIDTH) {
    $result = [];
    $words = explode(' ', $string);
    $currentLine = '';
 
    foreach ($words as $word) {
        if (strlen($currentLine . $word) <= $maxLength) {
            $currentLine .= ($currentLine ? ' ' : '') . $word;
        } else {
            if ($currentLine) {
                $result[] = $currentLine;
                $currentLine = $word;
            } else {
                // If a single word is longer than maxLength, split it
                $result[] = substr($word, 0, $maxLength);
                $currentLine = substr($word, $maxLength);
            }
        }
    }
 
    if ($currentLine) {
        $result[] = $currentLine;
    }
 
    return $result;
}

После чего я могу использовать ее следующим образом:

<?php
foreach (splitString($redditPost) as $line) {
    fwrite($printer, $line) . "n";
}

Теперь все, что осталось — запустить скрипт!

Использование и подведение итогов

Я могу запускать печать вручную просто выполняя php print.php, но вместо этого я настроил крон‑задание, которое будет делать это вместо меня.

Каждое утро примерно в 8 утра начинается печать моей личной сводки. Я отрываю ее и просматриваю утром за чашкой кофе.

Как я узнаю ежедневные новости с помощью матричного принтера - 2

Может прозвучать глупо, но мне нравится иметь ограниченное количество новостей на одном листе бумаги. Это дает возможность дочитать и остановиться на этом вместо бесконечного скроллинга сайтов и приложений соцсетей.

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

Автор: MrPizzly

Источник


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js