Last active
October 14, 2023 14:32
-
-
Save probonopd/5991009 to your computer and use it in GitHub Desktop.
Working way to create a simple Ethernet/WLAN web radio player using a cheap tiny WR703N router and a cheap USB C-Media soundcard. Switch radio stations with the reset switch. -- THE ACTUAL CODE ON THIS PAGE IS ***OUTDATED***, use https://github.com/probonopd/minikrebs instead!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# http://www.aliexpress.com/item/New-Mini-Portable-Wireless-3G-Router-TP-LINK-TL-WR703N-150M-150Mbps-WR703N-Pocket-size-Wifi/948558329.html | |
# Someone should convince TP-Link to build this with more Flash, serial headers, bare board, *duino style... | |
# This is the starting point for a webradio appliance in the works | |
# TODO: Preconfigure everything, hook up Arduino for IR control sending and receiving | |
wget http://downloads.openwrt.org/attitude_adjustment/12.09/ar71xx/generic/OpenWrt-ImageBuilder-ar71xx_generic-for-linux-i486.tar.bz2 | |
tar xfj OpenWrt-ImageBuilder* | |
cd "$HOME/Downloads/OpenWrt-ImageBuilder-ar71xx_generic-for-linux-i486" | |
make image PROFILE=TLWR703 PACKAGES="luci -firewall -iptables -kmod-ipt-nathelper -kmod-ppp -kmod-pppoe -kmod-pppox -ppp -ppp-mod-pppoe -dnsmasq -radvd avrdude ser2net kmod-usb-audio alsa-utils madplay luci-theme-bootstrap -luci-theme-openwrt coreutils-stty nano" FILES=radio/ | |
bin/ar71xx/openwrt-ar71xx-generic-tl-wr703n-v1-squashfs-sysupgrade.bin | |
Flash this via the OpenWRT interface. (Use the "factory.bin" if OpenWRT was not yet installed on the WR703N before.) Check the checkbox to keep the configuration in order to keep your network setup intact (such as dhcp over Ethernet or wireless or whatever). This is brilliant design! | |
# Watch out for: | |
# [mktplinkfw] *** error: images are too big | |
# If the image is too large, then the .bin is not generated | |
# On the device: | |
amixer sset Speaker "80%" | |
wget http://swr-mp3-m-swr1bw.akacast.akamaistream.net/7/245/137133/v1/gnl.akacast.akamaistream.net/swr-mp3-m-swr1bw -O - | madplay - | |
# Before launching, need to wait for Internet. After launching, do something with the LED. | |
echo "(need to figure this out)" > /etc/rc.local | |
chmod a+x /etc/rc.local | |
Tricks with the LED | |
Turn blue led on : | |
echo "1" > /sys/devices/platform/leds-gpio/leds/tp-link:blue:system/brightness | |
Turn blue led off : | |
echo "0" > /sys/devices/platform/leds-gpio/leds/tp-link:blue:system/brightness | |
Blink blue led on/off : | |
cd /sys/devices/platform/leds-gpio/leds/tp-link:blue:system/ | |
echo timer > trigger | |
echo 100 > delay_on | |
echo 100 > delay_off | |
Note : The delay_on value must be less than 255 | |
INSIGHT: | |
* Playing webradio over Ethernet or WLAN with USB sound card needs 0.9 W | |
* Boot takes 30 secs, about 5(?) could be saved in u-boot | |
* Flash is the key bottleneck of the device. Net to get nfs extroot going? | |
* madplay is a tiny mp3 player, it needs 13% CPU when playing SWR3 | |
Cool stuff I can do: | |
# Switch off USB power - the key to a VERY power efficient standby... | |
echo "0" > /sys/devices/virtual/gpio/gpio8/value | |
# Switch on USB again (works) | |
echo "1" > /sys/devices/virtual/gpio/gpio8/value | |
TODO: | |
* See http://www.neophob.com/files/Diplomarbeit.pdf | |
* Write real proper packaged luci module on git http://dentrassi.de/2012/07/26/extending-openwrt/ | |
* Investigate http://piie.net/index.php?section=tplink-radio - there an ATtiny is hooked up; by using an ATmega instead Pronto Hex sending and IR receiving should be a possibility... | |
* Store tuned station in a file to be able to recover from there https://forum.openwrt.org/viewtopic.php?pid=195539#p195539 | |
* Write script/webinterface (lua?) | |
* Can I use the buttons/GPIOs of the C-Media USB sound card? There are 4 buttons on it... See http://neophob.com/2007/05/openwrt-mp3-player-with-keyboard-and-display/ | |
* Find out if the blue LED and/or other GPIOs can be used for IR sending/LIRC | |
* Hook up ATmega to send and receive IR codes, and have that controlled via HTTP and/or IR | |
* shairport - likely way too large | |
* Hook up SD Card via GPIO (if net mounting does not work at all) | |
=== | |
To set up a working Ethernet and WLAN Internet connection (only one of the two need to be connected) | |
# Client mode | |
https://github.com/krebscode/minikrebs/blob/master/traits/network/client-mode/files/etc/config/network | |
/etc/config/network | |
====== | |
config interface 'lan' | |
option ifname 'eth0' | |
option proto 'dhcp' | |
===== | |
Select under "Interfaces - WAN": Create / Assign firewall-zone: lan (green) for *ALL* networks! --> Now I can ssh in and I can use HTTP | |
Interfaces: Interface WAN: delete | |
Wifi: Scan, Join Network, Create / Assign firewall-zone: select lan (green)!, Submit, Save and apply | |
Interfaces -> WWAN -> Advanced settings -> Override MAC address: Has the WRONG IP, so set IP+1, Save and apply | |
Interfaces: Both LAN and WWAN should be green now and both should have an IP address | |
HTML interface should be reachable on both addresses | |
If you have totally mis-configured the network and can't get in anymore, use "OpenWRT Failsafe" (using an Ethernet cable and the reset button) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
cat > /etc/rc.local <<\EOF | |
#!/bin/sh | |
# too early here - convert to init.d | |
cd '/sys/devices/platform/leds-gpio/leds/tp-link:blue:system/' | |
echo timer > trigger | |
echo 50 > delay_on | |
echo 1950 > delay_off | |
cd - | |
# Without this, I get problems on Unitymedia (not needed anymore, was a network misconfiguration) | |
# echo "nameserver 8.8.8.8" > /etc/resolv.conf | |
# /etc/init.d/dnsmasq stop | |
# fritzlisten & | |
# GPIO 29 is on "pin" R17-S (south end of resistor 17) | |
# This is connected to the Arudino reset pin, hence we need to change it from "grounded" to "isolated" | |
# so that the Arduino can start running | |
echo 29 > /sys/class/gpio/export | |
echo out > /sys/class/gpio/gpio29/direction | |
echo 1 > /sys/class/gpio/gpio29/value # isolated | |
## If we have an IP address via Ethernet, then we don't need WLAN | |
#RESULT=$(ifconfig br-lan | grep "inet addr") | |
#if [ "$RESULT" ] ; then | |
# wifi down | |
# rmmod ath9k | |
# rmmod ath9k_common ath9k_hw ath | |
# rmmod mac80211 cfg80211 compat aes_generic crypto_algapi | |
#fi | |
# Configure Bonjour | |
HOSTNAME=$(uci get system.@system[0].hostname) | |
cat > /etc/mDNSResponder.conf <<EOxF | |
$HOSTNAME SSH | |
_ssh._tcp. local | |
22 | |
$HOSTNAME Web Interface | |
_http._tcp local | |
80 | |
path=/cgi-bin/luci/admin/radio/stations | |
EOxF | |
exit 0 | |
EOF | |
chmod a+x /etc/rc.local | |
################################################### | |
cat > /etc/hotplug.d/usb/usb-audio-radio <<\EOF | |
#!/bin/sh | |
case "$PRODUCT" in | |
d8c*) // Only do this if the USB device in question is a C-Media USB audio device | |
case "$ACTION" in | |
add) | |
logger -t button-hotplug Device: $DEVICE / Product: $PRODUCT / Action: $ACTION | |
logger -t C-Media USB audio device detected, starting Radio | |
/etc/init.d/radio start | |
;; | |
remove) | |
logger -t button-hotplug Device: $DEVICE / Product: $PRODUCT / Action: $ACTION | |
/etc/init.d/radio stop | |
;; | |
esac | |
;; | |
esac | |
EOF | |
chmod a+x /etc/hotplug.d/usb/usb-audio-radio | |
################################################### | |
cat > /etc/init.d/radio <<\EOF | |
#!/bin/sh /etc/rc.common | |
start() { | |
NUMBER_OF_PIDS=$(pidof radio | wc -w) | |
# FIXME: Why the heck is this count 2 (but it works) | |
if [ $NUMBER_OF_PIDS == 2 ] ; then | |
radio >/dev/null 2>&1 & | |
fi | |
} | |
stop() { | |
# FIXME: Have the radio script itself kill | |
# all the helper processes insted of the following line | |
killall radio aplay mplay arduinolisten 2>/dev/null | |
} | |
EOF | |
chmod a+x /etc/init.d/radio | |
################################################### | |
cat > /usr/bin/radio <<\EOF | |
#!/bin/sh | |
trap "killall -9 arduinolisten ; kill $$" SIGINT # also ctrl-D | |
trap "killall -9 arduinolisten ; kill $$" EXIT # also killall? | |
# Optionally, launch the Arduino handler that reacts to commands sent from the Arduino | |
/usr/bin/arduinolisten & | |
echo $! > /tmp/process_arduinolisten.pid | |
SPEAKER=$(amixer scontrols | head -n 1 | cut -d "'" -f 2) | |
amixer sset $SPEAKER '60%' | |
play() | |
{ | |
wget -U "" "$1" -O - | madplay -o wave:- - | aplay -Dplug:dmix | |
} | |
id() | |
{ | |
play "http://translate.google.com/translate_tts?tl=$1&q=$2" | |
} | |
podcast() | |
{ | |
play $(wget "$1" -O - | grep "enclosure url.*mp3" | head -n 1 | cut -d '"' -f 2) | |
} | |
# TODO: Grab from configuration managed by uci | |
while true; do | |
# SWR1 | |
id de esweeeerrr1 | |
play http://swr-mp3-m-swr1bw.akacast.akamaistream.net/7/245/137133/v1/gnl.akacast.akamaistream.net/swr-mp3-m-swr1bw | |
# SWR2 | |
id de esweeeerrr2 | |
play http://swr-mp3-s-swr2.akacast.akamaistream.net/7/204/137135/v1/gnl.akacast.akamaistream.net/swr-mp3-s-swr2 | |
# SWR3 | |
id de esweeeerrr3 | |
play http://swr-mp3-m-swr3.akacast.akamaistream.net/7/720/137136/v1/gnl.akacast.akamaistream.net/swr-mp3-m-swr3 | |
# Digitally imported | |
id en_US digitally+imported | |
play $(wget -U "" "http://www.di.fm/mp3/ambient.pls" -O - | grep http | head -n 1 | cut -d "=" -f 2) | |
# Radio Darmstadt | |
id de radio+darmstadt | |
play "http://livestream.radiodarmstadt.de:8000/" | |
# C-Radar Darmstadt | |
id de zeh+radar+darmstadt | |
podcast http://c-radar.ccc.de/rss | |
done | |
EOF | |
chmod a+x /usr/bin/radio | |
################################################### | |
# Otherwise dmix crackles | |
cat > /etc/asound.conf <<\EOF | |
defaults.dmix."USB-Audio".period_size 2048 | |
# defaults.pcm.dmix.rate 48000 | |
EOF | |
################################################### | |
mkdir -p /etc/hotplug.d/button | |
cat > /etc/hotplug.d/button/buttons <<\EOF | |
#!/bin/sh | |
# To handle short and long presses, see http://wiki.openwrt.org/doc/howto/hardware.button | |
logger $BUTTON | |
logger $ACTION | |
if [ $BUTTON = reset -a $ACTION = pressed ]; then | |
killall wget | |
killall madplay | |
fi | |
EOF | |
chmod a+x /etc/hotplug.d/button/buttons | |
################################################### | |
# Configure ser2net to expose the serial port on telnet port 1234 (still need to start it somewhere) | |
cat > /etc/ser2net.conf <<\EOF | |
1234:raw:0:/dev/ttyATH0:57600 | |
EOF | |
################################################### | |
# Remove the login console on ttyATH0 (note that the kernel still outputs to there!) | |
cat > /etc/inittab <<\EOF | |
::sysinit:/etc/init.d/rcS S boot | |
::shutdown:/etc/init.d/rcS K shutdown | |
EOF | |
################################################### | |
cat > /usr/bin/dilandau <<\EOF | |
#!/bin/sh | |
# | |
# Search for a song on Dilandau and play the first result | |
# | |
SEARCH=$(echo $1 | sed -e 's| |%20|g') | |
echo $SEARCH | |
wget -U "" "http://www.dilandau.eu/download-songs-mp3/$SEARCH/1.html" -O /tmp/_html | |
SNIP=$(grep -o -e "a\ download.*" /tmp/_html | head -n 1) | |
PART1=$(echo $SNIP | cut -d '"' -f 4) | |
PART2=$(echo $SNIP | cut -d '"' -f 6) | |
URL=$PART1$PART2 | |
wget -U "" "$URL" -O - | madplay -o wave:- - | aplay -Dplug:dmix | |
EOF | |
chmod a+x /usr/bin/dilandau | |
################################################### | |
cat > /usr/bin/metaebene <<\EOF | |
#!/bin/sh | |
# | |
# Search for episodes on Metaebene and play the first result | |
# | |
wget -U "" "http://metaebene.me/updates/" -O /tmp/_html | |
SNIP=$(grep -o -e "thumbnail.*" /tmp/_html | head -n 1) | |
URL=$(echo $SNIP | cut -d '"' -f 3) | |
wget -U "" "$URL" -O /tmp/_html | |
MP3=$(grep -r "og:audio.*.mp3" /tmp/_html | head -n 1 | cut -d '"' -f 4) | |
wget -U "" "$MP3" -O - | madplay - | |
EOF | |
chmod a+x /usr/bin/metaebene | |
################################################### | |
cat > /usr/bin/arduinolisten <<\EOF | |
#!/bin/sh | |
# This assumes a sketch on the Arduino on ttyATH0 that outputs hashes like 7354AEB4 | |
stty -F /dev/ttyATH0 cs8 57600 ignbrk -brkint -icrnl -imaxbel -opost -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke noflsh -ixon -crtscts | |
SPEAKER=$(amixer scontrols | head -n 1 | cut -d "'" -f 2) | |
while read LINE; do | |
# echo $LINE | |
LENGTH=$(echo "$LINE" | wc -m) | |
if [ "$LENGTH" != "10" ] ; then | |
continue | |
fi | |
echo $LINE | |
case "$LINE" in | |
3F90319C*|7354AEB4*) | |
amixer set "$SPEAKER" 2dB+ ;; | |
7054A9FD*|3C902CE5*) | |
amixer set "$SPEAKER" 2dB- ;; | |
DB91DA59*|A7CD5D41*|AACD61F8*|DE91DF10*) | |
killall wget ; killall madplay ;; | |
esac | |
done < /dev/ttyATH0 | |
EOF | |
chmod a+x /usr/bin/arduinolisten | |
################################################### | |
cat > /usr/bin/arduinoreset <<\EOF | |
#!/bin/sh | |
# GPIO 29 is on "pin" R17-S (south end of resistor 17) | |
echo 0 > /sys/class/gpio/gpio29/value # grounded | |
sleep 1 # Is needed to make it reliable | |
echo 1 > /sys/class/gpio/gpio29/value # isolated | |
EOF | |
chmod a+x /usr/bin/arduinoreset | |
################################################### | |
cat > /usr/bin/arduinoflash <<\EOF | |
#!/bin/sh | |
set -e | |
if [ -z "$1" ] ; then | |
URL=http://arduinocodeinone.googlecode.com/svn-history/r2/trunk/Arduino/Blink/applet/Blink.hex | |
else | |
URL="$1" | |
fi | |
wget -c "$URL" -O /tmp/_tmp.hex | |
# Reset using "pin" R17-S (south end of resistor 17) | |
echo 0 > /sys/class/gpio/gpio29/value # grounded | |
sleep 1 # Is needed to make it reliable | |
echo 1 > /sys/class/gpio/gpio29/value # isolated | |
avrdude -v -v -v -v -p atmega328p -c arduino -P /dev/ttyATH0 -b 57600 -D -U flash:w:/tmp/_tmp.hex:i || echo "Is another process accessing the port?" | |
rm /tmp/_tmp.hex | |
EOF | |
chmod a+x /usr/bin/arduinoflash | |
################################################### | |
# Remove blackness from web interface | |
cat > /www/index.html <<\EOF | |
<?xml version="1.0" encoding="utf-8"?> | |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> | |
<html xmlns="http://www.w3.org/1999/xhtml"> | |
<head> | |
<meta http-equiv="refresh" content="0; URL=/cgi-bin/luci" /> | |
</head> | |
<body> | |
</body> | |
</html> | |
EOF | |
################################################### | |
cat > /usr/bin/package-local <<\EOF | |
#!/bin/sh | |
# | |
# This script will one day generate an installable OpenWRT package | |
# from all locally changed files on the running OpenWRT system that are not | |
# known (keep) conf files. This is useful in rapid (scripting) development. | |
# Currently it "just" creates 2 tar.gz files | |
# | |
cd /overlay | |
opkg list-changed-conffiles > /tmp/excludes | |
find /lib/upgrade/keep.d -type f -exec cat {} \; | grep -v "#" >> /tmp/excludes | |
tar cvz -X /tmp/excludes -f /tmp/overlay.tar.gz * 2>/dev/null | |
tar cvz -T /tmp/excludes -f /tmp/overlay-excluded.tar.gz 2>/dev/null | |
rm /tmp/excludes | |
cd - | |
ls -lh /tmp/overlay*tar.gz | |
exit 0 | |
################################################################################## | |
# Now create the ipkg (untested, probably buggy) | |
# For this, "ar" would need to be installed (package binutils is relatively large) | |
################################################################################## | |
mkdir -p /tmp/_ipkg/CONTROL | |
cat > /tmp/_ipkg/CONTROL/control <<\ExOF | |
Package: custom-scripts | |
Version: 0.1 | |
Description: Custom scripts | |
Architecture: all | |
Section: extra | |
Priority: optional | |
Maintainer: user <user@host> | |
Homepage: | |
Depends: busybox | |
Source: Inside this file | |
ExOF | |
cd /tmp/_ipkg/CONTROL | |
tar cvz -f /tmp/_ipkg/control.tar.gz * 2>/dev/null | |
cd - | |
mv /tmp/overlay.tar.gz /tmp/_ipkg/data.tar.gz | |
echo "2.0" > /tmp/_ipkg/debian-binary | |
ar -r /tmp/custom-scripts.ipk ./debian-binary ./data.tar.gz ./control.tar.gz | |
rm -rf /tmp/_ipkg | |
EOF | |
chmod a+x /usr/bin/package-local | |
################################################### | |
avrdude seems to be able to burn at 57600 | |
avrdude -p atmega328p -c arduino -P /dev/ttyATH0 -b 57600 -D -U flash:w:ASCIITable.cpp.hex:i | |
Serial is unstable/not working at 3.3V and the integrated 8 MHz oscillator, at least it does not work for me when not calibrated. Hence I am now using the 328p with a 16 MHz quartz and flashing now works if I first short reset to GND, second launch avrdude, third remove the reset short to GND | |
If ser2net is running, then I can also flash using ardude on the desktop machine, like so (the WR703n is the host "atheros" here): | |
avrdude -p atmega328p -c arduino -P net:atheros:1234 -b 57600 -D -U flash:w:/tmp/build8675192537251004413.tmp/sketch_jul19b.cpp.hex:i | |
TODO: Have this show up in the Arduino IDE | |
FIRST, pull Reset to GND. SECOND, issue the avrdude command. THIRD, release the Reset pin from GND. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# According to http://wiki.openwrt.org/toh/tp-link/tl-wr703n#gpios | |
# GPIO 29 is on "pin" R17-S (south end of resistor 17) | |
echo 29 > /sys/class/gpio/export | |
echo out > /sys/class/gpio/gpio29/direction | |
cat /sys/class/gpio/gpio29/active_low | |
# 0 | |
# This means that it is NOT active low, but passive low | |
# During boot until intervention, this is low = grounded! | |
echo 1 > /sys/class/gpio/gpio29/value # isolated | |
echo 0 > /sys/class/gpio/gpio29/value # grounded | |
# Works, as measured with Saleae Logic Analyzer (just hooking up a LED and R does not appear to work!) :-) | |
=== | |
On Ubuntu, I can do | |
sudo modprobe gpio-ir-recv # --> the module loads, but how do I configure it... | |
$ modinfo gpio-ir-recv | |
filename: /lib/modules/3.8.0-17-generic/kernel/drivers/media/rc/gpio-ir-recv.ko | |
license: GPL v2 | |
description: GPIO IR Receiver driver | |
srcversion: 1255AA557BA1B3F5DC3091D | |
depends: rc-core | |
intree: Y | |
vermagic: 3.8.0-17-generic SMP mod_unload modversions 686 | |
$ modinfo rc_core | |
filename: /lib/modules/3.8.0-17-generic/kernel/drivers/media/rc/rc-core.ko | |
license: GPL | |
author: Mauro Carvalho Chehab <[email protected]> | |
srcversion: 7159999129901243D2A7921 | |
depends: | |
intree: Y | |
vermagic: 3.8.0-17-generic SMP mod_unload modversions 686 | |
parm: debug:int | |
sudo modprobe ir-rc5-decoder # --> dmesg "IR RC5(x) protocol handler initialized" | |
$ modinfo ir-rc5-decoder | |
filename: /lib/modules/3.8.0-17-generic/kernel/drivers/media/rc/ir-rc5-decoder.ko | |
description: RC5(x) IR protocol decoder | |
author: Red Hat Inc. (http://www.redhat.com) | |
author: Mauro Carvalho Chehab <[email protected]> | |
license: GPL | |
srcversion: 050C9B1440208EE3FABEBBA | |
depends: rc-core | |
intree: Y | |
vermagic: 3.8.0-17-generic SMP mod_unload modversions 686 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
Install to | |
/usr/lib/lua/luci/controller/myapp/mymodule.lua | |
]]-- | |
module("luci.controller.myapp.mymodule", package.seeall) | |
function fork_exec(command) | |
local pid = nixio.fork() | |
if pid > 0 then | |
return | |
elseif pid == 0 then | |
-- change to root dir | |
nixio.chdir("/") | |
-- patch stdin, out, err to /dev/null | |
local null = nixio.open("/dev/null", "w+") | |
if null then | |
nixio.dup(null, nixio.stderr) | |
nixio.dup(null, nixio.stdout) | |
nixio.dup(null, nixio.stdin) | |
if null:fileno() > 2 then | |
null:close() | |
end | |
end | |
-- replace with target command | |
nixio.exec("/bin/sh", "-c", command) | |
end | |
end | |
function index() | |
entry({"click", "here", "now"}, call("action_tryme"), "Click here", 10).dependent=false | |
entry({"my", "new", "template"}, template("myapp-mymodule/helloworld"), "Hello world", 20).dependent=false | |
end | |
function action_tryme() | |
luci.http.prepare_content("text/plain") | |
luci.http.write("Haha, playing some music now...") | |
fork_exec("killall sh madplay wget; sleep 1; /usr/bin/dilandau Thank+you+for+the+music") | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment