Monday, April 04, 2011

Setting up services on la Fonera / Accton MR3201A

I was wondering if writing this post in English, Spanish or French, probably French would be the most appropriated to keep up my learning but then it would become longer to write it up. Spanish, my mother tongue is the easiest, but to be honest English is probably the most useful one to reach more people, it is the kind of the universal language, something like mathematics for Science.

Nowadays there is common sense on having the last hardware, the last software, the quickest cpu, the biggest disk, and sometimes we forget that to build up great services it is not necessary; cheap hardware and open opensource one is the perfect mix for our needs.

It has been some time since a had around a little router Accton MR3201A, this router is almost the same hardware (if not the same) as the one it uses la Fonera. I wanted to use this little piece of hardware to separate some services from my computer, and make them independent to hangs, reboots or other kind of issues that my computer might have.

For that reason, I decided to use the mini-router as a linux box handling the next services:
  • VPN server (pptp)
  • Firewall (iptables)
  • Voice over ip and PBX (Asterisk)
  • IRC bouncer (miau)
  • Bandwidth monitor (bwm)
  • Samba client (cifs)
  • Ssh server (dropbear)
  • Mail server (ssmtp)
  • Wireless router (wireless-tools)
Just to be clear, our purpose is have all that software running on top of the next hardware:

CPU: 183.50 mhz
RAM: 16 MB
Flash: 8 MB


Cool isn't it?

So lets do some exercise with your hands and prepare your fingers to do some typing :-)

First thing we need to do is install the base system, as the resources we are going to use are quite tight, I decided to use OpenWrt, the kamikaze version that it is already adapted for this type of hardware:

OPENWRT
W I R E L E S S F R E E D O M
KAMIKAZE (8.09.2, r18961) -------------------------
* 10 oz Vodka Shake well with ice and strain
* 10 oz Triple sec mixture into 10 shot glasses.
* 10 oz lime juice Salute!
---------------------------------------------------

So, to have the router flashed I used the Gargoyle flashing method, all the instructions are explained at http://www.gargoyle-router.com/wiki/doku.php?id=fon_flash
With the very nice application fon flash.

That process will end up giving to you a fully functional linux box where you can log in and configure your wireless options and iptables firewall.
Once of the first things you might want to do is update the repository with:

opkg update

Then we are ready to install all the packages that will help us get all the services up.

VPN PPTP SERVER:

One of the first things I wanted to have running was a VPN server, for being able to browse securely the internet while conected to Open wireless networks avoiding any type of man in the middle attack. Also I wanted to keep the vpn server separate from my computer, placing it inside a DMZ network making even more difficult to compromise my personal data in the rare event of breaking my PPTP password with a dictionary attack.

So lets install the package:

opkg install pptpd

After installing it, we need to configure the vpn settings and configure the firewall (iptables)
For PPTP vpn will have to adapt the next files

/etc/ppp/options.pptpd
and
/etc/chap-secrets

root@OpenWrt:/etc/ppp# cat options.pptpd
#we can debug and use logfile to debug issues, it is very useful
#debug
#logfile /tmp/pptp-server.log
auth
name "pptp-server"
lcp-echo-failure 3
lcp-echo-interval 60
default-asyncmap
mtu 1482
mru 1482
nobsdcomp
nodeflate
#noproxyarp
#nomppc
mppe required,no40,no56,stateless
require-mschap-v2
refuse-chap
refuse-mschap
refuse-eap
refuse-pap
#your local vpn server will resolve dns
ms-dns 192.168.1.12
#plugin radius.so

root@OpenWrt:/etc/ppp# cat chap-secrets
#USERNAME PROVIDER PASSWORD IPADDRESS
yourusername pptp-server yourpassword 192.168.3.55

Make sure the service is running with:
root@OpenWrt:/etc/ppp# ps aux | grep pp
977 root 1136 S pptpd

and enable it at boot time with:

/etc/init.d/pptpd enable

Once you have the service running it will be necessary to adapt your firewall rules, opening port 1723 and protocol 47 from the wan side and also adding some forwarding rules to let the vpn clients access the Internet, so for that you can configure your firewall user rules.

root@OpenWrt:/etc/init.d# cat /etc/firewall.user
# This file is interpreted as shell script.
# Put your custom iptables rules here, they will
# be executed with each firewall (re-)start.
iptables -A input_wan -p tcp --dport 1723 -j ACCEPT
iptables -A input_wan -p 47 -j ACCEPT

iptables -A input_rule -i ppp+ -j ACCEPT
iptables -A forwarding_rule -i ppp+ -j ACCEPT
iptables -A forwarding_rule -o ppp+ -j ACCEPT
iptables -A output_rule -o ppp+ -j ACCEPT

Unless I have missed something, thats all to have the vpn server running. I used it a lot to connect from my Android phone using the built vpn application.

IRC BOUNCER:

From time to time I like to connect to specific channels on the IRC to ask or answer some questions on specific channels. I hate to leave a question
open without answer or waiting someone reply and have to disconnect myself from the channel for whatever reason. I also don't like to make noise on the channel by showing constant connection or disconnection messages. That's why I like to use an IRC bouncer, it keeps my channels always connected and put me on sleep as necessary saving all the chat history, making it independent if I connect from the phone, my computer or any other device I connect from.

