probonopd
7/13/2013 - 2:59 PM

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 rad

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