0
<< предыдущая заметкаследующая заметка >>
23 сентября 2023
Простейший Чипльдуктор

Почти сутки не мог взять себя в руки и приступить к чистке ведер грибов, что принес из леса. А всё потому, что у меня не было спокойного радио. Пришлось сперва его снова настроить. Снова — потому что чипльдуктор у меня был, но убилась флешка. Вы спросите: чего я не делал бэкап? Отвечу: она и убилась, когда я ее вынул и пытался сделать бэкап — не все кардридеры одинаково полезны.

Как сделать простейший Чипльдуктор?

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

1. Дешевый одноплатный чип с Линуксом — Raspberry PI, Orange, Banana или что-то похожее.
2. Звуковая карта USB c кнопками.
3. USB-колонки.

Звуковая карта с кнопками на Aliexpress стоит 70 руб. Колонки я уже несколько лет всем настоятельно советую ОКЛИК OK-330. За просто смешные 300 руб в любом Ашане или компьютерном ларьке вы получаете охрененного качества и громкости звук — но только при условии хорошего источника питания. Впрочем Raspberry PI дает вполне хорошее питание в своем USB-разъеме.

Итак, краткая инструкция (для себя писал), что делать с Raspberry.

Установка системы Raspberry Pi OS

Этот процесс достаточно описан на raspberrypi.com - ставим Raspberry Pi OS (то, что раньше называлось Raspbian). Есть какие-то официальные инсталляторы на флешкарту, но я всегда делал через dd. Не знаю, как сейчас, еще полгода назад была пара граблей, связанных с доступом ssh, дефолтным паролем и выключенным изначально диапазоном высокоскоростного вайфая Wifi5.

подробнее о граблях
1) Включаем из коробки ssh: в партицию boot надо дописать не только пустой файл с именем ssh чтобы включился доступ по ssh.

2) Туда же положить файл wpa_supplicant.conf чтобы сразу поднялся файфай сетки LLEO с паролем ytgfhjkm

ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=RU

network={

ssid="LLEO"

psk="ytgfhjkm"

key_mgmt=WPA-PSK

proto=WPA2
}

3) В последней версии тамошней системы нетрадиционно ориентированные люди убрали "ради вашей безопасности" дефолтный логин/пароль. Блять, можно подумать, кому-то удалось достучаться к чужой Raspberry по ssh сквозь NAT домашнего роутера. А если хакер уже во внутренней домашней сети или каким-то образом Raspberry получила белый IP в обход роутера, то что, такой админ не в состоянии сам уследить за паролями? Серьезно? Там ещё была какая-то телега, типа законодательство разных стран теперь запрещает выпускать устройства с дефолтными паролями. Грета блядь Тумберг и философский камень. Для установки пароля предлагается запустить на Raspberry какой-то инсталляционный софт, дебилы блять, а ничего, что у меня нет шнура её к монитору подключить? Короче, решение такое: надо сгенерировать хэш пароля 'raspberry' echo 'raspberry' | openssl passwd -6 -stdin и, добавив перед ним логин с двоеточкой, записать готовую строку в userconf.txt на той же boot-партиции:

pi:$6$ss0.VZ5qwrBCLimA$vO8dlrAjiPUSa9weEbu6VLpwlUe8SqpPUA0SawgfPw61QkePS1lGQnv2hZzXa5Wyzvj4DyXdHDlLk7/Ek7Ju/1

И вот тогда после установки системы на флешку можно логиниться по ssh в режиме headless с логином pi и паролем raspberry, не подключая дисплей и не открывая весь этот графический ужас.

4) Последняя небольшая вишенка говна на малиновом пироге: мало кто знает, что Raspberry (например, 4) умеет Wifi-5. Но диапазон у него не включен. И включается он вручную весьма нетрадиционным заклинанием, как это модно у линуксоидов:

echo "Так было:"
sudo iw reg get
echo "Теперь включаем диапазон:"
sudo iw reg set US
echo "Появились новые диапазоны:"
sudo iw reg get