So, to install it:

opkg install miau

and then we need to configure miaurc file, in my case I only set up some of the options, you may want to adapt it to your needs.
If we egrep showing only the lines that I used we can see:

root@OpenWrt:/etc/miau# cat miaurc | egrep -v "^#" | egrep -v "^$"
nicknames = {
"yournickname"
}
realname = "Name"
username = "username"
password = "yourpassword"
listenport = "yourport"
servers = {
"irc.freenode.net":"8001"
}
connhosts = {
"*":"yes"
}
channels = {
"#ubuntu"
"#mysql"
"#arduino"
"#openwrt"
"#android"
"#android-dev"
}
rejoin = "true"
leave = "false"
onconnect = {
"p":"nickserv":"identify yourpassword"
}


Again, if you want to provide access you will have to configure iptables, in my case I added separate rules for each client, so we enable access based on mac address for connections with source on the wan interface editing the file /etc/config/firewall

config rule
option src wan
#your mac address
option src_mac 00:e0:00:00:00:9a
option target ACCEPT


and then reload the firewall with: /etc/init.d/firewall reload

At this point, it is good to explain how is my personal setup, as it might be different from yours, but knowing mine you will be able to adapt it for your needs.
My network looks like:
You can see the network setup better Here.


ASTERISK PBX

One of the most important services I wanted to have running on our little box was Asterisk, a fully open source telephony system that allows you almost anything related with IP telephony.

Jumping from one country to another makes me avoid the crazy rates that main telephony providers offer us. Not only that, I want to improve the way telephony works to adapt ip telephony to me and not adapt myself to telephony, yes I know this sounds like a commercial, but it is true.
I want to have my own personalized voicemail, start it during the night or after some seconds, route the call to another phone, send me an email with the message attached as soon it is received and if I'm not at home. Receive on the same phone calls from different numbers and route outgoing calls with the provider that I prefer or the cheapest one depending on the nature of the call. And all those features are provided by Asterisk.

So lets review how is my asterisk setup. Currrently I have two incoming numbers, my French landline and a Spanish virtual number(DID) that allows my Spanish friends and family call me with landline cost, something that in Spain usually is free if you call from another landline.
Outgoing calls are routed differently depending if the call is to Sweden, Spain, France or any other place in the world.
Voicemail is configured to speak french if the call is received from France, if the call comes from Spain it will speak spanish and English from any other part in the world. Also the voicemail will send me and email with the saved voice attached as a file and all its details in the exact moment the message is left.

As we are using tiny resources I had to setup all asterisk voices and sounds from the different languages out of the system by mounting a samba share stored in my main computer. This is the only extra resource required from outside as the sound files and stored messages need more space than the one available in la Fonera. Newer type of foneras comes with usb interfaces, so it would be very easy to setup extra storages setting a usb key, but the Accton router that I use don't have usb port, so I mount the samba share using cifs.

I use the next opkg packages for that:
cifsmount - 1.5-2 -
kmod-fs-cifs - 2.6.26.8-atheros-1 -

And I mount the filesystem at boot time with:

root@OpenWrt:/etc/init.d# cat cifs
#!/bin/sh /etc/rc.common
# Example script
# Copyright (C) 2007 OpenWrt.org

START=10
STOP=15

boot() {
echo boot
# commands to run at boot
# continue with the start() section
start
}
start() {
echo "mounting /mnt/internet"
mount.cifs //myserverip/share /mnt/internet -o user=user,pass=xxxxxx
}
stop() {
echo "unmounting /mnt/sharedmount"
umount /mnt/sharedmount
}

Remember to enable it on boot:

/etc/init.d/cifs enable

Then on the mounted device I store all the voices, and tell asterisk to look for them making symlinks at:

root@OpenWrt:/jffs/usr/lib/asterisk/sounds# ls -l
-rw-r--r-- 1 root root 611 Jan 9 2009 CHANGES-asterisk-core-en-1.4.14
-rw-r--r-- 1 root root 122 Oct 27 2008 CREDITS-asterisk-core-en-1.4.14
-rw-r--r-- 1 root root 16118 Jan 9 2009 LICENSE-asterisk-core-en-1.4.14
-rw-r--r-- 1 root root 19440 Oct 27 2008 core-sounds-en.txt
lrwxrwxrwx 1 root root 23 Dec 30 2009 en -> /mnt/sharedmount/sounds/en
lrwxrwxrwx 1 root root 21 Dec 30 2009 es -> /mnt/sharedmount/sounds/
lrwxrwxrwx 1 root root 23 Dec 30 2009 fr -> /mnt/sharedmount/sounds/fr

Language specific files can be found at:

Time!!!, now that I see those dates 2009, I remember that I had to setup the time to be syncronized as for some reason the device was loosing the time on each reboot, for doing that what I did is setup a cron job using rdate built in command from Openwrt.

root@OpenWrt:/etc/init.d# cat S51crond
#!/bin/sh
# start crond
/usr/sbin/crond -c /etc/spool/cron/crontabs

root@OpenWrt:/etc/init.d# crontab -l
0 0 * * * /usr/sbin/rdate 128.138.140.44

