commit 10/22/2015
This commit is contained in:
652
lib/sim900/gsm.py
Normal file
652
lib/sim900/gsm.py
Normal file
@@ -0,0 +1,652 @@
|
||||
#The MIT License (MIT)
|
||||
#
|
||||
#Copyright (c) 2014-2015 Bohdan Danishevsky ( dbn@aminis.com.ua )
|
||||
#
|
||||
#Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
#of this software and associated documentation files (the "Software"), to deal
|
||||
#in the Software without restriction, including without limitation the rights
|
||||
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
#copies of the Software, and to permit persons to whom the Software is
|
||||
#furnished to do so, subject to the following conditions:
|
||||
#
|
||||
#The above copyright notice and this permission notice shall be included in all
|
||||
#copies or substantial portions of the Software.
|
||||
#
|
||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
#SOFTWARE.
|
||||
|
||||
"""
|
||||
This file is part of sim-module package. Implements basic functions of SIM900 modules.
|
||||
|
||||
sim-module package allows to communicate with SIM 900 modules: send SMS, make HTTP requests and use other
|
||||
functions of SIM 900 modules.
|
||||
|
||||
Copyright (C) 2014-2015 Bohdan Danishevsky ( dbn@aminis.com.ua ) All Rights Reserved.
|
||||
"""
|
||||
|
||||
import time
|
||||
import serial
|
||||
import logging
|
||||
from lib.sim900.simshared import *
|
||||
|
||||
class GsmSpecialCharacters:
|
||||
ctrlz = 26 #//Ascii character for ctr+z. End of a SMS.
|
||||
cr = 0x0d #//Ascii character for carriage return.
|
||||
lf = 0x0a #//Ascii character for line feed.
|
||||
|
||||
class SimGsmState:
|
||||
UNKNOWN = 0
|
||||
ERROR = 1
|
||||
IDLE = 2
|
||||
READY = 3
|
||||
ATTACHED = 4
|
||||
TCPSERVERWAIT = 5
|
||||
TCPCONNECTEDSERVER = 6
|
||||
TCPCONNECTEDCLIENT = 7
|
||||
|
||||
|
||||
class SimGsmPinRequestState:
|
||||
UNKNOWN = -1
|
||||
NOPINNEEDED = 0
|
||||
|
||||
SIM_PIN = 1
|
||||
SIM_PUK = 2
|
||||
|
||||
PH_SIM_PIN = 3
|
||||
PH_SIM_PUK = 4
|
||||
|
||||
SIM_PIN2 = 5
|
||||
SIM_PUK2 = 6
|
||||
|
||||
class SimGsmSerialPortHandler(AminisLastErrorHolderWithLogging):
|
||||
def __init__(self, serial, logger = None):
|
||||
AminisLastErrorHolderWithLogging.__init__(self, logger)
|
||||
self.input = bytearray()
|
||||
self.__serial = serial
|
||||
|
||||
#stores last executed command result
|
||||
self.lastResult = None
|
||||
|
||||
def openPort(self):
|
||||
try:
|
||||
self.__serial.open()
|
||||
self.flush()
|
||||
except Exception as e:
|
||||
self.setError("exception till port openning: {0}".format(e))
|
||||
return False
|
||||
except:
|
||||
self.setError("error opening port")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def __sendRawBytes(self, data, maxWaitTime = 1000):
|
||||
"""
|
||||
Sends raw bytes to the SIM module
|
||||
|
||||
:param data: data which must be send
|
||||
:param maxWaitTime: max wait time for sending sequence
|
||||
:return: True if data was send, otherwise returns False
|
||||
"""
|
||||
bytesToSend = len(data)
|
||||
sentBytes = 0
|
||||
start = time.time()
|
||||
|
||||
self.logger.debug("{0}, sending: {1}".format(inspect.stack()[0][3], data))
|
||||
|
||||
while sentBytes < bytesToSend:
|
||||
if timeDelta(start) >= maxWaitTime:
|
||||
self.setWarn("__sendRawBytes(): timed out")
|
||||
return False
|
||||
|
||||
sentBytes += self.__serial.write(data[sentBytes : ])
|
||||
if sentBytes == 0:
|
||||
time.sleep(0.001)
|
||||
continue
|
||||
|
||||
return True
|
||||
|
||||
def print(self, commandString, encoding = "ascii"):
|
||||
"""
|
||||
Sends string data to the SIM module
|
||||
|
||||
:param commandString: data what must be sent
|
||||
:param encoding: before sending string it will be converted to the bytearray with this encoding
|
||||
:return: True if everything is OK, otherwise returns false
|
||||
"""
|
||||
data = bytearray(commandString, encoding)
|
||||
return self.__sendRawBytes(data)
|
||||
|
||||
def simpleWrite(self, commandLine, encoding = "ascii"):
|
||||
"""
|
||||
Just alias for print() method
|
||||
|
||||
:param commandLine: data which must be sent
|
||||
:param encoding: before sending string it will be converted to the bytearray with this encoding
|
||||
:return: True if data sent, otherwise returns False
|
||||
"""
|
||||
return self.print(commandLine, encoding)
|
||||
|
||||
def printLn(self, commandString, encoding = "ascii"):
|
||||
"""
|
||||
Sends string data and CR/LF in the end to the SIM module
|
||||
|
||||
:param commandString: data which must be sent
|
||||
:param encoding: before sending string it will be converted to the bytearray with this encoding
|
||||
:return: True if data sent, otherwise returns False
|
||||
"""
|
||||
data = bytearray(commandString, encoding) + bytearray([GsmSpecialCharacters.cr, GsmSpecialCharacters.lf])
|
||||
return self.__sendRawBytes(data)
|
||||
|
||||
def simpleWriteLn(self, commandLine, encoding = "ascii"):
|
||||
"""
|
||||
Just alias for printLn() method
|
||||
|
||||
:param commandLine: data which must be sent
|
||||
:param encoding: before sending string it will be converted to the bytearray with this encoding
|
||||
:return: True if data sent, otherwise returns False
|
||||
"""
|
||||
|
||||
return self.printLn(commandLine, encoding)
|
||||
|
||||
def flushInput(self):
|
||||
"""
|
||||
Flushes input buffer
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
try:
|
||||
self.__serial.flushInput()
|
||||
except Exception as e:
|
||||
self.setError("error flushing: {0}".format(e))
|
||||
except:
|
||||
self.setError("error flushing")
|
||||
|
||||
def flushOutput(self):
|
||||
"""
|
||||
Flushes output buffer
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
try:
|
||||
self.__serial.flushOutput()
|
||||
except Exception as e:
|
||||
self.setError("error flushing: {0}".format(e))
|
||||
except:
|
||||
self.setError("error flushing")
|
||||
|
||||
def readFixedSzieByteArray(self, bytesCount, maxWaitTime):
|
||||
start = time.time()
|
||||
buffer = bytearray()
|
||||
try:
|
||||
while True:
|
||||
#checking for timeout
|
||||
if timeDelta(start) >= maxWaitTime:
|
||||
return None
|
||||
|
||||
receivedBytesQty = 0
|
||||
while True:
|
||||
bytesToRead = 10 if ((bytesCount - len(buffer)) >= 10) else 1
|
||||
b = self.__serial.read(bytesToRead)
|
||||
|
||||
if (b is None) or (len(b) == 0):
|
||||
break
|
||||
|
||||
buffer += bytearray(b)
|
||||
receivedBytesQty += len(b)
|
||||
|
||||
if len(buffer) == bytesCount:
|
||||
return buffer
|
||||
|
||||
#if we have nothing in input - let's go sleep for some time
|
||||
if receivedBytesQty == 0:
|
||||
time.sleep(0.003)
|
||||
|
||||
#comming there by timeout
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
self.setError(e)
|
||||
return None
|
||||
except:
|
||||
self.setError("reading error...")
|
||||
return None
|
||||
|
||||
|
||||
def readNullTerminatedLn(self, maxWaitTime = 5000, codepage = "ascii"):
|
||||
start = time.time()
|
||||
|
||||
start = time.time()
|
||||
buffer = bytearray()
|
||||
try:
|
||||
while True:
|
||||
#checking for timeout
|
||||
if timeDelta(start) >= maxWaitTime:
|
||||
return None
|
||||
|
||||
receivedBytesQty = 0
|
||||
while True:
|
||||
b = self.__serial.read(1)
|
||||
|
||||
if (b is None) or (len(b) == 0):
|
||||
break
|
||||
|
||||
#checking that we have NULL symbol in
|
||||
idx = b.find(0x00)
|
||||
if idx != -1:
|
||||
buffer.extend(b[:idx])
|
||||
return buffer.decode(codepage)
|
||||
|
||||
buffer += bytearray(b)
|
||||
receivedBytesQty += len(b)
|
||||
|
||||
#if we have nothing in input - let's go sleep for some time
|
||||
if receivedBytesQty == 0:
|
||||
time.sleep(0.003)
|
||||
|
||||
#comming there by timeout
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
self.setError(e)
|
||||
return None
|
||||
except:
|
||||
self.setError("reading error...")
|
||||
return None
|
||||
|
||||
def readLn(self, maxWaitTime = 5000, codepage = "ascii"):
|
||||
"""
|
||||
Returns text string from SIM module. Can return even empty strings.
|
||||
|
||||
:param maxWaitTime: max wait interval for operation
|
||||
:param codepage: code page of result string
|
||||
:return: received string
|
||||
"""
|
||||
start = time.time()
|
||||
buffer = bytearray()
|
||||
try:
|
||||
while True:
|
||||
#checking for timeout
|
||||
if timeDelta(start) >= maxWaitTime:
|
||||
return None
|
||||
|
||||
receivedBytesQty = 0
|
||||
while True:
|
||||
b = self.__serial.read(1)
|
||||
|
||||
if (b is None) or (len(b) == 0):
|
||||
break
|
||||
|
||||
buffer += bytearray(b)
|
||||
receivedBytesQty += len(b)
|
||||
|
||||
if codepage is not None:
|
||||
#checking for line end symbols
|
||||
line = buffer.decode(codepage)
|
||||
if '\n' in line:
|
||||
return line.strip()
|
||||
elif ord('\n') in buffer:
|
||||
return buffer
|
||||
|
||||
#if we have nothing in input - let's go sleep for some time
|
||||
if receivedBytesQty == 0:
|
||||
time.sleep(0)
|
||||
|
||||
#comming there by timeout
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
self.setError(e)
|
||||
return None
|
||||
except:
|
||||
self.setError("reading error...")
|
||||
return None
|
||||
|
||||
def readDataLine(self, maxWaitTime = 500, codepage = "ascii"):
|
||||
"""
|
||||
Returns non empty data string. So, if it will receive empty string function will continue non empty string
|
||||
retrieving
|
||||
|
||||
:param maxWaitTime: max wait time for receiving
|
||||
:param codepage: code page of result string, if it's a None - will return a bytearray
|
||||
:return: received string
|
||||
"""
|
||||
ret = None
|
||||
start = time.time()
|
||||
|
||||
while True:
|
||||
#checking for timeout
|
||||
if timeDelta(start) >= maxWaitTime:
|
||||
break
|
||||
|
||||
#reading string
|
||||
#TODO: need to fix timeout (substract already spent time interval)
|
||||
line = self.readLn(maxWaitTime, codepage)
|
||||
|
||||
#removing garbage symbols
|
||||
if line is not None:
|
||||
line = str(line).strip()
|
||||
|
||||
#if we have non empty string let's return it
|
||||
if len(line) > 0:
|
||||
return line
|
||||
else:
|
||||
#if we have empty line - let's continue reading
|
||||
continue
|
||||
else:
|
||||
#returning None if None received
|
||||
if line is None:
|
||||
return None
|
||||
|
||||
continue
|
||||
|
||||
#we will come here by timeout
|
||||
return None
|
||||
|
||||
def flush(self):
|
||||
"""
|
||||
Flushes input and output buffers
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
try:
|
||||
self.__serial.flush()
|
||||
except Exception as e:
|
||||
self.setError("error flushing: {0}".format(e))
|
||||
except:
|
||||
self.setError("error flushing")
|
||||
|
||||
def closePort(self):
|
||||
"""
|
||||
Closes COM port
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
try:
|
||||
self.__serial.close()
|
||||
except Exception as e:
|
||||
self.setError("error closing port: {0}".format(e))
|
||||
except:
|
||||
self.setError("error closing port")
|
||||
|
||||
@staticmethod
|
||||
def isCrLf(symbol):
|
||||
"""
|
||||
Returns True when parameter is CR/LF symbol, otherwise returns False
|
||||
|
||||
:param symbol: symbol for analysis
|
||||
:return: True when CR/LF symbol, otherwise returns False
|
||||
"""
|
||||
return (symbol == GsmSpecialCharacters.cr) or (symbol == GsmSpecialCharacters.lf)
|
||||
|
||||
@staticmethod
|
||||
def getLastNonEmptyString(strings):
|
||||
"""
|
||||
Parses strings array and returns last non empty string from array
|
||||
|
||||
:param strings: strings array for analysis
|
||||
:return: last non empty string, otherwise None
|
||||
"""
|
||||
|
||||
#if there is no data - returning None
|
||||
if strings is None:
|
||||
return None
|
||||
|
||||
qty = len(strings)
|
||||
if qty == 0:
|
||||
return None
|
||||
|
||||
#looking for last non empty string
|
||||
for i in range(qty):
|
||||
s = str(strings[-(i+1)]).strip()
|
||||
if len(s) > 0:
|
||||
return s
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def removeEndResult(strings, targetString):
|
||||
"""
|
||||
Searches and removes last string which contains result
|
||||
:param strings:
|
||||
:param targetString:
|
||||
:return:
|
||||
"""
|
||||
ret = ""
|
||||
|
||||
#searching for target string
|
||||
while len(strings) > 0:
|
||||
s = str(strings[-1]).strip()
|
||||
|
||||
strings.pop(len(strings)-1)
|
||||
if s == targetString:
|
||||
break
|
||||
|
||||
#compiling result
|
||||
qty = len(strings)
|
||||
for i in range(qty):
|
||||
ret += strings[i]
|
||||
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
def parseStrings(buffer, encoding = "ascii"):
|
||||
"""
|
||||
Parses string (from given encoding), looks for cr/lf and retutrns strings array
|
||||
:param buffer: input string
|
||||
:param encoding: encoding
|
||||
:return: strings array
|
||||
"""
|
||||
|
||||
#decoding
|
||||
bigString = buffer.decode(encoding)
|
||||
|
||||
#searching for cr/lf and making strings array
|
||||
if "\r" in bigString:
|
||||
ret = bigString.split("\r")
|
||||
else:
|
||||
ret = [bigString]
|
||||
|
||||
return ret
|
||||
|
||||
def commandAndStdResult(self, commandText, maxWaitTime = 5000, possibleResults = None):
|
||||
self.lastResult = None
|
||||
|
||||
#setting up standard results
|
||||
if possibleResults is None:
|
||||
possibleResults = ["OK", "ERROR"]
|
||||
|
||||
start = time.time()
|
||||
buffer = bytearray()
|
||||
|
||||
self.flush()
|
||||
|
||||
#sending command
|
||||
self.simpleWriteLn(commandText)
|
||||
|
||||
try:
|
||||
while True:
|
||||
if timeDelta(start) >= maxWaitTime:
|
||||
break
|
||||
|
||||
readBytesQty = 0
|
||||
while True:
|
||||
b = self.__serial.read(100)
|
||||
|
||||
if (b is not None) and (len(b) >= 1):
|
||||
buffer += bytearray(b)
|
||||
self.logger.debug("{0}: buffer = {1}".format(inspect.stack()[0][3], buffer))
|
||||
|
||||
readBytesQty += len(b)
|
||||
continue
|
||||
else:
|
||||
break
|
||||
|
||||
#if we have no data - let's go sleep for tiny amount of time
|
||||
if readBytesQty == 0:
|
||||
time.sleep(0.005)
|
||||
continue
|
||||
|
||||
#parsing result strings
|
||||
strings = SimGsm.parseStrings(buffer[:])
|
||||
self.logger.debug("{0}: strings = {1}".format(inspect.stack()[0][3], strings))
|
||||
|
||||
if strings is None:
|
||||
time.sleep(0.01)
|
||||
continue
|
||||
|
||||
#if we have some strings let's parse it
|
||||
if len(strings) > 0:
|
||||
lastString = SimGsm.getLastNonEmptyString(strings[:])
|
||||
|
||||
if lastString in possibleResults:
|
||||
self.lastResult = lastString
|
||||
return SimGsm.removeEndResult(strings[:], lastString)
|
||||
|
||||
time.sleep(0.05)
|
||||
|
||||
return None
|
||||
except Exception as e:
|
||||
self.setError(e)
|
||||
return None
|
||||
except:
|
||||
self.setError("reading error...")
|
||||
return None
|
||||
|
||||
def execSimpleCommand(self, commandText, result, timeout = 500):
|
||||
ret = self.commandAndStdResult(commandText, timeout, [result])
|
||||
if (ret is None) or (self.lastResult != result):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def execSimpleOkCommand(self, commandText, timeout = 500):
|
||||
self.logger.debug("executing command '{0}'".format(commandText))
|
||||
|
||||
ret = self.commandAndStdResult(commandText, timeout, ["OK", "ERROR"])
|
||||
if (ret is None) or (self.lastResult != "OK"):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def execSimpleCommandsList(self, commandsList):
|
||||
for command in commandsList:
|
||||
if not self.execSimpleOkCommand(command[0], command[1]):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
class SimGsm(SimGsmSerialPortHandler):
|
||||
def __init__(self, serial, logger = None):
|
||||
SimGsmSerialPortHandler.__init__(self, serial, logger)
|
||||
|
||||
self.__state = SimGsmState.UNKNOWN
|
||||
self.pinState = SimGsmPinRequestState.UNKNOWN
|
||||
|
||||
def begin(self, numberOfAttempts = 5):
|
||||
ok = False
|
||||
|
||||
self.flush()
|
||||
|
||||
needDisableEcho = False
|
||||
|
||||
for i in range(numberOfAttempts):
|
||||
self.printLn("AT")
|
||||
line = self.readDataLine(2000, "ascii")
|
||||
|
||||
#if we do not have something in input - let's go sleep
|
||||
if line is None:
|
||||
time.sleep(0.2)
|
||||
continue
|
||||
|
||||
#we have echo, need to reconfigure
|
||||
if line == "AT":
|
||||
#we have ECHO, need reconfigure
|
||||
needDisableEcho = True
|
||||
line = self.readDataLine(500, "ascii")
|
||||
if line == "OK":
|
||||
ok = True
|
||||
break
|
||||
|
||||
elif line == "OK":
|
||||
ok = True
|
||||
break
|
||||
|
||||
if not ok:
|
||||
return False
|
||||
|
||||
#disabling echo if needed
|
||||
if needDisableEcho:
|
||||
self.logger.info("Disabling echo, calling 'ATE0'")
|
||||
self.simpleWriteLn("ATE0")
|
||||
time.sleep(0.5)
|
||||
self.flush()
|
||||
|
||||
commands = [
|
||||
["ATV1", 500], #short answer for commands
|
||||
["AT+CMEE=0", 500], #disabling error report
|
||||
["AT", 5000] #checking state
|
||||
]
|
||||
|
||||
for cmd in commands:
|
||||
self.logger.debug("configuring, calling: {0}".format(cmd[0]))
|
||||
if not self.execSimpleOkCommand(commandText=cmd[0],timeout=cmd[1]):
|
||||
return False
|
||||
|
||||
#checking PIN state
|
||||
if not self.__checkPin():
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def __checkPin(self):
|
||||
msg = self.commandAndStdResult("AT+CPIN?")
|
||||
if msg is None:
|
||||
return False
|
||||
|
||||
if self.lastResult != "OK":
|
||||
return False
|
||||
|
||||
msg = str(msg).strip()
|
||||
|
||||
values = splitAndFilter(msg, ":")
|
||||
msg.split(":")
|
||||
|
||||
if len(values) < 2:
|
||||
self.setError("Wrong response for PIN state request")
|
||||
return False
|
||||
|
||||
if values[0] != "+CPIN":
|
||||
self.setError("Wrong response for PIN state request. First value = '{0}'".format(values[0]))
|
||||
return False
|
||||
|
||||
v = " ".join([v for v in values[1:]])
|
||||
|
||||
if v == "READY":
|
||||
self.pinState = SimGsmPinRequestState.NOPINNEEDED
|
||||
elif v == "SIM PIN":
|
||||
self.pinState = SimGsmPinRequestState.SIM_PIN
|
||||
elif v == "SIM PUK":
|
||||
self.pinState = SimGsmPinRequestState.SIM_PUK
|
||||
elif v == "PH_SIM PIN":
|
||||
self.pinState = SimGsmPinRequestState.PH_SIM_PIN
|
||||
elif v == "PH_SIM PUK":
|
||||
self.pinState = SimGsmPinRequestState.PH_SIM_PUK
|
||||
elif v == "SIM PIN2":
|
||||
self.pinState = SimGsmPinRequestState.SIM_PIN2
|
||||
elif v == "SIM PUK2":
|
||||
self.pinState = SimGsmPinRequestState.SIM_PUK2
|
||||
else:
|
||||
self.pinState = SimGsmPinRequestState.UNKNOWN
|
||||
self.setError("Unknown PIN request answer: {0}".format(v))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def enterPin(self, pinCode):
|
||||
return self.execSimpleOkCommand("AT+CPIN=\"{0}\"".format(pinCode))
|
||||
Reference in New Issue
Block a user