Однажды осенним днем ​​я решил, что хочу что-то построить с моим неиспользованным Pi первого поколения. Я остановился на создании «умной» колонки, которая будет приветствовать людей, входящих в комнату, и проигрывать музыку в лифте, пока они не уйдут, а в конце тоже прощается. Я взял за правило не использовать разъем AUX, чтобы было интереснее. Для срабатывания устройства должен использоваться ультразвуковой датчик, потому что он у меня уже был, и мой домовладелец не обрадуется, если я начну прикреплять переключатели к дверной коробке. Также есть что-то волшебное в том, чтобы размахивать рукой, чтобы все происходило.

Подумав, стек выглядел так:

  1. Используйте NodeJS для кода. Python обычно в моде, но мне было любопытно, насколько хорошо здесь справится Node.
  2. Чтобы добиться звука, я использовал усилитель MAX98357, который может работать с динамиком мощностью 3 Вт и сопротивлением 4 Ом. Он дешевый, простой в использовании, а на aliexpress их очень много.
  3. Старый проверенный HC-SR04 ультразвуковой датчик. Также дешево и достаточно хорошо.
  4. Напоследок я вытащил от aliexpress динамик мощностью 3 Вт и сопротивлением 4 Ом.

Список возможностей, который я имел в виду, был следующим:

  1. Воспроизвести какое-нибудь приветствие после срабатывания
  2. Переход к воспроизведению обычной музыки в лифте
  3. Прощай, когда снова сработал
  4. Создайте простой веб-интерфейс для загрузки файлов
  5. Регулировка громкости в веб-интерфейсе

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

Шаг 0: Настройка Raspberry / IDE

Первое поколение Pi в основном представляет собой Pi Zero большего форм-фактора. Под капотом не так много мощности, поэтому легкая ОС просто необходима. Создал загрузочную SD-карту с Raspberry Pi Imager с Raspberry Pi OS Lite в качестве образа. Вы не можете изменить никакие настройки изображения таким образом, поэтому вам нужно будет подключить его к дисплею с клавиатурой для настройки ssh, а также сетевых настроек. Это касается баребонов, теперь просто для установки node.

При использовании node есть обязательный инструмент под названием NVM, который можно использовать для установки и управления различными версиями. Сам инструмент установился нормально, но ни одной версии node установить не удалось, приходилось делать вручную. Первый Pi - это ARMv6, что также означает, что вы застряли на v10.24.10, но для этого варианта использования это вполне нормально.

Хотя я мог бы просто написать код на своей основной машине, а затем скопировать все на Pi для тестирования, для моей IDE есть отличный плагин - Visual Studio Code. Однако есть одна проблема: он не поддерживает ARMv6 устройства, поскольку они считаются слишком медленными для обработки рабочей нагрузки. Не бойтесь, мы можем использовать другой плагин, чтобы получить большинство функций - sshfs. Это позволяет вам смонтировать Pi как рабочее место через ssh, поэтому вам не нужно делать ручное копирование. Однако у него есть один недостаток - IntelliSense не работает, поэтому в итоге я выполнял большую часть работы на своей основной машине, а меньшую - непосредственно на Pi.

Шаг 1. Воспроизведение аудио

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

Я был дешев и не хотел тратить 15 долларов на AMP от Adafruit, поэтому я потратил 3 доллара и заказал один у aliexpress. Возникла охота на диких гусей, так как я никак не мог заставить его работать - полная тишина.

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

Сдался и заказал еще один AMP на aliexpress, пока я ждал, когда он приедет, мне не хватило терпения и я заказал еще один на eBay. Один из eBay прибыл первым и тоже не работал, поэтому мне пришлось глубоко погрузиться в файл конфигурации ALSA, который представлял собой огромную кроличью нору. Плохая документация и очень недружелюбный пользователь. В итоге я получил свой собственный файл конфигурации, который был сшит из образцов интернета, которые казались правильными, но все еще не работали.