Asterisk is very powerful and also once you get use to the language it uses is very easy to setup, basically you will have to deal only with a bunch of files: sip.conf, extensions.conf and voicemail.conf

Due to the cifs mount I had also to change the default directory used by asterisk, by editing:
root@OpenWrt:/etc/asterisk# cat asterisk.conf
[directories]
astetcdir => /etc/asterisk
astmoddir => /usr/lib/asterisk/modules
astvarlibdir => /usr/lib/asterisk
astdatadir => /usr/lib/asterisk
astagidir => /usr/lib/asterisk/agi-bin
;astspooldir => /var/spool/asterisk
;had to add below line
astspooldir => /mnt/sharedmount/var/spool/asterisk
astrundir => /var/run/
astlogdir => /var/log/asterisk
[...]

Also another file that you might want to take a look if you're having troubles while making audio codec translations is modules.conf inside /etc/, I had to enable a couple of connections to have the right translations up. Basically I had to enable conversion from codec alaw to linear pcm. My sip provider for the french landline is Freephonie, provider that comes by default with Internet french provider Free.fr, this provider for some reason doesn't work with gsm codec and work with allaw codec, as the voicemail messages are stored as wav files, it was necessary for asterisk to do some translations and thats why I had to enable some conversions.

root@OpenWrt:/etc/asterisk# cat modules.conf | egrep "^load"
load => codec_alaw.so ; A-law Coder/Decoder
load => codec_gsm.so ; GSM/PCM16 (signed linear) Codec Translation
load => codec_ulaw.so ; Mu-law Coder/Decoder

So, lets take a look to my main asterisk configuration files:

root@OpenWrt:/etc/asterisk# cat modules.conf | egrep "^load"
load => codec_alaw.so ; A-law Coder/Decoder
load => codec_gsm.so ; GSM/PCM16 (signed linear) Codec Translation
load => codec_ulaw.so ; Mu-law Coder/Decoder


root@OpenWrt:/etc/asterisk# cat sip.conf
[general]
language=es
defaultexpirey=1800
dtmfmode = auto


;Register and get calls from Telsome, to our number 965xxxxxxx
register => username:password@voip3.telsome.com
;Register and get calls from Free.fr, to our number 095xxxxxxx
register => username:password@freephonie.net
disallow=all
allow=ulaw
allow=alaw
allow=speex

[telsome]
type=friend
qualify=yes
secret=xxxxxxxxx
username=965xxxxxx
host=voip3.telsome.com
dtmfmode=rfc2833
canreinvite=no
disallow=all
allow=ulaw
allow=alaw
allow=gsm
insecure=port,invite
fromdomain=voip3.telsome.com
context=incoming

[1000]
type=peer
qualify=yes
dtmfmode=rfc2833
callerid="one pc" <1000>
language=es
context=myphones
host=dynamic
secret=xxxxxxx
disallow=all
allow=ulaw
allow=alaw
allow=gsm
dtmfmode=rfc2833
;nat=yes
mailbox=1000@default


[1001]
type=peer
qualify=yes
dtmfmode=rfc2833
callerid="android phone" <1001>
;language=es
context=myphones
host=dynamic
secret=xxxxx
disallow=all
allow=ulaw
allow=alaw
allow=gsm
dtmfmode=rfc2833
;nat=yes
mailbox=1001@default

[1002]
type=peer
qualify=yes
dtmfmode=rfc2833
callerid="peque" <1002>
;language=es
context=myphones
host=dynamic
secret=xxxxx
disallow=all
allow=ulaw
allow=alaw
allow=gsm
dtmfmode=rfc2833
;nat=yes

[justvoip]
type=peer
host=sip.justvoip.com
fromdomain=sip.justvoip.com
fromuser=965xxxxxxxxxx
username=xxxxxxxx
secret=xxxxxxxx
qualify=yes
canreinvite=no
dtmfmode=rfc2833
context=from-justvoip
incominglimit=1
;language=es
disallow=all
allow=ulaw
allow=alaw
allow=gsm

[12voip]
type=peer
host=sip.justvoip.com
fromdomain=sip.12voip.com
fromuser=965xxxxxxxx
username=xxxxxxxxxx
secret=xxxxxxxxxx
qualify=yes
canreinvite=no
dtmfmode=rfc2833
context=from-12voip
incominglimit=1
;language=es
disallow=all
allow=ulaw
allow=alaw
allow=gsm


[freephonie-out]
type=peer
host=freephonie.net
username=095xxxxxxx
fromuser=095xxxxxxx
secret=xxxxx
qualify=yes
nat=yes

[freephonie-in]
type=peer
qualify=yes
host=freephonie.net
context=fromfree

Lets see the extensions.conf

root@OpenWrt:/etc/asterisk# cat extensions.conf
[...]

[general]
static=yes
writeprotect=no
clearglobalvars=no

[globals]
; Global variables goes here

;[incoming]
; Nothing should land here yet, but every context should end in
; a Hangup(), so we do that.
;exten => s,1,Hangup()


[myphones]
; When we dial something from the phones we just added in
; sip.conf, Asterisk will look for a matching extension here,
; in this context.

; First Phone, extension 1000. If 1000 is called, here is
; where we land, and the device registered with the
; name 1000, is dialed, after that Asterisk hangs up.
;we call extension for 14 seconds after that the spanish voicemail goes on
exten => 1000,1,Dial(SIP/1000,14)
exten => 1000,2,Set(CHANNEL(language)=es)
;s will skip the vm-intro message and u will talk the unavailable message
exten => 1000,3,VoiceMail(1000@default,s,u)
exten => 1000,4,PlayBack(vm-goodbye)
exten => 1000,n,Hangup()

; The same goes for Second Phone, extension 1001
exten => 1001,1,Dial(SIP/1001,15)
exten => 1001,2,VoiceMail(1001@default)
exten => 1001,3,PlayBack(vm-goodbye)
exten => 1001,n,Hangup()

; The same goes for Android Phone, extension 1002
exten => 1002,1,Dial(SIP/1002)
exten => 1002,n,Hangup()

; Testing extension, prepare to be insulted like a
; Monthy Python knight
exten => 201,1,Answer()
exten => 201,n,Playback(tt-monkeys)
exten => 201,n,Hangup()

; Voicemail
exten => 8,1,VoiceMailMain(s${CALLERIDNUM})
exten => 8,2,Hangup

; Echo-test, it is good to test if we have sound in both directions.
; The call is answered
exten => 202,1,Answer()
; Welcome message is played
exten => 202,n,Playback(dir-welcome)
; Play information about the echo test
exten => 202,n,Playback(demo-echotest)
; Do the echo test, end with the # key
exten => 202,n,Echo()
; Plays information that the echo test is done
exten => 202,n,Playback(demo-echodone)
; Goodbye message is played
exten => 202,n,Playback(demo-thanks)
; Hangup() ends the call, hangs up the line
exten => 202,n,Hangup()

;call to Spain route through 12voip
exten => _0034[0123456789].,1,Dial(SIP/12voip/${EXTEN})
exten => _0034[0123456789].,n,Congestion()
exten => _0034[0123456789].,n,Hangup()
;calls to Sweden routed through 12voip
exten => _0046[0123456789].,1,Dial(SIP/12voip/${EXTEN})
exten => _0046[0123456789].,n,Congestion()
exten => _0046[0123456789].,n,Hangup()

;calls to France with international prefix through 12voip
;we can use also Free.fr as the output will be free as well.
exten => _0033[0123456789].,1,Dial(SIP/12voip/${EXTEN})
exten => _0033[0123456789].,n,Congestion()
exten => _0033[0123456789].,n,Hangup()

;calls to French landline without international prefix
;info taken from
;http://sinhaladweepa.ruwenzori.net/index.php/2008/06/03/configuration-asterisk-pour-freephonie-en-sip
;exten => _09.,1,Dial(SIP/freephonie-out/${EXTEN})
exten => _09.,1,Dial(SIP/12voip/${EXTEN})
exten => _09.,1,n,Congestion()
exten => _09.,1,n,Hangup()

[incoming]
;incoming context handle incoming calls from my spanish DID
;so we output the voicemail in spanish :-) after 14 seconds
exten => s,1,Log(NOTICE, Incoming call from ${CALLERID(all)})
exten => s,2,Dial(SIP/1000,14)
exten => s,3,Set(CHANNEL(language)=es)
;with the s we skip the default msg and put our unavail
exten => s,4,VoiceMail(1000@default,s,u)
exten => s,5,PlayBack(vm-goodbye)
exten => s,n,Hangup()
; End of the "incoming" context

[fromfree]
;Calls received from France goes to the french voicemail, but same extension
exten => s,1,Log(NOTICE, Incoming call from ${CALLERID(all)})
exten => s,2,Dial(SIP/1000,14)
exten => s,3,Set(CHANNEL(language)=fr)
exten => s,4,VoiceMail(1000@default)
exten => s,5,PlayBack(vm-goodbye)
exten => s,n,Hangup()

Fun!, lets take a look to the voicemail configuration that will send us the handy emails once we get a message:

root@OpenWrt:/etc/asterisk# cat voicemail.conf | egrep -v ";"
[general]
format=wav
serveremail=asterisk
attach=yes
skipms=3000
maxsilence=10
silencethreshold=128
maxlogins=3

fromstring=The Asterisk PBX
emaildateformat=%A, %B %d, %Y at %r
mailcmd=/usr/sbin/ssmtp -t

[default]
format=wav
1000 => pass,name,user@gmail.com,attach=yes
1001 => pass,name,user@gmail.com

Note that Asterisk need an external email program to send the emails, for this little router I decided to use ssmtp.

mailcmd=/usr/sbin/ssmtp -t

the configuration of ssmtp is fairly simple to use it with gmail, you only have to fill up info inside the next files:

root@OpenWrt:/etc/ssmtp# cat revaliases
# sSMTP aliases
#
# Format: local_account:outgoing_address:mailhub
#
# Example: root:your_login@your.domain:mailhub.your.domain[:port]
# where [:port] is an optional port number that defaults to 25.
root:username@gmail.com:smtp.gmail.com:587
mainuser:username@gmail.com:smtp.gmail.com:587


