Raspberry PiとArduinoで作るWi-Fiインターネットラジオ:世界中の局をストリーミング
必要な部品と材料
| 部品 | 数量 | 備考 |
|---|---|---|
| Arduino UNO | 1 | |
| Raspberry Pi 3 Model B | 1 | 内蔵Wi-Fi搭載、アンテナ不要 |
| SainSmart LCDキーパッドシールド (Arduino用) | 1 | プッシュボタン付きLCD |
| 5V電源 | 1 |
使用するアプリとオンラインサービス
- Python
プロジェクト概要
概要
工作や料理中にラジオを聴くのが好きですが、私の住んでいる場所ではFM受信状態が悪く、インターネット経由で世界中の局を聴きたいと考えていました。スマホで聴くのは避けたいため、ネットで調べたいくつかの解決策を組み合わせてこのプロジェクトを作りました。
ベースにしたのは akellyirl氏の「Arduino / Raspberry Pi Internet Radio」 というチュートリアルです。Raspberry Pi上でPythonコードが動作し、Linuxの音楽プレイヤーデーモン mpd とクライアント mpc を使用します。nanpy というライブラリでPythonからArduinoを操作し、ボタン入力とLCD表示を制御しています。
Arduino用のLCD/キーパッドシールド(SainSmart製)を使用し、後から壊れた目覚まし時計から取り出した4ボタンストリップを追加しました。当初はRaspberry Pi 2とUSB Wi-Fiアダプタを使っていましたが、現在は内蔵Wi-Fiを持つRP3に移行しています。
一番の難関は、聴きたい局の正しいストリーミングURLを見つけることでした。最終的に URL HELPER という、再生中のメディアURLをスニッフィングするツールを見つけて解決しました。音声出力は、ジャンクから回収したアクティブスピーカーを使用しています。
インストール手順 (RP3用)
元チュートリアル (akellyirl氏、Arduino掲載) に基づく手順です。
- Raspbianを最新パッケージに更新:
$ sudo apt-get update mpd/mpcパッケージをインストール:$ sudo apt-get install mpc mpd
PythonからArduinoと通信するため、nanpy ライブラリ(Python側)とファームウェア(Arduino側)を導入します。
- Arduinoパッケージをインストール(nanpyファームウェアビルド用):
$ apt-get install arduino nanpyPythonライブラリを取得・展開(/home/piにて):$ wget https://pypi.python.org/packages/source/n/nanpy/nanpy-v0.7.tar.gz $ tar xvf nanpy-v0.7.tar.gzsetuptools(nanpyの依存) を取得・展開:$ wget https://pypi.python.org/packages/source/s/setuptools/setuptools-0.6c11.tar.gz#md5=7df2a529a074f613b509fb44feefe74e $ tar xvf setuptools-0.6c11.tar.gzpyserial(nanpyの依存) を取得・展開:$ wget https://pypi.python.org/packages/source/p/pyserial/pyserial-2.6.tar.gz $ tar xvf pyserial-2.6.tar.gzsetuptoolsをインストール:$ cd setuptools-0.6c11 $ sudo python setup.py installnanpyPythonライブラリをインストール:$ cd ../nanpy $ sudo python setup.py installpyserialをインストール:$ cd ../pyserial-2.6 $ sudo python setup.py install
必要なファイルが揃ったので、Arduinoにnanpyファームウェアを書き込みます。
ファームウェア書き込み
nanpy は、Raspberry Pi上のPythonプログラムから、慣れ親しんだArduinoスケッチ風の構文でArduinoを操作できるようにするものです。例えばアナログ入力読み取りは Python側で int val = Arduino.analogRead(14) のように書けます。
nanpyには2つの構成要素があります:
- Pythonライブラリ:Arduinoコマンドを馴染みの構文で記述し、Arduinoと通信
- ファームウェア:Arduino上で動作し、Pi上のPythonと通信
前ステップでPython側をインストールし、Arduino側ファームウェアもPi上にダウンロード済みです。これをArduinoに書き込みます。
ArduinoをRaspberry PiのUSBポートに接続してください。
- nanpyファームウェアディレクトリへ移動し、書き込み実行:
$ cd ../nanpy/firmware $ export BOARD=uno $ make $ make upload /dev/ttyACM0でデバイスが見つからないエラーが出たら、Arduinoをもう一方のUSBポートに挿し直してください。
私はプレイリスト読み込み用スクリプトとメインのPythonスクリプト(コードタブに掲載)を作成し、PythonスクリプトをRaspberry Pi起動時に自動実行するよう設定しました。また、IPアドレス表示や音声出力切替などのメニュー機能用に追加ボタンも設けました。
最新アップデート:ロータリーエンコーダーを追加して選局と音量調整ができるようにし、3Dプリンタでケースを作り直しました。
ソースコード
piradio (Python)
from datetime import datetime
from subprocess import *
from time import sleep, strftime
from Queue import Queue
from threading import Thread
import os
from nanpy import Arduino, Lcd
import socket
Arduino.pinMode(14, input)
lcd = Lcd([8,9,4,5,6,7],[16,2]) # Sainsmartシールド用LCDピン設定
lcd.printString("Ruben's RadioZ",0,0)
lcd.printString("Loading" + "."*3,0,1)
sleep(5)
max_trax = 10
x = 1
loop_menu = 1
loop_radio = 1
stations=[]
# ボタン押下時に局名表示するためのリスト
stations.append("Ecco 99 ")
stations.append("Galgalaz ")
stations.append("Galaz ")
stations.append("88 FM ")
stations.append("Gimel ")
stations.append("Radios 100fm ")
stations.append("Kol Hamusica ")
stations.append("WQXR New York ")
stations.append("WPR Winsconsin ")
stations.append("BBC world servic")
stations.append("LINN jazz")
stations.append("LINN classical")
def get_local_ip_address(target):
ipaddr = ''
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect((target, 8000))
ipaddr = s.getsockname()[0]
s.close()
except:
pass
return ipaddr
print "Raspberry Pi - Local IP Address"
print(get_local_ip_address('10.0.1.1'))
print(get_local_ip_address('google.com'))
def display_ipaddr():
# IPアドレス表示 (wlan0/eth0対応)
show_wlan0 = "ip addr show wlan0 | cut -d/ -f1 | awk '/inet/ {printf \"w%15.15s\", $2}'"
show_eth0 = "ip addr show eth0 | cut -d/ -f1 | awk '/inet/ {printf \"e%15.15s\", $2}'"
ipaddr = get_local_ip_address('10.0.1.1')
lcd.printString('IP Address:',0,0)
lcd.printString(ipaddr,0,1)
sleep(2)
def displaymenu():
# 5番目のボタンでメニュー表示
if x==1:
lcd.printString("1. Display ",0,0); lcd.printString(" IP Address ",0,1)
elif x==2:
lcd.printString("2. Audio Output ",0,0); lcd.printString(" to hdmi Port ",0,1)
elif x==3:
lcd.printString("3. Audio Output ",0,0); lcd.printString(" to Analog port",0,1)
elif x==4:
lcd.printString("4. Audio Output ",0,0); lcd.printString(" Auto Sel. Port",0,1)
elif x==5:
lcd.printString("5. Reload the ",0,0); lcd.printString(" Playlist ",0,1)
elif x==6:
lcd.printString("6. ShutDown ",0,0); lcd.printString(" the System ",0,1)
else:
lcd.printString("7. Exit to ",0,0); lcd.printString(" Main Menu ",0,1)
def load_playlist():
output = run_cmd("mpc clear")
output = run_cmd("/home/pi/radio/radio_playlist.sh")
def run_cmd(cmd):
p = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT)
output = p.communicate()[0]
return output
def getKey():
val = Arduino.analogRead(14)
if val == 1023: return "NONE"
elif val < 100: return "RIGHT"
elif val < 150: return "UP"
elif val < 330: return "DOWN"
elif val < 510: return "LEFT"
elif val < 750: return "SEL"
else: return "KBD_FAULT"
load_playlist()
def getTrack():
L= [S.strip('\n') for S in os.popen('mpc').readlines()]
output = run_cmd("mpc current")
station = output[0:16]
track = output[-17:-1]
lcd.printString(station + " "*(16 - len(station)), 0, 0)
lcd.printString(track + " "*(16 - len(track)), 0, 1)
track_num = 1
os.system("sudo service mpd restart")
os.system("mpc play "+str(track_num))
while loop_radio == 1:
getTrack()
loop_menu = 1
x = 1
key = getKey()
if key == "UP":
track_num += 1
if track_num > max_trax: track_num = 1
lcd.printString(stations[track_num-1],0,0)
os.system("sudo service mpd restart")
os.system("mpc play " + str(track_num))
getTrack()
elif key == "DOWN":
track_num -= 1
if track_num < 1: track_num = max_trax
lcd.printString(stations[track_num-1],0,0)
os.system("sudo service mpd restart")
os.system("mpc play " + str(track_num))
getTrack()
elif key == "LEFT":
os.system("mpc volume +10")
lcd.printString(16*" ", 0, 0); lcd.printString(16*" ", 0, 1)
output = run_cmd("mpc volume")
lcd.printString("VOLUME UP:", 0, 0); lcd.printString(output, 5, 1)
sleep(.25)
lcd.printString(16*" ",0,0); lcd.printString(16*" ",0,1)
elif key == "RIGHT":
os.system("mpc volume -10")
lcd.printString(16*" ",0,0); lcd.printString(16*" ", 0,1)
output = run_cmd("mpc volume")
lcd.printString("VOLUME DOWN:", 0, 0); lcd.printString(output, 5, 1)
sleep(.25)
lcd.printString(16*" ",0,0); lcd.printString(16*" ",0,1)
elif key == "SEL":
while loop_menu == 1:
displaymenu()
key = getKey()
if key == "RIGHT":
os.system("mpc volume +10")
lcd.printString(16*" ", 0, 0); lcd.printString(16*" ", 0, 1)
output = run_cmd("mpc volume")
lcd.printString("VOLUME UP:", 0, 0); lcd.printString(output, 5, 1)
sleep(.25)
lcd.printString(16*" ",0,0); lcd.printString(16*" ",0,1)
elif key == "LEFT":
os.system("mpc volume -10")
lcd.printString(16*" ",0,0); lcd.printString(16*" ", 0,1)
output = run_cmd("mpc volume")
lcd.printString("VOLUME DOWN:", 0, 0); lcd.printString(output, 5, 1)
sleep(.25)
lcd.printString(16*" ",0,0); lcd.printString(16*" ",0,1)
elif key == "UP":
if x <= 1: x = 7
else: x = x - 1
elif key == "DOWN":
if x >= 7: x = 1
else: x = x + 1
elif key == "SEL":
if x == 1: display_ipaddr(); sleep(1)
elif x == 2:
output = run_cmd("amixer -q cset numid=3 2")
lcd.printString("Audio OUT-->HDMI", 0, 0); lcd.printString("output ", 0, 1); sleep(.5)
elif x == 3:
output = run_cmd("amixer -q cset numid=3 1")
lcd.printString("Audio OUT->Analog", 0, 0); lcd.printString("output ", 0, 1); sleep(.5)
elif x == 4:
output = run_cmd("amixer -q cset numid=3 0")
lcd.printString("Audio OUT-> Auto", 0, 0); lcd.printString("output ", 0, 1); sleep(.5)
elif x == 5:
load_playlist(); os.system('mpc play 1')
elif x == 6:
lcd.printString("Good Bye ", 0, 0); lcd.printString("Have a Nice Day ", 0, 1)
output = run_cmd("mpc stop"); quit()
elif x == 7:
loop_menu = 0; getTrack(); break
elif key == "RIGHT":
os.system("mpc volume +2")
lcd.printString(16*" ",0,0); lcd.printString(16*" ", 0,1)
output = run_cmd("mpc volume")
lcd.printString("VOLUME UP:", 0, 0); lcd.printString(output, 5, 1)
sleep(.25)
lcd.printString(16*" ",0,0); lcd.printString(16*" ",0,1)
elif key == "LEFT":
os.system("mpc volume -2")
lcd.printString(16*" ", 0, 0); lcd.printString(16*" ", 0, 1)
output = run_cmd("mpc volume")
lcd.printString("VOLUME DOWN:", 0, 0); lcd.printString(output, 5, 1)
sleep(.25)
lcd.printString(16*" ",0,0); lcd.printString(16*" ",0,1)
radio_playlist.sh (シェルスクリプト)
#! /bin/sh
mpc clear 1>/dev/null
#1 ecco99
mpc add https://99.livecdn.biz/99fm_aac
#2 galgalaz
mpc add https://glglzwizzlv.bynetcdn.com/glglz_mp3
#3 galaz
mpc add https://glzwizzlv.bynetcdn.com/glz_mp3
#4 88 fm
mpc add https://ibala.vidnt.com:8000/iba_radio-88fmM
#5 gimel
mpc add https://ibala.vidnt.com:8000/iba_radio-gimelM
#6 radios 100 fm
mpc add https://100fm.streamgates.net/Radios100Fm
#7 kol hamusica
mpc add https://ibala.vidnt.com:8000/iba_radio-kolmusicaM
#8 wqxr
mpc add https://stream.wqxr.org/wqxr
#9 npr current
mpc add https://current.stream.publicradio.org/kcmp.mp3
#10 bbc
mpc add https://bbcwssc.ic.llnwd.net/stream/bbcwssc_mp1_ws-eieuk
#11 linn jazz
mpc add https://radio.linnrecords.com:8003/autodj
#12 linn classical
mpc add https://radio.linnrecords.com:8004/autodj
回路図
製造プロセス
- LIRCでプロフェッショナルなRaspberry Piユニバーサルリモコンを構築
- Raspberry PiでRFをDCに変換: エンベロープ検波器の構築とトラブルシューティング
- Cycle Chaser: Raspberry Piで自転車をナイトタイムライトショーに変身
- Raspberry Piベースの泥棒検知器を構築 - ステップバイステップガイド
- Raspberry Pi、MCP9808、InfluxDB、Grafanaで作る家庭温度モニター
- Raspberry Piで構築するホームオートメーションシステム:リモート制御対応
- MrRobot: Ubuntu Mobile で操作する Rapiro ロボット – 音声、タッチ、シェイクインタラクション
- DIY Wall-E 風 Raspberry Pi CD ボックスロボット
- Bluetooth 制御 Raspberry Pi ロボットに音声フィードバックを追加して構築
- Arduino & Raspberry Pi でインターネット制御ビデオストリーミングロボットを構築
- RasPiRobot Board V2:Raspberry Pi を強力なロボットコントローラーに拡張