Package phones :: Module com_moto
[hide private]
[frames] | no frames]

Source Code for Module phones.com_moto

  1  ### BITPIM 
  2  ### 
  3  ### Copyright (C) 2006 Joe Pham <djpham@bitpim.org> 
  4  ### 
  5  ### This program is free software; you can redistribute it and/or modify 
  6  ### it under the terms of the BitPim license as detailed in the LICENSE file. 
  7  ### 
  8  ### $Id: com_moto.py 4516 2007-12-21 22:00:57Z djpham $ 
  9   
 10  """Communicate with Motorola phones using AT commands.  This code is for all Motorola phones 
 11  with specific subclasses for CDMA and GSM variants""" 
 12   
 13  # system modules 
 14  import sha 
 15   
 16  # BitPim modules 
 17  import commport 
 18  import com_etsi 
 19  import phoneinfo 
 20  import prototypes 
 21  import p_moto 
 22  import sms 
 23   
24 -class Phone(com_etsi.Phone):
25 """Talk to a generic Motorola phone. 26 """ 27 desc='Motorola' 28 protocolclass=p_moto 29 MODEPHONEBOOK="modephonebook" 30
31 - def __init__(self, logtarget, commport):
32 super(Phone,self).__init__(logtarget, commport) 33 self.mode=self.MODENONE
34 35 # Common/Support routines
36 - def set_mode(self, mode):
37 """Set the current phone mode""" 38 _req=self.protocolclass.modeset() 39 _req.mode=mode 40 self.sendATcommand(_req, None) 41 self.comm.sendatcommand('')
42
43 - def charset_ascii(self):
44 """Set the charset to ASCII (default)""" 45 _req=self.protocolclass.charset_set_req() 46 _req.charset=self.protocolclass.CHARSET_ASCII 47 self.sendATcommand(_req, None)
48
49 - def charset_ucs2(self):
50 """Set the charset to UCS-2, used for most string values""" 51 _req=self.protocolclass.charset_set_req() 52 _req.charset=self.protocolclass.CHARSET_UCS2 53 self.sendATcommand(_req, None)
54
55 - def select_phonebook(self, phonebook=None):
56 _req=self.protocolclass.select_phonebook_req() 57 if phonebook: 58 _req.pb_type=phonebook 59 self.sendATcommand(_req, None)
60
61 - def ucs2_to_ascii(self, v):
62 """convert an UCS-2 to ASCII string""" 63 return v.decode('hex').decode('utf_16be')
64 - def ascii_to_ucs2(self, v):
65 """convert an ascii string to UCS-2""" 66 return v.encode('utf_16be').encode('hex').upper()
67
68 - def _setmodemodemtophonebook(self):
69 self.log('Switching from modem to phonebook') 70 self.set_mode(self.protocolclass.MODE_PHONEBOOK) 71 return True
72
73 - def _setmodemodem(self):
74 self.log('Switching to modem') 75 try: 76 self.comm.sendatcommand('E0V1') 77 self.set_mode(self.protocolclass.MODE_MODEM) 78 return True 79 except: 80 return False
81
82 - def _setmodephonebook(self):
83 self.setmode(self.MODEMODEM) 84 self.setmode(self.MODEPHONEBOOK) 85 return True
86
87 - def _setmodephonebooktomodem(self):
88 self.log('Switching from phonebook to modem') 89 self.set_mode(self.protocolclass.MODE_MODEM) 90 return True
91
92 - def decode_utf16(self, v):
93 """Decode a Motorola unicode string""" 94 # 1st, find the terminator if exist 95 _idx=v.find('\x00\x00') 96 # decode the string 97 if _idx==-1: 98 return v.decode('utf_16_le') 99 else: 100 return v[:_idx+1].decode('utf_16_le')
101 - def encode_utf16(self, v):
102 """Encode a unicode/string into a Motorola unicode""" 103 return (v+'\x00').encode('utf_16_le')
104 105 # Phone info routines
106 - def get_model(self):
107 _req=self.protocolclass.model_req() 108 return self.sendATcommand(_req, self.protocolclass.string_resp)[0].value
109 - def get_manufacturer(self):
110 _req=self.protocolclass.manufacturer_req() 111 return self.sendATcommand(_req, self.protocolclass.string_resp)[0].value
112 - def get_phone_number(self):
113 self.setmode(self.MODEPHONEBOOK) 114 _req=self.protocolclass.number_req() 115 _s=self.sendATcommand(_req, self.protocolclass.string_resp)[0].value 116 self.setmode(self.MODEMODEM) 117 return _s.replace(',', '')
118 - def get_firmware_version(self):
119 _req=self.protocolclass.firmware_req() 120 return self.sendATcommand(_req, self.protocolclass.string_resp)[0].value
121 - def get_signal_quality(self):
122 _req=self.protocolclass.signal_req() 123 _res=self.sendATcommand(_req, self.protocolclass.signal_resp)[0] 124 return str(100*int(_res.rssi)/31)+'%'
125 - def get_battery_level(self):
126 _req=self.protocolclass.battery_req() 127 _res=self.sendATcommand(_req, self.protocolclass.battery_resp)[0] 128 return '%d%%'%_res.level
129 - def getphoneinfo(self, phone_info):
130 self.log('Getting Phone Info') 131 self.setmode(self.MODEMODEM) 132 _total_keys=len(phoneinfo.PhoneInfo.standard_keys) 133 for _cnt,e in enumerate(phoneinfo.PhoneInfo.standard_keys): 134 self.progress(_cnt, _total_keys, 135 'Retrieving Phone '+e[1]) 136 f=getattr(self, 'get_'+e[0]) 137 setattr(phone_info, e[0], f())
138 139 # fundamentals
140 - def getfundamentals(self, results):
141 """Gets information fundamental to interopating with the phone and UI. 142 143 Currently this is: 144 145 - 'uniqueserial' a unique serial number representing the phone 146 - 'groups' the phonebook groups 147 - 'wallpaper-index' map index numbers to names 148 - 'ringtone-index' map index numbers to ringtone names 149 150 This method is called before we read the phonebook data or before we 151 write phonebook data. 152 """ 153 self.log("Retrieving fundamental phone information") 154 self.progress(0, 100, 'Retrieving fundamental phone information') 155 self.setmode(self.MODEPHONEBOOK) 156 self.charset_ascii() 157 self.log("Phone serial number") 158 results['uniqueserial']=sha.new(self.get_esn()).hexdigest() 159 # now read groups 160 self.log("Reading group information") 161 results['groups']=self._get_groups() 162 # ringtone index 163 self.setmode(self.MODEBREW) 164 self.log('Reading Ringtone Index') 165 results['ringtone-index']=self._get_ringtone_index() 166 # getting wallpaper-index 167 self.log('Reading Wallpaper Index') 168 results['wallpaper-index']=self._get_wallpaper_index() 169 # Update the group ringtone ID 170 self._update_group_ringtone(results) 171 # All done 172 self.log("Fundamentals retrieved") 173 self.setmode(self.MODEMODEM) 174 return results
175
176 - def _update_group_ringtone(self, results):
177 _ringtone_index=results.get('ringtone-index', {}) 178 _groups=results.get('groups', {}) 179 for _key,_entry in _groups.items(): 180 _rt_idx=_entry['ringtone'] 181 _groups[_key]['ringtone']=_ringtone_index.get(_rt_idx, {}).get('name', None) 182 results['groups']=_groups
183 - def _setup_ringtone_name_dict(self, fundamentals):
184 """Create a new ringtone dict keyed by name for lookup""" 185 _rt_index=fundamentals.get('ringtone-index', {}) 186 _rt_name_index={} 187 for _key,_entry in _rt_index.items(): 188 _rt_name_index[_entry['name']]=_key 189 return _rt_name_index
190 - def _setup_group_name_dict(self, fundamentals):
191 """Create a new group dict keyed by name for lookup""" 192 _grp_name_index={} 193 for _key,_entry in fundamentals.get('groups', {}).items(): 194 _grp_name_index[_entry['name']]=_key 195 return _grp_name_index
196 197 # speed dial handling stuff
198 - def _mark_used_slots(self, entries, sd_slots, key_name):
199 """Mark the speed dial slots being used""" 200 for _key,_entry in enumerate(entries): 201 _sd=_entry.get('speeddial', None) 202 if _sd is not None: 203 if sd_slots[_sd]: 204 entries[_key]['speeddial']=None 205 else: 206 sd_slots[_sd]=_entry[key_name]
207
208 - def _get_sd_slot(self, entries, sd_slots, key_name):
209 """Populate the next available speed dial""" 210 for _index,_entry in enumerate(entries): 211 if _entry.get('speeddial', None) is None: 212 try: 213 _new_sd=sd_slots.index(False) 214 entries[_index]['speeddial']=_new_sd 215 sd_slots[_new_sd]=_entry[key_name] 216 except ValueError: 217 self.log('Failed to allocate speed dial value')
218
219 - def _ensure_speeddials(self, fundamentals):
220 """Make sure that each and every number/email/mail list has a 221 speed dial, which is being used as the slot/index number 222 """ 223 _pb_book=fundamentals.get('phonebook', {}) 224 _sd_slots=[False]*(self.protocolclass.PB_TOTAL_ENTRIES+1) 225 _sd_slots[0]=True 226 # go through the first round and mark the slots being used 227 for _key,_pb_entry in _pb_book.items(): 228 self._mark_used_slots(_pb_entry.get('numbers', []), _sd_slots, 229 'number') 230 self._mark_used_slots(_pb_entry.get('emails', []), _sd_slots, 231 'email') 232 self._mark_used_slots(_pb_entry.get('maillist', []), _sd_slots, 233 'entry') 234 # go through the 2nd time and populate unknown speed dials 235 for _key, _pb_entry in _pb_book.items(): 236 self._get_sd_slot(_pb_entry.get('numbers', []), _sd_slots, 237 'number') 238 self._get_sd_slot(_pb_entry.get('emails', []), _sd_slots, 239 'email') 240 self._get_sd_slot(_pb_entry.get('maillist', []), _sd_slots, 241 'entry') 242 return _sd_slots
243 244 # subclass needs to define these
245 - def _get_groups(self):
246 raise NotImplementedError
247 - def _get_ringtone_index(self):
248 raise NotImplementedError
249 - def _get_wallpaper_index(self):
250 raise NotImplementedError
251 - def _save_groups(self, fundamentals):
252 raise NotImplementedError
253 - def _build_pb_entry(self, entry, pb_book, fundamentals):
254 raise NotImplementedError
255 256 # Phonebook stuff
257 - def _build_pb_entry(self, entry, pb_book, fundamentals):
258 """Build a BitPim phonebook entry based on phone data. 259 Need to to implement in subclass for each phone 260 """ 261 raise NotImplementedError
262 - def _update_mail_list(self, pb_book, fundamentals):
263 raise NotImplementedError
264
265 - def getphonebook(self, result):
266 """Reads the phonebook data. The L{getfundamentals} information will 267 already be in result.""" 268 self.log('Getting phonebook') 269 self.setmode(self.MODEPHONEBOOK) 270 # pick the main phonebook 271 self.select_phonebook() 272 # setting up 273 pb_book={} 274 result['pb_list']=[] 275 result['sd_dict']={} 276 # loop through and read 10 entries at a time 277 _total_entries=self.protocolclass.PB_TOTAL_ENTRIES 278 _req=self.protocolclass.read_pb_req() 279 for _start_idx in range(1, _total_entries+1, 10): 280 _end_idx=min(_start_idx+9, _total_entries) 281 _req.start_index=_start_idx 282 _req.end_index=_end_idx 283 for _retry_cnt in range(2): 284 try: 285 self.progress(_end_idx, _total_entries, 286 'Reading contact entry %d to %d'%(_start_idx, _end_idx)) 287 _res=self.sendATcommand(_req, self.protocolclass.read_pb_resp) 288 for _entry in _res: 289 self._build_pb_entry(_entry, pb_book, result) 290 break 291 except: 292 if _retry_cnt: 293 self.log('Failed to read phonebook data') 294 else: 295 self.log('Failed to read phonebook data, retrying...') 296 self._update_mail_list(pb_book, result) 297 self.setmode(self.MODEMODEM) 298 del result['pb_list'], result['sd_dict'] 299 _keys=result['groups'].keys() 300 result['categories']=[x['name'] for _,x in result['groups'].items()] 301 result['phonebook']=pb_book 302 return pb_book
303
304 - def savephonebook(self, result):
305 "Saves out the phonebook" 306 self.log('Writing phonebook') 307 self.setmode(self.MODEPHONEBOOK) 308 # setting up what we need 309 result['ringtone-name-index']=self._setup_ringtone_name_dict(result) 310 result['group-name-index']=self._setup_group_name_dict(result) 311 result['sd-slots']=self._ensure_speeddials(result) 312 # save the group 313 self._save_groups(result) 314 self._write_pb_entries(result) 315 # clean up 316 del result['ringtone-name-index'], result['group-name-index'] 317 del result['sd-slots'] 318 self.setmode(self.MODEMODEM) 319 return result
320 321 # Calendar Stuff------------------------------------------------------------
322 - def _build_cal_entry(self, entry, calendar, fundamentals):
323 """Build a BitPim calendar object from phonebook data""" 324 raise NotImplementedError
325
326 - def del_calendar_entry(self, index):
327 _req=self.protocolclass.calendar_write_ex_req() 328 _req.index=index 329 _req.nth_event=0 330 _req.ex_event_flag=0 331 self.sendATcommand(_req, None)
332
333 - def lock_calendar(self, lock=True):
334 """Lock the calendar to access it""" 335 _req=self.protocolclass.calendar_lock_req() 336 if lock: 337 _req.lock=1 338 else: 339 _req.lock=0 340 self.sendATcommand(_req, None)
341
342 - def getcalendar(self,result):
343 """Read all calendars from the phone""" 344 self.log('Reading calendar entries') 345 self.setmode(self.MODEPHONEBOOK) 346 self.lock_calendar() 347 _total_entries=self.protocolclass.CAL_TOTAL_ENTRIES 348 _max_entry=self.protocolclass.CAL_MAX_ENTRY 349 _req=self.protocolclass.calendar_read_req() 350 _calendar={ 'exceptions': [] } 351 for _start_idx in range(0, _total_entries, 10): 352 _end_idx=min(_start_idx+9, _max_entry) 353 _req.start_index=_start_idx 354 _req.end_index=_end_idx 355 for _retry in range(2): 356 try: 357 self.progress(_end_idx, _total_entries, 358 'Reading calendar entry %d to %d'%(_start_idx, _end_idx)) 359 _res=self.sendATcommand(_req, self.protocolclass.calendar_req_resp) 360 for _entry in _res: 361 self._build_cal_entry(_entry, _calendar, result) 362 except: 363 if _retry: 364 self.log('Failed to read calendar data') 365 else: 366 self.log('Failed to read calendar data, retrying ...') 367 self._process_exceptions(_calendar) 368 del _calendar['exceptions'] 369 self.lock_calendar(False) 370 self.setmode(self.MODEMODEM) 371 result['calendar']=_calendar 372 return result
373
374 - def savecalendar(self, result, merge):
375 """Save calendar entries to the phone""" 376 self.log('Writing calendar entries') 377 self.setmode(self.MODEPHONEBOOK) 378 self.lock_calendar() 379 self._write_calendar_entries(result) 380 self.lock_calendar(False) 381 self.setmode(self.MODEMODEM) 382 return result
383 384 # SMS stuff----------------------------------------------------------------
385 - def select_default_SMS(self):
386 """Select the default SMS storage""" 387 _req=self.protocolclass.sms_sel_req() 388 self.sendATcommand(_req, None)
389
390 - def _process_sms_header(self, _header, _entry):
391 _addr=_header.sms_addr.strip(' ').replace('"', '') 392 if _header.has_date: 393 _entry.datetime=_header.sms_date 394 if _header.sms_type==self.protocolclass.SMS_REC_UNREAD: 395 _entry.read=False 396 _entry.folder=_entry.Folder_Inbox 397 _entry._from=_addr 398 elif _header.sms_type==self.protocolclass.SMS_REC_READ: 399 _entry.read=True 400 _entry.folder=_entry.Folder_Inbox 401 _entry._from=_addr 402 elif _header.sms_type==self.protocolclass.SMS_STO_UNSENT: 403 _entry._to=_addr 404 _entry.folder=_entry.Folder_Saved 405 else: 406 _entry._to=_addr 407 _entry.folder=_entry.Folder_Sent
408
409 - def _process_sms_text(self, res, entry):
410 _s=res[1] 411 _open_p=_s.find('(') 412 _close_p=_s.find(')') 413 if _open_p==0 and _close_p!=-1: 414 # extract the subj 415 entry.subject=_s[1:_close_p] 416 res[1]=_s[_close_p+1:] 417 entry.text='\n'.join(res[1:])
418
419 - def _process_sms_result(self, _res, _sms, fundamentals):
420 """Process an SMS result as returned from the phone""" 421 _buf=prototypes.buffer(_res[0]) 422 _header=self.protocolclass.sms_m_read_resp() 423 _field_cnt=len(_res[0].split(',')) 424 _header.has_date=_field_cnt>2 425 _header.date_terminated=_field_cnt>4 # the date field also has a ',' 426 _header.readfrombuffer(_buf, logtitle='Reading SMS Response') 427 _entry=sms.SMSEntry() 428 self._process_sms_header(_header, _entry) 429 self._process_sms_text(_res, _entry) 430 _sms[_entry.id]=_entry
431
432 - def getsms(self, fundamentals):
433 """Read SMS messages from the phone""" 434 self.log('Reading SMS messages') 435 self.setmode(self.MODEPHONEBOOK) 436 _sms={} 437 try: 438 self.select_default_SMS() 439 _req=self.protocolclass.sms_list_req() 440 _sms_list=self.sendATcommand(_req, None, True) 441 _sms_item=self.protocolclass.sms_list_resp() 442 for _entry in _sms_list: 443 _buf=prototypes.buffer(_entry) 444 _sms_item.readfrombuffer(_buf, 445 logtitle='Reading an SMS List Item') 446 try: 447 _res=self.comm.sendatcommand('+MMGR=%d'%_sms_item.index, 448 retry=True) 449 self._process_sms_result(_res, _sms, fundamentals) 450 except commport.ATError: 451 self.log('Failed to read SMS Item %d'%_sms_item.index) 452 except: 453 if __debug__: 454 self.setmode(self.MODEMODEM) 455 raise 456 self.setmode(self.MODEMODEM) 457 fundamentals['canned_msg']=[] 458 fundamentals['sms']=_sms 459 return fundamentals
460 461 #------------------------------------------------------------------------------ 462 parentprofile=com_etsi.Profile
463 -class Profile(parentprofile):
464 BP_Calendar_Version=3
465