/* $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, };