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