LG VX7000 Update

Jungho Park <[email protected]>
Newsgroups gmane.comp.mobile.bitpim.devel
Message-ID <[email protected]>
Hello

I modified p_lgvx7000.p and com_lgvx7000.py to enable 
calendar, memo, sms, and call history. I modified code 
copied from lgvx4650. There are some issues but I am not 
familiar with Python.

Calendar issue
- Repeat Yearly is not supported.
- Deleted or some events with Repeat have a strange 4 byte 
date, which causes exception error.

Memo (I just used LG VX4650 code)
- Appears to be OK

SMS
- Somehow, "Locked" does not work for Inbox.

Call History
- Appears to be OK

I have LG VX7000. If you want to test this phone, send me 
the code. Thanks.

Jungho Park

======================================
Jungho Park ([email protected])
245 Natural History Bldg
1301 W.Green St.
Urbana, IL 61801-3011
======================================
com_lgvx7000.py (text/plain, 24.2 KB)
### BITPIM
###
### Copyright (C) 2003-2005 Roger Binns <[email protected]>
###
### This program is free software; you can redistribute it and/or modify
### it under the terms of the BitPim license as detailed in the LICENSE file.
###
### $Id: com_lgvx7000.py,v 1.10 2005/08/24 04:32:52 djpham Exp $

"""Communicate with the LG VX7000 cell phone

The VX7000 is substantially similar to the VX6000 but also supports video.

The code in this file mainly inherits from VX4400 code and then extends where
the 6000 has extra functionality

"""

# standard modules
import time
import cStringIO
import sha

# my modules
import bpcalendar
import call_history
import common
import copy
import p_lgvx7000
import com_lgvx4400
import com_brew
import com_phone
import com_lg
import memo
import prototypes
import sms

