#!/usr/bin/env python
# coding=utf-8
#
# Python implementation of pd100_get.pl
#
# Author: Patrick Salecker
# URL: http://www.salecker.org/software/pd100/en
# Last modified: 06.12.2009

import time
import os
import socket
import re

import threading

from struct import unpack
import binascii

HOST = "192.168.1.102"
PORT = 80
PASSWORD = ""
FILENAME = "test.jpg"
INTERVAL = 0.1

#############################################################
# IF $typacket == 0x4f
# dopacket:
# -> - to camera
# <- - from camera
#############################################################
#t/f hex explanation
# -> 00 - reset camera's config for logged user
# <- 01 - show camera's mac address
# -> 02 - login info sending to camera
# <- 03 - login answer from camera (access/deny)
# -> 10 - clearing something ????
# <- 16 - answer to "10", maybe cleared ????
# -> 04 - something setup asking ????
# <- 11 - some code sended (answer)
# <- 05 - again something sended
##############################################################
#$typacket == 0x56
##############################################################
# -> 00 - send code gotted from 05 in 0x04f setup (see upper)
##############################################################
#$typacket == 0x4f
##############################################################
# -> 07 - clearing something (or say "start")
##############################################################
#$typacket == 0x56
##############################################################
# <- 01 - sending data
##############################################################

def value2hexstring(value,length):
	#print hexstring(23,2),"17"
	#print hexstring(0,2),"00"
	#print hexstring(1,2),"01"
	#print hexstring(1,8),"01000000"
	hstr="%X" %value
	if len(hstr)==length:
		return hstr
	else:
		rest=length-len(hstr)
		if rest%2!=0:
			rest=rest-1
			hstr="0"+hstr
		return hstr+"0"*(rest)
		
def new_socket():
	"""connect to HOST,PORT
	"""
	s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	s.connect((HOST,PORT))
	return s

def makepacket(data,typacket,dopacket):
	dsize=len(data)/2
	
	bla="4d4f5f%s%s00000000000000000000%s%s%s" % (
		typacket,
		value2hexstring(dopacket,2),
		value2hexstring(dsize,8),
		value2hexstring(dsize,8),
		data
	)
	#print bla
	if len(bla)%2!=0:
		bla=bla+"0"
	#print len(data)/2+46,len(bla)
	return binascii.unhexlify(bla)
		
def countpacketdata(obj,buf):
	#perl
	#if ($buf =~ m/^\x4d\x4f\x5f(.)(.)\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00(.{4})(.{4}).*/){
	bla=re.compile(r"^\x4d\x4f\x5f(.)(.)\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00(.{4})(.{4}).*")
	result= bla.split(buf)
	if len(result)>4:
		#print repr(result)
		obj.typacket = "%X"%ord(result[1])
		obj.dopacket = ord(result[2])
		
		psize=  unpack("i",result[3])[0]
		psizecheck=  unpack("i",result[4])[0]
		
		if psize == psizecheck:
			return psize
		else:
			return 0
	else:
		return 0;
		
