[layer23] DTMF support
[osmocom-bb.git] / src / host / layer23 / src / mobile / vty_interface.c
index e3d34fe..57046ac 100644 (file)
@@ -27,6 +27,7 @@
 
 #include <osmocore/utils.h>
 #include <osmocore/gsm48.h>
+#include <osmocore/talloc.h>
 
 #include <osmocom/bb/common/osmocom_data.h>
 #include <osmocom/bb/common/networks.h>
 #include <osmocom/bb/mobile/gps.h>
 #include <osmocom/vty/telnet_interface.h>
 
+void *l23_ctx;
+
 int mncc_call(struct osmocom_ms *ms, char *number);
 int mncc_hangup(struct osmocom_ms *ms);
 int mncc_answer(struct osmocom_ms *ms);
 int mncc_hold(struct osmocom_ms *ms);
 int mncc_retrieve(struct osmocom_ms *ms, int number);
+int mncc_dtmf(struct osmocom_ms *ms, char *dtmf);
 
 extern struct llist_head ms_list;
 extern struct llist_head active_connections;
@@ -83,6 +87,31 @@ static void print_vty(void *priv, const char *fmt, ...)
        }
 }
 
+int vty_check_number(struct vty *vty, const char *number)
+{
+       int i;
+
+       for (i = 0; i < strlen(number); i++) {
+               /* allow international notation with + */
+               if (i == 0 && number[i] == '+')
+                       continue;
+               if (!(number[i] >= '0' && number[i] <= '9')
+                && number[i] != '*'
+                && number[i] != '#'
+                && !(number[i] >= 'a' && number[i] <= 'c')) {
+                       vty_out(vty, "Invalid digit '%c' of number!%s",
+                               number[i], VTY_NEWLINE);
+                       return -EINVAL;
+               }
+       }
+       if (number[0] == '\0' || (number[0] == '+' && number[1] == '\0')) {
+               vty_out(vty, "Given number has no digits!%s", VTY_NEWLINE);
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
 int vty_reading = 0;
 
 static void vty_restart(struct vty *vty)
@@ -163,8 +192,11 @@ static void gsm_states_dump(struct osmocom_ms *ms, struct vty *vty)
        else
                vty_out(vty, " manual network selection: %s%s", 
                        plmn_m_state_names[ms->plmn.state], VTY_NEWLINE);
-       vty_out(vty, " cell selection: %s%s", 
-               cs_state_names[ms->cellsel.state], VTY_NEWLINE);
+       vty_out(vty, " cell selection: %s",
+               cs_state_names[ms->cellsel.state]);
+       if (ms->rrlayer.state == GSM48_RR_ST_IDLE && ms->cellsel.selected)
+               vty_out(vty, " (ARFCN %d)", ms->cellsel.sel_arfcn);
+       vty_out(vty, "%s", VTY_NEWLINE);
        vty_out(vty, " radio ressource layer: %s%s", 
                gsm48_rr_state_names[ms->rrlayer.state], VTY_NEWLINE);
        vty_out(vty, " mobility management layer: %s", 
@@ -629,39 +661,42 @@ DEFUN(call, call_cmd, "call MS_NAME (NUMBER|emergency|answer|hangup|hold)",
        "Hold current active call\n")
 {
        struct osmocom_ms *ms;
-       int i;
+       struct gsm_settings *set;
+       struct gsm_settings_abbrev *abbrev;
+       char *number;
 
        ms = get_ms(argv[0], vty);
        if (!ms)
                return CMD_WARNING;
+       set = &ms->settings;
 
-       if (!strcmp(argv[1], "emergency"))
-               mncc_call(ms, (char *)argv[1]);
-       else switch (argv[1][0]) {
-       case 'a':
+       if (set->ch_cap == GSM_CAP_SDCCH) {
+               vty_out(vty, "Basic call is not supported for SDCCH only "
+                       "mobile%s", VTY_NEWLINE);
+               return CMD_WARNING;
+       }
+
+       number = (char *)argv[1];
+       if (!strcmp(number, "emergency"))
+               mncc_call(ms, number);
+       else if (!strcmp(number, "answer"))
                mncc_answer(ms);
-               break;
-       case 'h':
-               if (argv[1][1] == 'a')
-                       mncc_hangup(ms);
-               else
-                       mncc_hold(ms);
-               break;
-       default:
-               for (i = 0; i < strlen(argv[1]); i++) {
-                       /* allow international notation with + */
-                       if (i == 0 && argv[1][i] == '+')
-                               continue;
-                       if (!(argv[1][i] >= '0' && argv[1][i] <= '9')
-                        && argv[1][i] != '*'
-                        && argv[1][i] != '#'
-                        && !(argv[1][i] >= 'a' && argv[1][i] <= 'c')) {
-                               vty_out(vty, "Invalid digit '%c'%s", argv[1][i],
+       else if (!strcmp(number, "hangup"))
+               mncc_hangup(ms);
+       else if (!strcmp(number, "hold"))
+               mncc_hold(ms);
+       else {
+               llist_for_each_entry(abbrev, &set->abbrev, list) {
+                       if (!strcmp(number, abbrev->abbrev)) {
+                               number = abbrev->number;
+                               vty_out(vty, "Dialing number '%s'%s", number,
                                        VTY_NEWLINE);
-                               return CMD_WARNING;
+                               break;
                        }
                }
-               mncc_call(ms, (char *)argv[1]);
+               if (vty_check_number(vty, number))
+                       return CMD_WARNING;
+               mncc_call(ms, number);
        }
 
        return CMD_SUCCESS;
@@ -682,6 +717,29 @@ DEFUN(call_retr, call_retr_cmd, "call MS_NAME retrieve [number]",
        return CMD_SUCCESS;
 }
 
+DEFUN(call_dtmf, call_dtmf_cmd, "call MS_NAME dtmf DIGITS",
+       "Make a call\nName of MS (see \"show ms\")\n"
+       "One or more DTMF digits to transmit")
+{
+       struct osmocom_ms *ms;
+       struct gsm_settings *set;
+
+       ms = get_ms(argv[0], vty);
+       if (!ms)
+               return CMD_WARNING;
+       set = &ms->settings;
+
+       if (!set->cc_dtmf) {
+               vty_out(vty, "DTMF not supported, please enable!%s",
+                       VTY_NEWLINE);
+               return CMD_WARNING;
+       }
+
+       mncc_dtmf(ms, (char *)argv[1]);
+
+       return CMD_SUCCESS;
+}
+
 DEFUN(network_show, network_show_cmd, "network show MS_NAME",
        "Network ...\nShow results of network search (again)\n"
        "Name of MS (see \"show ms\")")
@@ -817,6 +875,7 @@ static void config_write_ms_single(struct vty *vty, struct osmocom_ms *ms)
 {
        struct gsm_settings *set = &ms->settings;
        struct gsm_support *sup = &ms->support;
+       struct gsm_settings_abbrev *abbrev;
 
        vty_out(vty, "ms %s%s", ms->name, VTY_NEWLINE);
        switch(set->sim_type) {
@@ -883,6 +942,14 @@ static void config_write_ms_single(struct vty *vty, struct osmocom_ms *ms)
                else
                        vty_out(vty, " no codec half-speed%s", VTY_NEWLINE);
        }
+       if (llist_empty(&set->abbrev))
+               vty_out(vty, " no abbrev%s", VTY_NEWLINE);
+       else {
+               llist_for_each_entry(abbrev, &set->abbrev, list)
+                       vty_out(vty, " abbrev %s %s%s%s%s", abbrev->abbrev,
+                               abbrev->number, (abbrev->name[0]) ? " " : "",
+                               abbrev->name, VTY_NEWLINE);
+       }
        vty_out(vty, " support%s", VTY_NEWLINE);
        SUP_WRITE(sms_ptp, "sms");
        SUP_WRITE(a5_1, "a5/1");
@@ -1347,6 +1414,80 @@ DEFUN(cfg_no_codec_half, cfg_ms_no_codec_half_cmd, "no codec half-speed",
        return CMD_SUCCESS;
 }
 
+DEFUN(cfg_abbrev, cfg_ms_abbrev_cmd, "abbrev ABBREVIATION NUMBER [name]",
+       "Store given abbreviation number\n1-3 digits abbreviation\n"
+       "Number to store for the abbreviation "
+       "(Use digits '0123456789*#abc', and '+' to dial international)\n"
+       "Name of the abbreviation")
+{
+       struct osmocom_ms *ms = vty->index;
+       struct gsm_settings *set = &ms->settings;
+       struct gsm_settings_abbrev *abbrev;
+       int i;
+
+       llist_for_each_entry(abbrev, &set->abbrev, list) {
+               if (!strcmp(argv[0], abbrev->abbrev)) {
+                       vty_out(vty, "Given abbreviation '%s' already stored, "
+                               "delete first!%s", argv[0], VTY_NEWLINE);
+                       return CMD_WARNING;
+               }
+       }
+
+       if (strlen(argv[0]) >= sizeof(abbrev->abbrev)) {
+               vty_out(vty, "Given abbreviation too long%s", VTY_NEWLINE);
+               return CMD_WARNING;
+       }
+
+       for (i = 0; i < strlen(argv[0]); i++) {
+               if (argv[0][i] < '0' || argv[0][i] > '9') {
+                       vty_out(vty, "Given abbreviation must have digits "
+                               "0..9 only!%s", VTY_NEWLINE);
+                       return CMD_WARNING;
+               }
+       }
+
+       if (vty_check_number(vty, argv[1]))
+               return CMD_WARNING;
+
+       abbrev = talloc_zero(l23_ctx, struct gsm_settings_abbrev);
+       if (!abbrev) {
+               vty_out(vty, "No Memory!%s", VTY_NEWLINE);
+               return CMD_WARNING;
+       }
+       llist_add_tail(&abbrev->list, &set->abbrev);
+       strncpy(abbrev->abbrev, argv[0], sizeof(abbrev->abbrev) - 1);
+       strncpy(abbrev->number, argv[1], sizeof(abbrev->number) - 1);
+       if (argc >= 3)
+               strncpy(abbrev->name, argv[2], sizeof(abbrev->name) - 1);
+
+       return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_abbrev, cfg_ms_no_abbrev_cmd, "no abbrev [abbreviation]",
+       NO_STR "Remove given abbreviation number or all numbers\n"
+       "Abbreviation number to remove")
+{
+       struct osmocom_ms *ms = vty->index;
+       struct gsm_settings *set = &ms->settings;
+       struct gsm_settings_abbrev *abbrev, *abbrev2;
+       uint8_t deleted = 0;
+
+       llist_for_each_entry_safe(abbrev, abbrev2, &set->abbrev, list) {
+               if (argc < 1 || !strcmp(argv[0], abbrev->abbrev)) {
+                       llist_del(&abbrev->list);
+                       deleted = 1;
+               }
+       }
+
+       if (argc >= 1 && !deleted) {
+               vty_out(vty, "Given abbreviation '%s' not found!%s",
+                       argv[0], VTY_NEWLINE);
+               return CMD_WARNING;
+       }
+
+       return CMD_SUCCESS;
+}
+
 static int config_write_dummy(struct vty *vty)
 {
        return CMD_SUCCESS;
@@ -1397,6 +1538,30 @@ DEFUN(cfg, cfg_cmd, "no " cmd, NO_STR "Disable " desc " support") \
        return CMD_SUCCESS; \
 }
 
+#define SET_EN(cfg, cfg_cmd, item, cmd, desc, restart) \
+DEFUN(cfg, cfg_cmd, cmd, "Enable " desc "support") \
+{ \
+       struct osmocom_ms *ms = vty->index; \
+       struct gsm_settings *set = &ms->settings; \
+       if (restart) \
+               vty_restart(vty); \
+       set->item = 1; \
+       return CMD_SUCCESS; \
+}
+
+#define SET_DI(cfg, cfg_cmd, item, cmd, desc, restart) \
+DEFUN(cfg, cfg_cmd, "no " cmd, NO_STR "Disable " desc " support") \
+{ \
+       struct osmocom_ms *ms = vty->index; \
+       struct gsm_settings *set = &ms->settings; \
+       if (restart) \
+               vty_restart(vty); \
+       set->item = 0; \
+       return CMD_SUCCESS; \
+}
+
+SET_EN(cfg_ms_sup_dtmf, cfg_ms_sup_dtmf_cmd, cc_dtmf, "dtmf", "DTMF", 0);
+SET_DI(cfg_ms_sup_no_dtmf, cfg_ms_sup_no_dtmf_cmd, cc_dtmf, "dtmf", "DTMF", 0);
 SUP_EN(cfg_ms_sup_sms, cfg_ms_sup_sms_cmd, sms_ptp, "sms", "SMS", 0);
 SUP_DI(cfg_ms_sup_no_sms, cfg_ms_sup_no_sms_cmd, sms_ptp, "sms", "SMS", 0);
 SUP_EN(cfg_ms_sup_a5_1, cfg_ms_sup_a5_1_cmd, a5_1, "a5/1", "A5/1", 0);
@@ -1491,6 +1656,10 @@ DEFUN(cfg_ms_sup_ch_cap, cfg_ms_sup_ch_cap_cmd, "channel-capability "
                return CMD_WARNING;
        }
 
+       if (ch_cap != set->ch_cap
+        && (ch_cap == GSM_CAP_SDCCH || set->ch_cap == GSM_CAP_SDCCH))
+               vty_restart(vty);
+
        set->ch_cap = ch_cap;
 
        return CMD_SUCCESS;
@@ -1800,6 +1969,7 @@ int ms_vty_init(void)
        install_element(ENABLE_NODE, &network_select_cmd);
        install_element(ENABLE_NODE, &call_cmd);
        install_element(ENABLE_NODE, &call_retr_cmd);
+       install_element(ENABLE_NODE, &call_dtmf_cmd);
 
        install_element(CONFIG_NODE, &cfg_gps_device_cmd);
        install_element(CONFIG_NODE, &cfg_gps_baud_cmd);
@@ -1838,12 +2008,16 @@ int ms_vty_init(void)
        install_element(MS_NODE, &cfg_ms_codec_half_cmd);
        install_element(MS_NODE, &cfg_ms_codec_half_pref_cmd);
        install_element(MS_NODE, &cfg_ms_no_codec_half_cmd);
+       install_element(MS_NODE, &cfg_ms_abbrev_cmd);
+       install_element(MS_NODE, &cfg_ms_no_abbrev_cmd);
        install_element(MS_NODE, &cfg_ms_testsim_cmd);
        install_element(MS_NODE, &cfg_ms_support_cmd);
        install_node(&support_node, config_write_dummy);
        install_default(SUPPORT_NODE);
        install_element(SUPPORT_NODE, &ournode_exit_cmd);
        install_element(SUPPORT_NODE, &ournode_end_cmd);
+       install_element(SUPPORT_NODE, &cfg_ms_sup_dtmf_cmd);
+       install_element(SUPPORT_NODE, &cfg_ms_sup_no_dtmf_cmd);
        install_element(SUPPORT_NODE, &cfg_ms_sup_sms_cmd);
        install_element(SUPPORT_NODE, &cfg_ms_sup_no_sms_cmd);
        install_element(SUPPORT_NODE, &cfg_ms_sup_a5_1_cmd);