class Phone(com_lg.LGNewIndexedMedia,com_lgvx4400.Phone):
    "Talk to the LG VX7000 cell phone"

    desc="LG-VX7000"

    protocolclass=p_lgvx7000
    serialsname='lgvx7000'

    builtinringtones= ('Low Beep Once', 'Low Beeps', 'Loud Beep Once', 'Loud Beeps') + \
                      tuple(['Ringtone '+`n` for n in range(1,11)]) + \
                      ('No Ring',)

    ringtonelocations= (
        # type       index-file   size-file directory-to-use lowest-index-to-use maximum-entries type-major
        ( 'ringers', 'dload/sound.dat', 'dload/soundsize.dat', 'dload/snd', 100, 50, 1),
        )

    builtinwallpapers = () # none

    wallpaperlocations= (
        ( 'images', 'dload/image.dat', 'dload/imagesize.dat', 'dload/img', 100, 50, 0),
        )
        
    
    def __init__(self, logtarget, commport):
        com_lgvx4400.Phone.__init__(self,logtarget,commport)
        com_lg.LGNewIndexedMedia.__init__(self)
        self.mode=self.MODENONE

    my_model='VX7000'

    # SMS stuff

    def savesms(self, result, merge):
        self._setquicktext(result)
        result['rebootphone']=True
        return result

    def _setquicktext(self, result):
        sf=self.protocolclass.sms_quick_text()
        quicktext=result.get('canned_msg', [])
        count=0
        for entry in quicktext:
            if count < self.protocolclass.SMS_CANNED_MAX_ITEMS:
                sf.msgs.append(entry['text'][:self.protocolclass.SMS_CANNED_MAX_LENGTH-1])
                count+=1
            else:
                break
        if count!=0:
            # don't create the file if there are no entries 
            buf=prototypes.buffer()
            sf.writetobuffer(buf)
            self.logdata("Writing calendar", buf.getvalue(), sf)
            self.writefile(self.protocolclass.SMS_CANNED_FILENAME, buf.getvalue())
        return

    def getsms(self, result):
        # get the quicktext (LG name for canned messages)
        result['canned_msg']=self._getquicktext()
        result['sms']=self._readsms()
        return result

    def _readsms(self):
        res={}
        # go through the sms directory looking for messages
        for item in self.getfilesystem("sms").values():
            if item['type']=='file':
                folder=None
                for f,pat in self.protocolclass.SMS_PATTERNS.items():
                    if pat.match(item['name']):
                        folder=f
                        break
                if folder:
                    buf=prototypes.buffer(self.getfilecontents(item['name'], True))
                    self.logdata("SMS message file " +item['name'], buf.getdata())
                if folder=='Inbox':
                    sf=self.protocolclass.sms_in()
                    sf.readfrombuffer(buf)
                    entry=self._getinboxmessage(sf)
                    res[entry.id]=entry
                elif folder=='Sent':
                    sf=self.protocolclass.sms_out()
                    sf.readfrombuffer(buf)
                    entry=self._getoutboxmessage(sf)
                    res[entry.id]=entry
                elif folder=='Saved':
                    sf=self.protocolclass.sms_saved()
                    sf.readfrombuffer(buf)
                    if sf.outboxmsg:
                        entry=self._getoutboxmessage(sf.outbox)
                    else:
                        entry=self._getinboxmessage(sf.inbox)
                    entry.folder=entry.Folder_Saved
                    res[entry.id]=entry
        return res 

    def _getquicktext(self):
        quicks=[]
        try:
            buf=prototypes.buffer(self.getfilecontents("sms/mediacan000.dat"))
            sf=self.protocolclass.sms_quick_text()
            sf.readfrombuffer(buf)
            self.logdata("SMS quicktext file sms/mediacan000.dat", buf.getdata(), sf)
            for rec in sf.msgs:
                if rec.msg!="":
                    quicks.append({ 'text': rec.msg, 'type': sms.CannedMsgEntry.user_type })
        except com_brew.BrewNoSuchFileException:
            pass # do nothing if file doesn't exist
        return quicks

    def _getinboxmessage(self, sf):
        entry=sms.SMSEntry()
        entry.folder=entry.Folder_Inbox
        entry.datetime="%d%02d%02dT%02d%02d%02d" % (sf.GPStime)
        entry._from=self._getsender(sf.sender, sf.sender_length)
        entry.subject=sf.subject
        entry.locked=sf.locked
        if sf.priority==0:
            entry.priority=sms.SMSEntry.Priority_Normal
        else:
            entry.priority=sms.SMSEntry.Priority_High
        entry.read=sf.read
        txt=""
        if sf.num_msg_elements==1 and sf.bin_header1==0:
            txt=self._get_text_from_sms_msg_without_header(sf.msgs[0].msg, sf.msglengths[0].msglength)
        else:
            for i in range(sf.num_msg_elements):
                txt+=self._get_text_from_sms_msg_with_header(sf.msgs[i].msg, sf.msglengths[i].msglength)
        entry.text=unicode(txt, errors='ignore')
        entry.callback=sf.callback
        return entry

    def _getoutboxmessage(self, sf):
        entry=sms.SMSEntry()
        entry.folder=entry.Folder_Sent
        entry.datetime="%d%02d%02dT%02d%02d00" % ((sf.timesent))
        # add all the recipients
        for r in sf.recipients:
            if r.number:
                confirmed=(r.status==5)
                confirmed_date=None
                if confirmed:
                    confirmed_date="%d%02d%02dT%02d%02d00" % r.timereceived
                entry.add_recipient(r.number, confirmed, confirmed_date)
        entry.subject=sf.subject
        txt=""
        if sf.num_msg_elements==1 and not sf.messages[0].binary:
            txt=self._get_text_from_sms_msg_without_header(sf.messages[0].msg, sf.messages[0].length)
        else:
            for i in range(sf.num_msg_elements):
                txt+=self._get_text_from_sms_msg_with_header(sf.messages[i].msg, sf.messages[i].length)
        entry.text=unicode(txt, errors='ignore')
        if sf.priority==0:
            entry.priority=sms.SMSEntry.Priority_Normal
        else:
            entry.priority=sms.SMSEntry.Priority_High
        entry.locked=sf.locked
        entry.callback=sf.callback
        return entry

    def _get_text_from_sms_msg_without_header(self, msg, num_septets):
        out=""
        for i in range(num_septets):
            tmp = (msg[(i*7)/8].byte<<8) | msg[((i*7)/8) + 1].byte
            bit_index = 9 - ((i*7) % 8)
            out += chr((tmp >> bit_index) & 0x7f)
        return out

    def _get_text_from_sms_msg_with_header(self, msg, num_septets):
        data_len = ((msg[0].byte+1)*8+6)/7
        seven_bits={}
        raw={}
        out={}
        # re-order the text into the correct order for separating into
        # 7-bit characters
        for i in range(0, (num_septets*7)/8+7, 7):
            for k in range(7):
                raw[i+6-k]=msg[i+k].byte
        # extract the 7-bit chars
        for i in range(num_septets+7):
            tmp = (raw[(i*7)/8]<<8) | raw[((i*7)/8) + 1]
            bit_index = 9 - ((i*7) % 8)
            seven_bits[i] = (tmp >> bit_index) & 0x7f
        # correct the byte order and remove the data portion of the message
        i=0
        for i in range(0, num_septets+7, 8):
            for k in range(8):
                if(i+7-k-data_len>=0):
                    if i+k<num_septets+7:
                        out[i+7-k-data_len]=seven_bits[i+k]
        res=""
        for i in range(num_septets-data_len):
            res+=chr(out[i])
        return res

    def _getsender(self, raw, len):
        result=""
        for i in range(len):
            if(raw[i].byte==10):
                result+="0"
            else:
                result+="%d" % raw[i].byte
        return result

    # Calendar stuff------------------------------------------------------------
    # all taken care by the VX4400
    # Calendar stuff------------------------------------------------------------
    def getcalendar(self,result):
        # Read exceptions file first
        try:
            buf=prototypes.buffer(self.getfilecontents(
                self.protocolclass.cal_exception_file_name))
            ex=self.protocolclass.scheduleexceptionfile()
            ex.readfrombuffer(buf)
            self.logdata("Calendar exceptions", buf.getdata(), ex)
            exceptions={}
            for i in ex.items:
                exceptions.setdefault(i.pos, []).append( (i.year,i.month,i.day) )
        except com_brew.BrewNoSuchFileException:
            exceptions={}

        # Now read schedule
        try:
            buf=prototypes.buffer(self.getfilecontents(
                self.protocolclass.cal_data_file_name))
            if len(buf.getdata())<2:
                # file is empty, and hence same as non-existent
                raise com_brew.BrewNoSuchFileException()
            sc=self.protocolclass.schedulefile()
            self.logdata("Calendar", buf.getdata(), sc)
            sc.readfrombuffer(buf)
            res=self.get_cal(sc, exceptions, result.get('ringtone-index', {}))
        except com_brew.BrewNoSuchFileException:
            res={}
        result['calendar']=res
        return result

    def savecalendar(self, dict, merge):
        # ::TODO:: obey merge param
        # get the list of available voice alarm files
        voice_files={}
        if self._cal_has_voice_id:
            try:
                file_list=self.getfilesystem(self.protocolclass.cal_dir)
                for k in file_list.keys():
                    if k.endswith(self.protocolclass.cal_voice_ext):
                        voice_files[int(k[8:11])]=k
            except:
                self.log('Failed to list Calendar Voice Files')
        # build the schedule file
        sc=self.protocolclass.schedulefile()
        sc_ex=self.set_cal(sc, dict.get('calendar', {}),
                           dict.get('ringtone-index', {}),
                           voice_files)
        buf=prototypes.buffer()
        sc.writetobuffer(buf)
        self.writefile(self.protocolclass.cal_data_file_name,
                         buf.getvalue())
        # build the exceptions
        exceptions_file=self.protocolclass.scheduleexceptionfile()
        for k,l in sc_ex.items():
            for x in l:
                _ex=self.protocolclass.scheduleexception()
                _ex.pos=k
                _ex.year, _ex.month, _ex.day=x
                exceptions_file.items.append(_ex)
        buf=prototypes.buffer()
        exceptions_file.writetobuffer(buf)
        self.writefile(self.protocolclass.cal_exception_file_name,
                         buf.getvalue())
        # clear out any alarm voice files that may have been deleted
        if self._cal_has_voice_id:
            for k,e in voice_files.items():
                try:
                    self.rmfile(e)
                except:
                    self.log('Failed to delete file '+e)
        return dict

    _repeat_values={
        protocolclass.CAL_REP_DAILY: bpcalendar.RepeatEntry.daily,
        protocolclass.CAL_REP_MONFRI: bpcalendar.RepeatEntry.daily,
        protocolclass.CAL_REP_WEEKLY: bpcalendar.RepeatEntry.weekly,
        protocolclass.CAL_REP_MONTHLY: bpcalendar.RepeatEntry.monthly,
        protocolclass.CAL_REP_YEARLY: bpcalendar.RepeatEntry.yearly
        }

    def _build_cal_repeat(self, event, exceptions):
        rep_val=Phone._repeat_values.get(event.repeat, None)
        if not rep_val:
            return None
        rep=bpcalendar.RepeatEntry(rep_val)
        if event.repeat==self.protocolclass.CAL_REP_MONFRI:
            rep.interval=rep.dow=0
        elif event.repeat!=self.protocolclass.CAL_REP_YEARLY:
            rep.interval=1
            rep.dow=0
        # do exceptions
        cal_ex=exceptions.get(event.pos, [])
        for e in cal_ex:
            rep.add_suppressed(*e)
        return rep

    def _get_voice_id(self, event, entry):
        if event.hasvoice:
            entry.voice=event.voiceid

    def _build_cal_entry(self, event, exceptions, ringtone_index):
        # return a copy of bpcalendar object based on my values
        # general fields
        entry=bpcalendar.CalendarEntry()
        entry.start=event.start
        entry.end=event.end
        entry.description=event.description
        # check for allday event
        if entry.start[3:]==(0, 0) and entry.end[3:]==(23, 59):
            entry.allday=True
        # alarm
        if event.alarmtype:
            entry.alarm=event.alarmhours*60+event.alarmminutes
        # ringtone
        rt_idx=event.ringtone
        # hack to account for the VX4650 weird ringtone setup
        # if rt_idx<50:
            # 1st part of builtin ringtones, need offset by 1
        #    rt_idx+=1
        entry.ringtone=ringtone_index.get(rt_idx, {'name': None} )['name']
        # voice ID if applicable
        if self._cal_has_voice_id:
            self._get_voice_id(event, entry)
        # repeat info
        entry.repeat=self._build_cal_repeat(event, exceptions)
        return entry

    def get_cal(self, sch_file, exceptions, ringtone_index):
        res={}
        for event in sch_file.events:
            if event.pos==-1:   # blank entry
                continue
            cal_event=self._build_cal_entry(event, exceptions, ringtone_index)
            res[cal_event.id]=cal_event
        return res

    _alarm_info={
        -1: (protocolclass.CAL_REMINDER_NONE, 100, 100),
        0: (protocolclass.CAL_REMINDER_ONTIME, 0, 0),
        5: (protocolclass.CAL_REMINDER_5MIN, 5, 0),
        10: (protocolclass.CAL_REMINDER_10MIN, 10, 0),
        60: (protocolclass.CAL_REMINDER_1HOUR, 0, 1),
        1440: (protocolclass.CAL_REMINDER_1DAY, 0, 24),
        2880: (protocolclass.CAL_REMINDER_2DAYS, 0, 48) }
    _default_alarm=(protocolclass.CAL_REMINDER_NONE, 100, 100)    # default alarm is off
    _phone_dow={
        1: protocolclass.CAL_DOW_SUN,
        2: protocolclass.CAL_DOW_MON,
        4: protocolclass.CAL_DOW_TUE,
        8: protocolclass.CAL_DOW_WED,
        16: protocolclass.CAL_DOW_THU,
        32: protocolclass.CAL_DOW_FRI,
        64: protocolclass.CAL_DOW_SAT
        }

    def _set_repeat_event(self, event, entry, exceptions):
        rep_val=self.protocolclass.CAL_REP_NONE
        day_bitmap=0
        rep=entry.repeat
        if rep:
            rep_type=rep.repeat_type
            rep_interval=rep.interval
            rep_dow=rep.dow
            if rep_type==bpcalendar.RepeatEntry.daily:
                if rep_interval==0:
                    rep_val=self.protocolclass.CAL_REP_MONFRI
                elif rep_interval==1:
                    rep_val=self.protocolclass.CAL_REP_DAILY
            elif rep_type==bpcalendar.RepeatEntry.weekly:
                start_dow=1<<datetime.date(*event.start[:3]).isoweekday()%7
                if (rep_dow==0 or rep_dow==start_dow) and rep_interval==1:
                    rep_val=self.protocolclass.CAL_REP_WEEKLY
                    day_bitmap=self._phone_dow.get(start_dow, 0)
            elif rep_type==bpcalendar.RepeatEntry.monthly:
                if rep_dow==0:
                    rep_val=self.protocolclass.CAL_REP_MONTHLY
            else:
                rep_val=self.protocolclass.CAL_REP_YEARLY
            if rep_val!=self.protocolclass.CAL_REP_NONE:
                # build exception list
                if rep.suppressed:
                    day_bitmap|=self.protocolclass.CAL_DOW_EXCEPTIONS
                for x in rep.suppressed:
                    exceptions.setdefault(event.pos, []).append(x.get()[:3])
                # this is a repeat event, set the end date appropriately
                if event.end[:3]==entry.no_end_date:
                    event.end=self.protocolclass.CAL_REPEAT_DATE+event.end[3:]
                else:
                    event.end=entry.end
        event.repeat=rep_val
        event.daybitmap=day_bitmap
            
    def _set_alarm(self, event, entry):
        # set alarm value based on entry's value, or its approximation
        keys=Phone._alarm_info.keys()
        keys.sort()
        keys.reverse()
        _alarm_val=entry.alarm
        _alarm_key=None
        for k in keys:
            if _alarm_val>=k:
                _alarm_key=k
                break
        event.alarmtype, event.alarmminutes, event.alarmhours=Phone._alarm_info.get(
            _alarm_key, self._default_alarm)

    def _set_voice_id(self, event, entry, voice_files):
        if entry.voice and \
           voice_files.has_key(entry.voice-self.protocolclass.cal_voice_id_ofs):
            event.hasvoice=1
            event.voiceid=entry.voice
            del voice_files[entry.voice-self.protocolclass.cal_voice_id_ofs]
        else:
            event.hasvoice=0
            event.voiceid=self.protocolclass.CAL_NO_VOICE
        
    def _set_cal_event(self, event, entry, exceptions, ringtone_index,
                       voice_files):
        # desc
        event.description=entry.description
        # start & end times
        if entry.allday:
            event.start=entry.start[:3]+(0,0)
            event.end=entry.start[:3]+(23,59)
        else:
            event.start=entry.start
            event.end=entry.start[:3]+entry.end[3:]
        # make sure the event lasts in 1 calendar day
        if event.end<event.start:
            event.end=event.start[:3]+(23,59)
        # alarm
        self._set_alarm(event, entry)
        # ringtone
        rt=0    # always default to the first bultin ringtone
        if entry.ringtone:
            for k,e in ringtone_index.items():
                if e['name']==entry.ringtone:
                    rt=k
                    break
            # if rt and rt<50:
            #    rt-=1
        event.ringtone=rt
        # voice ID
        if self._cal_has_voice_id:
            self._set_voice_id(event, entry, voice_files)
        # repeat
        self._set_repeat_event(event, entry, exceptions)
            
    def set_cal(self, sch_file, cal_dict, ringtone_index, voice_files):
        sch_file.numactiveitems=len(cal_dict)
        exceptions={}
        _pos=2
        _packet_size=None
