├── .gitignore ├── README.md ├── smsdispatcher.py └── sms.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IteadSIM800 2 | A Python 3 library for driving the [Itead RPI SIM800 GSM/GPRS Add-On V2.0](https://www.itead.cc/wiki/RPI_SIM800_GSM/GPRS_ADD-ON_V2.0) 3 | with the Raspberry Pi. 4 | 5 | ## Hardware 6 | If you are using a Raspberry Pi Model 1 A+/B+, Model 2/3 B or Zero then this add-on board will connect directly to the 40-pin GPIO. 7 | If you are using an Model 1 A/B with a 28-pin GPIO connector then you will have to connect the board to the Pi with ribbon cable 8 | or dupont connectors as the composite video and audio connectors are in the way. Only the follow seven pins need to be connected: 9 | 10 | | Pin | Function | 11 | | --- | --- | 12 | | 1 | 3.3V | 13 | | 2 | 5V | 14 | | 6 | GND | 15 | | 8 | TXD | 16 | | 10 | RXD | 17 | | 11 | SIM800 Power | 18 | | 12 | SIM800 Reset | 19 | 20 | ## Software 21 | This library has been tested with Python 3.4.2 running on a Raspberry Pi Model B (Rev.2) with Raspbian Jessie Lite (2016-05-27). 22 | It depends on PySerial and Ben Croston's RPi.GPIO which can be installed (if not already) as follows: 23 | 24 | ``` 25 | $ sudo apt-get update 26 | $ sudo apt-get install python3-rpi.gpio 27 | $ sudo pip3 install pyserial 28 | ``` 29 | 30 | The file `sms.py` is both the library and a working example if read/executed: 31 | 32 | ``` 33 | $ sudo python3 sms.py 34 | ``` 35 | 36 | Most methods also contain (brief) comments as to there function. 37 | 38 | This setup has been tested in the UK using a SIM card from [giffgaff](https://www.giffgaff.com/) with whom we have no relationship, 39 | personal or otherwise. 40 | -------------------------------------------------------------------------------- /smsdispatcher.py: -------------------------------------------------------------------------------- 1 | from threading import Thread 2 | from queue import Queue, Empty 3 | from redis import Redis 4 | import json, re, time 5 | from datetime import datetime 6 | 7 | from sms import SMS, BALANCE_USSD, NetworkStatus 8 | 9 | LOGGER="SMSDispatcher" 10 | TEN_MEGABYTES=10485760 11 | FIVE_MINUTES=300. 12 | PORT="/dev/ttyAMA0" 13 | BAUD=9600 14 | 15 | def taskWorker(): 16 | _redis=Redis() 17 | _redis.set("sim800NetworkStatus", "Unknown") 18 | _redis.set("sim800Balance","0") 19 | _redis.set("sim800RSSI",0) 20 | 21 | logger=logging.getLogger(LOGGER) 22 | balanceRegExp=re.compile(r"£(\d){1,2}\.(\d){2}") 23 | 24 | try: 25 | sms=SMS(PORT,BAUD,logger=logger) 26 | sms.setup() 27 | if not sms.turnOn(): 28 | logger.critical("Failed to turn on SMS!") 29 | return 30 | if not sms.setEchoOff(): 31 | logger.critical("Failed to set SMS echo off!") 32 | return 33 | sms.setTime(datetime.now()) 34 | 35 | netStat="Unknown" 36 | while netStat!="Good": 37 | netStat=sms.getNetworkStatus() 38 | if netStat is not None: 39 | if netStat in (NetworkStatus.RegisteredHome, NetworkStatus.RegisteredRoaming): 40 | netStat="Good" 41 | elif netStat in (NetworkStatus.Searching,): netStat="Searching" 42 | else: netStat="Bad" 43 | else: netStat="Unknown" 44 | _redis.set("sim800NetworkStatus", netStat) 45 | 46 | checkBalance=True 47 | statusCheckTime=0. 48 | while True: 49 | if taskQueue.empty(): 50 | if checkBalance: 51 | checkBalance=False 52 | balanceMsg=sms.sendUSSD(BALANCE_USSD) 53 | logger.info("Balance message: {}".format(balanceMsg)) 54 | match=balanceRegExp.search(balanceMsg) 55 | if match is not None: 56 | balance=match.group(0) 57 | logger.info("Balance amount: {}".format(balance)) 58 | _redis.set("sim800Balance",balance) 59 | 60 | if (time.time()-statusCheckTime)>FIVE_MINUTES: 61 | rssi=sms.getRSSI() 62 | if rssi is not None: rssi=(rssi.value/4.)*100 63 | else: rssi=0 64 | _redis.set("sim800RSSI",rssi) 65 | 66 | netStat=sms.getNetworkStatus() 67 | if netStat is not None: 68 | if netStat in (NetworkStatus.RegisteredHome, NetworkStatus.RegisteredRoaming): 69 | netStat="Good" 70 | elif netStat in (NetworkStatus.Searching,): netStat="Searching" 71 | else: netStat="Bad" 72 | else: netStat="Unknown" 73 | _redis.set("sim800NetworkStatus", netStat) 74 | 75 | statusCheckTime=time.time() 76 | 77 | try: task=taskQueue.get(timeout=60) 78 | except Empty: continue 79 | if task is None: continue 80 | 81 | phoneNumber=task.get('phoneNumber') 82 | message=task.get('message') 83 | 84 | if phoneNumber and message: 85 | logger.info("Sending SMS: {}, {}".format(phoneNumber, message)) 86 | if sms.sendSMS(phoneNumber, message): 87 | logger.info("SMS sent successfully") 88 | checkBalance=True 89 | else: logger.error("Failed to send SMS! {}, {}".format(phoneNumber, message)) 90 | else: logger.error("Task is not valid: {}".format(task)) 91 | 92 | taskQueue.task_done() 93 | except Exception as e: 94 | logger.critical("Exception in task thread: {}".format(e)) 95 | return 96 | 97 | def main(): 98 | logger=logging.getLogger(LOGGER) 99 | _redis=Redis() 100 | pubsub=_redis.pubsub() 101 | pubsub.subscribe(['sms']) 102 | for msg in pubsub.listen(): 103 | if msg['channel']!=b'sms': 104 | logger.debug("Got message unknown channel {}".format(msg['channel'])) 105 | continue 106 | if msg['type']=='subscribe': 107 | logger.info("Subscribed to channel") 108 | continue 109 | if msg['type']!='message': 110 | logger.debug("Got unknown message type {}".format(msg['type'])) 111 | continue 112 | try: 113 | data=msg['data'].decode('utf-8') 114 | data=json.loads(data) 115 | except Exception as e: 116 | logging.error("Failed to decode data: {}, {}".format(msg['data'], e)) 117 | continue 118 | taskQueue.put(data) 119 | 120 | if __name__=="__main__": 121 | import sys, logging 122 | from argparse import ArgumentParser 123 | 124 | def exceptionHook(etype, evalue, etraceback): 125 | from traceback import format_tb 126 | 127 | logger=logging.getLogger(LOGGER) 128 | logstr="{name}; {value}; {traceback}".format( 129 | name=etype.__name__, 130 | value=str(evalue) or "(None)", 131 | traceback="\n".join(format_tb(traceback)) 132 | ) 133 | logger.critical(logstr) 134 | for h in logger.handlers: 135 | try: h.flush() 136 | except: continue 137 | 138 | parser=ArgumentParser(description="SMS Dispatcher.") 139 | parser.add_argument("-d", "--debug", dest="debug", default=False, 140 | action="store_true", help="turn on debug information") 141 | parser.add_argument("-s", "--stdout", dest="stdout", default=False, 142 | action="store_true", help="re-direct logging output to stdout") 143 | options=parser.parse_args() 144 | loglevel=logging.DEBUG if options.debug else logging.WARNING 145 | 146 | logger=logging.getLogger(LOGGER) 147 | if options.stdout: handler=logging.StreamHandler(sys.stdout) 148 | else: 149 | from logging.handlers import RotatingFileHandler 150 | handler=RotatingFileHandler("./smsdispatcher.log", maxBytes=TEN_MEGABYTES, backupCount=5) 151 | handler.setFormatter(logging.Formatter("%(asctime)s : %(levelname)s -> %(message)s")) 152 | logger.addHandler(handler) 153 | logger.setLevel(loglevel) 154 | 155 | sys.excepthook=exceptionHook 156 | 157 | taskQueue=Queue() 158 | taskThread=Thread(target=taskWorker) 159 | taskThread.start() 160 | main() -------------------------------------------------------------------------------- /sms.py: -------------------------------------------------------------------------------- 1 | from serial import Serial 2 | import RPi.GPIO as IO, atexit, logging, sys 3 | from time import sleep 4 | from enum import IntEnum 5 | from datetime import datetime 6 | 7 | PORT="/dev/ttyAMA0" 8 | BAUD=9600 9 | GSM_ON=11 10 | GSM_RESET=12 11 | DATE_FMT='"%y/%m/%d,%H:%M:%S%z"' 12 | 13 | APN="giffgaff.com" 14 | APN_USERNAME="giffgaff" 15 | APN_PASSWORD="" # Leave blank 16 | 17 | BALANCE_USSD="*100#" 18 | 19 | # Balance: *100*7# 20 | # Remaining Credit: *100# 21 | # Voicemail: 443 (costs 8p!) 22 | # Text Delivery Receipt (start with): *0# 23 | # Hide calling number: #31# 24 | 25 | class ATResp(IntEnum): 26 | ErrorNoResponse=-1 27 | ErrorDifferentResponse=0 28 | OK=1 29 | 30 | class SMSMessageFormat(IntEnum): 31 | PDU=0 32 | Text=1 33 | 34 | class SMSTextMode(IntEnum): 35 | Hide=0 36 | Show=1 37 | 38 | class SMSStatus(IntEnum): 39 | Unread=0 40 | Read=1 41 | Unsent=2 42 | Sent=3 43 | All=4 44 | 45 | @classmethod 46 | def fromStat(cls, stat): 47 | if stat=='"REC UNREAD"': return cls.Unread 48 | elif stat=='"REC READ"': return cls.Read 49 | elif stat=='"STO UNSENT"': return cls.Unsent 50 | elif stat=='"STO SENT"': return cls.Sent 51 | elif stat=='"ALL"': return cls.All 52 | 53 | @classmethod 54 | def toStat(cls, stat): 55 | if stat==cls.Unread: return "REC UNREAD" 56 | elif stat==cls.Read: return "REC READ" 57 | elif stat==cls.Unsent: return "STO UNSENT" 58 | elif stat==cls.Sent: return "STO SENT" 59 | elif stat==cls.All: return "ALL" 60 | 61 | class RSSI(IntEnum): 62 | """ 63 | Received Signal Strength Indication as 'bars'. 64 | Interpretted form AT+CSQ return value as follows: 65 | 66 | ZeroBars: Return value=99 (unknown or not detectable) 67 | OneBar: Return value=0 (-115dBm or less) 68 | TwoBars: Return value=1 (-111dBm) 69 | ThreeBars: Return value=2...30 (-110 to -54dBm) 70 | FourBars: Return value=31 (-52dBm or greater) 71 | """ 72 | 73 | ZeroBars=0 74 | OneBar=1 75 | TwoBars=2 76 | ThreeBars=3 77 | FourBars=4 78 | 79 | @classmethod 80 | def fromCSQ(cls, csq): 81 | csq=int(csq) 82 | if csq==99: return cls.ZeroBars 83 | elif csq==0: return cls.OneBar 84 | elif csq==1: return cls.TwoBars 85 | elif 2<=csq<=30: return cls.ThreeBars 86 | elif csq==31: return cls.FourBars 87 | 88 | class NetworkStatus(IntEnum): 89 | NotRegistered=0 90 | RegisteredHome=1 91 | Searching=2 92 | Denied=3 93 | Unknown=4 94 | RegisteredRoaming=5 95 | 96 | @atexit.register 97 | def cleanup(): IO.cleanup() 98 | 99 | class SMS(object): 100 | def __init__(self, port, baud, logger=None, loglevel=logging.WARNING): 101 | self._port=port 102 | self._baud=baud 103 | 104 | self._ready=False 105 | self._serial=None 106 | 107 | if logger: self._logger=logger 108 | else: 109 | self._logger=logging.getLogger("SMS") 110 | handler=logging.StreamHandler(sys.stdout) 111 | handler.setFormatter(logging.Formatter("%(asctime)s : %(levelname)s -> %(message)s")) 112 | self._logger.addHandler(handler) 113 | self._logger.setLevel(loglevel) 114 | 115 | def setup(self): 116 | """ 117 | Setup the IO to control the power and reset inputs and the serial port. 118 | """ 119 | self._logger.debug("Setup") 120 | IO.setmode(IO.BOARD) 121 | IO.setup(GSM_ON, IO.OUT, initial=IO.LOW) 122 | IO.setup(GSM_RESET, IO.OUT, initial=IO.LOW) 123 | self._serial=Serial(self._port, self._baud) 124 | 125 | def reset(self): 126 | """ 127 | Reset (turn on) the SIM800 module by taking the power line for >1s 128 | and then wait 5s for the module to boot. 129 | """ 130 | self._logger.debug("Reset (duration ~6.2s)") 131 | IO.output(GSM_ON, IO.HIGH) 132 | sleep(1.2) 133 | IO.output(GSM_ON, IO.LOW) 134 | sleep(5.) 135 | 136 | def sendATCmdWaitResp(self, cmd, response, timeout=.5, interByteTimeout=.1, attempts=1, addCR=False): 137 | """ 138 | This function is designed to check for simple one line responses, e.g. 'OK'. 139 | """ 140 | self._logger.debug("Send AT Command: {}".format(cmd)) 141 | self._serial.timeout=timeout 142 | self._serial.inter_byte_timeout=interByteTimeout 143 | 144 | status=ATResp.ErrorNoResponse 145 | for i in range(attempts): 146 | bcmd=cmd.encode('utf-8')+b'\r' 147 | if addCR: bcmd+=b'\n' 148 | 149 | self._logger.debug("Attempt {}, ({})".format(i+1, bcmd)) 150 | #self._serial.write(cmd.encode('utf-8')+b'\r') 151 | self._serial.write(bcmd) 152 | self._serial.flush() 153 | 154 | lines=self._serial.readlines() 155 | lines=[l.decode('utf-8').strip() for l in lines] 156 | lines=[l for l in lines if len(l) and not l.isspace()] 157 | self._logger.debug("Lines: {}".format(lines)) 158 | if len(lines)<1: continue 159 | line=lines[-1] 160 | self._logger.debug("Line: {}".format(line)) 161 | 162 | if not len(line) or line.isspace(): continue 163 | elif line==response: return ATResp.OK 164 | else: return ATResp.ErrorDifferentResponse 165 | return status 166 | 167 | def sendATCmdWaitReturnResp(self, cmd, response, timeout=.5, interByteTimeout=.1): 168 | """ 169 | This function is designed to return data and check for a final response, e.g. 'OK' 170 | """ 171 | self._logger.debug("Send AT Command: {}".format(cmd)) 172 | self._serial.timeout=timeout 173 | self._serial.inter_byte_timeout=interByteTimeout 174 | 175 | self._serial.write(cmd.encode('utf-8')+b'\r') 176 | self._serial.flush() 177 | lines=self._serial.readlines() 178 | for n in range(len(lines)): 179 | try: lines[n]=lines[n].decode('utf-8').strip() 180 | except UnicodeDecodeError: lines[n]=lines[n].decode('latin1').strip() 181 | 182 | lines=[l for l in lines if len(l) and not l.isspace()] 183 | self._logger.debug("Lines: {}".format(lines)) 184 | 185 | if not len(lines): return (ATResp.ErrorNoResponse, None) 186 | 187 | _response=lines.pop(-1) 188 | self._logger.debug("Response: {}".format(_response)) 189 | if not len(_response) or _response.isspace(): return (ATResp.ErrorNoResponse, None) 190 | elif response==_response: return (ATResp.OK, lines) 191 | return (ATResp.ErrorDifferentResponse, None) 192 | 193 | def parseReply(self, data, beginning, divider=',', index=0): 194 | """ 195 | Parse an AT response line by checking the reply starts with the expected prefix, 196 | splitting the reply into its parts by the specified divider and then return the 197 | element of the response specified by index. 198 | """ 199 | self._logger.debug("Parse Reply: {}, {}, {}, {}".format(data, beginning, divider, index)) 200 | if not data.startswith(beginning): return False, None 201 | data=data.replace(beginning,"") 202 | data=data.split(divider) 203 | try: return True,data[index] 204 | except IndexError: return False, None 205 | 206 | def getSingleResponse(self, cmd, response, beginning, divider=",", index=0, timeout=.5, interByteTimeout=.1): 207 | """ 208 | Run a command, get a single line response and the parse using the 209 | specified parameters. 210 | """ 211 | status,data=self.sendATCmdWaitReturnResp(cmd,response,timeout=timeout,interByteTimeout=interByteTimeout) 212 | if status!=ATResp.OK: return None 213 | if len(data)!=1: return None 214 | ok,data=self.parseReply(data[0], beginning, divider, index) 215 | if not ok: return None 216 | return data 217 | 218 | def turnOn(self): 219 | """ 220 | Check to see if the module is on, if so return. If not, attempt to 221 | reset the module and then check that it is responding. 222 | """ 223 | self._logger.debug("Turn On") 224 | for i in range(2): 225 | status=self.sendATCmdWaitResp("AT", "OK", attempts=5) 226 | if status==ATResp.OK: 227 | self._logger.debug("GSM module ready.") 228 | self._ready=True 229 | return True 230 | elif status==ATResp.ErrorDifferentResponse: 231 | self._logger.debug("GSM module returned invalid response, check baud rate?") 232 | elif i==0: 233 | self._logger.debug("GSM module is not responding, resetting...") 234 | self.reset() 235 | else: self._logger.error("GSM module failed to respond after reset!") 236 | return False 237 | 238 | def setEchoOff(self): 239 | """ 240 | Switch off command echoing to simply response parsing. 241 | """ 242 | self._logger.debug("Set Echo Off") 243 | self.sendATCmdWaitResp("ATE0", "OK") 244 | status=self.sendATCmdWaitResp("ATE0", "OK") 245 | return status==ATResp.OK 246 | 247 | def getLastError(self): 248 | """ 249 | Get readon for last error 250 | """ 251 | self._logger.debug("Get Last Error") 252 | error=self.getSingleResponse("AT+CEER","OK","+CEER: ") 253 | return error 254 | 255 | def getIMEI(self): 256 | """ 257 | Get the IMEI number of the module 258 | """ 259 | self._logger.debug("Get International Mobile Equipment Identity (IMEI)") 260 | status,imei=self.sendATCmdWaitReturnResp("AT+GSN","OK") 261 | if status==ATResp.OK and len(imei)==1: return imei[0] 262 | return None 263 | 264 | def getVersion(self): 265 | """ 266 | Get the module firmware version. 267 | """ 268 | self._logger.debug("Get TA Revision Identification of Software Release") 269 | revision=self.getSingleResponse("AT+CGMR","OK","Revision",divider=":",index=1) 270 | return revision 271 | 272 | def getSIMCCID(self): 273 | """ 274 | The the SIM ICCID. 275 | """ 276 | self._logger.debug("Get SIM Integrated Circuit Card Identifier (ICCID)") 277 | status,ccid=self.sendATCmdWaitReturnResp("AT+CCID","OK") 278 | if status==ATResp.OK and len(ccid)==1: return ccid[0] 279 | return None 280 | 281 | def getNetworkStatus(self): 282 | """ 283 | Get the current network connection status. 284 | """ 285 | self._logger.debug("Get Network Status") 286 | status=self.getSingleResponse("AT+CREG?","OK","+CREG: ",index=1) 287 | if status is None: return status 288 | return NetworkStatus(int(status)) 289 | 290 | def getRSSI(self): 291 | """ 292 | Get the current signal strength in 'bars' 293 | """ 294 | self._logger.debug("Get Received Signal Strength Indication (RSSI)") 295 | csq=self.getSingleResponse("AT+CSQ","OK","+CSQ: ") 296 | if csq is None: return csq 297 | return RSSI.fromCSQ(csq) 298 | 299 | def enableNetworkTimeSync(self, enable): 300 | self._logger.debug("Enable network time synchronisation") 301 | status=self.sendATCmdWaitResp("AT+CLTS={}".format(int(enable)),"OK") 302 | return status==ATResp.OK 303 | 304 | def getTime(self): 305 | """ 306 | Get the current time 307 | """ 308 | self._logger.debug("Get the current time") 309 | time=self.getSingleResponse("AT+CCLK?","OK","+CCLK: ", divider="'") 310 | if time is None: return time 311 | return datetime.strptime(time[:-1]+'00"', DATE_FMT) 312 | 313 | def setTime(self, time): 314 | """ 315 | """ 316 | self._logger.debug("Set the current time: {}".format(time)) 317 | time=datetime.strftime(time, DATE_FMT) 318 | if time[-4]!="+": time=time[:-1]+'+00"' 319 | status=self.sendATCmdWaitResp("AT+CCLK={}".format(time),"OK") 320 | return status==ATResp.OK 321 | 322 | def setSMSMessageFormat(self, format): 323 | """ 324 | Set the SMS message format either as PDU or text. 325 | """ 326 | status=self.sendATCmdWaitResp("AT+CMGF={}".format(format), "OK") 327 | return status==ATResp.OK 328 | 329 | def setSMSTextMode(self, mode): 330 | status=self.sendATCmdWaitResp("AT+CSDH={}".format(mode), "OK") 331 | return status==ATResp.OK 332 | 333 | def getNumSMS(self): 334 | """ 335 | Get the number of SMS on SIM card 336 | """ 337 | self._logger.debug("Get Number of SMS") 338 | if not self.setSMSMessageFormat(SMSMessageFormat.Text): 339 | self._logger.error("Failed to set SMS Message Format!") 340 | return False 341 | 342 | if not self.setSMSTextMode(SMSTextMode.Show): 343 | self._logger.error("Failed to set SMS Text Mode!") 344 | return False 345 | 346 | num=self.getSingleResponse('AT+CPMS?', "OK", "+CPMS: ", divider='"SM",', index=1) 347 | if num is None: return num 348 | n,t,*_=num.split(',') 349 | return int(n),int(t) 350 | 351 | def readSMS(self, number): 352 | """ 353 | Returns status, phone number, date/time and message in location specified by 'number'. 354 | """ 355 | self._logger.debug("Read SMS: {}".format(number)) 356 | if not self.setSMSMessageFormat(SMSMessageFormat.Text): 357 | self._logger.error("Failed to set SMS Message Format!") 358 | return None 359 | 360 | if not self.setSMSTextMode(SMSTextMode.Show): 361 | self._logger.error("Failed to set SMS Text Mode!") 362 | return None 363 | 364 | status,(params,msg)=self.sendATCmdWaitReturnResp("AT+CMGR={}".format(number),"OK") 365 | if status!=ATResp.OK or not params.startswith("+CMGR: "): return None 366 | 367 | # stat : message status = "REC UNREAD", "REC READ", "STO UNSENT", "STO SENT", "ALL" 368 | # oa : originating address 369 | # alpha : string of "oa" or "da" 370 | # scts : service center timestamp "YY/MM/DD,HH:MM:SS+ZZ" 371 | # tooa : originating address type 372 | # fo : 373 | # pid : protocol ID 374 | # dcs : data coding scheme 375 | # sca : 376 | # tosca : 377 | # length : length of the message body 378 | stat,oa,alpha,scts1,scts2,tooa,fo,pid,dcs,sca,tosca,length=params[7:].split(',') 379 | 380 | scts=scts1+','+scts2 381 | tz=scts[-2:] 382 | scts=scts[:-1]+'00"' 383 | scts=datetime.strptime(scts, DATE_FMT) 384 | return SMSStatus.fromStat(stat),oa[1:-1],scts,msg 385 | 386 | def readAllSMS(self, status=SMSStatus.All): 387 | self._logger.debug("Read All SMS") 388 | if not self.setSMSMessageFormat(SMSMessageFormat.Text): 389 | self._logger.error("Failed to set SMS Message Format!") 390 | return None 391 | 392 | if not self.setSMSTextMode(SMSTextMode.Show): 393 | self._logger.error("Failed to set SMS Text Mode!") 394 | return None 395 | 396 | status,msgs=self.sendATCmdWaitReturnResp('AT+CMGL="{}"'.format(SMSStatus.toStat(status)), "OK") 397 | if status!=ATResp.OK or not msgs[0].startswith("+CMGL: ") or len(msgs)%2!=0: return None 398 | 399 | formatted=[] 400 | for n in range(0, len(msgs), 2): 401 | params,msg=msgs[n:n+2] 402 | if n==0: params=params[7:] 403 | loc,stat,oa,alpha,scts1,scts2,tooa,fo,pid,dcs,sca,tosca,length=params.split(',') 404 | scts=scts1+','+scts2 405 | tz=scts[-2:] 406 | scts=scts[:-1]+'00"' 407 | scts=datetime.strptime(scts, DATE_FMT) 408 | formatted.append((loc,SMSStatus.fromStat(stat),oa[1:-1],scts,msg)) 409 | return formatted 410 | 411 | def deleteSMS(self, number): 412 | """ 413 | Delete the SMS in location specified by 'number'. 414 | """ 415 | self._logger.debug("Delete SMS: {}".format(number)) 416 | if not self.setSMSMessageFormat(SMSMessageFormat.Text): 417 | self._logger.error("Failed to set SMS Message Format!") 418 | return False 419 | status=self.sendATCmdWaitResp("AT+CMGD={:03d}".format(number), "OK") 420 | return status==ATResp.OK 421 | 422 | def sendSMS(self, phoneNumber, msg): 423 | """ 424 | Send the specified message text to the provided phone number. 425 | """ 426 | self._logger.debug("Send SMS: {} '{}'".format(phoneNumber, msg)) 427 | if not self.setSMSMessageFormat(SMSMessageFormat.Text): 428 | self._logger.error("Failed to set SMS Message Format!") 429 | return False 430 | 431 | status=self.sendATCmdWaitResp('AT+CMGS="{}"'.format(phoneNumber), ">", addCR=True) 432 | if status!=ATResp.OK: 433 | self._logger.error("Failed to send CMGS command part 1! {}".format(status)) 434 | return False 435 | 436 | cmgs=self.getSingleResponse(msg+"\r\n\x1a", "OK", "+", divider=":", timeout=11., interByteTimeout=1.2) 437 | return cmgs=="CMGS" 438 | 439 | def sendUSSD(self, ussd): 440 | """ 441 | Send Unstructured Supplementary Service Data message 442 | """ 443 | self._logger.debug("Send USSD: {}".format(ussd)) 444 | reply=self.getSingleResponse('AT+CUSD=1,"{}"'.format(ussd), "OK", "+CUSD: ", index=1, timeout=11., interByteTimeout=1.2) 445 | return reply 446 | 447 | if __name__=="__main__": 448 | s=SMS(PORT,BAUD,loglevel=logging.DEBUG) 449 | s.setup() 450 | if not s.turnOn(): exit(1) 451 | if not s.setEchoOff(): exit(1) 452 | print("Good to go!") 453 | print(s.getIMEI()) 454 | print(s.getVersion()) 455 | print(s.getSIMCCID()) 456 | #print(s.getLastError()) 457 | ns=s.getNetworkStatus() 458 | print(ns) 459 | if ns not in (NetworkStatus.RegisteredHome, NetworkStatus.RegisteredRoaming): 460 | exit(1) 461 | print(s.getRSSI()) 462 | #print(s.enableNetworkTimeSync(True)) 463 | # print(s.getTime()) 464 | # print(s.setTime(datetime.now())) 465 | # print(s.getTime()) 466 | # print(s.sendSMS("+441234567890", "Hello World!")) 467 | print(s.sendUSSD(BALANCE_USSD)) 468 | #print(s.getLastError()) 469 | print(s.getNumSMS()) 470 | #print(s.readSMS(1)) 471 | #print(s.deleteSMS(1)) 472 | #print(s.readAllSMS()) 473 | --------------------------------------------------------------------------------