I always wanted to see where the readers of my blog are comming from, so I took a picture frame and inserted a world map, a handfull of leds and an arduino. Some soldering and some rubyscripts later I had my hardware geo-aware logfile visualization.
I have a script running at the server that parses new ip adresses from the log file and geocodes them. Than the continent code is sent to my mac where i have a little script that forwards the continent code to the serial port. And finally the arduino in the picture frame is making the leds blink. The whole project was hacked together as a weekend project so the scripts might need some "fine-tuning" :-)
This is my day 5 project for 30DaysOfCreativity
so please read my blog to make the leds blink :-)

The world map is released under a creative commons licence and cand be downloaded here
The geocoding is done using the iprange to continent mappings from countryipblocks.net
The hardware part looks like this (just some leds and resistors)

This is the code i'm running on my arduino int count[] = { 0,0,0,0,0,0,0 };
1 void setup() { 2 pinMode(2,OUTPUT); 3 pinMode(3,OUTPUT); 4 pinMode(4,OUTPUT); 5 pinMode(5,OUTPUT); 6 pinMode(6,OUTPUT); 7 pinMode(7,OUTPUT); 8 pinMode(8,OUTPUT); 9 Serial.begin( 9600 ); 10 } 11 12 void loop() { 13 if (Serial.available() > 0) { 14 byte in = Serial.read(); 15 if (in >= '1') { 16 count[ in - '1' ] = 50; 17 } 18 } 19 for( int i =0; i < 7; i++) { 20 if (count[i] > 0) { 21 digitalWrite( i+2, HIGH); 22 count[i]--; 23 } else { 24 digitalWrite( i+2, LOW); 25 } 26 } 27 28 delay(10); 29 }
This is the script that runs on the computer the arduino is connected to
1 require 'rubygems' 2 require 'serialport' 3 require 'socket' 4 include Socket::Constants 5 SERIALPORT="/dev/tty.usbserial-A600ag8n" 6 7 8 sp = SerialPort.new( SERIALPORT, 9600, 8, 1, SerialPort::NONE) 9 sleep(2) 10 (1..7).each{ |i| 11 sp.write( "#{i}" ) 12 sleep(0.2) 13 } 14 15 socket = Socket.new( AF_INET, SOCK_STREAM, 0 ) 16 sockaddr = Socket.pack_sockaddr_in( 2200, '' ) 17 socket.bind( sockaddr ) 18 socket.listen( 5 ) 19 while(true) 20 client, client_sockaddr = socket.accept 21 led = client.readline.chomp 22 sp.write( led ) 23 client.close() 24 end 25 26 sp.close()
and this is the script that runns on my server and geocodes the ip adresses
1 require 'socket' 2 include Socket::Constants 3 4 def ip2int(ip) 5 block = ip.split('.') 6 ipi = block[0].to_i * 256 * 256 * 256 + block[1].to_i * 256 * 256 + block[2].to_i * 256 + block[3].to_i 7 return ipi 8 end 9 10 def parseCountryFile( fn ) 11 ranges = [] 12 f = File.new("geocodeip/country_ip/#{fn}") 13 f.each_line { |l| 14 if l[0] != '#'[0] then 15 tmp = l.split("-") 16 start = ip2int(tmp[0].strip) 17 stop = ip2int(tmp[1].strip) 18 19 ranges << [start, stop] 20 end 21 22 } 23 return ranges 24 end 25 26 def findIp( map, ip ) 27 ipi = ip2int( ip ) 28 map.each{ |continent,ranges| 29 ranges.each{ |start,stop| 30 return continent if ipi > start and ipi < stop 31 } 32 } 33 return "4" 34 end 35 36 def process( map, line ) 37 t = Thread.new do 38 continent = findIp( map, line.split(" ")[0]) 39 socket = Socket.new( AF_INET, SOCK_STREAM, 0 ) 40 sockaddr = Socket.pack_sockaddr_in( 2200, 'nikkimac' ) 41 socket.connect( sockaddr ) 42 socket.puts continent 43 socket.close 44 puts continent 45 end 46 end 47 48 map = {} 49 map["1"] = parseCountryFile("Oceania_range.txt") 50 map["2"] = parseCountryFile("Asia_range.txt") 51 map["3"] = parseCountryFile("India_range.txt") 52 map["4"] = parseCountryFile("Europe_range.txt") 53 map["5"] = parseCountryFile("Africa_range.txt") 54 map["6"] = parseCountryFile("South_America_range.txt") 55 map["7"] = parseCountryFile("North_America_range.txt") 56 57 puts "parsing done ..." 58 59 f = File.new( "/var/log/apache2/access.log" ) 60 f.seek( 0, IO::SEEK_END ); 61 last = f.pos 62 while true 63 sleep(1) 64 size = f.stat.size 65 if last < size then 66 l = f.readline 67 process( map, l ) 68 while !f.eof 69 l = f.readline 70 process( map, l ) 71 end 72 last = f.pos 73 end 74 end
Leave a comment...