RE: About AT modules and buffering through SIM memory again

"Oded Arbel" <[email protected]>
Newsgroups gmane.comp.mobile.kannel.devel
Message-ID <[email protected]>
Did I just forgot to attach the patch ? yes I did :-)

Sorry

Oded Arbel
m-Wise Inc.
[email protected]

--
Boris: "In addition to our summer and winter estate, he owned a
valuable piece of land. True, it was a small piece, but he
carried it with him wherever he went."
	-- from 'Love and Death'

> > -----Original Message-----
> > From: Stipe Tolj [mailto:[email protected]]
> > Subject: Re: About AT modules and buffering through SIM memory again
> > 
> > >From my side, as long as basic functionality is not harmed and
> > stability is improved, please submit and we will review it.
> 
> Ok, here's the m-Wise version of Matthew Flex's SIM buffering 
> - it's a patch against smsc_at2.c and I tried to stay away 
> from touching anything not inside an #ifdef. there are a few 
> places where I couldn't help myself ( ;-> ), but most is 
> pretty self explanatory, except in one case : I had to change 
> at2_wait_modem_command to return the number of messages 
> received and processed during the call, and I found no other 
> easy way to do it, w/o rewriting major functionality or 
> submitting to ugly non-procedural programming style.
> 
> Oh, also the timestamp patches are applied with a bit of a 
> change - I used a swap_nibbles helper function as suggested 
> by Richard Braakman.
> 
> Cheers
> 
> Oded Arbel
> m-Wise Inc.
> [email protected]
> 
> --
> A low level language is one whose programs require attention 
> to the irrelevant.
>
smsc_at2.patch (application/octet-stream, 16.8 KB)
--- kannel-dev/gateway/gw/smsc_at2.c	Fri Feb  8 19:30:18 2002
+++ gateway/gw/smsc_at2.c	Tue Feb 12 18:24:19 2002
@@ -51,6 +51,10 @@
     int		skip_smsc_addr;
     int		prepend_zero_smsc;
     double	send_line_sleep;	/* wait seconds after having sent a line */
+#ifdef USE_SIM_BUFFERING
+	char *cpms_sm; /* set message storage to SM */
+	char *cpms_me; /* set message storage to ME */
+#endif
 };
 
 /* indexes into table below. Has to match ! */
@@ -67,6 +71,17 @@
 #define	MAX_MODEM_TYPES		9
 struct modem_def ModemTypes[MAX_MODEM_TYPES] =
 {
+#ifdef USE_SIM_BUFFERING
+    { "autodetect",    "AT+IFC=2,2"    , 0     , "AT+CNMI=1,2,0,0,0",  NULL,           NULL,   0, 0, 1, 0, 0, 0.1, "AT+CPMS=\"SM\"","AT+CPMS=\"ME\"" },
+    { "wavecom",       "AT+IFC=2,2"    , 9600  , "AT+CNMI=1,2,0,0,0",  "WAVECOM",      NULL,   0, 0, 1, 1, 1, 0.1, "AT+CPMS=\"SM\"","AT+CPMS=\"ME\"" },
+    { "premicell",     "AT+IFC=2,2"    , 9600  , "AT+CNMI=1,2,0,0,0",  "PREMICEL",     NULL,   0, 0, 0, 0, 0, 0.1, "AT+CPMS=\"SM\"","AT+CPMS=\"ME\"" },
+    { "siemens-tc35",  "AT\\Q3"        , 19200 , "AT+CNMI=1,2,0,0,1",  "SIEMENS",      "TC35", 0, 1, 1, 1, 1, 0.1, "AT+CPMS=\"SM\"","AT+CPMS=\"ME\"" },
+    { "siemens",       "AT\\Q3"        , 19200 , "AT+CNMI=1,2,0,0,0",  "SIEMENS",      "M20",  0, 1, 1, 1, 1, 0.1, "AT+CPMS=\"SM\"","AT+CPMS=\"ME\"" },
+    { "nokiaphone",    "AT+IFC=2,2"    , 9600  , "AT+CNMI=1,2,0,0,0",  "NokiaPhone" ,  NULL,   0, 1, 1, 1, 1, 0.1, "AT+CPMS=\"SM\"","AT+CPMS=\"ME\"" },
+    { "falcom",		"AT+IFC=2,2"    , 9600  , "AT+CNMI=1,2,0,0,0",  "Falcom",       NULL,   0, 0, 1, 0, 0, 0.1, "AT+CPMS=\"SM\"","AT+CPMS=\"ME\"" },
+    { "ericsson",	"AT+IFC=2,2"    , 9600  , "AT+CNMI=3,2,0,0",    "R520m",        NULL,   0, 0, 1, 1, 1, 0.1, "AT+CPMS=\"SM\",\"SM\",\"SM\"","AT+CPMS=\"ME\",\"ME\",\"ME\"" },
+    { "alcatel",	"AT+IFC=2,2"    , 9600  , "AT+CNMI=1,3,0,0,0",  "Alcatel",  	NULL,   0, 0, 1, 1, 1, 0.1, "AT+CPMS=\"SM\"","AT+CPMS=\"ME\"" } 
+#else
     { "autodetect", 	"AT+IFC=2,2" 	, 0     , "AT+CNMI=1,2,0,0,0",	NULL,		NULL, 	0, 0, 1, 0, 0, 0.1 },
     { "wavecom", 	"AT+IFC=2,2" 	, 9600  , "AT+CNMI=1,2,0,0,0",	"WAVECOM",	NULL,	0, 0, 1, 1, 1, 0.1 },
     { "premicell", 	"AT+IFC=2,2"	, 9600  , "AT+CNMI=1,2,0,0,0",	"PREMICEL",	NULL,	0, 0, 0, 0, 0, 0.1 },
@@ -76,6 +91,7 @@
     { "falcom",		"AT+IFC=2,2"	, 9600  , "AT+CNMI=1,2,0,0,0",	"Falcom",	NULL,	0, 0, 1, 0, 0, 0.1 },
     { "ericsson",	"AT+IFC=2,2"	, 9600  , "AT+CNMI=3,2,0,0",	"R520m",	NULL,	0, 0, 1, 1, 1, 0.1 },
     { "alcatel",    "AT+IFC=2,2"    , 9600  , "AT+CNMI=1,3,0,0,0",  "Alcatel",  NULL,   0, 0, 1, 1, 1, 0.1 } 
+#endif
 };
 
 /* maximum data to attempt to read in one go */
@@ -99,6 +115,27 @@
 /* The number of times to attempt to send a message should sending fail */
 #define RETRY_SEND 3
  
+#ifdef USE_SIM_BUFFERING
+/* define USE_SIM_BUFFERING to poll SIM memory at regular interval to catch messages in memory
+   this was tested to be somewhat problematic with Siemens M20 modem (for details see
+   MsgID <[email protected]> in Kannel-devel mailing list)
+   it was tested to work on Wavecom WM02B */
+
+/* once in how many listener loop to check the memory of the card */
+#define POLL_INTERVAL 20
+
+/* Define this to use SM memory (generally slower then ME memory) */
+#define USE_SM_MEM
+#undef USE_ME_MEM
+
+#ifdef USE_SM_MEM
+#define MEM_SELECT_COMMAND ModemTypes[privdata->modemid].cpms_sm
+#else 
+#define MEM_SELECT_COMMAND ModemTypes[privdata->modemid].cpms_me
+#endif
+
+#endif
+
 typedef struct PrivAT2data
 {
     List	*outgoing_queue;
@@ -116,6 +153,12 @@
     int 	phase2plus;
     Octstr	*validityperiod;    
     int		alt_dcs;
+#ifdef USE_SIM_BUFFERING
+	int input_mem_sms_capacity;
+	int input_mem_sms_used;
+	int output_mem_sms_capacity;
+	int output_mem_sms_used;
+#endif
 } PrivAT2data;
 
 
@@ -130,7 +173,7 @@
 void	at2_flush_buffer(PrivAT2data *privdata);
 int	at2_init_device(PrivAT2data *privdata);
 int	at2_send_modem_command(PrivAT2data *privdata,char *cmd, time_t timeout, int greaterflag);
-int	at2_wait_modem_command(PrivAT2data *privdata, time_t timeout, int greaterflag);
+int	at2_wait_modem_command(PrivAT2data *privdata, time_t timeout, int greaterflag, int* messages_collected);
 void	at2_set_speed(PrivAT2data *privdata, int bps);
 void	at2_device_thread(void *arg);
 int	at2_shutdown_cb(SMSCConn *conn, int finish_sending);
@@ -153,6 +196,11 @@
 void	at2_detect_speed(PrivAT2data *privdata);
 int	at2_detect_modem_type(PrivAT2data *privdata);
 int	at2_modem2id(char *name);
+int	swap_nibbles(char byte);
+#ifdef USE_SIM_BUFFERING
+void at2_check_messages_in_memory(PrivAT2data *privdata);
+int at2_memory_check(PrivAT2data* privdata);
+#endif
 
 /******************************************************************************
 ** For debugging purposes octstr_destroy might be a macro,
@@ -555,7 +603,7 @@
     	the next command. 10 sec should be suficient */
     	if(!privdata->pin_ready)
     	{
-   	   ret = at2_wait_modem_command(privdata,10, 0);
+   	   ret = at2_wait_modem_command(privdata,10, 0,NULL);
     	   if(ret == -1) /* timeout */
     	       return -1;
     	}
@@ -611,6 +659,12 @@
      ret = at2_send_modem_command(privdata, ModemTypes[privdata->modemid].init1, 0, 0);
     	if(ret != 0)
     	    return -1;
+#ifdef USE_SIM_BUFFERING
+		/* set message storage location for SIM buffering using the CPMS command */
+		ret = at2_send_modem_command(privdata, MEM_SELECT_COMMAND,0, 0);
+		if (ret != 0)
+			return -1;
+#endif
     info(0, "AT SMSC successfully opened.");
     return 0;
 }
@@ -632,7 +686,7 @@
 int at2_send_modem_command(PrivAT2data *privdata,char *cmd, time_t timeout, int gt_flag)
 {
     at2_write_line(privdata,cmd);
-    return at2_wait_modem_command(privdata, timeout, gt_flag);
+    return at2_wait_modem_command(privdata, timeout, gt_flag, NULL);
 }
 
 
@@ -641,7 +695,7 @@
 ** waits for the modem to send us something
 */
 
-int at2_wait_modem_command(PrivAT2data *privdata, time_t timeout, int gt_flag)
+int at2_wait_modem_command(PrivAT2data *privdata, time_t timeout, int gt_flag, int* messages_collected)
 {
     Octstr *line = NULL;
     Octstr *line2 = NULL;
@@ -651,6 +705,7 @@
     time_t cur_time;
     Msg	*msg;
     int len;
+	int cmgr_flag = 0;
  
     time(&end_time);
     if(timeout == 0)
@@ -705,7 +760,11 @@
 	   	ret = 1;
 	   	goto end;
 	   }
-           if (-1 != octstr_search(line, octstr_imm("+CMT"), 0))
+           if (-1 != octstr_search(line, octstr_imm("+CMT"), 0) 
+#ifdef USE_SIM_BUFFERING
+			   || ((-1 != octstr_search(line, octstr_imm("+CMGR:"), 0)) && (cmgr_flag = 1)) 
+#endif
+			   )
            {
            	line2 = at2_wait_line(privdata,1,0);
  
@@ -731,9 +790,17 @@
                     	{
 			    msg->sms.smsc_id = octstr_duplicate(privdata->conn->id);
                     	    bb_smscconn_receive(privdata->conn, msg);
+							if (messages_collected)
+								++(*messages_collected);
                     	}
+#ifdef USE_SIM_BUFFERING
+						if (!cmgr_flag) {
+#endif
                     	if(privdata->phase2plus)
 		    	    at2_write_line(privdata,"AT+CNMA");
+#ifdef USE_SIM_BUFFERING
+						}
+#endif
 			O_DESTROY(pdu);
 		    }
                 }
@@ -768,6 +835,134 @@
     return ret;
 }
 
+#ifdef USE_SIM_BUFFERING
+/******************************************************************************
+** at2_check_messages_in_memory
+** checks whether any messages are buffered in the SM or ME memory and extract
+** them.
+*/
+void at2_check_messages_in_memory(PrivAT2data* privdata)
+{
+	char cmd[20];
+
+	// get memory status
+	if (at2_memory_check(privdata) == -1) 
+	{
+		debug("bb.smsc.at2",0,"AT2[%s]: memory check error",octstr_get_cstr(privdata->device));
+		return;
+	}
+
+	if (privdata->input_mem_sms_used)
+	{ // that is - greater then 0, meaning there are some messages to fetch
+		// now - I used to just loop over the first input_mem_sms_used locations, but it doesn't
+		// hold, since under load, messages may be received while we're in the loop, and get stored
+		// in locations towards the end of the list, thus creating 'holes' in the memory.
+		// there are two ways we can fix this : (a) just read the last message location, delete it and return.
+		// it's not a complete solution since holes can still be created if messages are recieved between the
+		// memory check and the delete command, and anyway - it will slow us down and won't hold well under pressure
+		// (b) just scan the entire memory each call, bottom to top. this will be slow too, but it'll be reliable. 
+		//
+		// we can massivly improve performance by stopping after input_mem_sms_used messages have been read,
+		// but send_modem_command returns 0 for no message as well as for a message read, and the only other
+		// way to implement it is by doing memory_check after each read and stoping when input_mem_sms_used
+		// get to 0. this is slow (modem commands take time) so we improve speed only if there are less then
+		// 10 messages in memory.
+		// 
+		// I implemented the alternative - changed at2_wait_modem_command to return the number of messages it 
+		// collected
+		int i;
+		int message_count = 0; // cound number of messages collected
+
+		debug("bb.smsc.at2",0,"AT2[%s]: %d messages waiting in memory",octstr_get_cstr(privdata->device),privdata->input_mem_sms_used);
+		
+		for (i = 1; i <= privdata->input_mem_sms_capacity && 
+			message_count < privdata->input_mem_sms_used; ++i) 
+		{ // loop till end of memory or collected enouch messages
+			int old_message_count = message_count;
+			sprintf(cmd,"AT+CMGR=%d",i);
+			/* read one message from memory */
+			at2_write_line(privdata,cmd);
+			if (at2_wait_modem_command(privdata,0,0,&message_count) != 0)
+			{
+			   	debug("bb.smsc.at2",0,"AT2[%s]: failed to get message %d.",octstr_get_cstr(privdata->device),i);
+				continue; /* failed to read the message - skip to next message */
+			}
+
+			if (old_message_count == message_count) /* no need to delete if no message collected */
+				continue;
+
+			sprintf(cmd,"AT+CMGD=%d",i); /* delete the message we just read */
+			if (at2_send_modem_command(privdata,cmd,7,0) != 0)
+			{  /* 3 seconds is not enough with some modems if the message is large - so we'll give it 7 seconds */
+			   	debug("bb.smsc.at2",0,"AT2[%s]: failed to delete message %d.",octstr_get_cstr(privdata->device),i);
+				continue; /* failed to delete the message, we'll just ignore it for now - this is bad, since
+							if the message really didn't get deleted - we'll see it next time around. */
+			}
+		}
+	}
+	//at2_send_modem_command(privdata, ModemTypes[privdata->modemid].init1, 0, 0);
+}
+
+/*****************************************************************************
+ * Memory capacity and useage check
+ */
+int at2_memory_check(PrivAT2data *privdata) 
+{
+	long values[4]; // array to put response data in
+	int pos; // position of parser in data stream
+	int ret; // return code
+	Octstr* search_cpms = NULL;
+
+	if (at2_send_modem_command(privdata, "AT+CPMS?",0,0) != 0) //MEM_SELECT_COMMAND
+	{ // select memory type and get report
+		debug("bb.smsc.at2.memory_check",0,"failed to send mem select command to modem");
+		return -1;
+	}
+
+	search_cpms = octstr_create("+CPMS:");
+
+	if (pos = octstr_search(privdata->lines,search_cpms,0) != -1)
+	{ // got back a +CPMS response
+		int index = 0; // index in values array
+		pos += 6; // position of parser in the stream - start after header
+		
+		pos = octstr_search(privdata->lines,octstr_imm(","),pos) +1 ; // skip memory indication
+		while (index < 4 && 
+			pos < octstr_len(privdata->lines) && 
+			(pos = octstr_parse_long(&values[index],privdata->lines,pos,10)) != -1)
+		{ // find all the values
+			++pos; // skip number seperator;
+			++index; // increment array index
+			if (index == 2)
+				pos = octstr_search(privdata->lines,octstr_imm(","),pos)+1; // skip second memory indication
+		}
+		
+		if (index < 4)
+		{ // didn't get all memory data - I don't why, so I'll bail
+		   	debug("bb.smsc.at2",0,"AT2[%s]: couldn't parse all memory locations : %d:'%s'.",
+				octstr_get_cstr(privdata->device), index,octstr_get_cstr(privdata->lines)[pos]);
+			O_DESTROY(search_cpms);
+			return -1;
+		}
+
+		privdata->input_mem_sms_used = values[0];
+		privdata->input_mem_sms_capacity = values[1];
+		privdata->output_mem_sms_used = values[2];
+		privdata->output_mem_sms_capacity = values[3];
+
+		ret = 0; // everything cool
+		
+		//  clear the buffer
+		O_DESTROY(privdata->lines);
+	} else {
+	   	debug("bb.smsc.at2",0,"AT2[%s]: no correct header for CPMS response.",octstr_get_cstr(privdata->device));
+		ret = -1; // didn't get a +CPMS response - this is clearly an error
+	}
+
+	O_DESTROY(search_cpms);
+	return ret; 
+}
+#endif
 
 /******************************************************************************
 ** at2_set_speed
@@ -838,7 +1033,7 @@
     SMSCConn	*conn = arg;
     PrivAT2data	*privdata = conn->data;
 
-    int l;
+    int l,m;
    
     conn->status = SMSCCONN_CONNECTING;
     
@@ -869,7 +1064,15 @@
     	 if(l>0)
     	     at2_send_messages(privdata);
     	 else
-             at2_wait_modem_command(privdata,1,0);
+             at2_wait_modem_command(privdata,1,0,NULL);
+#ifdef USE_SIM_BUFFERING
+		 m++; 
+		 if (l == 0 && m > POLL_INTERVAL)
+		 {
+			 m = 0;
+			 at2_check_messages_in_memory(privdata);
+		 }
+#endif
    }
     at2_close_device(privdata);
     conn->status = SMSCCONN_DISCONNECTED;
@@ -1043,10 +1246,17 @@
 
     buffer = octstr_duplicate(line);
     /* find the beginning of a message from the modem*/ 
