changeset 27:c97feb7ef9e9

Strasbourg
author gyorgyf
date Thu, 20 Sep 2012 23:55:48 +0100
parents 2ec521bbd543
children 6022a370c7dd
files mcserver/mcdevel.cfg mcserver/mcserver.cfg mcserver/mcserver.py visualclient/visclient.py
diffstat 4 files changed, 350 insertions(+), 122 deletions(-) [+]
line wrap: on
line diff
--- a/mcserver/mcdevel.cfg	Fri Jun 22 18:07:53 2012 +0100
+++ b/mcserver/mcdevel.cfg	Thu Sep 20 23:55:48 2012 +0100
@@ -2,54 +2,41 @@
 [global]
 
 # kakapo<->golden
-server.socket_host = "192.168.2.184"
-# server.socket_host = "127.0.0.1" 
+# server.socket_host = "192.168.2.184"
+server.socket_host = "127.0.0.1" 
 server.socket_port = 8030
-server.thread_pool = 30
+server.thread_pool = 50
 server.show_tracebacks = False
 
 engine.autoreload_on = True #should be off
 engine.autoreload_frequency = 180 #sec
 
-tools.sessions.on = True
+tools.sessions.on = False
 tools.sessions.timeout = 600 #min
 tools.sessions.clean_freq = 120	#min	
-# tools.sessions.storage_type = 'directory'
-# tools.sessions.SESSIONS_PATH = 'sessions/'
-# tools.sessions.SESSIONS_PATH = 'sessions'
 
 # expose everything in static, but note that sessions are enabled in this scope
 tools.staticdir.on = True
 tools.staticdir.dir = 'static'
-# tools.staticdir.content_types = {'n3': 'text/rdf+n3', 'rdf': 'application/rdf+xml'}
 
-# log.error_file = "mcserver.log"
+log.screen = None
+# log.access_file = 'mood-access.log'
+log.error_file = "mcserver.log"
 
 #main app config
 [/]
+tools.proxy.on = True
 response.timeout = 600 #sec
 request.show_tracebacks = False
-log.access_file = 'mcserver-access.log'
-log.error_file = 'mcserver-error.log'
+# log.access_file = 'mcserver-access.log'
+# log.error_file = 'mcserver-error.log'
 # tools.staticdir.root = 'mcserver'
 tools.staticdir.on = True
 tools.staticdir.dir = 'static'
 
+# [/mood]
+# log.access_file = 'mood-access.log'
+
 # explicit configuration of static content with sessions disabled
 [/script]
 tools.sessions.on = False
-
-[/css]
-tools.sessions.on = False
-
-[/img]
-tools.sessions.on = False
-
-[/images]
-tools.sessions.on = False
-
-[/data]
-tools.sessions.on = False
-
-[/temp]
-tools.sessions.on = False
--- a/mcserver/mcserver.cfg	Fri Jun 22 18:07:53 2012 +0100
+++ b/mcserver/mcserver.cfg	Thu Sep 20 23:55:48 2012 +0100
@@ -4,31 +4,29 @@
 # kakapo<->golden
 server.socket_host = "192.168.122.144" 
 server.socket_port = 8030
-server.thread_pool = 30
+server.thread_pool = 50
 server.show_tracebacks = False
 
 engine.autoreload_on = False #should be off
 engine.autoreload_frequency = 180 #sec
 
-tools.sessions.on = True
+tools.sessions.on = False
 tools.sessions.timeout = 600 #min
 tools.sessions.clean_freq = 120	#min	
-# tools.sessions.storage_type = 'directory'
-# tools.sessions.SESSIONS_PATH = 'sessions/'
-# tools.sessions.SESSIONS_PATH = 'sessions'
 
 # expose everything in static, but note that sessions are enabled in this scope
 tools.staticdir.on = True
 tools.staticdir.dir = 'static'
