PyXR

c:\projects\bitpim\src \ phones \ com_moto.py



0001 ### BITPIM
0002 ###
0003 ### Copyright (C) 2006 Joe Pham <djpham@bitpim.org>
0004 ###
0005 ### This program is free software; you can redistribute it and/or modify
0006 ### it under the terms of the BitPim license as detailed in the LICENSE file.
0007 ###
0008 ### $Id: com_moto.py 4516 2007-12-21 22:00:57Z djpham $
0009 
0010 """Communicate with Motorola phones using AT commands.  This code is for all Motorola phones
0011 with specific subclasses for CDMA and GSM variants"""
0012 
0013 # system modules
0014 import sha
0015 
0016 # BitPim modules
0017 import commport
0018 import com_etsi
0019 import phoneinfo
0020 import prototypes
0021 import p_moto
0022 import sms
0023 
0024 class Phone(com_etsi.Phone):
0025     """Talk to a generic Motorola phone.
0026     """
0027     desc='Motorola'
0028     protocolclass=p_moto
0029     MODEPHONEBOOK="modephonebook"
0030 
0031     def __init__(self, logtarget, commport):
0032         super(Phone,self).__init__(logtarget, commport)
0033         self.mode=self.MODENONE
0034 
0035     # Common/Support routines
0036     def set_mode(self, mode):
0037         """Set the current phone mode"""
0038         _req=self.protocolclass.modeset()
0039         _req.mode=mode
0040         self.sendATcommand(_req, None)
0041         self.comm.sendatcommand('')
0042         
0043     def charset_ascii(self):
0044         """Set the charset to ASCII (default)"""
0045         _req=self.protocolclass.charset_set_req()
0046         _req.charset=self.protocolclass.CHARSET_ASCII
0047         self.sendATcommand(_req, None)
0048         
0049     def charset_ucs2(self):
0050         """Set the charset to UCS-2, used for most string values"""
0051         _req=self.protocolclass.charset_set_req()
0052         _req.charset=self.protocolclass.CHARSET_UCS2
0053         self.sendATcommand(_req, None)
0054 
0055     def select_phonebook(self, phonebook=None):
0056         _req=self.protocolclass.select_phonebook_req()
0057         if phonebook:
0058             _req.pb_type=phonebook
0059         self.sendATcommand(_req, None)
0060 
0061     def ucs2_to_ascii(self, v):
0062         """convert an UCS-2 to ASCII string"""
0063         return v.decode('hex').decode('utf_16be')
0064     def ascii_to_ucs2(self, v):
0065         """convert an ascii string to UCS-2"""
0066         return v.encode('utf_16be').encode('hex').upper()
0067 
0068     def _setmodemodemtophonebook(self):
0069         self.log('Switching from modem to phonebook')
0070         self.set_mode(self.protocolclass.MODE_PHONEBOOK)
0071         return True
0072 
0073     def _setmodemodem(self):
0074         self.log('Switching to modem')
0075         try:
0076             self.comm.sendatcommand('E0V1')
0077             self.set_mode(self.protocolclass.MODE_MODEM)
0078             return True
0079         except:
0080             return False
0081 
0082     def _setmodephonebook(self):
0083         self.setmode(self.MODEMODEM)
0084         self.setmode(self.MODEPHONEBOOK)
0085         return True
0086         
0087     def _setmodephonebooktomodem(self):
0088         self.log('Switching from phonebook to modem')
0089         self.set_mode(self.protocolclass.MODE_MODEM)
0090         return True
0091 
0092     def decode_utf16(self, v):
0093         """Decode a Motorola unicode string"""
0094         # 1st, find the terminator if exist
0095         _idx=v.find('\x00\x00')
0096         # decode the string
0097         if _idx==-1:
0098             return v.decode('utf_16_le')
0099         else:
0100             return v[:_idx+1].decode('utf_16_le')
0101     def encode_utf16(self, v):
0102         """Encode a unicode/string into a Motorola unicode"""
0103         return (v+'\x00').encode('utf_16_le')
0104 
0105     # Phone info routines
0106     def get_model(self):
0107         _req=self.protocolclass.model_req()
0108         return self.sendATcommand(_req, self.protocolclass.string_resp)[0].value
0109     def get_manufacturer(self):
0110         _req=self.protocolclass.manufacturer_req()
0111         return self.sendATcommand(_req, self.protocolclass.string_resp)[0].value
0112     def get_phone_number(self):
0113         self.setmode(self.MODEPHONEBOOK)
0114         _req=self.protocolclass.number_req()
0115         _s=self.sendATcommand(_req, self.protocolclass.string_resp)[0].value
0116         self.setmode(self.MODEMODEM)
0117         return _s.replace(',', '')
0118     def get_firmware_version(self):
0119         _req=self.protocolclass.firmware_req()
0120         return self.sendATcommand(_req, self.protocolclass.string_resp)[0].value
0121     def get_signal_quality(self):
0122         _req=self.protocolclass.signal_req()
0123         _res=self.sendATcommand(_req, self.protocolclass.signal_resp)[0]
0124         return str(100*int(_res.rssi)/31)+'%'
0125     def get_battery_level(self):
0126         _req=self.protocolclass.battery_req()
0127         _res=self.sendATcommand(_req, self.protocolclass.battery_resp)[0]
0128         return '%d%%'%_res.level
0129     def getphoneinfo(self, phone_info):
0130         self.log('Getting Phone Info')
0131         self.setmode(self.MODEMODEM)
0132         _total_keys=len(phoneinfo.PhoneInfo.standard_keys)
0133         for _cnt,e in enumerate(phoneinfo.PhoneInfo.standard_keys):
0134             self.progress(_cnt, _total_keys,
0135                           'Retrieving Phone '+e[1])
0136             f=getattr(self, 'get_'+e[0])
0137             setattr(phone_info, e[0], f())
0138 
0139     # fundamentals
0140     def getfundamentals(self, results):
0141         """Gets information fundamental to interopating with the phone and UI.
0142 
0143         Currently this is:
0144 
0145           - 'uniqueserial'     a unique serial number representing the phone
0146           - 'groups'           the phonebook groups
0147           - 'wallpaper-index'  map index numbers to names
0148           - 'ringtone-index'   map index numbers to ringtone names
0149 
0150         This method is called before we read the phonebook data or before we
0151         write phonebook data.
0152         """
0153         self.log("Retrieving fundamental phone information")
0154         self.progress(0, 100, 'Retrieving fundamental phone information')
0155         self.setmode(self.MODEPHONEBOOK)
0156         self.charset_ascii()
0157         self.log("Phone serial number")
0158         results['uniqueserial']=sha.new(self.get_esn()).hexdigest()
0159         # now read groups
0160         self.log("Reading group information")
0161         results['groups']=self._get_groups()
0162         # ringtone index
0163         self.setmode(self.MODEBREW)
0164         self.log('Reading Ringtone Index')
0165         results['ringtone-index']=self._get_ringtone_index()
0166         # getting wallpaper-index
0167         self.log('Reading Wallpaper Index')
0168         results['wallpaper-index']=self._get_wallpaper_index()
0169         # Update the group ringtone ID
0170         self._update_group_ringtone(results)
0171         # All done
0172         self.log("Fundamentals retrieved")
0173         self.setmode(self.MODEMODEM)
0174         return results
0175 
0176     def _update_group_ringtone(self, results):
0177         _ringtone_index=results.get('ringtone-index', {})
0178         _groups=results.get('groups', {})
0179         for _key,_entry in _groups.items():
0180             _rt_idx=_entry['ringtone']
0181             _groups[_key]['ringtone']=_ringtone_index.get(_rt_idx, {}).get('name', None)
0182         results['groups']=_groups
0183     def _setup_ringtone_name_dict(self, fundamentals):
0184         """Create a new ringtone dict keyed by name for lookup"""
0185         _rt_index=fundamentals.get('ringtone-index', {})
0186         _rt_name_index={}
0187         for _key,_entry in _rt_index.items():
0188             _rt_name_index[_entry['name']]=_key
0189         return _rt_name_index
0190     def _setup_group_name_dict(self, fundamentals):
0191         """Create a new group dict keyed by name for lookup"""
0192         _grp_name_index={}
0193         for _key,_entry in fundamentals.get('groups', {}).items():
0194             _grp_name_index[_entry['name']]=_key
0195         return _grp_name_index
0196 
0197     # speed dial handling stuff
0198     def _mark_used_slots(self, entries, sd_slots, key_name):
0199         """Mark the speed dial slots being used"""
0200         for _key,_entry in enumerate(entries):
0201             _sd=_entry.get('speeddial', None)
0202             if _sd is not None:
0203                 if sd_slots[_sd]:
0204                     entries[_key]['speeddial']=None
0205                 else:
0206                     sd_slots[_sd]=_entry[key_name]
0207 
0208     def _get_sd_slot(self, entries, sd_slots, key_name):
0209         """Populate the next available speed dial"""
0210         for _index,_entry in enumerate(entries):
0211             if _entry.get('speeddial', None) is None:
0212                 try:
0213                     _new_sd=sd_slots.index(False)
0214                     entries[_index]['speeddial']=_new_sd
0215                     sd_slots[_new_sd]=_entry[key_name]
0216                 except ValueError:
0217                     self.log('Failed to allocate speed dial value')
0218                 
0219     def _ensure_speeddials(self, fundamentals):
0220         """Make sure that each and every number/email/mail list has a
0221         speed dial, which is being used as the slot/index number
0222         """
0223         _pb_book=fundamentals.get('phonebook', {})
0224         _sd_slots=[False]*(self.protocolclass.PB_TOTAL_ENTRIES+1)
0225         _sd_slots[0]=True
0226         # go through the first round and mark the slots being used
0227         for _key,_pb_entry in _pb_book.items():
0228             self._mark_used_slots(_pb_entry.get('numbers', []), _sd_slots,
0229                                   'number')
0230             self._mark_used_slots(_pb_entry.get('emails', []), _sd_slots,
0231                                   'email')
0232             self._mark_used_slots(_pb_entry.get('maillist', []), _sd_slots,
0233                                   'entry')
0234         # go through the 2nd time and populate unknown speed dials
0235         for _key, _pb_entry in _pb_book.items():
0236             self._get_sd_slot(_pb_entry.get('numbers', []), _sd_slots,
0237                               'number')
0238             self._get_sd_slot(_pb_entry.get('emails', []), _sd_slots,
0239                               'email')
0240             self._get_sd_slot(_pb_entry.get('maillist', []), _sd_slots,
0241                               'entry')
0242         return _sd_slots
0243 
0244     # subclass needs to define these
0245     def _get_groups(self):
0246         raise NotImplementedError
0247     def _get_ringtone_index(self):
0248         raise NotImplementedError
0249     def _get_wallpaper_index(self):
0250         raise NotImplementedError
0251     def _save_groups(self, fundamentals):
0252         raise NotImplementedError
0253     def _build_pb_entry(self, entry, pb_book, fundamentals):
0254         raise NotImplementedError
0255 
0256     # Phonebook stuff
0257     def _build_pb_entry(self, entry, pb_book, fundamentals):
0258         """Build a BitPim phonebook entry based on phone data.
0259         Need to to implement in subclass for each phone
0260         """
0261         raise NotImplementedError
0262     def _update_mail_list(self, pb_book, fundamentals):
0263         raise NotImplementedError
0264 
0265     def getphonebook(self, result):
0266         """Reads the phonebook data.  The L{getfundamentals} information will
0267         already be in result."""
0268         self.log('Getting phonebook')
0269         self.setmode(self.MODEPHONEBOOK)
0270         # pick the main phonebook
0271         self.select_phonebook()
0272         # setting up
0273         pb_book={}
0274         result['pb_list']=[]
0275         result['sd_dict']={}
0276         # loop through and read 10 entries at a time
0277         _total_entries=self.protocolclass.PB_TOTAL_ENTRIES
0278         _req=self.protocolclass.read_pb_req()
0279         for _start_idx in range(1, _total_entries+1, 10):
0280             _end_idx=min(_start_idx+9, _total_entries)
0281             _req.start_index=_start_idx
0282             _req.end_index=_end_idx
0283             for _retry_cnt in range(2):
0284                 try:
0285                     self.progress(_end_idx, _total_entries,
0286                                   'Reading contact entry %d to %d'%(_start_idx, _end_idx))
0287                     _res=self.sendATcommand(_req, self.protocolclass.read_pb_resp)
0288                     for _entry in _res:
0289                         self._build_pb_entry(_entry, pb_book, result)
0290                     break
0291                 except:
0292                     if _retry_cnt:
0293                         self.log('Failed to read phonebook data')
0294                     else:
0295                         self.log('Failed to read phonebook data, retrying...')
0296         self._update_mail_list(pb_book, result)
0297         self.setmode(self.MODEMODEM)
0298         del result['pb_list'], result['sd_dict']
0299         _keys=result['groups'].keys()
0300         result['categories']=[x['name'] for _,x in result['groups'].items()]
0301         result['phonebook']=pb_book
0302         return pb_book
0303 
0304     def savephonebook(self, result):
0305         "Saves out the phonebook"
0306         self.log('Writing phonebook')
0307         self.setmode(self.MODEPHONEBOOK)
0308         # setting up what we need
0309         result['ringtone-name-index']=self._setup_ringtone_name_dict(result)
0310         result['group-name-index']=self._setup_group_name_dict(result)
0311         result['sd-slots']=self._ensure_speeddials(result)
0312         # save the group
0313         self._save_groups(result)
0314         self._write_pb_entries(result)
0315         # clean up
0316         del result['ringtone-name-index'], result['group-name-index']
0317         del result['sd-slots']
0318         self.setmode(self.MODEMODEM)
0319         return result
0320 
0321     # Calendar Stuff------------------------------------------------------------
0322     def _build_cal_entry(self, entry, calendar, fundamentals):
0323         """Build a BitPim calendar object from phonebook data"""
0324         raise NotImplementedError
0325 
0326     def del_calendar_entry(self, index):
0327         _req=self.protocolclass.calendar_write_ex_req()
0328         _req.index=index
0329         _req.nth_event=0
0330         _req.ex_event_flag=0
0331         self.sendATcommand(_req, None)
0332 
0333     def lock_calendar(self, lock=True):
0334         """Lock the calendar to access it"""
0335         _req=self.protocolclass.calendar_lock_req()
0336         if lock:
0337             _req.lock=1
0338         else:
0339             _req.lock=0
0340         self.sendATcommand(_req, None)
0341 
0342     def getcalendar(self,result):
0343         """Read all calendars from the phone"""
0344         self.log('Reading calendar entries')
0345         self.setmode(self.MODEPHONEBOOK)
0346         self.lock_calendar()
0347         _total_entries=self.protocolclass.CAL_TOTAL_ENTRIES
0348         _max_entry=self.protocolclass.CAL_MAX_ENTRY
0349         _req=self.protocolclass.calendar_read_req()
0350         _calendar={ 'exceptions': [] }
0351         for _start_idx in range(0, _total_entries, 10):
0352             _end_idx=min(_start_idx+9, _max_entry)
0353             _req.start_index=_start_idx
0354             _req.end_index=_end_idx
0355             for _retry in range(2):
0356                 try:
0357                     self.progress(_end_idx, _total_entries,
0358                                   'Reading calendar entry %d to %d'%(_start_idx, _end_idx))
0359                     _res=self.sendATcommand(_req, self.protocolclass.calendar_req_resp)
0360                     for _entry in _res:
0361                         self._build_cal_entry(_entry, _calendar, result)
0362                 except:
0363                     if _retry:
0364                         self.log('Failed to read calendar data')
0365                     else:
0366                         self.log('Failed to read calendar data, retrying ...')
0367         self._process_exceptions(_calendar)
0368         del _calendar['exceptions']
0369         self.lock_calendar(False)
0370         self.setmode(self.MODEMODEM)
0371         result['calendar']=_calendar
0372         return result
0373 
0374     def savecalendar(self, result, merge):
0375         """Save calendar entries to the phone"""
0376         self.log('Writing calendar entries')
0377         self.setmode(self.MODEPHONEBOOK)
0378         self.lock_calendar()
0379         self._write_calendar_entries(result)
0380         self.lock_calendar(False)
0381         self.setmode(self.MODEMODEM)
0382         return result
0383 
0384     # SMS stuff----------------------------------------------------------------
0385     def select_default_SMS(self):
0386         """Select the default SMS storage"""
0387         _req=self.protocolclass.sms_sel_req()
0388         self.sendATcommand(_req, None)
0389 
0390     def _process_sms_header(self, _header, _entry):
0391         _addr=_header.sms_addr.strip(' ').replace('"', '')
0392         if _header.has_date:
0393             _entry.datetime=_header.sms_date
0394         if _header.sms_type==self.protocolclass.SMS_REC_UNREAD:
0395             _entry.read=False
0396             _entry.folder=_entry.Folder_Inbox
0397             _entry._from=_addr
0398         elif _header.sms_type==self.protocolclass.SMS_REC_READ:
0399             _entry.read=True
0400             _entry.folder=_entry.Folder_Inbox
0401             _entry._from=_addr
0402         elif _header.sms_type==self.protocolclass.SMS_STO_UNSENT:
0403             _entry._to=_addr
0404             _entry.folder=_entry.Folder_Saved
0405         else:
0406             _entry._to=_addr
0407             _entry.folder=_entry.Folder_Sent
0408 
0409     def _process_sms_text(self, res, entry):
0410         _s=res[1]
0411         _open_p=_s.find('(')
0412         _close_p=_s.find(')')
0413         if _open_p==0 and _close_p!=-1:
0414             # extract the subj
0415             entry.subject=_s[1:_close_p]
0416             res[1]=_s[_close_p+1:]
0417         entry.text='\n'.join(res[1:])
0418 
0419     def _process_sms_result(self, _res, _sms, fundamentals):
0420         """Process an SMS result as returned from the phone"""
0421         _buf=prototypes.buffer(_res[0])
0422         _header=self.protocolclass.sms_m_read_resp()
0423         _field_cnt=len(_res[0].split(','))
0424         _header.has_date=_field_cnt>2
0425         _header.date_terminated=_field_cnt>4    # the date field also has a ','
0426         _header.readfrombuffer(_buf, logtitle='Reading SMS Response')
0427         _entry=sms.SMSEntry()
0428         self._process_sms_header(_header, _entry)
0429         self._process_sms_text(_res, _entry)
0430         _sms[_entry.id]=_entry
0431 
0432     def getsms(self, fundamentals):
0433         """Read SMS messages from the phone"""
0434         self.log('Reading SMS messages')
0435         self.setmode(self.MODEPHONEBOOK)
0436         _sms={}
0437         try:
0438             self.select_default_SMS()
0439             _req=self.protocolclass.sms_list_req()
0440             _sms_list=self.sendATcommand(_req, None, True)
0441             _sms_item=self.protocolclass.sms_list_resp()
0442             for _entry in _sms_list:
0443                 _buf=prototypes.buffer(_entry)
0444                 _sms_item.readfrombuffer(_buf,
0445                                          logtitle='Reading an SMS List Item')
0446                 try:
0447                     _res=self.comm.sendatcommand('+MMGR=%d'%_sms_item.index,
0448                                                  retry=True)
0449                     self._process_sms_result(_res, _sms, fundamentals)
0450                 except commport.ATError:
0451                     self.log('Failed to read SMS Item %d'%_sms_item.index)
0452         except:
0453             if __debug__:
0454                 self.setmode(self.MODEMODEM)
0455                 raise
0456         self.setmode(self.MODEMODEM)
0457         fundamentals['canned_msg']=[]
0458         fundamentals['sms']=_sms
0459         return fundamentals
0460 
0461 #------------------------------------------------------------------------------
0462 parentprofile=com_etsi.Profile
0463 class Profile(parentprofile):
0464     BP_Calendar_Version=3
0465 

Generated by PyXR 0.9.4