diff --git a/src/mod/endpoints/mod_gsmopen/gsmopen.h b/src/mod/endpoints/mod_gsmopen/gsmopen.h index 5aadb8a08b..ffbae38514 100644 --- a/src/mod/endpoints/mod_gsmopen/gsmopen.h +++ b/src/mod/endpoints/mod_gsmopen/gsmopen.h @@ -197,6 +197,12 @@ typedef enum { #define GSMOPEN_MAX_INTERFACES 64 +#define USSD_ENCODING_AUTO 0 +#define USSD_ENCODING_PLAIN 1 +#define USSD_ENCODING_HEX_7BIT 2 +#define USSD_ENCODING_HEX_8BIT 3 +#define USSD_ENCODING_UCS2 4 + #ifndef WIN32 struct GSMopenHandles { int currentuserhandle; @@ -405,6 +411,14 @@ struct private_object { int sms_cnmi_not_supported; int sms_pdu_not_supported; + int ussd_request_encoding; + int ussd_response_encoding; + int ussd_request_hex; + int ussd_received; + int ussd_status; + char ussd_message[1024]; + char ussd_dcs[256]; + struct timeval call_incoming_time; switch_mutex_t *controldev_lock; @@ -561,3 +575,5 @@ int serial_audio_shutdown(private_t *tech_pvt); #ifndef WIN32 void find_ttyusb_devices(private_t *tech_pvt, const char *dirname); #endif// WIN32 +int gsmopen_ussd(private_t *tech_pvt, char *ussd, int waittime); +int ussd_incoming(private_t *tech_pvt); diff --git a/src/mod/endpoints/mod_gsmopen/gsmopen_protocol.cpp b/src/mod/endpoints/mod_gsmopen/gsmopen_protocol.cpp index c490d36f37..12b6903e94 100644 --- a/src/mod/endpoints/mod_gsmopen/gsmopen_protocol.cpp +++ b/src/mod/endpoints/mod_gsmopen/gsmopen_protocol.cpp @@ -1392,6 +1392,69 @@ int gsmopen_serial_read_AT(private_t *tech_pvt, int look_for_ack, int timeout_us } } + if ((strncmp(tech_pvt->line_array.result[i], "+CUSD:", 6) == 0)) { + if (option_debug > 1) + DEBUGA_GSMOPEN("|%s| USSD received\n", GSMOPEN_P_LOG, tech_pvt->line_array.result[i]); + int res = 0, status = 0; + unsigned int dcs = 0; + char ussd_msg[1024]; + memset(tech_pvt->ussd_message, '\0', sizeof(tech_pvt->ussd_message)); + memset(tech_pvt->ussd_dcs, '\0', sizeof(tech_pvt->ussd_dcs)); + res = sscanf(&tech_pvt->line_array.result[i][6], "%d,\"%1023[0-9A-F]\",%d", &status, ussd_msg, &dcs); + if (res == 1) { + NOTICA("received +CUSD with status %d\n", GSMOPEN_P_LOG, status); + tech_pvt->ussd_received = 1; + tech_pvt->ussd_status = status; + } else if (res == 3) { + tech_pvt->ussd_received = 1; + tech_pvt->ussd_status = status; + + //identifying dcs alphabet according to GSM 03.38 Cell Broadcast Data Coding Scheme + //CBDataCodingScheme should be used here, but it appears to be buggy (ucs2 messages are not recognized) + int alphabet = DCS_RESERVED_ALPHABET; + if (dcs == 0x11) { + alphabet = DCS_SIXTEEN_BIT_ALPHABET; + } else if ((dcs & 0xF0) <= 0x30){ + alphabet = DCS_DEFAULT_ALPHABET; + } else if ((dcs & 0xC0) == 0x40 || (dcs & 0xF0) == 0x90) { + alphabet = dcs & (3 << 2); + }; + + if ( (tech_pvt->ussd_response_encoding == USSD_ENCODING_AUTO && alphabet == DCS_DEFAULT_ALPHABET) + || tech_pvt->ussd_response_encoding == USSD_ENCODING_HEX_7BIT ) { + SMSDecoder d(ussd_msg); + d.markSeptet(); + string ussd_dec = gsmToLatin1(d.getString(strlen(ussd_msg) / 2 * 8 / 7)); + iso_8859_1_to_utf8(tech_pvt, (char *) ussd_dec.c_str(), tech_pvt->ussd_message, sizeof(tech_pvt->ussd_message)); + strcpy(tech_pvt->ussd_dcs, "default alphabet"); + } else if ( (tech_pvt->ussd_response_encoding == USSD_ENCODING_AUTO && alphabet == DCS_SIXTEEN_BIT_ALPHABET) + || tech_pvt->ussd_response_encoding == USSD_ENCODING_UCS2 ) { + ucs2_to_utf8(tech_pvt, ussd_msg, tech_pvt->ussd_message, sizeof(tech_pvt->ussd_message)); + strcpy(tech_pvt->ussd_dcs, "16-bit alphabet"); + } else if ( (tech_pvt->ussd_response_encoding == USSD_ENCODING_AUTO && alphabet == DCS_EIGHT_BIT_ALPHABET) + || tech_pvt->ussd_response_encoding == USSD_ENCODING_HEX_8BIT ) { + char ussd_dec[1024]; + memset(ussd_dec, '\0', sizeof(ussd_dec)); + hexToBuf(ussd_msg, (unsigned char*)ussd_dec); + iso_8859_1_to_utf8(tech_pvt, (char *) ussd_dec, tech_pvt->ussd_message, sizeof(tech_pvt->ussd_message)); + strcpy(tech_pvt->ussd_dcs, "8-bit alphabet"); + } else if ( tech_pvt->ussd_response_encoding == USSD_ENCODING_PLAIN ) { + string ussd_dec = gsmToLatin1(ussd_msg); + iso_8859_1_to_utf8(tech_pvt, (char *) ussd_dec.c_str(), tech_pvt->ussd_message, sizeof(tech_pvt->ussd_message)); + strcpy(tech_pvt->ussd_dcs, "default alphabet"); + } else { + ERRORA("USSD data coding scheme not supported=%d\n", GSMOPEN_P_LOG, dcs); + } + + NOTICA("USSD received: status=%d, message='%s', dcs='%d'\n", + GSMOPEN_P_LOG, tech_pvt->ussd_status, tech_pvt->ussd_message, dcs); + + ussd_incoming(tech_pvt); + } else { + ERRORA ("res=%d, +CUSD command has wrong format: %s\n", + GSMOPEN_P_LOG, res, tech_pvt->line_array.result[i]); + } + } if ((strncmp(tech_pvt->line_array.result[i], "*ECAV", 5) == 0) || (strncmp(tech_pvt->line_array.result[i], "*ECAM", 5) == 0)) { /* sony-ericsson call processing unsolicited messages */ int res, ccid, ccstatus, calltype, processid, exitcause, number, type; @@ -2858,6 +2921,57 @@ int gsmopen_sendsms(private_t *tech_pvt, char *dest, char *text) return RESULT_SUCCESS; } +int gsmopen_ussd(private_t *tech_pvt, char *ussd, int waittime) +{ + int res = 0; + DEBUGA_GSMOPEN("gsmopen_ussd: %s\n", GSMOPEN_P_LOG, ussd); + if (tech_pvt->controldevprotocol == PROTOCOL_AT) { + char at_command[1024]; + + string ussd_enc = latin1ToGsm(ussd); + SMSEncoder e; + e.markSeptet(); + e.setString(ussd_enc); + string ussd_hex = e.getHexString(); + + memset(at_command, '\0', sizeof(at_command)); + tech_pvt->ussd_received = 0; + if (tech_pvt->ussd_request_encoding == USSD_ENCODING_PLAIN + ||tech_pvt->ussd_request_encoding == USSD_ENCODING_AUTO) { + snprintf(at_command, sizeof(at_command), "AT+CUSD=1,\"%s\",15", ussd_enc.c_str()); + res = gsmopen_serial_write_AT_ack(tech_pvt, at_command); + if (res && tech_pvt->ussd_request_encoding == USSD_ENCODING_AUTO) { + DEBUGA_GSMOPEN("Plain request failed, trying HEX7 encoding...\n", GSMOPEN_P_LOG); + snprintf(at_command, sizeof(at_command), "AT+CUSD=1,\"%s\",15", ussd_hex.c_str()); + res = gsmopen_serial_write_AT_ack(tech_pvt, at_command); + if (res == 0) { + DEBUGA_GSMOPEN("HEX 7-bit request encoding will be used from now on\n", GSMOPEN_P_LOG); + tech_pvt->ussd_request_encoding = USSD_ENCODING_HEX_7BIT; + } + } + } else if (tech_pvt->ussd_request_encoding == USSD_ENCODING_HEX_7BIT) { + snprintf(at_command, sizeof(at_command), "AT+CUSD=1,\"%s\",15", ussd_hex.c_str()); + res = gsmopen_serial_write_AT_ack(tech_pvt, at_command); + } else if (tech_pvt->ussd_request_encoding == USSD_ENCODING_HEX_8BIT) { + string ussd_h8 = bufToHex((const unsigned char*)ussd_enc.c_str(), ussd_enc.length()); + snprintf(at_command, sizeof(at_command), "AT+CUSD=1,\"%s\",15", ussd_h8.c_str()); + res = gsmopen_serial_write_AT_ack(tech_pvt, at_command); + } else if (tech_pvt->ussd_request_encoding == USSD_ENCODING_UCS2) { + char ussd_ucs2[1000]; + memset(ussd_ucs2, '\0', sizeof(ussd_ucs2)); + utf8_to_ucs2(tech_pvt, ussd, strlen(ussd), ussd_ucs2, sizeof(ussd_ucs2)); + snprintf(at_command, sizeof(at_command), "AT+CUSD=1,\"%s\",15", ussd_ucs2); + res = gsmopen_serial_write_AT_ack(tech_pvt, at_command); + } + if (res) { + return res; + } + if (waittime > 0) + res = gsmopen_serial_read_AT(tech_pvt, 1, 0, waittime, "+CUSD", 1); + } + return res; +} + /************************************************/ /* LUIGI RIZZO's magic */ diff --git a/src/mod/endpoints/mod_gsmopen/mod_gsmopen.cpp b/src/mod/endpoints/mod_gsmopen/mod_gsmopen.cpp index 881c1d8c87..a92303351a 100644 --- a/src/mod/endpoints/mod_gsmopen/mod_gsmopen.cpp +++ b/src/mod/endpoints/mod_gsmopen/mod_gsmopen.cpp @@ -49,6 +49,8 @@ SWITCH_STANDARD_API(sendsms_function); #define SENDSMS_SYNTAX "gsmopen_sendsms interface_name destination_number SMS_text" SWITCH_STANDARD_API(gsmopen_dump_function); #define GSMOPEN_DUMP_SYNTAX "gsmopen_dump " +SWITCH_STANDARD_API(gsmopen_ussd_function); +#define USSD_SYNTAX "gsmopen_ussd [nowait]" #define FULL_RELOAD 0 #define SOFT_RELOAD 1 @@ -1231,6 +1233,8 @@ static switch_status_t load_config(int reload_type) const char *portaudiopindex = "1"; const char *speexecho = "1"; const char *speexpreprocess = "1"; + const char *ussd_request_encoding = "auto"; + const char *ussd_response_encoding = "auto"; uint32_t interface_id = 0; int controldevice_speed = 115200; //FIXME TODO @@ -1403,6 +1407,10 @@ static switch_status_t load_config(int reload_type) imei = val; } else if (!strcasecmp(var, "gsmopen_serial_sync_period")) { gsmopen_serial_sync_period = val; + } else if (!strcasecmp(var, "ussd_request_encoding")) { + ussd_request_encoding = val; + } else if (!strcasecmp(var, "ussd_response_encoding")) { + ussd_response_encoding = val; } } @@ -1554,6 +1562,18 @@ static switch_status_t load_config(int reload_type) globals.GSMOPEN_INTERFACES[interface_id].no_sound = atoi(no_sound); globals.GSMOPEN_INTERFACES[interface_id].gsmopen_serial_sync_period = atoi(gsmopen_serial_sync_period); + globals.GSMOPEN_INTERFACES[interface_id].ussd_request_encoding = + strcasecmp(ussd_request_encoding, "plain") == 0 ? USSD_ENCODING_PLAIN : + strcasecmp(ussd_request_encoding, "hex7") == 0 ? USSD_ENCODING_HEX_7BIT : + strcasecmp(ussd_request_encoding, "hex8") == 0 ? USSD_ENCODING_HEX_8BIT : + strcasecmp(ussd_request_encoding, "ucs2") == 0 ? USSD_ENCODING_UCS2 : USSD_ENCODING_AUTO; + + globals.GSMOPEN_INTERFACES[interface_id].ussd_response_encoding = + strcasecmp(ussd_response_encoding, "plain") == 0 ? USSD_ENCODING_PLAIN : + strcasecmp(ussd_response_encoding, "hex7") == 0 ? USSD_ENCODING_HEX_7BIT : + strcasecmp(ussd_response_encoding, "hex8") == 0 ? USSD_ENCODING_HEX_8BIT : + strcasecmp(ussd_response_encoding, "ucs2") == 0 ? USSD_ENCODING_UCS2 : USSD_ENCODING_AUTO; + globals.GSMOPEN_INTERFACES[interface_id].controldevice_speed = controldevice_speed; //FIXME globals.GSMOPEN_INTERFACES[interface_id].controldevprotocol = controldevprotocol; //FIXME globals.GSMOPEN_INTERFACES[interface_id].running = running; //FIXME @@ -1781,7 +1801,12 @@ static switch_status_t chat_send(switch_event_t *message_event) from ? from : "NULL"); goto end; } else { - gsmopen_sendsms(tech_pvt, (char *) to, (char *) body); + if (strcasecmp(to, "ussd") == 0) { + gsmopen_ussd(tech_pvt, (char *) body, 0); + } else { + gsmopen_sendsms(tech_pvt, (char *) to, (char *) body); + } + } } end: @@ -1853,6 +1878,8 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_gsmopen_load) SWITCH_ADD_API(commands_api_interface, "gsmopen_dump", "gsmopen_dump interface", gsmopen_dump_function, GSMOPEN_DUMP_SYNTAX); SWITCH_ADD_API(commands_api_interface, "gsmopen_sendsms", "gsmopen_sendsms interface destination_number SMS_text", sendsms_function, SENDSMS_SYNTAX); + SWITCH_ADD_API(commands_api_interface, "gsmopen_ussd", "gsmopen_ussd interface ussd_code wait_seconds", gsmopen_ussd_function, + SENDSMS_SYNTAX); SWITCH_ADD_CHAT(chat_interface, GSMOPEN_CHAT_PROTO, chat_send); /* indicate that the module should continue to be loaded */ @@ -2660,6 +2687,79 @@ SWITCH_STANDARD_API(sendsms_function) return SWITCH_STATUS_SUCCESS; } +SWITCH_STANDARD_API(gsmopen_ussd_function) +{ + char *mycmd = NULL, *argv[3] = { 0 }; + int argc = 0; + int waittime = 20; + private_t *tech_pvt = NULL; + + if (!zstr(cmd) && (mycmd = strdup(cmd))) { + argc = switch_separate_string(mycmd, ' ', argv, (sizeof(argv) / sizeof(argv[0]))); + } + + if (!argc) { + stream->write_function(stream, "ERROR, usage: %s", USSD_SYNTAX); + goto end; + } + + if (argc < 2) { + stream->write_function(stream, "ERROR, usage: %s", USSD_SYNTAX); + goto end; + } + + if (argc >= 3 && strcasecmp(argv[2], "nowait")==0) { + waittime = 0; + } + + if (argv[0]) { + int i; + int found = 0; + + for (i = 0; !found && i < GSMOPEN_MAX_INTERFACES; i++) { + /* we've been asked for a normal interface name, or we have not found idle interfaces to serve as the "ANY" interface */ + if (strlen(globals.GSMOPEN_INTERFACES[i].name) + && (strncmp(globals.GSMOPEN_INTERFACES[i].name, argv[0], strlen(argv[0])) == 0)) { + tech_pvt = &globals.GSMOPEN_INTERFACES[i]; + NOTICA("Trying to send USSD request: interface=%s, ussd=%s\n", GSMOPEN_P_LOG, argv[0], argv[1]); + found = 1; + break; + } + + } + if (!found) { + stream->write_function(stream, "ERROR: A GSMopen interface with name='%s' was not found\n", argv[0]); + switch_safe_free(mycmd); + + return SWITCH_STATUS_SUCCESS; + } else { + int err = gsmopen_ussd(tech_pvt, (char *) argv[1], waittime); + if (err == AT_ERROR) { + stream->write_function(stream, "ERROR: command failed\n"); + } else if (!waittime) { + stream->write_function(stream, "USSD request has been sent\n"); + } else if (err) { + stream->write_function(stream, "ERROR: USSD request timeout (%d)\n", err); + } else if (!tech_pvt->ussd_received) { + stream->write_function(stream, "ERROR: no response received\n"); + } else { + stream->write_function(stream, "Status: %d%s\n", tech_pvt->ussd_status, + tech_pvt->ussd_status == 0 ? " - completed" : + tech_pvt->ussd_status == 1 ? " - action required" : + tech_pvt->ussd_status == 2 ? " - error" : ""); + if (strlen(tech_pvt->ussd_message) != 0) + stream->write_function(stream, "Text: %s\n", tech_pvt->ussd_message); + } + } + } else { + stream->write_function(stream, "ERROR, usage: %s", USSD_SYNTAX); + } + end: + switch_safe_free(mycmd); + + return SWITCH_STATUS_SUCCESS; +} + int dump_event_full(private_t *tech_pvt, int is_alarm, int alarm_code, const char *alarm_message) { switch_event_t *event; @@ -2807,7 +2907,44 @@ int sms_incoming(private_t *tech_pvt) return 0; } +int ussd_incoming(private_t *tech_pvt) +{ + switch_event_t *event; + switch_core_session_t *session = NULL; + int event_sent_to_esl = 0; + DEBUGA_GSMOPEN("received USSD on interface %s: TEXT=%s|\n", GSMOPEN_P_LOG, tech_pvt->name, tech_pvt->ussd_message); + +/* mod_sms begin */ + if (switch_event_create(&event, SWITCH_EVENT_MESSAGE) == SWITCH_STATUS_SUCCESS) { + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "proto", GSMOPEN_CHAT_PROTO); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "login", tech_pvt->name); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "from", "ussd"); + //switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "date", tech_pvt->sms_date); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "datacodingscheme", tech_pvt->ussd_dcs); + //switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "servicecentreaddress", tech_pvt->sms_servicecentreaddress); + //switch_event_add_header(event, SWITCH_STACK_BOTTOM, "messagetype", "%d", tech_pvt->sms_messagetype); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "subject", "USSD MESSAGE"); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "to", tech_pvt->name); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "hint", tech_pvt->name); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "to_proto", GSMOPEN_CHAT_PROTO); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "from_user", "ussd"); + //switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "from_host", "from_host"); + //switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "from_full", "from_full"); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "to_user", tech_pvt->name); + //switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "to_host", "to_host"); + switch_event_add_header(event, SWITCH_STACK_BOTTOM, "ussd_status", "%d", tech_pvt->ussd_status); + switch_event_add_body(event, "%s\n", tech_pvt->ussd_message); + //switch_core_chat_send("GLOBAL", event); /* mod_sms */ + switch_core_chat_send("GLOBAL", event); /* mod_sms */ + } else { + + ERRORA("cannot create event on interface %s. WHY?????\n", GSMOPEN_P_LOG, tech_pvt->name); + } + /* mod_sms end */ + + return 0; +} #ifndef WIN32 #define PATH_MAX_GIOVA PATH_MAX