##        _today=datetime.date.today().timetuple()[:5]
        for k, e in cal_dict.items():
##            # only send either repeat events or present&future single events
##            if e.repeat or (e.start>=_today):
            event=self.protocolclass.scheduleevent()
            event.pos=_pos
            self._set_cal_event(event, e, exceptions, ringtone_index,
                                voice_files)
            sch_file.events.append(event)
            if not _packet_size:
                _packet_size=event.packetsize()
            _pos+=_packet_size
        return exceptions

    # Text Memo stuff-----------------------------------------------------------
    def getmemo(self, result):
        # read the memo file
        try:
            buf=prototypes.buffer(self.getfilecontents(
                self.protocolclass.text_memo_file))
            text_memo=self.protocolclass.textmemofile()
            text_memo.readfrombuffer(buf)
            res={}
            for m in text_memo.items:
                entry=memo.MemoEntry()
                entry.text=m.text
                res[entry.id]=entry
        except com_brew.BrewNoSuchFileException:
            res={}
        result['memo']=res
        return result

    def savememo(self, result, merge):
        text_memo=self.protocolclass.textmemofile()
        memo_dict=result.get('memo', {})
        keys=memo_dict.keys()
        keys.sort()
        text_memo.itemcount=len(keys)
        for k in keys:
            entry=self.protocolclass.textmemo()
            entry.text=memo_dict[k].text
            text_memo.items.append(entry)
        buf=prototypes.buffer()
        text_memo.writetobuffer(buf)
        self.writefile(self.protocolclass.text_memo_file, buf.getvalue())
        return result

    # Call History stuff--------------------------------------------------------
    _call_history_info={
        call_history.CallHistoryEntry.Folder_Incoming: protocolclass.incoming_call_file,
        call_history.CallHistoryEntry.Folder_Outgoing: protocolclass.outgoing_call_file,
        call_history.CallHistoryEntry.Folder_Missed: protocolclass.missed_call_file
        }
    def getcallhistory(self, result):
        # read the call history files
        res={}
        for _folder, _file_name in Phone._call_history_info.items():
            try:
                buf=prototypes.buffer(self.getfilecontents(_file_name))
                hist_file=self.protocolclass.callhistoryfile()
                hist_file.readfrombuffer(buf)
                for i in range(hist_file.itemcount):
                    hist_call=hist_file.items[i]
                    entry=call_history.CallHistoryEntry()
                    entry.folder=_folder
                    entry.datetime=hist_call.datetime
                    entry.number=hist_call.number
                    entry.name=hist_call.name
                    if _folder!=call_history.CallHistoryEntry.Folder_Missed:
                        entry.duration=hist_call.duration
                    res[entry.id]=entry
            except com_brew.BrewNoSuchFileException:
                pass
        result['call_history']=res
        return result

parentprofile=com_lgvx4400.Profile
class Profile(parentprofile):
    protocolclass=Phone.protocolclass
    serialsname=Phone.serialsname
    phone_manufacturer='LG Electronics Inc'
    phone_model='VX7000'

    WALLPAPER_WIDTH=176
    WALLPAPER_HEIGHT=184
    MAX_WALLPAPER_BASENAME_LENGTH=32
    WALLPAPER_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 ."
    WALLPAPER_CONVERT_FORMAT="jpg"
   
    MAX_RINGTONE_BASENAME_LENGTH=32
    RINGTONE_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 ."

    # the 7000 doesn't have seperate origins - they are all dumped in "images"
    imageorigins={}
    imageorigins.update(common.getkv(parentprofile.stockimageorigins, "images"))
    def GetImageOrigins(self):
        return self.imageorigins

    # our targets are the same for all origins
    imagetargets={}
    imagetargets.update(common.getkv(parentprofile.stockimagetargets, "wallpaper",
                                      {'width': 176, 'height': 184, 'format': "JPEG"}))
    imagetargets.update(common.getkv(parentprofile.stockimagetargets, "pictureid",
                                      {'width': 176, 'height': 184, 'format': "JPEG"}))
    imagetargets.update(common.getkv(parentprofile.stockimagetargets, "outsidelcd",
                                      {'width': 96, 'height': 80, 'format': "JPEG"}))
    imagetargets.update(common.getkv(parentprofile.stockimagetargets, "fullscreen",
                                      {'width': 176, 'height': 220, 'format': "JPEG"}))

    def GetTargetsForImageOrigin(self, origin):
        return self.imagetargets

 
    def __init__(self):
        parentprofile.__init__(self)

    _supportedsyncs=(
        ('phonebook', 'read', None),  # all phonebook reading
        ('calendar', 'read', None),   # all calendar reading
        ('wallpaper', 'read', None),  # all wallpaper reading
        ('ringtone', 'read', None),   # all ringtone reading
        ('phonebook', 'write', 'OVERWRITE'),  # only overwriting phonebook
        ('calendar', 'write', 'OVERWRITE'),   # only overwriting calendar
        ('wallpaper', 'write', 'MERGE'),      # merge and overwrite wallpaper
        ('wallpaper', 'write', 'OVERWRITE'),
        ('ringtone', 'write', 'MERGE'),      # merge and overwrite ringtone
        ('ringtone', 'write', 'OVERWRITE'),
        ('memo', 'read', None),     # all memo list reading DJP
        ('memo', 'write', 'OVERWRITE'),  # all memo list writing DJP
        ('call_history', 'read', None),
        ('sms', 'read', None),
        ('sms', 'write', 'OVERWRITE'),
        )
p_lgvx7000.p (text/plain, 11.3 KB)
### BITPIM
###
### Copyright (C) 2003-2005 Roger Binns <[email protected]>
###
### This program is free software; you can redistribute it and/or modify
### it under the terms of the BitPim license as detailed in the LICENSE file.
###
### $Id: p_lgvx7000.p,v 1.8 2005/03/14 08:26:48 rogerb Exp $

%{

"""Various descriptions of data specific to LG VX7000"""

from common import PhoneBookBusyException

from prototypes import *

# Make all lg stuff available in this module as well
from p_lg import *

# we are the same as lgvx4400 except as noted
# below
from p_lgvx4400 import *

# We use LSB for all integer like fields
UINT=UINTlsb
BOOL=BOOLlsb

NORINGTONE=65535 # -1 in two bytes
NOMSGRINGTONE=65535 # -1 in two bytes 
NOWALLPAPER=0 # of course it wouldn't be 65535 ...

NUMSPEEDDIALS=100
FIRSTSPEEDDIAL=2
LASTSPEEDDIAL=99
NUMPHONEBOOKENTRIES=500
MAXCALENDARDESCRIPTION=38

NUMEMAILS=2
NUMPHONENUMBERS=5

# Text Memo const
text_memo_file='sch/memo.dat'

# SMS const
sms_dir='sms'
sms_ext='.dat'
sms_inbox_prefix='sms/inbox'
sms_inbox_name_len=len(sms_inbox_prefix)+3+len(sms_ext)
sms_saved_prefix='sms/sf'
sms_saved_name_len=len(sms_saved_prefix)+2+len(sms_ext)
sms_outbox_prefix='sms/outbox'
sms_outbox_name_len=len(sms_outbox_prefix)+3+len(sms_ext)
sms_canned_file='sms/mediacan000.dat'
SMS_CANNED_MAX_ITEMS=18

# Call History const
incoming_call_file='pim/incoming_log.dat'
outgoing_call_file='pim/outgoing_log.dat'
missed_call_file='pim/missed_log.dat'

# The numbertype tab is different than all other LG phones
numbertypetab= ( None, 'cell', 'home', 'office', 'cell2', 'fax' )

%}

PACKET speeddial:
    2 UINT {'default': 0xffff} +entry
    1 UINT {'default': 0xff} +number

PACKET speeddials:
    * LIST {'length': NUMSPEEDDIALS, 'elementclass': speeddial} +speeddials
    
PACKET indexentry:
    2 UINT index
    2 UINT type
    84 STRING filename  "includes full pathname"
    4 UINT {'default': 0} +date "i think this is bitfield of the date"
    4 UINT dunno

PACKET indexfile:
    "Used for tracking wallpaper and ringtones"
    * LIST {'elementclass': indexentry, 'createdefault': True} +items

PACKET sizefile:
    "Used for tracking the total size used by a particular type of media"
    4 UINT size

# All STRINGS have raiseonterminatedread as False since the phone does
# occassionally leave out the terminator byte
# Note if you change the length of any of these fields, you also
# need to modify com_lgvx7000 to give a different truncateat parameter
# in the convertphonebooktophone method
PACKET pbentry:
    4  UINT serial1
    2  UINT {'constant': 0x181, 'constantexception': PhoneBookBusyException} +entrysize
    4  UINT serial2
    2  UINT entrynumber 
    23 STRING {'raiseonunterminatedread': False} name
    2  UINT group
    *  LIST {'length': NUMEMAILS} +emails:
        49 STRING {'raiseonunterminatedread': False} email
    2  UINT ringtone                                     "ringtone index for a call"
    2  UINT msgringtone                                  "ringtone index for a text message"
    2  UINT wallpaper
    * LIST {'length': NUMPHONENUMBERS} +numbertypes:
        1 UINT numbertype
    * LIST {'length': NUMPHONENUMBERS} +numbers:
        49 STRING {'raiseonunterminatedread': False} number
    * UNKNOWN +unknown

