Differenze tra le versioni di "Web radio"

Da Lecco.
(Sistema operativo: probabilmente si trattava di un problema hardware del Raspberry; col Banana Pi funziona tutto come previsto)
(DNS dinamico: HTTPS al posto di HTTP)
 
(84 versioni intermedie di uno stesso utente non sono mostrate)
Riga 1: Riga 1:
 
Con questo progetto si è realizzata la web radio della Comunità pastorale di Missaglia (LC).
 
Con questo progetto si è realizzata la web radio della Comunità pastorale di Missaglia (LC).
  
Il sistema prevede il collegamento audio via Internet tra le chiese parrocchiali di Maresso e Missaglia, in modo tale da poter ascoltare presso la chiesa locale eventi che si svolgano nell'altra chiesa; quanto viene diffuso attraverso l'impianto audio della chiesa viene inoltre trasmesso via etere ai parrocchiani dotati di un particolare apparecchio radio. Quando non ci sono eventi in corso, viene raccolto e trasmesso lo stream di Radio Mater.
+
Il sistema prevede il collegamento audio via Internet tra le chiese parrocchiali di Maresso e Missaglia, in modo tale da poter ascoltare presso la chiesa locale eventi che si svolgano nell'altra chiesa; quanto viene diffuso attraverso l'impianto audio della chiesa viene inoltre trasmesso via etere ai parrocchiani dotati di un particolare apparecchio radio. <!--Quando non ci sono eventi in corso, viene raccolto e trasmesso lo stream di Radio Mater.-->
  
 
In un secondo tempo si renderà disponibile una playlist di brani da trasmettersi in automatico nel caso saltasse il collegamento ad Internet.
 
In un secondo tempo si renderà disponibile una playlist di brani da trasmettersi in automatico nel caso saltasse il collegamento ad Internet.
Riga 15: Riga 15:
 
== Hardware ==
 
== Hardware ==
 
Per ogni stazione ricetrasmittente:
 
Per ogni stazione ricetrasmittente:
* 1 [http://raspberrypi.org/ Raspberry Pi Model B Rev 2.0] oppure 1 [http://www.lemaker.org/ Banana Pi] [A]
+
* 1 [http://www.lemaker.org/ Banana Pi] [A]
* 1 [http://www.fasttech.com/products/0/10001230/1102600-aluminum-heatsinks-for-raspberry-pi-3-pack set di dissipatori in alluminio] [F]
 
 
* 1 [http://lowpowerlab.com/atxraspi/ ATXRaspi] [L]
 
* 1 [http://lowpowerlab.com/atxraspi/ ATXRaspi] [L]
 
* 1 [https://fasttech.com/p/1247400 JY-MCU Mini RS232 to TTL Converter Module Board] [F]
 
* 1 [https://fasttech.com/p/1247400 JY-MCU Mini RS232 to TTL Converter Module Board] [F]
Riga 46: Riga 45:
  
 
=== Console port ===
 
=== Console port ===
Il Raspberry Pi dispone di un'interfaccia seriale via GPIO, già configurata su Raspbian per funzionare come console seriale. Abbiamo quindi deciso di sfruttare questa potenzialità per poter facilmente accedere al sistema operativo in caso di malfunzionamento.
+
Il Banana Pi dispone sul GPIO J11 di [http://linux-sunxi.org/LeMaker_Banana_Pi#Adding_a_serial_port un'interfaccia seriale] già configurata (usando Bananian) per funzionare come console seriale. Abbiamo quindi deciso di sfruttare questa potenzialità per poter facilmente accedere al sistema operativo in caso di malfunzionamento.
  
Occorre collegare i pin GPIO ad una scheda costituita da un integrato (MAX3232) e da cinque condensatori. Questa scheda, [https://blogs.oracle.com/speakjava/entry/serial_communications_with_a_raspberry facilmente costruibile anche in casa], fornisce un'interfaccia [http://it.wikipedia.org/wiki/EIA_RS-232 RS-232] standard, a 5 V; il MAX3232 invece funziona anche a tensioni più basse, come quella del Raspberry Pi (3,3 V).
+
P01, attaccato al GPIO J12, corrisponde a TXD; P02, subito a fianco, a RXD.
 +
 
 +
Occorre collegare i pin GPIO ad una scheda costituita da un integrato (MAX3232) e da cinque condensatori. Questa scheda, [https://blogs.oracle.com/speakjava/entry/serial_communications_with_a_raspberry facilmente costruibile anche in casa], fornisce un'interfaccia [http://it.wikipedia.org/wiki/EIA_RS-232 RS-232] standard, a 5 V; il MAX3232 invece funziona anche a tensioni più basse, come quella del Banana Pi (3,3 V).
  
 
Noi abbiamo optato per l'acquisto di una scheda già pronta; esistono prodotti dotati di connettore saldato, ma abbiamo preferito acquistarne uno  sprovvisto, per poter posizionare la scheda liberamente all'interno dell'unità rack.
 
Noi abbiamo optato per l'acquisto di una scheda già pronta; esistono prodotti dotati di connettore saldato, ma abbiamo preferito acquistarne uno  sprovvisto, per poter posizionare la scheda liberamente all'interno dell'unità rack.
  
 
Per questioni estetiche abbiamo deciso di evitare di posizionare un connettore D-sub sul pannello frontale; ci siamo quindi inventati una nostra soluzione proprietaria, che trae ispirazione dalla [http://en.wikipedia.org/wiki/Rollover_cable console port di Cisco].
 
Per questioni estetiche abbiamo deciso di evitare di posizionare un connettore D-sub sul pannello frontale; ci siamo quindi inventati una nostra soluzione proprietaria, che trae ispirazione dalla [http://en.wikipedia.org/wiki/Rollover_cable console port di Cisco].
 +
 +
=== Bottone di accensione ===
 +
La procedura è spiegata chiaramente sul sito ufficiale della scheda [http://lowpowerlab.com/atxraspi/ ATXRaspi]. In sintesi abbiamo alimentato la scheda saldando direttamente due fili all'alimentatore; abbiamo attaccato un bottone e un LED saldando i fili del primo direttamente, e interponendo una resistenza adeguata per il LED; abbiamo attaccato i 4 cavi diretti alla scheda ATXRaspi (alimentazione sui pin laterali e controllo in mezzo, GPIO28 a IN e GPIO30 ad OUT) all'header J12 del Banana Pi che corrisponde al P5 del Raspberry Pi (la fila più vicina all'header P1).
  
 
{|
 
{|
!
 
 
!PIN
 
!PIN
 
!BCM
 
!BCM
 
|-
 
|-
|TX
+
|J12-P03
|8
+
|28
|14
 
 
|-
 
|-
|RX
+
|J12-P05
|10
+
|30
|15
 
 
|}
 
|}
 
=== Bottone di accensione ===
 
La procedura è spiegata chiaramente sul sito ufficiale della scheda [http://lowpowerlab.com/atxraspi/ ATXRaspi]. In sintesi abbiamo alimentato la scheda saldando direttamente due fili all'alimentatore; abbiamo attaccato un bottone e un LED saldando i fili del primo direttamente, e interponendo una resistenza adeguata per il LED; abbiamo saldato 4 pin header all'header P5 del Raspberry Pi (la fila più vicina all'header P1), ai quali abbiamo attaccato i 4 cavi diretti alla scheda ATXRaspi (alimentazione sui pin laterali e controllo in mezzo, GPIO28 a IN e GPIO30 ad OUT).
 
  
 
Abbiamo riscritto lo script <code>shutdowncheck.py</code> da zero, in Python, ponendolo nella directory <code>/root/</code> e dandogli i permessi di esecuzione da parte del proprietario (l'utente <code>root</code>):
 
Abbiamo riscritto lo script <code>shutdowncheck.py</code> da zero, in Python, ponendolo nella directory <code>/root/</code> e dandogli i permessi di esecuzione da parte del proprietario (l'utente <code>root</code>):
Riga 136: Riga 134:
 
|-
 
|-
 
|1
 
|1
|22
+
|19
|25
+
|10
 
|-
 
|-
 
|2
 
|2
Riga 154: Riga 152:
 
|23
 
|23
 
|11
 
|11
|}
 
 
=== Circuito relè ===
 
{|
 
!PIN
 
!BCM
 
|-
 
|13
 
|27
 
 
|}
 
|}
  
 
== Software ==
 
== Software ==
* [https://github.com/hifi/raspbian-ua-netinst raspbian-ua-netinst]
+
* [https://www.bananian.org/ Bananian Linux]
* [http://ddclient.sf.net/ DDclient]
+
* [https://github.com/LeMaker/RPi.GPIO_BP/tree/bananapi RPi.GPIO_BP]
 
* [http://icecast.org/ices.php IceS]
 
* [http://icecast.org/ices.php IceS]
 
* [http://videolan.org/ VLC]
 
* [http://videolan.org/ VLC]
 
* [http://icecast.org/ Icecast]
 
* [http://icecast.org/ Icecast]
 +
* [https://httpd.apache.org/ Apache]
  
 
=== Sistema operativo ===
 
=== Sistema operativo ===
Seguire le istruzioni presenti [https://github.com/hifi/raspbian-ua-netinst qui] (o [https://www.bananian.org/download qui], nel caso del Banana Pi) per ottenere rispettivamente una versione recente di Raspbian oppure di Bananian.
+
Seguire le istruzioni presenti [https://www.bananian.org/download qui] per ottenere una versione recente di Bananian e copiarla sulla scheda SD.
  
Nel primo caso, una volta estratto l'installer sulla scheda SD, montare la scheda e creare al suo interno un file da salvare come <code>installer-config.txt</code>, il cui contenuto sarà:
+
Inserire la scheda SD nel Banana Pi; collegare il cavo di rete al dispositivo, possibilmente un monitor HDMI e una tastiera, e alimentarlo.
 +
 
 +
Effettuare il login (username: <code>root</code>; password: <code>pi</code>) e lanciare <code>bananian-config</code>.
 +
 
 +
Per comodità installare vim:
 
<pre>
 
<pre>
preset=server
+
# aptitude update
packages=apt-utils,vim,aptitude,bash-completion,ddclient,libio-socket-ssl-perl,alsa-base,ices2,vlc-nox,hexedit
+
# aptitude install vim
mirror=http://mirrordirector.raspbian.org/raspbian/
+
# aptitude purge vim-tiny
release=wheezy
 
hostname=pi # TO BE MODIFIED
 
domainname=
 
rootpw=raspbian # TO BE MODIFIED
 
cdebootstrap_cmdline=
 
bootsize=+50M # /boot partition size as given to fdisk
 
rootsize=    # / partition size in megabytes, leave empty to use all free space
 
timeserver=time.nist.gov
 
ip_addr=dhcp
 
ip_netmask=0.0.0.0
 
ip_broadcast=0.0.0.0
 
ip_gateway=0.0.0.0
 
ip_nameservers=
 
online_config= # URL to extra config that will be executed after installer-config.txt
 
usbroot= # set to 1 to install to first USB disk
 
cmdline="dwc_otg.lpm_enable=0 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 elevator=deadline"
 
rootfstype=ext4
 
rootfs_mkfs_options=
 
rootfs_install_mount_options='noatime,data=writeback,nobarrier,noinit_itable'
 
rootfs_mount_options='errors=remount-ro,noatime'
 
 
</pre>
 
</pre>
  
Se si dispone di un [http://guide.debianizzati.org/index.php/Usare_approx_per_creare_una_cache_dei_pacchetti_usabile_in_una_LAN server approx], dopo averlo configurato con il nuovo repository, modificare la terza riga di <code>installer-config.txt</code> come segue:
+
Installare infine i programmi usati per questo progetto:
 
<pre>
 
<pre>
mirror=http://IP_SERVER:9999/raspbian/
+
# aptitude install alsa-base ices2 vlc-nox hexedit python3-dev gcc unzip
 
</pre>
 
</pre>
(ovviamente sostituendo IP_SERVER con l'indirizzo IP del server).
 
 
Smontare la scheda SD e inserirla nel Raspberry Pi; collegare il cavo di rete al Raspberry, possibilmente un monitor HDMI e alimentarlo. L'installazione dovrebbe procedere automaticamente. Seguire poi le istruzioni precedentemente indicate, al primo boot.
 
  
 
Installare <code>python3-rpi.gpio</code>:
 
Installare <code>python3-rpi.gpio</code>:
 
<pre>
 
<pre>
# aptitude update
+
# wget https://github.com/LeMaker/RPi.GPIO_BP/archive/bananapi.zip
# aptitude install python3-rpi.gpio
+
# unzip bananapi.zip
 +
# cd RPi.GPIO_BP-bananapi/
 +
# python3 setup.py install
 +
# cd ..
 +
# rm -r RPi.GPIO_BP-bananapi bananapi.zip
 
</pre>
 
</pre>
  
Impostare l'indirizzo IP come fisso, modificando il file <code>/etc/network/interfaces</code>; nel nostro caso abbiamo scelto come indirizzo <code>192.168.0.40</code> in un caso, e <code>192.168.1.40</code> in un altro. Potrebbe essere necessario ridurre il range gestito dal server DHCP (ad esempio configurando adeguatamente il router) in modo tale da non avere conflitti con questi indirizzi.
+
Impostare l'indirizzo IP come fisso, modificando il file <code>/etc/network/interfaces</code> ed, eventualmente, <code>/etc/resolv.conf</code>; nel nostro caso abbiamo scelto come indirizzo <code>192.168.0.40</code> in un caso, e <code>192.168.1.40</code> in un altro. Potrebbe essere necessario ridurre il range gestito dal server DHCP (ad esempio configurando adeguatamente il router) in modo tale da non avere conflitti con questi indirizzi.
  
Per impostare la scheda audio esterna come dispositivo di default, creare il file <code>/etc/asound.conf</code> col seguente contenuto:
+
Per impostare la scheda audio esterna come dispositivo di default, verificare che il file <code>/etc/asound.conf</code> presenti il seguente contenuto:
 
<pre>
 
<pre>
 
pcm.!default {
 
pcm.!default {
Riga 230: Riga 205:
 
}
 
}
 
</pre>
 
</pre>
 
Nel caso del Banana Pi il file di configurazione è già presente.
 
  
 
=== DNS dinamico ===
 
=== DNS dinamico ===
Premessa: a causa di [https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=528950 questo bug], per usufruire di questo servizio è necessario installare una versione di ddclient >= 3.8.2.
+
La condizione ideale sarebbe quella di disporre di un indirizzo IP statico per ogni ricetrasmittente. Purtroppo è molto più probabile che il provider fornisca un indirizzo IP dinamico. Per rendere sempre raggiungibili i diversi host occorre quindi ricorrere ad un servizio di DNS dinamico.
  
La condizione ideale sarebbe quella di disporre di un indirizzo IP statico per ogni ricetrasmittente. Purtroppo è molto più probabile che il contratto col provider offra un indirizzo IP dinamico.
+
Per questo progetto si è deciso di sfruttare [http://wiki.opennicproject.org/dotDynTLD OpenNIC]. Occorre creare un account sul sito e poi registrare un dominio <code>.dyn</code> per ogni stazione ricetrasmittente<!-- , che si consiglia di impostare con TTL pari a 60 secondi (default 300, pari a 5 minuti) -->.
  
Per rendere sempre raggiungibili i diversi host occorre quindi sfruttare un servizio di DNS dinamico. Per questo progetto si è deciso di sfruttare [http://dnsdynamic.org/ DNSdynamic]. Occorre creare un account sul sito e poi registrare un dominio di terzo livello per ogni stazione ricetrasmittente.
+
Per ogni dominio, una volta preso nota dell'URI messo a disposizione da OpenNIC per aggiornare l'indirizzo IP, è sufficiente scrivere sulla macchina che vogliamo raggiungere da Internet uno script (che chiameremo <code>/root/ddns.sh</code>) analogo al seguente, sostituendovi l'URI precedentemente appuntato:
 +
<pre>
 +
#!/bin/sh
 +
wget -qO- "https://api.opennicproject.org/ddns/example.dyn?user=myUserName&auth=myAuthCode" > /dev/null
 +
</pre>
 +
Una volta datogli i permessi di esecuzione, creeremo un cronjob da lanciarsi ogni 10 minuti:
 +
<pre>
 +
# crontab -e
 +
</pre>
 +
<pre>
 +
# m h  dom mon dow  command
 +
*/10 * * * * /root/ddns.sh
 +
</pre>
  
Impostare il file di configurazione (<code>/etc/ddclient.conf</code>) come segue:
+
Bisogna infine pazientare il tempo necessario affinché i nuovi domini da noi registrati si propaghino per tutta la rete (in genere bastano pochi minuti). Si può verificare che sia tutto a posto lato DNS lanciando questo comando da una macchina che sfrutti i DNS OpenNIC:
 
<pre>
 
<pre>
daemon=60                              # check every 60 seconds
+
$ dig mio_dominio.dyn
syslog=yes                              # log update msgs to syslog
 
pid=/var/run/ddclient.pid              # record PID in file.
 
ssl=yes                                # use ssl-support.  Works with
 
                                        # ssl-library
 
use=web, web=myip.dnsdynamic.org        # get ip from server.
 
server=www.dnsdynamic.org              # default server
 
login=INDIRIZZO_EMAIL                  # default login
 
password='LA_MIA_PASSWORD'              # default password
 
server=www.dnsdynamic.org,              \
 
protocol=dyndns2                        \
 
IL_MIO_DOMINIO.QUALCOSA.QUALCOS'ALTRO
 
 
</pre>
 
</pre>
  
 
=== Configurazione di IceS ===
 
=== Configurazione di IceS ===
Copiare il file /usr/share/doc/ices2/examples/ices-alsa.xml in <code>/root/</code> e modificare le righe evidenziate in grassetto:
+
Copiare il file <code>/usr/share/doc/ices2/examples/ices-alsa.xml</code> in <code>/root/</code> e modificare le righe evidenziate in grassetto:
  
 
  <?xml version="1.0"?>
 
  <?xml version="1.0"?>
Riga 280: Riga 254:
 
         <!-- metadata used for stream listing -->
 
         <!-- metadata used for stream listing -->
 
         <metadata>
 
         <metadata>
             <name>Example stream name</name>
+
             <name>'''Radio[Nome]'''</name>
             <genre>Example genre</genre>
+
             <genre>'''Catholic'''</genre>
             <description>A short description of your stream</description>
+
             <description>'''Parish Radio from [...] (Brianza, Italy)'''</description>
             <url>http://mysite.org</url>
+
             <url>'''http://reginamartiri.it'''</url>
 
         </metadata>
 
         </metadata>
 
   
 
   
Riga 298: Riga 272:
 
             <!-- Read metadata (from stdin by default, or -->
 
             <!-- Read metadata (from stdin by default, or -->
 
             <!-- filename defined below (if the latter, only on SIGUSR1) -->
 
             <!-- filename defined below (if the latter, only on SIGUSR1) -->
             <param name="metadata">1</param>
+
             <param name="metadata">'''0'''</param>
             <param name="metadatafilename">test</param>
+
             '''<s><param name="metadatafilename">test</param></s>'''
 
         </input>
 
         </input>
 
   
 
   
Riga 347: Riga 321:
 
                 Set to the frequency (in Hz) you wish to resample to, -->
 
                 Set to the frequency (in Hz) you wish to resample to, -->
 
                
 
                
             '''<!--''' <resample>
+
             '''<s><resample></s>'''
                <in-rate>44100</in-rate>
+
                '''<s><in-rate>44100</in-rate></s>'''
                 <out-rate>22050</out-rate>
+
                 '''<s><out-rate>22050</out-rate></s>'''
             </resample> '''-->'''
+
             '''<s></resample></s>'''
 
         </instance>
 
         </instance>
 
   
 
   
Riga 362: Riga 336:
 
VLC, così come è compilato nel pacchetto fornito, non consente di essere eseguito dall'utente <code>root</code>. Per evitare di ricompilare tutto il programma è sufficiente sostituire la stringa "geteuid" con "getppid" usando un editor esadecimale:
 
VLC, così come è compilato nel pacchetto fornito, non consente di essere eseguito dall'utente <code>root</code>. Per evitare di ricompilare tutto il programma è sufficiente sostituire la stringa "geteuid" con "getppid" usando un editor esadecimale:
 
  # hexedit /usr/bin/vlc
 
  # hexedit /usr/bin/vlc
Premere TAB per passare alla modalità ASCII; cercare la stringa con CTRL+S; posizionarsi sui caratteri da sostituire; digitare i caratteri; premere CTRL+X per salvare e uscire.
+
Premere TAB per passare alla modalità ASCII; cercare la stringa con CTRL+S; posizionarsi sui caratteri da sostituire; digitare i caratteri; premere CTRL+X per salvare e uscire. La procedura deve essere ripetuta ogniqualvolta si vada ad aggiornare VLC via APT.
  
 
[http://radiomater.org/ Radio Mater] mette a disposizione lo stream in quattro formati, pensati per quattro player multimendiali:
 
[http://radiomater.org/ Radio Mater] mette a disposizione lo stream in quattro formati, pensati per quattro player multimendiali:
Riga 371: Riga 345:
  
 
=== Icecast ===
 
=== Icecast ===
Icecast va installato su un server con banda sufficiente a permettere il download a tutti gli utenti che dovessero collegarli.
+
Icecast va installato su un server con banda sufficiente a permettere il download a tutti gli utenti che dovessero collegarsi.
  
 
Per installarlo su un server Debian:
 
Per installarlo su un server Debian:
Riga 378: Riga 352:
 
Rispondere "No" alla proposta di configurazione.
 
Rispondere "No" alla proposta di configurazione.
  
Per configurarlo, modificare il file <code>/etc/icecast.xml</code> riducendo il numero di client che si possono connettere da 100 a 5 e impostando le 3 password (source, relay e admin). Mettere anche <code>ENABLE=true</code> in <code>/etc/default/icecast2</code>, in modo che parta automaticamente.
+
Per configurarlo, modificare il file <code>/etc/icecast2/icecast.xml</code> riducendo il numero di client che si possono connettere da 100 a 5 e impostando le 3 password (source, relay e admin); al fine di permettere ad Icecast di leggere questo file, modificarne i permessi con:
 +
# chmod 644 /etc/icecast2/icecast.xml
  
Ricordarsi di aprire la porta 8000 nel caso in cui il server Icecast riceva da Internet i flussi da ritrasmettere.
+
Mettere <code>ENABLE=true</code> in <code>/etc/default/icecast2</code>, in modo che Icecast parta automaticamente.
 +
 
 +
Eventualmente cambiare la porta di default (8000) sempre nel file di configurazione; ricordarsi di aprirla nel caso in cui il server Icecast riceva da Internet i flussi da ritrasmettere.
 +
 
 +
== Programmazione bottoni ==
 +
Ecco lo script <code>buttoncontroller.py</code>, posto nella directory <code>/root/</code> con i permessi di esecuzione da parte del proprietario (l'utente <code>root</code>):
 +
<pre>
 +
#!/usr/bin/env python3
 +
 
 +
import RPi.GPIO as GPIO
 +
import time
 +
import subprocess
 +
 
 +
GPIO.setwarnings(False)
 +
 
 +
# set up GPIO using BCM numbering
 +
GPIO.setmode(GPIO.BCM)
 +
 
 +
# mapping buttons, LEDs and programs
 +
button_pins = [17, 22, 18, 8, 7]
 +
led_pins = [10, 23, 24, 9, 11]
 +
programs = ['ices2 /root/ices-alsa.xml',
 +
            'cvlc http://URL:8000/FILE.ogg',
 +
            None, None, '']
 +
 
 +
# set buttons as GPIO input
 +
for pin in button_pins:
 +
    GPIO.setup(pin, GPIO.IN)
 +
 
 +
# set LEDs as GPIO output
 +
for pin in led_pins:
 +
    GPIO.setup(pin, GPIO.OUT)
 +
 
 +
# detect button pushes
 +
for pin in button_pins:
 +
    GPIO.add_event_detect(pin, GPIO.RISING, bouncetime=500)
 +
 
 +
# test LEDs
 +
for pin in led_pins:
 +
    GPIO.output(pin, True)
 +
    time.sleep(0.2)
 +
    GPIO.output(pin, False)
 +
 
 +
def run(button):
 +
    GPIO.output(led_pins[button], True)
 +
    if programs[button] is not '':
 +
        return subprocess.Popen(programs[button].split())
 +
    else:
 +
        time.sleep(0.2)
 +
        GPIO.output(led_pins[button], False)
 +
        return None
 +
 
 +
 
 +
if  __name__ == '__main__':
 +
    p = None
 +
    while True:
 +
        for button, pin in enumerate(button_pins):
 +
            if GPIO.event_detected(pin) and programs[button] is not None:
 +
                for pin in led_pins:
 +
                    GPIO.output(pin, False)
 +
                if p:
 +
                    p.terminate()
 +
                p = run(button)
 +
        time.sleep(0.1)
 +
</pre>
 +
sostituendo <code>URL</code> e <code>FILE</code> adeguatamente.
 +
 
 +
Come ultimo passo abbiamo aggiunto a <code>/etc/rc.local</code> la seguente riga, giusto prima di <code>exit 0</code>:
 +
exec /root/buttoncontroller.py &
  
 
== Test ==
 
== Test ==
Abbiamo messo a punto una serie di test per verificare rigorosamente l'affidabilità hardware e software del prototipo. Nel caso in cui un punto fallisse, si è trovata una soluzione e si è ripetuto quel punto.
+
Abbiamo messo a punto una serie di test per verificare rigorosamente l'affidabilità hardware e software del prototipo. Nel caso in cui un punto fallisse, si trovi una soluzione e si ripeta quel punto.
  
 
# Collegare ad un PC la scheda audio modificata, un impianto audio all'ingresso della scheda e realizzare una trasmissione della durata minima di 2 ore, senza interruzioni.
 
# Collegare ad un PC la scheda audio modificata, un impianto audio all'ingresso della scheda e realizzare una trasmissione della durata minima di 2 ore, senza interruzioni.
 
# Utilizzare la scheda audio per ricevere uno stream dal server Icecast per almeno un'ora senza interruzioni, verificando il corretto funzionamento di entrambe le uscite.
 
# Utilizzare la scheda audio per ricevere uno stream dal server Icecast per almeno un'ora senza interruzioni, verificando il corretto funzionamento di entrambe le uscite.
# Partendo da un'installazione minimale del sistema operativo, con i soli collegamenti strettamente indispensabili, installare il software di streaming e ripetere il primo punto usando questa volta il Raspberry Pi per effettuare la trasmissione.
+
# Partendo da un'installazione minimale del sistema operativo, con i soli collegamenti strettamente indispensabili, installare il software di streaming e ripetere il primo punto usando questa volta il Banana Pi per effettuare la trasmissione.
# Con i soli collegamenti strettamente indispensabili, installare il player multimediale e ripetere il secondo punto usando questa volta il Raspberry Pi per effettuare la ricezione.
+
# Con i soli collegamenti strettamente indispensabili, installare il player multimediale e ripetere il secondo punto usando questa volta il Banana Pi per effettuare la ricezione.
 
# Realizzare i collegamenti minimali sia per la ricezione che per la trasmissione e ripetere i due punti precedenti.
 
# Realizzare i collegamenti minimali sia per la ricezione che per la trasmissione e ripetere i due punti precedenti.
 
# Collegare man mano tutti i fili, ripetendo ogni volta il punto precedente usando però come interfaccia uomo-macchina quella definitiva (led e bottoni).
 
# Collegare man mano tutti i fili, ripetendo ogni volta il punto precedente usando però come interfaccia uomo-macchina quella definitiva (led e bottoni).
 
# Simulare l'interruzione del collegamento ad Internet.
 
# Simulare l'interruzione del collegamento ad Internet.
 +
 +
== Problemi ==
 +
=== Bottone di accensione (Missaglia) ===
 +
Il bottone di accensione a Missaglia non attiva lo spegnimento quando tenuto premuto; trovare la causa e porvi rimedio.
 +
 +
=== 1/3/2015: collegamento fallito ===
 +
Si sanno 3 cose:
 +
# il primo marzo 2015 si è manifestato un problema che ha reso impossibile il collegamento tra la chiesa di Missaglia a quella di Maresso;
 +
# il problema è stato localizzato a Missaglia;
 +
# il problema si manifestava con l'impossibilità da parte di IceS di collegarsi al server Icecast (collocato in Oregon), facendo quindi fallire la trasmissione.
 +
 +
Tutto ciò è stato dedotto da numerosi test e dall'analisi del log di IceS svolti la sera stessa collegandoci via SSH dalla chiesa di Maresso alla macchina di Missaglia.
 +
 +
Purtroppo non è stato possibile individuare la causa del problema, in quanto quando ci siamo recati sul posto il pomeriggio successivo, tutto è tornato a funzionare perfettamente.
  
 
== TODO ==
 
== TODO ==
* l'IP statico non funziona; capire perché
+
* bottoni virtuali per comandare le macchine da remoto
* [http://www.savagehomeautomation.com/projects/raspberry-pi-rs232-serial-interface-options-revisit.html interfaccia seriale] (vedi anche [http://learn.adafruit.com/adafruits-raspberry-pi-lesson-5-using-a-console-cable/ qui], [http://www.trainelectronics.com/RaspberryPi/ qui] in basso, [http://www.fasttech.com/product/1247400-jy-mcu-mini-rs232-to-ttl-converter-module-board-35 qui] e [https://blogs.oracle.com/speakjava/entry/serial_communications_with_a_raspberry qui])
+
* fare in modo che, in caso di interruzione del processo a causa di un errore inatteso, il LED corrispondente (trasmetti o ricevi) si spenga
 +
* scrivere un programmino da far girare su una macchina indipendente che mandi un messaggio di allarme (e-mail, per esempio) non appena qualcosa smette di funzionare correttamente; dalla versione 2.4.0 di Icecast è disponibile un'[http://icecast.org/docs/icecast-2.4.1/changes.html API JSON] che potrebbe tornare comoda
 +
* configurare i router (QoS) in modo da garantire alle macchine una banda minima in upload sufficiente per trasmettere gli stream
 +
* inserire le nostre radio nelle "[http://icecast.org/docs/icecast-2.3.2/yp.html pagine gialle]" di Icecast
 +
* [http://www.savagehomeautomation.com/projects/raspberry-pi-rs232-serial-interface-options-revisit.html interfaccia seriale] (vedi anche [http://learn.adafruit.com/adafruits-raspberry-pi-lesson-5-using-a-console-cable/ qui], [http://www.trainelectronics.com/RaspberryPi/ qui] in basso, [http://www.fasttech.com/product/1247400-jy-mcu-mini-rs232-to-ttl-converter-module-board-35 qui] e [https://blogs.oracle.com/speakjava/entry/serial_communications_with_a_raspberry qui]); [http://techpubs.sgi.com/library/tpl/cgi-bin/getdoc.cgi?coll=linux&db=bks&srch=&fname=/SGI_EndUser/SGIconsole_HW_CG/sgi_html/apb.html qui] lo schema per il cavo
 
* [http://www.plcforum.it/f/topic/145470-alimentare-raspberry-tramite-gpio-5v/ alimentazione]
 
* [http://www.plcforum.it/f/topic/145470-alimentare-raspberry-tramite-gpio-5v/ alimentazione]
* [http://protoplates.com/frontpaneldesigner.html disegno] [http://protoplates.com/designtips.html e] stampa dei frontali
 
 
* reset
 
* reset
* testare il codec Opus con DarkIce
+
* testare il codec Opus (con DarkIce?); serve Icecast >= 2.4.0
 
* documentare circuiti
 
* documentare circuiti
 
* test
 
* test
 +
* studiare la programmazione radiofonica (palinsesto): probabilmente serviranno [http://jackaudio.org/ JACK] e [http://liquidsoap.fm/ liquidsoap]; [http://soapbox.chrismarquardt.com/post/podcast-streaming-setup-the-big-picture qui] una lettura interessante
 +
 +
== Possibili evoluzioni ==
 +
Le macchine realizzate sono sostanzialmente dei prototipi. Chi volesse perfezionarli ed eventualmente portarli ad una scala industriale troverà qui alcuni spunti:
 +
* [http://protoplates.com/frontpaneldesigner.html disegno] [http://protoplates.com/designtips.html e] stampa dei frontali
 +
* aggiunta di un display sul frontalino
 +
* sostituzione di Banana Pi + scheda audio con una scheda progettata ad hoc
  
 
== Sitografia ==
 
== Sitografia ==
 
* [http://elinux.org/RPi_Low-level_peripherals eLinux - RPi Low-level peripherals]
 
* [http://elinux.org/RPi_Low-level_peripherals eLinux - RPi Low-level peripherals]
 
* [http://elinux.org/RPi_BCM2835_GPIOs eLinux - RPi BCM2835 GPIOs]
 
* [http://elinux.org/RPi_BCM2835_GPIOs eLinux - RPi BCM2835 GPIOs]
 +
* [http://hardware-libre.fr/2014/07/banana-pi-gpio-now-supported/ Banana Pi GPIO]

Versione attuale delle 19:48, 4 ago 2020

Con questo progetto si è realizzata la web radio della Comunità pastorale di Missaglia (LC).

Il sistema prevede il collegamento audio via Internet tra le chiese parrocchiali di Maresso e Missaglia, in modo tale da poter ascoltare presso la chiesa locale eventi che si svolgano nell'altra chiesa; quanto viene diffuso attraverso l'impianto audio della chiesa viene inoltre trasmesso via etere ai parrocchiani dotati di un particolare apparecchio radio.

In un secondo tempo si renderà disponibile una playlist di brani da trasmettersi in automatico nel caso saltasse il collegamento ad Internet.

In futuro si valuterà se rendere fruibile al pubblico lo stream della web radio.

Il sistema è modulare, per far sì che si possano aggiungere in futuro nuove stazioni ricetrasmittenti e mettere così in comunicazione un maggior numero di chiese tra loro.

Prerequisiti

  • connessione ad Internet (ADSL o superiore)
  • impianti audio con almeno un ingresso e un'uscita liberi (possibilmente bilanciati)

Hardware

Per ogni stazione ricetrasmittente:

Fornitori:

Pannelli

Per la progettazione dei pannelli dell'unità rack, una valida metodologia di lavoro potrebbe essere questa (seguendo anche questi consigli). Purtroppo non è possibile al momento adattarla al mondo del software libero, a causa della indisponibilità di strumenti CAD adeguati.

Per questo progetto abbiamo dunque preferito attendere l'arrivo di tutti i pezzi da montare sui pannelli, posizionandoli manualmente sui pannelli smontati in una disposizione indicativa, e prendere le misure con metro e calibro.

Le misure reali del pannello frontale da 1U da noi acquistato sono 482,6 mm × 44,3 mm; quelle del pannello posteriore sono 400 mm × 41 mm.

Console port

Il Banana Pi dispone sul GPIO J11 di un'interfaccia seriale già configurata (usando Bananian) per funzionare come console seriale. Abbiamo quindi deciso di sfruttare questa potenzialità per poter facilmente accedere al sistema operativo in caso di malfunzionamento.

P01, attaccato al GPIO J12, corrisponde a TXD; P02, subito a fianco, a RXD.

Occorre collegare i pin GPIO ad una scheda costituita da un integrato (MAX3232) e da cinque condensatori. Questa scheda, facilmente costruibile anche in casa, fornisce un'interfaccia RS-232 standard, a 5 V; il MAX3232 invece funziona anche a tensioni più basse, come quella del Banana Pi (3,3 V).

Noi abbiamo optato per l'acquisto di una scheda già pronta; esistono prodotti dotati di connettore saldato, ma abbiamo preferito acquistarne uno sprovvisto, per poter posizionare la scheda liberamente all'interno dell'unità rack.

Per questioni estetiche abbiamo deciso di evitare di posizionare un connettore D-sub sul pannello frontale; ci siamo quindi inventati una nostra soluzione proprietaria, che trae ispirazione dalla console port di Cisco.

Bottone di accensione

La procedura è spiegata chiaramente sul sito ufficiale della scheda ATXRaspi. In sintesi abbiamo alimentato la scheda saldando direttamente due fili all'alimentatore; abbiamo attaccato un bottone e un LED saldando i fili del primo direttamente, e interponendo una resistenza adeguata per il LED; abbiamo attaccato i 4 cavi diretti alla scheda ATXRaspi (alimentazione sui pin laterali e controllo in mezzo, GPIO28 a IN e GPIO30 ad OUT) all'header J12 del Banana Pi che corrisponde al P5 del Raspberry Pi (la fila più vicina all'header P1).

PIN BCM
J12-P03 28
J12-P05 30

Abbiamo riscritto lo script shutdowncheck.py da zero, in Python, ponendolo nella directory /root/ e dandogli i permessi di esecuzione da parte del proprietario (l'utente root):

#!/usr/bin/env python3

import RPi.GPIO as GPIO
import time
import subprocess

GPIO.setwarnings(False)

# set up GPIO using BCM numbering
GPIO.setmode(GPIO.BCM)

GPIO.setup(28, GPIO.IN)
GPIO.setup(30, GPIO.OUT)
GPIO.output(30, True)

# detect button pushes
GPIO.add_event_detect(28, GPIO.RISING, bouncetime=500)

if  __name__ == '__main__':
    while True:
        if GPIO.event_detected(28):
            print('PIN 28 requested a SYSTEM HALT!')
            p = subprocess.Popen('shutdown -h now'.split())
        time.sleep(0.5)

Infine abbiamo aggiunto a /etc/rc.local la seguente riga, giusto prima di exit 0:

exec /root/shutdowncheck.py &

Bottoni

PIN BCM
1 11 17
2 15 22
3 12 18
4 24 8
5 26 7

LED

PIN BCM
1 19 10
2 16 23
3 18 24
4 21 9
5 23 11

Software

Sistema operativo

Seguire le istruzioni presenti qui per ottenere una versione recente di Bananian e copiarla sulla scheda SD.

Inserire la scheda SD nel Banana Pi; collegare il cavo di rete al dispositivo, possibilmente un monitor HDMI e una tastiera, e alimentarlo.

Effettuare il login (username: root; password: pi) e lanciare bananian-config.

Per comodità installare vim:

# aptitude update
# aptitude install vim
# aptitude purge vim-tiny

Installare infine i programmi usati per questo progetto:

# aptitude install alsa-base ices2 vlc-nox hexedit python3-dev gcc unzip

Installare python3-rpi.gpio:

# wget https://github.com/LeMaker/RPi.GPIO_BP/archive/bananapi.zip
# unzip bananapi.zip
# cd RPi.GPIO_BP-bananapi/
# python3 setup.py install
# cd ..
# rm -r RPi.GPIO_BP-bananapi bananapi.zip

Impostare l'indirizzo IP come fisso, modificando il file /etc/network/interfaces ed, eventualmente, /etc/resolv.conf; nel nostro caso abbiamo scelto come indirizzo 192.168.0.40 in un caso, e 192.168.1.40 in un altro. Potrebbe essere necessario ridurre il range gestito dal server DHCP (ad esempio configurando adeguatamente il router) in modo tale da non avere conflitti con questi indirizzi.

Per impostare la scheda audio esterna come dispositivo di default, verificare che il file /etc/asound.conf presenti il seguente contenuto:

pcm.!default {
		type hw
		card 1
}

ctl.!default {
		type hw
		card 1
}

DNS dinamico

La condizione ideale sarebbe quella di disporre di un indirizzo IP statico per ogni ricetrasmittente. Purtroppo è molto più probabile che il provider fornisca un indirizzo IP dinamico. Per rendere sempre raggiungibili i diversi host occorre quindi ricorrere ad un servizio di DNS dinamico.

Per questo progetto si è deciso di sfruttare OpenNIC. Occorre creare un account sul sito e poi registrare un dominio .dyn per ogni stazione ricetrasmittente.

Per ogni dominio, una volta preso nota dell'URI messo a disposizione da OpenNIC per aggiornare l'indirizzo IP, è sufficiente scrivere sulla macchina che vogliamo raggiungere da Internet uno script (che chiameremo /root/ddns.sh) analogo al seguente, sostituendovi l'URI precedentemente appuntato:

#!/bin/sh
wget -qO- "https://api.opennicproject.org/ddns/example.dyn?user=myUserName&auth=myAuthCode" > /dev/null

Una volta datogli i permessi di esecuzione, creeremo un cronjob da lanciarsi ogni 10 minuti:

# crontab -e
# m h  dom mon dow   command
*/10 * * * * /root/ddns.sh

Bisogna infine pazientare il tempo necessario affinché i nuovi domini da noi registrati si propaghino per tutta la rete (in genere bastano pochi minuti). Si può verificare che sia tutto a posto lato DNS lanciando questo comando da una macchina che sfrutti i DNS OpenNIC:

$ dig mio_dominio.dyn

Configurazione di IceS

Copiare il file /usr/share/doc/ices2/examples/ices-alsa.xml in /root/ e modificare le righe evidenziate in grassetto:

<?xml version="1.0"?>
<ices>

    <background>0</background>
    <logpath>/var/log/ices</logpath>
    <logfile>ices.log</logfile>
    <logsize>2048</logsize>
    <loglevel>4</loglevel>
    <consolelog>0</consolelog>


    <stream>
        <metadata>
            <name>Radio[Nome]</name>
            <genre>Catholic</genre>
            <description>Parish Radio from [...] (Brianza, Italy)</description>
            <url>http://reginamartiri.it</url>
        </metadata>

        <input>
            <module>alsa</module>
            <param name="rate">44100</param>
            <param name="channels">2</param>
            <param name="device">hw:1,0</param>
            <param name="metadata">0</param>
            <param name="metadatafilename">test</param>
        </input>


        <instance>

            <hostname>indirizzo_IP_corretto</hostname>
            <port>8000</port>
            <password>password_corretta</password>
            <mount>/nome_stream.ogg</mount>
            <yp>0</yp>   


            <encode>  
                <quality>0</quality>
                <samplerate>44100</samplerate>
                <channels>1</channels>
            </encode>

            <downmix>1</downmix>

             
            <resample>
                <in-rate>44100</in-rate>
                <out-rate>22050</out-rate>
            </resample>
        </instance>

    </stream>
</ices>

Creare la directory per i log:

# mkdir /var/log/ices

VLC

VLC, così come è compilato nel pacchetto fornito, non consente di essere eseguito dall'utente root. Per evitare di ricompilare tutto il programma è sufficiente sostituire la stringa "geteuid" con "getppid" usando un editor esadecimale:

# hexedit /usr/bin/vlc

Premere TAB per passare alla modalità ASCII; cercare la stringa con CTRL+S; posizionarsi sui caratteri da sostituire; digitare i caratteri; premere CTRL+X per salvare e uscire. La procedura deve essere ripetuta ogniqualvolta si vada ad aggiornare VLC via APT.

Radio Mater mette a disposizione lo stream in quattro formati, pensati per quattro player multimendiali:

  • iTunes, mono, codifica MPEG Audio layer 1/2/3 (mpga), campionamento 24000 Hz, bitrate 32 kb/s
  • Windows Media Player, stereo, codifica Windows Media Audio 2 (WMA2), campionamento 32000 Hz (32 bit per campione), bitrate variabile attorno ai 32 kb/s
  • RealPlayer, mono, codifica MPEG Audio layer 1/2/3 (mpga), campionamento 24000 Hz, bitrate 32 kb/s
  • QuickTime, VLC 2.1.2 non lo supporta

Icecast

Icecast va installato su un server con banda sufficiente a permettere il download a tutti gli utenti che dovessero collegarsi.

Per installarlo su un server Debian:

# aptitude install icecast2

Rispondere "No" alla proposta di configurazione.

Per configurarlo, modificare il file /etc/icecast2/icecast.xml riducendo il numero di client che si possono connettere da 100 a 5 e impostando le 3 password (source, relay e admin); al fine di permettere ad Icecast di leggere questo file, modificarne i permessi con:

# chmod 644 /etc/icecast2/icecast.xml

Mettere ENABLE=true in /etc/default/icecast2, in modo che Icecast parta automaticamente.

Eventualmente cambiare la porta di default (8000) sempre nel file di configurazione; ricordarsi di aprirla nel caso in cui il server Icecast riceva da Internet i flussi da ritrasmettere.

Programmazione bottoni

Ecco lo script buttoncontroller.py, posto nella directory /root/ con i permessi di esecuzione da parte del proprietario (l'utente root):

#!/usr/bin/env python3

import RPi.GPIO as GPIO
import time
import subprocess

GPIO.setwarnings(False)

# set up GPIO using BCM numbering
GPIO.setmode(GPIO.BCM)

# mapping buttons, LEDs and programs
button_pins = [17, 22, 18, 8, 7]
led_pins = [10, 23, 24, 9, 11]
programs = ['ices2 /root/ices-alsa.xml',
            'cvlc http://URL:8000/FILE.ogg',
            None, None, '']

# set buttons as GPIO input
for pin in button_pins:
    GPIO.setup(pin, GPIO.IN)

# set LEDs as GPIO output
for pin in led_pins:
    GPIO.setup(pin, GPIO.OUT)

# detect button pushes
for pin in button_pins:
    GPIO.add_event_detect(pin, GPIO.RISING, bouncetime=500)

# test LEDs
for pin in led_pins:
    GPIO.output(pin, True)
    time.sleep(0.2)
    GPIO.output(pin, False)

def run(button):
    GPIO.output(led_pins[button], True)
    if programs[button] is not '':
        return subprocess.Popen(programs[button].split())
    else:
        time.sleep(0.2)
        GPIO.output(led_pins[button], False)
        return None


if  __name__ == '__main__':
    p = None
    while True:
        for button, pin in enumerate(button_pins):
            if GPIO.event_detected(pin) and programs[button] is not None:
                for pin in led_pins:
                    GPIO.output(pin, False)
                if p:
                    p.terminate()
                p = run(button)
        time.sleep(0.1)

sostituendo URL e FILE adeguatamente.

Come ultimo passo abbiamo aggiunto a /etc/rc.local la seguente riga, giusto prima di exit 0:

exec /root/buttoncontroller.py &

Test

Abbiamo messo a punto una serie di test per verificare rigorosamente l'affidabilità hardware e software del prototipo. Nel caso in cui un punto fallisse, si trovi una soluzione e si ripeta quel punto.

  1. Collegare ad un PC la scheda audio modificata, un impianto audio all'ingresso della scheda e realizzare una trasmissione della durata minima di 2 ore, senza interruzioni.
  2. Utilizzare la scheda audio per ricevere uno stream dal server Icecast per almeno un'ora senza interruzioni, verificando il corretto funzionamento di entrambe le uscite.
  3. Partendo da un'installazione minimale del sistema operativo, con i soli collegamenti strettamente indispensabili, installare il software di streaming e ripetere il primo punto usando questa volta il Banana Pi per effettuare la trasmissione.
  4. Con i soli collegamenti strettamente indispensabili, installare il player multimediale e ripetere il secondo punto usando questa volta il Banana Pi per effettuare la ricezione.
  5. Realizzare i collegamenti minimali sia per la ricezione che per la trasmissione e ripetere i due punti precedenti.
  6. Collegare man mano tutti i fili, ripetendo ogni volta il punto precedente usando però come interfaccia uomo-macchina quella definitiva (led e bottoni).
  7. Simulare l'interruzione del collegamento ad Internet.

Problemi

Bottone di accensione (Missaglia)

Il bottone di accensione a Missaglia non attiva lo spegnimento quando tenuto premuto; trovare la causa e porvi rimedio.

1/3/2015: collegamento fallito

Si sanno 3 cose:

  1. il primo marzo 2015 si è manifestato un problema che ha reso impossibile il collegamento tra la chiesa di Missaglia a quella di Maresso;
  2. il problema è stato localizzato a Missaglia;
  3. il problema si manifestava con l'impossibilità da parte di IceS di collegarsi al server Icecast (collocato in Oregon), facendo quindi fallire la trasmissione.

Tutto ciò è stato dedotto da numerosi test e dall'analisi del log di IceS svolti la sera stessa collegandoci via SSH dalla chiesa di Maresso alla macchina di Missaglia.

Purtroppo non è stato possibile individuare la causa del problema, in quanto quando ci siamo recati sul posto il pomeriggio successivo, tutto è tornato a funzionare perfettamente.

TODO

  • bottoni virtuali per comandare le macchine da remoto
  • fare in modo che, in caso di interruzione del processo a causa di un errore inatteso, il LED corrispondente (trasmetti o ricevi) si spenga
  • scrivere un programmino da far girare su una macchina indipendente che mandi un messaggio di allarme (e-mail, per esempio) non appena qualcosa smette di funzionare correttamente; dalla versione 2.4.0 di Icecast è disponibile un'API JSON che potrebbe tornare comoda
  • configurare i router (QoS) in modo da garantire alle macchine una banda minima in upload sufficiente per trasmettere gli stream
  • inserire le nostre radio nelle "pagine gialle" di Icecast
  • interfaccia seriale (vedi anche qui, qui in basso, qui e qui); qui lo schema per il cavo
  • alimentazione
  • reset
  • testare il codec Opus (con DarkIce?); serve Icecast >= 2.4.0
  • documentare circuiti
  • test
  • studiare la programmazione radiofonica (palinsesto): probabilmente serviranno JACK e liquidsoap; qui una lettura interessante

Possibili evoluzioni

Le macchine realizzate sono sostanzialmente dei prototipi. Chi volesse perfezionarli ed eventualmente portarli ad una scala industriale troverà qui alcuni spunti:

  • disegno e stampa dei frontali
  • aggiunta di un display sul frontalino
  • sostituzione di Banana Pi + scheda audio con una scheda progettata ad hoc

Sitografia