-    pos = octstr_search(buffer, octstr_imm("+CMT:"), 0);
-    if(pos == -1) 
+    if (pos = octstr_search(buffer, octstr_imm("+CMT:"), 0) != -1)
+		pos +=5;
+	else if (pos = octstr_search(buffer, octstr_imm("+CMGR:"), 0) != -1) {
+		pos += 6;
+		if ((pos = octstr_search(buffer,octstr_imm(","),pos)) != -1) // skip status field in +CMGR response
+			pos++;
+		else
+		goto nomsg;
+	}
+	else
 	goto nomsg;
-    pos += 5;
     pos = octstr_search(buffer, octstr_imm(","), pos);
     if(pos == -1)
 	goto nomsg;
@@ -1165,15 +1375,21 @@
         pos++;
         
         /* get the timestamp */
-        mtime.year   = octstr_get_char(pdu, pos) + 1900; pos++;
-        mtime.month  = octstr_get_char(pdu, pos); pos++;
-        mtime.day    = octstr_get_char(pdu, pos); pos++;
-        mtime.hour   = octstr_get_char(pdu, pos); pos++;
-        mtime.minute = octstr_get_char(pdu, pos); pos++;
-        mtime.second = octstr_get_char(pdu, pos); pos++;
+	mtime.year =  swap_nibbles(octstr_get_char(pdu, pos)); pos++;
+	mtime.year += (mtime.year < 70 ? 2000 : 1900);
+	mtime.month  = swap_nibbles(octstr_get_char(pdu, pos)) ; pos++;
+	mtime.day = swap_nibbles(octstr_get_char(pdu, pos)); pos++;
+	mtime.hour = swap_nibbles(octstr_get_char(pdu, pos)); pos++;
+	mtime.minute  = swap_nibbles(octstr_get_char(pdu, pos)); pos++;
+	mtime.second  = swap_nibbles(octstr_get_char(pdu, pos)); pos++;
+
         /* time zone: */
         /* XXX handle negative time zones */
