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 :file:`libgammu/phone/at/atgen.c`. At the bottom of the file, you can find two arrays, one defining driver interface (:c:type:`GSM_Phone_Functions`) and second one defining callbacks (see :ref:`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: .. code-block:: c 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 :file:`libgammu/gsmphones.c`) or vendor based decision to use some commands. Invoking AT command +++++++++++++++++++ The AT commands are invoked using :c:func:`GSM_WaitFor`, or a wrapper :c:func:`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: .. code-block:: c 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 :c:func:`ATGEN_ParseReply`, which should be able to handle all encoding and parsing magic. You can grab lines from the reply using :c:func:`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: .. code-block:: c 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 :c:func:`ATGEN_HandleCMSError` and :c:func:`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 :c:func:`ATGEN_ParseReply` and processing parsed data.