-# tools.staticdir.content_types = {'n3': 'text/rdf+n3', 'rdf': 'application/rdf+xml'}
 
+log.screen = None
+# log.access_file = 'mood-access.log'
+log.error_file = "mcserver.log"
 
 #main app config
 [/]
 response.timeout = 600 #sec
 request.show_tracebacks = False
-# log.access_file = 'mcserver-access.log'
-# log.error_file = 'mcserver-error.log'
+tools.proxy.on = True
 # tools.staticdir.root = 'mcserver'
 tools.staticdir.on = True
 tools.staticdir.dir = 'static'
@@ -36,3 +34,6 @@
 # explicit configuration of static content with sessions disabled
 [/script]
 tools.sessions.on = False
+
+[/static]
+tools.sessions.on = False
--- a/mcserver/mcserver.py	Fri Jun 22 18:07:53 2012 +0100
+++ b/mcserver/mcserver.py	Thu Sep 20 23:55:48 2012 +0100
@@ -27,48 +27,55 @@
 
 class Input(object):
 	
-	def __init__(self,x,y):
+	def __init__(self,x,y,age,dist):
 		self.time = time.time()
 		self.x=float(x)
 		self.y=float(y)
 		self.count = 1
+		self.age = age
+		self.dist = dist
 		
 	def __cmp__(self,other):
 		d = math.sqrt( math.pow((self.x-other.x),2) + math.pow((self.y-other.y),2) )
-		if d < 0.05 : 
+		if d < self.dist : 
 			return 0
 		else :
 			return -1
 	
 	def dead(self):
-		return time.time()-self.time > 1.5
+		return time.time()-self.time > self.age
 		
 	def __repr__(self):
-		return (self.x,self.y,self.count).__repr__()
+		# return (self.x,self.y,self.count).__repr__()
+		return "(%.4f,%.4f,%i)" %(self.x,self.y,self.count)
 		
 	def inc(self):
 		self.time = time.time()
 		self.count += 1
-		print self.count
+		# print self.count
 
 
 class MoodConductor:
 	
 	def __init__(self):
 		self.inputs = []
+		self.age = 3
+		self.dist = 0.1
+		self.ninp = 12
 	
-	def index(self):
-		return ""
+	# def index(self):
+	# 	return str()
 	
 	@cp.expose
 	def mood(self,x,y):
-		print "Received coordinates", x,y, "\n"
-		i = Input(x,y)
+		# print "Received coordinates", x,y, "\n"
+		i = Input(x,y,self.age,self.dist)
 		if i in self.inputs :
 			self.inputs[self.inputs.index(i)].inc()
 		else :
 			self.inputs.insert(0,i)
-		self.inputs = self.inputs[:10]
+		self.inputs = self.inputs[:self.ninp]
+		cp.log.error("%s - %.6s-%.6s" %(str(cp.request.remote.ip),x,y))
 		return str()
 		
 	@cp.expose
@@ -76,6 +83,21 @@
 		for i in self.inputs :
 			if i.dead() : self.inputs.remove(i)
 		return self.inputs.__repr__()
+		
+	@cp.expose
+	def config(self,**kwargs):
+		if kwargs.has_key('age') :
+			self.age = float(kwargs['age'])
+		if kwargs.has_key('dist') :
+			self.dist = float(kwargs['dist'])
+		if kwargs.has_key('ninp') :
+			self.ninp = int(kwargs['ninp'])
+		return str()
+
+	@cp.expose
+	def getconf(self):
+		self.config_list = ['age','dist','ninp']
+		return str(map(lambda x: (x,"%.3f" % self.__dict__[x]),self.config_list)) #+ " Sessions: " + str(cp.tools.sessions)
 	
 
 
--- a/visualclient/visclient.py	Fri Jun 22 18:07:53 2012 +0100
+++ b/visualclient/visclient.py	Thu Sep 20 23:55:48 2012 +0100
@@ -15,6 +15,9 @@
 import gradients
 from gradients import genericFxyGradient
 