root@OpenWrt:/etc/ssmtp# cat ssmtp.conf | grep -v "#"
root=postmaster
mailhub=mail
rewriteDomain=
hostname=_HOSTNAME_
root=username@gmail.com
mailhub=smtp.gmail.com:587
rewriteDomain=
hostname=username@gmail.com
UseSTARTTLS=YES
AuthUser=username
AuthPass=xxxxxxxxxxxx
FromLineOverride=YES

I don't want to extend myself explaining every single line on the configuration files, please note that this configuration should be adapted to your particular needs, and as you may know linux, like other things in life is not just copy & paste, so the best way for you to understand things will be read before you type. Search, read, study and understand. ;-)

Sunday, February 27, 2011

Abriendo las puertas de Internet

A veces me da la impresión de que en vez de evolucionar, algunos se empeñan en no hacerlo con la excusa de sacar un supuesto mayor beneficio económico.

Hace ya unos meses que utilizo un Nexus one, uno de tantos teléfonos Android. Lo adquirí mediante una de esas tarifas abusivas con permanencia de un año, y una cuota mensual bastante elevada para el uso que le doy.
Al adquirirlo, las palabras Internet ilimitado aparecían por todas partes, genial pensé, será uno de esos pocos operadores que permiten la voz sobre ip..

No es que sea una persona muy habladora, pero vamos, que si disfrutamos del lujo de tener Internet en nuestras manos, es muy probable que aparezca la ocasión en la que necesitemos realizar una llamada sin que ésta sea tarificada de una manera abusiva.

Me estoy dando cuenta de que poco a poco estoy dando mucho uso a los routers pequeños, por un lado tenemos la fonera haciendo buenas migas con arduino y la lámpara de emociones. Por otro lado tenemos un router Acton que después de ser flasheado con Openwrt es el encargado de mantener vivas las sesiones de IRC mediante Miau, a la vez que sirve de centraliza Asterisk y servidor de vpn PPTP. Y tan sólo utilizando el 50% de sus recursos (memoria y sistema de ficheros).

La centralita Asterisk la tengo conectada a algún proveedor de telefonía ip que me deja las llamadas a fijos de la mayoría de países gratuitas y a móviles a bastante buen precio, por ahora podríamos decir que está funcionando a medio gas, ya que aún tengo pendiente añadirle algún número virtual para poder recibir llamadas entrantes.
Que grande sería poder instalar unas cuantas centralitas asociadas a estos minirouters y proveer de servicio de cabina gratuita mundial en todos los rincones.
13 kbits, eso son los recursos de red que consume una llamada empleando el codec de audio GSM.

Decíamos que uno de los peque routers se encarga de mantener un servidor de vpn, efectivamente, mantiene un servidor para conexiones PPTP, por qué este tipo si no es el más seguro? por simplicidad en su configuración y porque fue el primero que probé :-)
La idea del servidor de vpn se me ocurrió como alternativa al ssh -D bind address, así podría en rutar todos los puertos sin problema y darme una mayor seguridad allá done estuviera conectado.
Pero hace unos días que me picaba la mosca de si sería posible enrutar Voz sobre Ip sobre una VPN casera, en principio los números para el ancho de banda necesario eran favorables, pero había que probarlo, no podía permitir tener esa Internet en mis manos y que un ente abstracto estuviera capando puertos necesarios para la voz sobre ip y conexiones a sus proveedores.

Empecé por la instalación de PPTPD mediante:
opkg update
opkg install pptpd
opkg install kmod-crypto
opkg install kmod-mppe
/etc/init.d/pptpd enable
/etc/init.d/pptpd start

configurando las credenciales de acceso en:
/etc/ppp/chap-secrets

y haciendo el fichero un poco más seguro
chmod 600 /etc/chap-secrets

para comprobar la configuración miré aquí.

Una vez la Vpn ya estaba levantada, teníamos que poner al pequeño router en Internet bien mediante nateo del puerto tcp 1723 y el protocolo GRE. Mi router freebox no me permitía natear un protocolo, sólo puertos así que la única solución viable era utilizar la opción de colocar el servidor de vpn en la zona DMZ de la freebox.

A partir de ahí ya sólo era cuestión de movernos al cliente, en éste caso el móvil android el cual viene con capacidad de realizar conexiones Vpn. Desde el menu settings, wireless & networks, VPN settings , Add VPN , eligiendo PPTP VPN.
Elegí Enable encryption, opción segura pero no demasiado, sin utilizar certificados.

Una vez la vpn estaba levantada desde la conexión 3G sólo me quedaba configurar Sipdroid, aplicación de voz sobre ip opensource para Android, con mi proveedor sip.
De todos los codecs de Audio disponibles en sipdroid, cuando quiero utilizar voip sobre la vpn fuerzo GSM ya que su uso de ancho de banda es limitado. Si estoy en casa utilizando el wifi utilizo algo como G722 o PCMA, codecs que podríamos llamar el High Definition de la voz.

Y voilà, lo que antes no era posible por limitaciones impuestas, ya es posible, mediante un uso mínimo de ancho de banda.
Por fín puedo llamar con tranquilidad a España u otros países desde cualquier lugar, de manera segura y a un precio justo.


Sunday, February 06, 2011

Midiendo el ánimo del mundo