-        mtime.hour  += octstr_get_char(pdu, pos); pos++;
+        /* time zone is not "swapped nibble", with the MSB as the sign (1 is negative). the problem is that
+        +1 means that we have to substract 1 from the hour to get GMT. also remember that the time zone is measured
+        in quarters of the hour and not in full hours.
+        FIXME: we need to get the module of the division by 4 to the minute field */
+	mtime.hour += ((octstr_get_char(pdu, pos) >> 7) ? 1 : -1) * (octstr_get_char(pdu, pos) & 127) / 4; pos++;
         stime = date_convert_universal(&mtime);
         
         /* get data length */
@@ -1310,8 +1526,11 @@
     
     do
     {
-    	if( ( msg = list_extract_first(privdata->outgoing_queue )))
+    	if ( msg = list_extract_first(privdata->outgoing_queue ))
 	    at2_send_one_message(privdata, msg);
+		 /* clear modem buffer, in case some leftovers from last send were left (you'd be surprised 
+			how often that happens, under load).*/
+		at2_wait_modem_command(privdata, 1, 0, NULL);
     } while (msg);
 }
 
@@ -1325,6 +1544,7 @@
     int ret = -1; 
     char sc[3];
     int retries = RETRY_SEND;
+	char tmp_id[500]; // message id for DLR
    
    /* the standard says you should be prepending the PDU with 00 to indicate to use the default SC.
       some older modems dont expect this so it can be disabled 
@@ -1354,15 +1587,60 @@
 	    at2_write(privdata,command);
 	    at2_write_ctrlz(privdata);
         /* wait 20 secs for modem command */
-	    ret = at2_wait_modem_command(privdata, 20, 0);
+	    ret = at2_wait_modem_command(privdata, 20, 0,NULL);
 	    debug("bb.at", 0, "send command status: %d", ret);
 	    if(ret != 0) /* OK only */
 	    	continue;
@@ -1826,4 +2104,11 @@
     return 0;
 }
 
-
+/* this silly thing here will just translate a "swapped nibble" pseodo Hex encoding
+   (from PDU) into something that people can actually understand.
+   implementation completly ripped off Dennis Malmstrom timestamp patches against 1.0.3.
+   thanks Dennis ! */
+int swap_nibbles(char byte)
+{
+	return ( ( byte & 15 ) * 10 ) + ( byte >> 4 );
+}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.