+from threading import Thread
+from random import random
+
 # from pytagcloud import create_tag_image, make_tags
 # from pytagcloud.lang.counter import get_tag_counts
 
@@ -29,11 +32,63 @@
 ecol   = (0,0,0,255)
 
 # X,Y=1140,900
-X,Y = 600,400
-NBLOBS = 15
+# X,Y = 600,400
+X,Y = 800,600
+
+NBLOBS = 18
 BLOBSIZE = 25
 G=110
 FADE = 15
+DIST = 0.1 # blob equivalence tolerance
+FRAMERATE = 60
+
+# Connection:
+# IP = "192.168.2.184:8030"
+IP = "138.37.95.215"
+HTTP_TIMEOUT = 3
+SERVER_UPDATE_INTERVAL = 0.8
+
+class Indicator(object):
+	
+	off_color = pg.Color(110,0,0)
+	on_color = pg.Color(0,120,0)
+	
+	def __init__(self,bg,pos):
+		self.visible = True
+		self.ison = True
+		self.x,self.y = pos
+		self.xs = int(self.x * X)
+		self.ys = int(Y - (self.y * Y))
+		self.c = self.off_color
+		self.size = 6
+		self.bg = bg
+		
+	def reinit(self,bg):
+		self.bg = bg
+		self.xs = int(self.x * X)
+		self.ys = int(Y - (self.y * Y))
+		
+	def draw(self):
+		if self.visible :
+			pg.draw.circle(self.bg, self.c, (self.xs,self.ys),self.size,0)
+		
+	def toggle(self):
+		if self.ison == True :
+			self.off()
+		else :
+			self.on()
+		return self
+			
+	def on(self):
+		self.c = self.on_color			
+		self.ison = True
+		return self
+		
+	def off(self):
+		self.c = self.off_color
+		self.ison = False
+		return self
+
 
 class Blob(object):
 	
@@ -55,12 +110,13 @@
 				
 	def __cmp__(self,other):
 		d = math.sqrt( math.pow((self.x-other.x),2) + math.pow((self.y-other.y),2) )
-		if d < 0.03 : 
+		if d < DIST : 
 			return 0
 		else :
 			return -1
 		
 	def draw(self):
+		if not self.visible : return
 		d=int(self.size)
 		self.bg.blit(gradients.radial(self.size, (self.c[0],self.c[1],self.c[2],self.alpha), (0,0,0,self.alpha)), (self.xs-d,self.ys-d))
 		self.alpha = 255 - int(self.age()*self.FADE) 
@@ -102,10 +158,11 @@
 
 
 class VisualClient(object):
+	'''Main visualisation client.'''
 	
 	def __init__(self):
 		# self.conn = ht.HTTPConnection("192.168.2.184:8030")
-		self.conn = ht.HTTPConnection("138.37.95.215")
+		# self.conn = ht.HTTPConnection("138.37.95.215")
 		
 		pg.init()
 		
@@ -119,6 +176,7 @@
 		self.bg = pg.Surface(self.screen.get_size())
 		self.bg = self.bg.convert()
 		self.bg.fill((0,0,0))
+		pg.display.set_gamma(100.0)
 		
 		
 		self.scol   = (0,255,0,255)
@@ -128,28 +186,30 @@
 		self.blobs = []
 		self.moods = []
 		self.read_mood_data()
-		# pg.quit()
-		# sys.exit(-1)
 		
 		self.FADE = FADE
 		
+		self.indicators = {
+		"conn":Indicator(self.bg,(0.98,0.02)),
+		"update":Indicator(self.bg,(0.96,0.02)),
+		"data":Indicator(self.bg,(0.94,0.02)),
+		"receive":Indicator(self.bg,(0.92,0.02))}
+		
+		self.thread = None		
+		self.running = False
+		self.fullscreen = False
+		self.init_reconnect = False
 		pass
 		
+	
 	def read_mood_data(self):
-		
+		'''Read the mood position and color information form csv file.'''
 		with open('moods.csv') as mf:
 			data = mf.readlines()[1:]
 			for line in data :
 				l = line.split(',')
 				mood = Mood(l[0],l[1],l[2])
 				self.moods.append(mood)
-		# with open('feelings.txt') as ff:
-		# 	data = ff.readlines()
-		# 	data = map(lambda x: x.split('\t'),data)
-		# 	for mood in self.moods :
-		# 		for colors in data :
-		# 			if mood.word == colors[0] :
-		# 				mood.color = colors[2]
 		with open('colors.txt') as ff:
 			data = ff.readlines()[1:]
 			data = map(lambda x: x.split(','),data)
@@ -160,15 +220,43 @@
 					if d < cd :
 						cd = d
 						mood.color = tuple(map(lambda x: int(pow(math.atan((float(x)/7.0)),12.5)),(colors[2],colors[3],colors[4])))
+						# mood.color = tuple(map(lambda x: int(x),(colors[2],colors[3],colors[4])))
+		return True
+	
 		
-		# for mood in self.moods:
-		# 	if mood.color :
-		# 		# mood.color = map(lambda x: '0x'+str(x).strip(),[mood.color[0:2],mood.color[2:4],mood.color[4:]])
-		# 		mood.color = tuple(map(lambda x: int(x),mood.color))
-		# 		print mood.color
+	def start_update_thread(self):
+		'''Start the thread that reads data from the server.'''
+		self.running = True		
+		self.thread = Thread(target = self.update_thread)
+		self.thread.daemon = True
+		self.thread.start()
 		
+	def stop_update_thread(self):
+		'''Stop the thread and allow some time fot the connections to close.'''
+		self.running = False
+		try :
+			self.thread.join(2)
+		except :
+			print "No update thread to join."
+
+	def update_thread(self):
+		'''The server update thread'''
+		while self.running :
+			try :				
+				self.update()
+				# self.indicators["update"].visible = True
+			except Exception, e:
+				if str(e).strip() : print "Exception: ", str(e), type(e), len(str(e).strip())
+				self.indicators["conn"].off()
+				# self.indicators["update"].visible = False
+			time.sleep(SERVER_UPDATE_INTERVAL)
+
 		
 	def update(self):
+		'''Update the blob list from the server. This should be in a thread.'''
+		
+		# indicate connection health by toggling an indictor
+		self.indicators["update"].toggle()
 		
 		# delete invisibles
 		for blob in self.blobs :
@@ -181,35 +269,42 @@
 		self.conn.endheaders()
 		res = self.conn.getresponse()
 		data = res.read()
-		data = eval(data)
+		data = eval(data)		
 		if not data : 
 			self.conn.close()
+			self.indicators["data"].toggle()
 			return False
 		for d in data :
 			# coordstxt = "x:%s y:%s c:%s" %d
-			x,y,c = d
-			
-			cmood = None
-			d = cd = sys.float_info.max
-			for mood in self.moods :
-				d = mood.get_distance(x,y)
-				if d < cd :
-					cd = d
-					cmood = mood
-
-			new = Blob(self.bg,x,y,mood=cmood,fade=self.FADE)
-			if not new in self.blobs :
-				self.blobs.insert(0,new)
-			elif c > self.blobs[self.blobs.index(new)].count:
-				self.blobs[self.blobs.index(new)].increment(c)
-
+			x,y,count = d
+			self.add_blob(x,y,count)
+			self.indicators["receive"].toggle()
 		self.conn.close()
 		self.blobs = self.blobs[:NBLOBS]
 		return True
