Skip to content

Instantly share code, notes, and snippets.

@probonopd
Last active October 14, 2023 14:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save probonopd/5991009 to your computer and use it in GitHub Desktop.
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!
# 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)
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.
# 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 <mchehab@redhat.com>
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 <mchehab@redhat.com>
license: GPL
srcversion: 050C9B1440208EE3FABEBBA
depends: rc-core
intree: Y
vermagic: 3.8.0-17-generic SMP mod_unload modversions 686
--[[
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