No veo la tele, pocas veces lo hago, ni siquiera cuando estoy en España. Sólo sintonizo algún canal francés via VLC gracias a mi proveedor de adsl (Free) cuando estoy en Lyon, el cual nos deja los 20 megas a 30 euros mensuales.

La verdad es que con la cantidad de información a la que tenemos acceso estos días prefiero intentar filtrar aquello que pueda ser interesante, sin que ese canal de información sea sólo en una dirección.
Así que hace unas semanas me decidí a medir el ánimo mundial basándome en estudios ya comenzados relativos a ésta materia, registrando los eventos más importantes y dando un clima más cálido a la casa, mezclando hardware y software libre con sentimientos reales de personas.

Para poder llevar a cabo el proyecto era necesario estudiar cuáles son las palabras que identifican mejor los sentimientos, me disponía a estudiar las variaciones de la siguiente lista de sentimientos, la clase Tipo de Animo.

class TipoAnimo:
AMOR=0
ALEGRIA=1
SORPRESA=2
IRA=3
ENVIDIA=4
TRISTEZA=5
MIEDO=6
NUM_TIPOS_ANIMO = 7



Así empezaba la preparación del código Python que se encargaría d e enviar las consultas a Twitter para su posterior procesado.
Algunos ejemplos de palabras seleccionadas para la búsqueda de emociones son las siguientes:


AMOR_QUERY = '\"te+quiero+mucho\"+OR+\"te+quiero+más\"+OR+\"amo+tanto\"+[...]
IRA_QUERY='\"te+odio\"+OR+\"siento+rabia\"+OR+\"le+odio\"+OR+\"estoy+furioso\"+[...]
ALEGRIA_QUERY='\"mas+feliz\"+OR+\"bastante+feliz\"+OR+\"tan+feliz\"+OR+[...]
SORPRESA_QUERY='\"no+me+lo+puedo+creer\"+OR+\"increible\"+OR+\"asombro\"+OR+[...
ENVIDIA_QUERY='\"ambiciono\"+OR+\"codicio\"+OR+\"mucha+envidia\"+OR+[...]
TRISTEZA_QUERY='\"muy+triste\"+OR+\"tan+deprimido\"+OR+\"estoy+llorando\"+OR+[...]
MIEDO_QUERY='\"muy+asustado\"+OR+\"tan+asustada\"+OR+\"realmente+asustado\"+OR+[...]


Teníamos entonces las palabras exactas que nos marcarían las emociones. Adecuándolas para evitar falsos positivos del estilo "quiero comer", para ello utilizaremos palabras con adverbios y pronombres, nos sirven para acotar mejor la búsqueda y seleccionar la emoción en cuestión de una manera más exacta.
A modo de ejemplo y para mostrar la importancia de la selección de las palabras, hace unos días me extraño mucho que durante la etapa de estudio siempre había una emoción que no decrecía en intensidad, durante la noche en América del Sur y las primeras horas de la mañana de España, era el miedo, pensé que igual la noche tuviera algo que ver con esta curiosa actitud...

Hasta que me fijé, en que una de las palabras seleccionadas para medir el miedo era "Horror", y viendo los tweets generados por esa palabra había muchos tweets en inglés, normal que el miedo no durmiera en español, los anglo parlantes lo tenía a su merced durante su zona horaria.

Necesitábamos también una magnitud que nos permitiera la medida de las emociones así utilizamos el número de tweets por minuto que gracias a la fantástica API de búsqueda de twitter, podíamos conseguir con una sencilla función en python:

def parse_tps(animoID):
print 'query_dict[animoID]= '+query_dict[animoID]
#query can be done either json or atom
base_url='http://search.twitter.com/search.json?q='+query_dict[animoID]+'&rpp=30&locale=es&result_type=recent'
f = 0
try:
f = urllib2.urlopen(base_url)
except urllib2.URLError, (err):
print "URL error(%s)" % (err)
if (f != 0):
a = json.loads(f.read())
#debug
#todo keep the msg if somethings happens
b = json.dumps(a, sort_keys=True, indent=4)
first_tw_time = a['results'][0]['created_at']
last_tw_time= a['results'][29]['created_at']
tstart = time_string_to_stamp(first_tw_time)
tend = time_string_to_stamp(last_tw_time)
tps = 30 / (tstart - tend)
else:
print 'We shouldnt be here, as this is bad'
tps= c.all_tpm[animoID] / 60 #If we cannot get value from http we keep the old one
#returning the tweets per second and all the message just in case we have an alert
return tps,b

Una vez registrados los tweets por minuto ya podíamos empezar a estudiar las emociones, mi idea se centraba en saber que ocurría en el mundo en tiempo real, mediante la fase de estudio observaba picos de alguna emoción que hacía que me picase la curiosidad. ¿Qué estaba pasando en twitter?
Pero no tenía tiempo para saberlo, el pasado era pasado, los "timestamps" demasiado ajustados para una posterior búsqueda y el tiempo que requería esa búsqueda no lo tenía y no lo quería.

Tampoco me apetecía que múltiples alertas cortas en el tiempo estuvieran generando alarmas continuamente. Así que necesitábamos aplicar algunas fórmulas matemáticas o de estadística que dieran el peso adecuado a las emociones.

