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

Недавно я приобрел матричный принтер на 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‑арт, но быстро обнаружил, что поддержка символов на принтере гораздо меньше, чем ожидалось.

Похоже, пришло время разобраться, как он работает на самом деле. Благодаря усилиям людей, собирающих различную информацию, мне удалось найти полный мануал к принтеру в 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 утра начинается печать моей личной сводки. Я отрываю ее и просматриваю утром за чашкой кофе.

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

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

Комментарии (19)


  1. sintech
    07.10.2024 16:12
    +8

    Ударный матричный принтер -> матричный принтер

    принтер с серийным портом -> принтер с последовательным портом


    1. attackedpeople
      07.10.2024 16:12
      +8

      Дата рождения:
      30 июля 1997 -> 30 июля 1977


  1. vesowoma
    07.10.2024 16:12
    +2

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

    А если бумагу выбрать помягче, то утро начнется не с кофе, и не будет лишней генерации макулатуры!

    * пошел патентовать туалетный принтер с безопасной красящей лентой


    1. xSVPx
      07.10.2024 16:12

      Кстати отличная тема, чтоб погоду там печатало, новости итп. В принципе, наверное на обратной стороне можно, чтоб упростить сертификацию ;).

      Заодно механизм должен выдавать нужное количество бумаги :)


  1. Yak52
    07.10.2024 16:12
    +3

    А с какого года Центроникс (IEEE 1284) стал последовательным портом?


    1. checkpoint
      07.10.2024 16:12

      Видимо с тех пока как "USB to Parallel IEEE 1284 Printer Cable Adapter (CB-CN36)" на который ссылается автор, превратился с его легкой руки в "Serial to USB Adapter" (именно так написано в оригинальном тексте).


  1. 21224
    07.10.2024 16:12
    +8

    Теперь автор не только подрывает своё душевное здоровье, но и бессмысленно расходует бумагу... Куда больше пользы принесёт проект персонального помощника, который с утра будет пробегать по новостным сайтам и сообщать: "Ничего интересного не произошло, наслаждайтесь вашей реальной жизнью"


    1. FanatPHP
      07.10.2024 16:12

      del


  1. FanatPHP
    07.10.2024 16:12

    Идея конечно интересная, но не очень понятно, почему невозможность получить данные одного субреддита (ну или погоды, если на то пошло) оставляет без распечатки целиком. Я бы всё-таки при возникновении проблем печатал спецсимвол "нишмагла"  ¯\_(ツ)_/¯и продолжал выполнение дальше.


  1. 0xC0CAC01A
    07.10.2024 16:12

    Буханкатролейбус.жыпыгы )


  1. altcms
    07.10.2024 16:12
    +1

    По утрам замечательная замена будильника пока печатает весь дом проснётся.


    1. attackedpeople
      07.10.2024 16:12

      есть риск что организм начнёт успевать за такой азбукой морзе-брайля и на слух поняв что ничего интересного дальше спать - я вот мочевой пузырь не подводит и успеваешь по пути чайником щёлкнуть


  1. Sergey_Kh
    07.10.2024 16:12

    Прикольная идея.


    В наших условиях принтер - 1000 рублей, бумага с отрывной перфорацией 2000 листов А4 - 1000 рублей (хватит на 5 лет).
    Ну и еще плюс лента. Раньше, помню, картриджи заправляли лентой для печатных машинок. Нужно поискать варианты)


  1. inkelyad
    07.10.2024 16:12

    Для усиления эстетического удовольствия надо было пойти дальше и заставить печатать на ленте.


  1. salnicoff
    07.10.2024 16:12

    А ему не жалко бумаги?


  1. PwrUsr
    07.10.2024 16:12

    заодно будильник можно сделать....


  1. checkpoint
    07.10.2024 16:12

    Я правильно понял, что автор статьи НЕ знает как заставить матричный принтер печатать unicode и про escape-последовательности он не слышал ? Тогда о чем статья ? Как парсить Reddit на PHP ?

    PS: Заглянул в манул на NP-10 - все необходимые команды у этого принтера в наличии (Глава 6 указанного руководства).

    PPS: В мануале на принтер половина текста это программы на BASIC с использованием Esc последовательностей. Хорошие были времена, пользователи умели кодить, пусть хотя бы на Барсике.


    1. salnicoff
      07.10.2024 16:12
      +2

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


      1. checkpoint
        07.10.2024 16:12
        +1

        Именно! Об этом и стоило бы написать. И опубликовать код конвертера. В этом была бы какая-то практическая ценность, так как в условиях санкций в случае глобального армагеддона это бы сильно пригодилось.

        У меня в офисе лежит рабочий и заправленный FX-1050. Пару лет назад использовали его для печати маркировки на ракорде фотопленки, из под FreeBSD. В том числе решали проблему с UTF-8.

        PS: Кодировка UTF-8 появилась всего спустя 6 лет с момента выпуска этого принтера - 2 сентября 1992 года, и предложил её не кто иной как Кен Томпсон: You might want to look at /usr/ken/utf/utf.c and see if you can make it prettier.