Простейший способ его всякий раз включить при старте системы:

sudo crontab -e

И прописать там в конце:
@reboot iw reg set US

Обживаем систему

Итак, система настроена. Поставить полезные и любимые утилитки:

sudo apt install mc catdoc docx2txt enca figlet sshfs mp3info make gcc curl whois libjpeg-progs davfs2

Поставить PHP (он нам пригодится для key.php):

sudo apt install php

Себе я ставлю заодно сервер и свой движок — nginx, mysql, memcache, но...

но это вам не нужно
php-mbstring php-curl php-mysqli libnginx-mod-rtmp mariadb-server mariadb-client php-fpm memcached php-memcache

Желательно везде вместо php-... писать php8.0-..., потому что нынешний Raspberrian ставит по умолчанию php 7.4. Про memcache: сервер называется memcached, а плагин php-memcache, не php-memcached! Потому что такой есть тоже, но тяжелый, медленный и ненужный. Не забываем затем нахер apache2, который поставил php:

apt purge apache2

Настройка MYSQL, чтоб два раза не вставать

sudo mysql -u root

CREATE DATABASE my_base DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci;

GRANT ALL PRIVILEGES ON my_base.* TO lleo@localhost IDENTIFIED BY 'MyPassword' WITH GRANT OPTION;

SET @@SQL_MODE = REPLACE(@@SQL_MODE, 'STRICT_TRANS_TABLES', '');
SET @@SQL_MODE = REPLACE(@@SQL_MODE, 'STRICT_ALL_TABLES', '');
FLUSH PRIVILEGES;

Установить пароль:

ALTER USER 'root'@'localhost' IDENTIFIED BY 'mypassword';

Добавить в /etc/mysql/my.cnf:

[mysqld]
sql_mode=NO_ENGINE_SUBSTITUTION

Перезапустить, чтобы сработало.

nginx

Простейший конфиг nginx для php /etc/nginx/sites-enabled/site.conf

server {

listen 80 default_server;

listen [::]:80 default_server;

server_name _;

index index.html index.htm index.php;

client_max_body_size 500M;

location / {

root /home/www;

try_files $uri /index.php?$args;

location ~ \.php$ {

fastcgi_split_path_info ^(.+\.php)(/.+)$;

try_files $fastcgi_script_name =404;

fastcgi_pass unix:/var/run/php/php-fpm.sock;

fastcgi_index index.php;

include fastcgi_params;

proxy_set_header Host $host;

proxy_set_header X-Real-IP $remote_addr;

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

client_max_body_size 500m;

client_body_buffer_size 128k;

proxy_connect_timeout 90;

proxy_send_timeout 90;

proxy_read_timeout 90;

proxy_buffer_size 4k;

proxy_buffers 4 32k;

proxy_busy_buffers_size 64k;

proxy_temp_file_write_size 64k;

}

}
}

Но php толком не заработает, пока в начало файла /etc/nginx/fastcgi_params не добавить строчку:

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

Но всё это, напоминаю, только если вам нужен ещё и сервер. Для чипльдуктора не нужен.

Не забываем установить локаль ru_RU.UTF-8, задолбало:

sudo dpkg-reconfigure locales


ОБЖИВАЕМ ЗВУКОВУЮ КАРТУ

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

usermod -aG audio lleo

Далее нам нужно узнать про нашу звуковую карту ровно три вещи. Ее код на USB-шине, ее номер в системе и ее имя устройства громкости. Начинаем с кода, смотрим, какие USB-девайсы у нас вообще есть в USB:

lsusb

Raspberry покажет что-то типа такого:

типа такого
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 003: ID 8086:0808 Intel Corp. USB PnP Sound Device
Bus 001 Device 002: ID 2109:3431 VIA Labs, Inc. Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

Собственно 8086:0808 - это и есть номер нашей USB-звуковушки с кнопками. Это то, как карту видит USB. Запомнили его. Теперь смотрим, какие видит аудиоустройства система:

sudo aplay -l

