#!/usr/bin/env python2.5
# pyqrc v0.1.3 - QuadRobi client written in Python
# Copyright (C) Peter Ivanov <ivanovp@gmail.com>, 2007
#
# Tested on Nokia N800
# Licence: GPL
#
# Code starts: 2007-12-01 12:23:51
# Last update: 2007-12-12 20:37:21 ivanovp {Time-stamp}
#
# TODO:
# . keep alive message (eletjel)
# . dialog to enter host and port
# ~ fix disconnect bug

#import sys
import osso		# for N800
import gtk 
import hildon	# for N800
import gobject
import socket
import time
import threading
import Queue

DEFAULT_HOST="192.168.1.2"
DEFAULT_PORT=1776
PROG='pyqrc'
VER='0.1.3'
PROG_HEADER="%s v%s" % (PROG, VER)
PROG_DESC="QuadRobi client"

# Internal thread, a worker for the class QuadRobiClient
class QuadRobiThread (threading.Thread):	
	# time between sending commands over socket
	SOCKET_SLEEP_TIME = 0.1	# 0.1 sec

	def __init__ (self, queueIn, queueOut):
		print "QuadRobiThread __init__!"
		self._connected = False
		self._queueIn = queueIn
		self._queueOut = queueOut
		#self.ready = threading.Condition ()
		threading.Thread.__init__ (self)

	def __del__ (self):
		print "QuadRobiThread __del__!"
		if self._connected:
			self._socket.close ()

	def cmdConnect (self, host=DEFAULT_HOST, port=DEFAULT_PORT, sock=None):
		self._socket = sock
		if sock is None:
			self._socket = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
		else:
			self._socket = sock
		self._socket.settimeout (5.0)
		try:
			self._socket.connect ((host, port))
		except socket.error, socket.timeout:
		 	self._connected = False
		 	self._socket.close ()
			self._queueOut.put ("error_cannot_connect")
		 	return
		self._queueOut.put ("connected")
		self._connected = True

	def cmdDisconnect (self):
		if self._connected:
			self._socket.close ()

	def cmdSend (self, payload):
		if self._connected:
			self._socket.send (payload)
			time.sleep (self.SOCKET_SLEEP_TIME)
		else:
			self._queueOut.put ("error_not_connected")
			print "Cannot send payload: '" + payload + "'. Not connected!"
	
	def cmdSleep (self, time):
		if time > 0.0 and time < 1000.0:
			time.sleep (time)
		else:
			self._queueOut.put ("Time value must be between 0 and 1000 seconds!")

	def run (self):
		print "QuadRobiThread run!"
		quit = False
		while not quit:
			input = self._queueIn.get ()
			if (input['command'] == 'connect'):
				self.cmdConnect (input['host'], input['port'])
			if (input['command'] == 'disconnect'):
				self.cmdDisconnect ()
			if (input['command'] == 'send'):
				self.cmdSend (input['payload'])
			if (input['command'] == 'sleep'):
				self.cmdSleep (input['time'])
			if (input['command'] == 'quit'):
				quit = True
		print "QuadRobiThread exiting..."

# Class to connect to the car over TCP/IP
class QuadRobiClient:
	TIMEOUT = 100 		# 100 msec
	# True: use queue to send control characters over TCP/IP (low latency!)
	# False: use directly the socket to send control characters over TCP/IP
	USE_QUEUE=False

	def __init__ (self, app, sock=None):
		gobject.timeout_add (self.TIMEOUT, self.threadPendingCommands)
		self._connected = False
		self._app = app
		self._queueIn = Queue.Queue (0)
		self._queueOut = Queue.Queue (0)
		self._thread = QuadRobiThread (self._queueIn, self._queueOut).start ()

	def __del__ (self):
		self.quit ()

	def quit (self):
		# stop the thread
		self._queueIn.put ({'command': 'quit'})

	def threadPendingCommands (self):
		#print "threadPendingCommands!"
		try:
			output = self._queueOut.get_nowait ()
		except Queue.Empty:
			return True
		if output == "connected":
			self._app.osso_sysnote.system_note_infoprint ("Connected to %s:%i." % (self._host, self._port))
			self._connected = True
		if output == "error_cannot_connect":
			self._app.osso_sysnote.system_note_dialog ("Cannot connect to %s:%i!" % (self._host, self._port), 'error')
			self._connected = False
		if output == "error_not_connected":
			#self._app.osso_sysnote.system_note_dialog ("Not connected!", 'error')
			self._app.osso_sysnote.system_note_infoprint ("Not connected!")
			self._connected = False
		#print "threadPendingCommands() Data from thread:", output
		return True

	def connect (self, host_=DEFAULT_HOST, port_=DEFAULT_PORT):
		if not self._connected:
			self._host = host_
			self._port = port_
			self._queueIn.put ({'command': 'connect', 'host': host_, 'port': port_ })

	def disconnect (self):
		if self._connected:
			self._queueIn.put ({'command': 'disconnect'})
			self._connected = False

	def sendCmd (self, command):
		if USE_QUEUE:
			self._queueIn.put ({'command': 'send', 'payload': command })
		else:
			self._thread._socket.send (command)
			time.sleep (self._thread.SOCKET_SLEEP_TIME)

	# Car will go forward
	def forward (self):
		self.sendCmd ('f')

	# Car will go backward
	def backward (self):
		self.sendCmd ('b')

	# Car will stop (free run)
	def stop (self):
		self.sendCmd ('s')

	# Car will brake
	def brake (self):
		self.sendCmd ('B')

	# Front wheels will turn left (and car will turn left also if go forward)
	def left (self):
		self.sendCmd ('l')

	# Front wheels will turn right (and car will turn right also if go forward)
	def right (self):
		self.sendCmd ('r')

	# Wheels will return to normal position (and car will go straight)
	def middle (self):
		self.sendCmd ('m')

	def isConnected (self):
		return self._connected