+		
+		
+	def add_blob(self,x,y,count=1):
+		'''Insert a blob to the list of blobs'''
+		# find mood correxponding to x,y
+		cmood = None
+		d = cd = sys.float_info.max
+		for mood in self.moods :
+			d = mood.get_distance(x,y)
+			if d < cd :
+				cd = d
+				cmood = mood
+		# create new blob or increase click count on existing one
+		new = Blob(self.bg,x,y,mood=cmood,fade=self.FADE)
+		if not new in self.blobs :
+			self.blobs.insert(0,new)
+		elif count > self.blobs[self.blobs.index(new)].count:
+			self.blobs[self.blobs.index(new)].increment(count)
+		pass
 
 		
 	def draw(self):
-		self.bg.fill((0,0,0))			
+		self.bg.fill((0,0,0))
 		# self.bg.blit(gradients.radial(19, self.scol, self.ecol), (rect_x,rect_y))
 		l = copy.copy(self.blobs)
 		l.reverse()
@@ -220,63 +315,186 @@
 		pg.draw.line(self.bg, (G,G,G), (int(X/2.0),0),(int(X/2.0),Y), 1)
 		pg.draw.line(self.bg, (G,G,G), (0,int(Y/2.0)),(X,int(Y/2.0)), 1)
 		
+		# indicators
+		for i in self.indicators.itervalues() :
+			i.draw()
+			
+	def read_keys(self):
+		'''Read keys'''
+		for event in pg.event.get() :
+			# Quit (event)
+			if event.type == QUIT:
+				self.quit()
+			elif event.type == KEYDOWN :
+				# Post Quit: Esc, q
+				if event.key == K_ESCAPE or event.key == K_q :
+					pg.event.post(pg.event.Event(QUIT))
+				# Reset: Space
+				elif event.key == K_SPACE :
+					self.blobs = []
+				# Random : r
+				elif event.key == K_r :
+					self.add_blob(random(),random(),count=5)
+				# Connection : c
+				elif event.key == K_c :
+					self.init_reconnect = True
+					self.indicators["conn"].off()
+				# Fullscreen: f
+				elif event.key == K_f :
+					# pg.display.toggle_fullscreen()
+					self.toggle_screen_mode()
+				# Toggle fade: s
+				elif event.key == K_s :
+					if self.FADE :
+						print "fade off"
+						self.indicators["conn"].off()
+						self.FADE = 0
+						for blob in self.blobs :
+							blob.fade(0)
+					else:
+						print "fade on"
+						self.indicators["conn"].on()
+						self.FADE = 15
+						for blob in self.blobs :
+							blob.fade(15)
+							blob.reset_time()
+		pass
+		
+	def toggle_screen_mode(self):
+		'''Go back and forth between full screen mode.'''
+		if self.fullscreen == False:
+			globals()['_X'] = globals()['X']
+			globals()['_Y'] = globals()['Y']
+			globals()['X'] = 1440
+			globals()['Y'] = 900			
+			self.screen = pg.display.set_mode((X, Y))			
+			self.fullscreen = True
+			self.bg = pg.Surface(self.screen.get_size())
+			self.bg = self.bg.convert()
+			self.bg.fill((0,0,0))
+			for i in self.indicators.itervalues() :
+				i.reinit(self.bg)
+				i.draw()			
+		else :
+			globals()['X'] = globals()['_X']
+			globals()['Y'] = globals()['_Y']
+			self.screen = pg.display.set_mode((X, Y))			
+			self.fullscreen = False
+			self.bg = pg.Surface(self.screen.get_size())
+			self.bg = self.bg.convert()
+			self.bg.fill((0,0,0))
+			for i in self.indicators.itervalues() :
+				i.reinit(self.bg)
+				i.draw()			
+		pg.display.toggle_fullscreen()
+		
 
 				
 	def run(self):
-		# conn = ht.HTTPConnection("192.168.2.184:8030")
-		self.conn = ht.HTTPConnection("138.37.95.215")
 		
