X-Git-Url: http://git.jankratochvil.net/?p=gnokii.git;a=blobdiff_plain;f=common%2Fat-hw.c;fp=common%2Fat-hw.c;h=74b9f91f8e209d482e5bacd24c7266016f1e8ccc;hp=0000000000000000000000000000000000000000;hb=49dd905279a8e62936e3713510ab0fd738e20ecb;hpb=2f2703c9133032c12671ca5c77ae626b8fb178d4 diff --git a/common/at-hw.c b/common/at-hw.c new file mode 100644 index 0000000..74b9f91 --- /dev/null +++ b/common/at-hw.c @@ -0,0 +1,2659 @@ +/* + + $Id$ + + G N O K I I + + A Linux/Unix toolset and driver for Nokia mobile phones. + + Copyright (C) 2001 Jan Kratochvil, + based on code by Hugh Blemings & Pavel Janík ml. + + Released under the terms of the GNU GPL, see file COPYING for more details. + + This file provides an API for accessing functions on AT (Hayes) command + compatible hardware modems such as Siemens M20. + See README-AT for more details on supported mobile phones and GSM modems. + + The various routines are called ATHW (whatever) as a concatenation of AT + and HardWare communication type. + + $Log$ + Revision 1.1.1.1 2002/04/03 00:08:02 short + Found in "gnokii-working" directory, some November-patches version + + +*/ + +#define ATHW_DEBUG 1 +/* #define DISABLE_CMGF0 1 */ /* Force AT+CMGF=1 on phones capable +CMGF=0 */ + +/* System header files */ + +#include +#include +#include +#include +#include + + +#ifdef WIN32 + +#include +#include "win32/winserial.h" + +#undef IN +#undef OUT + +#define WRITEPHONE(a, b, c) WriteCommBlock(b, c) +#define sleep(x) Sleep((x) * 1000) +#define usleep(x) Sleep(((x) < 1000) ? 1 : ((x) / 1000)) +extern HANDLE hPhone; + +#else + +#define WRITEPHONE(a, b, c) device_write(b, c) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "device.h" +#include "devices/unixserial.h" + +#endif + +/* Various header file */ + +#include "config.h" +#include "misc.h" +#include "gsm-common.h" + +/* Global variables used by code in gsm-api.c to expose the functions + supported by this model of phone. */ + + +#if __unices__ +/* fd opened in device.c */ +extern int device_portfd; +#endif + +/* Our private defines */ + +#define ATHW_CME_NOT_FOUND (22) /* +CME ERROR: 22 */ +#define ATHW_CMS_INVALID_MEMORY_INDEX (321) /* +CMS ERROR: 321 */ + +/* When now catchbuffer was provided and must have some space to decode + * OK/ERROR/... result codes. + */ +#define ATHW_CATCHBUFFER_LENGTH 0x400 + +/* Default message reference, filled-in by GSM modem + */ +#define ATHW_PDU_MR_DEFAULT (0x00) + +/* Maximum PDU size in bytes + */ +#define GNOKII_MAX_PDU_LENGTH (64+GSM_MAX_DESTINATION_LENGTH/2+(GSM_MAX_SMS_LENGTH*7)/8) + +/* Local variables */ + + +#ifndef WIN32 +static char PortDevice[GSM_MAX_DEVICE_NAME_LENGTH]; +#endif +static bool RequestTerminate; +static GSM_MemoryType ATHW_CurrentMemoryType=GMT_XX; +static int ATHW_CurrentCMGF=-1; +static bool ATHW_CMGS_CMGF1_8bit_HaveBinHex; /* AT+CMGS in +CMGF==1 and 8bit mode accepts hexstring */ +static bool ATHW_HaveCNMI=false; +static bool ATHW_HaveSiemensMGR=false; /* "AT^SMGR" supported? */ +static bool ATHW_HaveSiemensMGL=false; /* "AT^SMGL" supported? */ +static int ATHW_CNMI_count=-1; /* value (-1) means it is not yet known */ + +/* We don't know whether we should place SMSC in the front of +CMGS PDU + * so we will try both methods. + * When we will at least once successfuly send a message, we will *_force-it + * as the probability of wrong settings and successful message send is REALLY low. :-) + * (Maybe not so low when we will try to send prefix and no prefix is expected, + * this is also the reason why we will first try to NOT to send the prefix as we + * would have to successfuly hit some valid SMS center by the initial part of PDU.) + * Note: Applicable only if +CMGF==0 + */ +static bool ATHW_CurrentSMSCPrefix=false; +static bool ATHW_CurrentSMSCPrefix_force=false; +/* Always try 4 retries - w/o prefix, w/prefix, w/o prefix again, w/prefix again, fail + * This number should be probable even to give some 'stability' when all SMSes are failing + */ +#define ATHW_CURRENTSMSCPREFIX_RETRIES (4) + +/* Please see the comment above ATHW_SaveSMS_StatSupported_solve(). + * Number of retry sessions before giving up sending SMS during SaveSMS. + */ +#define ATHW_SAVESMS_STATSUPPORTED_RETRIES (2) + + +static char ATHW_CatchBuffer[ATHW_CATCHBUFFER_LENGTH]; +static char *ATHW_CatchBufferPtr=ATHW_CatchBuffer; /* current destination writing ptr */ +static char *ATHW_CatchBufferMarker; /* marks begin of currently catched stream */ +static GSM_Error *ATHW_CatchBufferErrorP; + +static long ATHW_RX_Patrol_CME_ERROR_code; +static long ATHW_RX_Patrol_CMS_ERROR_code; + +typedef char *(*ATHW_RX_PatrolFunc)(char *after); +typedef void (*ATHW_RX_PatrolReset)(void); + +struct ATHW_RX_Patrol { + const char *buoy; + ATHW_RX_PatrolFunc func; + ATHW_RX_PatrolReset reset; + }; + +static const struct ATHW_RX_Patrol *ATHW_RX_Patrol_Current; + +static void ATHW_CatchBufferReset(void); +static void ATHW_CatchBufferMarkStart(GSM_Error *errorcodep); + +#ifndef WIN32 + +static pthread_t Thread; +# if __unices__ +static pthread_t selThread; +# endif + +#endif + +/* Local variables used by get/set phonebook entry code. Buffer is used as a + source or destination for phonebook data and other functions... Error is + set to GE_NONE by calling function, set to GE_COMPLETE or an error code by + handler routines as appropriate. */ + +static GSM_MemoryStatus *CurrentMemoryStatus; +static GSM_Error CurrentMemoryStatusError; + +static GSM_PhonebookEntry *CurrentPhonebookEntry; +static GSM_Error CurrentPhonebookError; + +static GSM_SMSMessage *CurrentSMSMessage; +static char *CurrentSMSMessagePDU; /* hex string representation for +CMGF=0 patrol */ +static size_t CurrentSMSMessagePDU_size; /* used only if (+CMGF==0), size in bytes */ +static GSM_Error CurrentSMSMessageError; + +static GSM_SMSStatus *CurrentSMSStatus; +static GSM_Error CurrentSMSStatusError; + +static GSM_MessageCenter *CurrentMessageCenter; +static GSM_Error CurrentMessageCenterError; + +static float *CurrentRFLevel; /* AT+CSQ */ +static GSM_Error GetRFLevelError; + +static float *CurrentBatteryLevel; /* AT+CBC */ +static GSM_Error GetBatteryLevelError; + +static GSM_PowerSource *CurrentPowersource; /* AT+CBC */ +static GSM_Error GetPowersourceError; + +static GSM_Error DialVoiceError; + +static GSM_Error CancelCallError; + +static GSM_Error CurrentPhoneInfoError; + +static unsigned char Manufacturer[GSM_MAX_MANUFACTURER_LENGTH]; +static unsigned char Model[GSM_MAX_MODEL_LENGTH]; +static unsigned char Revision[GSM_MAX_REVISION_LENGTH]; +static unsigned char IMEI[GSM_MAX_IMEI_LENGTH]; + +static char CurrentIncomingCall[20] = " "; + +static GSM_NetworkInfo *CurrentNetworkInfo; +static GSM_Error CurrentNetworkInfoError; + + +/* Pointer to a callback function used to return changes to a calls status */ +/* This saves unreliable polling */ +static void (*CallPassup)(char c); + + +/* "catchbufer" can be provided as NULL: + * - sizeof() will be bogus but it will be ingored anyway + */ + +static void ATHW_TX_SendCommand(GSM_Error *errorcodep,const struct ATHW_RX_Patrol *patrol,const char *fmt,...) G_GNUC_PRINTF(3,4); +static void ATHW_TX_SendCommand(GSM_Error *errorcodep,const struct ATHW_RX_Patrol *patrol,const char *fmt,...) +{ +char *command; +size_t command_len; +va_list ap; +int writephone_got; + + if (errorcodep) + *errorcodep=GE_BUSY; + ATHW_RX_Patrol_Current=patrol; + + va_start(ap,fmt); + command_len=gvasprintf(&command,fmt,ap); + va_end(ap); + if (-1==command_len) { + if (errorcodep) + *errorcodep=GE_INTERNALERROR; + return; + } + + ATHW_CatchBufferMarkStart(errorcodep); + + writephone_got=WRITEPHONE(PortFD, command, command_len); + +#ifdef ATHW_DEBUG + write(1,"CMD:",4); + write(1,command,command_len); + write(1,"\n",1); +#endif + + free(command); + if (command_len!=writephone_got) { + if (errorcodep) + *errorcodep=GE_INTERNALERROR; + return; + } + + /* success but we don't wait for the result code */ +} + +/* This function is used to get storage status from the phone. It currently + supports two different memory areas - internal and SIM. */ + +static GSM_Error +wait_on(volatile GSM_Error *what, int timeout) +{ +GSM_Error r=GE_TIMEOUT; /* shut up GCC when (timeout==0) */ + + while (timeout && ((r=*what)==GE_BUSY)) { + if (!--timeout) { + r=GE_TIMEOUT; + break; + } + usleep(100000); + } + /* any specific patrollers are no longer valid */ + ATHW_RX_Patrol_Current=NULL; + +#ifdef ATHW_DEBUG + printf("wait_on finish, timeout=%d\n",(r==GE_TIMEOUT)); +#endif + + return(r); +} + +#define WAIT_ON(what, timeout) \ + do { \ + GSM_Error res = wait_on(what, timeout); \ + if (res != GE_NONE) \ + return res; \ + } while (0) + +/* I hope GCC gets this bunch optimized on the normal the normal errorcodep!=NULL case + */ +#define ATHW_TX_SENDCOMMAND_WAIT_ON(errorcodep,timeout,patrol,args...) \ + do { \ +GSM_Error _ATHW_TX_SENDCOMMAND_WAIT_ON_err,*_ATHW_TX_SENDCOMMAND_WAIT_ON_errp; \ + \ + if (!(_ATHW_TX_SENDCOMMAND_WAIT_ON_errp=(errorcodep))) \ + _ATHW_TX_SENDCOMMAND_WAIT_ON_errp=&_ATHW_TX_SENDCOMMAND_WAIT_ON_err; \ + ATHW_TX_SendCommand(_ATHW_TX_SENDCOMMAND_WAIT_ON_errp,(patrol),args); \ + WAIT_ON(_ATHW_TX_SENDCOMMAND_WAIT_ON_errp,(timeout)); \ + } while (0) + + +#define ATHW_ERR_WRAPPER(expr) \ + do { \ + GSM_Error err=(expr); \ + if (err!=GE_NONE) \ + return(err); \ + } while (0) + +/* Currently we use a heuristic for extraction as at least Nokia 9000i doesn't + * escape quote characters even if they are inside enquoted strings! + * When we find -- ", -- we assume it is the separator although it may be false! + * Hmm, Siemens M20 has the same broken behaviour. + */ +static void ATHW_ExtractString(char *dest,size_t destlen,char *src,int element_no) +{ +char *srcend,*start,*end; + + *dest='\0'; + if (!(srcend=strchr(src,'\n'))) + return; /* INTERNAL error! */ + do { + while (*src==' ') src++; + if (*src!='"') { + start=src; + while (srcd_buf && d[-1]==' ') + d--; + *d='\0'; +} + +#define ATHW_BUFFERTRIMCOPY_OFFSET(d,s,offset) (ATHW_BufferTrimCopy((d)+(offset),sizeof((d))-(offset),(s))) +#define ATHW_BUFFERTRIMCOPY(d,s) (ATHW_BUFFERTRIMCOPY_OFFSET((d),(s),0)) + +static GSM_Error ATHW_GetPhoneInfo(void) +{ +/* +CGMI=Request Manufacturer Identification + * +CGMM=Request Model Identification + * +CGMR=Request Revision Identification + * +CGSN=Request Product Serial Number Identification (IMEI) + */ + + ATHW_TX_SENDCOMMAND_WAIT_ON(&CurrentPhoneInfoError/*errorcodep*/,20/*timeout*/,NULL/*patrol*/,"/AT+CGMI\r"); + ATHW_BUFFERTRIMCOPY(Manufacturer,ATHW_CatchBufferMarker); + + ATHW_TX_SENDCOMMAND_WAIT_ON(&CurrentPhoneInfoError/*errorcodep*/,20/*timeout*/,NULL/*patrol*/,"/AT+CGMM\r"); + ATHW_BUFFERTRIMCOPY_OFFSET(Model,ATHW_CatchBufferMarker,3); + memcpy(Model,"AT-",3); + + ATHW_TX_SENDCOMMAND_WAIT_ON(&CurrentPhoneInfoError/*errorcodep*/,20/*timeout*/,NULL/*patrol*/,"/AT+CGMR\r"); + ATHW_BUFFERTRIMCOPY(Revision,ATHW_CatchBufferMarker); + + ATHW_TX_SENDCOMMAND_WAIT_ON(&CurrentPhoneInfoError/*errorcodep*/,20/*timeout*/,NULL/*patrol*/,"/AT+CGSN\r"); + ATHW_BUFFERTRIMCOPY(IMEI,ATHW_CatchBufferMarker); + + return(GE_NONE); +} + +static unsigned char ATHW_SMStoFO(GSM_SMSMessage *SMS) +{ + return(0 + |((SMS->Type==GST_MO)<<0) + |(2<<3) /*=integer*/ + |0 /* bit 5 is Status Report Request, not supported */ + |((!!SMS->UDHPresent)<<6) /*UDH set*/ + |((!!SMS->ReplyViaSameSMSC)<<7) /*ReplyPath*/ + ); +} + +static unsigned char ATHW_SMStoDCS(GSM_SMSMessage *SMS) +{ +int class=(SMS->Class==-1 ? 1 : SMS->Class); /* we default to class 1 (Mobile Equipment target) */ + + if (!SMS->EightBit && class==1) + return(0x00); + return(0xF0 | ((!!SMS->EightBit)<<2) | ((class&0x03)<<0)); +} + +/* Nokia 9000i: We need to temporarily turn on ECHO otherwise we wouldn't got the "\n> " prompt + * (At least Siemens M20 doesn't have this broken behaviour.) + */ +static GSM_Error ATHW_SMS_CMGF01_pre(void) +{ + if (ATHW_CurrentCMGF==1) { + /* We just cannot send UDH header in pure CMGF 1 mode */ + if (CurrentSMSMessage->UDHPresent && (!CurrentSMSMessage->EightBit || !ATHW_CMGS_CMGF1_8bit_HaveBinHex)) + return(GE_INTERNALERROR); + + /* AT+CSMP=,,, */ + ATHW_TX_SENDCOMMAND_WAIT_ON(&CurrentSMSMessageError/*errorcodep*/,10/*timeout*/,NULL/*patrol*/, + "AT+CSMP=%d,%d,%d,%d\r", + /* : */ ATHW_SMStoFO(CurrentSMSMessage), + /* : */ SMS_Validity_to_VP(CurrentSMSMessage->Validity), + + /* FIXME: Should we query current "MessageCenter.No" to get "Format" field? */ + /* : */ (unsigned char)CurrentSMSMessage->MessageCenter.Format, + + /* : */ ATHW_SMStoDCS(CurrentSMSMessage) + ); + } + + ATHW_TX_SENDCOMMAND_WAIT_ON(&CurrentSMSMessageError/*errorcodep*/,10/*timeout*/,NULL/*patrol*/, + "ATE1\r"); + + return(GE_NONE); +} + +static GSM_Error ATHW_SMS_CMGF01_post(void) +{ + ATHW_TX_SENDCOMMAND_WAIT_ON(&CurrentSMSMessageError/*errorcodep*/,10/*timeout*/,NULL/*patrol*/, + "ATE0\r"); + + return(GE_NONE); +} + +/* For now we just kick out all quotes in the source "string" as no escaping + * method was found on Nokia 9000i - what the other ones? + */ +static const char *ATHW_Enquote(const char *string) +{ + +#define ATHW_ENQUOTE_SLOTS (4) + +struct ATHW_Enquote_slot { + char *buf; + size_t len; + }; + +static struct ATHW_Enquote_slot slots[ATHW_ENQUOTE_SLOTS]; +static struct ATHW_Enquote_slot *slot=slots; +size_t stringl=strlen(string); +char *d; +const char *r,*s; + + if (slot->len<1+stringl+1+1) { /* "string"\0 */ +char *newbuf; +size_t newlen=(!slot->len ? 0x100 : slot->len*2); + + if (!(newbuf=realloc(slot->buf,newlen))) + return(NULL); + slot->buf=newbuf; + slot->len=newlen; + } + + d=slot->buf; + *d++='"'; + for (s=string;*s;s++) { + if (*s=='"') + continue; + *d++=*s; + } + *d++='"'; + *d++='\0'; + + r=slot->buf; + if (++slot>=slots+ARRAY_LEN(slots)) + slot=slots; + return(r); +} + +static char *ATHW_PhoneSetup_CMGF1_Detect_Patrol_9(char *after) +{ + /* We got at least echo of the initial "9" digit. + * Nokia 9000i/9110i/CardPhone will echo even "Q" followed by ERROR, + * but Nokia 9210 will give ERROR immediately (w/o echo of "Q"). + * When the feature is not supported we will get GE_TIMEOUT and + * ATHW_CMGS_CMGF1_8bit_HaveBinHex will be reset to "false" by the error check + * in ATHW_PhoneSetup_CMGF1_Detect_core(). + */ + ATHW_CMGS_CMGF1_8bit_HaveBinHex=true; + + WRITEPHONE(PortFD,"Q",1); + + /* We don't need to patrol for "Q", its detection would be cross-phone + * incompatible. + */ + + return(after); /* eat line - it will be just one character... */ +} + +static const struct ATHW_RX_Patrol ATHW_PhoneSetup_CMGF1_Detect_Patrol_9_struct= + { "9",ATHW_PhoneSetup_CMGF1_Detect_Patrol_9 }; + +static char *ATHW_PhoneSetup_CMGF1_Detect_Patrol_GT_SPACE(char *after) +{ + WRITEPHONE(PortFD,"9",1); + ATHW_RX_Patrol_Current=&ATHW_PhoneSetup_CMGF1_Detect_Patrol_9_struct; + return(after); /* eat line - it will be just one character... */ +} + +static const struct ATHW_RX_Patrol ATHW_PhoneSetup_CMGF1_Detect_Patrol_GT_SPACE_struct= + { "\n> ",ATHW_PhoneSetup_CMGF1_Detect_Patrol_GT_SPACE }; + +/* ATHW_CMGS_CMGF1_8bit_HaveBinHex feature is AFAIK present on Nokia 9000i/9110 + * and also on Nokia CardPhone (but CardPhone supports +CMGF==0 so it is useless there). + * On Nokia 9000i we will detect the feature as present although it cannot be used + * for logo/ring sending as it will later fail to set UDH bit in FO by AT+CSMP. + */ +static GSM_Error ATHW_PhoneSetup_CMGF1_Detect_core(void) +{ +GSM_SMSMessage sms; + + ATHW_CMGS_CMGF1_8bit_HaveBinHex=false; /* default */ + + /* We need to reset the catchbuffer as we would otherwise + * catch some completely bogus previous output. + * We are called only during phone setup so here is no risk in loosing + * some unsolicited result codes. + */ + ATHW_CatchBufferReset(); + + CurrentSMSMessage=&sms; + /* for ATHW_SMStoFO(): */ + CurrentSMSMessage->UDHPresent=false; + CurrentSMSMessage->Type=GST_MO; + CurrentSMSMessage->UDHPresent=false; /* warning: it would fail on Nokia 9000i or Siemens M20! */ + CurrentSMSMessage->ReplyViaSameSMSC=false; + /* for SMS_Validity_to_VP(): */ + CurrentSMSMessage->Validity=GSMV_72_Hours; /* default */ + /* for ATHW_SMS_CMGF01_pre(): */ + CurrentSMSMessage->MessageCenter.Format=GSMF_Text; + /* for ATHW_SMStoDCS(): */ + CurrentSMSMessage->EightBit=true; /* IMPORTANT!: The ONLY meaning of the whole procedure! */ + CurrentSMSMessage->Class=1; + /* Do proper AT+CSMP= and also ATE1 */ + ATHW_SMS_CMGF01_pre(); + + ATHW_TX_SendCommand(&CurrentSMSMessageError/*errorcodep*/,&ATHW_PhoneSetup_CMGF1_Detect_Patrol_GT_SPACE_struct/*patrol*/, + "AT+CMGS=%s\r",ATHW_Enquote("123456"/*just some test number*/)); + + /* Timeout 5 is too small for Nokia 9000i, give it a rest... + */ + if (GE_TIMEOUT==wait_on(&CurrentSMSMessageError,20/*timeout*/)) + ATHW_CMGS_CMGF1_8bit_HaveBinHex=false; + + return(GE_NONE); +} + +static GSM_Error ATHW_PhoneSetup_CMGS_Reset(void) +{ +GSM_Error err; +int retry; + + /* We try to escape AT+CMGS mode, at least Siemens M20 then needs to get some rest + */ + WRITEPHONE(PortFD,"\x1B\r",2); + usleep(500000); + + ATHW_CatchBufferReset(); /* output can be very bogus due to escaping */ + + for (retry=0;retry<3;retry++) { + ATHW_TX_SendCommand(&err/*errorcodep*/,NULL/*patrol*/, + "AT\r"); + if (wait_on(&err,10/*timeout*/)==GE_NONE) + return(err); /* success */ + } + /* Hmm, we've probably stucked the phone :-( + */ + return(err); /* failure */ +} + +static void ATHW_PhoneSetup_CMGF1_Detect(void) +{ + ATHW_PhoneSetup_CMGF1_Detect_core(); /* errors of the detection are ignored */ + ATHW_PhoneSetup_CMGS_Reset(); + ATHW_SMS_CMGF01_post(); /* turn off echo */ +#ifdef ATHW_DEBUG + printf("ATHW_PhoneSetup_CMGF1_Detect: ATHW_CMGS_CMGF1_8bit_HaveBinHex=%d\n",(int)ATHW_CMGS_CMGF1_8bit_HaveBinHex); +#endif +} + +static GSM_Error ATHW_PhoneSetup_CMGFSet(int CMGF) +{ + ATHW_CurrentCMGF=-1; + ATHW_TX_SENDCOMMAND_WAIT_ON(NULL/*errorcodep*/,10/*timeout*/,NULL/*patrol*/, + "AT+CMGF=%d\r",CMGF); + ATHW_CurrentCMGF=CMGF; + + if (CMGF==1) + ATHW_PhoneSetup_CMGF1_Detect(); + + return(GE_NONE); +} + +static GSM_Error ATHW_PhoneSetup(void) +{ +GSM_Error err; + + ATHW_CurrentMemoryType=GMT_XX; /* invalidate */ + + ATHW_PhoneSetup_CMGS_Reset(); + + ATHW_TX_SENDCOMMAND_WAIT_ON(NULL/*errorcodep*/,60/*timeout*/,NULL/*patrol*/, + "AT&F\r"); + + ATHW_TX_SENDCOMMAND_WAIT_ON(NULL/*errorcodep*/,10/*timeout*/,NULL/*patrol*/, + "ATE0\r"); + ATHW_TX_SENDCOMMAND_WAIT_ON(NULL/*errorcodep*/,10/*timeout*/,NULL/*patrol*/, + "AT+CSDH=1\r"); + ATHW_TX_SENDCOMMAND_WAIT_ON(NULL/*errorcodep*/,10/*timeout*/,NULL/*patrol*/, + "AT+CMEE=1\r"); + ATHW_TX_SENDCOMMAND_WAIT_ON(NULL/*errorcodep*/,10/*timeout*/,NULL/*patrol*/, + "AT+CRC=1\r"); /* request "+CRING: VOICE" reporting */ + + /* Try to detect Siemens M20 with M1 backward compatibility mode */ + ATHW_TX_SendCommand(&err/*errorcodep*/,NULL/*patrol*/, + "AT+CSMS=128\r"); + if (GE_NONE==wait_on(&err,10/*timeout*/)) { + ATHW_CurrentSMSCPrefix=true; /* it will be true ASAP +CMGS=0 is done */ + ATHW_CurrentSMSCPrefix_force=true; + } + ATHW_TX_SENDCOMMAND_WAIT_ON(NULL/*errorcodep*/,10/*timeout*/,NULL/*patrol*/, + "AT+CSMS=0\r"); /* turn on ATHW_CurrentSMSCPrefix, if possible */ + +#ifndef DISABLE_CMGF0 + if (GE_NONE!=ATHW_PhoneSetup_CMGFSet(0)) +#endif + ATHW_PhoneSetup_CMGFSet(1); + + ATHW_TX_SendCommand(&err/*errorcodep*/,NULL/*patrol*/, + "AT^SMGR=?\r"); /* it should just return "\nOK\n" */ + ATHW_HaveSiemensMGR=(GE_NONE==wait_on(&err,10/*timeout*/)); + ATHW_TX_SendCommand(&err/*errorcodep*/,NULL/*patrol*/, + "AT^SMGL=?\r"); /* it should just return "\nOK\n" */ + ATHW_HaveSiemensMGL=(GE_NONE==wait_on(&err,10/*timeout*/)); + + ATHW_HaveCNMI=true; + ATHW_TX_SendCommand(&err/*errorcodep*/,NULL/*patrol*/, + "AT+CNMI=2,1\r"); /* =2 (buffer when data active), =1 (+CMTI codes) */ + if (GE_NONE!=wait_on(&err,10/*timeout*/)) { + ATHW_TX_SendCommand(&err/*errorcodep*/,NULL/*patrol*/, + "AT+CNMI=1,1\r"); /* =1 (discard when data active), =1 (+CMTI codes) */ + if (GE_NONE!=wait_on(&err,10/*timeout*/)) + ATHW_HaveCNMI=false; + } + ATHW_CNMI_count=-1; /* unknown yet */ + + ATHW_TX_SendCommand(&err/*errorcodep*/,NULL/*patrol*/, + "AT+COPS=3,2\r"); /* =3 (just set ), =2 (numeric) */ + wait_on(&err,10/*timeout*/); /* error ignored, GetNetworkInfo will find the failure itself */ + + /* Enable LAC+CellID returns, it will also disable unsolicited changes reporting + * but we ignore it successfuly. + */ + ATHW_TX_SendCommand(&err/*errorcodep*/,NULL/*patrol*/, + "AT+CREG=2\r"); + wait_on(&err,10/*timeout*/); /* error ignored, GetNetworkInfo will find the failure itself */ + + ATHW_GetPhoneInfo(); + + return(GE_NONE); +} + + +static void ATHW_RX_Char(char rx_byte); +static void ATHW_SigHandler(int status); +static bool ATHW_OpenSerial(void); + +GSM_Phone phone_at_hw; /* forward declaration */ + +/* Initialise variables and state machine. */ + +static GSM_Error ATHW_Init(GSM_Data *data, GSM_Statemachine *state) +{ + RequestTerminate = false; + CallPassup = NULL; + + /* Create and start main thread. */ + +#ifdef WIN32 +{ +int rtn; + rtn = ! OpenConnection(State->Link.PortDevice,ATHW_RX_Char,ATHW_KeepAliveProc); + if (rtn != 0) { + return(GE_INTERNALERROR); +} +#else + SAFE_STRNCPY_SIZEOF(PortDevice,state->Link.PortDevice); + if (!ATHW_OpenSerial()) + return(GE_INTERNALERROR); +#endif + + ATHW_ERR_WRAPPER(ATHW_PhoneSetup()); + + return (GE_NONE); +} + +#if __unices__ +/* thread for handling incoming data */ +void ATHW_SelectLoop() +{ + int err; + fd_set readfds; + struct timeval timeout; + + FD_ZERO(&readfds); + FD_SET(device_portfd, &readfds); + /* set timeout to 15 seconds */ + timeout.tv_sec=15; + timeout.tv_usec=0; + while (!RequestTerminate) { + err = select(device_portfd + 1, &readfds, NULL, NULL, &timeout); + /* call singal handler to process incoming data */ + if ( err > 0 ) ATHW_SigHandler(0); + else if (err == -1) perror("Error in SelectLoop"); + } +} +#endif + +static GSM_Error ATHW_AnswerCall(GSM_Data *data, GSM_Statemachine *state) +{ + /* data->CallNo not present up to Nokia 9110i */ + ATHW_TX_SENDCOMMAND_WAIT_ON(&CurrentPhoneInfoError/*errorcodep*/,20/*timeout*/,NULL/*patrol*/,"ATA\r"); + return(GE_NONE); +} + +/* Applications should call ATHW_Terminate to shut down the ATHW thread and + close the serial port. */ + +static GSM_Error ATHW_Terminate(GSM_Data *data, GSM_Statemachine *state) +{ + /* Request termination of thread */ + RequestTerminate = true; + +#ifndef WIN32 + /* Now wait for thread to terminate. */ + pthread_join(Thread, NULL); + + /* Close serial port. */ + device_close(); + +#else + CloseConnection(); +#endif + + return(GE_NONE); +} + +/* +CSQ: Signal Quality + * CurrentRFLevel = 1st arg + */ +static char *ATHW_RX_Patrol_CSQ(char *after) +{ +long l; + + l=ATHW_ExtractNumber(after/*src*/,0/*element_no*/); + if (l>=0 && l<=31) + *CurrentRFLevel=l; + + return(after); /* eat line */ +} + +static const struct ATHW_RX_Patrol ATHW_RX_Patrol_CSQ_struct= + { "\n+CSQ:", ATHW_RX_Patrol_CSQ }; + +static GSM_Error ATHW_GetRFLevel(GSM_Data *data, GSM_Statemachine *state) +{ + CurrentRFLevel=data->RFLevel; + + ATHW_TX_SENDCOMMAND_WAIT_ON(&GetRFLevelError/*errorcodep*/,30/*timeout*/,&ATHW_RX_Patrol_CSQ_struct,"AT+CSQ\r"); + + if (*data->RFUnits!=GRF_CSQ) { + *data->RFUnits = GRF_Percentage; /* required by xgnokii_lowlevel.c */ + *data->RFLevel = (*data->RFLevel)*100/31; /* +CSQ */ + } + + return (GE_NONE); +} + +/* +CBC: Battery Charge + * CurrentPowersource = 1st arg + * CurrentBatteryLevel = 2nd arg + */ +static char *ATHW_RX_Patrol_CBC(char *after) +{ +long l; + + if (CurrentPowersource) { + l=ATHW_ExtractNumber(after/*src*/,0/*element_no*/); + if (l==0 || l==1) + *CurrentPowersource=(l==1 ? GPS_ACDC : GPS_BATTERY); + } + + if (CurrentBatteryLevel) { + l=ATHW_ExtractNumber(after/*src*/,1/*element_no*/); + if (l>=0 && l<=100) + *CurrentBatteryLevel=l; + } + + return(after); /* eat line */ +} + +static const struct ATHW_RX_Patrol ATHW_RX_Patrol_CBC_struct= + { "\n+CBC:", ATHW_RX_Patrol_CBC }; + +static GSM_Error ATHW_GetBatteryLevel(GSM_Data *data, GSM_Statemachine *state) +{ + CurrentBatteryLevel=data->BatteryLevel; + *data->BatteryUnits = GBU_Percentage; + + ATHW_TX_SendCommand(&GetBatteryLevelError/*errorcodep*/,&ATHW_RX_Patrol_CBC_struct,"AT+CBC\r"); + if (GE_NONE!=wait_on(&GetBatteryLevelError,30/*timeout*/)) { + /* We are probably on AC powered device such as Nokia CardPhone + */ + *CurrentBatteryLevel=100; + } + + + return (GE_NONE); +} + +static GSM_Error ATHW_GetPowersource(GSM_Data *data, GSM_Statemachine *state) +{ + CurrentPowersource=data->PowerSource; + + ATHW_TX_SendCommand(&GetPowersourceError/*errorcodep*/,&ATHW_RX_Patrol_CBC_struct,"AT+CBC\r"); + if (GE_NONE!=wait_on(&GetPowersourceError,30/*timeout*/)) { + /* We are probably on AC powered device such as Nokia CardPhone + */ + *CurrentPowersource=GPS_ACDC; + } + + return (GE_NONE); +} + +static GSM_Error ATHW_DialVoice(GSM_Data *data, GSM_Statemachine *state) +{ + /* "ATDnumber;" is notation supported at least on Nokia up to 9110i + * We need to wait for establishing a connection as other commands would otherwise break it! + */ + ATHW_TX_SENDCOMMAND_WAIT_ON(&DialVoiceError/*errorcodep*/,600/*timeout*/,NULL/*patrol*/,"ATD%s;\r",data->VoiceNumber); + + return(GE_NONE); +} + +/* Dial a data call - type specifies request to use: + type 0 should normally be used + type 1 should be used when calling a digital line - corresponds to ats35=0 + Maybe one day we'll know what they mean! + FIXME: ATHW currently ignores it - what value should be used for S35? +*/ + +static GSM_Error ATHW_DialData(GSM_Data *data, GSM_Statemachine *state) +{ + CallPassup=data->DataCallPassUp; + + switch (*data->DataType) { + case 0: /* FALLTHRU */ + case 1: /* FALLTHRU */ + default: + break; + break; + case -1: /* Just used to set the call passup */ + return GE_NONE; + break; + } + + ATHW_TX_SendCommand(NULL/*errorcodep*/,NULL/*patrol*/,"ATD%s\r",data->DataNumber); + return(GE_NONE); +} + +static GSM_Error ATHW_GetIncomingCallNr(GSM_Data *data, GSM_Statemachine *state) +{ + if (*CurrentIncomingCall != ' ') { + strcpy(data->IncomingCallNr, CurrentIncomingCall); + return GE_NONE; + } else return GE_BUSY; +} + +static GSM_Error ATHW_CancelCall(GSM_Data *data, GSM_Statemachine *state) +{ + ATHW_TX_SENDCOMMAND_WAIT_ON(&CancelCallError/*errorcodep*/,60/*timeout*/,NULL/*patrol*/,"ATH\r"); + + return(GE_NONE); +} + +/* messagecenter->No" is NOT set as it is input argument */ +static void ATHW_MessageCenterClear(GSM_MessageCenter *messagecenter) +{ + messagecenter->Name[0]='\0'; /* not present up to Nokia 9110i */ + messagecenter->Recipient[0]='\0'; /* not present up to Nokia 9110i */ + messagecenter->Number[0]='\0'; /* default */ + messagecenter->Format=GSMF_Text; /* default */ + messagecenter->Validity=GSMV_72_Hours; /* default */ +} + +/* +CSCA: + * MessageCenter->Number = 1st arg + */ +static char *ATHW_RX_Patrol_CSCA(char *after) +{ + ATHW_EXTRACTSTRING(CurrentMessageCenter->Number,after/*src*/,0/*element_no*/); + + return(after); /* eat line */ +} + +static const struct ATHW_RX_Patrol ATHW_RX_Patrol_CSCA_struct= + { "\n+CSCA:", ATHW_RX_Patrol_CSCA }; + +/* +CSMP: + * CurrentMessageCenter->Format = 3rd arg + * CurrentMessageCenter->Validity = 2nd arg + */ +static char *ATHW_RX_Patrol_CSMP(char *after) +{ +long l; + + l=ATHW_ExtractNumber(after/*src*/,2/*element_no*/); + if (l>=0 && l<0x100) + CurrentMessageCenter->Format=(GSM_SMSMessageFormat)l; + + l=ATHW_ExtractNumber(after/*src*/,1/*element_no*/); + if (l>=0 && l<0x100) + CurrentMessageCenter->Validity=(GSM_SMSMessageValidity)l; + + return(after); /* eat line */ +} + +static const struct ATHW_RX_Patrol ATHW_RX_Patrol_CSMP_struct= + { "\n+CSMP:", ATHW_RX_Patrol_CSMP }; + +/* This function sends to the mobile phone a request for the SMS Center */ + +static GSM_Error ATHW_GetSMSCenter(GSM_Data *data, GSM_Statemachine *state) +{ + CurrentMessageCenter = data->MessageCenter; + + if (CurrentMessageCenter->No!=1) /* "CurrentMessageCenter->No" not present up to Nokia 9110i */ + return(GE_INTERNALERROR); /* FIXME: some better code? */ + ATHW_MessageCenterClear(CurrentMessageCenter); + + ATHW_TX_SENDCOMMAND_WAIT_ON(&CurrentMessageCenterError/*errorcodep*/,10/*timeout*/,&ATHW_RX_Patrol_CSCA_struct,"AT+CSCA?\r"); + ATHW_TX_SENDCOMMAND_WAIT_ON(&CurrentMessageCenterError/*errorcodep*/,10/*timeout*/,&ATHW_RX_Patrol_CSMP_struct,"AT+CSMP?\r"); + + printf("message center OK: %s\n",CurrentMessageCenter->Number); + return(GE_NONE); +} + +/* This function set the SMS Center profile on the phone. */ + +static GSM_Error ATHW_SetSMSCenter(GSM_Data *data, GSM_Statemachine *state) +{ + CurrentMessageCenter = data->MessageCenter; + ATHW_TX_SENDCOMMAND_WAIT_ON(&CurrentMessageCenterError/*errorcodep*/,10/*timeout*/,NULL/*patrol*/, + "AT+CSCA=%s\r",ATHW_Enquote(CurrentMessageCenter->Number)); + return(GE_NONE); +} + +static int ATHW_RX_Patrol_SiemensMGL_count; + +static char *ATHW_RX_Patrol_SiemensMGL(char *after) +{ +char *s; + + /* There may be left string from AT+CMGL=? which would confuse us! + */ + for (s=after;isspace(*s);s++); + if (*s=='(') + return(after); /* eat line */ + + /* Lines with our wanted "REC UNREAD" should be already filtered by the request + */ + ATHW_RX_Patrol_SiemensMGL_count++; + + return(after); /* eat line */ +} + +static const struct ATHW_RX_Patrol ATHW_RX_Patrol_SiemensMGL_struct= + { "\n^SMGL:", ATHW_RX_Patrol_SiemensMGL }; + +static void ATHW_Update_CNMI_count(void) +{ +const char *state; + + if (!ATHW_HaveSiemensMGL) + return; + + switch (ATHW_CurrentCMGF) { + case 0: state="0"; break; + case 1: state=ATHW_Enquote("REC UNREAD"); break; + default: + return; /* not supported */ + } + + ATHW_RX_Patrol_SiemensMGL_count=0; + ATHW_TX_SendCommand(&CurrentSMSStatusError/*errorcodep*/,&ATHW_RX_Patrol_SiemensMGL_struct/*patrol*/, + "AT^SMGL=%s\r",state); + if (GE_NONE==wait_on(&CurrentSMSStatusError,600/*timeout*/)) + ATHW_CNMI_count=ATHW_RX_Patrol_SiemensMGL_count; /* otherwise not updated */ +} + +/* +CPMS: + * CurrentSMSStatus->Used = 2nd arg + * CurrentSMSStatus->Slots = 3nd arg + */ +static char *ATHW_RX_Patrol_CPMS(char *after) +{ +long l; + + l=ATHW_ExtractNumber(after/*src*/,1/*element_no*/); + if (l>=0 && lUsed=l; + + l=ATHW_ExtractNumber(after/*src*/,2/*element_no*/); + if (l>=0 && lSlots=l; + + return(after); /* eat line */ +} + +static const struct ATHW_RX_Patrol ATHW_RX_Patrol_CPMS_struct= + { "\n+CPMS:", ATHW_RX_Patrol_CPMS }; + +/* Note: "Status->UnRead" cannot be detected at all up to Nokia 9110i + * as its GEOS always reads the incoming message immediately. + * On some other devices it could be probably solved by: AT+CMGL="REC UNREAD" + */ +static GSM_Error ATHW_GetSMSStatus(GSM_Data *data, GSM_Statemachine *state) +{ + CurrentSMSStatus = data->SMSStatus; + if (ATHW_CNMI_count<0) + ATHW_Update_CNMI_count(); + CurrentSMSStatus->UnRead=(ATHW_CNMI_count<0 ? 0 : ATHW_CNMI_count); /* not present up to Nokia 9110i */ + CurrentSMSStatus->Used =10; /* default */ + CurrentSMSStatus->Slots=10; /* default */ + ATHW_TX_SENDCOMMAND_WAIT_ON(&CurrentSMSStatusError/*errorcodep*/,10/*timeout*/,&ATHW_RX_Patrol_CPMS_struct, + "AT+CPMS?\r"); + return(GE_NONE); +} + +static GSM_Error ATHW_GetImei(GSM_Data *data, GSM_Statemachine *state) +{ + if (*IMEI) { + strcpy(data->Imei,IMEI); + return (GE_NONE); + } else return (GE_TRYAGAIN); +} + +static GSM_Error ATHW_GetRevision(GSM_Data *data, GSM_Statemachine *state) +{ + if (*Revision) { + strcpy(data->Revision,Revision); + return (GE_NONE); + } else return (GE_TRYAGAIN); +} + +static GSM_Error ATHW_GetModel(GSM_Data *data, GSM_Statemachine *state) +{ + if (*Model) { + strcpy(data->Model,Model); + return (GE_NONE); + } else return (GE_TRYAGAIN); +} + +static GSM_Error ATHW_GetManufacturer(GSM_Data *data, GSM_Statemachine *state) +{ + if (*Model) { + strcpy(data->Manufacturer,Manufacturer); + return (GE_NONE); + } else return (GE_TRYAGAIN); +} + +/* This function translates GMT_MemoryType to the string for AT+CPBS + * Nokia 9000i: ("SM") + * Siemens M20: ("SM","FD","LD","RC","ON","ME","MC","MT") + */ + +#define GETMEMORYTYPE_ENTRY(code) \ + case GMT_##code: return( #code ); + +static const char *ATHW_GetMemoryType(GSM_MemoryType memory_type) +{ + switch (memory_type) { + GETMEMORYTYPE_ENTRY(ME) + GETMEMORYTYPE_ENTRY(SM) + GETMEMORYTYPE_ENTRY(FD) + GETMEMORYTYPE_ENTRY(ON) + GETMEMORYTYPE_ENTRY(EN) + GETMEMORYTYPE_ENTRY(DC) + GETMEMORYTYPE_ENTRY(RC) + GETMEMORYTYPE_ENTRY(MC) + GETMEMORYTYPE_ENTRY(LD) + GETMEMORYTYPE_ENTRY(MT) + GETMEMORYTYPE_ENTRY(TA) + GETMEMORYTYPE_ENTRY(CB) + default: return(NULL); + } +} + +#define ATHW_GETMEMORYTYPE(dst,memory_type) \ + do { \ + (dst)=ATHW_GetMemoryType((memory_type)); \ + if (!(dst)) \ + return(GE_INVALIDMEMORYTYPE); \ + } while (0) + +static GSM_Error ATHW_SelectPhonebookMemory(GSM_MemoryType memory_type) +{ +const char *atmemtype; + + if (memory_type==ATHW_CurrentMemoryType) + return(GE_NONE); + ATHW_CurrentMemoryType=GMT_XX; + + ATHW_GETMEMORYTYPE(atmemtype,memory_type); + ATHW_TX_SENDCOMMAND_WAIT_ON(&CurrentPhonebookError/*errorcodep*/,10/*timeout*/,NULL/*patrol*/, + "AT+CPBS=%s\r",ATHW_Enquote(atmemtype)); + + ATHW_CurrentMemoryType=memory_type; + + return(GE_NONE); +} + +/* +CPBR: (GetMemoryStatus) + * CurrentMemoryStatus->Free=1st arg (1-) + */ +static char *ATHW_RX_Patrol_CPBR_GetMemoryStatus(char *after) +{ +char buf[32]; +int num_1,num_2; + + ATHW_EXTRACTSTRING(buf,after/*src*/,0/*element_no*/); + if (2==sscanf(buf,"(%d-%d)",&num_1,&num_2)) { + CurrentMemoryStatus->Used=0; /* not present up to Nokia 9110i, we ignore it now */ + CurrentMemoryStatus->Free=num_2; + } + + return(after); /* eat line */ +} + +static const struct ATHW_RX_Patrol ATHW_RX_Patrol_CPBR_GetMemoryStatus_struct= + { "\n+CPBR:", ATHW_RX_Patrol_CPBR_GetMemoryStatus }; + +static GSM_Error ATHW_GetMemoryStatus(GSM_Data *data, GSM_Statemachine *state) +{ + CurrentMemoryStatus=data->MemoryStatus; + + ATHW_ERR_WRAPPER(ATHW_SelectPhonebookMemory(CurrentMemoryStatus->MemoryType)); + + CurrentMemoryStatus->Used=0; /* default */ + CurrentMemoryStatus->Free=0; /* default */ + + ATHW_TX_SENDCOMMAND_WAIT_ON(&CurrentMemoryStatusError/*errorcodep*/,10/*timeout*/,&ATHW_RX_Patrol_CPBR_GetMemoryStatus_struct, + "AT+CPBR=?\r"); + + return(GE_NONE); +} + +static void ATHW_DateTimeSetCurrent(GSM_DateTime *datetime) +{ +time_t current=time(NULL); +struct tm *tm=localtime(¤t); + + datetime->AlarmEnabled=false; + + datetime->Year =tm->tm_year+1900; + datetime->Month =tm->tm_mon+1; + datetime->Day =tm->tm_mday; + datetime->Hour =tm->tm_hour; + datetime->Minute=tm->tm_min; + datetime->Second=tm->tm_sec; + + datetime->Timezone=timezone; +} + +/* +CPBR: (ReadPhonebook) + * CurrentPhonebookEntry->Name =4th arg + * CurrentPhonebookEntry->Number=2nd arg + */ +static char *ATHW_RX_Patrol_CPBR_ReadPhonebook(char *after) +{ + CurrentPhonebookEntry->Empty=false; + ATHW_EXTRACTSTRING(CurrentPhonebookEntry->Name ,after/*src*/,3/*element_no*/); + ATHW_EXTRACTSTRING(CurrentPhonebookEntry->Number,after/*src*/,1/*element_no*/); + + return(after); /* eat line */ +} + +static const struct ATHW_RX_Patrol ATHW_RX_Patrol_CPBR_ReadPhonebook_struct= + { "\n+CPBR:", ATHW_RX_Patrol_CPBR_ReadPhonebook }; + +/* Routine to get specifed phone book location. Designed to be called by + * application. Will block until location is retrieved or a timeout/error + * occurs. + */ +static GSM_Error ATHW_ReadPhonebook(GSM_Data *data, GSM_Statemachine *state) +{ + CurrentPhonebookEntry=data->PhonebookEntry; + + ATHW_ERR_WRAPPER(ATHW_SelectPhonebookMemory(CurrentPhonebookEntry->MemoryType)); + + /* We may get just OK response (Siemens M20) which means empty location + */ + CurrentPhonebookEntry->Empty=true; + CurrentPhonebookEntry->Group=0; /* not present up to Nokia 9110i */ + ATHW_DateTimeSetCurrent(&CurrentPhonebookEntry->Date); /* not present up to Nokia 9110i */ + CurrentPhonebookEntry->SubEntriesCount=0; /* not present up to Nokia 9110i */ + CurrentPhonebookEntry->Name[0]='\0'; /* default */ + CurrentPhonebookEntry->Number[0]='\0'; /* default */ + + ATHW_TX_SendCommand(&CurrentPhonebookError/*errorcodep*/,&ATHW_RX_Patrol_CPBR_ReadPhonebook_struct, + "AT+CPBR=%d\r",CurrentPhonebookEntry->Location); + if (GE_NONE!=wait_on(&CurrentPhonebookError,10/*timeout*/)) { + /* Nokia 9000i returns ATHW_CME_NOT_FOUND error code for empty locations + */ + if (ATHW_CME_NOT_FOUND!=ATHW_RX_Patrol_CME_ERROR_code) + return(CurrentPhonebookError); + return(GE_NONE); + } + + return(GE_NONE); +} + + +/* Routine to write phonebook location in phone. Designed to be called by + application code. Will block until location is written or timeout + occurs. */ + +static GSM_Error ATHW_WritePhonebook(GSM_Data *data, GSM_Statemachine *state) +{ + CurrentPhonebookEntry=data->PhonebookEntry; + + ATHW_ERR_WRAPPER(ATHW_SelectPhonebookMemory(CurrentPhonebookEntry->MemoryType)); + + ATHW_TX_SENDCOMMAND_WAIT_ON(&CurrentPhonebookError/*errorcodep*/,60/*timeout*/,NULL/*patrol*/, + (CurrentPhonebookEntry->Empty ? "AT+CPBW=%d\r" : "AT+CPBW=%d,%s,,%s\r"), + CurrentPhonebookEntry->Location, + ATHW_Enquote(CurrentPhonebookEntry->Number), + ATHW_Enquote(CurrentPhonebookEntry->Name) + ); + + return(GE_NONE); +} + +static const char *ATHW_SMStoStat(GSM_SMSMessage *SMS) +{ + switch (CurrentSMSMessage->Type) { + case GST_MT: + switch (CurrentSMSMessage->Status) { + case GSS_NOTSENTREAD: + return("REC UNREAD"); + case GSS_SENTREAD: + return("REC READ"); + } + return(NULL); + case GST_MO: + switch (CurrentSMSMessage->Status) { + case GSS_NOTSENTREAD: + return("STO UNSENT"); + case GSS_SENTREAD: + return("STO SENT"); + } + return(NULL); + default: + return(NULL); + } + /* NOTREACHED */ +} + +/* RETURN: Success of recognizing "SMSstat" + */ +static bool ATHW_StattoSMS(GSM_SMSMessage *SMS,const char *SMSstat) +{ + /**/ if (!strcmp(SMSstat,"0") || !strcmp(SMSstat,"REC UNREAD")) { + SMS->Type=GST_MT; + SMS->Status=GSS_NOTSENTREAD; + } + else if (!strcmp(SMSstat,"1") || !strcmp(SMSstat,"REC READ")) { + SMS->Type=GST_MT; + SMS->Status=GSS_SENTREAD; + } + else if (!strcmp(SMSstat,"2") || !strcmp(SMSstat,"STO UNSENT")) { + SMS->Type=GST_MO; + SMS->Status=GSS_NOTSENTREAD; + } + else if (!strcmp(SMSstat,"3") || !strcmp(SMSstat,"STO SENT")) { + SMS->Type=GST_MO; + SMS->Status=GSS_SENTREAD; + } + else return(false); /* not recognized! */ + return(true); +} + +/* scts=="01/10/25,02:28:18+08", scts WILL BE DESTROYED! + */ +static bool ATHW_SCTStoSMS_CMGF1(GSM_SMSMessage *SMS,char *scts) +{ +GSM_DateTime *DateTime=&SMS->Time; +bool timezone_minus; +const char *fmt="%d/%d/%d,%d:%d:%d+%d"; /* trailing '\0' IS used! */ +int i; + + if (strlen(scts)!=20) + return(false); + if ((timezone_minus=(scts[17]=='-'))) + scts[17]='+'; + i=0; + do { + if (!isdigit(scts[i++])) + return(false); + if (!isdigit(scts[i++])) + return(false); + if (scts[i]!=fmt[i]) + return(false); + } while (fmt[i++]); + /* string is completely valid now */ + + sscanf(scts,fmt, + &DateTime->Year, + &DateTime->Month, + &DateTime->Day, + &DateTime->Hour, + &DateTime->Minute, + &DateTime->Second, + &DateTime->Timezone); + if (DateTime->Year>=85) + DateTime->Year-=100; + DateTime->Year+=2000; + if (timezone_minus) + DateTime->Timezone=-DateTime->Timezone; + return(true); +} + +static void ATHW_DCStoSMS(GSM_SMSMessage *SMS,unsigned char dcs) +{ + switch ((dcs&0xF0)>>4) { + case 0x0: + switch (dcs&0x0F) { + case 0x0: + CurrentSMSMessage->EightBit=false; + break; + } + break; + case 0xF: + CurrentSMSMessage->EightBit=!!(dcs&0x04); /* bit 2 */ + CurrentSMSMessage->Class=(dcs&0x03); /* bits 0 & 1 */ + break; + } +} + +static bool ATHW_SCTStoSMS_CMGF0(GSM_SMSMessage *SMS,unsigned char *scts) +{ +GSM_DateTime *DateTime=&SMS->Time; +int *fields[]={ /* FIXME: offsetof() would be nice here but currently not supported by Gnokii */ + &DateTime->Year, + &DateTime->Month, + &DateTime->Day, + &DateTime->Hour, + &DateTime->Minute, + &DateTime->Second, + &DateTime->Timezone, + }; +int i; +unsigned char digit0,digit1; + + for (i=0;i 9 + || (digit1=((*scts)>>4)&0x0F) > 9 + ) + return(false); + (*fields[i])=10*digit0 + digit1; + } + /* scts is completely valid now */ + + if (DateTime->Year>=85) + DateTime->Year-=100; + DateTime->Year+=2000; + return(true); +} + +/* Value MUST match ((fo>>3)&0x03) ! + */ +enum ATHW_FO_Validity { + ATHW_FOV_None =0, + ATHW_FOV_Reserved=1, + ATHW_FOV_Relative=2, + ATHW_FOV_Absolute=3, + }; +static const size_t ATHW_FO_Validity_sizes[]={ + 0, /* ATHW_FOV_None */ + 0, /* ATHW_FOV_Reserved - not valid */ + 1, /* ATHW_FOV_Relative */ + 7, /* ATHW_FOV_Absolute */ + }; + +/* RETURN: Success of recognizing "SMSstat" + */ +static void ATHW_FOtoSMS(GSM_SMSMessage *SMS,enum ATHW_FO_Validity *validityp,unsigned char fo) +{ + switch (fo&0x03) { + case 0: SMS->Type=GST_MT; break; + case 1: SMS->Type=GST_MO; break; + default: /* FIXME: value 2 and 3? */ + } + + /* bit 2 - reject duplices - not supported by Gnokii + */ + + if (validityp) + *validityp=(enum ATHW_FO_Validity)((fo>>3)&0x03); + + /* bit 5 - status report request - not supported by Gnokii + */ + + SMS->UDHPresent=!!(fo&(1<<6)); + + SMS->ReplyViaSameSMSC=!!(fo&(1<<7)); +} + +static GSM_Error ATHW_RX_Patrol_CMGR_CMGF0_core(char *after,char *messagetext,char *end) +{ +unsigned char pdu[GNOKII_MAX_PDU_LENGTH],*pduend,*s; +char *oada; +size_t oada_size,udl; +enum ATHW_FO_Validity FO_validity; +unsigned udbits; /* 7 or 8 bits in UserData */ + + if (end-messagetext>2*sizeof(pdu)) + return(GE_INTERNALERROR); /* input PDU hex too long! */ + if (!(pduend=SMS_BlockFromHex(pdu,messagetext,end-messagetext/*len*/))) + return(GE_INTERNALERROR); + s=pdu; + if (ATHW_CurrentSMSCPrefix) { + if (s >= pduend + || *s < 1/*type*/ + || *s > 1/*type*/ + + ( sizeof(CurrentSMSMessage->MessageCenter.Number) + -1/*'\0' termination*/ + +1 )/2/*bytes of rounded-up nibbles*/ + || s +1/*type*/ +((*s)+1)/2/*bytes of rounded-up nibbles*/ > pduend /* whole SMSC.Number must fit */ + ) + return(GE_INTERNALERROR); + if (!(s=SemiOctetUnpack(CurrentSMSMessage->MessageCenter.Number,sizeof(CurrentSMSMessage->MessageCenter.Number), + s+1,2*((*s) -1/*type*/ )))) + return(GE_INTERNALERROR); /* invalid SMSCenter */ + } + if (s +1/**/ +1/*received number length*/ > pduend) + return(GE_INTERNALERROR); + ATHW_FOtoSMS(CurrentSMSMessage,&FO_validity,*s++); + + switch (CurrentSMSMessage->Type) { + + case GST_MT: + oada =CurrentSMSMessage->Sender; + oada_size=sizeof(CurrentSMSMessage->Sender); + break; + + case GST_MO: + oada =CurrentSMSMessage->Destination; + oada_size=sizeof(CurrentSMSMessage->Destination); + + /* message reference is really not interesting to be read */ + if (s +1/**/ > pduend) + return(GE_INTERNALERROR); + s++; + + break; + + /* FIXME: GST_DR - Delivery Report - not supported! + */ + default: + /* Unable to skip OA/DA, parsing would fail + */ + return(GE_INTERNALERROR); + } + + if (oada) { + if (s + 1/*type*/ +((*s)+1)/2/*bytes of rounded-up nibbles*/ > pduend) + return(GE_INTERNALERROR); + if (!(s=SemiOctetUnpack(oada,oada_size,s+1,(*s)/*nibbles*/))) + return(GE_INTERNALERROR); /* invalid OA/DA */ + } + + if (s +1/**/ +1/**/ > pduend) + return(GE_INTERNALERROR); + + /* FIXME: Is it correct to fill "MessageCenter" fields from SMS body? */ + CurrentSMSMessage->MessageCenter.Format=*s++; /* */ + + ATHW_DCStoSMS(CurrentSMSMessage,*s++); /* */ + + switch (CurrentSMSMessage->Type) { + + case GST_MT: /* SCTS field */ + if (s +7/*scts*/ > pduend) + return(GE_INTERNALERROR); + if (!ATHW_SCTStoSMS_CMGF0(CurrentSMSMessage,s)) + return(GE_INTERNALERROR); + s+=7; + break; + + case GST_MO: /* VP field */ + if (s +ATHW_FO_Validity_sizes[(unsigned)FO_validity] > pduend) + return(GE_INTERNALERROR); + switch (FO_validity) { + case ATHW_FOV_None: + break; + case ATHW_FOV_Reserved: + return(GE_INTERNALERROR); /* not supported */ + case ATHW_FOV_Relative: + CurrentSMSMessage->Validity=SMS_VP_to_Validity((GSM_SMSMessageValidity)*s); + break; + case ATHW_FOV_Absolute: + /* leave default, not supported by Gnokii */ + break; + } + s+=ATHW_FO_Validity_sizes[(unsigned)FO_validity]; + break; + + default: + /* To be written when other types get supported in the above switch + */ + } + + if (s +1/*udl*/ > pduend) + return(GE_INTERNALERROR); + + /* We are pretty strict as UDL must exactly match the end of SMS + * as the is the primary check for the good guess of SMSCenter presentness + * during guessing by ATHW_RX_Patrol_CMGR_CMGF0_retrier(). + */ + udl=(*s++); + udbits=(CurrentSMSMessage->EightBit ? 8 : 7); + if (s +( udl*udbits +7/*round-up*/ )/8 != pduend) + return(GE_INTERNALERROR); + + if (CurrentSMSMessage->UDHPresent) { + if (0 + || s +1/*sizeof(UDH[0])*/ > pduend + || s +1/*sizeof(UDH[0])*/ + *s/*UDH[0]*/ > pduend + || 1/*sizeof(UDH[0])*/ + *s/*UDH[0]*/ > sizeof(CurrentSMSMessage->UDH) + ) + return(GE_INTERNALERROR); + memcpy(CurrentSMSMessage->UDH,s, 1/*sizeof(UDH[0])*/ + *s/*UDH[0]*/ ); + s+=1/*sizeof(UDH[0])*/ + *s/*UDH[0]*/; + udl-=(( 1/*sizeof(UDH[0])*/ +CurrentSMSMessage->UDH[0] )*8 +0/*round-down*/ )/udbits; + } + + if (udl > (sizeof(CurrentSMSMessage->MessageText) -1/*terminating '\0'*/)) + return(GE_INTERNALERROR); + CurrentSMSMessage->MessageTextLength=udl; + CurrentSMSMessage->MessageText[CurrentSMSMessage->MessageTextLength]='\0'; + + if (udbits==8) { + memcpy(CurrentSMSMessage->MessageText,s,udl); + } + else { /* udhbits==7 */ + UnpackEightBitsToSeven((7-(!CurrentSMSMessage->UDHPresent ? 0 : 1+CurrentSMSMessage->UDH[0]))%7, /* offset */ + pduend-s, /* in_length */ + udl, /* out_length */ + s, /* input */ + CurrentSMSMessage->MessageText /* output */ + ); + CurrentSMSMessage->MessageText[udl]='\0'; + } + + return(GE_NONE); +} + +/* This is just a variant of ATHW_SMS_CMGF0(), I was too lazy to generalize it + */ +static GSM_Error ATHW_RX_Patrol_CMGR_CMGF0_retrier(char *after,char *messagetext,char *end) +{ +GSM_Error err,err_first=GE_INTERNALERROR/*shut up GCC*/; +int retry; + + for (retry=0;retry<2;retry++) { + /* There is no need to give up trying decoding (=> retried>0 ) + * by both ways as it does't cost us anything (not as in ATHW_SMS_CMGF0()). + */ + if (GE_NONE==(err=ATHW_RX_Patrol_CMGR_CMGF0_core(after,messagetext,end),1/*retried*/)) { + ATHW_CurrentSMSCPrefix_force=true; + return(err); + } + if (ATHW_CurrentSMSCPrefix_force) + return(err); + if (!retry) + err_first=err; + ATHW_CurrentSMSCPrefix=!ATHW_CurrentSMSCPrefix; + } + /* Return rather the first error code as it is more probable that + * it was generated with valid ATHW_CurrentSMSCPrefix setting + */ + return(err_first); +} + +/* +CMGR message reading will be finalized by "\nOK\n" which will terminate wait_on(). + * But invalid message data followed by "\nOK\n" are still invalid so we have to indicate it. + */ +static void ATHW_RX_Patrol_CMGR_CMGF0(char *after,char *messagetext,char *end) +{ +GSM_Error err; + + err=ATHW_RX_Patrol_CMGR_CMGF0_retrier(after,messagetext,end); + if (err!=GE_NONE) + CurrentSMSMessageError=err; +} + +/* +CMGR: (+CMGF==1) + * CurrentSMSMessage->Status = 1st arg + * CurrentSMSMessage->Sender = 2nd arg + * CurrentSMSMessage->Destination = 2nd arg + * ...etc., see the code below + * + * Type==GST_MT: + * +CMGR: ,,,,,,, ,,, + * 0 1 2 3 4 5 6 7 8 9 10 + * Type==GSM_MO: + * +CMGR: ,, ,,,,,,,, + * 0 1 2 3 4 5 6 7 8 9 10 + */ +static void ATHW_RX_Patrol_CMGR_CMGF1(char *after,char *messagetext,char *end) +{ +char buf[32]; +int fieldno_fo=-1,fieldno_dcs=-1; +long l; + + switch (CurrentSMSMessage->Type) { + + case GST_MT: + ATHW_EXTRACTSTRING(CurrentSMSMessage->Sender,after/*src*/,1/*element_no*/); + + /* Service centre time stamp string format "01/10/25,02:28:18+08" + * Note: not present up to Nokia 9110i + */ + ATHW_EXTRACTSTRING(buf ,after/*src*/,3/*element_no*/); + ATHW_SCTStoSMS_CMGF1(CurrentSMSMessage,buf/*scts*/); /* error ignored, leave the default value */ + + fieldno_fo=5; + fieldno_dcs=7; + break; + + case GST_MO: + ATHW_EXTRACTSTRING(CurrentSMSMessage->Destination,after/*src*/,1/*element_no*/); + fieldno_fo=4; + fieldno_dcs=6; + + l=ATHW_ExtractNumber(after/*src*/,7/*element_no*/); + if (l>=0 && l<0x100) + CurrentSMSMessage->Validity=SMS_VP_to_Validity((GSM_SMSMessageValidity)l); + break; + + default: + /* Not supported */ + } + + if (fieldno_fo>=0) { + l=ATHW_ExtractNumber(after/*src*/,fieldno_fo/*element_no*/); + if (l>=0 && l<0x100) + ATHW_FOtoSMS(CurrentSMSMessage, + NULL, /* validityp - solved by GSM device in +CMGF==1 */ + l); + } + + if (fieldno_dcs>=0) { + l=ATHW_ExtractNumber(after/*src*/,fieldno_dcs/*element_no*/); + if (l>=0 && l<0x100) + ATHW_DCStoSMS(CurrentSMSMessage,l); + } + + ATHW_EXTRACTSTRING(CurrentSMSMessage->MessageCenter.Number,after/*src*/,8/*element_no*/); + + CurrentSMSMessage->MessageTextLength=GNOKII_MIN(end-messagetext,sizeof(CurrentSMSMessage->MessageText)-1); + memcpy(CurrentSMSMessage->MessageText,messagetext,CurrentSMSMessage->MessageTextLength); + CurrentSMSMessage->MessageText[CurrentSMSMessage->MessageTextLength]='\0'; +} + +/* +CMGR: + * CurrentSMSMessage->Type = 1st arg + */ +static char *ATHW_RX_Patrol_CMGR(char *after) +{ +char buf[32],*end,*messagetext; + + end=strchr(after,'\n'); + if (!end++) + return(after); /* assert, INTERNAL! */ + if (!*end) + return(NULL); /* need data */ + if (*end=='\n') /* 2nd optional '\n' */ + end++; /* '\n' should be really double as it was "\r\n" from the device */ + messagetext=end; + if (!(end=strchr(end,'\n'))) + return(NULL); /* need data */ + /* Now we have read "+CMGR:*\n[\n]*\n" */ + + /* 1st arg is numeric(+CMGF==0)/alpha(+CMGF==1), ATHW_StattoSMS parses it all + */ + ATHW_EXTRACTSTRING(buf,after/*src*/,0/*element_no*/); + ATHW_StattoSMS(CurrentSMSMessage,buf); /* error ignored: what to do if unknown? */; + + switch (ATHW_CurrentCMGF) { + case 0: ATHW_RX_Patrol_CMGR_CMGF0(after,messagetext,end); break; + case 1: ATHW_RX_Patrol_CMGR_CMGF1(after,messagetext,end); break; + } + + return(++end); /* eat the whole SMS block, INCLUDING the trailing '\n' */ +} + +static const struct ATHW_RX_Patrol ATHW_RX_Patrol_CMGR_struct= + { "\n+CMGR:", ATHW_RX_Patrol_CMGR }; + +static const struct ATHW_RX_Patrol ATHW_RX_Patrol_SiemensMGR_struct= + { "\n^SMGR:", ATHW_RX_Patrol_CMGR }; + +/* Currently we always set the first two memory types as the syntax with empty + * preceding elements is not supported at least by Nokia 9110i & Siemens M20. + * Ideal case would be to first query the state by AT+CPMS? and then the settings + * would be written back. + * We never need to set the third argument, for example on Nokia 9110i it even isn't + * supported for "SM" memory type! + */ +static GSM_Error ATHW_SMS_SelectMemoryType(GSM_SMSMessage *SMS) +{ +const char *atmemtype; +const char *atmemtype_quoted; + + ATHW_GETMEMORYTYPE(atmemtype,SMS->MemoryType); + atmemtype_quoted=ATHW_Enquote(atmemtype); + + ATHW_TX_SENDCOMMAND_WAIT_ON(&CurrentSMSMessageError/*errorcodep*/,10/*timeout*/,NULL/*patrol*/, + "AT+CPMS=%s,%s\r",atmemtype_quoted,atmemtype_quoted); + + return(GE_NONE); +} + +static GSM_Error ATHW_GetSMS(GSM_Data *data, GSM_Statemachine *state) +{ + CurrentSMSMessage = data->SMSMessage; + + ATHW_ERR_WRAPPER(ATHW_SMS_SelectMemoryType(CurrentSMSMessage)); /* select - reading/deleting */ + + ATHW_DateTimeSetCurrent(&CurrentSMSMessage->Time); /* not present up to Nokia 9110i */ + ATHW_DateTimeSetCurrent(&CurrentSMSMessage->SMSCTime); /* not present up to Nokia 9110i */ + CurrentSMSMessage->MessageTextLength=0; /* default */ + CurrentSMSMessage->Validity=72/*hours*/*60; /* default */ + CurrentSMSMessage->UDHPresent=false; /* default */ + CurrentSMSMessage->MessageText[0]='\0'; /* default */ + + ATHW_MessageCenterClear(&CurrentSMSMessage->MessageCenter); /* default */ + CurrentSMSMessage->MessageCenter.No=0; /* default - input for GetSMSCenter */ + + CurrentSMSMessage->Sender[0]='\0'; /* default */ + CurrentSMSMessage->Destination[0]='\0'; /* default */ + CurrentSMSMessage->MessageNumber=CurrentSMSMessage->Location; /* default */ + /* CurrentSMSMessage->MemoryType is input argument */ + CurrentSMSMessage->Type=GST_UN; /* default, detection of EMPTY SMSes! */ + CurrentSMSMessage->Status=GSS_SENTREAD; /* default */ + CurrentSMSMessage->Class=1; /* default */ + CurrentSMSMessage->EightBit=false; /* default */ + CurrentSMSMessage->Compression=false; /* default */ + /* CurrentSMSMessage->Location is input argument */ + CurrentSMSMessage->ReplyViaSameSMSC=false; /* default */ + + /* We may now read "unread" SMS which will change its status to "read". + * We don't know its current state so we don't know whether the number + * of "unread" SMSes will change. + */ + if (!ATHW_HaveSiemensMGR) + ATHW_CNMI_count=-1; + + ATHW_TX_SendCommand(&CurrentSMSMessageError/*errorcodep*/, + (ATHW_HaveSiemensMGR ? &ATHW_RX_Patrol_SiemensMGR_struct : &ATHW_RX_Patrol_CMGR_struct)/*patrol*/, + (ATHW_HaveSiemensMGR ? "AT^SMGR=%d\r" : "AT+CMGR=%d\r"), + CurrentSMSMessage->Location); + if (GE_NONE!=wait_on(&CurrentSMSMessageError,60/*timeout*/)) { + if (ATHW_CMS_INVALID_MEMORY_INDEX!=ATHW_RX_Patrol_CMS_ERROR_code) + return(CurrentSMSMessageError); + return(GE_EMPTYSMSLOCATION); + } + if (CurrentSMSMessage->Type==GST_UN) /* Empty slot with "\nOK\n" response detection */ + return(GE_EMPTYSMSLOCATION); + + return(GE_NONE); +} + +static GSM_Error ATHW_DeleteSMS(GSM_Data *data, GSM_Statemachine *state) +{ + CurrentSMSMessage = data->SMSMessage; + + ATHW_ERR_WRAPPER(ATHW_SMS_SelectMemoryType(CurrentSMSMessage)); /* select - reading/deleting */ + + ATHW_CNMI_count=-1; + + ATHW_TX_SENDCOMMAND_WAIT_ON(&CurrentSMSMessageError/*errorcodep*/,30/*timeout*/,NULL/*patrol*/, + "AT+CMGD=%d\r",CurrentSMSMessage->Location); + + return(GE_NONE); +} + +static const struct ATHW_RX_Patrol *ATHW_RX_Patrol_GT_SPACE_patrol_after; + +/* RETURNS: "true" if successful + */ +static bool WRITEPHONE_hex(unsigned char *buf,size_t buflen) +{ +char *hex; +size_t hexsize; /* should be ==2*buflen */ +size_t got; + + if (!(hex=malloc(2*buflen))) + return(false); + hexsize=(SMS_BlockToHex(hex,buf,buflen)-hex); + got=WRITEPHONE(PortFD,hex,hexsize); + free(hex); + + if (hexsize!=got) + return(false); + return(true); +} + +static char *ATHW_RX_Patrol_GT_SPACE(char *after) +{ + switch (ATHW_CurrentCMGF) { + + case 0: + if (!WRITEPHONE_hex(CurrentSMSMessagePDU,CurrentSMSMessagePDU_size)) + goto fail; + break; + + case 1: + if (CurrentSMSMessage->UDHPresent) { /* ATHW_CMGS_CMGF1_8bit_HaveBinHex==true assumed */ + if (!WRITEPHONE_hex(CurrentSMSMessage->UDH,1+CurrentSMSMessage->UDH[0])) + goto fail; + } + if (CurrentSMSMessage->EightBit && ATHW_CMGS_CMGF1_8bit_HaveBinHex) { + if (!WRITEPHONE_hex( CurrentSMSMessage->MessageText,CurrentSMSMessage->MessageTextLength)) + goto fail; + } + else { + if (CurrentSMSMessage->MessageTextLength!=WRITEPHONE(PortFD,CurrentSMSMessage->MessageText,CurrentSMSMessage->MessageTextLength)) + goto fail; + } + break; + } + + if (1 !=WRITEPHONE(PortFD,"\x1A"/*CTRL-Z*/,1)) { +fail: + CurrentSMSMessageError=GE_INTERNALERROR; + /* FALLTHRU to exit path */ + } + + /* We may be actually doing +CMGS instead of +CMGW but we do not yet + * need to patrol any output from +CMGS so don't bother its distinguishing now. + */ + ATHW_RX_Patrol_Current=ATHW_RX_Patrol_GT_SPACE_patrol_after; + + /* We just cannot return "after" as it would mean "eat line". + * Just leaving there an additional '\n' is pretty harmless. + */ + after[-1]='\n'; + return(after-1); +} + +static const struct ATHW_RX_Patrol ATHW_RX_Patrol_GT_SPACE_struct= + { "\n> ",ATHW_RX_Patrol_GT_SPACE }; + +static char *ATHW_SMS_TypeToSenderOrDestination(GSM_SMSMessage *SMS) +{ + switch (CurrentSMSMessage->Type) { + case GST_MO: + return(SMS->Destination); + case GST_MT: + return(SMS->Sender); + default: + return(NULL); + } + /* NOTREACHED */ +} + +/* The second argument is the size of the data in octets, + * excluding User Data Header - important only for 8bit data + * Ooops, we cannot share the PDU generating code with Gnokii FBUS code + * as FBUS doesn't use PDU format in its SMS-Send packet! + */ +static GSM_Error ATHW_SendSMS_CMGF0_core(GSM_Data *data, GSM_Statemachine *state,const char *commandfmt) G_GNUC_PRINTF(3,0); +static GSM_Error ATHW_SendSMS_CMGF0_core(GSM_Data *data, GSM_Statemachine *state,const char *commandfmt) +{ +unsigned char pdu[GNOKII_MAX_PDU_LENGTH]; +unsigned char *d,*pdustart,*bodystart,*pdudatalengthp; +int i; +char *SMSsenderdestination; + + d=pdu; + + if (ATHW_CurrentSMSCPrefix) { + + /* We should get SMSC number */ + if (CurrentSMSMessage->MessageCenter.No) { +GSM_Data data_messagecenter; + + data_messagecenter.MessageCenter=&CurrentSMSMessage->MessageCenter; + ATHW_ERR_WRAPPER(ATHW_GetSMSCenter(&data_messagecenter,state)); + CurrentSMSMessage->MessageCenter.No = 0; + } + if (!*CurrentSMSMessage->MessageCenter.Number) + *d++=0x00; /* total SMSC length */ + else { + i=SemiOctetPack(CurrentSMSMessage->MessageCenter.Number,d+1); + *d= 1/*type*/ + (i+1)/2/*rounded-up bytes of nibbles*/ ; + d+= 1/*length*/ + 1/*type*/ + (i+1)/2/*rounded-up bytes of nibbles*/ ; + } + } + /* We MUST NOT count SMSCenter length into +CMGS=%d count argument + */ + pdustart=d; + + *d++=ATHW_SMStoFO(CurrentSMSMessage); /* : First Octet of SMS-SUBMIT */ + *d++=ATHW_PDU_MR_DEFAULT; /* Message Reference */ + SMSsenderdestination=ATHW_SMS_TypeToSenderOrDestination(CurrentSMSMessage); + i=SemiOctetPack((!SMSsenderdestination ? "" : CurrentSMSMessage->Destination),d+1); + *d= i/*nibbles*/ ; + d+= 1/*length*/ + 1/*type*/ + (i+1)/2/*rounded-up bytes of nibbles*/ ; + *d++=(unsigned char)CurrentSMSMessage->MessageCenter.Format; + *d++=ATHW_SMStoDCS(CurrentSMSMessage); + *d++=SMS_Validity_to_VP(CurrentSMSMessage->Validity); + pdudatalengthp=d++; + + bodystart=d; + if (CurrentSMSMessage->UDHPresent) { +size_t UDHlen=1+CurrentSMSMessage->UDH[0]; + + memcpy(d,CurrentSMSMessage->UDH,UDHlen); + d+=UDHlen; + } + if (CurrentSMSMessage->EightBit) { + memcpy(d,CurrentSMSMessage->MessageText,CurrentSMSMessage->MessageTextLength); + d+=CurrentSMSMessage->MessageTextLength; + } else { +size_t byteslen=PackSevenBitsToEight( + /* check it out yourself, really the number of used bits for UDH header on the start + * as we will need to allocate initial bit to align SMS->MessageText on the 7-bit boundary + */ + (7-(d-bodystart))%7, + CurrentSMSMessage->MessageText,d); + d+=byteslen; + } + *pdudatalengthp=((d-bodystart)*8)/(CurrentSMSMessage->EightBit ? 8 : 7); + + CurrentSMSMessagePDU=pdu; + CurrentSMSMessagePDU_size=d-pdu; + + ATHW_ERR_WRAPPER(ATHW_SMS_CMGF01_pre()); + ATHW_TX_SENDCOMMAND_WAIT_ON(&CurrentSMSMessageError/*errorcodep*/,150/*timeout*/,&ATHW_RX_Patrol_GT_SPACE_struct/*patrol*/, + commandfmt,d-pdustart); + ATHW_ERR_WRAPPER(ATHW_SMS_CMGF01_post()); + + return(GE_NONE); +} + +/* Houston, we have a problem. + * Siemens M20 supports +CMGW specification but on my model it just + * reports ERROR (and is not respected). + * Fortunately it will write "+CMGW: \n" before and the message gets written + * so we try to ignore ERROR reports during initial probing of support. + */ +static bool ATHW_SaveSMS_StatSupported=true; +static bool ATHW_SaveSMS_StatSupported_force=false; +static int ATHW_SaveSMS_StatSupported_retries=ATHW_SAVESMS_STATSUPPORTED_RETRIES; + +static GSM_Error ATHW_SaveSMS_StatSupported_solve(GSM_Error err,int retried) +{ + if (ATHW_SaveSMS_StatSupported_force) + return(err); + if (err==GE_NONE || CurrentSMSMessage->MessageNumber) { + ATHW_SaveSMS_StatSupported_force=true; + return(GE_NONE); + } + if (!ATHW_SaveSMS_StatSupported_retries) { + ATHW_SaveSMS_StatSupported=false; + ATHW_SaveSMS_StatSupported_force=true; + return(err); + } + /* Count only the sending sessions, not each attempt. + * Otherwise SMS with not-acceptable for write would + * cost us ATHW_CURRENTSMSCPREFIX_RETRIES number of retries + * in ATHW_SaveSMS_StatSupported_retries counter! + */ + if (!retried) + ATHW_SaveSMS_StatSupported_retries--; + return(err); +} + +/* This is just a wrapper for ATHW_CurrentSMSCPrefix retrying, please see the comment + * at the definition of this variable. + */ +/* This is just a variant of ATHW_RX_Patrol_CMGR_CMGF0(), I was too lazy to generalize it + */ +static GSM_Error ATHW_SMS_CMGF0(GSM_Data *data, GSM_Statemachine *state,const char *commandfmt) +{ +GSM_Error err,err_first=GE_INTERNALERROR/*shut up GCC*/; +int retry; + + for (retry=0;retryDestination)); + ATHW_ERR_WRAPPER(ATHW_SMS_CMGF01_post()); + + return(GE_SMSSENDOK); +} + +static char *ATHW_RX_Patrol_CMGS_CMGW(char *after) +{ +long l; + + l=ATHW_ExtractNumber(after/*src*/,0/*element_no*/); + if (l>=0 && l<=INT_MAX/* SMS->Location is int */) + CurrentSMSMessage->MessageNumber=l; + + return(after); /* eat line */ +} + +static const struct ATHW_RX_Patrol ATHW_RX_Patrol_CMGS_struct= + { "\n+CMGS:", ATHW_RX_Patrol_CMGS_CMGW }; + +static GSM_Error ATHW_SendSMS(GSM_Data *data, GSM_Statemachine *state) +{ + CurrentSMSMessage = data->SMSMessage; + ATHW_RX_Patrol_GT_SPACE_patrol_after=&ATHW_RX_Patrol_CMGS_struct; + CurrentSMSMessage->MessageNumber=0; /* default */ + + switch (ATHW_CurrentCMGF) { + case 0: return(ATHW_SendSMS_CMGF0(data,state)); + case 1: return(ATHW_SendSMS_CMGF1(data,state)); + } + return(GE_INTERNALERROR); +} + +static const struct ATHW_RX_Patrol ATHW_RX_Patrol_CMGW_struct= + { "\n+CMGW:", ATHW_RX_Patrol_CMGS_CMGW }; + +static GSM_Error ATHW_SaveSMS_CMGF0(GSM_Data *data, GSM_Statemachine *state) +{ +const char *SMSstat; +char *commandfmt=NULL; /* just paranoia */ +GSM_Error err; + + SMSstat=(!ATHW_SaveSMS_StatSupported ? NULL : ATHW_SMStoStat(CurrentSMSMessage)); + gasprintf(&commandfmt,"AT+CMGW=%%d,%s\r",(!SMSstat ? "" : ATHW_Enquote(SMSstat))); + if (!commandfmt) + return(GE_INTERNALERROR); + + err=ATHW_SMS_CMGF0(data,state,commandfmt); + free(commandfmt); + + return(err); +} + +static GSM_Error ATHW_SaveSMS_CMGF1(GSM_Data *data, GSM_Statemachine *state) +{ +const char *SMSstat; +char *SMSsenderdestination; +GSM_Error err; + + SMSstat=(!ATHW_SaveSMS_StatSupported ? NULL : ATHW_SMStoStat(CurrentSMSMessage)); + SMSsenderdestination=ATHW_SMS_TypeToSenderOrDestination(CurrentSMSMessage); + + ATHW_ERR_WRAPPER(ATHW_SMS_CMGF01_pre()); + ATHW_TX_SendCommand(&CurrentSMSMessageError/*errorcodep*/,&ATHW_RX_Patrol_GT_SPACE_struct/*patrol*/, + "AT+CMGW=%s,,%s\r", + (!SMSsenderdestination ? "" : ATHW_Enquote(SMSsenderdestination)), + (!SMSstat ? "" : ATHW_Enquote(SMSstat))); + err=wait_on(&CurrentSMSMessageError,150/*timeout*/); + ATHW_ERR_WRAPPER(ATHW_SMS_CMGF01_post()); + + return(ATHW_SaveSMS_StatSupported_solve(err,0/*retried-no retry session here*/)); +} + +static GSM_Error ATHW_SaveSMS(GSM_Data *data, GSM_Statemachine *state) +{ + CurrentSMSMessage = data->SMSMessage; + ATHW_RX_Patrol_GT_SPACE_patrol_after=&ATHW_RX_Patrol_CMGW_struct; + CurrentSMSMessage->MessageNumber=0; /* default */ + + ATHW_ERR_WRAPPER(ATHW_SMS_SelectMemoryType(CurrentSMSMessage)); /* select - writing */ + + switch (ATHW_CurrentCMGF) { + case 0: return(ATHW_SaveSMS_CMGF0(data,state)); + case 1: return(ATHW_SaveSMS_CMGF1(data,state)); + } + return(GE_INTERNALERROR); +} + +static GSM_Error ATHW_Reset(GSM_Data *data, GSM_Statemachine *state) +{ + ATHW_ERR_WRAPPER(ATHW_PhoneSetup()); + + return(GE_NONE); +} + +/* +COPS: + * CurrentNetworkInfo->NetworkCode = 3rd arg + */ +static char *ATHW_RX_Patrol_COPS(char *after) +{ +long l; +char *s; + + l=ATHW_ExtractNumber(after/*src*/,1/*element_no*/); + /* check whether numeric operator mode is on */ + if (l!=2) + return(after); /* eat line */ + + ATHW_EXTRACTSTRING(CurrentNetworkInfo->NetworkCode,after/*src*/,2/*element_no*/); + if (strlen(CurrentNetworkInfo->NetworkCode)!=5) { +fail: + CurrentNetworkInfo->NetworkCode[0]='\0'; + return(after); /* eat line */ + } + for (s=CurrentNetworkInfo->NetworkCode;*s;s++) + if (!isdigit(*s)) + goto fail; + /* network code valid */ + + memmove(CurrentNetworkInfo->NetworkCode+3+1,CurrentNetworkInfo->NetworkCode+3,2 +1/*terminating '\0'*/); + CurrentNetworkInfo->NetworkCode[3]=' '; + + return(after); /* eat line */ +} + +static const struct ATHW_RX_Patrol ATHW_RX_Patrol_COPS_struct= + { "\n+COPS:", ATHW_RX_Patrol_COPS }; + +static void ATHW_RX_Patrol_CREG_hex4valid(char *hex4) +{ +char *s; + + if (strlen(hex4)!=4) { +fail: + hex4[0]='\0'; + return; + } + for (s=hex4;*s;s++) + if (!isxdigit(*s)) + goto fail; +} + +/* +CREG: + * CurrentNetworkInfo->CellID = 4rd arg + * CurrentNetworkInfo->LAC = 3nd arg + */ +static char *ATHW_RX_Patrol_CREG(char *after) +{ +long l; + + l=ATHW_ExtractNumber(after/*src*/,1/*element_no*/); + /* check whether we don't report stale information + */ + if (l==1/*registered-home network*/ || l==5/*registered-roaming*/) + return(after); /* eat line */ + + ATHW_EXTRACTSTRING( CurrentNetworkInfo->CellID,after/*src*/,3/*element_no*/); + ATHW_RX_Patrol_CREG_hex4valid(CurrentNetworkInfo->CellID); + ATHW_EXTRACTSTRING( CurrentNetworkInfo->LAC ,after/*src*/,2/*element_no*/); + ATHW_RX_Patrol_CREG_hex4valid(CurrentNetworkInfo->LAC ); + + return(after); /* eat line */ +} + +static const struct ATHW_RX_Patrol ATHW_RX_Patrol_CREG_struct= + { "\n+CREG:", ATHW_RX_Patrol_CREG }; + +static GSM_Error ATHW_GetNetworkInfo(GSM_Data *data, GSM_Statemachine *state) +{ + CurrentNetworkInfo=data->NetworkInfo; + + ATHW_TX_SENDCOMMAND_WAIT_ON(&CurrentNetworkInfoError/*errorcodep*/,10/*timeout*/,&ATHW_RX_Patrol_COPS_struct/*patrol*/, + "AT+COPS?\r"); + ATHW_TX_SENDCOMMAND_WAIT_ON(&CurrentNetworkInfoError/*errorcodep*/,10/*timeout*/,&ATHW_RX_Patrol_CREG_struct/*patrol*/, + "AT+CREG?\r"); + + /* When no information was gather we will rather report failure + */ + if (1 + && !*CurrentNetworkInfo->NetworkCode + && !*CurrentNetworkInfo->CellID + && !*CurrentNetworkInfo->LAC + ) + return(GE_INTERNALERROR); + + + return(GE_NONE); +} + + +#ifndef WIN32 + +/* Called by initialisation code to open comm port in asynchronous mode. */ + +static bool ATHW_OpenSerial(void) +{ + int result; + +#if __unices__ + int rtn; +#else + struct sigaction sig_io; + + /* Set up and install handler before enabling async IO on port. */ + + sig_io.sa_handler = ATHW_SigHandler; + sig_io.sa_flags = 0; + sigaction (SIGIO, &sig_io, NULL); +#endif + + /* Open device. */ + + result = device_open(PortDevice, false/*with_odd_parity*/, true/*with_async*/, -1/*with_hw_handshake*/, GCT_Serial); + + if (!result) { + perror(_("Couldn't open AT device")); + return false; + } + +#if __unices__ + /* create a thread to handle incoming data from mobile phone */ + rtn = pthread_create(&selThread, NULL, (void*)ATHW_SelectLoop, (void*)NULL); + if (rtn != 0) return false; +#endif + + /* device_changespeed() not needed as device_open() now automatically + * sets the user-specified (or default) speed. + */ + return (true); +} + +static void ATHW_SigHandler(int status) +{ + unsigned char buffer[255]; + int count, res; + res = device_read(buffer, 255); + for (count = 0; count < res ; count ++) + ATHW_RX_Char(buffer[count]); +} +#endif /* WIN32 */ + +static char *ATHW_RX_Patrol_OK(char *after) +{ + /* Some patrol may have already indicated some error! + */ + if (ATHW_CatchBufferErrorP && *ATHW_CatchBufferErrorP==GE_BUSY) + *ATHW_CatchBufferErrorP=GE_NONE; + + return(after); /* eat line */ +} + +static char *ATHW_RX_Patrol_ERROR(char *after) +{ + if (ATHW_CatchBufferErrorP) + *ATHW_CatchBufferErrorP=GE_INTERNALERROR; + + return(after); /* eat line */ +} + +/* We can't kick the user with anything else */ +#define ATHW_RX_Patrol_NO_CARRIER ATHW_RX_Patrol_ERROR +#define ATHW_RX_Patrol_DELAYED ATHW_RX_Patrol_ERROR +#define ATHW_RX_Patrol_NO_DIALTONE ATHW_RX_Patrol_ERROR +#define ATHW_RX_Patrol_BUSY ATHW_RX_Patrol_ERROR + +static char *ATHW_RX_Patrol_CMX_ERROR(char *after,long *errorp) +{ +char *end=NULL; +long l; + + while (*after==' ') after++; + + l=strtol(after,&end,10); + if (*after && l>=0 && l=0) { + ATHW_CNMI_count++; +#ifdef ATHW_DEBUG + printf("ATHW_CNMI_count increased to %d\n",ATHW_CNMI_count); +#endif + } + + return(after); /* eat line */ +} + +static const struct ATHW_RX_Patrol ATHW_RX_Patrols[]={ + { "\nOK\n" ,ATHW_RX_Patrol_OK }, + { "\nERROR\n" ,ATHW_RX_Patrol_ERROR }, + { "\nNO CARRIER\n" ,ATHW_RX_Patrol_NO_CARRIER }, + { "\nDELAYED\n" ,ATHW_RX_Patrol_DELAYED }, + { "\nNO DIALTONE\n" ,ATHW_RX_Patrol_NO_DIALTONE }, + { "\nBUSY\n" ,ATHW_RX_Patrol_BUSY }, + { "\n+CME ERROR:" ,ATHW_RX_Patrol_CME_ERROR, ATHW_RX_Patrol_CME_ERROR_reset }, + { "\n+CMS ERROR:" ,ATHW_RX_Patrol_CMS_ERROR, ATHW_RX_Patrol_CMS_ERROR_reset }, + { "\n+CRING: VOICE\n",ATHW_RX_Patrol_CRING_VOICE }, + { "\n+CMTI:" ,ATHW_RX_Patrol_CMTI }, + }; + +static void ATHW_CatchBufferReset(void) +{ + ATHW_CatchBufferPtr=ATHW_CatchBuffer; + *ATHW_CatchBufferPtr='\0'; + ATHW_CatchBufferMarker=NULL; +} + +static void ATHW_CatchBufferMarkStart(GSM_Error *errorcodep) +{ +static GSM_Error err_trashcan; +const struct ATHW_RX_Patrol *patrol; + + if (!errorcodep) + errorcodep=&err_trashcan; + ATHW_CatchBufferErrorP=errorcodep; + + ATHW_CatchBufferMarker=ATHW_CatchBufferPtr; + + for (patrol=ATHW_RX_Patrols;patrolreset) + (*patrol->reset)(); +} + +static const struct ATHW_RX_Patrol *ATHW_RX_Char_EvalPatrol_best; +static char *ATHW_RX_Char_EvalPatrol_best_found; + +static void ATHW_RX_Char_EvalPatrol(const struct ATHW_RX_Patrol *patrol) +{ +char *found,*foundend; + + if (!patrol) + return; + + if (!(found=strstr(ATHW_CatchBuffer,patrol->buoy))) + return; + + /* When the buoy doesn't end with '\n' we need to find some '\n' after it. + * Otherwise the whole line hasn't been read yet and we to yet wait. + * It means that we doesn't support Patrol which would catch an incomplete line + * - the only exception are the explicite checks for functions like "ATHW_RX_Patrol_GT_SPACE_struct" :-( + */ + foundend=found+strlen(patrol->buoy); + if (foundend<=found) /* assert */ + return; + if (1 /* FIXME: This list is an ugly solution... */ + && patrol!=&ATHW_RX_Patrol_GT_SPACE_struct + && patrol!=&ATHW_PhoneSetup_CMGF1_Detect_Patrol_GT_SPACE_struct + && patrol!=&ATHW_PhoneSetup_CMGF1_Detect_Patrol_9_struct + + && foundend>found && foundend[-1]!='\n' && !strchr(foundend,'\n')) + return; /* no whole line has been read yet */ + + if (!ATHW_RX_Char_EvalPatrol_best || ATHW_RX_Char_EvalPatrol_best_found>found) { + ATHW_RX_Char_EvalPatrol_best=patrol; + ATHW_RX_Char_EvalPatrol_best_found=found; + } +} + + +/* RX_State machine for receive handling. Called once for each character + received from the phone/phone. */ + +static void ATHW_RX_Char(char rx_byte) +{ +size_t offset; +#ifdef ATHW_DEBUG +#if 0 + dprintf(_("Received character '%c' (0x%02X)\n"), + (!isgraph(rx_byte) ? '?' : rx_byte),(unsigned char)rx_byte); +#endif + putchar(rx_byte); +#endif + +/* We try to keep back 1/2 of buffer data, ineffectively shifting it up when the buffer + * gets filled up. + */ + if (ATHW_CatchBufferPtr>=ATHW_CatchBuffer+sizeof(ATHW_CatchBuffer) -1/*Terminating '\0'*/ ) { +char *movefrom; + +#ifdef ATHW_DEBUG + dprintf(_("Shifting buffer:\n%s\n__END__\n"),ATHW_CatchBuffer); +#endif + movefrom=ATHW_CatchBuffer+(sizeof(ATHW_CatchBuffer)/2); + if (1 && ATHW_CatchBufferMarker + && ATHW_CatchBufferMarker>ATHW_CatchBuffer + && ATHW_CatchBufferMarkerbuoy); +#endif +#endif + found=ATHW_RX_Char_EvalPatrol_best_found; + foundend=found+strlen(ATHW_RX_Char_EvalPatrol_best->buoy); + end=(*ATHW_RX_Char_EvalPatrol_best->func)(foundend); + + if (!end) /* patrol returned NULL - it is on the track but it needs more data! */ + return; + if (end==foundend) { /* they did simple 'return(after);' */ + if (foundend[-1]!='\n') { + if (!(end=strchr(foundend,'\n'))) + end=foundend+strlen(foundend); + } + } + /* We place '\n' delimited at the *end */ + memmove(found+1,end,ATHW_CatchBufferPtr+1-end); + offset=end-(found+1); + ATHW_CatchBufferPtr-=offset; + ATHW_CatchBufferPtr[-1]='\n'; + + /* Move Marker right behind the squeezed data if we were inside + */ + if (ATHW_CatchBufferMarker>=found) { + if (ATHW_CatchBufferMarker<=end) + ATHW_CatchBufferMarker=ATHW_CatchBufferPtr+1; + else + ATHW_CatchBufferMarker-=offset; + } + } +} + +/* Here we initialise model specific functions. */ + +#define ATHW_FUNCTIONS_ENTRY(name) \ + case GOP_##name: return(ATHW_##name(data,state)); + +static GSM_Error ATHW_Functions(GSM_Operation op, GSM_Data *data, GSM_Statemachine *state) +{ +static GSM_Statemachine *catcher=NULL; + + if (!catcher || catcher==state) + catcher=state; + else + *((char *)NULL)=1; + + switch (op) { + ATHW_FUNCTIONS_ENTRY(Init) + ATHW_FUNCTIONS_ENTRY(Terminate) + ATHW_FUNCTIONS_ENTRY(GetMemoryStatus) + ATHW_FUNCTIONS_ENTRY(ReadPhonebook) + ATHW_FUNCTIONS_ENTRY(WritePhonebook) + ATHW_FUNCTIONS_ENTRY(GetSMSStatus) + ATHW_FUNCTIONS_ENTRY(GetSMSCenter) + ATHW_FUNCTIONS_ENTRY(SetSMSCenter) + ATHW_FUNCTIONS_ENTRY(GetSMS) + ATHW_FUNCTIONS_ENTRY(DeleteSMS) + ATHW_FUNCTIONS_ENTRY(SendSMS) + ATHW_FUNCTIONS_ENTRY(SaveSMS) + ATHW_FUNCTIONS_ENTRY(GetRFLevel) + ATHW_FUNCTIONS_ENTRY(GetBatteryLevel) + ATHW_FUNCTIONS_ENTRY(GetPowersource) + ATHW_FUNCTIONS_ENTRY(GetImei) + ATHW_FUNCTIONS_ENTRY(GetRevision) + ATHW_FUNCTIONS_ENTRY(GetModel) + ATHW_FUNCTIONS_ENTRY(GetManufacturer) + ATHW_FUNCTIONS_ENTRY(DialVoice) + ATHW_FUNCTIONS_ENTRY(DialData) + ATHW_FUNCTIONS_ENTRY(GetIncomingCallNr) + ATHW_FUNCTIONS_ENTRY(Reset) + ATHW_FUNCTIONS_ENTRY(CancelCall) + ATHW_FUNCTIONS_ENTRY(AnswerCall) + ATHW_FUNCTIONS_ENTRY(GetNetworkInfo) + + case GOP_Identify: { +GSM_Error err,r=GE_NONE; + if (GE_NONE!=(err=ATHW_GetImei (data,state))) + r=err; + if (GE_NONE!=(err=ATHW_GetRevision (data,state))) + r=err; + if (GE_NONE!=(err=ATHW_GetModel (data,state))) + r=err; + if (GE_NONE!=(err=ATHW_GetManufacturer(data,state))) + r=err; + return(r); + } + + default: + return(GE_NOTIMPLEMENTED); + } +} + +GSM_Phone phone_at_hw = { + UNIMPLEMENTED, /* IncomingFunctions - we don't use default StateMachine */ + UNIMPLEMENTED, /* DefaultFunction - we don't use default StateMachine */ + /* Mobile phone information */ + { + "AT", /* Supported models */ + 31, /* Max RF Level (AT+CSQ) */ + 0, /* Min RF Level (AT+CSQ) */ + GRF_CSQ, /* RF level units */ + 100, /* Max Battery Level (AT+CBC) */ + 0, /* Min Battery Level (AT+CBC) */ + GBU_Percentage, /* Battery level units */ + GDT_None, /* Have date/time support */ + GDT_None, /* Alarm supports time only */ + 0, /* No alarm available */ + 48, 84, /* Startup logo size */ + 14, 72, /* Op logo size */ + 14, 72, /* Caller logo size */ + }, + ATHW_Functions, +};