All pastes #963485 Raw Edit

pysmsd 0.1

public python v1 · immutable
#963485 ·published 2008-03-30 16:38 UTC
rendered paste body
Index: pyneod.py===================================================================--- pyneod.py	(revision 111)+++ pyneod.py	(working copy)@@ -33,6 +33,11 @@ 		from pygsmd import GsmPhone 		daemons.append(GsmPhone(bus)) 	except Exception, e: LOG(LOG_ERR, __name__, 'pygsmd', e)+if has_section('sms'):+	try:+		from pysmsd import GsmSmsStorage+		daemons.append(GsmSmsStorage(bus))+	except Exception, e: LOG(LOG_ERR, __name__, 'pysmsd', e) if has_section('gps'): 	try: 		from pygpsd import GpsLocationIndex: setup.py===================================================================--- setup.py	(revision 111)+++ setup.py	(working copy)@@ -19,6 +19,7 @@ 		'pyggld.py', 		'pygpsd.py', 		'pygsmd.py',+		'pysmsd.py', 		'pypppd.py', 		'pypwrd.py', 		]Index: pysmsd.py===================================================================--- pysmsd.py	(revision 0)+++ pysmsd.py	(revision 0)@@ -0,0 +1,503 @@+#!/usr/bin/env python2.5+# -*- coding: latin1 -*-++# SMS Storage Daemon+# License: GPL, copyright Bernhard Kaindl <bkaindl@ffii.org>, 2008+# Derived from pygsmd.py, subject to:+# copyright : m. dietrich+# license: gpl++# Current status: In development:+#+# only ListEntries, RetrieveEntry and StoreEntry is implemented, but StoreEntrys+# successfully tested, in my setup, the Calypso always refused writing to the SIM.+#+# org.mobile.Storage.ListEntries gives all entries, no filters, no parameters.++# Biggest bug: Errors from the modem are not handled yet, needed if SIM not unlocked+# (if PIN/PUK is needed) to access SMS.++# Usage:+#  for standalone testing (after starting the muxer):+#    ./pysmsd.py &+#  for deployment (again after starting the muxer):+#    ./pyneod.py &++# Calling:+# alias dbus-query="dbus-send --system --print-reply --type=method_call"+# dbus-query --dest=org.mobile /org/mobile/Storage/SMS org.mobile.Storage.ListEntries +# Returns:+#   array [+#      string "5"+#   ]+# dbus-query --dest=org.mobile /org/mobile/Storage/SMS org.mobile.Storage.RetrieveEntry string:5+# Returns:+#   array [+#      dict entry(+#         string "index"+#         variant             int32 5+#      )+#      dict entry(+#         string "stat"+#         variant             string "REC READ"+#      )+#      dict entry(+#         string "book"+#         variant             string "Ingrid Steger"+#      )+#      dict entry(+#         string "number"+#         variant             string "+491790123456"+#      )+#      dict entry(+#         string "time"+#         variant             string "11:45:18+04"+#      )+#      dict entry(+#         string "date"+#         variant             string "08/03/23"+#      )+#      dict entry(+#         string "message"+#         variant             string "Danke! Ja schaud guat aus ... Buszale  hdvl"+#      )+#   ]+# TODO for this output: Maybe bring time and date into a standard format (to be decided)+# (could use datetime for it) and convert GSM timezone to common one (+04 -> GMT+1, ...)+# dbus-query --dest=org.mobile /org/mobile/Storage/SMS org.mobile.Storage.StoreEntry uint32:+491795965727 string:Hi+# Error org.freedesktop.DBus.Python.pysmsd.GsmError: Traceback (most recent call last):+#   File "/usr/lib64/python2.5/site-packages/dbus/service.py", line 655, in _message_cb+#     retval = candidate_method(self, *args, **keywords)+#   File "/windows/D/moko/pyneod/pysmsd.py", line 453, in StoreEntry+#     # FIXME: Test this, it always fails on my Neo with my SIM...+# GsmError: ('Writing SMS failed with:', '+CMS ERROR: operation not allowed')++__revision = '$Rev: 99 $'++# the serial does all the initialization of the serial port for this+# module. this includes line speed, hw flow control and the like.+import serial+from gobject import source_remove, io_add_watch, IO_IN, timeout_add+from time import sleep+# datetime can be used to parse the date and time returned in the SMS data:+from datetime import datetime+from base import log_info, log_debug, config, LOG_ERR, LOG_WARNING, LOG_INFO, LOG_DEBUG, LOG+from freesmartphone import *++init_commands = ( # base settings+	# echo off, verbose result on, report mobile equipment error,+	# cellular result codes and single numbering scheme:+	'Z',    # Being really slow...+	'E0V1', # Start sloowly...+	'+CMEE=2;+CRC=1;+CSNS=0',+	'+CFUN=1',+	#'+CSMS=0', # response is +CSMS: 1,1,1   (all MT, MO and CBM supported)+	#'+CPMS=?',# response is +CPMS: ("ME","SM"),("ME","SM"),("ME","SM")+	# (ME and SIM memories reading and writing, but reading ME fails..)+	'+CPMS="SM","SM","SM"',     #(set SIM memory)+	'+CPIN?',+	'+CPMS?',               #(query current memories)+	#+CPMS: "ME",5,99,"ME",5,99,"ME",5,99 (five messages in ME, 99 total space)+	########################### CHARSET SELECTION ########################+	'+CSCS="IRA"',   # Set IRA charset (only for now, USC2 later)        #+	#'+CSCS="UCS2"', # Set UCS-2 charset (later, but sure to be done)    #+	######################################################################+	'+CSDH=1;' # Show Text Mode Parameters (show additional info in CMGL/CMGR)+	'+CSCB=1', # (Just for interest: all CBMs are accepted)+	######################################################################+	# Set the New Message Indications properties:+	# +CNMI=[<mode>[,<mt>[,<bm>[,<ds>[,<bfr>]]]]]+	# mode=2: Buffer unsolicited result codes while link is reserved +	# mt=1: Enable getting new SMS indication with +CMTI: <mem>,<index>:+	# bm=2: New CBMs are routed directly to the TE using+	# +CBM: <sn>,<mid>,<dcs>,<page>,<pages><CR><LF><data>+	# ds=1: If SMS-STATUS-REPORT is forwarded to us also+	# (just for interest, can be disabled if we are not implementing+	#  SMS status here)+	# TODO: Use AT+CNMI=? -> +CNMI: (0-2),(0-3),(0-3),(0,1),(0,1)+	# to check if what you want is actually supported by the TE/TA!+	'+CNMI=2,1,2,1',+	'+CMGL="ALL"', # List all SMS+	)+statuscommand = ';'.join((+#	'+CMGF=0', # pdu mode sms enable +	'+CMGF=1', # text mode sms enable+	'+CSCS="IRA"', # Set IRA charset (only for now, USC2 later)+	'+CMGL="ALL"', # List all SMS+	))++import dbus+class GsmError(dbus.DBusException):+    _dbus_error_name = 'org.freesmartphone.Storage.SMS.GsmError'+class EntryNotfound(dbus.DBusException):+    _dbus_error_name = 'org.freesmartphone.Storage.SMS.NotFound'+class Busy(dbus.DBusException):+    _dbus_error_name = 'org.freesmartphone.Storage.SMS.Busy'+class NotReady(dbus.DBusException):+    _dbus_error_name = 'org.freesmartphone.Storage.SMS.NotReady'+class InternalError(dbus.DBusException):+    _dbus_error_name = 'org.freesmartphone.Storage.SMS.InternalError'++class GsmSmsStorage(serial.Serial, NotifyObject):+	'''+	this class just fires requests against the modem. if the modem+	answers, those responses are parsed and if the answers are state+	responses methods with the same name are called if a subclass+	implements those.++	this class does not contain gsm-state managment or interpretation+	of responses. it only controls if the modem is alive and reponses+	to standard status requests (see statuscommand which is fired+	every 3000 ms).++	TODO the statuscommand is currently constant. it should be+	possible to alter the states that are requested periodically.+	'''+	def __init__(self, bus):+		self.bus = bus+		# glib's waiters+		self.tow = None+		self.iow = None+		serial.Serial.__init__(self)+		NotifyObject.__init__(self,+			object_path='/org/mobile/Storage/SMS',+			bus_name=BusName(DBUS_NAME, bus=self.bus),+			)+		self.__ctor()+		LOG(LOG_INFO, __name__, 'waiting for muxer to tell us a channel')+		timeout_add(100, self.__connect_to_muxer)++	def __ctor(self):+		# reset div. info+		#+		self.baudrate = 115200+		self.command = None+		self.commands = []+		self._device_info = dict()+		self.in_command = False+		self.ready = False+		self.update_in_progress = True+		self.pin_required = False+		self._sms = dict()+		self._sms_idx = -1+		self._sms_list_new = []+		self._sms_indices_new = []+		self.poll_interval = int(config.get('gsm', 'poll_interval'))+		self.port = '/dev/null'+		self.rtscts = True+		self.silence = 0+		self._sim_auth_status = dict()+		self.timeout = 7+		self.writeTimeout = 1+		self.xonxoff = False+		# serial config++	def __connect_to_muxer(self):+		'''+		if gsm does not control the device itsself it will inquire one from the+		muxer. glib calls this method every x seconds to try that.+		'''+		LOG(LOG_DEBUG, __name__, '__connect_to_muxer')+		if self.isOpen():+			LOG(LOG_WARNING, 'serial port still open')+		try:+			obj = self.bus.get_object('org.mobile.mux', '/org/mobile/mux/RemoteObject')+			obj.connect_to_signal('deactivate', self.__deactivate, dbus_interface='org.mobile.mux.RemoteInterface')+			obj = Interface(obj, 'org.mobile.mux.RemoteInterface')+			gsm_port = str(obj.alloc_channel('gsm')) # str() needed because pyserial doesnt use isinstance!+			if gsm_port:+				LOG(LOG_DEBUG, __name__, '__connect_to_muxer got port', gsm_port)+				# reset div. info+				self.__ctor()+				self.port = gsm_port+				self.open()+				self.iow = io_add_watch(self, IO_IN, self.__read)+				self.tow = timeout_add(self.poll_interval * 1000, self.__poll)+				self.commands = list(init_commands)+				self.write('\n\r') # channel wakeup+				#self.write('ATZE0V1\n\r') # channel wakeup+				#line = self.readline().strip()+				#LOG(LOG_DEBUG, __name__, 'AAAAT response', line)+				self.__write(self.commands.pop(0))+				return False+		except Exception, e:+			LOG(LOG_ERR, __name__, 'dbus error', e)+		timeout_add(self.poll_interval * 1000, self.__connect_to_muxer)+		return False++	def __deactivate(self, s):+		'''+		method that gets called if the muxer deactivates the channel+		'''+		self.__close()+		timeout_add(self.poll_interval * 1000, self.__connect_to_muxer)++	def __poll(self):+		'''+		method that is called regulary to check if the modem is alive.+		in the end a hard reset will be tried.+		'''+		if self.isOpen():+			self.silence += 1+			if self.in_command:+				if self.silence > 100:+					self.__write("")+			else:+				LOG(LOG_DEBUG, __name__, 'polling modem', self.silence)+				if self.commands:+					self.__write(self.commands.pop(0))+				#elif self.silence > 4:+					#LOG(LOG_ERR, __name__, 'resetting due to silence')+					#self.__write('+CFUN=4') # function disable+					#self.__reset()+				elif self.silence > 2:+					LOG(LOG_INFO, __name__, 'issue empty request to wakeup')+					self.__write("")+				else:+					if statuscommand not in self.commands:+						LOG(LOG_DEBUG, __name__, 'status request queued')+						self.__push(statuscommand)+		return True++	def __close(self, hard=False):+		'''+		switch off the modem+		'''+		LOG(LOG_DEBUG, __name__, '__close')+		if self.iow:+			source_remove(self.iow)+			self.iow = None+		if self.tow:+			source_remove(self.tow)+			self.tow = None+		if self.isOpen():+			self.close()+		self.__ctor()++	def __push(self, command):+		'''+		push a new command to the queue of commands. if no command is+		pending, this command will be written directly to the modem.+		'''+		if self.in_command:+			self.commands.append(command)+		else:+			self.__write(command)+++	def __write(self, command):+		'''+		write a command to the modem.+		'''+		try:+			self.command = command+			if command is not None:+				command = 'AT%s\r\n'% command+				LOG(LOG_DEBUG, __name__, 'write', command.strip())+				self.write(command)+				self.in_command = True+				#sleep(0.03)+		except Exception, e:+			LOG(LOG_ERR, __name__, 'error', e)++	def __read(self, source, condition):+		'''+		this method is called if the fd is readable (as glib tells+		us). it reads one line and interprets it.+		'''+		try:+			line = self.readline().strip()+			self.silence = 0+			if line: # ignore empty lines+				LOG(LOG_DEBUG, __name__, 'read', line.__repr__())+				if line == 'AT%s'% self.command: # ignore my echo...+					pass+				elif line in ('NO CARRIER', 'BUSY', 'ERROR', 'OK', ):+					name = 'gsm%s'% line+					name = name.replace(' ', '_')+					fnctn = getattr(self, name, None)+					if fnctn: fnctn(line)+					else: LOG(LOG_ERR, __name__, 'no fnctn', name, self.command)+					self.in_command = False+				# This only works with charset != IRA as we might get "OK" also as SMS Text,+				# But we would interpret it as OK response above -> Use UCS2 instead!+				elif self._sms_idx >= 0:+					self.CMGL_saveLineToSMS(line)+				elif line[0] == '+' or line[0] == '%': # state responses+					name, values = line[1:].split(': ', 1)+					values = values.split(',')+					if not 'ERROR' in name:+						name = 'gsm%s'% name+						fnctn = getattr(self, name, None)+						if fnctn: fnctn(*values)+						else: LOG(LOG_ERR, __name__, 'no fnctn', name, values)+					else:+						self.__error(self.command, name, *values)+						self.in_command = False+				else:+					LOG(LOG_ERR, __name__, 'read unknown', line.__repr__(), self.command)+					if self.command:+						values = line.split(',')+						name = 'gsm%s'% self.command[1:]+						fnctn = getattr(self, name, None)+						if fnctn: fnctn(*values)+						else: LOG(LOG_ERR, __name__, 'no fnctn', name, values)+			if not self.in_command and self.commands:+				self.__write(self.commands.pop(0))+		except Exception, e:+			LOG(LOG_ERR, __name__, e, line)+		return True+	def __del__(self):+		self.__close()+	### errors+	@notify(DIN_STORAGE, 'av')+	def error(self, texts):+		LOG(LOG_INFO, __name__, texts, 'returned from', self.command)+	def __error(self, *texts):+		self.error(texts)+	####################################################### +	### Called by all dbus methods to check if we are ready+	def check_ready(self):+		# We retrieve all the data beforehand and only check here if we have+		# the latest SMS data:+		if self.ready:+			return+		elif self.pin_required:+			raise NotReady('SIM is locked, needs PIN!')+		elif self.update_in_progress:+			raise Busy('SMS list is in progress of being updated, try again!')+		else:+			raise InternalError('Internal error, not ready but no explanation for it!')+	####################################################### +	# CMGR spec:+	# if text mode (+CMGF=1), command successful and SMS-DELIVER:+	# +CMGR: <stat>,<oa>,[<alpha>],<scts>[,<tooa>,<fo>,<pid>,<dcs>,<sca>,<tosca>,<length>]<CR><LF><data>+	# if text mode (+CMGF=1), command successful and SMS-SUBMIT:+	# +CMGR: <stat>,<da>,[<alpha>][,<toda>,<fo>,<pid>,<dcs>,[<vp>],<sca>,<tosca>,<length>]<CR><LF><data>+	# CMGL spec:+	# if text mode (+CMGF=1), command successful and SMS-SUBMITs and/or SMS-DELIVERs:+	# +CMGL: <index>,<stat>,<oa/da>,[<alpha>],[<scts>][,<tooa/toda>,<length>]<CR><LF><data>[<CR><LF>+	# +CMGL: <index>,<stat>,<da/oa>,[<alpha>],[<scts>][,<tooa/toda>,<length>]<CR><LF><data>[...]]+	####################################################### +	### LIST SMS format as we get it from the modem:+	# ['5', '"REC READ"', '"436644490292"', '', '"08/03/23', '11:45:18+04"', '145', '91']'+	def gsmCMGL(self,index,stat,oada,alph,date,time,tooatoda,length):+		self.update_in_progress = True+		LOG(LOG_INFO, __name__, 'sms metadata:', index,stat,oada,alph,date,time,tooatoda,length)+		# Put the metadata into a dict as we get the message only with the next line:+		if (tooatoda == '145'): # Number should start with "+" (first char of oada is: '"')+			oada = oada.replace('"', '+', 1)+		self._sms = dict(self._sms,+			index  = int(index),+			number = oada.replace('"', ''),+			# FIXME: Result parser may find some "," in the alph from the+			# Phone book here and we may have messed up already!: Fix parser+			# to not look for "," within double-quoted result strings!+			book   = alph.replace('"', ''),+			date   = date.replace('"', ''),+			# FIXME: Convert GSM timezone to common format (+04 => GMT+1 and so on)+			time   = time.replace('"', ''),+			stat   = stat.replace('"', ''),+			)+		self._sms_indices_new.append(index);+		self._sms_idx = index+	####################################################### +	### SMS Helper:+	def CMGL_saveLineToSMS(self, line):+		# We stored the SMS metadata, now add the message and append the+		# complete SMS dict record to the (so far received SMS list):+		LOG(LOG_INFO, __name__, 'got smstext:', line)+		self._sms = dict(self._sms, message=line)+		self._sms_list_new.append([int(self._sms.get('index')), self._sms])+		self._sms_idx = -1+	####################################################### +	### RetrieveEntry: gets the SMS index of an SMS, returns the SMS record+	# TODO: Implement additional org.freesmartphone.Storage parameters:+	#    <arg type="s" name="filter" direction="in" /><!-- later -->+	#	filters may include also: "REC READ", "REC UNREAD", "ALL", ...+	#    <arg type="i" name="limit" direction="in" />+	@method(DIN_STORAGE, 's', 'a{sv}')+	def RetrieveEntry(self, primary_key):+		# TODO: raise defined exception if primary_key does not convert to int:+		index = int(primary_key)+		for sms in self.sms_list:+			if sms[0] == index:+				return sms[1]+		raise EntryNotfound('The requested SMS index could not be found!')+	@method(DIN_STORAGE, '', 'as')+	def ListEntries(self):+# If the main loop were in a separate thread, this could work (in theory, if it was C):+#		while (not self.ready) or self.in_command:+#			LOG(LOG_INFO, __name__, 'sleep a bit....', self.ready, self.in_command)+#			sleep(1)+		self.check_ready()+		return self.sms_indices+	#@method(DIN_STORAGE, 'a{sv}', 's')+	#def StoreEntry(self, sms):+	@method(DIN_STORAGE, 'is', 's')+	def StoreEntry(self, number, text):+		LOG(LOG_INFO, __name__, 'StoreEntry called with:', number, text)+		# For catching SIM not ready, needs to be improved:+		self.check_ready()+#		while (not self.ready) or self.in_command:+#			LOG(LOG_INFO, __name__, 'sleep a bit....', self.ready, self.in_command)+#			sleep(1)+		if self.in_command:+			raise Busy('This should not be possible!!!! AT command in progress, try again!')+		# Or, read the data, store it in a list, and make readline calls from status loop read it++		#number = sms.get('number')+		#text   = sms.get('text')+		#+CMGW[=<oa/da>[,<tooa/toda>[,<stat>]]]<CR>+		#text is entered<ctrl-Z/ESC>+		#self.write('AT+CMGW=%s\r\n%s%c'% (number, text, 26))+		self.write('AT+CMGW=%s\r%s%c'% (number, text, 26))+		line = self.readline() # One Empty line if we sent one line.+		# Responses:+		# +CMGW: <index>+		# +CMS ERROR: <err> e.g.: +CMS ERROR: operation not allowed+		line = self.readline().strip() # Response+		LOG(LOG_INFO, __name__, 'read:', line)+		# FIXME: Test this, it always fails on my Neo with my SIM...+		if line.find('+CMGW:') == -1:+			raise GsmError('Writing SMS failed with:', line)+		primary_key = line.replace('+CMGW:', '').strip()+		return primary_key+	####################################################### +	### STATUS CHECKS+	def gsmCPIN(self, pin_state):+		LOG(LOG_INFO, __name__, 'pin state', pin_state)+		if (pin_state != 'READY'):+			self.pin_required = True+	####################################################### +	### RESPONSE HANDLERS+	# TODO: Add Handler for '+CMS ERROR: SIM PIN required' and set self.pin_required then.+	def gsmOK(self, line):+		#print "OK for command:", command+		if self.command == '+CMGL="ALL"' or self.command == '+CMGF=1;+CSCS="IRA";+CMGL="ALL"':+			self.sms_indices = self._sms_indices_new; # New SMS indices+			self.sms_list    = self._sms_list_new+			self._sms_indices_new = []; # Clean SMS indices+			self._sms_list_new = []+			self.ready = True+			self.update_in_progress = False+	def gsmERROR(self, line):+		self.error(line)+	def gsmCRING(self, state):+		self.error(line)+	def gsmNO_CARRIER(self, line):+		self.error(line)+	def gsmBUSY(self, line):+		self.error(line)++if __name__ == '__main__':+	from syslog import openlog, syslog, closelog, LOG_ERR, LOG_WARNING, LOG_INFO, LOG_DEBUG, LOG_DAEMON, LOG_NDELAY, LOG_PID, LOG_PERROR+	from gobject import MainLoop+	from dbus.mainloop.glib import DBusGMainLoop+	openlog('pygsmd', LOG_NDELAY|LOG_PID|LOG_PERROR, LOG_DAEMON,)+	DBusGMainLoop(set_as_default=True)+	mainloop = MainLoop()+	daemon = GsmSmsStorage(InitBus())+	mainloop.run()+	closelog()+# vim:tw=0:nowrapIndex: pyneod.ini===================================================================--- pyneod.ini	(revision 111)+++ pyneod.ini	(working copy)@@ -6,6 +6,9 @@ [ppp] apn = web.vodafone.de +[sms]+poll_interval = 10+ [gsm] poll_interval = 7