Package conduit :: Package modules :: Package PhoneModule :: Module Gammu
[hide private]

Source Code for Module conduit.modules.PhoneModule.Gammu

  1  #FIXME: Proper license and attribution 
  2  #Most code adapted from gammu (GPL2) and phonetooth (GPL2) 
  3   
  4  import os 
  5  import bluetooth 
  6  import logging 
  7  log = logging.getLogger("modules.Phone") 
  8   
  9  import conduit.dataproviders.DataProvider as DataProvider 
 10  import conduit.utils as Utils 
 11  import ScanThreads 
 12  import Data 
 13   
 14  try: 
 15      import gammu 
 16      log.info("Module Information: %s" % Utils.get_module_information(gammu, "__version__")) 
 17      GAMMU_SUPPORTED = True 
 18  except ImportError: 
 19      log.info("Gammu based phone support disabled") 
 20      GAMMU_SUPPORTED = False 
 21   
22 -class GammuPhone:
23 """ 24 Encapsulates the basic connection to a phone using gammu. 25 """
26 - def __init__(self, address, connection, model=''):
27 """ 28 Attempts to connect to the phone at address and connection and 29 get all available phone informatin, like the model, manufacturer, etc. 30 """ 31 self.address = address 32 self.connection = connection 33 self.model = model 34 self.isKnownToGammu = False 35 self.info = {} 36 37 self.sm = gammu.StateMachine() 38 self.sm.SetConfig(0, { 39 'StartInfo' : 'no', 40 'UseGlobalDebugFile' : 1, 41 'DebugFile' : '', 42 'SyncTime' : 'no', 43 'Connection' : connection, 44 'LockDevice' : 'no', 45 'DebugLevel' : 'nothing', 46 'Device' : address, 47 'Localize' : None, 48 'Model' : model} 49 ) 50 #dont catch errors thrown here.... 51 self.sm.Init() 52 #if we got here, then we managed a rudimentary connection 53 self.info['connection'] = connection 54 55 #catch non fatal errors because some phones might not 56 #support these fields 57 try: 58 self.info['manufacturer'] = self.sm.GetManufacturer() 59 except gammu.ERR_NOTSUPPORTED, gammu.ERR_NOTIMPLEMENTED: 60 self.info['manufacturer'] = "Unknown" 61 62 model = "Unknown" 63 try: 64 m = self.sm.GetModel() 65 if m[0] == '' or m[0] == 'unknown': 66 model = m[1] 67 else: 68 model = m[0] 69 self.isKnownToGammu = True 70 except gammu.ERR_NOTSUPPORTED, gammu.ERR_NOTIMPLEMENTED: 71 model = "Unknown" 72 self.info['model'] = model 73 74 try: 75 self.info['firmware'] = self.sm.GetFirmware()[0] 76 except gammu.ERR_NOTSUPPORTED, gammu.ERR_NOTIMPLEMENTED: 77 self.info['firmware'] = "Unknown"
78
79 -class Bluetooth(ScanThreads.ScanThread):
80 - def __init__(self, foundCallback):
81 ScanThreads.ScanThread.__init__(self) 82 self.foundCallback = foundCallback 83 self.discovery = ScanThreads.DeviceDiscovererFilter(self) 84 85 #keep a track of what phones we have seen, so we dont need to find 86 #their services over and over again 87 # address : name 88 self._phones = {}
89
90 - def lookup_model_information(self, address):
91 #try all bluetooth connections 92 for connection in Data.get_connections_from_bluetooth_address(address): 93 log.info("Connecting to %s Using: %s" % (address,connection)) 94 try: 95 phone = GammuPhone(address, connection) 96 phone.sm.Terminate() 97 log.info("\t ---> OK (%s)" % ', '.join(phone.info.values())) 98 return phone.info 99 100 except gammu.GSMError, val: 101 log.warn("\t ---> Failed") 102 103 #connection = None means we tried to connect and failed 104 return { 105 'connection' : None 106 }
107
108 - def run(self):
109 while self.is_cancelled() == False: 110 log.info("Beginning Bluetooth Scan") 111 try: 112 self.discovery.find_devices() 113 self.discovery.process_inquiry() 114 for address,info in self._found.items(): 115 if address in os.environ.get('PHONE_BLACKLIST',"").split(","): 116 log.info("Skipping %s, blacklisted" % address) 117 continue 118 #Use gammu to lookup the model information. This also tests 119 #if gammu can actually connect to the phone 120 if not info.has_key('connection'): 121 #gammu info 122 info.update( 123 self.lookup_model_information(address) 124 ) 125 #services, which may be useful for things like obexftp, 126 #and other dataproviders in the PhoneModule 127 services = bluetooth.find_service(address=address) 128 log.info("Supported services: %s" % ", ".join([i['name'] for i in services])) 129 info['services'] = services 130 131 self.foundCallback(address,info['name'],"bluetooth",info) 132 except bluetooth.BluetoothError: 133 log.warn("Error discovering services") 134 135 self.pause_scanning()
136
137 - def cancel(self):
138 ScanThreads.ScanThread.cancel(self) 139 self.discovery.cancel_inquiry()
140
141 -class Contact:
142 - def __init__(self, name, phoneNumber):
143 self.name = name 144 self.phoneNumber = phoneNumber
145
146 - def __str__(self):
147 return self.name + " - " + self.phoneNumber
148
149 -class GammuDataProvider(DataProvider.DataSource):
150 151 _name_ = "Contacts" 152 _module_type_ = "source" 153 _in_type_ = "contact" 154 _out_type_ = "contact" 155 156 #FIXME: What does this mean?? 157 MAX_EMPTY_GUESS = 5 158 MAX_EMPTY_KNOWN = 5 159
160 - def __init__(self, address, connection):
161 DataProvider.DataSource.__init__(self) 162 self.address = address 163 self.connection = connection 164 #FIXME: Stupid sharp phone obex over bluetooth is broken, so 165 #default to at 166 self.model = "at" 167 self.phone = None
168
169 - def get_UID(self):
170 return self.address
171
172 - def _guess_num_items(self):
173 return 200
174
175 - def _get_first_entry(self):
176 ''' 177 Initiates get next sequence. 178 179 Should be implemented in subclases. 180 ''' 181 raise NotImplementedError
182
183 - def _get_next_entry(self, location):
184 ''' 185 Gets next entry. 186 187 Should be implemented in subclases. 188 ''' 189 raise NotImplementedError
190
191 - def _get_entry(self, location):
192 ''' 193 Gets entry. 194 195 Should be implemented in subclases. 196 ''' 197 raise NotImplementedError
198
199 - def _get_num_items(self):
200 ''' 201 Gets status of entries. 202 203 Should be implemented in subclases. 204 ''' 205 raise NotImplementedError
206
207 - def _parse_entry(self):
208 ''' 209 Parses entry. 210 211 Should be implemented in subclases. 212 ''' 213 raise NotImplementedError
214
215 - def Send(self):
216 ''' 217 Sends entries to parent. 218 219 Should be implemented in subclases. 220 ''' 221 raise NotImplementedError
222
223 - def Run(self):
224 ''' 225 UNFINISHED PORT OF WAMMU's SUBCLASSABLE APPROACH FOR GETTING DATA 226 ''' 227 guess = False 228 try: 229 total = self._get_num_items() 230 except gammu.GSMError, val: 231 guess = True 232 total = self._guess_num_items() 233 234 remain = total 235 236 data = [] 237 238 try: 239 start = True 240 while remain > 0: 241 #if self.canceled: 242 # self.Canceled() 243 # return 244 try: 245 if start: 246 value = self._get_first_entry() 247 start = False 248 else: 249 try: 250 loc = value['Location'] 251 except TypeError: 252 loc = value[0]['Location'] 253 value = self._get_next_entry(loc) 254 except gammu.ERR_CORRUPTED: 255 log.warn('While reading, entry on location %d seems to be corrupted, ignoring it!' % loc) 256 continue 257 except gammu.ERR_EMPTY: 258 break 259 260 self._parse_entry(value) 261 if type(value) == list: 262 for i in range(len(value)): 263 value[i]['Synced'] = True 264 else: 265 value['Synced'] = True 266 data.append(value) 267 remain = remain - 1 268 except (gammu.ERR_NOTSUPPORTED, gammu.ERR_NOTIMPLEMENTED): 269 location = 1 270 empty = 0 271 while remain > 0: 272 #if self.canceled: 273 # self.Canceled() 274 # return 275 try: 276 value = self._get_entry(location) 277 self._parse_entry(value) 278 if type(value) == list: 279 for i in range(len(value)): 280 value[i]['Synced'] = True 281 else: 282 value['Synced'] = True 283 data.append(value) 284 remain = remain - 1 285 # If we didn't know count and reached end, try some more entries 286 if remain == 0 and guess: 287 remain = 20 288 total = total + 20 289 empty = 0 290 except gammu.ERR_EMPTY, val: 291 empty = empty + 1 292 # If we didn't know count and saw many empty entries, stop right now 293 if empty >= self.MAX_EMPTY_GUESS and guess: 294 break 295 # If we didn't read anything for long time, we bail out (workaround bad count reported by phone) 296 if empty >= self.MAX_EMPTY_KNOWN and remain < 10: 297 self.ShowError(val[0]) 298 remain = 0 299 except gammu.ERR_CORRUPTED: 300 log.warn('While reading, entry on location %d seems to be corrupted, ignoring it!' % location) 301 continue 302 except gammu.GSMError, val: 303 log.critical(val[0]) 304 return 305 location = location + 1 306 except gammu.ERR_INVALIDLOCATION, val: 307 # if we reached end with guess, it is okay 308 if not guess: 309 log.critical(val[0]) 310 return 311 except gammu.GSMError, val: 312 log.critical(val[0]) 313 return
314 315 #self.Send(data) 316
317 - def refresh(self):
318 if not self.phone: 319 self.phone = GammuPhone(self.address, self.connection, self.model) 320 log.debug("Connected to phone: %s" % (self.phone.info['model'])) 321 self.get_all()
322
323 - def get_all(self):
324 #phone = ME, sim = SM 325 location = "ME" 326 contactList = [] 327 328 status = self.phone.sm.GetMemoryStatus(Type=location) 329 remain = status['Used'] 330 log.debug("%s contacts on phone" % remain) 331 332 start = True 333 while remain > 0: 334 if start: 335 entry = self.phone.sm.GetNextMemory(Start=True, Type=location) 336 start = False 337 else: 338 entry = self.phone.sm.GetNextMemory(Location=entry['Location'], Type=location) 339 340 remain = remain - 1 341 342 contact = Contact('', '') 343 for v in entry['Entries']: 344 if v['Type'] == 'Number_General': 345 contact.phoneNumber = v['Value'] 346 elif v['Type'] == 'Text_Name': 347 contact.name = v['Value'] 348 349 if len(contact.name) > 0 and len(contact.phoneNumber) > 0: 350 print "*"*20,contact 351 #contactList.append(contact) 352 353 return contactList
354