Adding support for new phone

This document covers basic information on adding support for new phone into Gammu. It will never cover all details, but will give you basic instructions.

Adding support for new AT commands

The easiest situation is when all you need to support new device is to add support for new AT commands. All the protocol infrastructure is there, you only need to hook new code into right places.

The main code for AT driver is in libgammu/phone/at/atgen.c. At the bottom of the file, you can find two arrays, one defining driver interface (GSM_Phone_Functions) and second one defining callbacks (see Reply functions for more detailed description. You will definitely need to define callbacks for newly introduced commands, but the interface for desired functionality might already exist.

Detecting whether command is supported

As Gammu is trying to support as much phones as possible, you should try to make it automatically detect whether connected phone supports the command. This can be done on first invocation of affected operation or on connecting to phone. As we want to avoid lengthy connecting to phone, in most cases you should probe for support on first attempt to use given functionality. The code might look like following:

GSM_Error ATGEN_GetFoo(GSM_StateMachine *s) {
    GSM_Phone_ATGENData     *Priv = &s->Phone.Data.Priv.ATGEN;

    if (Priv->Foo_XXXX == 0) {
        ATGEN_CheckXXXX(s);
    }

    if (Priv->Foo_XXXX == AT_AVAILABLE) {
        /* Perform reading */
    }

    /* Fail with error or fallback to other methods */
    return ERR_NOTSUPPORTED;
}

GSM_Error ATGEN_CheckXXXX(GSM_StateMachine *s) {
    GSM_Error       error;
    GSM_Phone_ATGENData     *Priv = &s->Phone.Data.Priv.ATGEN;

    smprintf(s, "Checking availability of XXXX\n");
    ATGEN_WaitForAutoLen(s, "AT+XXXX=?\r", 0x00, 4, ID_GetProtocol);
    if (error == ERR_NONE) {
        Priv->Foo_XXXX = AT_AVAILABLE;
    } else {
        Priv->Foo_XXXX = AT_NOTAVAILABLE;
    }
    return error;
}


GSM_Reply_Function ATGENReplyFunctions[] = {
...
{ATGEN_GenericReply,        "AT+XXXX=?"             ,0x00,0x00,ID_GetProtocol                },
...

Alternatively (if detection is not possible), you can use features and phones database (see libgammu/gsmphones.c) or vendor based decision to use some commands.

Invoking AT command

The AT commands are invoked using GSM_WaitFor(), or a wrapper ATGEN_WaitForAutoLen(), where you don’t have to specify length for text commands and automatically sets error variable.

Generally you need to construct buffer and then invoke it. For some simple functions it is pretty straight forward:

GSM_Error ATGEN_GetBatteryCharge(GSM_StateMachine *s, GSM_BatteryCharge *bat)
{
    GSM_Error error;

    GSM_ClearBatteryCharge(bat);
    s->Phone.Data.BatteryCharge = bat;
    smprintf(s, "Getting battery charge\n");
    ATGEN_WaitForAutoLen(s, "AT+CBC\r", 0x00, 4, ID_GetBatteryCharge);
    return error;
}

As you can see, it is often required to store pointer to data store somewhere, for most data types s->Phone.Data does contain the pointer to do that.

Parsing reply

For parsing reply, you should use ATGEN_ParseReply(), which should be able to handle all encoding and parsing magic. You can grab lines from the reply using GetLineString().

The reply function needs to be hooked to the reply functions array, so that it is invoked when reply is received from the phone.

Continuing in above example for getting battery status, the (simplified) function would look like:

GSM_Error ATGEN_ReplyGetBatteryCharge(GSM_Protocol_Message *msg, GSM_StateMachine *s)
{
    GSM_Error error;
    GSM_Phone_ATGENData *Priv = &s->Phone.Data.Priv.ATGEN;
    GSM_BatteryCharge *BatteryCharge = s->Phone.Data.BatteryCharge;
    int bcs = 0, bcl = 0;

    switch (s->Phone.Data.Priv.ATGEN.ReplyState) {
        case AT_Reply_OK:
            smprintf(s, "Battery level received\n");
            error = ATGEN_ParseReply(s,
                GetLineString(msg->Buffer, &Priv->Lines, 2),
                "+CBC: @i, @i",
                &bcs,
                &bcl);

            BatteryCharge->BatteryPercent = bcl;

            switch (bcs) {
                case 0:
                    BatteryCharge->ChargeState = GSM_BatteryPowered;
                    break;
                case 1:
                    BatteryCharge->ChargeState = GSM_BatteryConnected;
                    break;
                case 2:
                    BatteryCharge->ChargeState = GSM_BatteryCharging;
                    break;
                default:
                    BatteryCharge->ChargeState = 0;
                    smprintf(s, "WARNING: Unknown battery state: %d\n", bcs);
                    break;
            }
            return ERR_NONE;
        case AT_Reply_Error:
            smprintf(s, "Can't get battery level\n");
            return ERR_NOTSUPPORTED;
        case AT_Reply_CMSError:
            smprintf(s, "Can't get battery level\n");
            return ATGEN_HandleCMSError(s);
        case AT_Reply_CMEError:
            return ATGEN_HandleCMEError(s);
        default:
            return ERR_UNKNOWNRESPONSE;
    }
}


GSM_Reply_Function ATGENReplyFunctions[] = {
...
{ATGEN_ReplyGetBatteryCharge,       "AT+CBC"                ,0x00,0x00,ID_GetBatteryCharge   },
...

As you can see, all reply function first need to handle which error code did they receive and return appropriate error if needed. Functions ATGEN_HandleCMSError() and ATGEN_HandleCMEError() simplify this, but you might need to customize it by handling some error codes manually (eg. when phone returns error on empty location).

The rest of the function is just call to ATGEN_ParseReply() and processing parsed data.