Nos interesa saber qué es una emoción normal en twitter y que es lo que supone un cambio en ese comportamiento, para ello, y viendo resultados de otros estudios, me decidí por utilizar moving averages. Aplicando un factor de suavizado a las medidas registradas, concretamente un factor exponencial que diera una importancia exponencial en el tiempo para las medidas pasadas.

S_{t} = \alpha \times Y_{t} + (1-\alpha) \times S_{t-1}



El ánimo mundial normal irá evolucionando, al igual que la temperatura global del planeta puede cambiar, el ánimo lo hace de la misma manera, como por ejemplo las épocas de glaciación influirán de una manera diferente en la temperatura media o en la temperatura inmediata.

Así en el código diferenciaremos entre emociones, ánimo y temperamento mundial. Las emociones son inmediatas y están basadas directamente en la cantidad de tweets por minuto para un instante determinado. El ánimo se extiende más en el tiempo y finalmente el temperamento del mundo abarca toda la historia del mundo, desde que el código se lanzó a ejecutar, claro.

#aplicamos exponential moving averages
self.animo_mundial_avg[animoID] = self.animo_mundial_avg[animoID] * (1 - a) + tpm * a
#debug print 'timestamp: '+str(self.timestamp)+' animo at T '+str(self.animo_mundial_avg)
for i in range(NUM_TIPOS_ANIMO):
self.ratios_temperamento[i] = self.ratios_temperamento[i] * (1 - a) + self.ratios_animo_mundial[i] * a

El procesado de las emociones se realizaría por un lado y la muestra de los resultados por otro. Para la parte de resultados utilicé una placa de prototipado Open Source, Arduino.
El circuito montado sobre la placa es muy simple, es el mismo que sirve para encender y apagar un led, en este caso con las modificaciones oportunas de software para adaptarlo a la medida de las emociones.

Después de compilar Arduino desde sus fuentes sobre un Ubuntu Lucid 64 bits, me puse manos a la obra con el código que se encargaría de recoger los resultados de procesado Python para mostrarlos con el led RGB.
El led mostraría en tiempo real las reacciones de las emociones de la gente siguiendo el siguiente patrón de colores:

enum COLORID {
ROSA = 0,
AMARILLO,
NARANJA,
ROJO,
VERDE,
AZUL,
BLANCO,
NUM_COLORS,
};

Amor rosa, Alegría amarillo, Naranja sorpresa, Rojo ira, Verde envidia, Azul tristeza y Blanco miedo. Modificando suavemente los cambios de colores mediante una función de fading.

Necesitaba mostrar de alguna manera visual cuándo se generaba algún evento importante y para ello introduje una función de flash, de manera que cuando algo extraordinario ocurre el código Python le pasa via serie a Arduino un número completamente diferente a una emoción, le pasa un 9 (las emociones se identifican de 0 a 6), y entonces Arduino se encarga de realizar el apagado y encendido del led mostrando el color de la emoción que ha generado la alerta. Para ello utilizo las siguientes líneas:

for (int numflashes = 5; numflashes >= 0; numflashes-=1){
analogWrite(RED_LED_PIN,0);
analogWrite(GREEN_LED_PIN,0);
analogWrite(BLUE_LED_PIN,0);
delay(1000);
analogWrite(RED_LED_PIN,Colors[lastcolorID].r);
analogWrite(GREEN_LED_PIN,Colors[lastcolorID].g);
analogWrite(BLUE_LED_PIN,Colors[lastcolorID].b);
delay(1000);
}

Antes comentaba que Python le pasaba vía serie las alertas a Arduino, en efecto, vía USB, siendo este usb el cable de alimentación y datos para Arduino. Por ahora es necesario tener una ventana de consola abierta para la interfaz serie que usemos, utilizando ino (cli de arduino) podríamos abrir una sesión de screen ejecutando: ion -p /dev/ttyACM1 por ejemplo.

La portabilidad de nuestra lámpara de emociones llega con la Fonera o un router Acton, mediante el flasheado de OpenWrt podemos dar una nueva vida a la Fonera si es que ya no la utilizamos. La fonera tiene una memoria limitada, pero nos permite instalar python-mini mediante el gestor de paquetes opkg y con unas pinceladas al código y añadiendo una pilas a modo de batería, ya estaríamos preparados para ir a la calle parando a las personas y explicándoles que has conseguido almacenar en una lámpara todo el ánimo mundial.

O bien, como también explican otros, utilizarla en tu mesita de noche para dormirte cuando el nivel de Ira haya disminuido. O quien sabe, igual hacerte rico comprando y vendiendo acciones teniendo en cuenta el ánimo del mundo.

Por el momento yo la utilizaré un tiempo para registrar los grandes cambios de ánimo y temperamento, mediante el envío de un email cuando se produzcan los picos indicados anteriormente. Y lo mismo retroalimentaré mi cuenta de twitter, con las emociones recogidas en un intento de dar conciencia al mundo de si mismo.

Por supuesto me encanta tener la lámpara cerca del ordenador mientras trabajo o escucho música y quedarme pensando cuando esa pequeña luz cambia de color por las emociones de otros, no sé, es una extraña sensación.





