Пару месяцев назад я в очередной раз прогуливался по комиссионкам, и моё внимание привлёк стоящий на полке агрегат, чем-то похожий на музыкальный центр Bose.

Однако, взяв его с полки, я обнаружил, что это табло-бегущая строка!

Поначалу я подумал, что оно светодиодное, а зачем оно такое мне? Тем более, что у меня уже есть шикарные плазменные часы дома. Ну и поставил было его на полку обратно. Раздавшийся в этот момент громкий звон дал понять — внутри есть струны, а значит, это самый что ни на есть настоящий ВЛИ!

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

❯ Первое включение

Приносим домой, втыкаем в розетку — убеждаемся, что делает оно всё то же, что и на тестовом стенде в комиссионке. Ну, хотя бы по пути не разбили, значит %)

Включается, говорит, что версия 1.12, а затем в цикле жалуется, что никаких сообщений нет. Значит, надо их как-то туда запихать.

❯ Что это за зверь

Шильдик на задней панели гласит, что девайс произведён Nagano Japan Radio Co. Ltd., и имеет модель NJE-105. Отдельная наклейка поверх утверждает, что версия прошивки — та самая, 1.12.

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

Если заглянуть внутрь отсека — там мы находим как раз интерфейсный разъём. С одного конца аж целый DB-25:

А вот с противоположного — нечто, с опознанием чего я затрудняюсь.

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

Продавал такой набор оператор NTT Docomo. То есть, если вам нужно было, например, постоянно в офисе видеть свежие новости — вы покупали пейджер с таким табло и подключали на него подписку на нужные вам каналы. Как только на пейджер придёт сообщение — оно сразу начнёт отображаться на табло.

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

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

Но, к счастью, обладатель пейджера записал хотя бы часть протокола, вот в такой суровой обстановке :-)

https://web.archive.org/web/20040724131622/http://www.hydra.cx/msgbd/mb_labo.JPG

❯ Нужно больше ламповой теплоты!

Снимем переднюю панель и посмотрим, что там внутри.

Чутьё не подвело — это действительно огромный вакуумно-люминисцентный индикатор. Под ним находится лишь блок питания и вентилятор, включённый через термопереключатель на 45 градусов.

Сам дисплей производства фирмы Futaba — они, к сожалению, свернули производство ВЛИ в конце 2021 года.

Второй бит у DIP-переключателя непонятно зачем, а вот первый включает какой-то режим тестирования — скорее всего, для проверки на заводе:

Трёхцветность экрана достигается треугольными субпикселями двух цветов — оранжевого и зелёного:

Оттенки этого дисплея напоминают мне те, что устанавливали в поездах и на станциях, чем он мне и приглянулся.

Оп, моя остановочка!

❯ Протокол

Дальнейший поиск привёл, разве что, к упоминанию о том, что какая-то программа когда-то существовала, но автор удалил её с сайта. К счастью, в интернет-архиве сохранилась другая страница, автор которой смог записать многие управляющие последовательности с оригинального пейджера. Ради сохранения этой информации, ниже привожу вольный перевод описания протокола:

Схема подключения

Судя по всему, распиновка идентична обычному RS-232 25-pin, разве что логические уровни здесь TTL. Изначальный автор применил два инвертера 7414 в качестве буфера, но я бы не рисковал и поставил MAX232. Хотя зачем оно вообще в наше время, когда переходники с TTL UART на USB продаются за сущие копейки :-) — прим. авт.

Формат пакета данных

Передача идёт на скорости 9600 бод, 8N1.

Каждый пакет начинается с \r\n . Дальше идёт текущая дата и время в формате MMDDHHmm (ASCII), например, для 9 марта 16:39 это будет 03091639(hex: 30 33 30 39 31 36 33 39). Зачем это используется, кроме синхронизации внутренних часов табло — непонятно.

После этого — до 128 байт текста в кодировке Shift-JIS. В конце — ещё раз \r\n.

Атрибуты текста

Атрибуты текста задаются в виде двух букв, указывающих цвет и эффект. Если атрибуты вставляются посреди текста, то отбиваются тильдами, например: ~AW~.

Значения атрибутов

Буква

Цвет

A

Зелёный

B

Оранжевый

C

Жёлтый

Буква

Анимация

Эффект

A

Обычная прокрутка

Нету

B

Мигание

C

Инвертированный

D

Мигающий инвертированный

E

Заполнение

Нету

F

Мигание

G

Инвертированный

H

Мигающий инвертированный

I

Пауза

Нету

J

Мигание

K

Инвертированный

L

Мигающий инвертированный

W

Статичное отображение

Нету

X

Мигание

Y

Инвертированный

Z

Мигающий инвертированный

Команды

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

Полный список команд

Команда

Параметры (если есть)

Описание

