All devices should inherit from WCDMAWrapper in order to override its base methods. It accepts two argument for its constructor, an instance or subclass of DevicePlugin and the udi of the device to use.
A DevicePlugin contains all the necessary information to speak with the device: a data port, a control port (if exists), a baudrate, etc. It also has the following attributes and members that you can customise for your device plugin:
The object wader.common.hardware.base.WCDMACustomizer acts as a container for all the device-specific customizations such as:
Lets have a look at the NovatelXU870 plugin:
import serial
from wader.common import consts
from wader.common.hardware.novatel import (NovatelWCDMADevicePlugin,
NovatelWCDMACustomizer,
NOVATEL_BAND_DICT)
from wader.common.hardware.base import build_band_dict
class NovatelXU870Customizer(NovatelWCDMACustomizer):
"""
:class:`~wader.common.hardware.novatel.NovatelWCDMACustomizer` for XU870
"""
# Supported bands (from Novatel docs)
# GSM/GPRS
# GSM 850 824 -894MHz
# EGSM 900 880-960MHz
# DCS 1800 1710-1880MHz
# PCS 1900 1850-1990MHz
# WCDMA
# UMTS 850 (Band V) 824 -894MHz
# UMTS 1900 (Band II) 1850-1990MHz
# UMTS 2100 (Band I) 1920-2170MHz
band_dict = build_band_dict(
NOVATEL_BAND_DICT,
[consts.MM_NETWORK_BAND_ANY,
consts.MM_NETWORK_BAND_G850,
consts.MM_NETWORK_BAND_EGSM,
consts.MM_NETWORK_BAND_DCS,
consts.MM_NETWORK_BAND_PCS,
consts.MM_NETWORK_BAND_U850,
# XXX: Novatel docs show UMTS 1900 (Band II)
# but consts.py has this as UMTS 1900 Class IX
consts.MM_NETWORK_BAND_U1900,
consts.MM_NETWORK_BAND_U2100])
class NovatelXU870(NovatelWCDMADevicePlugin):
""":class:`~wader.common.plugin.DevicePlugin` for Novatel's XU870"""
name = "Novatel XU870"
version = "0.1"
author = u"Pablo Martí"
custom = NovatelXU870Customizer()
__remote_name__ = "Merlin XU870 ExpressCard"
__properties__ = {
'ID_VENDOR_ID': [0x1410],
'ID_MODEL_ID': [0x1430],
}
def preprobe_init(self, ports, info):
# Novatel secondary port needs to be flipped from DM to AT mode
# before it will answer our AT queries. So the primary port
# needs this string first or auto detection of ctrl port fails.
# Note: Early models/firmware were DM only
ser = serial.Serial(ports[0], timeout=1)
ser.write('AT$NWDMAT=1\r\n')
ser.close()
novatelxu870 = NovatelXU870()
In an ideal world, devices have a unique vendor and product id tuple, they conform to the relevant CDMA or WCDMA specs, and that’s it. The device is identified by its vendor and product ids and double-checked with its __remote_name__ attribute (the response to an AT+GMR command). This vendor and product id tuple will usually use the usb bus, however some devices might end up attached to the pci or pcmcia buses. The last line in the plugin will create an instance of the plugin in wader’s plugin system -otherwise it will not be found!.
Take for example the HuaweiE620 class:
import re
from wader.common.hardware.huawei import (HuaweiWCDMADevicePlugin,
HuaweiWCDMACustomizer)
from wader.common.command import build_cmd_dict
E620_CMD_DICT = HuaweiWCDMACustomizer.cmd_dict.copy()
E620_CMD_DICT['get_roaming_ids'] = build_cmd_dict(re.compile(
"""
\r\n
\+CPOL:\s(?P<index>\d+),"(?P<netid>\d+)"
""", re.VERBOSE))
class HuaweiE620Customizer(HuaweiWCDMACustomizer):
cmd_dict = E620_CMD_DICT
class HuaweiE620(HuaweiWCDMADevicePlugin):
""":class:`~wader.common.plugin.DevicePlugin` for Huawei's E620"""
name = "Huawei E620"
version = "0.1"
author = u"Pablo Martí"
custom = HuaweiE620Customizer()
__remote_name__ = "E620"
__properties__ = {
'usb_device.vendor_id': [0x12d1],
'usb_device.product_id': [0x1001],
}
The E620 plugin is identical to the XU870 except one small difference regarding the parsing of the get_roaming_ids command. The E620 omits some information that other devices do output, and the regular expression object that parses it has to be updated. We get a new copy of the cmd_dict dictionary attribute and modify it with the new regexp the get_roaming_ids entry. The new cmd_dict is specified in its Customizer object.
from wader.common.hardware.huawei import (HuaweiWCDMADevicePlugin,
HuaweiSIMClass)
class HuaweiE220SIMClass(HuaweiSIMClass):
"""Huawei E220 SIM Class"""
def __init__(self, sconn):
super(HuaweiE220SIMClass, self).__init__(sconn)
def initialize(self, set_encoding=False):
d = super(HuaweiE220SIMClass, self).initialize(set_encoding)
def init_cb(size):
self.sconn.get_smsc()
# before switching to UCS2, we need to get once the SMSC number
# otherwise as soon as we send a SMS, the device would reset
# as if it had been unplugged and replugged to the system
def process_charset(charset):
"""
Do not set charset to UCS2 if is not necessary, returns size
"""
if charset == "UCS2":
return size
else:
d3 = self.sconn.set_charset("UCS2")
d3.addCallback(lambda ignored: size)
return d3
d2 = self.sconn.get_charset()
d2.addCallback(process_charset)
return d2
d.addCallback(init_cb)
return d
class HuaweiE220(HuaweiWCDMADevicePlugin):
""":class:`~wader.common.plugin.DevicePlugin` for Huawei's E220"""
name = "Huawei E220"
version = "0.1"
author = u"Pablo Martí"
sim_klass = HuaweiE220SIMClass
__remote_name__ = "E220"
__properties__ = {
'usb_device.vendor_id': [0x12d1],
'usb_device.product_id': [0x1003, 0x1004],
}
Huawei’s E220, despite sharing its manufacturer with the E620, has a couple of minor differences that deserve some explanation. There’s a bug in its firmware that will reset the device if you ask its SMSC. The workaround is to get once the SMSC before switching to UCS2, you’d be amazed of how long took me to discover the fix. The second difference with the E620 is that the E220 can have several product_ids, thus its product_id list has two elements.
from twisted.python import log
from wader.common.hardware.option import (OptionWCDMADevicePlugin,
OptionWCDMACustomizer)
from wader.common.sim import SIMBaseClass
from wader.common.statem.auth import AuthStateMachine
from wader.contrib.modal import mode
class OptionColtAuthStateMachine(AuthStateMachine):
"""
Custom AuthStateMachine for Option Colt
This device has a rather buggy firmware that yields all sort of
weird errors. For example, if PIN authentication is disabled on the SIM
and you issue an AT+CPIN? command, it will reply with a +CPIN: SIM PUK2
"""
pin_needed_status = AuthStateMachine.pin_needed_status
puk_needed_status = AuthStateMachine.puk_needed_status
puk2_needed_status = AuthStateMachine.puk2_needed_status
class get_pin_status(mode):
"""
Returns the authentication status
The SIM can be in one of the following states:
- SIM is ready (already authenticated, or PIN disabled)
- PIN is needed
- PIN2 is needed (not handled)
- PUK is needed
- PUK2 is needed
- SIM is not inserted
- SIM's firmware error
"""
def __enter__(self):
pass
def __exit__(self):
pass
def do_next(self):
log.msg("Instantiating get_pin_status mode....")
d = self.device.sconn.get_pin_status()
d.addCallback(self.get_pin_status_cb)
d.addErrback(self.sim_failure_eb)
d.addErrback(self.sim_busy_eb)
d.addErrback(self.sim_no_present_eb)
d.addErrback(log.err)
class OptionColtSIMClass(SIMBaseClass):
"""Option Colt SIM Class"""
def __init__(self, sconn):
super(OptionColtSIMClass, self).__init__(sconn)
def initialize(self, set_encoding=False):
self.charset = 'IRA'
d = super(OptionColtSIMClass, self).initialize(set_encoding)
d.addCallback(self.set_size)
return d
class OptionColtCustomizer(OptionWCDMACustomizer):
""":class:`~wader.common.hardware.Customizer` for Option Colt"""
auth_klass = OptionColtAuthStateMachine
class OptionColt(OptionWCDMADevicePlugin):
""":class:`~wader.common.plugin.DevicePlugin` for Option Colt"""
name = "Option Colt"
version = "0.1"
author = u"Pablo Martí"
custom = OptionColtCustomizer()
sim_klass = OptionColtSIMClass
__remote_name__ = "129"
__properties__ = {
'ID_VENDOR_ID': [0x0af0],
'ID_MODEL_ID': [0x5000],
}
optioncolt = OptionColt()
This data card is the buggiest card we’ve found so far, and has proven to be an excellent challenge for the extensibility and granularity of our plugin system. Basically we’ve found the following bugs on the card’s firmware:
So we had to modify the AuthStateMachine for this particular device and its cmd_dict.