integrate the gsmmodem into project
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import weakref
|
||||
|
||||
from gsmmodem.exceptions import CmeError, InterruptedException, InvalidStateException
|
||||
|
||||
|
||||
class Call(object):
|
||||
""" A voice call """
|
||||
|
||||
DTMF_COMMAND_BASE = '+VTS='
|
||||
dtmfSupport = False # Indicates whether or not DTMF tones can be sent in calls
|
||||
|
||||
def __init__(self, gsmModem, callId, callType, number, callStatusUpdateCallbackFunc=None):
|
||||
"""
|
||||
:param gsmModem: GsmModem instance that created this object
|
||||
:param number: The number that is being called
|
||||
"""
|
||||
self._gsmModem = weakref.proxy(gsmModem)
|
||||
self._callStatusUpdateCallbackFunc = callStatusUpdateCallbackFunc
|
||||
# Unique ID of this call
|
||||
self.id = callId
|
||||
# Call type (VOICE == 0, etc)
|
||||
self.type = callType
|
||||
# The remote number of this call (destination or origin)
|
||||
self.number = number
|
||||
# Flag indicating whether the call has been answered or not (backing field for "answered" property)
|
||||
self._answered = False
|
||||
# Flag indicating whether or not the call is active
|
||||
# (meaning it may be ringing or answered, but not ended because of a hangup event)
|
||||
self.active = True
|
||||
|
||||
@property
|
||||
def answered(self):
|
||||
return self._answered
|
||||
|
||||
@answered.setter
|
||||
def answered(self, answered):
|
||||
self._answered = answered
|
||||
if self._callStatusUpdateCallbackFunc:
|
||||
self._callStatusUpdateCallbackFunc(self)
|
||||
|
||||
def sendDtmfTone(self, tones):
|
||||
""" Send one or more DTMF tones to the remote party (only allowed for an answered call)
|
||||
|
||||
Note: this is highly device-dependent, and might not work
|
||||
|
||||
:param digits: A str containining one or more DTMF tones to play, e.g. "3" or "\*123#"
|
||||
|
||||
:raise CommandError: if the command failed/is not supported
|
||||
:raise InvalidStateException: if the call has not been answered, or is ended while the command is still executing
|
||||
"""
|
||||
if self.answered:
|
||||
dtmfCommandBase = self.DTMF_COMMAND_BASE.format(cid=self.id)
|
||||
toneLen = len(tones)
|
||||
for tone in list(tones):
|
||||
try:
|
||||
self._gsmModem.write('AT{0}{1}'.format(dtmfCommandBase, tone), timeout=(5 + toneLen))
|
||||
|
||||
except CmeError as e:
|
||||
if e.code == 30:
|
||||
# No network service - can happen if call is ended during DTMF transmission (but also if DTMF is sent immediately after call is answered)
|
||||
raise InterruptedException('No network service', e)
|
||||
elif e.code == 3:
|
||||
# Operation not allowed - can happen if call is ended during DTMF transmission
|
||||
raise InterruptedException('Operation not allowed', e)
|
||||
else:
|
||||
raise e
|
||||
else:
|
||||
raise InvalidStateException('Call is not active (it has not yet been answered, or it has ended).')
|
||||
|
||||
def hangup(self):
|
||||
""" End the phone call.
|
||||
|
||||
Does nothing if the call is already inactive.
|
||||
"""
|
||||
if self.active:
|
||||
self._gsmModem.write('ATH')
|
||||
self.answered = False
|
||||
self.active = False
|
||||
if self.id in self._gsmModem.activeCalls:
|
||||
del self._gsmModem.activeCalls[self.id]
|
||||
@@ -0,0 +1,40 @@
|
||||
from gsmmodem.modem import Call
|
||||
|
||||
|
||||
class IncomingCall(Call):
|
||||
|
||||
CALL_TYPE_MAP = {'VOICE': 0}
|
||||
|
||||
""" Represents an incoming call, conveniently allowing access to call meta information and -control """
|
||||
def __init__(self, gsmModem, number, ton, callerName, callId, callType):
|
||||
"""
|
||||
:param gsmModem: GsmModem instance that created this object
|
||||
:param number: Caller number
|
||||
:param ton: TON (type of number/address) in integer format
|
||||
:param callType: Type of the incoming call (VOICE, FAX, DATA, etc)
|
||||
"""
|
||||
if callType in self.CALL_TYPE_MAP:
|
||||
callType = self.CALL_TYPE_MAP[callType]
|
||||
super(IncomingCall, self).__init__(gsmModem, callId, callType, number)
|
||||
# Type attribute of the incoming call
|
||||
self.ton = ton
|
||||
self.callerName = callerName
|
||||
# Flag indicating whether the call is ringing or not
|
||||
self.ringing = True
|
||||
# Amount of times this call has rung (before answer/hangup)
|
||||
self.ringCount = 1
|
||||
|
||||
def answer(self):
|
||||
""" Answer the phone call.
|
||||
:return: self (for chaining method calls)
|
||||
"""
|
||||
if self.ringing:
|
||||
self._gsmModem.write('ATA')
|
||||
self.ringing = False
|
||||
self.answered = True
|
||||
return self
|
||||
|
||||
def hangup(self):
|
||||
""" End the phone call. """
|
||||
self.ringing = False
|
||||
super(IncomingCall, self).hangup()
|
||||
@@ -0,0 +1,27 @@
|
||||
import weakref
|
||||
|
||||
from gsmmodem.models.Sms import Sms
|
||||
|
||||
|
||||
class ReceivedSms(Sms):
|
||||
""" An SMS message that has been received (MT) """
|
||||
|
||||
def __init__(self, gsmModem, status, number, time, text, smsc=None, udh=[], index=None):
|
||||
super(ReceivedSms, self).__init__(number, text, smsc)
|
||||
self._gsmModem = weakref.proxy(gsmModem)
|
||||
self.status = status
|
||||
self.time = time
|
||||
self.udh = udh
|
||||
self.index = index
|
||||
|
||||
def reply(self, message):
|
||||
""" Convenience method that sends a reply SMS to the sender of this message """
|
||||
return self._gsmModem.sendSms(self.number, message)
|
||||
|
||||
def sendSms(self, dnumber, message):
|
||||
""" Convenience method that sends a SMS to someone else """
|
||||
return self._gsmModem.sendSms(dnumber, message)
|
||||
|
||||
def getModem(self):
|
||||
""" Convenience method that returns the gsm modem instance """
|
||||
return self._gsmModem
|
||||
@@ -0,0 +1,27 @@
|
||||
from gsmmodem.models.Sms import Sms
|
||||
from gsmmodem.models.StatusReport import StatusReport
|
||||
|
||||
|
||||
class SentSms(Sms):
|
||||
""" An SMS message that has been sent (MO) """
|
||||
|
||||
ENROUTE = 0 # Status indicating message is still enroute to destination
|
||||
DELIVERED = 1 # Status indicating message has been received by destination handset
|
||||
FAILED = 2 # Status indicating message delivery has failed
|
||||
|
||||
def __init__(self, number, text, reference, smsc=None):
|
||||
super(SentSms, self).__init__(number, text, smsc)
|
||||
self.report = None # Status report for this SMS (StatusReport object)
|
||||
self.reference = reference
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
""" Status of this SMS. Can be ENROUTE, DELIVERED or FAILED
|
||||
|
||||
The actual status report object may be accessed via the 'report' attribute
|
||||
if status is 'DELIVERED' or 'FAILED'
|
||||
"""
|
||||
if self.report == None:
|
||||
return SentSms.ENROUTE
|
||||
else:
|
||||
return SentSms.DELIVERED if self.report.deliveryStatus == StatusReport.DELIVERED else SentSms.FAILED
|
||||
@@ -0,0 +1,24 @@
|
||||
import abc
|
||||
|
||||
|
||||
class Sms(object):
|
||||
""" Abstract SMS message base class """
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
# Some constants to ease handling SMS statuses
|
||||
STATUS_RECEIVED_UNREAD = 0
|
||||
STATUS_RECEIVED_READ = 1
|
||||
STATUS_STORED_UNSENT = 2
|
||||
STATUS_STORED_SENT = 3
|
||||
STATUS_ALL = 4
|
||||
# ...and a handy converter for text mode statuses
|
||||
TEXT_MODE_STATUS_MAP = {'REC UNREAD': STATUS_RECEIVED_UNREAD,
|
||||
'REC READ': STATUS_RECEIVED_READ,
|
||||
'STO UNSENT': STATUS_STORED_UNSENT,
|
||||
'STO SENT': STATUS_STORED_SENT,
|
||||
'ALL': STATUS_ALL}
|
||||
|
||||
def __init__(self, number, text, smsc=None):
|
||||
self.number = number
|
||||
self.text = text
|
||||
self.smsc = smsc
|
||||
@@ -0,0 +1,24 @@
|
||||
import weakref
|
||||
|
||||
from gsmmodem.models.Sms import Sms
|
||||
|
||||
|
||||
class StatusReport(Sms):
|
||||
""" An SMS status/delivery report
|
||||
|
||||
Note: the 'status' attribute of this class refers to this status report SM's status (whether
|
||||
it has been read, etc). To find the status of the message that caused this status report,
|
||||
use the 'deliveryStatus' attribute.
|
||||
"""
|
||||
|
||||
DELIVERED = 0 # SMS delivery status: delivery successful
|
||||
FAILED = 68 # SMS delivery status: delivery failed
|
||||
|
||||
def __init__(self, gsmModem, status, reference, number, timeSent, timeFinalized, deliveryStatus, smsc=None):
|
||||
super(StatusReport, self).__init__(number, None, smsc)
|
||||
self._gsmModem = weakref.proxy(gsmModem)
|
||||
self.status = status
|
||||
self.reference = reference
|
||||
self.timeSent = timeSent
|
||||
self.timeFinalized = timeFinalized
|
||||
self.deliveryStatus = deliveryStatus
|
||||
+21
-223
@@ -2,18 +2,27 @@
|
||||
|
||||
""" High-level API classes for an attached GSM modem """
|
||||
|
||||
import sys, re, logging, weakref, time, threading, abc, codecs
|
||||
from datetime import datetime
|
||||
from time import sleep
|
||||
import logging
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import weakref
|
||||
|
||||
from .serial_comms import SerialComms
|
||||
from .exceptions import CommandError, InvalidStateException, CmeError, CmsError, InterruptedException, TimeoutException, PinRequiredError, IncorrectPinError, SmscNumberUnknownError
|
||||
from .pdu import encodeSmsSubmitPdu, decodeSmsPdu, encodeGsm7, encodeTextMode
|
||||
from .util import SimpleOffsetTzInfo, lineStartingWith, allLinesMatchingPattern, parseTextModeTimeStr, removeAtPrefix
|
||||
|
||||
#from . import compat # For Python 2.6 compatibility
|
||||
from gsmmodem.util import lineMatching
|
||||
from gsmmodem.exceptions import EncodingError
|
||||
from gsmmodem.util import lineMatching
|
||||
from .exceptions import CommandError, InvalidStateException, CmeError, CmsError, TimeoutException, PinRequiredError, \
|
||||
SmscNumberUnknownError
|
||||
from .models.Call import Call
|
||||
from .models.IncomingCall import IncomingCall
|
||||
from .models.ReceivedSms import ReceivedSms
|
||||
from .models.SentSms import SentSms
|
||||
from .models.Sms import Sms
|
||||
from .models.StatusReport import StatusReport
|
||||
from .pdu import encodeSmsSubmitPdu, decodeSmsPdu, encodeGsm7, encodeTextMode
|
||||
from .serial_comms import SerialComms
|
||||
from .util import lineStartingWith, parseTextModeTimeStr, removeAtPrefix
|
||||
|
||||
PYTHON_VERSION = sys.version_info[0]
|
||||
|
||||
CTRLZ = '\x1a'
|
||||
@@ -23,102 +32,6 @@ if PYTHON_VERSION >= 3:
|
||||
xrange = range
|
||||
dictValuesIter = dict.values
|
||||
dictItemsIter = dict.items
|
||||
else: #pragma: no cover
|
||||
dictValuesIter = dict.itervalues
|
||||
dictItemsIter = dict.iteritems
|
||||
|
||||
|
||||
class Sms(object):
|
||||
""" Abstract SMS message base class """
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
# Some constants to ease handling SMS statuses
|
||||
STATUS_RECEIVED_UNREAD = 0
|
||||
STATUS_RECEIVED_READ = 1
|
||||
STATUS_STORED_UNSENT = 2
|
||||
STATUS_STORED_SENT = 3
|
||||
STATUS_ALL = 4
|
||||
# ...and a handy converter for text mode statuses
|
||||
TEXT_MODE_STATUS_MAP = {'REC UNREAD': STATUS_RECEIVED_UNREAD,
|
||||
'REC READ': STATUS_RECEIVED_READ,
|
||||
'STO UNSENT': STATUS_STORED_UNSENT,
|
||||
'STO SENT': STATUS_STORED_SENT,
|
||||
'ALL': STATUS_ALL}
|
||||
|
||||
def __init__(self, number, text, smsc=None):
|
||||
self.number = number
|
||||
self.text = text
|
||||
self.smsc = smsc
|
||||
|
||||
|
||||
class ReceivedSms(Sms):
|
||||
""" An SMS message that has been received (MT) """
|
||||
|
||||
def __init__(self, gsmModem, status, number, time, text, smsc=None, udh=[], index=None):
|
||||
super(ReceivedSms, self).__init__(number, text, smsc)
|
||||
self._gsmModem = weakref.proxy(gsmModem)
|
||||
self.status = status
|
||||
self.time = time
|
||||
self.udh = udh
|
||||
self.index = index
|
||||
|
||||
def reply(self, message):
|
||||
""" Convenience method that sends a reply SMS to the sender of this message """
|
||||
return self._gsmModem.sendSms(self.number, message)
|
||||
|
||||
def sendSms(self, dnumber, message):
|
||||
""" Convenience method that sends a SMS to someone else """
|
||||
return self._gsmModem.sendSms(dnumber, message)
|
||||
|
||||
def getModem(self):
|
||||
""" Convenience method that returns the gsm modem instance """
|
||||
return self._gsmModem
|
||||
|
||||
class SentSms(Sms):
|
||||
""" An SMS message that has been sent (MO) """
|
||||
|
||||
ENROUTE = 0 # Status indicating message is still enroute to destination
|
||||
DELIVERED = 1 # Status indicating message has been received by destination handset
|
||||
FAILED = 2 # Status indicating message delivery has failed
|
||||
|
||||
def __init__(self, number, text, reference, smsc=None):
|
||||
super(SentSms, self).__init__(number, text, smsc)
|
||||
self.report = None # Status report for this SMS (StatusReport object)
|
||||
self.reference = reference
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
""" Status of this SMS. Can be ENROUTE, DELIVERED or FAILED
|
||||
|
||||
The actual status report object may be accessed via the 'report' attribute
|
||||
if status is 'DELIVERED' or 'FAILED'
|
||||
"""
|
||||
if self.report == None:
|
||||
return SentSms.ENROUTE
|
||||
else:
|
||||
return SentSms.DELIVERED if self.report.deliveryStatus == StatusReport.DELIVERED else SentSms.FAILED
|
||||
|
||||
|
||||
class StatusReport(Sms):
|
||||
""" An SMS status/delivery report
|
||||
|
||||
Note: the 'status' attribute of this class refers to this status report SM's status (whether
|
||||
it has been read, etc). To find the status of the message that caused this status report,
|
||||
use the 'deliveryStatus' attribute.
|
||||
"""
|
||||
|
||||
DELIVERED = 0 # SMS delivery status: delivery successful
|
||||
FAILED = 68 # SMS delivery status: delivery failed
|
||||
|
||||
def __init__(self, gsmModem, status, reference, number, timeSent, timeFinalized, deliveryStatus, smsc=None):
|
||||
super(StatusReport, self).__init__(number, None, smsc)
|
||||
self._gsmModem = weakref.proxy(gsmModem)
|
||||
self.status = status
|
||||
self.reference = reference
|
||||
self.timeSent = timeSent
|
||||
self.timeFinalized = timeFinalized
|
||||
self.deliveryStatus = deliveryStatus
|
||||
|
||||
|
||||
class GsmModem(SerialComms):
|
||||
""" Main class for interacting with an attached GSM modem """
|
||||
@@ -434,7 +347,7 @@ class GsmModem(SerialComms):
|
||||
else:
|
||||
raise PinRequiredError('AT+CPIN')
|
||||
|
||||
def write(self, data, waitForResponse=True, timeout=10, parseError=True, writeTerm=TERMINATOR, expectedResponseTermSeq=None):
|
||||
def write(self, data, waitForResponse=True, timeout:float =10, parseError=True, writeTerm=TERMINATOR, expectedResponseTermSeq=None):
|
||||
""" Write data to the modem.
|
||||
|
||||
This method adds the ``\\r\\n`` end-of-line sequence to the data parameter, and
|
||||
@@ -1290,7 +1203,7 @@ class GsmModem(SerialComms):
|
||||
call = activeCall
|
||||
call.ringCount += 1
|
||||
if call == None:
|
||||
callId = len(self.activeCalls) + 1;
|
||||
callId = len(self.activeCalls) + 1
|
||||
call = IncomingCall(self, callerNumber, ton, callerName, callId, callType)
|
||||
self.activeCalls[callId] = call
|
||||
self.incomingCallCallback(call)
|
||||
@@ -1601,121 +1514,6 @@ class GsmModem(SerialComms):
|
||||
if timeLeft <= 0:
|
||||
raise TimeoutException()
|
||||
|
||||
|
||||
class Call(object):
|
||||
""" A voice call """
|
||||
|
||||
DTMF_COMMAND_BASE = '+VTS='
|
||||
dtmfSupport = False # Indicates whether or not DTMF tones can be sent in calls
|
||||
|
||||
def __init__(self, gsmModem, callId, callType, number, callStatusUpdateCallbackFunc=None):
|
||||
"""
|
||||
:param gsmModem: GsmModem instance that created this object
|
||||
:param number: The number that is being called
|
||||
"""
|
||||
self._gsmModem = weakref.proxy(gsmModem)
|
||||
self._callStatusUpdateCallbackFunc = callStatusUpdateCallbackFunc
|
||||
# Unique ID of this call
|
||||
self.id = callId
|
||||
# Call type (VOICE == 0, etc)
|
||||
self.type = callType
|
||||
# The remote number of this call (destination or origin)
|
||||
self.number = number
|
||||
# Flag indicating whether the call has been answered or not (backing field for "answered" property)
|
||||
self._answered = False
|
||||
# Flag indicating whether or not the call is active
|
||||
# (meaning it may be ringing or answered, but not ended because of a hangup event)
|
||||
self.active = True
|
||||
|
||||
@property
|
||||
def answered(self):
|
||||
return self._answered
|
||||
@answered.setter
|
||||
def answered(self, answered):
|
||||
self._answered = answered
|
||||
if self._callStatusUpdateCallbackFunc:
|
||||
self._callStatusUpdateCallbackFunc(self)
|
||||
|
||||
def sendDtmfTone(self, tones):
|
||||
""" Send one or more DTMF tones to the remote party (only allowed for an answered call)
|
||||
|
||||
Note: this is highly device-dependent, and might not work
|
||||
|
||||
:param digits: A str containining one or more DTMF tones to play, e.g. "3" or "\*123#"
|
||||
|
||||
:raise CommandError: if the command failed/is not supported
|
||||
:raise InvalidStateException: if the call has not been answered, or is ended while the command is still executing
|
||||
"""
|
||||
if self.answered:
|
||||
dtmfCommandBase = self.DTMF_COMMAND_BASE.format(cid=self.id)
|
||||
toneLen = len(tones)
|
||||
for tone in list(tones):
|
||||
try:
|
||||
self._gsmModem.write('AT{0}{1}'.format(dtmfCommandBase,tone), timeout=(5 + toneLen))
|
||||
|
||||
except CmeError as e:
|
||||
if e.code == 30:
|
||||
# No network service - can happen if call is ended during DTMF transmission (but also if DTMF is sent immediately after call is answered)
|
||||
raise InterruptedException('No network service', e)
|
||||
elif e.code == 3:
|
||||
# Operation not allowed - can happen if call is ended during DTMF transmission
|
||||
raise InterruptedException('Operation not allowed', e)
|
||||
else:
|
||||
raise e
|
||||
else:
|
||||
raise InvalidStateException('Call is not active (it has not yet been answered, or it has ended).')
|
||||
|
||||
def hangup(self):
|
||||
""" End the phone call.
|
||||
|
||||
Does nothing if the call is already inactive.
|
||||
"""
|
||||
if self.active:
|
||||
self._gsmModem.write('ATH')
|
||||
self.answered = False
|
||||
self.active = False
|
||||
if self.id in self._gsmModem.activeCalls:
|
||||
del self._gsmModem.activeCalls[self.id]
|
||||
|
||||
|
||||
class IncomingCall(Call):
|
||||
|
||||
CALL_TYPE_MAP = {'VOICE': 0}
|
||||
|
||||
""" Represents an incoming call, conveniently allowing access to call meta information and -control """
|
||||
def __init__(self, gsmModem, number, ton, callerName, callId, callType):
|
||||
"""
|
||||
:param gsmModem: GsmModem instance that created this object
|
||||
:param number: Caller number
|
||||
:param ton: TON (type of number/address) in integer format
|
||||
:param callType: Type of the incoming call (VOICE, FAX, DATA, etc)
|
||||
"""
|
||||
if callType in self.CALL_TYPE_MAP:
|
||||
callType = self.CALL_TYPE_MAP[callType]
|
||||
super(IncomingCall, self).__init__(gsmModem, callId, callType, number)
|
||||
# Type attribute of the incoming call
|
||||
self.ton = ton
|
||||
self.callerName = callerName
|
||||
# Flag indicating whether the call is ringing or not
|
||||
self.ringing = True
|
||||
# Amount of times this call has rung (before answer/hangup)
|
||||
self.ringCount = 1
|
||||
|
||||
def answer(self):
|
||||
""" Answer the phone call.
|
||||
:return: self (for chaining method calls)
|
||||
"""
|
||||
if self.ringing:
|
||||
self._gsmModem.write('ATA')
|
||||
self.ringing = False
|
||||
self.answered = True
|
||||
return self
|
||||
|
||||
def hangup(self):
|
||||
""" End the phone call. """
|
||||
self.ringing = False
|
||||
super(IncomingCall, self).hangup()
|
||||
|
||||
class Ussd(object):
|
||||
""" Unstructured Supplementary Service Data (USSD) message.
|
||||
|
||||
|
||||
@@ -18,11 +18,6 @@ if PYTHON_VERSION >= 3:
|
||||
unichr = chr
|
||||
toByteArray = lambda x: bytearray(codecs.decode(x, 'hex_codec')) if type(x) == bytes else bytearray(codecs.decode(bytes(x, 'ascii'), 'hex_codec')) if type(x) == str else x
|
||||
rawStrToByteArray = lambda x: bytearray(bytes(x, 'latin-1'))
|
||||
else: #pragma: no cover
|
||||
MAX_INT = sys.maxint
|
||||
dictItemsIter = dict.iteritems
|
||||
toByteArray = lambda x: bytearray(x.decode('hex')) if type(x) in (str, unicode) else x
|
||||
rawStrToByteArray = bytearray
|
||||
|
||||
TEXT_MODE = ('\n\r !\"#%&\'()*+,-./0123456789:;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz') # TODO: Check if all of them are supported inside text mode
|
||||
# Tables can be found at: http://en.wikipedia.org/wiki/GSM_03.38#GSM_7_bit_default_alphabet_and_extension_table_of_3GPP_TS_23.038_.2F_GSM_03.38
|
||||
|
||||
Reference in New Issue
Block a user