Я был готов бросить полотенце, но тут пришло третье. Мне это показалось очень забавным, потому что это было из Казахстана. Не знал, что aliexpress есть продавцы оттуда, было впечатление, что это только из Китая.

Думаю, в третий раз - прелесть - наконец-то сработало. Проверил скрипт со страницы Adafruit, и он тоже сработал. Это был долгий и утомительный процесс, но мне все же удалось снизить Adafruits цену, поэтому я считаю это победой. Никогда не признавайте поражение, просто купите больше, возможно, сломанных AMP и надейтесь, что один будет работать так, как задумано.

В конце попыток я переключился с Pi первого поколения на Pi Zero, потому что я купил его для устранения неполадок и мне очень понравился формфактор.

Шаг 2: код

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

  1. pigpio для взаимодействия с GPIO
  2. express для обслуживания веб-интерфейса
  3. bootstrap для стилизации веб-интерфейса
  4. ejs шаблонизатор для веб-интерфейса
  5. audio-play и audio-loader для загрузки и воспроизведения mp3
  6. typescript вместе с ts-node для быстрой разработки с типобезопасностью

Разделение всего на отдельные модули для поддержания порядка дает следующую файловую структуру:

  • index.ts - точка входа для начальной загрузки всего
  • sonar.ts - инкапсулирует ультразвуковой датчик
  • audio-manager.ts - здесь все, что связано со звуком
  • app-loop-logic.ts - как видно из названия, он содержит код для отслеживания состояния приложения. Запуск и остановка воспроизведения звука, снятие показаний сонара
  • express.ts - HTTP-сервер со всеми необходимыми параметрами маршрутизации
  • config-manager - функции для сохранения и загрузки данных в файл JSON

У каждого файла есть своя ответственность, приятно. Окончательный код можно найти здесь. Не стесняйтесь судить и использовать его по своему усмотрению.

Шаг 2.0: Веб-интерфейс

Здесь все максимально просто, без jQuery или React. Обслуживание файлов HTML, CSS и JS. Поскольку в приложении есть состояние: список аудиофайлов, воспроизведение аудио и т. Д. Мне пришлось использовать механизм шаблонов для безболезненной вставки данных, я выбрал ejs, поскольку он требует очень небольшой настройки.

// express.ts
app.set("view engine", "ejs");
app.set("views", path.join(__dirname, "/resources"));
// package.json
"dependencies": {
    "ejs": "3.1.6",
    ...
},
// html, now .ejs
<label class="btn btn-primary <%= on %>">
    <input
      type="radio"
      name="options"
      id="on"
      autocomplete="off"
      <% if(on !== "") {%>
        checked
      <% } %>
    >Play Audio
</label>

CSS содержит всего несколько классов, так как большинство из них сделано с bootstrap, а JS просто добавляет слушателей для кнопок и выполняет HTTP-запросы.

Шаг 2.1 Ультразвуковой датчик

В самой библиотеке был пример на своей странице для ультразвукового датчика, так что это было проще простого. Пришлось изменить только одну вещь - вывод триггера по умолчанию использовался AMP, поэтому изменил его, чтобы использовать другой вывод CLK - GPIO4.

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

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

import { Gpio } from "pigpio";
const PIN_TRIG = 4;
const PIN_ECHO = 24;
const MICROSECDONDS_PER_CM = 1e6 / 34321;
export interface ISonar {
    getDistance: () => Promise<number>;
}
export const sonarFactory = async (): Promise<ISonar | undefined> => {
    const trigger = new Gpio(PIN_TRIG, { mode: Gpio.OUTPUT });
    const echo = new Gpio(PIN_ECHO, { mode: Gpio.INPUT, alert: true });
    let value: number | undefined;
    let startTick = 0;
    echo.on("alert", (level, tick) => {
        if (level === 1) {
            startTick = tick;
        } else {
            const endTick = tick;
            const diff = (endTick >> 0) - (startTick >> 0); // Unsigned 32 bit arithmetic
            value = diff / 2 / MICROSECDONDS_PER_CM;
        }
    });
    return {
        getDistance: async (): Promise<number> => {
            return new Promise((resolve, reject) => {
                value = undefined;
                trigger.trigger(10, 1);
                const handle = setInterval(() => {
                    if (value) {
                        clearInterval(handle);
                        resolve(value); 
                    }
                }, 10);
            });
        },
    };
};