NJEC1

текстовая строка

Подпись к произвольным (не принадлежащим никакой категории) сообщениям. Кроме как между произвольными сообщениями не отображается.

NJEC2

Удалить подпись к произвольным сообщениям.

NJEM2

Удалить все произвольные сообщения.

NJER

Перезагрузить табло.

NJET

Отобразить текущее время в виде сообщения 「現在時刻はxx時xx分です。」 ("Текущее время хх часов хх минут")

NJEV1

Отобразить контакты для связи. Если не установлены, выводит сообщение об ошибке.

NJEV2

Отобразить подпись для новостных сообщений. По умолчанию — 「時事通信ニュース速報」 ("Сводки новостей на текущий момент")

NJEV3

Отобразить подпись к произвольным сообщениям. Если не установлена, выводит сообщение об ошибке.

Установка настроек (NJESxx, где xx — номер параметра)

Просмотреть текущее значение параметра можно, отправив тот же номер параметра в команде NJEDxx без аргументов.

NJES02

00 / 01 / 02

Обычный режим / Энергосбережение / Автопереключение

NJES03

от 00 до 23

Час начала обычного режима работы

NJES04

от 00 до 23

Час окончания обычного режима работы

NJES05

00 / 01

Вкл/выкл отображение произвольных сообщений

NJES06

00 / 01 / 02

Скорость прокрутки

NJES07

00 / 01 / 02 / 03

Скорость мигания (00 — не мигать вообще)

NJES08

00 ~ 10

Длительность пауз, сек.

NJES18

00 ~ 10

Время жизни сообщений, ч.

NJES20

00 ~ 10

Время жизни экстренных новостей, ч.

NJES21

00 ~ 10

Время жизни обычных новостей, ч.

NJES26

00 / 01

Показ сообщений по мере прихода вне очереди

NJES34

00 ~ 20

Количество сообщений в каждом цикле отображения

NJES35

00 ~ 30

Показ подписи к новостям каждые N циклов

NJES37

00 ~ 30

Количество обычных новостей в каждом цикле

NJES38

00 ~ 30

Количество рекламы в каждом цикле

NJES57~74, NJES76~79

??

?? Установка категории новостей

NJES82

00 ~ 10

Время жизни произвольных сообщений, ч.

NJES86

00 / 01

Показ произвольных сообщений вне очереди

NJES89

00~20

Показ подписи к произвольным сообщениям каждые N циклов

NJES90

00~20

Количество произвольных сообщений за цикл

Команды получения сообщений

]011A110

nnMMDDhhmmaa{текст}

Запись сообщения

nn — номер от 01 по 99, MMDDhhmm — дата и время, aa — атрибуты отображения; текст также может содержать атрибуты

]011F120

nnMMDDhhmmaa{текст}

Запись рекламы

]011B111

nnMMDDhhmmaa{текст}

Запись экстренного сообщения

]011A110

00{текст}

Установка заголовка сообщений

]011A210

nn

Удаление сообщения № nn

]011F220

nn

Удаление рекламы

]011B211

nn

Удаление экстренного сообщения

]011A210

00

Удаление заголовка сообщений

❯ Вывод сообщений

Изначально я купил этот дисплей в расчёте на то, чтобы заменить им кассовый дисплей покупателя, который использую сейчас в диджейских стримах: (сверху на музыкальном центре на фоне, основной движ примерно с 9:48)

Всё же кассовый дисплей для такого слишком маловат.

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

Так как приковывать табло к столу с компом не хотелось, то в ход пошла очередная ESP32. К сожалению, выход 5 вольт на DB25 не имеет запаса по току, поэтому пришлось вывести наружу 24 вольта с блока питания и преобразовать их самому.

Также оказалось, что на ESP32 не работает функция iconv() — но, к счастью, для Shift-JIS есть отдельная библиотека. На базе этого получилось написать простейшую функцию для отправки пакетов на табло.

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

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

Поэтому, пишем простенькое подобие "аллокатора" сообщений :-) Таким образом, каждый "виджет" сможет зарезервировать себе "слайд":

mid.number = mgr->reserve(mid.kind);

А когда тот уже не нужен — освободить:

mgr->remove(mid);

В остальном про код мало что можно рассказать — в отличие от тех же плазменных часов, где пришлось свою графическую библиотеку писать, здесь же просто работа с текстом.

Из того, что показывать, было решено вывести:

  • Погоду

  • "Слово дня" на английском

  • Дату и время

  • Текущий играющий трек в Foobar2000

  • Отправителя и тему входящей почты (IMAP)

Также добавлен проброс с USB-UART у ESP32 напрямую на табло, чтобы впоследствии всё равно хоть как-то интегрировать его с Traktor-OBS-Relay.