PACKET pbreadentryresponse:
    "Results of reading one entry"
    *  pbheader header
    *  pbentry  entry

PACKET pbupdateentryrequest:
    * pbheader {'command': 0x04, 'flag': 0x01} +header
    * pbentry entry

PACKET pbappendentryrequest:
    * pbheader {'command': 0x03, 'flag': 0x01} +header
    * pbentry entry

# Schedule (Calendar)
PACKET scheduleexception:
    4 UINT pos "Refers to event id (position in schedule file) that this suppresses"
    1 UINT day
    1 UINT month
    2 UINT year

PACKET scheduleexceptionfile:
    * LIST {'elementclass': scheduleexception} +items

#63 bytes
#    P UINT { 'constant': 60 } packet_size "Faster than packetsize()"
#    1 UINT { 'default': 0 } +pad2
PACKET scheduleevent:
    4 UINT pos "position within file, used as an event id"
    4 LGCALDATE start
    4 LGCALDATE end
    1 UINT repeat
    2 UINT daybitmap  "which days a weekly repeat event happens on"
    1 UINT alarmminutes  "a value of 100 indicates not set"
    1 UINT alarmhours    "a value of 100 indicates not set"
    1 UINT alarmtype    "preset alarm reminder type"
    1 UINT { 'default': 0 } +snoozedelay   "in minutes, not for this phone"
    1 UINT ringtone
    4 UINT { 'default': 0 } +pad2
    39 STRING {'raiseontruncate': False } description

PACKET schedulefile:
    2 UINT numactiveitems
    * LIST {'elementclass': scheduleevent} +events

# Text Memos
PACKET textmemo:
    151 STRING { 'raiseonunterminatedread': False, 'raiseontruncate': False } text

PACKET textmemofile:
    4 UINT itemcount
    * LIST { 'elementclass': textmemo } +items

# calling history file
PACKET callentry:
    4 GPSDATE datetime
    4 UNKNOWN pad1
    4 UINT duration
    49 STRING { 'raiseonunterminatedread': False } number
    36 STRING { 'raiseonunterminatedread': False } name
    8 UNKNOWN pad2

PACKET callhistoryfile:
    4 UINT itemcount
    1 UNKNOWN pad1
    * LIST { 'elementclass': callentry } +items

# SMS stuff
PACKET SMSInboxFile:
    113 UNKNOWN pad1
    4 LGCALDATE datetime
    10 UNKNOWN pad2
    1 UINT locked
    9 UNKNOWN pad3
    3770 STRING { 'raiseonunterminatedread': False, 'raiseontruncate': False } text
    57 STRING { 'raiseonunterminatedread': False, 'raiseontruncate': False } _from
    47 UNKNOWN pad4
    57 STRING { 'raiseonunterminatedread': False, 'raiseontruncate': False } callback
    * UNKNOWN pad5

PACKET SMSSavedFile:
    4 UINT outboxmsg
    4 UNKNOWN pad
    if self.outboxmsg:
        * SMSOutboxFile outbox
    if not self.outboxmsg:
        * SMSInboxFile inbox

PACKET SMSOutboxFile:
    4 UNKNOWN pad1
    1 UINT locked
    4 LGCALDATE datetime
    1610 STRING { 'raiseonunterminatedread': False, 'raiseontruncate': False } text
    4 UNKNOWN pad2
    35 STRING { 'raiseonunterminatedread': False, 'raiseontruncate': False } callback
    35 STRING { 'raiseonunterminatedread': False, 'raiseontruncate': False } _to
    * UNKNOWN pad3

PACKET SMSCannedMsg:
    101 STRING { 'raiseonunterminatedread': False, 'raiseontruncate': False, 'default': '' } +text

PACKET SMSCannedFile:
    * LIST { 'length': SMS_CANNED_MAX_ITEMS, 'elementclass': SMSCannedMsg } +items
