| Trees | Indices | Help |
|
|---|
|
|
1 ### BITPIM
2 ###
3 ### Copyright (C) 2006 Denis Tonn <denis@tonn.ca>
4 ### Inspiration and code liberally adopted from:
5 ### Stephen A. Wood and Joe Pham
6 ###
7 ### This program is free software; you can redistribute it and/or modify
8 ### it under the terms of the BitPim license as detailed in the LICENSE file.
9 ###
10 ### $Id$
11
12 """Communicate with a Samsung SPH-A840 from Telus Canada"""
13
14 # Notes:
15 #
16 # This Phone does not cleanly return from BREW mode, so everything is
17 # done through modem mode.
18 #
19 # Groups are handled differently on this phone, so I have hard coded
20 # them. In any event, there is no way to query them as part of a
21 # PBentry. Restoring data to a phone loses group assignments.
22 # at#pbgrw does not modify group names - seems to be ignored in phone
23 # even though it returns OK.
24 #
25 # Cannot query/extract Ringtones so that is hard coded. Built in tones
26 # are ok and can be changed through BitPim.
27 #
28 # Cannot query/extract Wallpaper so builtin values are hardcoded and
29 # everything else is maintained through a write to phone
30 #
31 # Special case email and url fields. If these are blank they MUST have
32 # a quoted space in the write or phone data is corrupted.
33 #
34 # uslot 1 should not be used. I have not done anything in this code to
35 # prevent it, but the phone will complain if you try and edit a
36 # pbentry with uslot = 1. Change the "location" on the phone and it's
37 # fine.
38 #
39 # There is no way of setting (or unsetting) a "secret" pbentry on this
40 # phone.
41 #
42 # Calendar writes fail (ERROR) unless start_time and end_time are the
43 # same.
44 #
45 # Occasionally the phone will have position 0 as the speeddial number,
46 # when it has only one number even when that number isn't in position
47 # 0.
48 #
49
50 import time
51 import datetime
52
53 import sha
54 import re
55 import struct
56
57 import bpcalendar
58 import prototypes
59 import todo
60 import memo
61
62 import common
63 import commport
64 import p_brew
65 import p_samsungspha840_telus
66 import com_brew
67 import com_phone
68 import com_samsung_packet
69 import helpids
70
71 numbertypetab=('home','office','cell','pager','fax')
72
73
74 ### Phone class
75
76 parentphone=com_samsung_packet.Phone
78 "Talk to a Samsung SPH-A840 phone (Telus)"
79
80 __read_timeout=0.5
81 __cal_end_datetime_value=None
82 __cal_alarm_values={0: -1, 1: 0, 2: 10, 3: 30, 4: 60 }
83 __cal_max_name_len=32
84 _cal_max_events_per_day=5
85
86 desc="SPH-A840 (Telus)"
87 helpid=helpids.ID_PHONE_SAMSUNGOTHERS
88 protocolclass=p_samsungspha840_telus
89 serialsname='spha840T'
90
91 builtin_ringtones=(
92 (0, ['Default Tone']),
93 (1, ['Ring %02d'%x for x in range(1, 6)]),
94 (6, ['Melody %02d'%x for x in range(1, 6)]),
95 )
96
97 # wallpaper values
98 # 8 = no image
99 # 3 = human 1-20
100 # 4 = animal 1-15
101 # 5 = others 1-10
102 # 2 = gallery - and wallpaper_pic_path points to the file name
103 # 7 = image clips
104
105 # wallpaper_modifier
106 # selects specific image in human, animal, others
107 # selects offset in file for image clips
108 # must be 0 if no image
109 # value ignored if gallery image
110
111 builtin_pictures=(
112 (0, ['Human %02d'%x for x in range(1, 21)]),
113 (30, ['Animal %02d'%x for x in range(1, 16)]),
114 (60, ['Other %02d'%x for x in range(1, 11)]),
115 (80, ['On Phone Image']),
116 )
117
119 com_samsung_packet.Phone.__init__(self, logtarget, commport)
120 self.numbertypetab=numbertypetab
121 self.mode=self.MODENONE
122
124 """Gets information fundamental to interoperating with the phone and UI."""
125 # use a hash of ESN and other stuff (being paranoid)
126 self.log("Retrieving fundamental phone information")
127 self.log("Phone serial number")
128 self.setmode(self.MODEMODEM)
129 results['uniqueserial']=sha.new(self.get_esn()).hexdigest()
130 # getting ringtone-index
131 self.log("Setting up Built in Ring Tone index")
132 results['ringtone-index']=self._get_builtin_index(self.builtin_ringtones)
133 # getting wallpaper-index
134 self.log("Setting up Built in Wallpaper index")
135 results['wallpaper-index']=self.get_wallpaper_index()
136 self.log("Ignoring Corrupted Phone group information")
137 # groups are hard coded since this phone corrupts the read of groups
138 results['groups']=self.read_groups()
139 self.log("Fundamentals retrieved")
140 return results
141
143 _res={}
144 for _starting_idx,_list in builtin_list:
145 _idx=_starting_idx
146 for _entry in _list:
147 _res[_idx]={ 'name': _entry,
148 'origin': 'builtin' }
149 _idx+=1
150 return _res
151
155
157 "Extend incomplete lines"
158 nfields=24 # Can we get this from packet def?
159 ncommas=self.countcommas(line)
160 if ncommas<0: # Un terminated quote
161 line+='"'
162 ncommas = -ncommas
163 if nfields-ncommas>1:
164 line=line+","*(nfields-ncommas-1)
165 return line
166
168 inquote=False
169 ncommas=0
170 for c in line:
171 if c == '"':
172 inquote = not inquote
173 elif not inquote and c == ',':
174 ncommas+=1
175 if inquote:
176 ncommas = -ncommas
177 return ncommas
178
181
184
187
190
192 # This phone returns crap for group names which causes an assertion error in the database
193 # because of duplicate "blank" names in the groups.
194 # so to bypass I have set the group names manually
195 g={}
196 g[0]={'name': "Friends"}
197 g[1]={'name': "Business"}
198 g[2]={'name': "Family"}
199 g[3]={'name': "General"}
200 g[4]={'name': "No Group"}
201 return g
202
203
205 """This phone doesn't save or read groups properly
206 so we are skipping the save"""
207 return
208
209
210
211 #
212 #
213 # Called by extractphonebookentry to extract wallpaper from phone entry data
214 #
215 #
217 # wallpaper values
218 # 8 = no image
219 # 3 = human 1-20 res index 1-20
220 # 4 = animal 1-15 res index 30-45
221 # 5 = others 1-10 res index 60-70
222 # 2 = gallery - and wallpaper_pic_path points to the file name
223 # 6 or 7 = image clips - modifier selects offset in image file
224 # 1 = I assume downloaded - never downloaded a wallpaper
225 # gallery and images and downloads are treated separately as index 80
226 # - save just keeps whatever wallpaper data the phone already has and is not managed by bitpim
227
228 # wallpaper_modifier
229 # selects specific image in human, animal, others
230 # selects offset in file for image clips
231 # must be 0 if no image
232 # value ignored if gallery image file name
233 _wp_index=fundamentals.get('wallpaper-index', {})
234 if entry.wallpaper==8:
235 return # no image
236 if entry.wallpaper in (0,1,2,6,7):
237 wallpaper_index=80 # use "on phone" dict entry name in bitpim and recycle current phone data
238 if entry.wallpaper==3:
239 wallpaper_index=entry.wallpaper_modifier
240 if entry.wallpaper==4:
241 wallpaper_index=30+entry.wallpaper_modifier
242 if entry.wallpaper==5:
243 wallpaper_index=60+entry.wallpaper_modifier
244 # get the name out of the dict
245 _wp_name=_wp_index.get(wallpaper_index, {}).get('name', None)
246 if _wp_name:
247 res['wallpapers']=[{ 'wallpaper': _wp_name, 'use': 'call' }]
248
249 #
250 #
251 # Called by extractphonebookentry to extract ringtone from phone entry data
252 #
253 #
255 _rt_index=fundamentals.get('ringtone-index', {})
256 _rt_name=_rt_index.get(entry.ringtone, {}).get('name', None)
257 if _rt_name:
258 res['ringtones']=[{ 'ringtone': _rt_name, 'use': 'call' }]
259
260 #
261 #
262 # Called by getphonebook to extract single phone entry data
263 #
264 #
266 res={}
267 res['serials']=[ {'sourcetype': self.serialsname,
268 'slot': entry.slot,
269 'sourceuniqueid': fundamentals['uniqueserial']} ]
270 # only one name
271 res['names']=[ {'full': entry.name} ]
272 # only one category
273 cat=fundamentals['groups'].get(entry.group, {'name': "Unassigned"})['name']
274 if cat!="Unassigned":
275 res['categories']=[ {'category': cat} ]
276 # only one email
277 if len(entry.email):
278 res['emails']=[ {'email': entry.email} ]
279 # only one url
280 if len(entry.url):
281 res['urls']=[ {'url': entry.url} ]
282 res['numbers']=[]
283 secret=0
284 speeddialtype=entry.speeddial
285 speeddial_has_been_set=0 # need this for fix for bad phonebook data
286 numberindex=0
287 for type in self.numbertypetab:
288 if len(entry.numbers[numberindex].number):
289 numhash={'number': entry.numbers[numberindex].number, 'type': type }
290 # if entry.numbers[numberindex].secret==1:
291 # secret=1
292 self.log("ENTRY -speeddial : "+str(speeddialtype)+" -index : "+str(numberindex)+" -uslot : "+str(entry.slot))
293 # fix possible bad phone data - always force dial entry to first number as safety measure
294 # Otherwise we will lose the uslot data completely
295 if speeddial_has_been_set==0:
296 numhash['speeddial']=entry.uslot
297 speeddial_has_been_set=1
298 if speeddialtype==numberindex:
299 numhash['speeddial']=entry.uslot
300 speeddial_has_been_set=1
301 self.log("EXIT -speeddial : "+str(speeddialtype)+" -index : "+str(numberindex)+" -uslot : "+str(entry.slot))
302 res['numbers'].append(numhash)
303 numberindex+=1
304
305 # Field after each number is secret flag. Setting secret on
306 # phone sets secret flag for every defined phone number
307 res['flags']=[ {'secret': secret} ]
308 _args=(res, entry, fundamentals)
309 self._extract_wallpaper(*_args)
310 self._extract_ringtone(*_args)
311 return res
312
313 #
314 #
315 # Get the phone book
316 #
317 #
319 """Read the phonebook data."""
320 pbook={}
321 self.setmode(self.MODEPHONEBOOK)
322
323 count=0
324 req=self.protocolclass.phonebookslotrequest()
325 lastname=""
326 for slot in range(1,self.protocolclass.NUMPHONEBOOKENTRIES+1):
327 req.slot=slot
328 res=self.sendpbcommand(req, self.protocolclass.phonebookslotresponse, fixup=self.pblinerepair)
329 if len(res) > 0:
330 lastname=res[0].entry.name
331 if len(lastname)>0:
332 self.log(`slot`+": "+lastname+" uSlot : "+str(res[0].entry.uslot))
333 entry=self.extractphonebookentry(res[0].entry, result)
334 pbook[count]=entry
335 count+=1
336 self.progress(slot, self.protocolclass.NUMPHONEBOOKENTRIES, lastname)
337 result['phonebook']=pbook
338 cats=[]
339 for i in result['groups']:
340 if result['groups'][i]['name']!='Unassigned':
341 cats.append(result['groups'][i]['name'])
342 result['categories']=cats
343 self.setmode(self.MODEMODEM)
344 return pbook
345
346 #
347 #
348 # Called by savephonebook to create an initial phone book entry
349 #
350 #
352 e=self.protocolclass.pbentry()
353 for k in entry:
354 # special treatment for lists
355 if k=='numbertypes' or k=='secrets':
356 continue
357 # get a phone index for the ring tone name
358 if k=='ringtone':
359 _rt_index=data.get('ringtone-index', {})
360 for rgt in _rt_index.keys():
361 _rt_name=_rt_index.get(rgt, {}).get('name', None)
362 if _rt_name==entry['ringtone']:
363 e.ringtone=rgt # and save it for the phonebook entry
364 continue
365 # get a a pair of indexs for the wallpaper name - wallpaper and wallpaper_modifier
366 elif k=='wallpaper':
367 # setup default values - no image
368 e.wallpaper=8
369 e.wallpaper_modifier=0
370 e.wallpaper_file=""
371 # now see if we have anything stored in the database
372 _wp_index=data.get('wallpaper-index', {})
373 for wpn in _wp_index.keys():
374 _wp_name=_wp_index.get(wpn, {}).get('name', None)
375 if _wp_name==entry['wallpaper']: # found the name, now convert to a pair of phone indexes
376 if wpn<30: # built in Human type, modifier is wpn
377 e.wallpaper=3
378 e.wallpaper_modifier=wpn
379 if wpn>29 and wpn<60: # built in Animals type, modifier is wpn-30
380 e.wallpaper=4
381 e.wallpaper_modifier=wpn-30
382 if wpn>59 and wpn<80: # built in Others type, modifier is wpn-60
383 e.wallpaper=5
384 e.wallpaper_modifier=wpn-60
385 if wpn==80: # on phone - special processing in caller code
386 e.wallpaper=8
387 e.wallpaper_modifier=8 # will not be stored on phone just a sig
388 continue
389 elif k=='numbers':
390 for numberindex in range(self.protocolclass.NUMPHONENUMBERS):
391 enpn=self.protocolclass.phonenumber()
392 e.numbers.append(enpn)
393 for i in range(len(entry[k])):
394 numberindex=entry['numbertypes'][i]
395 e.numbers[numberindex].number=entry[k][i]
396 # e.numbers[numberindex].secret=entry['secrets'][i]
397 e.numbers[numberindex].secret=0
398 continue
399 # everything else we just set
400 setattr(e, k, entry[k])
401
402 return e
403
404 #
405 #
406 # Save the phone book
407 #
408 #
410 "Saves out the phonebook"
411
412 pb=data['phonebook']
413 keys=pb.keys()
414 keys.sort()
415 keys=keys[:self.protocolclass.NUMPHONEBOOKENTRIES]
416
417 self.setmode(self.MODEPHONEBOOK)
418 # 1- Read the existing phonebook so that we cache birthday field and wallpaper file path and stuff
419 # 2- Erase all entries.
420 uslots={}
421 names={}
422 birthdays={}
423 wallpaper_s={}
424 wallpaper_files={}
425 wallpaper_modifiers={}
426 req=self.protocolclass.phonebookslotrequest()
427
428 self.log('Erasing '+self.desc+' phonebook')
429 progressmax=self.protocolclass.NUMPHONEBOOKENTRIES+len(keys)
430 for slot in range(1,self.protocolclass.NUMPHONEBOOKENTRIES+1):
431 req.slot=slot
432 self.progress(slot,progressmax,"Erasing "+`slot`)
433 try:
434 res=self.sendpbcommand(req,self.protocolclass.phonebookslotresponse, fixup=self.pblinerepair)
435 if len(res) > 0:
436 self.log("Starting capture data for : "+res[0].entry.name)
437 names[slot]=res[0].entry.name
438 birthdays[slot]=res[0].entry.birthday
439 wallpaper_s[slot]=res[0].entry.wallpaper
440 wallpaper_files[slot]=res[0].entry.wallpaper_file
441 wallpaper_modifiers[slot]=res[0].entry.wallpaper_modifier
442 self.log("Captured data for : "+res[0].entry.name)
443 self.log("User Slot from phone: "+str(res[0].entry.uslot))
444 else:
445 names[slot]=""
446 except:
447 names[slot]=""
448 self.log("Slot "+`slot`+" Empty slot or read failed")
449 reqerase=self.protocolclass.phonebooksloterase()
450 reqerase.slot=slot
451 self.sendpbcommand(reqerase, self.protocolclass.phonebookslotupdateresponse)
452 # Skip this.. the group save commands don't work
453 # self.savegroups(data)
454
455 for i in range(len(keys)):
456 slot=keys[i]
457 req=self.protocolclass.phonebookslotupdaterequest()
458 req.entry=self.makeentry(pb[slot],data)
459 # groups are not handled through the phonebook data on this phone
460 req.entry.group=self.protocolclass.DEFAULT_GROUP
461
462 # force a single space into email and url if they are zero length
463 if len(req.entry.email)==0:
464 req.entry.email=" "
465 if len(req.entry.url)==0:
466 req.entry.url=" "
467 # restore the stuff we may not have in the database
468 if names[slot]==req.entry.name:
469 req.entry.birthday=birthdays[slot] # no analogy on bitpim database, just keep with entry
470 # test for internal sig and keep whatever wallpaper is already on the phone
471 if req.entry.wallpaper==self.protocolclass.DEFAULT_WALLPAPER and req.entry.wallpaper_modifier==8: # yup, keep previous stuff
472 req.entry.wallpaper=wallpaper_s[slot]
473 req.entry.wallpaper_file=wallpaper_files[slot]
474 req.entry.wallpaper_modifier=wallpaper_modifiers[slot]
475 # make sure the default wallpaper has no modifier applied
476 if req.entry.wallpaper==self.protocolclass.DEFAULT_WALLPAPER:
477 req.entry.wallpaper_modifier=self.protocolclass.DEFAULT_WALLPAPER_MODIFIER
478 self.log('Writing entry '+`slot`+" - "+req.entry.name+" uSlot : "+str(req.entry.uslot))
479 self.log('Request: '+str(req))
480 self.progress(i+self.protocolclass.NUMPHONEBOOKENTRIES,progressmax,"Writing "+req.entry.name)
481 self.sendpbcommand(req, self.protocolclass.phonebookslotupdateresponse)
482 self.progress(progressmax+1,progressmax+1, "Phone book write completed")
483 self.setmode(self.MODEMODEM)
484 return data
485 #
486 #
487 # Get the phone Calendar
488 #
489 #
491 entries = {}
492 self.log("Getting calendar entries")
493 self.setmode(self.MODEPHONEBOOK)
494 req=self.protocolclass.eventrequest()
495 cal_cnt=0
496 for slot in range(self.protocolclass.NUMCALENDAREVENTS):
497 req.slot=slot
498 res=self.sendpbcommand(req,self.protocolclass.eventresponse)
499 if len(res) > 0:
500 self.progress(slot+1, self.protocolclass.NUMCALENDAREVENTS,
501 res[0].eventname)
502 # build a calendar entry
503 entry=bpcalendar.CalendarEntry()
504 # start time date
505 entry.start=res[0].start[0:5]
506 if res[0].end:
507 # valid end time
508 entry.end=res[0].start[0:5]
509 else:
510 entry.end=entry.start
511 # description
512 entry.description=res[0].eventname
513 try:
514 alarm=self.__cal_alarm_values[res[0].alarm]
515 except:
516 alarm=None
517 entry.alarm=alarm
518 # update calendar dict
519 entries[entry.id]=entry
520 cal_cnt += 1
521 result['calendar']=entries
522 self.setmode(self.MODEMODEM)
523 return result
524
525 #
526 #
527 # Save the phone Calendar
528 #
529 #
531 self.log("Sending calendar entries")
532 cal=self.process_calendar(dict['calendar'])
533 # testing
534 if __debug__:
535 print 'processed calendar: ', len(cal), ' items'
536 for c in cal:
537 print c.description,':', c.start
538 self.setmode(self.MODEPHONEBOOK)
539 self.log("Saving calendar entries")
540 cal_cnt=0
541 req=self.protocolclass.eventupdaterequest()
542 l = self.protocolclass.NUMCALENDAREVENTS
543 for c in cal:
544 # Save this entry to phone
545 # self.log('Item %d' %k)
546 # pos
547 req.slot=cal_cnt
548 # start date time
549 # print "Start ",c.start
550 req.start=list(c.start)+[0]
551 # end date time must be the same as start time for this phone write
552 req.end=req.start
553 # time stamp
554 req.timestamp=list(time.localtime(time.time())[0:6])
555 # print "Alarm ",c.alarm
556 req.alarm=c.alarm
557 # Name, check for bad char & proper length
558 # name=c.description.replace('"', '')
559 name=c.description
560 if len(name)>self.__cal_max_name_len:
561 name=name[:self.__cal_max_name_len]
562 req.eventname=name
563 # and save it
564 self.progress(cal_cnt+1, l, "Updating "+name)
565 self.sendpbcommand(req,self.protocolclass.eventupdateresponse)
566 cal_cnt += 1
567 # delete the rest of the calendar slots
568 self.log('Deleting unused entries')
569 for k in range(cal_cnt, l):
570 self.progress(k, l, "Deleting entry %d" % k)
571 reqerase=self.protocolclass.eventsloterase()
572 reqerase.slot=k
573 self.sendpbcommand(reqerase, self.protocolclass.eventupdateresponse)
574 self.setmode(self.MODEMODEM)
575
576 return dict
577
578 #### Profile class
579
580 parentprofile=com_samsung_packet.Profile
582 deviceclasses=("modem",)
583
584 BP_Calendar_Version=3
585 protocolclass=Phone.protocolclass
586 serialsname=Phone.serialsname
587 phone_manufacturer='SAMSUNG ELECTRONICS CO.,LTD.'
588 phone_model='SCH-A850 /180'
589
593
594 _supportedsyncs=(
595 ('phonebook', 'read', None), # all phonebook reading
596 ('phonebook', 'write', 'OVERWRITE'), # only overwriting phonebook
597 ('calendar', 'read', None), # all calendar reading
598 ('calendar', 'write', 'OVERWRITE') # only overwriting calendar
599 )
600
601 # fill in the list of ringtone/sound origins on your phone
602 ringtoneorigins=('ringers')
603 #e.g.
604 #ringtoneorigins=('ringers', 'sounds')
605
607 _cal_alarm_values={
608 10: 2, 30: 3, 60: 4, -1: 0, 0: 1 }
609
611 self._start=self._end=self._alarm=self._desc=None
612 self._extract_cal_info(calendar_entry, new_date)
613
615 s=cal_entry.start
616 if new_date is not None:
617 s=new_date[:3]+s[3:]
618 self._start=s
619 self._end=cal_entry.end
620 self._desc=cal_entry.description
621 # approximate the alarm value
622 self._alarm=0
623 alarm=cal_entry.alarm
624 _keys=self._cal_alarm_values.keys()
625 _keys.sort()
626 _keys.reverse()
627 for k in _keys:
628 if alarm>=k:
629 self._alarm=self._cal_alarm_values[k]
630 break
631
644
647 start=property(fget=_get_start)
648
651 end=property(fget=_get_end)
652
655 description=property(fget=_get_desc)
656
659 alarm=property(fget=_get_alarm)
660
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sun Jan 24 16:21:15 2010 | http://epydoc.sourceforge.net |