Шаг 2.2: MP3-файлы

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

Ранее упомянутый ALSA имеет функцию командной строки для изменения громкости: amixer set PCM 100%, которая отлично работает с spawn утилитой, предоставляемой Node. Он выполняет команду в интерфейсе командной строки в другом процессе. Команда exec имела бы здесь больше смысла, но по какой-то причине она вызвала заминку, поэтому я выбрал вместо нее spawn.

export const setVolume = (volume: number) => {
    return new Promise<void>((resolve) => {
        const vol = spawn("amixer", ["set", "PCM", `${volume}%`]);
            vol.on("close", () => {
            resolve();
        });
        vol.on("error", (e) => {
            console.error(e);
        });
    });
};

Теперь, когда я мог регулировать громкость, я могу легко увеличивать / уменьшать воспроизведение звука, а затем приостанавливать воспроизведение звука, как только громкость становится достаточно низкой. Работало хорошо, но было огромное раздражение - загрузка и декодирование файлов mp3 заняли вечность, а разговоры здесь занимали несколько минут на один запуск.

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

Шаг 2.3 Ползучесть характеристик

Очень важно создать контрольный список MVP и придерживаться его, иначе это может привести к расползанию функций, и ваш проект никогда не будет завершен, это верно и для проектов DIY, как и этот. Я составил контрольный список, но продолжал добавлять в него что-то, так что любые временные рамки не учитывались. К счастью, в какой-то момент я остановился, окончательный результат можно увидеть ниже.

Список возможностей в итоге выглядел так:

  1. Перезагрузите Pi
  2. Выключите Pi
  3. Принудительное воспроизведение / остановка звука
  4. Включение / отключение функции сонара
  5. Выберите поток для воспроизведения из списка
  6. Добавить новые потоки
  7. Удалить потоки
  8. Установить громкость устройства
  9. Установите целевой уровень громкости, а также шаг затухания
  10. Показать проигрываемую в данный момент песню, если поток предоставляет такие данные (с помощью socket.io)

Это было почти полностью гладко, за исключением того, что у pigpio был некоторый конфликт при использовании ALSA для звука, к счастью, поиск в Google проблемы быстро помог исправить это.

// Fix ALSA audio isses caused by pigpio
configureClock(1, CLOCK_PWM);

Шаг 2.4 Запуск приложения

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

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

"scripts": {
    "start": "ts-node -T src/index.ts",
    "compile": "tsc --project ./tsconfig.json && node copy.js",
    "js": "node dist/index.js"
},

Сервисный файл, представленный ниже, можно найти в репозитории, а также с наиболее важными командами в файле readme.

[Unit]
Description=Ateja FM Service
After=network.target
[Service]
Type=simple
User=root
ExecStart=/usr/bin/node /home/pi/wc-node/dist/index.js
Restart=on-failure
[Install]
WantedBy=multi-user.target

Хотя запускать что-либо с правами суперпользователя не является хорошей практикой, в данном случае это требуется библиотекой pigpio для взаимодействия с GPIO.

Пока не могу остановиться

На этом этапе все работало отлично, и последней границей было упаковать все в красивую упаковку. Я выбрал onshape в качестве инструмента для создания чертежей САПР, потому что у него есть бесплатный план для любителей, а набор функций намного превосходит мои навыки. Единственный недостаток в том, что для бесплатного уровня у вас не может быть личных файлов, все общедоступно.

После нескольких итераций я пришел к окончательному дизайну, файлы можно найти здесь.

Последние мысли

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

NodeJS очень хорошо подходил, у него не возникало никаких проблем с тем, что что-то не работало. Это также достаточно производительно. Рекомендовано и буду использовать снова.

Ваше здоровье!