--- /dev/null
+/*
+
+ $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 <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <limits.h>
+
+
+#ifdef WIN32
+
+#include <windows.h>
+#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 <unistd.h>
+#include <termios.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <pthread.h>
+#include <errno.h>
+#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 <stat> 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 (src<srcend && *src!=',') src++;
+ end=src;
+ }
+ else { /* *src=='"' */
+ start=++src;
+ while (src<srcend && !(src[0]=='"' && (src[1]==',' || src+1==srcend))) src++;
+ end=src;
+ if (*src=='"')
+ src++;
+ }
+ while (*src==' ') src++;
+ if (*src==',')
+ src++;
+ /* here we have <start..end) as the current element */
+ } while (element_no--);
+
+ if (destlen-1<end-start)
+ end=start+destlen-1;
+
+ memcpy(dest,start,end-start);
+ dest[end-start]='\0';
+}
+
+#define ATHW_EXTRACTSTRING(dest,src,element_no) (ATHW_ExtractString((dest),sizeof((dest)),(src),(element_no)))
+
+static long ATHW_ExtractNumber(char *src,int element_no)
+{
+char buf[32],*end=NULL;
+long r;
+
+ ATHW_EXTRACTSTRING(buf,src,element_no);
+ r=strtol(buf,&end,10);
+ if (!*buf || (end && *end))
+ return(LONG_MIN);
+ return(r);
+}
+
+/* s/^\s+//; s/\s+$//; s/\s+/ /g; */
+
+static void ATHW_BufferTrimCopy(char *d_buf,size_t d_len,const char *s)
+{
+char *d_end=d_buf+d_len,*d;
+
+ for (d=d_buf;*s && d<d_end -1/*Terminating '\0'*/ ;s++) {
+ if (isspace(*s)) {
+ if (d==d_buf || d[-1]==' ')
+ continue;
+ *d++=' ';
+ continue;
+ }
+ *d++=*s;
+ }
+ if (d>d_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) /*<vp>=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=<fo>,<vp>,<pid>,<dcs> */
+ ATHW_TX_SENDCOMMAND_WAIT_ON(&CurrentSMSMessageError/*errorcodep*/,10/*timeout*/,NULL/*patrol*/,
+ "AT+CSMP=%d,%d,%d,%d\r",
+ /* <fo>: */ ATHW_SMStoFO(CurrentSMSMessage),
+ /* <vp>: */ SMS_Validity_to_VP(CurrentSMSMessage->Validity),
+
+ /* FIXME: Should we query current "MessageCenter.No" to get "Format" field? */
+ /* <pid>: */ (unsigned char)CurrentSMSMessage->MessageCenter.Format,
+
+ /* <dcs>: */ 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"); /* <mode>=2 (buffer when data active), <mt>=1 (+CMTI codes) */
+ if (GE_NONE!=wait_on(&err,10/*timeout*/)) {
+ ATHW_TX_SendCommand(&err/*errorcodep*/,NULL/*patrol*/,
+ "AT+CNMI=1,1\r"); /* <mode>=1 (discard when data active), <mt>=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"); /* <mode>=3 (just set <format>), <format>=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 <rssi>
+ */
+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 <bcs>
+ * CurrentBatteryLevel = 2nd arg <bcl>
+ */
+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 <sca>
+ */
+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 <pid>
+ * CurrentMessageCenter->Validity = 2nd arg <vp>
+ */
+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 <used1>
+ * CurrentSMSStatus->Slots = 3nd arg <total1>
+ */
+static char *ATHW_RX_Patrol_CPMS(char *after)
+{
+long l;
+
+ l=ATHW_ExtractNumber(after/*src*/,1/*element_no*/);
+ if (l>=0 && l<INT_MAX)
+ CurrentSMSStatus->Used=l;
+
+ l=ATHW_ExtractNumber(after/*src*/,2/*element_no*/);
+ if (l>=0 && l<INT_MAX)
+ CurrentSMSStatus->Slots=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-<index>)
+ */
+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 <text>
+ * CurrentPhonebookEntry->Number=2nd arg <number>
+ */
+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<ARRAY_LEN(fields);i++,scts++) {
+ if (0
+ || (digit0= (*scts) &0x0F) > 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/*<fo>*/ +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/*<mr>*/ > 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/*<pid>*/ +1/*<dcs>*/ > pduend)
+ return(GE_INTERNALERROR);
+
+ /* FIXME: Is it correct to fill "MessageCenter" fields from SMS body? */
+ CurrentSMSMessage->MessageCenter.Format=*s++; /* <pid> */
+
+ ATHW_DCStoSMS(CurrentSMSMessage,*s++); /* <dcs> */
+
+ 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 <stat>
+ * CurrentSMSMessage->Sender = 2nd arg <oa>
+ * CurrentSMSMessage->Destination = 2nd arg <da>
+ * ...etc., see the code below
+ *
+ * Type==GST_MT:
+ * +CMGR: <stat>,<oa>,<alpha>,<scts>,<tooa>,<fo>,<pid>,<dcs> ,<sca>,<tosca>,<length>
+ * 0 1 2 3 4 5 6 7 8 9 10
+ * Type==GSM_MO:
+ * +CMGR: <stat>,<da>,<alpha> ,<toda>,<fo>,<pid>,<dcs>,<vp>,<sca>,<tosca>,<length>
+ * 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*/);
+
+ /* <scts> 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 <stat>
+ */
+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 <stat> 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 <mem1> - 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 <mem1> - 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); /* <fo>: 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 <stat> specification but on my model it just
+ * reports ERROR (and <stat> is not respected).
+ * Fortunately it will write "+CMGW: <index>\n" before and the message gets written
+ * so we try to ignore ERROR reports during initial probing of <stat> 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;retry<ATHW_CURRENTSMSCPREFIX_RETRIES;retry++) {
+ if (GE_NONE==(err=ATHW_SaveSMS_StatSupported_solve(ATHW_SendSMS_CMGF0_core(data,state,commandfmt),retry))) {
+ 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);
+}
+
+static GSM_Error ATHW_SendSMS_CMGF0(GSM_Data *data, GSM_Statemachine *state)
+{
+GSM_Error err=ATHW_SMS_CMGF0(data,state,"AT+CMGS=%d\r");
+
+ if (err==GE_NONE)
+ return(GE_SMSSENDOK);
+ return(err);
+}
+
+/* The second argument is the size of the data in octets,
+ excluding User Data Header - important only for 8bit data */
+static GSM_Error ATHW_SendSMS_CMGF1(GSM_Data *data, GSM_Statemachine *state)
+{
+ ATHW_ERR_WRAPPER(ATHW_SMS_CMGF01_pre());
+ ATHW_TX_SENDCOMMAND_WAIT_ON(&CurrentSMSMessageError/*errorcodep*/,150/*timeout*/,&ATHW_RX_Patrol_GT_SPACE_struct/*patrol*/,
+ "AT+CMGS=%s\r",ATHW_Enquote(CurrentSMSMessage->Destination));
+ 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 <mem1> - 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 <oper>
+ */
+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 <lac>
+ * CurrentNetworkInfo->LAC = 3nd arg <cid>
+ */
+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<LONG_MAX && end && *end=='\n')
+ *errorp=l;
+
+ if (ATHW_CatchBufferErrorP)
+ *ATHW_CatchBufferErrorP=GE_INTERNALERROR;
+
+ return(after); /* eat line */
+}
+
+static char *ATHW_RX_Patrol_CME_ERROR(char *after)
+{
+ return(ATHW_RX_Patrol_CMX_ERROR(after,&ATHW_RX_Patrol_CME_ERROR_code));
+}
+
+static void ATHW_RX_Patrol_CME_ERROR_reset(void)
+{
+ ATHW_RX_Patrol_CME_ERROR_code=-1;
+}
+
+static char *ATHW_RX_Patrol_CMS_ERROR(char *after)
+{
+ return(ATHW_RX_Patrol_CMX_ERROR(after,&ATHW_RX_Patrol_CMS_ERROR_code));
+}
+
+static void ATHW_RX_Patrol_CMS_ERROR_reset(void)
+{
+ ATHW_RX_Patrol_CMS_ERROR_code=-1;
+}
+
+static char *ATHW_RX_Patrol_CRING_VOICE(char *after)
+{
+ if (CallPassup)
+ (*CallPassup)('V');
+
+ return(after); /* eat line */
+}
+
+static char *ATHW_RX_Patrol_CMTI(char *after)
+{
+#ifdef ATHW_DEBUG
+ printf("ATHW_RX_Patrol_CMTI: ATHW_CNMI_count==%d\n",ATHW_CNMI_count);
+#endif
+
+ if (ATHW_CNMI_count>=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;patrol<ATHW_RX_Patrols+ARRAY_LEN(ATHW_RX_Patrols);patrol++)
+ if (patrol->reset)
+ (*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_CatchBufferMarker<movefrom)
+ movefrom=ATHW_CatchBufferMarker;
+ offset=ATHW_CatchBuffer+sizeof(ATHW_CatchBuffer)-movefrom;
+ memmove(ATHW_CatchBuffer,movefrom,offset);
+ ATHW_CatchBufferPtr-=offset;
+ if (ATHW_CatchBufferMarker) {
+ if ((ATHW_CatchBufferMarker-=offset)<ATHW_CatchBuffer)
+ ATHW_CatchBufferMarker=ATHW_CatchBuffer;
+ }
+ }
+
+ /* NEVER store '\0' as it would knock-out completely our patrolling system!
+ */
+ if (rx_byte=='\r' || rx_byte=='\0')
+ rx_byte='\n';
+
+ *ATHW_CatchBufferPtr++=rx_byte;
+ *ATHW_CatchBufferPtr='\0';
+
+ for (;;) {
+const struct ATHW_RX_Patrol *patrol;
+char *found,*end,*foundend;
+
+ ATHW_RX_Char_EvalPatrol_best=NULL;
+ for (patrol=ATHW_RX_Patrols;patrol<ATHW_RX_Patrols+ARRAY_LEN(ATHW_RX_Patrols);patrol++) {
+ ATHW_RX_Char_EvalPatrol(patrol);
+ }
+ ATHW_RX_Char_EvalPatrol(ATHW_RX_Patrol_Current);
+ if (!ATHW_RX_Char_EvalPatrol_best)
+ return;
+
+#if 0
+#ifdef ATHW_DEBUG
+ printf("Invoking patrol for: %s\n",ATHW_RX_Char_EvalPatrol_best->buoy);
+#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,
+};