From 3038b393df86752477fc1fd9b4d00a77e2240ef1 Mon Sep 17 00:00:00 2001
From: Rob Swindell <rob@synchro.net>
Date: Wed, 1 Jun 2022 15:28:44 -0700
Subject: [PATCH] Add "Caller ID" support, enabled with AT#CID=1 or AT+VCID=1

Also controlled via [modem] CallerID key in svdm.ini fiile.
Reports the connected IP address between the first and second RING result.
Required a fix to reset the ringcount to 0 upon new connection.

Simplified the AT command parsing logic a bit.
---
 src/vdmodem/vdmodem.c | 365 +++++++++++++++++++++++-------------------
 1 file changed, 201 insertions(+), 164 deletions(-)

diff --git a/src/vdmodem/vdmodem.c b/src/vdmodem/vdmodem.c
index da84168754..371df19cc3 100644
--- a/src/vdmodem/vdmodem.c
+++ b/src/vdmodem/vdmodem.c
@@ -122,7 +122,6 @@ void usage(const char* progname)
 	exit(EXIT_SUCCESS);
 }
 
-const char* supported_cmds = "ADEHIMOQSVXZ&";
 const char* string_cmds = "D";
 #define MAX_SAVES 20
 struct modem {
@@ -141,6 +140,7 @@ struct modem {
 	bool numeric_mode;
 	bool offhook;
 	bool online; // false means "command mode"
+	bool caller_id;
 	bool ringing;
 	ulong ringcount;
 	ulong auto_answer;
@@ -214,17 +214,27 @@ const char* response_str[] = {
 	"CONNECT 9600"
 };
 
-char* response(struct modem* modem, enum modem_response code)
+char* verbal_response(struct modem* modem, const char* response)
+{
+	static char str[128];
+	safe_snprintf(str, sizeof(str), "%c%c%s%c%c", modem->cr, modem->lf, response, modem->cr, modem->lf);
+	return str;
+}
+
+char* numeric_response(struct modem* modem, int code)
 {
 	static char str[128];
+	safe_snprintf(str, sizeof(str), "%u%c", code, modem->cr);
+	return str;
+}
 
+char* response(struct modem* modem, enum modem_response code)
+{
 	if(modem->quiet)
 		return "";
 	if(modem->numeric_mode)
-		safe_snprintf(str, sizeof(str), "%u%c", code, modem->cr);
-	else
-		safe_snprintf(str, sizeof(str), "%c%c%s%c%c", modem->cr, modem->lf, response_str[code], modem->cr, modem->lf);
-	return str;
+		return numeric_response(modem, code);
+	return verbal_response(modem, response_str[code]);
 }
 
 char* ok(struct modem* modem)
@@ -288,6 +298,7 @@ const char* iniKeyESC = "ESC";
 const char* iniKeyExtResults = "ExtResults";
 const char* iniKeyDialWait = "DialWait";
 const char* iniKeyGuardTime = "GuardTime";
+const char* iniKeyCallerID = "CallerID";
 
 void init(struct modem* modem)
 {
@@ -297,6 +308,7 @@ void init(struct modem* modem)
 	modem->echo_off = !iniGetBool(ini, section, iniKeyEcho, TRUE);
 	modem->quiet = iniGetBool(ini, section, iniKeyQuiet, FALSE);
 	modem->numeric_mode = iniGetBool(ini, section, iniKeyNumeric, FALSE);
+	modem->caller_id = iniGetBool(ini, section, iniKeyCallerID, FALSE);
 	modem->cr = (char)iniGetInteger(ini, section,  iniKeyCR, '\r');
 	modem->lf = (char)iniGetInteger(ini, section, iniKeyLF, '\n');
 	modem->bs = (char)iniGetInteger(ini, section, iniKeyBS, '\b');
@@ -315,6 +327,7 @@ bool write_cfg(struct modem* modem)
 	iniSetBool(&ini, section, iniKeyEcho, !modem->echo_off, style);
 	iniSetBool(&ini, section, iniKeyQuiet, modem->quiet, style);
 	iniSetBool(&ini, section, iniKeyNumeric, modem->numeric_mode, style);
+	iniSetBool(&ini, section, iniKeyCallerID, modem->caller_id, style);
 	iniSetInteger(&ini, section, iniKeyCR, modem->cr, style);
 	iniSetInteger(&ini, section, iniKeyLF, modem->lf, style);
 	iniSetInteger(&ini, section, iniKeyBS, modem->bs, style);
@@ -718,175 +731,192 @@ char* atmodem_exec(struct modem* modem)
 	for(char* p = modem->buf; *p != '\0';) {
 		char ch = toupper(*p);
 		p++;
-		if(strchr(supported_cmds, ch) == NULL)
+		if(ch == '&') {
+			ch = toupper(*p);
+			ulong val = strtoul(p + 1, &p, 10);
+			switch(ch) {
+				case 'W':
+					resp = write_cfg(modem) ? ok(modem) : error(modem);
+					break;
+				case 'Z':
+					if(val >= MAX_SAVES)
+						return error(modem);
+					if(*p == '=') {
+						p++;
+						if(stricmp(p, "L") == 0)
+							p = modem->last;
+						SAFECOPY(modem->save[val], p);
+						return write_save(modem, val) ? ok(modem) : error(modem);
+					}
+					if(*p == '?' || stricmp(p, "L?") == 0) {
+						if(stricmp(p, "L?") == 0)
+							p = modem->last;
+						else
+							p = modem->save[val];
+						safe_snprintf(respbuf, sizeof(respbuf), "%c%s%c%c%s"
+							,modem->lf, p, modem->cr, modem->lf, ok(modem));
+						return respbuf;
+					}
+			}
+			continue;
+		}
+		// Caller ID control
+		if(ch == '#' || ch == '+') {
+			if(ch == '+' && toupper(*p) == 'V')
+				p++;
+			if(stricmp(p, "CID?") == 0) {
+				safe_snprintf(respbuf, sizeof(respbuf), "%c%u%c%c%s"
+					,modem->lf, modem->caller_id, modem->cr, modem->lf, ok(modem));
+				return respbuf;
+			}
+			if(stricmp(p, "CID=?") == 0) {
+				safe_snprintf(respbuf, sizeof(respbuf), "%c0,1%c%c%s"
+					,modem->lf, modem->cr, modem->lf, ok(modem));
+				return respbuf;
+			}
+			if(strnicmp(p, "CID=", 4) == 0) {
+				modem->caller_id = strtoul(p + 4, &p, 10);
+				continue;
+			}
 			return error(modem);
-		if(strchr(string_cmds, ch) == NULL) {
-			if(ch == '&') {
-				ch = toupper(*p);
-				ulong val = strtoul(p + 1, &p, 10); // unused
-				switch(ch) {
-					case 'W':
-						resp = write_cfg(modem) ? ok(modem) : error(modem);
+		}
+		// Numeric argument commands
+		ulong val = 0;
+		if(strchr(string_cmds, ch) == NULL)
+			val = strtoul(p, &p, 10);
+		switch(ch) {
+			case 'A':
+				return answer(modem);
+			case 'D':
+				if(sock != INVALID_SOCKET) {
+					dprintf("Can't dial: Already connected");
+					return error(modem);
+				}
+				if(*p == 'T' /* tone */|| *p == 'P' /* pulse */)
+					p++;
+				return dial(modem, p);
+			case 'E':
+				modem->echo_off = !val;
+				break;
+			case 'H':
+				modem->offhook = val;
+				modem->ringing = false;
+				if(!modem->offhook) {
+					if(sock != INVALID_SOCKET) {
+						disconnect(modem);
+					}
+				}
+				break;
+			case 'I':
+				switch(val) {
+					case 0:
+						safe_snprintf(respbuf, sizeof(respbuf)
+							,"\r\n" TITLE " v" VERSION " Copyright %s Rob Swindell\r\n%s/%s\r\n"
+							,&__DATE__[7]
+							,GIT_BRANCH
+							,GIT_HASH
+							);
 						break;
-					case 'Z':
-						if(val >= MAX_SAVES)
-							return error(modem);
-						if(*p == '=') {
-							p++;
-							if(stricmp(p, "L") == 0)
-								p = modem->last;
-							SAFECOPY(modem->save[val], p);
-							return write_save(modem, val) ? ok(modem) : error(modem);
-						}
-						if(*p == '?' || stricmp(p, "L?") == 0) {
-							if(stricmp(p, "L?") == 0)
-								p = modem->last;
-							else
-								p = modem->save[val];
-							safe_snprintf(respbuf, sizeof(respbuf), "%c%s%c%c%s"
-								,modem->lf, p, modem->cr, modem->lf, ok(modem));
-							return respbuf;
-						}
+					case 1:
+						safe_snprintf(respbuf, sizeof(respbuf), "\r\n%s\r\n", ini_fname);
+						break;
+					default:
+						return error(modem);
 				}
-				continue;
-			}
-			// Numeric argument commands
-			ulong val = strtoul(p, &p, 10);
-			switch(ch) {
-				case 'A':
-					return answer(modem);
-				case 'E':
-					modem->echo_off = !val;
-					break;
-				case 'H':
-					modem->offhook = val;
-					modem->ringing = false;
-					if(!modem->offhook) {
-						if(sock != INVALID_SOCKET) {
-							disconnect(modem);
-						}
+				return respbuf;
+			case 'O':
+				if(sock == INVALID_SOCKET)
+					return error(modem);
+				modem->online = true;
+				return connect_result(modem);
+				break;
+			case 'V':
+				modem->numeric_mode = !val;
+				resp = ok(modem); // Use the new verbal/numeric mode in response (ala USRobotics)
+				break;
+			case 'Q':
+				modem->quiet = val;
+				resp = ok(modem); // Use the new quiet/verbose mode in response (ala USRobotics)
+				break;
+			case 'S':
+				if(*p == '=') {
+					ulong sreg = val;
+					ulong val = strtoul(p + 1, &p, 10);
+					dprintf("S%lu = %lu", sreg, val);
+					switch(sreg) {
+						case 0:
+							if(val && listening_sock == INVALID_SOCKET) {
+								dprintf("Can't enable auto-answer when not in listening mode");
+								return error(modem);
+							}
+							modem->auto_answer = val;
+							break;
+						case 1:
+							modem->ringcount = val;
+							break;
+						case 2:
+							modem->esc = (char)val;
+							break;
+						case 3:
+							modem->cr = (char)val;
+							break;
+						case 4:
+							modem->lf = (char)val;
+							break;
+						case 5:
+							modem->bs = (char)val;
+							break;
+						case 7:
+							modem->dial_wait = val;
+							break;
+						case 12:
+							modem->guard_time = val;
+							break;
 					}
-					break;
-				case 'I':
+				} else if(*p == '?') {
 					switch(val) {
 						case 0:
-							safe_snprintf(respbuf, sizeof(respbuf)
-								,"\r\n" TITLE " v" VERSION " Copyright %s Rob Swindell\r\n%s/%s\r\n"
-								,&__DATE__[7]
-								,GIT_BRANCH
-								,GIT_HASH
-								);
+							val = modem->auto_answer;
 							break;
 						case 1:
-							safe_snprintf(respbuf, sizeof(respbuf), "\r\n%s\r\n", ini_fname);
+							val = modem->ringcount;
+							break;
+						case 2:
+							val = modem->esc;
+							break;
+						case 3:
+							val = modem->cr;
+							break;
+						case 4:
+							val = modem->lf;
+							break;
+						case 5:
+							val = modem->bs;
+							break;
+						case 7:
+							val = modem->dial_wait;
+							break;
+						case 12:
+							val = modem->guard_time;
 							break;
 						default:
-							return error(modem);
+							val = 0;
+							break;
 					}
+					safe_snprintf(respbuf, sizeof(respbuf), "%c%03lu%c%c%s"
+						,modem->lf, val, modem->cr, modem->lf, ok(modem));
 					return respbuf;
-				case 'O':
-					if(sock == INVALID_SOCKET)
-						return error(modem);
-					modem->online = true;
-					return connect_result(modem);
-					break;
-				case 'V':
-					modem->numeric_mode = !val;
-					resp = ok(modem); // Use the new verbal/numeric mode in response (ala USRobotics)
-					break;
-				case 'Q':
-					modem->quiet = val;
-					resp = ok(modem); // Use the new quiet/verbose mode in response (ala USRobotics)
-					break;
-				case 'S':
-					if(*p == '=') {
-						ulong sreg = val;
-						ulong val = strtoul(p + 1, &p, 10);
-						dprintf("S%lu = %lu", sreg, val);
-						switch(sreg) {
-							case 0:
-								if(val && listening_sock == INVALID_SOCKET) {
-									dprintf("Can't enable auto-answer when not in listening mode");
-									return error(modem);
-								}
-								modem->auto_answer = val;
-								break;
-							case 1:
-								modem->ringcount = val;
-								break;
-							case 2:
-								modem->esc = (char)val;
-								break;
-							case 3:
-								modem->cr = (char)val;
-								break;
-							case 4:
-								modem->lf = (char)val;
-								break;
-							case 5:
-								modem->bs = (char)val;
-								break;
-							case 7:
-								modem->dial_wait = val;
-								break;
-							case 12:
-								modem->guard_time = val;
-								break;
-						}
-					} else if(*p == '?') {
-						switch(val) {
-							case 0:
-								val = modem->auto_answer;
-								break;
-							case 1:
-								val = modem->ringcount;
-								break;
-							case 2:
-								val = modem->esc;
-								break;
-							case 3:
-								val = modem->cr;
-								break;
-							case 4:
-								val = modem->lf;
-								break;
-							case 5:
-								val = modem->bs;
-								break;
-							case 7:
-								val = modem->dial_wait;
-								break;
-							case 12:
-								val = modem->guard_time;
-								break;
-							default:
-								val = 0;
-								break;
-						}
-						safe_snprintf(respbuf, sizeof(respbuf), "%c%03lu%c%c%s"
-							,modem->lf, val, modem->cr, modem->lf, ok(modem));
-						return respbuf;
-					} else
-						return error(modem);
-					break;
-				case 'X':
-					modem->ext_results = val;
-					break;
-				case 'Z':
-					init(modem);
-					break;
-			}
-		} else { // string argument commands
-			switch(ch) {
-				case 'D':
-					if(sock != INVALID_SOCKET) {
-						dprintf("Can't dial: Already connected");
-						return error(modem);
-					}
-					if(*p == 'T' /* tone */|| *p == 'P' /* pulse */)
-						p++;
-					return dial(modem, p);
-			}
+				} else
+					return error(modem);
+				break;
+			case 'X':
+				modem->ext_results = val;
+				break;
+			case 'Z':
+				init(modem);
+				break;
+			default:
+				return error(modem);
 		}
 	}
 	return resp;
@@ -988,8 +1018,10 @@ void listen_thread(void* arg)
 			}
 			sock = newsock;
 			addr = newaddr;
-			if(!modem->offhook && !modem->online)
+			if(!modem->offhook && !modem->online) {
 				modem->ringing = true;
+				modem->ringcount = 0;
+			}
 		}
 	}
 }
@@ -1324,6 +1356,11 @@ int main(int argc, char** argv)
 					vdd_writestr(&wrslot, response(&modem, RING));
 					lastring = now;
 					modem.ringcount++;
+					if(modem.ringcount == 1 && modem.caller_id) {
+						char str[256];
+						SAFEPRINTF(str, "NMBR = %s", inet_addrtop(&addr, tmp, sizeof(tmp)));
+						vdd_writestr(&wrslot, verbal_response(&modem, str));
+					}
 					if(modem.auto_answer > 0 && modem.ringcount >= modem.auto_answer) {
 						vdd_writestr(&wrslot, answer(&modem));
 					}
-- 
GitLab