This commit was generated by cvs2svn to compensate for changes in r158,
[gnokii.git] / common / at-hw.c
diff --git a/common/at-hw.c b/common/at-hw.c
new file mode 100644 (file)
index 0000000..74b9f91
--- /dev/null
@@ -0,0 +1,2659 @@
+/*
+
+  $Id$
+  
+  G N O K I I
+
+  A Linux/Unix toolset and driver for Nokia mobile phones.
+
+  Copyright (C) 2001 Jan Kratochvil,
+  based on code by Hugh Blemings & Pavel Janík ml.
+
+  Released under the terms of the GNU GPL, see file COPYING for more details.
+
+  This file provides an API for accessing functions on AT (Hayes) command
+  compatible hardware modems such as Siemens M20.
+  See README-AT for more details on supported mobile phones and GSM modems.
+
+  The various routines are called ATHW (whatever) as a concatenation of AT
+  and HardWare communication type.
+
+  $Log$
+  Revision 1.1.1.1  2002/04/03 00:08:02  short
+  Found in "gnokii-working" directory, some November-patches version
+
+
+*/
+
+#define ATHW_DEBUG 1
+/* #define DISABLE_CMGF0 1 */          /* Force AT+CMGF=1 on phones capable +CMGF=0 */
+
+/* System header files */
+
+#include <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(&current);
+
+       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,
+};