Хотелось добавить ещё и свежие твиты для одного из списков в твиттере, чтобы видеть новости от локально живущих товарищей. Однако кое-кто сделал бесплатное АПИ write-only, а для чтения нужно платить 100 баксов в месяц, поэтому идея была отложена в чёрный ящик :-)

До кучи на скорую руку была слеплена и вебморда. Для неё я использовал библиотеку GyverPortal:

(так забавно в ридми у неё смотрится реклама новой версии, отмечающая, что новая работает через интернет и приложение для телефона — как будто это плюсы какие-то)

Дата и время

Ну, тут всё элементарно — резервируем "слайд", и форматируем на него текущее время. Всего кода на 60 строк, и проще его привести здесь, чем описывать:

Код слайда отображения времени
class TimeView {
public:
    TimeView(MessageManager* m) {
        mgr = m;
        mid.number = m->reserve(mid.kind);
    }

    ~TimeView() {
        mgr->remove(mid);
    }

    void update() {
        tk_time_of_day_t now_time = get_current_time_coarse();
        tk_date now_date = get_current_date();

        now_time.millisecond = 0; now_time.second = 0;

        if(memcmp(&now_time, &last_time, sizeof(tk_time_of_day_t)) == 0 && memcmp(&now_date, &last_date, sizeof(tk_date_t)) == 0) return;

        last_time = now_time;
        last_date = now_date;

        char buffer[64] = {0};
        nje_msg_attrib_t date_attr = { .color = COLOR_RED, .decor = STILL };
        nje_msg_attrib_t time_attr = { .color = COLOR_GREEN, .decor = STILL };

        snprintf(buffer, 64, "~%c%c~ %02d月 %02d日(%s)~%c%c~ %02d:%02d", 
            date_attr.color, date_attr.decor,
            now_date.month + 1, now_date.day, day_kanji[now_date.dayOfWeek], 
            time_attr.color, time_attr.decor,
            now_time.hour, now_time.minute
        );

        // Почему-то NJE-105 не нравится decor = STILL в начальных атрибутах сообщения, поэтому его пришлось вынести в текст сообщения
        mgr->update(mid, { .attributes = { .color = COLOR_RED, .decor = SCROLL }, buffer });

        if(now_date.month == 0 && now_date.day == 1) {
            if(sub_mid.number != 0) return;
            sub_mid.number = mgr->reserve(sub_mid.kind);
            mgr->update(sub_mid, { .attributes = {.color = COLOR_YELLOW, .decor = PULL_INVERSE}, .content = "Happy New Year!" });
        } else if (sub_mid.number != 0) {
            mgr->remove(sub_mid);
            sub_mid.number = 0;
        }
    }

private:
    const char * LOG_TAG = "TIME_VIEW";
    const char * day_kanji[7] = { "日", "月", "火", "水", "木", "金", "土" };
    MessageManager * mgr;
    tk_time_of_day_t last_time = { 0 };
    tk_date last_date = { 0 };
    nje_msg_identifier_t mid = { .kind = MSG_NORMAL };
    nje_msg_identifier_t sub_mid = { .kind = MSG_NORMAL };
};

Единственный подводный камень — в заголовке сообщения атрибут "Статичное отображение" использовать нельзя, табло почему-то просто вешается намертво, а после сброса жалуется на повреждение оперативной памяти. Поэтому пришлось этот атрибут вставить напрямую в текст сообщения.

Погода

Тут тоже всё было довольно элементарно — нужно было просто угнать код для обращения к OpenWeatherMap из часов, которые я делал раньше :-)

Так же как и дату-время, просто форматируем и выдаём на зарезервированный под это дело слайд.

Ключ доступа к АПИ тоже взял из часов — в бесплатном тарифе там столько доступов даётся, что мне одного ключа хватает на все устройства, включая два смартфона и смарт-часы.

Foobar2000

Здесь уже пришлось повозиться — единственным плюс-минус удобным способом вытягивать метаданные из fb2k оказался плагин foo_controlserver.

Был написан простенький клиент, в цикле долбящийся на заданный айпишник и порт. Если подключиться получилось, то он бесконечно слушает входящие строки, и вытаскивает из них события воспроизведения/паузы и название трека.

Формат там напоминает CSV, только разделителем является вертикальная черта. Соответственно, если она есть в названии трека или исполнителя, парсинг развалится и на экране будет чёрт знает что. Не идеально, но и не критично.

Слово дня

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

Недолгие поиски привели к Wordnik API. Дальше всё было тоже элементарно — получаем JSON, парсим его, выводим на экран.

Почта по IMAP

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

Казалось бы — протокол древний, строго описанный в RFC, плейнтекстовый: должна быть туча реализаций разного качества, от наколенных поделок до полноценных модулей-комбайнов.