Выдает кучу мусора (еще есть вывод звука в HDMI, например), но я вижу свою USB-Sound как устройство номер 3, запомним этот номер 3:

куча мусора
...
card 3Device [USB PnP Sound Device], device 0USB Audio [USB Audio]
  
Subdevices1/1
  Subdevice 
#0: subdevice #0

Наконец, смотрим, что в карте номер 3 видит микшер:

amixer -c 3

у меня такое

<?php
Simple mixer control 
'Speaker',0
  Capabilities
pvolume pswitch pswitch-joined
  Playback channels
Front Left Front Right
  Limits
Playback 0 151
  Mono
:
  
Front LeftPlayback 60 [40%] [-17.13dB] [on]
  
Front RightPlayback 60 [40%] [-17.13dB] [on]
...

Из чего заключаю, что устройство регулировки громкости сегодня называется 'Speaker'. Сука, при прошлой инсталляции системы ты у меня называлось 'PCM', а еще раньше 'Headphone'! Итак, запомнили: номер USB 8086:0808, карта номер 3, устройство громкости Speaker.

Осталось лишь поставить самую простую и легкую утилитку для воспроизведения mp3:

sudo apt install mpg123

Итак, звуковая система настроена.

УПРАВЛЕНИЕ ПРОЦЕССАМИ ОТ КНОПОК

Как сделать управление от кнопок звуковой карты? Я использую две софтинки. Демон keyboardoid, который я написал на C, при нажатии кнопок он вызовет key.php, который уже разбирается, что именно было нажато и какие действия выполнить. Итак, набираем gcc keyboardoid.c и получившийся a.out переименовываем в keyboardoid для ясности. Демон готов:

keyboardoid.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdbool.h>
#include <dirent.h>
#include <linux/input.h>

#define VENDORID  0x1c4f
#define PRODUCTID 0x0002

int16_t fd = -1;

char path[300];