class PD100:
	def __init__(self,filename=None):
		self.typacket="4f"
		self.dopacket=0
		self.child=None
		
		#open a socket to HOST,PORT
		self.s=new_socket()
		
		buf=makepacket("",self.typacket,self.dopacket)
		self.s.send(buf)
		
		buf=self.receive_next()
		
		if (self.dopacket==1):
			mac=buf[2:14]
			print "Connected to MAC: %s" %mac
		else:
			print "Connected, but don't know MAC!"
		
		self.login() #login to camera
		
		self.dopacket=16
		
		buf=makepacket("",self.typacket,self.dopacket)
		self.s.send(buf)
		buf=self.receive_next()
		
		if self.dopacket==22 and buf=="\x00":
			print "Answer to \"0x10\" OK!"
		else:
			if self.dopacket==22:
				print "Answer to \"0x10\" NOT OK!"
			else:
				print "No answer to \"0x10\"!"
			
		self.dopacket=4
		
		buf=makepacket("02",self.typacket,self.dopacket)
		self.s.send(buf)
		
		#skip useless/unknown data
		self.receive_next()
		
		buf=self.receive_next()
		
		if self.dopacket==17:
			print "Answer to \"0x04\" give: %s" %repr(buf)
			
		buf=self.receive_next()
		
		if self.dopacket==5:
			client_hash=self.get_client_hash(buf)
			
			#create another socket in another thread for receiving images
			self.child=ChildThread(client_hash,filename)
			self.child.start()
			
			# say to control socket "start"!
			self.typacket="4f"
			self.dopacket=7
			buf=makepacket("00000000",self.typacket,self.dopacket)
			self.s.send(buf)
			
			#self.loop()
			
	def login(self):
		"""login to camera with "admin" and PASSWORD
		"""
		self.dopacket=2
		
		pw=PASSWORD.encode("hex") + "0"*(26-(len(PASSWORD)*2))
		buf=makepacket("61646D696E0000000000000000"+pw,self.typacket,self.dopacket)
		self.s.send(buf)
		
		buf=self.receive_next()
		
		if buf=="\x00\x00\x02" and self.dopacket==3:
			print "login OK!"
		else:
			print "ERROR: wrong login or packet cmd"
			
	def get_client_hash(self,buf):
		client_hash=""
		#print repr(buf),len(buf)
		for bla in buf:
			hexstr="%X"%ord(bla)
			#print len(hexstr)
			if len(hexstr)%2!=0:
				hexstr="0"+hexstr
			client_hash=client_hash+hexstr
		client_hash=client_hash.lower()[4:]
		print "Client Hash: %s (%s)" %(client_hash,len(client_hash))
		return client_hash
			
	def loop(self):
		"""wait for keep-alive packets and answer
		"""
		while True:
			try:
				buf=self.s.recv(23)
			except:
				self.stop()
				break
			
			countpacketdata(self,buf)
			if self.dopacket==255:
				# ~ every 60sec
				print "Send keep-alive!";
				buf=makepacket("",self.typacket,self.dopacket)
				self.s.send(buf)
			elif self.dopacket==18:
				print "error: dopacket 18, break"
				break
			else:
				print "unknown data received: %s - %s" %(self.dopacket,repr(buf))
				
	def stop(self):
		"""clean disconnect
		"""
		# say to control socket "stop"!
		self.typacket="4f"
		self.dopacket=6
		buf=makepacket("",self.typacket,self.dopacket)
		self.s.send(buf)
		
		self.s.close()
		
		self.child.stop()
	
	def receive_next(self):
		"""receive the next packet
		"""
		buf=self.s.recv(23)
		dsize=countpacketdata(self,buf)
		buf=self.s.recv(dsize)
		
		return buf

class MainThread(threading.Thread):
	def __init__ (self):
		threading.Thread.__init__(self)
		self.is_running=False
		self.cam=None
		
	def stop(self):
		self.is_running=False
		self.cam.stop()
		
	def run(self):
		print "Main run"
		self.is_running=True
		self.cam=PD100()
		self.cam.loop()
		
	def get_jpeg(self):
		return self.cam.child.jpeg
		
	def is_ready(self):
		if self.cam!=None and self.cam.child!=None:
			return True
		return False

class ChildThread(threading.Thread):
	def __init__ (self,client_hash,filename=None):
		print "Child init, hash: %s" %client_hash
		self.client_hash=client_hash
		threading.Thread.__init__(self)
		self.typacket="56"
		self.dopacket=0
		self.jpeg=""
		self.filename=filename
		
		self.is_running=False
		self.s=new_socket()
		
	def stop(self):
		print "Child stop"
		self.is_running=False
		time.sleep(INTERVAL)
		self.s.close()
		
	def run(self):
		print "Child run"
		self.is_running=True
		
		buf=makepacket(self.client_hash,self.typacket,self.dopacket)
		self.s.send(buf)
		
		# loop for image gets
		print "Child loop"
		while self.typacket=="56" and self.is_running is True:
			if self.receive_image():
				#sleep if we received an image
				time.sleep(INTERVAL)

	def receive_image(self):
		"""request and receive the next image
		"""
		buf=self.s.recv(23)
		dsize=countpacketdata(self,buf)
			
		if dsize>0:
			#print "image!",dsize
			buf=self.s.recv(dsize)
			while len(buf)<dsize:
				buf=buf+self.s.recv(dsize)
			
			self.jpeg=buf[13:] #skip the first 13 chars
			#print dsize,len(buf),len(jpeg)
			if self.filename is not None:
				self.save_to_file()
			return True
		else:
			return False
	
	def save_to_file(self):
		"""write the jpeg code into a file
		"""
		f=open(FILENAME+".tmp","w")
		f.write(self.jpeg)
		f.close()
		os.rename(FILENAME+".tmp",FILENAME)


if __name__ == "__main__":
	bla=PD100(FILENAME)
	bla.loop()
