【环境准备】

1、操作系统:

Windows 7 x64 SP1

2、Python 版本:

python-3.6.3-amd64

3、第三方组件:

(1)、

pip3 install pyserial

(2)、下载smspdu

python3 setup.py build install


【列出所有串口】

#encoding: utf-8
#author: walker
#date: 2017-10-06 
#summary: Python 用 pyserial 列出所有串口

import serial.tools.list_ports

# 打印所有串口名
def ListPorts():
	serailPortList = list(serial.tools.list_ports.comports())
	print('serail port number: %d' % len(serailPortList))
	if len(serailPortList) < 1:
		print("Not found serail port!")
		return 
		
	for serailPort in serailPortList:
		se = serial.Serial(list(serailPort)[0], 9600, timeout=9)
		print('serail port name:%s' % se.portstr)
		se.close()
	
if __name__ == '__main__':
	ListPorts()


【发送纯英文(ASCII)短信】

#encoding: utf-8
#author: walker
#date: 2017-10-06 
#summary: Python 调用 GSM A6,发送英文短信

import time
import serial.tools.list_ports
		
class MessagerEn(object):
	# 构造函数,打开端口
	def __init__(self):        
		self.sePort = serial.Serial(port='COM3', baudrate=19200, bytesize=8, stopbits=1, timeout=3)
		time.sleep(3)
	
	# 析构函数,关闭端口
	def __del__(self): 
		self.sePort.close()
	
	# 重新打开端口
	def reopen(self):
		print('reopen ...')
		self.sePort.close()
		self.sePort = serial.Serial(port='COM3', baudrate=19200, bytesize=8, stopbits=1, timeout=3)
		time.sleep(3)

	# 执行指令
	# 返回从串口读取的返回值
	def execCmd(self, cmd, end='\r'):
		cmd = cmd + end
		bCmd = cmd.encode('ascii')
		print('write:%s' % bCmd)
		self.sePort.write(bCmd)
		response = self.sePort.readall()
		print('read :%s' % response)
		print()
		
		return response
	
	# 发送英文短信
	# 正确返回 True;错误返回 False
	def __sendMessageEn(self, phoneNumber, message):
		if not self.execCmd('AT+CMGF=1').endswith(b'\r\nOK\r\n'):		#设置TEXT MODE
			return False
			
		if not self.execCmd('AT+CSCS="GSM"').endswith(b'\r\nOK\r\n'):		
			return False
			
		if not self.execCmd('AT+CMGS="%s"' % phoneNumber).endswith(b'\r\n> '):		
			return False
		
		if not self.execCmd(message + '\x1A', end='').endswith(b'\r\nOK\r\n'):		# 十六进制的1A为结束符
			return False
			
		return True
	
	# 调用私有方法 __sendMessageEn
	# 在失败时重新打开串口
	def sendMessage(self, phoneNumber, message):
		for _ in range(0, 3):
			if self.__sendMessageEn(phoneNumber, message):
				break
			self.reopen()
	
if __name__ == '__main__':
	messagerEn = MessagerEn()
	messagerEn.sendMessage('18688754379', 'xxx')
	messagerEn.sendMessage('18688754379', 'zzz')


【发送中文(Unicode)短信】

#encoding: utf-8
#author: walker
#date: 2017-10-06 
#summary: Python 调用 GSM A6,发送中文(Unicode)短信
 
import time
import serial.tools.list_ports
         
class Messager(object):
    # 构造函数,打开端口
    def __init__(self):        
        self.sePort = serial.Serial(port='COM3', baudrate=19200, bytesize=8, stopbits=1, timeout=3)
        time.sleep(3)
     
    # 析构函数,关闭端口
    def __del__(self): 
        self.sePort.close()
         
    # 重新打开端口
    def reopen(self):
        print('reopen ...')
        self.sePort.close()
        self.sePort = serial.Serial(port='COM3', baudrate=19200, bytesize=8, stopbits=1, timeout=3)
        time.sleep(3)
 
    # 执行指令
    # 返回从串口读取的返回值
    def execCmd(self, cmd, end='\r'):
        cmd = cmd + end
        bCmd = cmd.encode('ascii')
        print('write:%s' % bCmd)
        self.sePort.write(bCmd)
        response = self.sePort.readall()
        print('read :%s' % response)
        print()
         
        return response
     
    # 发送英文短信
    # 正确返回 True;错误返回 False
    def __sendMessage(self, phoneNumber, message):
        if not self.execCmd('AT+CMGF=0').endswith(b'\r\nOK\r\n'):     #设置PDU格式
            return False
             
        if not self.execCmd('AT+CSCS="GSM"').endswith(b'\r\nOK\r\n'):    
            return False
         
        pdu_content = self.encodeMessage(phoneNumber, message)
        cmd = 'AT+CMGS=%d' % ((len(pdu_content)-2)/2)
        if not self.execCmd(cmd).endswith(b'\r\n> '):    
            return False
         
        # 十六进制的1A为结束符
        if not self.execCmd(pdu_content + '\x1A', end='').endswith(b'\r\nOK\r\n'):    
            return False
             
        return True
     
    # 调用私有方法 __sendMessage
    # 在失败时重新打开串口
    def sendMessage(self, phoneNumber, message):
        for _ in range(0, 3):
            if self.__sendMessage(phoneNumber, message):
                break
            self.reopen()
     
    # 将中文信息编码为PDU格式(ucs2)
    def encodeMessage(self, phoneNumber, message):
        tpdu = []
        if phoneNumber and message:             
            tpdu.append('001100')
            
            formatAddress = self.encodePhoneNumber(phoneNumber)
            tpdu.append(formatAddress)
 
            tpdu.append('0008AA')
             
            tpdu.append('%02X' % (len(message)*2))
            content = ''.join(['%02X' % c for c in message.encode('utf_16_be')])
            tpdu.append(content)
         
        print(''.join(tpdu))
        return ''.join(tpdu)
    
    # 将手机号编码为pdu所需格式
    def encodePhoneNumber(self, phoneNumber):
        #号码为11位数字
        if not re.match(r'\d{11}', phoneNumber):
            print('Error phoneNumber: %s' % phoneNumber)
        
        addr = '0D'  #号码长度13
        addr += '91'      #Type-of-Address,目标地址格式(TON/NPI) 
        #加86、F补成偶数个(14位),分7组翻转
        phoneNumber = '86' + phoneNumber + 'F'
        for i in range(0, 7):       
            addr += phoneNumber[2*i+1] + phoneNumber[2*i]
            
        return addr
     
if __name__ == '__main__':
    messager = Messager()
    messager.sendMessage('18688754379', '你好')
    messager.sendMessage('18688754379', '大家好')


相关阅读:

1、PDU编码(非常经典)

2、在线PDU格式编码/解码

3、使用python实现短信PDU编码


*** walker ***