int init_keyboard(unsigned int VENDOR,unsigned int PRODUCT) {
    
fd=-1;
  
int count=0;
  
struct dirent **files=NULL;
  
struct input_id id;

  
count scandir("/dev/input",&files,0,0)-1;
  while(
count>=0) {
    if (
fd==-&& strncmp(files[count]->d_name,"event"5)==0) {
      
sprintf(path,"/dev/input/%s",files[count]->d_name);
      
fd open(path,O_RDONLY);
      if(
fd>=0) {
        if(
ioctl(fd,EVIOCGID,(void *)&id)<perror("ioctl EVIOCGID");
        else {
          if(
id.vendor==VENDOR && id.product==PRODUCT && id.bustype==BUS_USB) { printf("Device found at %s ",path); }
          else {
            
close(fd);
            
fd = -1;
          }
        }
      } else {
        
fprintf(stderr,"Error opening %s:\t",path); perror("");
      }
    }
    
free(files[count--]);
  }
  
free(files);

  
int grab=1;
  if(
fd>=0ioctl(fd,EVIOCGRAB,&grab);
  else {
      
fprintf(stderr,"Device not found or access denied. ");
      return 
EXIT_FAILURE;
  }
  return 
0;
}


char remap_codes(int scancode){
  
char r=' ';
  switch(
scancode) {
    
// NumLock = off + N     // NumLock = on
    
case 0x45r=0; break;
    
// 000 -> 0 0 0          // 000 -> --- NO ---
    
case 0x52r='0'; break; case 0x6Er='0'; break;
    case 
0x4fr='1'; break; case 0x6Br='1'; break;
    case 
0x50r='2'; break; case 0x6Cr='2'; break;
    case 
0x51r='3'; break; case 0x6Dr='3'; break;
    case 
0x4br='4'; break; case 0x69r='4'; break;
    case 
0x4cr='5'; break; //--- NO ---
    
case 0x4dr='6'; break; case 0x6Ar='6'; break;
    case 
0x47r='7'; break; case 0x66r='7'; break;
    case 
0x48r='8'; break; case 0x67r='8'; break;
    case 
0x49r='9'; break; case 0x68r='9'; break;
    case 
0x53r='.'; break; case 0x6Fr='.'; break;
        case 
0x60r='E'; break; // Enter
        
case 0x62r='C'; break; // /
        
case 0x37r='Z'; break; // *
        
case 0x4ar='M'; break; // —
        
case 0x4er='P'; break; // +
        
case 0x0Er='B'; break; // Backspace
    
default:
    
printf("Scancode:0x%X — [%c]\n",scancode,(unsigned char)scancode);
    
r=(unsigned char)scancode;
  }
  return 
r;
}

char read_keyboard() {
  
struct input_event ev;
  
ssize_t n;

  while(
true) {
    
read(fd, &evsizeof (struct input_event));
    if(
== (ssize_t)-1) {
        if (
errno == EINTR) continue;
        else break;
    } else if(
!= sizeof ev) {
        
errno EIO;
        break;
    }
    if( 
ev.type==EV_KEY && ev.value==EV_KEY ) return remap_codes(ev.code);
  }
  return 
'!';
}


int connect_keyboard(int argc,chararg) {
    
char name[256] = "Unknown";
    if(
argc>1) {
    
// пробуем отсканировать VEND:PROD
    
unsigned int VEND=VENDORID,PROD=PRODUCTID;
    if(
2==sscanf(arg,"%x:%x",&VEND,&PROD)) {
        
printf("\nConnecting to device %04x:%04x... ",VEND,PROD);
        if(
init_keyboard(VEND,PROD)!=0) { printf("FAILED\n"); return(1); }
        
printf("OK\n");
    } else {
        
// пробуем найти path
        
snprintf(path,200,"%s",arg);
        
printf("\nConnecting to device %s ... ",path);
        if((
fd open(path,O_RDONLY)) == -1) { printf("FAILED\n"); return(1); }
        
printf("OK\n");
    }
    } else { 
// откроем дефолтный девайс
    
printf("\nConnecting to default device %04x:%04x... ",VENDORID,PRODUCTID);
        if(
init_keyboard(VENDORID,PRODUCTID)!=0) { printf("FAILED\n"); return(1); }
    
printf("OK\n");
    }
    
ioctl(fdEVIOCGNAME (sizeof (name)), name);
    
fprintf(stderr"Reading from: %s (%s)\n"pathname);
    return 
0;
}


int main(int argccharargv[]) {

    
printf("Runnung... (Example: \"sudo ./keyboardnoid 1C4F:0002 \"echo \\\"%%c\\\" >> /tmp/111.txt\")");

    
char r=0;
    
char action[300]="notify-send -t 300 \"KeyCode: %c\"";

    if(
argc>2snprintf(action,1024,"%s",argv[2]); // скопировать строку акции

    // коннектимся
    
while(1) { if(== connect_keyboard(argc,argv[1])) break; sleep(2); } // 1 сек

    
while(true) {
        if(!(
r=read_keyboard())) continue;

    
// printf("Scancode:0x%X — [%c]\n",r,(unsigned char)r);

    
if(r=='!') { // обрыв связи
        
sleep(1);
        
close(fd); printf("port_disconnected\n");
        
snprintf(path,100,action,'?'); system(path); // подать сигнал
        
while(true) { if(== connect_keyboard(argc,argv[1])) break; sleep(2); } // 10 сек
        
snprintf(path,100,action,'!'); system(path); // подать сигнал что все в порядке
        
continue;
    }

    if(
r==0x0D || r==0x0A || r=='"' || r=='\''r='@'// не надо нам энтеров и кавычек!
    
snprintf(path,100,action,r);
    
printf("ACTION: `%s`\n",path);
    
system(path);
    }
}

Демон отлавливает кнопки от любой клавиатуры - звуковая карта тоже создает устройство «клавиатура». Он крохотный, написан один раз навсегда и просто висит в памяти, занимая там самый мизер, какой вообще может занимать софт на Линуксе (я его для совсем бедных памятью линуксов изначально делал), а уж при нажатии кнопки вызывает уже специальный скрипт для разбора, чего с этой кнопкой делать. При запуске ему нужно указать номер USB-устройства - упаси вас боже перехватить управление своей собственной клавиатуры, как у меня однажды было при настройке, пришлось заходить по ssh и вырубать демона. Вторым аргументом демону передаем в кавычках строку произвольную запуска любой процедуры, где вместо %c демон передаст полученный код или последовательность. У меня запускается демон так: keyboardoid 8086:0808 "/usr/bin/nohup /home/work/audio/key.php \"%c\" А передает скан код в key.php, который будет запускать радио и регулировать громкость, я его набросал таким (впишите в первых строках свои настройки аудиокарты):

key.php

#!/usr/bin/php

<?php

// настройки аудиокарты
$hw='3'// 0 1
$nm="Speaker"// PCM Speaker Headphone

if($argc != 2) die("no keycodes\n");
$c=$argv[1];
$s=( strlen($c)>$c sprintf("0x%02X",ord($c)) );

echo 
"===> [$c] ".$s."\n";

    if(
$s=="0xA5"play("http://radio.4duk.ru/4duk128.mp3"); // верхняя — Чипльдук
elseif($s=="0xA4"stop(); // средняя — СТОП
elseif($s=="0xA3"play("http://hermitage.hostingradio.ru/hermitage128.mp3"); // нижняя — Эрмитаж
elseif($s=="0x72"volume(-0.1); // — Громкость убавить
elseif($s=="0x73"volume(+0.1); // — Громкость добавить
elseif($s=="0x71") { } // пока не придумал задачу для этой кнопки
else die("==> Unknown CODE\n");

function 
volume($add) { global $hw,$nm;
    
$file="/tmp/mixer_volume.txt";
    
$v=intvalfalse===($v=@file_get_contents($file)) ? 50 $v );
    if(
$add!=0) {
        
$v=$v+$add*(max($v,10));
        
$v=ceil($v);
        
$v=min($v,100);
        
$v=max($v,1);
        
file_put_contents($file,$v); chmod($file,0666);
    }
    
exec("/usr/bin/amixer -c ".$hw." sset ".$nm." ".$v."%");
}

function 
play($s) { global $hw;
    
stop();
    
exec("/usr/bin/nohup /usr/bin/mpg123 -q -a hw:".$hw." \"".$s."\" > /dev/null 2>&1 </dev/null &");
}

function 
stop() {
    
exec("/usr/bin/killall mpg123");
}

?>
?>

Осталось проверить пути файлов и вписать демона в старт системы. Я не стал париться, а просто вписал в sudo crontab -e две строки при старте - включение диапазона WiFi5 и запуск моего файлика /home/work/onstart.sh:

@reboot iw reg set US
@reboot /home/work/onstart.sh

Там я при старте системы негромко (40%) проигрываю сэмпл - полезно знать, что система готова или в квартире был сбой энергии все устройства перегрузились. И запускаю демона:

#!/bin/sh

echo "Restart: "`date` >> /home/work/onstart.log

/usr/bin/amixer -c 3 sset Speaker 40%
/usr/bin/mpg123 -a hw:3 "/home/work/audio/sample/eto_ja.mp3" 2>&1 >/dev/null

/home/work/audio/keyboardoid 8086:0808 "/usr/bin/nohup /home/work/audio/key.php \"%c\""

Вот и всё. Как видно в скрипте, на верхнюю кнопку я прописал свое любимое радио Чипльдук http://radio.4duk.ru/4duk128.mp3

На нижнюю - спокойное питерское радио Эрмитаж http://hermitage.hostingradio.ru/hermitage128.mp3

Штуку с колонками положил на холодильник, там у меня и розетка удобная, и не мешает никому, и до кнопок дотянуться удобно. Вот теперь можно чистить грибы!

<< предыдущая заметка следующая заметка >>
пожаловаться на эту публикацию администрации портала
архив понравившихся мне ссылок

Комментарии к этой заметке скрываются - они будут видны только вам и мне.

Оставить комментарий