class App (hildon.Program):		# for N800
#class App:						# for PC

	def __init__ (self):
		self.osso_c = osso.Context (PROG, VER, False)		# for N800
		hildon.Program.__init__ (self)						# for N800

		self.window = hildon.Window () 						# for N800
		#self.window = gtk.Window (gtk.WINDOW_TOPLEVEL)		# for PC
		self.window.set_title (PROG + " - " + PROG_DESC)
		self.window.connect ("destroy", self.quitCallback)
		self.window.connect ("key-press-event", self.keyPressCallback)
		self.window.connect ("window-state-event", self.windowStateChangeCallback)
		self.windowInFullscreen = False	# The window isn't in full screen mode initially.
		self.add_window (self.window)

		self.osso_sysnote = osso.SystemNote (self.osso_c)

		self.menu = gtk.Menu ()
		self.aboutMenu = gtk.MenuItem ("About")
		self.aboutMenu.connect ("activate", self.aboutCallback)
		self.menu.append (self.aboutMenu)

		#self.submenu = gtk.Menu ()
		#self.two = gtk.MenuItem("Two")
		#self.two.connect ("activate", self.hello)
		#self.submenu.append(self.two)
		#self.masikMenu.set_submenu (self.submenu)

		self.quitMenu = gtk.MenuItem ("Quit")
		self.quitMenu.connect ("activate", self.quitCallback)
		self.menu.append (self.quitMenu)

		self.window.set_menu (self.menu)

		self.vbox = gtk.VBox (False, 5)
		self.window.add (self.vbox)

		# Create a 3x3 table to place buttons
		self.table = gtk.Table (3, 3, True) # rows, columns
		self.table.set_row_spacings (5)
		self.table.set_col_spacings (5)
		self.vbox.pack_start (self.table, True, True, 0)

		# Create buttons
		self.forwardLeftButton = gtk.Button ("Forward Left")
		self.forwardLeftButton.connect ("button_press_event", self.forwardLeftPressedCallback)
		self.forwardLeftButton.connect ("button_release_event", self.forwardLeftReleasedCallback)
		self.table.attach (self.forwardLeftButton, 0, 1, 0, 1)

		self.forwardButton = gtk.Button ("Forward")
		self.forwardButton.connect ("button_press_event", self.forwardPressedCallback)
		self.forwardButton.connect ("button_release_event", self.forwardReleasedCallback)
		self.table.attach (self.forwardButton, 1, 2, 0, 1)

		self.forwardRightButton = gtk.Button ("Forward Right")
		self.forwardRightButton.connect ("button_press_event", self.forwardRightPressedCallback)
		self.forwardRightButton.connect ("button_release_event", self.forwardRightReleasedCallback)
		self.table.attach (self.forwardRightButton, 2, 3, 0, 1)
		
		self.brakeButton = gtk.Button ("Brake")
		self.brakeButton.connect ("button_press_event", self.brakePressedCallback)
		self.brakeButton.connect ("button_release_event", self.brakeReleasedCallback)
		self.table.attach (self.brakeButton, 1, 2, 1, 2)

		self.backwardLeftButton = gtk.Button ("Backward Left")
		self.backwardLeftButton.connect ("button_press_event", self.backwardLeftPressedCallback)
		self.backwardLeftButton.connect ("button_release_event", self.backwardLeftReleasedCallback)
		self.table.attach (self.backwardLeftButton, 0, 1, 2, 3)

		self.backwardButton = gtk.Button ("Backward")
		self.backwardButton.connect ("button_press_event", self.backwardPressedCallback)
		self.backwardButton.connect ("button_release_event", self.backwardReleasedCallback)
		self.table.attach (self.backwardButton, 1, 2, 2, 3)

		self.backwardRightButton = gtk.Button ("Backward Right")
		self.backwardRightButton.connect ("button_press_event", self.backwardRightPressedCallback)
		self.backwardRightButton.connect ("button_release_event", self.backwardRightReleasedCallback)
		self.table.attach (self.backwardRightButton, 2, 3, 2, 3)

		# HBox for connect/disconnect buttons
		self.hbox = gtk.HBox (True, 10)
		self.vbox.pack_start (self.hbox, False, False, 10)
		
		self.connectButton = gtk.Button ("Connect")
		#self.connectButton = gtk.Button (stock=gtk.STOCK_CONNECT)
		self.connectButton.connect ("button_press_event", self.connectCallback)
		self.hbox.pack_start (self.connectButton, True, True, 0)

		self.disconnectButton = gtk.Button ("Disconnect")
		self.disconnectButton.connect ("button_press_event", self.disconnectCallback)
		self.hbox.pack_start (self.disconnectButton, True, True, 0)

		self.qrc = QuadRobiClient (self)

	def about (self):
		self.osso_sysnote.system_note_dialog (\
				PROG_HEADER + " - " + PROG_DESC + "\n" + \
				"\n" + \
				"Copyright (C) Peter Ivanov, 2007\n" + \
				"E-mail: ivanovp@gmail.com\n" + \
				"Homepage: http://ivanov.eu/\n", \
				'notice')

	def aboutCallback (self, widget):
		self.about ()

	def quit (self):
		self.qrc.quit ()
		gtk.main_quit ()

	def quitCallback (self, widget):
		self.quit ()

	def keyPressCallback (self, widget, event, *args):
		if event.keyval == gtk.keysyms.F6: 
			# The "Full screen" hardware key has been pressed 
			if self.windowInFullscreen: 
				self.window.unfullscreen () 
			else: 
				self.window.fullscreen ()

	def windowStateChangeCallback (self, widget, event, *args): 
		if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN: 
			self.windowInFullscreen = True 
		else: 
			self.windowInFullscreen = False

	def forwardLeftPressedCallback (self, widget, event, *args):
		if self.qrc.isConnected ():
			self.qrc.left ()
			self.qrc.forward ()
		else:
			self.osso_sysnote.system_note_infoprint ("Not connected!")

	def forwardPressedCallback (self, widget, event, *args):
		if self.qrc.isConnected ():
			self.qrc.middle ()
			self.qrc.forward ()
		else:
			self.osso_sysnote.system_note_infoprint ("Not connected!")

	def forwardRightPressedCallback (self, widget, event, *args):
		if self.qrc.isConnected ():
			self.qrc.right ()
			self.qrc.forward ()
		else:
			self.osso_sysnote.system_note_infoprint ("Not connected!")

	def brakePressedCallback (self, widget, event, *args):
		if self.qrc.isConnected ():
			self.qrc.brake ()
		else:
			self.osso_sysnote.system_note_infoprint ("Not connected!")

	def backwardLeftPressedCallback (self, widget, event, *args):
		if self.qrc.isConnected ():
			self.qrc.left ()
			self.qrc.backward ()
		else:
			self.osso_sysnote.system_note_infoprint ("Not connected!")

	def backwardPressedCallback (self, widget, event, *args):
		if self.qrc.isConnected ():
			self.qrc.middle ()
			self.qrc.backward ()
		else:
			self.osso_sysnote.system_note_infoprint ("Not connected!")

	def backwardRightPressedCallback (self, widget, event, *args):
		if self.qrc.isConnected ():
			self.qrc.right ()
			self.qrc.backward ()
		else:
			self.osso_sysnote.system_note_infoprint ("Not connected!")

	def forwardLeftReleasedCallback (self, widget, event, *args):
		if self.qrc.isConnected ():
			self.qrc.middle ()
			self.qrc.stop ()
		else:
			self.osso_sysnote.system_note_infoprint ("Not connected!")

	def forwardReleasedCallback (self, widget, event, *args):
		if self.qrc.isConnected ():
			self.qrc.middle ()
			self.qrc.stop ()
		else:
			self.osso_sysnote.system_note_infoprint ("Not connected!")

	def forwardRightReleasedCallback (self, widget, event, *args):
		if self.qrc.isConnected ():
			self.qrc.middle ()
			self.qrc.stop ()
		else:
			self.osso_sysnote.system_note_infoprint ("Not connected!")

	def brakeReleasedCallback (self, widget, event, *args):
		if self.qrc.isConnected ():
			self.qrc.middle ()
			self.qrc.stop ()
		else:
			self.osso_sysnote.system_note_infoprint ("Not connected!")

	def backwardLeftReleasedCallback (self, widget, event, *args):
		if self.qrc.isConnected ():
			self.qrc.stop ()
			self.qrc.middle ()
		else:
			self.osso_sysnote.system_note_infoprint ("Not connected!")

	def backwardReleasedCallback (self, widget, event, *args):
		if self.qrc.isConnected ():
			self.qrc.middle ()
			self.qrc.stop ()
		else:
			self.osso_sysnote.system_note_infoprint ("Not connected!")

	def backwardRightReleasedCallback (self, widget, event, *args):
		if self.qrc.isConnected ():
			self.qrc.middle ()
			self.qrc.stop ()
		else:
			self.osso_sysnote.system_note_infoprint ("Not connected!")
	
	def connectCallback (self, widget, event, *args):
		self.qrc.connect ()
	
	def disconnectCallback (self, widget, event, *args):
		self.qrc.disconnect ()

	def run (self):
		self.window.show_all ()
		gtk.main ()

if __name__ == "__main__":  
	#print "Platform:", sys.platform
	app = App () 
	gtk.gdk.threads_enter ()
	app.run ()
	gtk.gdk.threads_leave ()

# vim:set noet:
