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

Source Code for Module phones.com_samsungspha840_telus

  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 
77 -class Phone(parentphone):
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
118 - def __init__(self, logtarget, commport):
119 com_samsung_packet.Phone.__init__(self, logtarget, commport) 120 self.numbertypetab=numbertypetab 121 self.mode=self.MODENONE
122
123 - def getfundamentals(self, results):
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
142 - def _get_builtin_index(self, builtin_list):
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
152 - def get_wallpaper_index(self):
153 _res=self._get_builtin_index(self.builtin_pictures) 154 return _res
155
156 - def pblinerepair(self, line):
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
167 - def countcommas(self, line):
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
179 - def savewallpapers(self, result):
180 raise NotImplementedError()
181
182 - def saveringtones(self, result):
183 raise NotImplementedError()
184
185 - def getringtones(self, results):
186 raise NotImplementedError()
187
188 - def getwallpapers(self, results):
189 raise NotImplementedError()
190
191 - def read_groups(self):
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
204 - def savegroups(self, data):
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 #
216 - def _extract_wallpaper(self, res, entry, fundamentals):
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 #
254 - def _extract_ringtone(self, res, entry, fundamentals):
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 #
265 - def extractphonebookentry(self, entry, fundamentals):
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 #
318 - def getphonebook(self, result):
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 #
351 - def makeentry(self, entry, data):
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 #
409 - def savephonebook(self, data):
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 #
490 - def getcalendar(self, result):
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 #
530 - def savecalendar(self, dict, merge):
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
581 -class Profile(parentprofile):
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
590 - def __init__(self):
591 parentprofile.__init__(self) 592 self.numbertypetab=numbertypetab
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
606 -class Samsung_Calendar:
607 _cal_alarm_values={ 608 10: 2, 30: 3, 60: 4, -1: 0, 0: 1 } 609
610 - def __init__(self, calendar_entry, new_date=None):
611 self._start=self._end=self._alarm=self._desc=None 612 self._extract_cal_info(calendar_entry, new_date)
613
614 - def _extract_cal_info(self, cal_entry, new_date):
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
632 - def __lt__(self, rhs):
633 return self.start<rhs.start
634 - def __le__(self, rhs):
635 return self.start<=rhs.start
636 - def __eq__(self, rhs):
637 return self.start==rhs.start
638 - def __ne__(self, rhs):
639 return self.start!=rhs.start
640 - def __gt__(self, rhs):
641 return self.start>rhs.start
642 - def __ge__(self, rhs):
643 return self.start>=rhs.start
644
645 - def _get_start(self):
646 return self._start
647 start=property(fget=_get_start) 648
649 - def _get_end(self):
650 return self._end
651 end=property(fget=_get_end) 652
653 - def _get_desc(self):
654 return self._desc
655 description=property(fget=_get_desc) 656
657 - def _get_alarm(self):
658 return self._alarm
659 alarm=property(fget=_get_alarm)
660