###
### SMS 
###
#
#   There are 3 types of SMS records, The inbox, outbox and unsent (pending)
#   Unlike other records in the phone each message is stored in a separate file
#   All messages are in the 'sms' directory in the root of the phone
#   Inbox messages are in files called 'inbox000.dat', the number 000 varies for
#   each message, typically there are no gaps in the numbering, but gaps can appear
#   if a message is deleted.
#   Outbox message are named 'outbox000.dat', unsent messages are named 'sf00.dat',
#   only two digit file name that suggests a max of 100 message for this type.
#   Messages in the outbox get updated when the message is received by the recipient,
#   they contain a delivery flag and a delivery time for all the possible 10 recipients.
#   The vx8100 supports SMS contatination, this allows you to send text messages that are
#   longer than 160 characters. The format is different for these type of messages, but
#   it is supported by this implementation.
#   The vx8100 also allows you to put small graphics, sounds and animations in a message.
#   This implementation does not support these, if they are contained in a message they
#   will be ignored and just the text will be shown when you view the message in bitpim.
#   The text in the the messages is stored in 7-bit characters, so they have
#   to be unpacked, in concatinated messages and messages with embeded graphics etc. the
#   format uses the GSM 03.38 specified format, a good example of this can be found at
#   "http://www.dreamfabric.com/sms/hello.html".
#   For simple messages less than 161 characters with no graphics the format is simpler, 
#   the 7-bit characters are just packed into memory in the order they appear in the
#   message.

PACKET msg_record:
    # the first few fields in this packet have something to do with the type of SMS
    # message contained. EMS and concatinated text are coded differently than a
    # simple text message
    1 UINT binary   # 0=simple text, 1=binary/concatinated
    1 UINT unknown3 # 0=simple text, 1=binary/concatinated
    1 UINT unknown4 # 0
    1 UINT unknown6 # 2=simple text, 9=binary/concatinated
    1 UINT length
    * LIST {'length': 220} +msg:
        1 UINT byte "individual byte of message"

PACKET recipient_record:
    49 STRING number
    2 UINT status   # 1 when sent, 5 when received, 2 failed to send
    4 LGCALDATE timesent
    4 LGCALDATE timereceived
    49 UNKNOWN unknown2

PACKET sms_saved:
    4 UINT outboxmsg
    4 UNKNOWN pad
    if self.outboxmsg:
        * sms_out outbox
    if not self.outboxmsg:
        * sms_in inbox

PACKET sms_out:
    4 UINT index # starting from 1, unique
    1 UINT locked # 1=locked
    1 UNKNOWN unknown2
    4 LGCALDATE timesent # time the message was sent
    6 UNKNOWN unknown2
    21 STRING subject
    1 UNKNOWN unknown4
    2 UINT num_msg_elements # up to 10
    * LIST {'elementclass': msg_record, 'length': 7} +messages
    14 UNKNOWN unknown1
    1 UINT priority # 0=normal, 1=high
    1 UNKNOWN unknown5
    35 STRING callback 
    * LIST {'elementclass': recipient_record,'length': 9} +recipients
    * UNKNOWN pad

PACKET SMSINBOXMSGFRAGMENT:
    * LIST {'length': 181} +msg: # this size could be wrong
        1 UINT byte "individual byte of message"

PACKET sms_in:
    10 UNKNOWN unknown1
    6 SMSDATE timesent
    3 UINT unknown2
    1 UINT callback_length # 0 for no callback number
    38 STRING callback
    1 UINT sender_length
    * LIST {'length': 38} +sender:
        1 UINT byte "individual byte of senders phone number"
    12 DATA unknown3 # set to zeros
    4 LGCALDATE lg_time # time the message was sent
    3 UNKNOWN unknown4
    4 GPSDATE GPStime # num seconds since 0h 1-6-80, time message received by phone
    4 UINT unknown5 # zero
    1 UINT read # 1 if message has been read, 0 otherwise
    1 UINT locked # 1 if the message is locked, 0 otherwise
    8 UINT unknown6 # zero
    1 UINT priority # 1 if the message is high priority, 0 otherwise
    21 STRING subject
    1 UINT bin_header1 # 0 in simple message 1 if the message contains a binary header
    1 UINT bin_header2 # 0 in simple message 9 if the message contains a binary header
    4 UINT unknown7 # zeros
    2 UINT multipartID # multi-part message ID, used for concatinated messages only
    1 UINT bin_header3 # 0 in simple message 2 if the message contains a binary header
    1 UINT num_msg_elements # max 10 elements (guessing on max here)
    * LIST {'length': 10} +msglengths:
        1 UINT msglength "lengths of individual messages in septets"
    10 UNKNOWN unknown8
    * LIST {'length': 10, 'elementclass': SMSINBOXMSGFRAGMENT} +msgs 
                # 181 bytes per message, uncertain on this, no multipart message available
                # 20 messages, 7-bit ascii for simple text. for binary header 
                # first byte is header length not including the length byte
                # rest depends on content of header, not known at this time.
                # text alway follows the header although the format it different
                # than a simple SMS
    * UNKNOWN unknown9
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.