How to add support for a new device

  1. Overview of a simple DevicePlugin
  2. Overview of a relatively simple DevicePlugin
  3. Overview of a not so simple DevicePlugin
  4. Overview of a complex DevicePlugin

All devices should inherit from either CDMAAdapter or WCDMAAdapter depending on the device type. Both classes accept one argument for its constructor, an instance of the class, DBusDevicePlugin. A DBusDevicePlugin 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 WCDMACustomizer or CDMACustomizer acts as a container for all the device-specific customizations, such as:

Overview of a simple DevicePlugin

Lets have a look at the NovatelXU870 plugin:

from wader.common.hardware.novatel import NovatelWCDMADevicePlugin

class NovatelXU870(NovatelWCDMADevicePlugin):
    """L{wader.common.plugin.DBusDevicePlugin} for Novatel's XU870"""
    name = "Novatel XU870"
    version = "0.1"
    author = u"Pablo Martí"

    __remote_name__ = "Merlin XU870 ExpressCard"

    __properties__ = {
        'usb_device.vendor_id' : [0x1410],
        'usb_device.product_id' : [0x1430],
    }

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 a 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 instace of the plugin in wader's plugin system -otherwise it will not be found!.

Overview of a relatively simple DevicePlugin

Take for example the HuaweiE620 class:

import re

from wader.common.hardware.huawei import (HuaweiWCDMADevicePlugin,
                                        HuaweiWCDMACustomizer)
from wader.common.command import get_cmd_dict_copy, OK_REGEXP, ERROR_REGEXP

info = dict(echo=None,
            end=OK_REGEXP,
            error=ERROR_REGEXP,
            extract=re.compile(
                """
                \r\n
                \+CPOL:\s(?P<index>\d+),"(?P<netid>\d+)"
                """, re.VERBOSE))

E620_CMD_DICT = get_cmd_dict_copy()
E620_CMD_DICT['get_roaming_ids'] = info

class HuaweiE620Customizer(HuaweiWCDMACustomizer):
    cmd_dict = E620_CMD_DICT


class HuaweiE620(HuaweiWCDMADevicePlugin):
    """L{wader.common.plugin.DBusDevicePlugin} 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 ommits 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 and modify it with the new regexp the get_roaming_ids entry. The new cmd_dict is specified in its Customizer object.

Overview of a not so simple DevicePlugin

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=False)
        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":
                    self.set_charset(charset)
                    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):
    """L{wader.common.plugin.DBusDevicePlugin} for Huawei's E220"""
    name = "Huawei E220"
    version = "0.1"
    author = u"Pablo Martí"
    simklass = 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 have two elements.

Overview of a complex DevicePlugin

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 epsilon.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):
        """
        Ask the PIN what's the PIN 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):
    """Huawei E220 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(lambda size: self.set_size(size))
        return d

class OptionColtCustomizer(OptionWCDMACustomizer):
    """L{wader.common.hardware.Customizer} for Option Colt"""
    authklass = OptionColtAuthStateMachine


class OptionColt(OptionWCDMADevicePlugin):
    """L{wader.common.plugin.DBusDevicePlugin} for Option Colt"""
    name = "Option Colt"
    version = "0.1"
    author = u"Pablo Martí"
    custom = OptionColtCustomizer()
    simklass = OptionColtSIMClass

    __remote_name__ = "129"

    __properties__ = {
        'usb_device.vendor_id' : [0x0af0],
        'usb_device.product_id' : [0x5000],
    }


optioncolt = OptionColt()

Option 3G Datacard 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.