fmscanWie in Teil 1 bereits kurz erklärt, soll das Arduino-Radio mit dem TEA5767-FM-Chip die UKW-Senderliste passend zum eigenen Standort automatisch erhalten. Im ersten Schritt wird daher zunächst versucht, den eigenen Standort zu bestimmen. Dafür braucht es keinen GPS-Sensor. Schon die IP-Adresse liefert hinreichend gut den Standort, zumindest auf einige Kilometer genau. Das genügt, um die Radio-Sendemasten in der Nähe zu finden. Hinweise darüber liefert die Webseite fmscan.org: Die Seite listet für einen beliebigen Ort die empfangbaren Radiosender auf.

Für die Standortbestimmung per IP-Adresse gibt es einige Web-Datenbanken. Viele davon können kostenlos genutzt werden. So ganz zuverlässig ist das Ganze nicht, besonders, wenn der Internetanschluss von einem internationalen Anbieter genutzt werden. Ob es funktioniert, muss man einfach ausprobieren. Ein Dienst, der bei mir gut funktioniert hat, ist ip-api.com. Der Dienst liefert ausführliche Informationen zum Standort der abgefragten IP-Adresse und gibt sie auf Wunsch im JSON Format zurück. Das Format hat den Vorteil, dass es die Daten lesbar und in strukturierter Form vorhält. Mit einer entsprechenden Parser-Bibiliothek ist es sehr einfach, die Daten aus einer JSON-Datei in Skripten zu verwenden.

Am Ende des Beitrags findet sich das python-Skript, mit dem der Standort bestimmt und die Senderliste von fmscan.org geladen wird. Der Code für die Standortbestimmung ist recht kurz. Er basiert auf den url*-Bibiliotheken (Zeile 3 – 6) und dem JSON-Parser (Zeile 7). In Zeile 25 wird die Webseite von ip-api.com geladen und die Daten im JSON angefordert. Schon in der nächsten Zeile wird das Ergebnis mit dem JSON-Parser ausgelesen. Der Webdienst holt die IP-Adresse automatisch aus der Anfrage, so dass sie nicht extra als Parameter übergeben werden muss. In der Antwort ist der Name der Stadt sowie die Geokoordinaten (Länge = longitude und Breite = latitude) enthalten.

Der Rest des Skriptes kümmert sich um die Abfrage der Senderliste von fmscan.org. Dieser Teil ist etwas tricky, da die Daten aus der Javascript-lastigen Webseite extrahiert werden müssen. Dafür muss auf Screen-Scraping zurückgegriffen werden. Die Bibliothek dryscape übernimmt dafür das komplette Rendern einer Webseite, die zudem auch per Javascript dynamisch generierte Inhalte enthalten darf. In Zeile 41 wird eine Session gestartet, in der die Eingaben in Formulare erzeugt und Mausklicks auf Links emuliert werden. So wird in Zeile 51 bis 54 die Stadt gesucht, die im ersten Teil des Skriptes anhand der IP-Adresse identifiziert wurde.

Die so generierte Webseite kann, wie in Zeile 57 auskommentiert, als PNG-Datei abgespeichert werden. Danach folgen XPath-Abfragen, um in der Seite zu navigieren und die Daten zu extrahieren. Zunächst wird aus dem Suchergebnis nach der Stadt diejenige ausgewählt, die am nächsten an den Geokoordinaten aus der IP-Abfrage liegt (Zeile 67 bis 79). In Zeile 81 wird für diese Stadt eine Abfrage der verfügbaren Sendestationen durchgeführt. Aus der zurückgegebenen Tabelle werden die Stationen ausgelesen, nach Sendestärke sortiert und in eine Datei geschrieben.

Das python-Skript wird auf dem RasPi ausgeführt und liefert die Senderliste, die im Anschluss an den Arduino übertragen wird. Wie das geht, wird im nächsten Teil 3 gezeigt.

#!/bin/env python
# -*- codepage: utf-8 -*-
import urllib
import urllib2
from urllib2 import urlopen
from contextlib import closing
import json
import dryscrape
import re
from decimal import Decimal
from operator import itemgetter

#
# Geolokation per IP-Adresse
#
# stadt = 'Berlin'
# longitude = '13.4584'
# latitude = '52.4975'
stadt = ''
longitude = ''
latitude = ''

url ='http://ip-api.com/json'
try:
    with closing(urlopen(url)) as response:
        location = json.load(response)
        print location
        stadt = location['city']
        longitude = location['lon']
        latitude = location['lat']      
        print stadt
        print longitude
        print latitude
except:
    print("Location could not be determined automatically")

#
# Suche lokaler Radiosender mit fmscan.org
#
url = 'http://www.fmscan.org/'
sess = dryscrape.Session(base_url = url)

# we don't need images
sess.set_attribute('auto_load_images', False)

# visit homepage and search for a term
sess.visit('/')
freq = sess.at_xpath('//a[@href="form.php?m=s&ex=0"]')
freq.click()

q = sess.at_xpath('//*[@name="search"]')
search_term = stadt
q.set(search_term)
q.form().submit()


# sess.render('geo.png')
# print "Screenshot written to 'geo.png'"

locations = []
# extract all links
for link in sess.xpath('//a[@href]'):
  url = link['href']
  m = re.search('qthset=([\w\s\./,]+)&lset=([-]*\d+\.\d+)&bset=([-]*\d+\.\d+)', url)
  if m is not None:
    # print 'Stadt:', m.group(1),'longitude:', m.group(2), 'latitude:', m.group(3)
    diff = abs(Decimal(longitude) - Decimal(m.group(2))) + abs(Decimal(latitude) - Decimal(m.group(3)))
    locations.append((url, m.group(1), m.group(2), m.group(3), diff))

s = sorted(locations, key=itemgetter(4))
print 'In der Datenbank gefunden:'
print 'url:', s[0][0]
print 'Stadt:', s[0][1]
print 'longitude:', s[0][2]
print 'latitude:', s[0][3]
print 'Entfernung:', "{:5.5f}".format(s[0][4]), "°"

url = s[0][0]
sess.visit(url)

g = sess.at_xpath('//input[@value="Generate"]')
g.form().submit()

stations = [('Freq','RDS','Programm','Distanz','dBuV')]
rows = sess.xpath('//div[2]/table[1]/tbody/tr')
for row in rows:
    if (len(row.xpath('./td/a/u'))>0):
        freq = row.xpath('./td/a/u')[0].text()
    else:
        freq = ''
    if (len(row.xpath('./td[5]'))>0):
        rds = row.xpath('./td[5]')[0].text()
    else:
        rds = ''
    if (len(row.xpath('./td[6]'))>0):
        program = row.xpath('./td[6]')[0].text()
    else:
        program = ''
    if (len(row.xpath('./td[10]'))>0):
        dist = row.xpath('./td[10]')[0].text()
    else:
        dist = ''
    if (len(row.xpath('./td[12]'))>0):
        dBuV = Decimal(re.search('(\d+)',row.xpath('./td[12]')[0].text()).group(1))
        # dBuV = row.xpath('./td[12]')[0].text()
    else:
        dBuV = 0.0
    stations.append((freq, rds, program, dist, dBuV))

s = sorted(stations, key=itemgetter(4), reverse=True)
station_file = 'stations.txt'
open(station_file,'w').write('\n'.join('%s;%s;%s;%s;%s' % x for x in s))
print 'Lokale Radiosender in die Datei %s geschrieben' % station_file