一、git仓库
https://github.com/tardigrade888/py-pdu
二、代码
2.1 ul_gsm.py
#!/usr/bin/python
# -*- coding: UTF-8 -*-
# ------------------------------------------------------------------------------
def b_swap( b ):
'swap nibbles'
return ((b >> 4) & 0xF) | ((b << 4) & 0xF0)
# ------------------------------------------------------------------------------
def bin2bcd( n ):
'convert binary to BCD'
bcd,shift = 0,0
while n != 0:
bcd |= (n % 10) << shift
n //= 10
shift += 4
return bcd
def bcd2bin( bcd ):
'convert BCD to binary'
n,mul = 0,1
while bcd != 0:
n += (bcd & 0xF) * mul
bcd >>= 4
mul *= 10
return n
# ------------------------------------------------------------------------------
def n2h4( n ):
'convert 4 bits to 1 hex char'
n &= 0xF
return chr( (n + 48) if ( n < 10 ) else (n + 55) )
def n2h8( n ):
'convert 8 bits to 2 hex chars'
return n2h4( n >> 4 ) + n2h4( n )
def n2h16( n ):
'convert 16 bits to 4 hex chars'
return n2h8( n >> 8 ) + n2h8( n )
def n2h( n ):
'convert number to Hex'
h = []
while (n > 0):
h.insert(0, n2h4(n))
n >>= 4
h = ['0'] if len(h) == 0 else h
return ''.join(h)
# ------------------------------------------------------------------------------
def h2n4( h ):
'convert Hex digit to number'
b = ord(h[0])
b = (b - 48) if (48 <= b <= 57) else b
b = (b - 55) if (65 <= b <= 90) else b
b = (b - 87) if (97 <= b <= 122) else b
return b & 0xF
def h2n( h ):
'convert Hex string to number'
n = 0
for c in h:
n = (n << 4) | h2n4(c)
return n
# ------------------------------------------------------------------------------
def s_is_7bit( s ):
'check if all chars of s is 7-bit'
for c in s:
if ( ord(c) > 127 ):
return False
return True
# ------------------------------------------------------------------------------
def s_to_bytes( s ):
'convert string to array of bytes'
return [ ord(c) for c in s ]
def bytes_to_s( a ):
'convert array of bytes to string'
return u''.join([ chr(b) for b in a ])
# ------------------------------------------------------------------------------
def bytes_to_hex( a ):
'convert array of bytes to Hex string'
return ''.join([ n2h8(b) for b in a ])
def hex_to_bytes( h ):
'convert Hex string to bytes'
return [ h2n(h[i*2 : (i+1)*2]) for i in range(len(h) // 2) ]
# ------------------------------------------------------------------------------
def phone_clean( s ):
'clean non-number chars from phone string'
p = []
for c in s:
if ( 48 <= ord(c) <= 57 ):
p.append(c)
return ''.join(p)
def phone_digits( s ):
'number of phone digits'
s = phone_unpack(s) if phone_packed(s) else s
return len( phone_clean( s ))
def phone_packed( s ):
'check if s is packed'
return not isinstance(s, str)
def phone_pack( p ):
'pack phone number string to byte array'
p = phone_clean(p)
p = hex_to_bytes( (p + 'F') if ((len(p) % 2) == 1) else p )
for i in range(len(p)):
p[i] = b_swap(p[i])
return p
def phone_unpack( p ):
'unpack phone number bytes to string'
s = ['+']
for n in p:
s.append( n2h8(b_swap(n)) )
s = ''.join(s)
return s[:-1] if (s[-1] == 'F') else s
# ------------------------------------------------------------------------------
def pdu_7to8( a ):
'pack 7-bit array to 8-bit'
if len(a) > 0:
lenOut = len(a) - (len(a) >> 3)
a.append(0)
for i1 in range(len(a)-2):
for i2 in range(len(a)-2, i1-1, -1):
a[i2] |= ( 0x80 if ((a[i2+1] & 1) != 0) else 0 )
a[i2+1] >>= 1
a = a[0:lenOut]
return a
def pdu_8to7( a ):
'unpack 8-bit array to 7-bit'
lenOut = len(a) + (len(a) >> 3) #+ (1 if ((len(a) & 7) != 0) else 0)
while (len(a) < lenOut):
a.append(0)
if (len(a) > 0):
for i1 in range(lenOut-1):
for i2 in range(lenOut-2, i1-1, -1):
a[i2+1] = (a[i2+1] << 1) | (1 if ((a[i2] & 0x80) != 0) else 0)
for i in range(len(a)):
a[i] &= 0x7F
return a
def pdu_ucs2_to_bytes( a ):
'convert UCS2 words to bytes'
b = []
for w in a:
b.append( (w >> 8) & 0xFF )
b.append( w & 0xFF )
return b
def pdu_s_len( s ):
'calculate length of char/UCS2 string'
return len(s) if s_is_7bit(s) else len(s)*2
def pdu_s_pack( s ):
'pack char/UCS2 string to bytes'
a = s_to_bytes(s)
return pdu_7to8(a) if s_is_7bit(s) else pdu_ucs2_to_bytes(a)
def pdu_s_unpack( a, ucs2 ):
'unpack bytes to char/UCS2 string, ucs2=False/True'
return bytes_to_s( [(a[i] << 8) + a[i+1] for i in range(0, len(a), 2)] if ucs2 else pdu_8to7(a) )
# ------------------------------------------------------------------------------
def date_pack( year,month,date, hour,minute,second, tz ):
'pack date fields to byte array'
return [
b_swap( bin2bcd( year % 100)),
b_swap( bin2bcd( month % 13)),
b_swap( bin2bcd( date % 32)),
b_swap( bin2bcd( hour % 25)),
b_swap( bin2bcd( minute % 60)),
b_swap( bin2bcd( second % 60)),
b_swap( bin2bcd( tz))
]
def date_unpack( a ):
'unpack packed date array to unpacked [year,month,date, hour,minute,second,tz]'
return [
bcd2bin( b_swap( a[0] )) + 2000,
bcd2bin( b_swap( a[1] )),
bcd2bin( b_swap( a[2] )),
bcd2bin( b_swap( a[3] )),
bcd2bin( b_swap( a[4] )),
bcd2bin( b_swap( a[5] )),
bcd2bin( b_swap( a[6] )),
]
# ------------------------------------------------------------------------------
class Sms:
'Sms message class, for generation and parsing'
def __init__(self, o={}):
self.error = True
self.smsc,self.smscLen,self.smscType = '',0,145
self.sender,self.senderLen,self.senderType = '',0,145
self.text,self.textLen,self.textBytes,self.textRaw = '',0,0,[]
self.smsSubmit,self.firstOctet = 0,0x11
self.tpMsgRef,self.tpPID,self.tpDCS,self.tpVP = 0,0,0xFF,0xAA
self.year,self.month,self.date,self.hour,self.minute,self.second,self.tz = 2000,1,1,0,0,0,0
self.pdu,self.pduHex,self.pduLen = [],'',0
self.join(o)
def join(self, o={}):
'joins o dict with this Sms'
for k in o:
if k in self.__dict__:
self.__dict__[k] = o[k]
def decode_in( self, pdu ):
'decode incoming SMS PDU hex/bytes'
if isinstance(pdu, str):
pdu = hex_to_bytes(pdu)
self.pdu,self.pduHex,i = pdu,bytes_to_hex(pdu),0
if len(pdu) >= 25:
self.smscLen = pdu[0]
if self.smscLen > 0:
self.smscType = pdu[1]
self.smsc = phone_unpack( pdu[2 : self.smscLen + 1] )
i += self.smscLen + 1
self.firstOctet = pdu[i]; i += 1
self.senderLen = (pdu[i] >> 1) + (pdu[i] & 1); i += 1
self.senderType = pdu[i]; i += 1
self.sender = phone_unpack( pdu[ i : i+self.senderLen ] ); i += self.senderLen
self.tpPID = pdu[i]; i += 1
self.tpDCS = pdu[i]; i += 1
date = date_unpack( pdu[i : i + 7] )
self.year,self.month,self.date,self.hour,self.minute,self.second,self.tz = date[0],date[1],date[2], date[3],date[4],date[5], date[6]
i += 7
self.textLen = pdu[i]; i += 1
self.textBytes = (self.textLen - (self.textLen >> 3)) if (self.tpDCS == 0) else self.textLen
print( bytes_to_hex(pdu[ i : i+self.textBytes ]))
self.text = pdu_s_unpack( pdu[ i : i+self.textBytes ], self.tpDCS == 8 )
'''
sms.textLen = sms.bytes[sms.smscLen + sms.senderLen + 13];
sms.textRaw = sms.bytes.slice(sms.smscLen + sms.senderLen + 14, sms.smscLen + sms.senderLen + 14 + sms.textLen);
sms.text = tools.bytesToString(tools.gsm.pdu.to7bit(sms.textRaw));
'''
self.error = False
return self
def encode_out( self, o={} ):
'encode outcoming PDU SMS'
self.join( o )
pdu = []
if len(self.smsc) != 0:
smsc = phone_pack( self.smsc )
pdu = [ len(smsc) + 1, self.smscType ] + smsc
else:
pdu = [0]
self.tpDCS = 0 if s_is_7bit(self.text) else 8
pdu += [ self.firstOctet, self.tpMsgRef ]
pdu += [ phone_digits( self.sender ), self.senderType ] + phone_pack( self.sender )
pdu += [ self.tpPID, self.tpDCS, self.tpVP ]
pdu += [ len( self.text ) if (self.tpDCS == 0) else len( self.text ) * 2 ]
pdu += pdu_s_pack( self.text )
self.pdu,self.pduHex = pdu,bytes_to_hex(pdu)
return self
2.2 使用教程
import ul_gsm as gsm
# SMS-SUBMIT encode test
sms = gsm.Sms().encode_out( { 'smsc':'+38068240656', 'sender':'+380993435400', 'text':u'юыо' } )
print( sms.pduHex )
print( gsm.pdu_s_pack(u'юыо') )
# SMS-DELIVER decode test
pdu = '07917238010010F5040BC87238880900F10000993092516195800AE8329BFD4697D9EC37'
pdu = '07911326040000F0040B911346610089F60000208062917314080CC8F71D14969741F977FD07'
sms = gsm.Sms().decode_in( pdu )
print(sms.smsc, sms.smscType, sms.firstOctet, sms.senderLen, sms.senderType, sms.sender, sms.tpPID, sms.tpDCS)
print(sms.year,sms.month,sms.date, sms.hour,sms.minute,sms.second, sms.tz)
print(sms.textLen, sms.textBytes, sms.text)
# date pack/unpack
print( gsm.date_unpack( gsm.date_pack( 2014,10,12, 17,5,30, 0 )))
print( gsm.bytes_to_hex( gsm.date_pack( 2014,10,12, 17,5,30, 0 )))
# string pack/unpack
print( bytes_to_hex( pdu_s_pack(u'0123456789') ) )
print( pdu_s_unpack( pdu_s_pack(u'0123456789абв'), True ) )
# phone pack/unpack
print(phone_unpack(phone_pack('+38099-343-54-07-01')))
print hex_to_bytes('0001020304aabbeeff')
print(bytes_to_s( s_to_bytes( u'01234абвыв' ) ))
三、注意
encode_out会自动调整短信字符位数,当内容为字符串是,长度为7位,当有中文是长度为16位