def connect(self, reconnect=False): """Creates the connection to the modem via pySerial, optionally killing and re-creating any existing connection.""" self._log("Connecting") # if no connection exists, create it # the reconnect flag is irrelevant if not hasattr(self, "device") or (self.device is None): with self.modem_lock: if 'rtscts' in self.device_kwargs: self.device_kwargs['rtscts'] = int(self.device_kwargs['rtscts']) self.device = DeviceWrapper( self.logger, *self.device_args, **self.device_kwargs) # the port already exists, but if we're # reconnecting, then kill it and recurse # to recreate it. this is useful when the # connection has died, but nobody noticed elif reconnect: self.disconnect() self.connect(False) return self.device
def connect(self, reconnect=False): """Creates the connection to the modem via pySerial, optionally killing and re-creating any existing connection.""" self._log("Connecting") # if no connection exists, create it # the reconnect flag is irrelevant if not hasattr(self, "device") or (self.device is None): with self.modem_lock: self.device = DeviceWrapper(self.logger, *self.device_args, **self.device_kwargs) # the port already exists, but if we're # reconnecting, then kill it and recurse # to recreate it. this is useful when the # connection has died, but nobody noticed elif reconnect: self.disconnect() self.connect(False) return self.device
class GsmModem(object): """pyGSM is a Python module which uses pySerial to provide a nifty interface to send and receive SMS via a GSM Modem. It was ported from RubyGSM, and provides (almost) all of the same features. It's easy to get started: # create a GsmModem object: >>> import pygsm >>> modem = pygsm.GsmModem(port="/dev/ttyUSB0") # harass Evan over SMS: # (try to do this before 11AM) >>> modem.send_sms("+13364130840", "Hey, wake up!") # check for incoming SMS: >>> print modem.next_message() <pygsm.IncomingMessage from +13364130840: "Leave me alone!"> There are various ways of polling for incoming messages -- a choice which has been deliberately left to the application author (unlike RubyGSM). Execute `python -m pygsm.gsmmodem` to run this example: # connect to the modem modem = pygsm.GsmModem(port=sys.argv[1]) # check for new messages every two # seconds for the rest of forever while True: msg = modem.next_message() # we got a message! respond with # something useless, as an example if msg is not None: msg.respond("Thanks for those %d characters!" % len(msg.text)) # no messages? wait a couple # of seconds and try again else: time.sleep(2) pyGSM is distributed via GitHub: http://github.com/adammck/pygsm Bugs reports (especially for unsupported devices) are welcome: http://github.com/adammck/pygsm/issues""" # override these after init, and # before boot. they're not sanity # checked, so go crazy. cmd_delay = 0.1 retry_delay = 2 max_retries = 10 modem_lock = threading.RLock() def __init__(self, *args, **kwargs): """Creates, connects to, and boots a GSM Modem. All of the arguments are optional (although "port=" should almost always be provided), and passed along to serial.Serial.__init__ verbatim. For all of the possible configration options, see: http://pyserial.wiki.sourceforge.net/pySerial#tocpySerial10 Alternatively, a single "device" kwarg can be passed, which overrides the default proxy-args-to-pySerial behavior. This is useful when testing, or wrapping the serial connection with some custom logic.""" if "logger" in kwargs: self.logger = kwargs.pop("logger") mode = "PDU" if "mode" in kwargs: mode = kwargs.pop("mode") # if a ready-made device was provided, store it -- self.connect # will see that we're already connected, and do nothing. we'll # just assume it quacks like a serial port if "device" in kwargs: self.device = kwargs.pop("device") # if a device is given, the other args are never # used, so were probably included by mistake. if len(args) or len(kwargs): raise (TypeError( "__init__() does not accept other arguments when a 'device' is given" )) # for regular serial connections, store the connection args, since # we might need to recreate the serial connection again later else: self.device_args = args self.device_kwargs = kwargs # to cache parts of multi-part messages # until the last part is delivered self.multipart = {} # to store unhandled incoming messages self.incoming_queue = [] if mode.lower() == "text": self.smshandler = TextSmsHandler(self) else: self.smshandler = PduSmsHandler(self) # boot the device on init, to fail as # early as possible if it can't be opened self.boot() LOG_LEVELS = { "traffic": 4, "read": 4, "write": 4, "debug": 3, "warn": 2, "error": 1 } def _log(self, str_, type_="debug"): """Proxies a log message to this Modem's logger, if one has been set. This is useful for applications embedding pyGSM that wish to show or log what's going on inside. The *logger* should be a function with three arguments: modem: a reference to this GsmModem instance message: the log message (a unicode string) type: a string contaning one of the keys of GsmModem.LOG_LEVELS, indicating the importance of this message. GsmModem.__init__ accepts an optional "logger" kwarg, and a minimal (dump to STDOUT) logger is available at GsmModem.logger: >>> GsmModem("/dev/ttyUSB0", logger=GsmModem.logger)""" if hasattr(self, "logger"): self.logger(self, str_, type_) @staticmethod def logger(_modem, message_, type_): print "%8s %s" % (type_, message_) def connect(self, reconnect=False): """Creates the connection to the modem via pySerial, optionally killing and re-creating any existing connection.""" self._log("Connecting") # if no connection exists, create it # the reconnect flag is irrelevant if not hasattr(self, "device") or (self.device is None): with self.modem_lock: self.device = DeviceWrapper(self.logger, *self.device_args, **self.device_kwargs) # the port already exists, but if we're # reconnecting, then kill it and recurse # to recreate it. this is useful when the # connection has died, but nobody noticed elif reconnect: self.disconnect() self.connect(False) return self.device def disconnect(self): """Disconnects from the modem.""" self._log("Disconnecting") # attempt to close and destroy the device if hasattr(self, "device") and (self.device is None): with self.modem_lock: if self.device.isOpen(): self.device.close() self.device = None return True # for some reason, the device # couldn't be closed. it probably # just isn't open yet return False def set_modem_config(self): """initialize the modem configuration with settings needed to process commands and send/receive SMS. """ # set some sensible defaults, to make # the various modems more consistant self.command("ATE0", raise_errors=False) # echo off self.command("AT+CMEE=1", raise_errors=False) # useful error messages self.command("AT+WIND=0", raise_errors=False) # disable notifications self.command("AT+CSMS=1", raise_errors=False) # set SMS mode to phase 2+ self.command(self.smshandler.get_mode_cmd()) # make sure in PDU mode # enable new message notification self.command("AT+CNMI=2,2,0,0,0", raise_errors=False) def boot(self, reboot=False): """Initializes the modem. Must be called after init and connect, but before doing anything that expects the modem to be ready.""" self._log("Booting") if reboot: # If reboot==True, force a reconnection and full modem reset. SLOW self.connect(reconnect=True) self.command("AT+CFUN=1") else: # else just verify connection self.connect() # In both cases, reset the modem's config self.set_modem_config() # And check for any waiting messages PRIOR to setting # the CNMI call--this is not supported by all modems-- # in which case we catch the exception and plow onward try: self._fetch_stored_messages() except errors.GsmError: pass def reboot(self): """Forces a reconnect to the serial port and then a full modem reset to factory and reconnect to GSM network. SLOW. """ self.boot(reboot=True) def _write(self, str): """Write a string to the modem.""" self._log(repr(str), "write") try: self.device.write(str) # if the device couldn't be written to, # wrap the error in something that can # sensibly be caught at a higher level except OSError, err: raise (errors.GsmWriteError)
class GsmModem(object): """pyGSM is a Python module which uses pySerial to provide a nifty interface to send and receive SMS via a GSM Modem. It was ported from RubyGSM, and provides (almost) all of the same features. It's easy to get started: # create a GsmModem object: >>> import pygsm >>> modem = pygsm.GsmModem(port="/dev/ttyUSB0") # harass Evan over SMS: # (try to do this before 11AM) >>> modem.send_sms("+13364130840", "Hey, wake up!") # check for incoming SMS: >>> print modem.next_message() <pygsm.IncomingMessage from +13364130840: "Leave me alone!"> There are various ways of polling for incoming messages -- a choice which has been deliberately left to the application author (unlike RubyGSM). Execute `python -m pygsm.gsmmodem` to run this example: # connect to the modem modem = pygsm.GsmModem(port=sys.argv[1]) # check for new messages every two # seconds for the rest of forever while True: msg = modem.next_message() # we got a message! respond with # something useless, as an example if msg is not None: msg.respond("Thanks for those %d characters!" % len(msg.text)) # no messages? wait a couple # of seconds and try again else: time.sleep(2) pyGSM is distributed via GitHub: http://github.com/adammck/pygsm Bugs reports (especially for unsupported devices) are welcome: http://github.com/adammck/pygsm/issues""" # override these after init, and # before boot. they're not sanity # checked, so go crazy. cmd_delay = 0.1 retry_delay = 2 max_retries = 10 modem_lock = threading.RLock() modem_type = "" def __init__(self, *args, **kwargs): """Creates, connects to, and boots a GSM Modem. All of the arguments are optional (although "port=" should almost always be provided), and passed along to serial.Serial.__init__ verbatim. For all of the possible configration options, see: http://pyserial.wiki.sourceforge.net/pySerial#tocpySerial10 Alternatively, a single "device" kwarg can be passed, which overrides the default proxy-args-to-pySerial behavior. This is useful when testing, or wrapping the serial connection with some custom logic.""" if "logger" in kwargs: self.logger = kwargs.pop("logger") mode = "PDU" if "mode" in kwargs: mode = kwargs.pop("mode") # if a ready-made device was provided, store it -- self.connect # will see that we're already connected, and do nothing. we'll # just assume it quacks like a serial port if "device" in kwargs: self.device = kwargs.pop("device") # if a device is given, the other args are never # used, so were probably included by mistake. if len(args) or len(kwargs): raise(TypeError("__init__() does not accept other arguments when a 'device' is given")) # for regular serial connections, store the connection args, since # we might need to recreate the serial connection again later else: self.device_args = args self.device_kwargs = kwargs # to cache parts of multi-part messages # until the last part is delivered self.multipart = {} # to store unhandled incoming messages self.incoming_queue = [] if mode.lower() == "text": self.smshandler = TextSmsHandler(self) else: self.smshandler = PduSmsHandler(self) # boot the device on init, to fail as # early as possible if it can't be opened self.boot() LOG_LEVELS = { "traffic": 4, "read": 4, "write": 4, "debug": 3, "warn": 2, "error": 1 } def _log(self, str_, type_="debug"): """Proxies a log message to this Modem's logger, if one has been set. This is useful for applications embedding pyGSM that wish to show or log what's going on inside. The *logger* should be a function with three arguments: modem: a reference to this GsmModem instance message: the log message (a unicode string) type: a string contaning one of the keys of GsmModem.LOG_LEVELS, indicating the importance of this message. GsmModem.__init__ accepts an optional "logger" kwarg, and a minimal (dump to STDOUT) logger is available at GsmModem.logger: >>> GsmModem("/dev/ttyUSB0", logger=GsmModem.logger)""" if hasattr(self, "logger"): self.logger(self, str_, type_) @staticmethod def logger(_modem, message_, type_): print "%8s %s" % (type_, message_) def connect(self, reconnect=False): """Creates the connection to the modem via pySerial, optionally killing and re-creating any existing connection.""" self._log("Connecting") # if no connection exists, create it # the reconnect flag is irrelevant if not hasattr(self, "device") or (self.device is None): with self.modem_lock: self.device = DeviceWrapper( self.logger, *self.device_args, **self.device_kwargs) # the port already exists, but if we're # reconnecting, then kill it and recurse # to recreate it. this is useful when the # connection has died, but nobody noticed elif reconnect: self.disconnect() self.connect(False) return self.device def disconnect(self): """Disconnects from the modem.""" self._log("Disconnecting") # attempt to close and destroy the device if hasattr(self, "device") and (self.device is not None): with self.modem_lock: if self.device.isOpen(): self.device.close() self.device = None return True # for some reason, the device # couldn't be closed. it probably # just isn't open yet return False def set_modem_config(self): """initialize the modem configuration with settings needed to process commands and send/receive SMS. """ # set some sensible defaults, to make # the various modems more consistant self.command("ATE0", raise_errors=False) # echo off #sniff the modem type modem_type = self.query("AT+CGMM") self._log("Modem type: " + modem_type) self.command("AT+CMEE=1", raise_errors=False) # useful error messages self.command("AT+CSMS=1", raise_errors=False) # set SMS mode to phase 2+ if (modem_type == "TC35i"): cnmi_command = "AT+CNMI=2,2,0,0,1" # according to the TC35i documentation the last parameter of # AT+CNMI can only be one. It will error on it and you will # not receive sms messages if it's not set like this. else: self.command("AT+WIND=0", raise_errors=False) # disable notifications, on the TC35i this command # does not exsist cnmi_command = "AT+CNMI=2,2,0,0,0" self.command(self.smshandler.get_mode_cmd() ) # make sure in PDU mode self.command(cnmi_command, raise_errors=False) # enable new message notification def boot(self, reboot=False): """Initializes the modem. Must be called after init and connect, but before doing anything that expects the modem to be ready.""" self._log("Booting") if reboot: # If reboot==True, force a reconnection and full modem reset. SLOW self.connect(reconnect=True) self.command("AT+CFUN=1") else: # else just verify connection self.connect() # In both cases, reset the modem's config self.set_modem_config() # And check for any waiting messages PRIOR to setting # the CNMI call--this is not supported by all modems-- # in which case we catch the exception and plow onward try: self._fetch_stored_messages() except errors.GsmError: pass def reboot(self): """Forces a reconnect to the serial port and then a full modem reset to factory and reconnect to GSM network. SLOW. """ self.boot(reboot=True) def _write(self, str): """Write a string to the modem.""" self._log(repr(str), "write") try: self.device.write(str) # if the device couldn't be written to, # wrap the error in something that can # sensibly be caught at a higher level except OSError, err: raise(errors.GsmWriteError)