Вот тут-то меня и поджидали анальные пирогенные боли и прочие мозговые страдания!

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

Поэтому пришлось интенсивно высирать 600 строк, которые упадут при первой же возможности — но вроде пока что работают.

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

Код отображения почты
std::map<imap_message_id_t, nje_msg_identifier_t> mail_map = {};

void mail_cb(imap_message_id_t id, const imap_message_info_t * info) {
    nje_msg_identifier_t mid = { .kind = MSG_NORMAL, .number = 0 };

    if(mail_map.count(id) == 1) {
        mid = mail_map[id];
    } else if(info != nullptr) {
        mid.number = mgr->reserve(mid.kind);
    }

    if(info != nullptr) {
        char buf[128] = { 0 };
        snprintf(buf, 127, "%s ~%c%c~%s", 
            (info->sender_name[0] == '?') ? info->sender_mail : info->sender_name,
            COLOR_RED, SCROLL,
            (info->subject == nullptr || info->subject[0] == '?') ? "(新着メール)" : info->subject);
        if(current_state == STATE_IDLE)
            mgr->update(mid, { .attributes = { COLOR_GREEN, SCROLL }, .content = buf });
        mail_map[id] = mid;
    } else if (mid.number != 0) {
        if(current_state == STATE_IDLE)
            mgr->remove(mid);
        mail_map.erase(id);
    }
}

Вторым сюрпризом оказался всё тот же нерабочий iconv. Я-то думал, что там просто не включили поддержку SJIS, но нет — он мёртвый совсем, даже при попытках конвертации из ASCII в ASCII выдаёт дулю. Поэтому заголовки сообщений поддерживаются только в виде UTF-8, а остальные замещаются просто на текст "Новое сообщение".

❯ Итоговый результат

В остальном исходники можно посмотреть на гитхабе, а пока полюбуемся на готовый результат:

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

А дойдут ли до этого у меня руки вы сможете узнать — среди тонн фоток еды, Мику, и прочего хлама из комиссионок — в моём телеграме :-)


Читайте также:


Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud - в нашем Telegram-канале

Жамкнуть ↩

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


  1. dlinyj
    02.07.2024 08:58
    +9

    Потрясный дисплей, очень крутой. Жаль что такие днём с огнём не сыщешь. За статью вам большое спасибо!


    1. vladkorotnev Автор
      02.07.2024 08:58
      +2

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


      1. dlinyj
        02.07.2024 08:58
        +1

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


      1. vvzvlad
        02.07.2024 08:58

        А сколько стоит?


        1. vladkorotnev Автор
          02.07.2024 08:58
          +1

          Около 5к йен было плюс-минус, вроде бы


          1. drdead
            02.07.2024 08:58
            +1

            Ну, если не опечатка и там реально к, а не ман, то с текущим "курсом" - копейки за такой аггрегат.


            1. vladkorotnev Автор
              02.07.2024 08:58
              +2

              Не, кому он сдался за пять манов-то, на электричестве потом больше потратишь %)


              1. dlinyj
                02.07.2024 08:58

                Ну если гугл корректно перевёл в рубли, то ценник более чем вменяем.


  1. grishkaa
    02.07.2024 08:58
    +3

    Удивлён тем, что он из коробки поддерживает кириллицу, правда, как почему-то часто бывает в CJK-шрифтах, некрасивую.


    1. vladkorotnev Автор
      02.07.2024 08:58
      +1

      Неудивительно, кириллица является частью стандарта JIS


  1. drdead
    02.07.2024 08:58
    +1

    (так забавно в ридми у неё смотрится реклама новой версии, отмечающая, что новая работает через интернет и приложение для телефона — как будто это плюсы какие-то)

    Так плюсы-то не для пользователя ;) Сначала только онлайн и через приложуху, потом за показ страницы заплати саб


    1. vladkorotnev Автор
      02.07.2024 08:58
      +2

      Не, думаю Александр так низко не падёт -- тут, скорее, наоборот адаптация к массовому юзеру, который не осиляет айпишник узнать вручную скорее всего


  1. m1shg4n
    02.07.2024 08:58
    +1

    Это происходило в Японии? Иначе как можно в здравом уме включать эту штуку в нашу сеть?


    1. vladkorotnev Автор
      02.07.2024 08:58
      +3

      Вообще да, но понижающие трансформаторы вроде бы тоже пока никто использовать не запрещает:-)

      Один знакомый в Москве вообще себе таковой на антресоли смонтировал, мощностью в пару киловатт, и две проводки по квартире развёл — с обычными розетками и с американскими. Импортных магнитофонов у него очень много просто %)


      1. dlinyj
        02.07.2024 08:58

        У меня был для таких целей рабочий трансформатор, даже с гнездом соответствующим. В переездах где-то потерялся. А так-то было удобно.