PyXR

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



0001 ### BITPIM
0002 ###
0003 ### Copyright (C) 2003-2005 Roger Binns <rogerb@rogerbinns.com>
0004 ### Copyright (C) 2006 Simon Capper <skyjunky@sbcglobal.net>
0005 ###
0006 ### This program is free software; you can redistribute it and/or modify
0007 ### it under the terms of the BitPim license as detailed in the LICENSE file.
0008 ###
0009 ### $Id: com_lglg8100.py 4303 2007-07-13 20:46:53Z djpham $
0010 
0011 """Communicate with the LG VX5200 cell phone
0012 
0013 
0014 The code in this file mainly inherits from VX8100 code and then extends where
0015 the 5200 has different functionality
0016 
0017 """
0018 
0019 # standard modules
0020 import time
0021 import calendar
0022 import cStringIO
0023 import sha
0024 
0025 # my modules
0026 import common
0027 import commport
0028 import copy
0029 import com_lgvx4400
0030 import p_brew
0031 import p_lglg8100
0032 import com_lgvx8100
0033 import com_brew
0034 import com_phone
0035 import com_lg
0036 import prototypes
0037 import bpcalendar
0038 import call_history
0039 import sms
0040 import memo
0041 
0042 class Phone(com_lgvx8100.Phone):
0043     "Talk to the LG LG8100 cell phone (Telus)"
0044 
0045     desc="LG-LG8100"
0046     helpid=None
0047     protocolclass=p_lglg8100
0048     serialsname='lglg8100'
0049 
0050     builtinringtones= ('Low Beep Once', 'Low Beeps', 'Loud Beep Once', 'Loud Beeps') + \
0051                       tuple(['Ringtone '+`n` for n in range(1,11)]) + \
0052                       ('No Ring',)
0053 
0054     ringtonelocations= (
0055         # type       index-file   size-file directory-to-use lowest-index-to-use maximum-entries type-major icon
0056         ( 'ringers', 'dload/aod.dat', 'dload/aodsize.dat', 'brew/16452/mds', 1000, 150, 6, 1, 0),
0057         # the sound index file uses the same index as the ringers, bitpim does not support this (yet)
0058 #        ( 'sounds', 'dload/mysound.dat', 'dload/mysoundsize.dat', 'brew/16452/ms', 100, 150, 2, 0, 151),
0059         )
0060 
0061     calendarlocation="sch/newschedule.dat"
0062     calendarexceptionlocation="sch/newschexception.dat"
0063     calenderrequiresreboot=1
0064     memolocation="sch/neomemo.dat"
0065 
0066     builtinwallpapers = () # none
0067 
0068     wallpaperlocations= (
0069         ( 'images', 'dload/image.dat', 'dload/imagesize.dat', 'brew/16452/mp', 100, 50, 0x300, 0, 0),
0070         #( 'video', 'dload/video.dat', None, 'brew/16452/mf', 1000, 50, 0x0304, 0, 0),
0071         )
0072         
0073     # for removable media (miniSD cards)
0074     _rs_path='mmc1/'
0075     _rs_ringers_path=_rs_path+'ringers'
0076     _rs_images_path=_rs_path+'images'
0077     media_info={ 'ringers': {
0078             'localpath': 'brew/16452/mds',
0079             'rspath': _rs_ringers_path,
0080             'vtype': protocolclass.MEDIA_TYPE_RINGTONE,
0081             'icon': protocolclass.MEDIA_RINGTONE_DEFAULT_ICON,
0082             'index': 1000,  # starting index
0083             'maxsize': 155,
0084             'indexfile': 'dload/aod.dat',
0085             'sizefile': 'dload/aodsize.dat',
0086             'dunno': 0, 'date': True,
0087         },
0088 #         'sounds': {
0089 #             'localpath': 'brew/16452/ms',
0090 #             'rspath': None,
0091 #             'vtype': protocolclass.MEDIA_TYPE_SOUND,
0092 #             'icon': protocolclass.MEDIA_IMAGE_DEFAULT_ICON,
0093 #             'index': 100,
0094 #             'maxsize': 155,
0095 #             'indexfile': 'dload/mysound.dat',
0096 #             'sizefile': 'dload/mysoundsize.dat',
0097 #             'dunno': 0, 'date': False },
0098          'images': {
0099              'localpath': 'brew/16452/mp',
0100              'rspath': _rs_images_path,
0101              'vtype': protocolclass.MEDIA_TYPE_IMAGE,
0102              'icon': protocolclass.MEDIA_IMAGE_DEFAULT_ICON,
0103              'index': 100,
0104              'maxsize': 155,
0105              'indexfile': 'dload/image.dat',
0106              'sizefile': 'dload/imagesize.dat',
0107              'dunno': 0, 'date': True },
0108 #         'video': {
0109 #             'localpath': 'brew/16452/mf',
0110 #             'rspath': None,
0111 #             'vtype': protocolclass.MEDIA_TYPE_VIDEO,
0112 #             'icon': protocolclass.MEDIA_VIDEO_DEFAULT_ICON,
0113 #             'index': 1000,
0114 #             'maxsize': 155,
0115 #             'indexfile': 'dload/video.dat',
0116 #             'sizefile': 'dload/videosize.dat',
0117 #             'dunno': 0, 'date': True },
0118          }
0119    
0120     def __init__(self, logtarget, commport):
0121         com_lgvx4400.Phone.__init__(self,logtarget,commport)
0122         self.mode=self.MODENONE
0123 
0124     def __del__(self):
0125         pass
0126 
0127     def getcalendar(self,result):
0128         res={}
0129         # Read exceptions file first
0130         try:
0131             buf=prototypes.buffer(self.getfilecontents(self.calendarexceptionlocation))
0132             ex=self.protocolclass.scheduleexceptionfile()
0133             ex.readfrombuffer(buf, logtitle="Calendar exceptions")
0134             exceptions={}
0135             for i in ex.items:
0136                 try:
0137                     exceptions[i.pos].append( (i.year,i.month,i.day) )
0138                 except KeyError:
0139                     exceptions[i.pos]=[ (i.year,i.month,i.day) ]
0140         except com_brew.BrewNoSuchFileException:
0141             exceptions={}
0142 
0143         # Now read schedule
0144         try:
0145             buf=prototypes.buffer(self.getfilecontents(self.calendarlocation))
0146             if len(buf.getdata())<3:
0147                 # file is empty, and hence same as non-existent
0148                 raise com_brew.BrewNoSuchFileException()
0149             sc=self.protocolclass.schedulefile()
0150             sc.readfrombuffer(buf, logtitle="Calendar")
0151             for event in sc.events:
0152                 # the vx8100 has a bad entry when the calender is empty
0153                 # stop processing the calender when we hit this record
0154                 if event.pos==0: #invalid entry
0155                     continue                   
0156                 entry=bpcalendar.CalendarEntry()
0157                 entry.desc_loc=event.description
0158                 try: # delete events are still in the calender file but have garbage dates
0159                     entry.start=event.start
0160                     entry.end=event.end
0161                 except ValueError:
0162                     continue
0163                 if event.alarmindex_vibrate&0x1:
0164                     entry.vibrate=0 # vibarate bit is inverted in phone 0=on, 1=off
0165                 else:
0166                     entry.vibrate=1
0167                 entry.repeat = self.makerepeat(event.repeat, event.start)
0168                 min=event.alarmminutes
0169                 hour=event.alarmhours
0170                 if min==0x64 or hour==0x64:
0171                     entry.alarm=None # no alarm set
0172                 else:
0173                     entry.alarm=hour*60+min
0174                 entry.ringtone=result['ringtone-index'][event.ringtone]['name']
0175                 entry.snoozedelay=0
0176                 # check for exceptions and remove them
0177                 if event.repeat[3] and exceptions.has_key(event.pos):
0178                     for year, month, day in exceptions[event.pos]:
0179                         entry.suppress_repeat_entry(year, month, day)
0180                 res[entry.id]=entry
0181 
0182             assert sc.numactiveitems==len(res)
0183         except com_brew.BrewNoSuchFileException:
0184             pass # do nothing if file doesn't exist
0185         result['calendar']=res
0186         return result
0187 
0188     def makerepeat(self, repeat, start):
0189         # get all the variables out of the repeat tuple
0190         # and convert into a bpcalender RepeatEntry
0191         type,dow,interval,interval2,exceptions=repeat
0192         #print "repeat "+`repeat`+" start "+`start`
0193         if type==0:
0194             repeat_entry=None
0195         else:
0196             repeat_entry=bpcalendar.RepeatEntry()
0197             if type==1: #daily
0198                 repeat_entry.repeat_type=repeat_entry.daily
0199                 repeat_entry.interval=interval
0200             elif type==2: #'monfri'
0201                 repeat_entry.repeat_type=repeat_entry.daily
0202                 repeat_entry.interval=0
0203             elif type==3: #'weekly'
0204                 repeat_entry.repeat_type=repeat_entry.weekly
0205                 repeat_entry.interval=interval
0206             elif type==5: #'monthly'
0207                 repeat_entry.repeat_type=repeat_entry.monthly
0208                 repeat_entry.interval2=interval2
0209                 repeat_entry.dow=0
0210             elif type==4: #'monthly' #Xth Y day (e.g. 2nd friday each month)
0211                 repeat_entry.repeat_type=repeat_entry.monthly
0212                 repeat_entry.interval=interval #X
0213                 repeat_entry.dow=dow #Y
0214                 repeat_entry.interval2=interval2
0215             elif type==6: #'yearly #Xth Y day of Month (2nd sunday of april)' Not supported by bitpim
0216                 #repeat_entry.repeat_type=repeat_entry.yearly
0217                 repeat_entry=None
0218             else: # =7 'yearly'
0219                 repeat_entry.repeat_type=repeat_entry.yearly
0220         return repeat_entry
0221 
0222     def setalarm(self, entry, data):
0223         # vx8100 only allows certain repeat intervals, adjust to fit, it also stores an index to the interval
0224         rc=True
0225         if entry.alarm>=10080:
0226             entry.alarm=10080
0227             data.alarmminutes=0
0228             data.alarmhours=168 # 1 week
0229             data.alarmindex_vibrate=0xe
0230         elif entry.alarm>=4320:
0231             entry.alarm=4320
0232             data.alarmminutes=0
0233             data.alarmhours=72 # 3 days
0234             data.alarmindex_vibrate=0xc
0235         elif entry.alarm>=1440:
0236             entry.alarm=1440
0237             data.alarmminutes=0
0238             data.alarmhours=24 # 1 day
0239             data.alarmindex_vibrate=0xa
0240         elif entry.alarm>=60:
0241             entry.alarm=60
0242             data.alarmminutes=0
0243             data.alarmhours=1 # 1 hour
0244             data.alarmindex_vibrate=0x8
0245         elif entry.alarm>=30:
0246             entry.alarm=30
0247             data.alarmminutes=30
0248             data.alarmhours=0 # 30 mins
0249             data.alarmindex_vibrate=0x6
0250         elif entry.alarm>=15:
0251             entry.alarm=15
0252             data.alarmminutes=15
0253             data.alarmhours=0
0254             data.alarmindex_vibrate=0x4
0255         elif entry.alarm>=0:
0256             entry.alarm=0
0257             data.alarmminutes=0
0258             data.alarmhours=0
0259             data.alarmindex_vibrate=0x2
0260         else: # no alarm
0261             data.alarmminutes=0x64
0262             data.alarmhours=0x64
0263             data.alarmindex_vibrate=1
0264             rc=False
0265         # set the vibrate bit
0266         if data.alarmindex_vibrate > 1 and entry.vibrate==0:
0267             data.alarmindex_vibrate+=1
0268         return rc
0269 
0270 
0271     def savecalendar(self, dict, merge):
0272         # ::TODO::
0273         # what will be written to the files
0274         eventsf=self.protocolclass.schedulefile()
0275         exceptionsf=self.protocolclass.scheduleexceptionfile()
0276         # what are we working with
0277         cal=dict['calendar']
0278         newcal={}
0279         #sort into start order, makes it possible to see if the calendar has changed        
0280         keys=[(x.start, k) for k,x in cal.items()]
0281         keys.sort()
0282         # number of entries
0283         eventsf.numactiveitems=len(keys)
0284         pos=0
0285         contains_alarms=False        
0286         # play with each entry
0287         for (_,k) in keys:
0288             # entry is what we will return to user
0289             entry=cal[k]
0290             data=self.protocolclass.scheduleevent()
0291             data.index=pos
0292             pos+=1
0293             data.pos=eventsf.packetsize()
0294             data.description=entry.desc_loc
0295             data.start=entry.start
0296             data.end=entry.end
0297             alarm_set=self.setalarm(entry, data)
0298             data.ringtone=0
0299             if alarm_set:
0300                 if entry.ringtone=="No Ring" and not entry.vibrate:
0301                     alarm_name="Low Beep Once"
0302                 else:
0303                     alarm_name=entry.ringtone
0304             else:# set alarm to "No Ring" gets rid of alarm icon on phone
0305                 alarm_name="No Ring"
0306             for i in dict['ringtone-index']:
0307                 if dict['ringtone-index'][i]['name']==alarm_name:
0308                     data.ringtone=i
0309             # check for exceptions and add them to the exceptions list
0310             exceptions=0
0311             if entry.repeat!=None:
0312                 if data.end[:3]==entry.no_end_date:
0313                     data.end=(2100, 12, 31)+data.end[3:]
0314                 for i in entry.repeat.suppressed:
0315                     de=self.protocolclass.scheduleexception()
0316                     de.pos=data.pos
0317                     de.day=i.date.day
0318                     de.month=i.date.month
0319                     de.year=i.date.year
0320                     exceptions=1
0321                     exceptionsf.items.append(de)
0322             if entry.repeat != None:
0323                 data.repeat=(self.getrepeattype(entry, exceptions))
0324             else:
0325                 data.repeat=((0,0,0,0,0))
0326             if data.alarmindex_vibrate!=1: # if alarm set
0327                 contains_alarms=True
0328             # put entry in nice shiny new dict we are building
0329             entry=copy.copy(entry)
0330             newcal[data.pos]=entry
0331             eventsf.events.append(data)
0332 
0333         buf=prototypes.buffer()
0334         eventsf.writetobuffer(buf, logtitle="New Calendar")
0335         # We check the existing calender as changes require a reboot for the alarms
0336         # to work properly, also no point writing the file if it is not changing
0337         if buf.getvalue()!=self.getfilecontents(self.calendarlocation):
0338             self.writefile(self.calendarlocation, buf.getvalue())
0339             if contains_alarms or self.calenderrequiresreboot:
0340                 self.log("Your phone has to be rebooted due to the calendar changing")
0341                 dict["rebootphone"]=True
0342         else:
0343             self.log("Phone calendar unchanged, no update required")
0344 
0345         buf=prototypes.buffer()
0346         exceptionsf.writetobuffer(buf, logtitle="Writing calendar exceptions")
0347         self.writefile(self.calendarexceptionlocation, buf.getvalue())
0348 
0349         # fix passed in dict
0350         dict['calendar']=newcal
0351 
0352         return dict
0353 
0354     def getrepeattype(self, entry, exceptions):
0355         #convert the bpcalender type into vx8100 type
0356         repeat_entry=bpcalendar.RepeatEntry()
0357         interval2=0
0358         if entry.repeat.repeat_type==repeat_entry.monthly:
0359             dow=entry.repeat.dow
0360             interval2=entry.repeat.interval2
0361             if entry.repeat.dow==0:
0362                 # set interval for month type 4 to start day of month, (required by vx8100)
0363                 interval=entry.start[2]
0364                 type=5
0365             else:
0366                 interval=entry.repeat.interval
0367                 type=4
0368         elif entry.repeat.repeat_type==repeat_entry.daily:
0369             dow=entry.repeat.dow
0370             interval=entry.repeat.interval
0371             if entry.repeat.interval==0:
0372                 type=2
0373             else:
0374                 type=1
0375         elif entry.repeat.repeat_type==repeat_entry.weekly:
0376             dow=entry.repeat.dow
0377             interval=entry.repeat.interval
0378             type=3
0379         elif entry.repeat.repeat_type==repeat_entry.yearly:
0380             # set interval to start day of month, (required by vx8100)
0381             interval=entry.start[2]
0382             # set dow to start month, (required by vx8100)
0383             dow=entry.start[1]
0384             type=7
0385         return (type, dow, interval, interval2, exceptions)
0386 
0387     def eval_detect_data(self, res):
0388         found=False
0389         if res.get(self.brew_version_txt_key, None) is not None:
0390             found=res[self.brew_version_txt_key][:len(self.my_version_txt)]==self.my_version_txt
0391         if found:
0392             res['model']=self.my_model
0393             res['manufacturer']='LG Electronics Inc'
0394             s=res.get(self.esn_file_key, None)
0395             if s:
0396                 res['esn']=self.get_esn(s)
0397 
0398     my_version_txt='CX8100'
0399     my_model='LG8100'
0400 
0401 
0402     # Media stuff---------------------------------------------------------------
0403     # Bypassing the 8100/9800 specific stuff
0404 #    def getmedia(self, maps, results, key):
0405  #       return com_lg.LGNewIndexedMedia2.getmedia(self, maps, results, key)
0406   #  def savemedia(self, mediakey, mediaindexkey, maps, results, merge, reindexfunction):
0407    #     return com_lg.LGNewIndexedMedia2.savemedia(self, mediakey, mediaindexkey, maps, results, merge, reindexfunction)
0408 
0409 parentprofile=com_lgvx8100.Profile
0410 class Profile(parentprofile):
0411     protocolclass=Phone.protocolclass
0412     serialsname=Phone.serialsname
0413 
0414     BP_Calendar_Version=3
0415     phone_manufacturer='LG Electronics Inc'
0416     phone_model='LG8100'
0417 
0418     WALLPAPER_WIDTH=176
0419     WALLPAPER_HEIGHT=220
0420     MAX_WALLPAPER_BASENAME_LENGTH=32
0421     WALLPAPER_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789()-_ ."
0422     WALLPAPER_CONVERT_FORMAT="jpg"
0423    
0424     # the 8100 uses "W" for wait in the dialstring, it does not support "T"
0425     DIALSTRING_CHARS="[^0-9PW#*]"
0426 
0427     MAX_RINGTONE_BASENAME_LENGTH=32
0428     RINGTONE_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789()-_ ."
0429 
0430     # the 8100 doesn't have seperate origins - they are all dumped in "images"
0431     imageorigins={}
0432     imageorigins.update(common.getkv(parentprofile.stockimageorigins, "images"))
0433     def GetImageOrigins(self):
0434         return self.imageorigins
0435 
0436     # our targets are the same for all origins
0437     imagetargets={}
0438     imagetargets.update(common.getkv(parentprofile.stockimagetargets, "outsidelcd",
0439                                       {'width': 128, 'height': 128, 'format': "JPEG"}))
0440     imagetargets.update(common.getkv(parentprofile.stockimagetargets, "fullscreen",
0441                                       {'width': 176, 'height': 220, 'format': "JPEG"}))
0442 
0443     def GetTargetsForImageOrigin(self, origin):
0444         return self.imagetargets
0445 
0446  
0447     def __init__(self):
0448         parentprofile.__init__(self)
0449 
0450     _supportedsyncs=(
0451         ('phonebook', 'read', None),  # all phonebook reading
0452         ('calendar', 'read', None),   # all calendar reading
0453         ('wallpaper', 'read', None),  # all wallpaper reading
0454         ('ringtone', 'read', None),   # all ringtone reading
0455         ('call_history', 'read', None),# all call history list reading
0456         ('sms', 'read', None),         # all SMS list reading
0457         ('memo', 'read', None),        # all memo list reading
0458         ('phonebook', 'write', 'OVERWRITE'),  # only overwriting phonebook
0459         ('calendar', 'write', 'OVERWRITE'),   # only overwriting calendar
0460         ('wallpaper', 'write', 'MERGE'),      # merge and overwrite wallpaper
0461         ('wallpaper', 'write', 'OVERWRITE'),
0462         ('ringtone', 'write', 'MERGE'),      # merge and overwrite ringtone
0463         ('ringtone', 'write', 'OVERWRITE'),
0464         ('sms', 'write', 'OVERWRITE'),        # all SMS list writing
0465         ('memo', 'write', 'OVERWRITE'),       # all memo list writing
0466         )
0467 
0468     field_color_data={
0469         'phonebook': {
0470             'name': {
0471                 'first': 1, 'middle': 1, 'last': 1, 'full': 1,
0472                 'nickname': 0, 'details': 1 },
0473             'number': {
0474                 'type': 5, 'speeddial': 5, 'number': 5, 'details': 5 },
0475             'email': 2,
0476             'address': {
0477                 'type': 0, 'company': 0, 'street': 0, 'street2': 0,
0478                 'city': 0, 'state': 0, 'postalcode': 0, 'country': 0,
0479                 'details': 0 },
0480             'url': 0,
0481             'memo': 1,
0482             'category': 1,
0483             'wallpaper': 1,
0484             'ringtone': 2,
0485             'storage': 0,
0486             },
0487         'calendar': {
0488             'description': True, 'location': True, 'allday': True,
0489             'start': True, 'end': True, 'priority': False,
0490             'alarm': True, 'vibrate': True,
0491             'repeat': True,
0492             'memo': False,
0493             'category': False,
0494             'wallpaper': False,
0495             'ringtone': True,
0496             },
0497         'memo': {
0498             'subject': True,
0499             'date': True,
0500             'secret': False,
0501             'category': False,
0502             'memo': True,
0503             },
0504         'todo': {
0505             'summary': False,
0506             'status': False,
0507             'due_date': False,
0508             'percent_complete': False,
0509             'completion_date': False,
0510             'private': False,
0511             'priority': False,
0512             'category': False,
0513             'memo': False,
0514             },
0515         }
0516 

Generated by PyXR 0.9.4