Código fuente del proyecto:
Referencias:
Documentación Arduino: http://www.arduino.cc/en/Main/Docs
Gnuplot: http://www.gnuplot.info/

Actualizaciones:
Dic. 2013 - Añadido soporte para la API 1.1 de twitter search

Sunday, January 09, 2011

Notes: building from source gphoto2

Following doc at http://www.gphoto.org/doc/manual/
Some aditional symlinks to add after installing dependencies, building and installing.

sudo ln -s /usr/local/lib/libgphoto2.so.6 /usr/lib/libgphoto2.so.6
sudo ln -s /usr/local/lib/libgphoto2_port.so.10 /usr/lib/libgphoto2_port.so.10

Yay :-), getting things ready for timelapse...

Saturday, November 13, 2010

OpenCV - link libraries and compile

#the source directory would change depending on where you install it

#output errors while compiling are very useful to debug
export LD_LIBRARY_PATH=/usr/src/opencv/release/lib/:$LD_LIBRARY_PATH
ldconfig
PKG_CONFIG_PATH=/usr/src/lib/pkgconfig:${PKG_CONFIG_PATH}
export PKG_CONFIG_PATH
ldconfig

#some examples and guidelines here
linuxconfig.org/introduction-to-computer-vision-with-opencv-on-linux

#compiling
g++ `pkg-config opencv --cflags --libs` example21.cpp -o example21

Friday, November 12, 2010

Building OpenCV

#lets install some requirements
#some of them comes from
#http://opencv.willowgarage.com/wiki/InstallGuide%20:%20Debian

sudo apt-get install pkgconfig
sudo apt-get install build-essential
sudo apt-get install cmake
sudo apt-get install libgtk2.0-dev libavcodec-dev \
libavformat-dev libjpeg62-dev libtiff4-dev
sudo apt-get install libpngwriter0c2
sudo apt-get install zlib1g-dbg zlib1g zlib1g-dev
sudo apt-get install libjasper-dev libjasper-runtime libjasper1
sudo apt-get install pngtools libtiff4-dev libtiff4 libtiffxx0c2
sudo apt-get install libtiff-tools
sudo apt-get install libavcodec-dev libavcodec52
sudo apt-get install libgstreamer0.10-0-dbg libgstreamer0.10-0
sudo apt-get install libgstreamer0.10-dev
sudo apt-get install libxine1-ffmpeg libxine-dev libxine1-bin
sudo apt-get install libunicap2 libunicap2-dev
sudo apt-get install libdc1394-22-dev libdc1394-22 libdc1394-utils
sudo apt-get install swig
sudo apt-get install libv4l-0 libv4l-dev
sudo apt-get install libpython2.6 python-dev python2.6-dev
sudo apt-get install subversion
sudo apt-get install yasm

#feel free to work under /usr/src/ or /usr/local/src ..
#build ffmpeg
sudo svn checkout svn://svn.ffmpeg.org/ffmpeg/trunk ffmpeg
./configure --enable-shared
make
sudo make install

#build opencv
sudo svn checkout https://code.ros.org/svn/opencv/trunk/opencv
cd /usr/src/opencv
mkdir release
cd release
cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/src \
-D BUILD_PYTHON_SUPPORT=ON -D BUILD_EXAMPLES=ON /usr/src/opencv/
make
sudo make install

#to make python work with opencv it is needed
mv /usr/src/lib/python2.6/site-packages/cv.so \
/usr/local/lib/python2.6/dist-packages/
#to test that it can read the library
# python2.6
>>> import cv
#it should work that way
#IPP librarys can be installed with license

Sunday, October 10, 2010

3 minutes news and weather information

alarm_podcast.py is a script that helps me to save some time during the morning, I can enjoy my breakfast while this program serves me the morning news and the weather forecast.

The script basically does 3 things:
1.- Connects to Yahoo weather to parse the current weather conditions and the forecast.
2.- All that information is sent to festival, who is doing the text to speech conversion and output to the speakers. This helps me a lot in order to know if I can take the bike or not that day.
3.- Finally the scripts check the national spanish television for a 2 minutes news video, that downloads, trying to bypass some obfuscation. Then the video is played with the realtime weather information, an icon showing the weather and also the time.

A cron job is taking care of running it daily from Mon to Fri. Using a bash script that is doing something like:

#!/bin/bash
pushd .
cd /home/user/weather
#lets clean the house first
find *.flv *.png -type f -mtime +3 -exec rm {} \;
python alarm_podcast.py
popd .

The script can be found on my open source github repository:

http://github.com/daniguerrero/spanish_news-weather

Sunday, September 12, 2010

Flash dd-wrt on Accton MR3201A Router

Go to the next page:
http://www.dd-wrt.com/site/support/router-database

select your model and download the linux.bin image, instead following the manual way like setting up a tftp server with the image and configuring the redboot loader...

Download from here:
http://www.gargoyle-router.com/download.php

The fonflash binarys corresponding your operating system in my case I used:
fon-flash-linux.tar.gz
untar and flash selecting the software (ddwrt in my case) using the nice gui or via command line tool:

sudo ./fon-flash -i eth0 -c ddwrt linux.bin

Once run the line above, power cycle your router and wait some time to have it flashed.