-		rect_x,rect_y = 0,0
-		counter = 0
-		
+		# setup connection
+		self.connect()
+				
 		# main loop
 		while True :
 			# pg.draw.circle(screen, pg.Color(255,0,0), (300,50),20,0)
 			# screen.blit(gradients.radial(99, scol, ecol), (401, 1))
 
-			for event in pg.event.get() :
-				if event.type == QUIT:
-					self.conn.close()				
-					pg.quit()
-					sys.exit()
-				elif event.type == KEYDOWN :
-					if event.key == K_ESCAPE :
-						pg.event.post(pg.event.Event(QUIT))
-					elif event.key == K_SPACE :
-						self.blobs = []
-					elif event.key == K_s :
-						if self.FADE :
-							print "fade off"
-							self.FADE = 0
-							for blob in self.blobs :
-								blob.fade(0)
-						else:
-							print "fade on"
-							self.FADE = 15
-							for blob in self.blobs :
-								blob.fade(15)
-								blob.reset_time()
-							
-								
-
+			self.read_keys()
 
 			# Draw 
 			self.draw()
 
-
-			# update from the server
-			counter += 1
-			if counter % 12 :
-				counter = 0
-				try :
-					self.update()
-				except:
-					pass
 			# update display
 			self.screen.blit(self.bg, (0, 0))
 			pg.display.flip()
-			self.fpsClock.tick(50)
-		pass
+			self.fpsClock.tick(FRAMERATE)
+			
+			if self.init_reconnect:
+				self.reconnect()
+			
+		return True
+
+		
+	def connect(self,retry = 5):
+		'''Connect to the server and test connection.'''
+		if not retry :
+			print "Server unreachable"
+			pg.quit()
+			raise SystemExit
+		if retry < 5 :
+			time.sleep(3)
+		try :
+			self.conn = ht.HTTPConnection(IP,timeout=HTTP_TIMEOUT)
+			# self.start_update_thread()
+			self.indicators["conn"].on()
+		except :
+			self.indicators["conn"].off()
+			self.connect(retry = retry-1)
+			
+		try:
+			self.conn.putrequest("GET","/moodconductor/index.html", skip_host=True)
+			self.conn.putheader("Host", "www.isophonics.net")
+			self.conn.endheaders()
+			res = self.conn.getresponse()
+			if res.status == 200 :
+				self.indicators["conn"].on()
+			else :
+				print "Server response:", res.status, res.reason
+				self.indicators["conn"].off()
+				if not hasattr(self,"noretry") and raw_input("Go offline? ") in ['y',''] :
+					return False
+				else :
+					self.noretry = None
+					self.connect(retry = retry-1)
+		except :
+			print "Exception while testing connection."
+			self.indicators["conn"].off()
+			# comment out in offline mode
+			if not hasattr(self,"noretry") and raw_input("Go offline? ") in ['y',''] :
+				return False
+			else :
+				self.noretry = None
+				self.connect(retry = retry-1)
+		self.start_update_thread()
+		return True
+		
+		
+		
+	def reconnect(self):
+		'''Called when c is pressed.'''
+		self.init_reconnect = False
+		# self.indicators["conn"].off().draw()
+		# self.screen.blit(self.bg, (0, 0))
+		# pg.display.flip()
+		
+		self.stop_update_thread()
+		time.sleep(1)
+		try :
+			self.conn.close()
+		except :
+			self.indicators["conn"].off()
+		try :
+			self.conn = ht.HTTPConnection(IP,timeout=HTTP_TIMEOUT)
+			self.conn.connect()
+			self.indicators["conn"].on()
+			print "Reconnected."
+		except :
+			self.indicators["conn"].off()
+			print "Error while reconnecting."
+		self.start_update_thread()
+		
+
+	def quit(self):
+		print "Quitting.."
+		self.indicators["conn"].off()		
+		self.stop_update_thread()
+		self.conn.close()
+		pg.quit()
+		sys.exit()
+	
+		
+		
 
 
 def main():