Package phones ::
Module com_motov710
|
|
1
2
3
4
5
6
7
8
9
10 """Communicate with Motorola phones using AT commands"""
11
12 import datetime
13 import sha
14
15
16 import bpcalendar
17 import common
18 import commport
19 import com_brew
20 import com_moto_cdma
21 import fileinfo
22 import nameparser
23 import prototypes
24 import p_motov710
25 import helpids
26
27 -class Phone(com_moto_cdma.Phone):
28 """ Talk to a Motorola V710 phone"""
29 desc='Moto-V710'
30 helpid=helpids.ID_PHONE_MOTOV710
31 protocolclass=p_motov710
32 serialsname='motov710'
33
34 builtinringtones=(
35 (0, ('Silent',)),
36 (5, ('Vibe Dot', 'Vibe Dash', 'Vibe Dot Dot', 'Vibe Dot Dash',
37 'Vibe Pulse')),
38 (11, ('Alert', 'Standard', 'Bells', 'Triads', 'Up and Down',
39 'Jitters', 'Upbeat')),
40 (22, ('Guitar Strings', 'High Impact')),
41 (30, ('Moonlit Haze', 'Nightlife', 'Wind Chime', 'Random',
42 'Bit & Bytes', 'Door Bell', 'Ding', 'One Moment', 'Provincial',
43 'Harmonics', 'Interlude', 'Snaggle', 'Cosmic', 'Gyroscope')),
44 (49, ('Chimes high', 'Chimes low', 'Ding', 'TaDa', 'Notify', 'Drum',
45 'Claps', 'Fanfare', 'Chord high', 'Chord low'))
46 )
47
48 - def __init__(self, logtarget, commport):
51
52
71
73 """Save the Group(Category) data"""
74
75 _groups=fundamentals.get('groups', {})
76
77 _name2ringtone={}
78 for _key,_entry in _groups.items():
79 _name2ringtone[_entry['name']]=_entry['ringtone']
80
81 _keys=_groups.keys()
82 _keys.sort()
83 _group_list=[_groups[x]['name'] for x in _keys]
84
85 _cats=fundamentals.get('categories', [])
86 if 'General' not in _cats:
87
88 _cats.append('General')
89
90 _new_list=[x for x in _group_list if x in _cats]
91 _new_list+=[x for x in _cats if x not in _group_list]
92 _new_group={}
93 for _idx,_entry in enumerate(_new_list):
94 _new_group[_idx+1]={ 'name': _entry,
95 'ringtone': _name2ringtone.get(_entry, None) }
96 _rt_name_index=fundamentals.get('ringtone-name-index', {})
97
98 _req=self.protocolclass.read_group_req()
99 _res=self.sendATcommand(_req, self.protocolclass.read_group_resp)
100 _req=self.protocolclass.del_group_req()
101 for e in _res:
102 if e.index==1:
103 continue
104 _req.index=e.index
105 self.sendATcommand(_req, None)
106
107 _req=self.protocolclass.write_group_req()
108 for _key,_entry in _new_group.items():
109 if _key==1:
110 continue
111 _req.index=_key
112 _req.name=_entry['name']
113 _req.ringtone=_rt_name_index.get(_entry.get('ringtone', None), 255)
114 self.sendATcommand(_req, None)
115 fundamentals['groups']=_new_group
116
137
150
151
152 - def _populate_pb_misc(self, pb_entry, pb_sub_entry, key_name,
153 entry, fundamentals):
154 """Populate ringtone, wallpaper to a number, email, or mail list
155 """
156
157 _rt_index=fundamentals.get('ringtone-index', {})
158 _rt_name=_rt_index.get(entry.ringtone, {}).get('name', None)
159 if _rt_name:
160 pb_sub_entry['ringtone']=_rt_name
161
162 if entry.picture_name:
163 pb_sub_entry['wallpaper']=common.basename(entry.picture_name)
164 if entry.is_primary:
165
166 pb_entry[key_name]=[pb_sub_entry]+pb_entry.get(key_name, [])
167 else:
168
169 pb_entry.setdefault(key_name, []).append(pb_sub_entry)
170
172 """extract the number into BitPim phonebook entry"""
173 _number_type=self.protocolclass.NUMBER_TYPE_NAME.get(entry.number_type, None)
174 _number={ 'number': entry.number, 'type': _number_type,
175 'speeddial': entry.index }
176 self._populate_pb_misc(pb_entry, _number, 'numbers', entry,
177 fundamentals)
178
179 fundamentals['sd_dict'][entry.index]=entry.number
180
182 """Extract the email component"""
183 _email={ 'email': entry.number,
184 'speeddial': entry.index }
185 self._populate_pb_misc(pb_entry, _email, 'emails', entry,
186 fundamentals)
187
188 fundamentals['sd_dict'][entry.index]=entry.number
189
191 """Extract the mailing list component"""
192 _num_list=entry.number.split(' ')
193 for _idx,_entry in enumerate(_num_list):
194 _num_list[_idx]=int(_entry)
195 _maillist={ 'entry': _num_list,
196 'speeddial': entry.index }
197 self._populate_pb_misc(pb_entry, _maillist, 'maillist', entry,
198 fundamentals)
199
200 fundamentals['sd_dict'][entry.index]=entry.number
201
202 - def _populate_pb_entry(self, pb_entry, entry, fundamentals):
203 """Populate a BitPim phonebook entry with one from the phone
204 """
205
206 _num_type=entry.number_type
207 if _num_type in self.protocolclass.NUMBER_TYPE:
208 self._populate_pb_number(pb_entry, entry, fundamentals)
209 elif _num_type in self.protocolclass.EMAIL_TYPE:
210 self._populate_pb_email(pb_entry, entry, fundamentals)
211
212
213
214
215 - def _build_pb_entry(self, entry, pb_book, fundamentals):
216 """Build a BitPim phonebook entry based on phone data.
217 """
218
219 try:
220 _idx=fundamentals['pb_list'].index(entry.name)
221 except ValueError:
222 _idx=None
223 if _idx is None:
224
225 _idx=len(fundamentals['pb_list'])
226 fundamentals['pb_list'].append(entry.name)
227 _group=fundamentals.get('groups', {}).get(entry.group, None)
228 pb_book[_idx]={ 'names': [{ 'full': entry.name }] }
229 if _group.get('name', None):
230 pb_book[_idx]['categories']=[{'category': _group['name'] }]
231 self._populate_pb_entry(pb_book[_idx], entry, fundamentals)
232
234 for _entry in entry['maillist']:
235 _name_list=[]
236 for m in _entry['entry']:
237 if sd_dict.has_key(m):
238 _name_list.append(sd_dict[m])
239
240 _entry['entry']='\x00\x00'.join(_name_list)
241
243 """Translate the contents of each mail list from speed-dial
244 into the corresponding names or numbers.
245 """
246 _sd_dict=fundamentals.get('sd_dict', {})
247 for _key,_entry in pb_book.items():
248 if _entry.has_key('maillist'):
249 self._update_a_mail_list(_entry, _sd_dict)
250
251 - def _del_pb_entry(self, entry_index):
252 """Delete the phonebook entry index from the phone"""
253 _req=self.protocolclass.del_pb_req()
254 _req.index=entry_index
255 try:
256 self.sendATcommand(_req, None)
257 except commport.ATError:
258 self.log('Failed to delete contact index %d'%entry_index)
259 except:
260 self.log('Failed to delete contact index %d'%entry_index)
261 if __debug__:
262 raise
263
265 """Return the group index of the group. Return 1(General) if none found
266 """
267 _grp_name=entry.get('categories', [{}])[0].get('category', None)
268 if not _grp_name:
269 return 1
270 for _key,_entry in fundamentals.get('groups', {}).items():
271 if _entry.get('name', None)==_grp_name:
272 return _key
273 return 1
274
276 """Return the ringtone code of this entry"""
277
278 _ringtone_name=fundamentals.get('phoneringtone', None)
279 if not _ringtone_name:
280 _ringtone_name=entry.get('ringtone', None)
281
282 if not _ringtone_name:
283 return 255
284 for _key,_entry in fundamentals.get('ringtone-index', {}).items():
285 if _entry['name']==_ringtone_name:
286 return _key
287 return 255
288
290 """Return the full path name for the wallpaper"""
291
292 _wp_name=fundamentals.get('phonewallpaper', None)
293
294 if not _wp_name:
295 _wp_name=entry.get('wallpaper', None)
296 if not _wp_name:
297 return ''
298 return '/a/'+self.protocolclass.WP_PATH+'/'+_wp_name
299
301 if fundamentals['primary']:
302 return 0
303 fundamentals['primary']=True
304 return 1
305
307 """Translate the mail list from text name to indices"""
308 _sd_list=fundamentals.get('sd-slots', [])
309 _names_list=entry.get('entry', '').split('\x00\x00')
310 _codes_list=[]
311 for _name in _names_list:
312 try:
313 _codes_list.append('%d'%_sd_list.index(_name))
314 except ValueError:
315 pass
316 return ' '.join(_codes_list)
317
318 - def _set_pb_entry_misc(self, req, entry, fundamentals):
319 """Set the ringtone, wallpaper, and primary parameters"""
320 req.ringtone=self._get_ringtone_code(entry, fundamentals)
321 req.is_primary=self._get_primary_code(fundamentals)
322 req.picture_name=self._get_wallpaper_name(entry, fundamentals)
323
324 - def _write_pb_entry_numbers(self, entry, req, fundamentals):
325 """Write all the numbers to the phone"""
326 req.local_type=self.protocolclass.LOCAL_TYPE_LOCAL
327 _cell1=False
328 for _entry in entry.get('numbers', []):
329 req.index=_entry['speeddial']
330 if req.index>self.protocolclass.PB_TOTAL_ENTRIES:
331
332 continue
333 req.number=_entry['number']
334 req.number_type=self.protocolclass.NUMBER_TYPE_CODE.get(
335 _entry['type'], self.protocolclass.NUMBER_TYPE_WORK)
336 if req.number_type==self.protocolclass.NUMBER_TYPE_MOBILE:
337 if _cell1:
338
339 req.number_type=self.protocolclass.NUMBER_TYPE_MOBILE2
340 else:
341 _cell1=True
342 self._set_pb_entry_misc(req, _entry, fundamentals)
343 self._del_pb_entry(req.index)
344 self.sendATcommand(req, None)
345
346 - def _write_pb_entry_emails(self, entry, req, fundamentals):
347 """Write all emails to the phone"""
348 req.number_type=self.protocolclass.NUMBER_TYPE_EMAIL
349 req.local_type=self.protocolclass.LOCAL_TYPE_UNKNOWN
350 _email1=False
351 for _entry in entry.get('emails', []):
352 req.index=_entry['speeddial']
353 if req.index>self.protocolclass.PB_TOTAL_ENTRIES:
354 continue
355 req.number=_entry['email']
356 if _email1:
357
358 req.number_type=self.protocolclass.NUMBER_TYPE_EMAIL2
359 else:
360 _email1=True
361 self._set_pb_entry_misc(req, _entry, fundamentals)
362 self._del_pb_entry(req.index)
363 self.sendATcommand(req, None)
364
365 - def _write_pb_entry_maillist(self, entry, req, fundamentals):
366 """Write all the mail lists to the phone"""
367 req.number_type=self.protocolclass.NUMBER_TYPE_MAILING_LIST
368 req.local_type=self.protocolclass.LOCAL_TYPE_UNKNOWN
369 for _entry in entry.get('maillist', []):
370 req.index=_entry['speeddial']
371 if req.index>self.protcolclass.PB_TOTAL_ENTRIES:
372 continue
373 req.number=self._build_pb_maillist(_entry, fundamentals)
374 self._set_pb_entry_misc(req, _entry, fundamentals)
375 self._del_pb_entry(req.index)
376 self.sendATcommand(req, None)
377
378 - def _write_pb_entry(self, entry, fundamentals):
379 """Write an phonebook entry to the phone"""
380 _req=self.protocolclass.write_pb_req()
381 _req.name=nameparser.getfullname(entry['names'][0])
382 _req.group=self._get_group_code(entry, fundamentals)
383 fundamentals['primary']=False
384 fundamentals['phonewallpaper']=entry.get('wallpapers', [{}])[0].get('wallpaper', None)
385 fundamentals['phoneringtone']=entry.get('ringtones', [{}])[0].get('ringtone', None)
386
387 self._write_pb_entry_numbers(entry, _req, fundamentals)
388
389 self._write_pb_entry_emails(entry, _req, fundamentals)
390
391
392 del fundamentals['primary'], fundamentals['phonewallpaper'],
393 fundamentals['phoneringtone']
394
396 """Write out the phonebook to the phone"""
397 _pb_book=fundamentals.get('phonebook', {})
398 _total_entries=len(_pb_book)
399 _cnt=0
400 for _key,_entry in _pb_book.items():
401 try:
402 _name=nameparser.getfullname(_entry['names'][0])
403 except:
404 _name='<Unknown>'
405 _cnt+=1
406 self.progress(_cnt, _total_entries,
407 'Writing contact %d: %s'%(_cnt, _name))
408 self._write_pb_entry(_entry, fundamentals)
409
410 for _index,_entry in enumerate(fundamentals.get('sd-slots', [])):
411 if not _entry:
412 self.progress(_index, self.protocolclass.PB_TOTAL_ENTRIES,
413 'Deleting contact slot %d'%_index)
414 self._del_pb_entry(_index)
415
416
417 - def _dow(self, ymd):
418 """Return a bitmap dayofweek"""
419 return 1<<(datetime.date(*ymd).isoweekday()%7)
420
444
445 - def _build_regular_cal_entry(self, entry, calendar, fundamentals):
446 """ Build a regular BitPim entry frm phone data"""
447 _bp_entry=bpcalendar.CalendarEntry()
448 _bp_entry.id=`entry.index`
449 _bp_entry.desc_loc=entry.title
450 _bp_entry.start=entry.start_date+entry.start_time
451 _t0=datetime.datetime(*_bp_entry.start)
452 _t1=_t0+datetime.timedelta(minutes=entry.duration)
453 _bp_entry.end=(_t1.year, _t1.month, _t1.day, _t1.hour, _t1.minute)
454 if entry.alarm_timed and entry.alarm_enabled:
455 _t3=datetime.datetime(*(entry.alarm_date+entry.alarm_time))
456 if _t0>=_t3:
457 _bp_entry.alarm=(_t0-_t3).seconds/60
458
459 _rep=self._build_repeat_part(entry, calendar, fundamentals)
460 if _rep:
461
462 _bp_entry.repeat=_rep
463 _bp_entry.end=bpcalendar.CalendarEntry.no_end_date+_bp_entry.end[3:]
464
465 calendar[_bp_entry.id]=_bp_entry
466
468 """Process all exceptions"""
469 for _idx,_exc in calendar.get('exceptions', []):
470 if not calendar.has_key(`_idx`):
471 continue
472 _rep=calendar[`_idx`].repeat
473 if _rep:
474 _date=calendar[`_idx`].start[:3]
475 for _i in range(_exc):
476 _date=_rep.next_date(_date)
477 calendar[`_idx`].suppress_repeat_entry(*_date)
478
479 - def _build_cal_entry(self, entry, calendar, fundamentals):
480 """Build a BitPim calendar object from phonebook data"""
481 if hasattr(entry, 'title'):
482
483 self._build_regular_cal_entry(entry, calendar, fundamentals)
484 else:
485
486 calendar['exceptions'].append((entry.index, entry.ex_event))
487
488 - def _build_phone_repeat_entry(self, entry, calendar):
489 """Build the repeat part of this phone entry"""
490 _rep=calendar.repeat
491 if _rep:
492
493 if _rep.repeat_type==_rep.daily:
494 entry.repeat_type=self.protocolclass.CAL_REP_DAILY
495 elif _rep.repeat_type==_rep.weekly:
496 entry.repeat_type=self.protocolclass.CAL_REP_WEEKLY
497 elif _rep.repeat_type==_rep.monthly:
498 if _rep.dow:
499 entry.repeat_type=self.protocolclass.CAL_REP_MONTHLY_NTH
500 else:
501 entry.repeat_type=self.protocolclass.CAL_REP_MONTHLY
502 else:
503 entry.repeat_type=self.protocolclass.CAL_REP_YEARLY
504 else:
505 entry.repeat_type=self.protocolclass.CAL_REP_NONE
506
507 - def _build_phone_alarm_entry(self, entry, calendar):
508 _alarm=calendar.alarm
509 if _alarm is None or _alarm==-1:
510 entry.alarm_timed=1
511 entry.alarm_enabled=0
512 entry.alarm_time=(0,0)
513 entry.alarm_date=(2000,0,0)
514 else:
515 entry.alarm_timed=1
516 entry.alarm_enabled=1
517 _d1=datetime.datetime(*calendar.start)-datetime.timedelta(minutes=_alarm)
518 entry.alarm_date=(_d1.year, _d1.month, _d1.day)
519 entry.alarm_time=(_d1.hour, _d1.minute)
520
521 - def _build_phone_entry(self, entry, calendar):
522 """Build a phone entry based on a BitPim calendar entry"""
523 entry.title=calendar.desc_loc
524 entry.start_time=calendar.start[3:]
525 entry.start_date=calendar.start[:3]
526 entry.duration=(datetime.datetime(*calendar.start[:3]+calendar.end[3:])-
527 datetime.datetime(*calendar.start)).seconds/60
528 self._build_phone_repeat_entry(entry, calendar)
529 self._build_phone_alarm_entry(entry, calendar)
530
531 - def _build_phone_exception_entry(self, entry, calendar, exceptions):
532 """Build a phone exception entry based on a BitPim entry"""
533 _rep=calendar.repeat
534 _end_date=calendar.end[:3]
535 _date=calendar.start[:3]
536 _ex_date=exceptions.get()[:3]
537 _cnt=0
538 while _date<=_end_date:
539 if _date==_ex_date:
540 entry.nth_event=_cnt
541 return True
542 _date=_rep.next_date(_date)
543 _cnt+=1
544 return False
545
547 """Write the calendar entries to the phone"""
548 _calendar=fundamentals.get('calendar', {})
549 _req=self.protocolclass.calendar_write_req()
550 _req_ex=self.protocolclass.calendar_write_ex_req()
551 _max_entry=self.protocolclass.CAL_MAX_ENTRY
552 _total_entries=len(_calendar)
553 _cal_cnt=0
554 for _,_cal in _calendar.items():
555 if _cal_cnt>_max_entry:\
556
557 break
558 self._build_phone_entry(_req, _cal)
559 _req.index=_cal_cnt
560 self.progress(_cal_cnt, _total_entries,
561 'Writing event: %s'%_cal.description)
562 self.sendATcommand(_req, None)
563 if _cal.repeat:
564 for _ex in _cal.repeat.suppressed[:self.protocolclass.CAL_TOTAL_ENTRY_EXCEPTIONS]:
565 if self._build_phone_exception_entry(_req_ex, _cal, _ex):
566 _req_ex.index=_cal_cnt
567 self.sendATcommand(_req_ex, None)
568 _cal_cnt+=1
569
570 for _index in range(_cal_cnt, self.protocolclass.CAL_TOTAL_ENTRIES):
571 self.progress(_index, _total_entries,
572 'Deleting event #%d'%_index)
573 self.del_calendar_entry(_index)
574
575
577 """Return a list of media being replaced"""
578 _index=fundamentals.get(index_key, {})
579 _media=fundamentals.get(media_key, {})
580 _index_file_list=[_entry['name'] for _,_entry in _index.items() \
581 if _entry.has_key('filename')]
582 _bp_file_list=[_entry['name'] for _,_entry in _media.items()]
583 return [x for x in _bp_file_list if x in _index_file_list]
584
586 for _key,_entry in index_dict.items():
587 if _entry.get('name', None)==name:
588 if item_key:
589
590 return _entry.get(item_key, None)
591 else:
592
593 return _key
594
595 - def _replace_files(self, index_key, media_key,
596 new_list, fundamentals):
597 """Replace existing media files with new contents"""
598 _index=fundamentals.get(index_key, {})
599 _media=fundamentals.get(media_key, {})
600 for _file in new_list:
601 _data=self._item_from_index(_file, 'data', _media)
602 if not _data:
603 self.log('Failed to write file %s due to no data'%_file)
604 continue
605 _file_name=self._item_from_index(_file, 'filename', _index)
606 if _file_name:
607
608 _stat=self.statfile(_file_name)
609 if _stat and _stat['size']!=len(_data):
610
611 try:
612 self.writefile(_file_name, _data)
613 except:
614 self.log('Failed to write file '+_file_name)
615 if __debug__:
616 raise
617
635
653
654
655 @classmethod
656 - def detectphone(_, coms, likely_ports, res, _module, _log):
680
681
682 parentprofile=com_moto_cdma.Profile
684
685 serialsname=Phone.serialsname
686
687 WALLPAPER_WIDTH=176
688 WALLPAPER_HEIGHT=220
689 MAX_WALLPAPER_BASENAME_LENGTH=37
690 WALLPAPER_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789()_ .-"
691 WALLPAPER_CONVERT_FORMAT="jpg"
692
693
694 usbids=( ( 0x22B8, 0x2A22, 1),
695 ( 0x22B8, 0x2A62, 1))
696 deviceclasses=("modem",)
697
698 phone_manufacturer='Motorola'
699 phone_model='V710 '
700 common_model_name='V710'
701 generic_phone_model='Motorola CDMA v710 Phone'
702
703
704 imageorigins={}
705 imageorigins.update(common.getkv(parentprofile.stockimageorigins, "images"))
708
709
710 imagetargets={}
711 imagetargets.update(common.getkv(parentprofile.stockimagetargets, "wallpaper",
712 {'width': 176, 'height': 200, 'format': "JPEG"}))
713 imagetargets.update(common.getkv(parentprofile.stockimagetargets, "outsidelcd",
714 {'width': 176, 'height': 140, 'format': "JPEG"}))
715 imagetargets.update(common.getkv(parentprofile.stockimagetargets, "fullscreen",
716 {'width': 176, 'height': 220, 'format': "JPEG"}))
719
722
723 _supportedsyncs=(
724 ('phonebook', 'read', None),
725 ('phonebook', 'write', 'OVERWRITE'),
726 ('calendar', 'read', None),
727 ('calendar', 'write', 'OVERWRITE'),
728 ('ringtone', 'read', None),
729 ('ringtone', 'write', 'OVERWRITE'),
730 ('wallpaper', 'read', None),
731 ('wallpaper', 'write', 'OVERWRITE'),
732 ('sms', 'read', None),
733 )
734
737
738 - def QueryAudio(self, origin, currentextension, afi):
739
740 if afi.format in ("MIDI", "QCP", "PMD"):
741 return currentextension, afi
742
743 if afi.format=="MP3":
744 if afi.channels==1 and 8<=afi.bitrate<=64 and 16000<=afi.samplerate<=22050:
745 return currentextension, afi
746
747 return ("mp3", fileinfo.AudioFileInfo(afi, **{'format': 'MP3', 'channels': 1, 'bitrate': 48, 'samplerate': 44100}))
748
749 field_color_data={
750 'phonebook': {
751 'name': {
752 'first': 1, 'middle': 1, 'last': 1, 'full': 1,
753 'nickname': 0, 'details': 1 },
754 'number': {
755
756 'type': True, 'speeddial': True, 'number': True,
757 'details': True,
758 'ringtone': True, 'wallpaper': True },
759 'email': True,
760 'email_details': {
761 'emailspeeddial': True, 'emailringtone': True,
762 'emailwallpaper': True },
763 'address': {
764 'type': 0, 'company': 0, 'street': 0, 'street2': 0,
765 'city': 0, 'state': 0, 'postalcode': 0, 'country': 0,
766 'details': 0 },
767 'url': 0,
768 'memo': 0,
769 'category': 1,
770 'wallpaper': 1,
771 'ringtone': 1,
772 'storage': 0,
773 },
774 'calendar': {
775 'description': True, 'location': True, 'allday': False,
776 'start': True, 'end': True, 'priority': False,
777 'alarm': True, 'vibrate': False,
778 'repeat': True,
779 'memo': False,
780 'category': False,
781 'wallpaper': False,
782 'ringtone': False,
783 },
784 'memo': {
785 'subject': False,
786 'date': False,
787 'secret': False,
788 'category': False,
789 'memo': False,
790 },
791 'todo': {
792 'summary': False,
793 'status': False,
794 'due_date': False,
795 'percent_complete': False,
796 'completion_date': False,
797 'private': False,
798 'priority': False,
799 'category': False,
800 'memo': False,
801 },
802 }
803