El rincón de Odicha

Sistemas y cacharros varios

chan_sebi (2) – Trasteando con el codigo

Posted by odicha en 30 junio 2009

Hola.

Pues voy a empezar a desnudar un poco chan_sebi para los que quieran trapichear con el fuente que sepan como está la cosa actualmente
Primero empezaremos con el arranque del módulo, que tiene la función mas “sucia” del código…

static int load_module(void)
{
 if(!modem_drv_config()){
  ast_log(LOG_ERROR, “No devices found. Not loading module.\n”);
  return AST_MODULE_LOAD_DECLINE;
 }

 if (!modem_load_config()) {
  ast_log(LOG_ERROR, “Unable to read config file %s. Not loading module.\n”, MODEM_CONFIG);
  return AST_MODULE_LOAD_DECLINE;
 }

 ast_cli_register_multiple(modem_cli, sizeof(modem_cli) / sizeof(modem_cli[0]));
 ast_register_application(app_sebistatus, modem_status_exec, sebistatus_synopsis, sebistatus_desc);
 ast_register_application(app_sebisendsms, modem_sendsms_exec, sebisendsms_synopsis, sebisendsms_desc);

 /* Make sure we can register our channel type */
 if (ast_channel_register(&modem_tech)) {
  ast_log(LOG_ERROR, “Unable to register channel class %s\n”, “Sebi”);
  return AST_MODULE_LOAD_FAILURE;
 }

 return 0;
}

Primero que nada llamamos a modem_drv_config. Esta funcion busca los modems que están conectados en el sistema y escribe sebi_devices.conf. Es bastante “cochina” pero funciona. Seguidamente llamamos a modem_load_config, que lee sebi.conf y sebi_devices.conf y crea las estructuras de los modems si se cumplen las condiciones (más adelante veremos con detalle las funciones). Si tenemos dispositivos que pueden arrancarse los registramos en asterisk. Ahora entramos a detallar las funciones. Comenzamos por modem_drv_config

static int modem_drv_config(void)
{

 char dirname[100],
  subdirname[100],
  ttydirname[100],
  filename[100],
  conf_path[200],
  value[5],
  fvalue[5],
  dataport[15],
  imei[16],
    *p=NULL;
   FILE *f, *g, *h, *conf_file;
   int conf_socket, res;
   DIR *d, *subd, *ttyd;
   struct dirent *fn;
   struct dirent *subfn;
   struct dirent *ttyfn;

   snprintf (conf_path, sizeof(conf_path), “%s/%s”, ast_config_AST_CONFIG_DIR, DEVICE_CONFIG);
   conf_file = fopen(conf_path, “w”);
  
   snprintf (dirname, sizeof(dirname), “/sys/bus/usb/devices/”);
   d = opendir (dirname);
   if (!d){
    fclose (conf_file);     
    return 0;
   }

 while ((fn = readdir (d)) != NULL){

 if ((!(strstr(fn->d_name, “:”))) && (!(strstr(fn->d_name, “.”))) && (!(strstr(fn->d_name, “usb”)))){
  snprintf (subdirname, sizeof(subdirname), “%s%s/”, dirname, fn->d_name);
  snprintf (filename, sizeof(filename), “%s%s/idVendor”, dirname, fn->d_name);
  f = fopen(filename, “r”);
  if (f){
   if (strstr(fgets(value, 5, f), “12d1”)){ // vendor Huawei FIXME Create ID Vendor / product table
    snprintf (filename, sizeof(filename), “%s%s/idProduct”, dirname, fn->d_name);
    g = fopen(filename, “r”);
    if (g){
     if (strstr(fgets(fvalue, 5, g), “1001”)){  // 1001 device – Search Interfaces 01 & 02
      fprintf (conf_file, “[%s]\n”, fn->d_name);
      ast_log(LOG_DEBUG, “device usb 12d1/1001 bus usb %s.\n”, fn->d_name);
      subd = opendir (subdirname);
      while ((subfn = readdir(subd)) != NULL){
       if (strstr(subfn->d_name, “:”)){
        snprintf (filename, sizeof(filename), “%s%s/%s/bInterfaceNumber”, dirname, fn->d_name, subfn->d_name);
        h = fopen(filename, “r”);
        if (h > 0){
         if (strstr(fgets(value, 3, h), “01”)){
          snprintf (ttydirname, sizeof(ttydirname), “%s%s/”, subdirname, subfn->d_name);
          ttyd = opendir (ttydirname);
          while ((ttyfn = readdir (ttyd)) != NULL){
           if (strstr(ttyfn->d_name, “ttyUSB”)){
            fprintf (conf_file, “voice = /dev/%s\n”, ttyfn->d_name);
            ast_log(LOG_DEBUG, “device usb 12d1/1001 (voice) port %s.\n”, ttyfn->d_name);
           }
          }
          if (ttyd) closedir (ttyd);
         } else if (strstr(value, “02”)){
          snprintf (ttydirname, sizeof(ttydirname), “%s%s/”, subdirname, subfn->d_name);
          ttyd = opendir (ttydirname);
          while ((ttyfn = readdir (ttyd)) != NULL){
           if (strstr(ttyfn->d_name, “ttyUSB”)){
            fprintf (conf_file, “data = /dev/%s\n”, ttyfn->d_name);
            ast_log(LOG_DEBUG, “device usb 12d1/1001 (data) port %s.\n”, ttyfn->d_name);
            snprintf (dataport, sizeof(dataport),”/dev/%s\n”, ttyfn->d_name);
            snprintf (temp_port, sizeof(temp_port),”/dev/%s”, ttyfn->d_name);
            res = modem_get_imei();
            ast_log(LOG_DEBUG, “device usb 12d1/1001 (imei) %s.\n”, temp_imei);
            fprintf (conf_file, “imei = %s\n”, temp_imei);
           }
          }
          if (ttyd) closedir (ttyd);
         }
        }
        if (h) fclose(h);
       }
      }
      if (subd) closedir(subd);
     } else if (strstr(fvalue, “1003”)){  // 1003 device – Search Interface 01
      fprintf (conf_file, “[%s]\n”, fn->d_name);
      ast_log(LOG_DEBUG, “device usb 12d1/1003 bus usb %s.\n”, fn->d_name);
      subd = opendir (subdirname);
      while ((subfn = readdir(subd)) != NULL){
       if (strstr(subfn->d_name, “:”)){
        snprintf (filename, sizeof(filename), “%s%s/%s/bInterfaceNumber”, dirname, fn->d_name, subfn->d_name);
        h = fopen(filename, “r”);
        if (h > 0){
         if (strstr(fgets(value, 3, h), “01”)){
          snprintf (ttydirname, sizeof(ttydirname), “%s%s/”, subdirname, subfn->d_name);
          ttyd = opendir (ttydirname);
          while ((ttyfn = readdir (ttyd)) != NULL){
           if (strstr(ttyfn->d_name, “ttyUSB”)){
            fprintf (conf_file, “data = /dev/%s\n”, ttyfn->d_name);
            ast_log(LOG_DEBUG, “device usb 12d1/1003 (data) port %s.\n”, ttyfn->d_name);
            snprintf (dataport, sizeof(dataport),”/dev/%s\n”, ttyfn->d_name);
            snprintf (temp_port, sizeof(temp_port),”/dev/%s”, ttyfn->d_name);
            res = modem_get_imei();
            ast_log(LOG_DEBUG, “device usb 12d1/1003 (imei) %s.\n”, temp_imei);
            fprintf (conf_file, “imei = %s\n”, temp_imei);
           }
          }
          if (ttyd) closedir (ttyd);
         }
        }
        if (h) fclose(h);
       }
      }
      if (subd) closedir(subd);
     }
     if (g) fclose(g);
    }
    if (f) fclose(f);
   }
  }
 }
 }
 if (d)
  closedir (d);
 if (conf_file) fclose(conf_file);
 return 1;
}

Pues lo que hace es bien simple aunque la funcion se vea bastante enrevesada. Leemos la carpeta /sys/bus/usb/devices buscando dispositivos cuyo vendor_id sea 12d1 (huawei). Si encuentra un dispositivo cuyo ven_id es 12d1 busca que su prod_id sea 1001 o 1003 que son todos los modems de la familia. Si es un 1001 buscamos los subids 01 (puerto de voz) y 02 (datos). Ademas leemos el IMEI llamando a la funcion modem_get_imei. Anotamos el bus_id de usb y todos estos datos en sebi_devices.conf. Nos queda algo asi como:

[2-1] => El bus_id de usb
data= ttyUSB2
voice = ttyUSB1
imei = 35xxxxxxxxxxxxx

Recursivamente exploramos toda la estructura usb y cerramos los ficheros y directorios que hemos abierto. Si hemos encontrado problemas al ejecutarlo devolvemos cero y se detiene la carga del modulo. En caso contrario la carga del modulo continua.
Repasemos modem_get_imei.

static int modem_get_imei()
{
 struct termios options;
 int dataport, s;
 char monitor = 1;
 //char temp_port[15];
 char buf[256];
 int state = 0;
 int retries = 0;
 
 
 temp_buf[0] = 0x00;
 snprintf (temp_imei, sizeof(temp_imei), “ERROR”);

 dataport= open( temp_port, O_RDWR | O_NOCTTY );

 if (dataport <0)
  return -1;
 options.c_cflag = B460800 | CRTSCTS | CS8 | CLOCAL | CREAD | O_NDELAY;
 options.c_iflag = IGNPAR;
 options.c_oflag = 0;
 options.c_lflag = 0;      
 options.c_cc[VMIN]=1;
 options.c_cc[VTIME]=0;
 tcflush(dataport, TCIOFLUSH);
 tcsetattr(dataport,TCSANOW,&options);

 if (!port_write(dataport, “ATE0\r”))
  monitor = 0;

 while (monitor) {

  s = port_read(dataport, buf, 1);
  if ((s > 0) && (buf[0] != 0x0) && (buf[0] != ‘\r’) && (buf[0] != ‘^’)) {
   
   switch (state) {
   case 0:
    if (strstr(buf, “OK”)) {
     port_write(dataport, “AT+CGSN\r”);
     state++;
    } else { 
     if (!port_write(dataport, “ATE0\r”)) monitor = 0;
    }
    break;

   case 1: // Get IMEI
    if  ((!(strstr(buf, “OK”))) && (!(strstr(buf, “ERROR”)))) {
     snprintf (temp_imei, sizeof(temp_imei), “%s”, buf);
     monitor = 0;
     close (dataport);
     return 1;
    } else if (strstr(buf, “ERROR”)){
     monitor = 0;
     close (dataport);
     return 0;
    }
    break;
   }
  } 

  if (s == 0) { // Timeouts
   if (retries > 2) {
    close (dataport);
    return 0;
   }
   if (state == 0){
    if (!port_write(dataport, “ATE0\r”)) monitor = 0;
    retries++;
   } else {
    if (!port_write(dataport, “AT+CGSN\r”)) monitor = 0;
    retries++;
   }
  }
 }
 close (dataport);
 return -1;

}

Conecta al puerto de datos del modem y lee el valor del imei con el comando AT correspondiente. No tiene secretos…

 

A continuacion cargamos modem_load_config.

static int modem_load_config(void)
{
 struct ast_config *cfg = NULL;
 struct ast_config *devices_cfg = NULL;
 char *cat = NULL;
 char *device_cat = NULL;
 struct ast_variable *var;
 const char *dataport, *voiceport, *imei, *device_imei,
  *pin, *csca, *sms_enabled, *voice_enabled, *context,
  *group, *volume, *nocallsetup, *language;
 struct modem_pvt *pvt;
 char buf[256];
 uint16_t vs;
 char *dr;
 int res, res2, s;

 devices_cfg = ast_config_load(DEVICE_CONFIG);
 if (!devices_cfg)
  return 0;

 cfg = ast_config_load(MODEM_CONFIG);
 if (!cfg)
  return 0;

 device_cat = ast_category_browse(devices_cfg, NULL);
 cat = ast_category_browse(cfg, NULL);
 while (device_cat) {
  ast_log(LOG_DEBUG, “Loading device %s.\n”, device_cat);
  dataport = ast_variable_retrieve(devices_cfg, device_cat, “data”);
  voiceport = ast_variable_retrieve(devices_cfg, device_cat, “voice”);
  device_imei = ast_variable_retrieve(devices_cfg, device_cat, “imei”);

  while (cat) {
   imei = ast_variable_retrieve(cfg, cat, “imei”);
   if (!strstr(imei, device_imei)) {
    cat = ast_category_browse(cfg, cat);
    continue;
   }
   ast_log(LOG_DEBUG, “Imei found for usb device %s => channel %s.\n”, device_cat, cat);

   pin = ast_variable_retrieve(cfg, cat, “pin”);
   csca = ast_variable_retrieve(cfg, cat, “csca”);
   sms_enabled = ast_variable_retrieve(cfg, cat, “sms”);
   voice_enabled = ast_variable_retrieve(cfg, cat, “voice”);
   context = ast_variable_retrieve(cfg, cat, “context”);
   group = ast_variable_retrieve(cfg, cat, “group”);
   volume = ast_variable_retrieve(cfg, cat, “volume”);
   language = ast_variable_retrieve(cfg, cat, “language”);
   nocallsetup = ast_variable_retrieve(cfg, cat, “nocallsetup”);

   if (pvt = ast_calloc(1, sizeof(*pvt))) {
    ast_copy_string(pvt->data_port, dataport, sizeof(pvt->data_port));
    ast_copy_string(pvt->imei, imei, sizeof(pvt->imei));
    if (!ast_strlen_zero(pin))
     ast_copy_string(pvt->pin, pin, sizeof(pvt->pin));
    if (!ast_strlen_zero(csca))
     ast_copy_string(pvt->csca, csca, sizeof(pvt->csca));

    if (voiceport && voice_enabled && ((*voice_enabled == ‘y’) || (*voice_enabled == ‘Y’))){
     pvt->type = MODEM_TYPE_VOICE;
     ast_copy_string(pvt->voice_port, voiceport, sizeof(pvt->voice_port));
    }else{
     pvt->type = MODEM_TYPE_SMS_ONLY;
    } 
    ast_copy_string(pvt->id, cat, sizeof(pvt->id));
    ast_copy_string(pvt->context, S_OR(context, “default”), sizeof(pvt->context));
    ast_copy_string(pvt->language, S_OR(language, “en”), sizeof(pvt->language));
    if (group)
     pvt->group = atoi(group); /* group 0 if invalid */
    if (!ast_strlen_zero(volume)){
     if ((atoi(volume) >0) && (atoi(volume) <6)){
      pvt->volume = atoi(volume);
     }
    }else{
     pvt->volume = 4;
    }
    if (!ast_strlen_zero(sms_enabled)) {
     if ((*sms_enabled == ‘y’) || (*sms_enabled == ‘Y’)) {
      pvt->sms_enabled = 1;
     }
    }
    if (!ast_strlen_zero(nocallsetup)) {
     if ((*nocallsetup == ‘y’) || (*nocallsetup == ‘Y’)) {
      pvt->no_callsetup = 1;
     }
    }

    pvt->state = MODEM_STATE_INIT;
    pvt->data_socket = -1;
    pvt->voice_socket = -1;
    pvt->monitor_thread = AST_PTHREADT_NULL;
    pvt->dsp = ast_dsp_new();

    res= data_connect(pvt);  //Conect modem data port
    if (res < 0) {
     ast_log(LOG_ERROR, “Unable to open data port %s. Not loading device %s.\n”, pvt->data_port, pvt->id);
     break;
    }else{
     pvt->data_socket = res;
    }
    
    if (pvt->type == MODEM_TYPE_VOICE){  //Try voice port
     res2 = voice_connect(pvt);
     if (res2 < 0){
      ast_log(LOG_ERROR, “Unable to open voice port %s. Not loading device %s.\n”, pvt->voice_port, pvt->id);
      break;
     }else{
      pvt->voice_socket = res2;
     }
    }

    ast_dsp_set_features(pvt->dsp, DSP_FEATURE_DTMF_DETECT);
    ast_dsp_digitmode(pvt->dsp, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_RELAXDTMF);
    AST_RWLIST_WRLOCK(&devices);
    AST_RWLIST_INSERT_HEAD(&devices, pvt, entry);
    AST_RWLIST_UNLOCK(&devices);

    if (start_monitor(pvt)) {
     //pvt->connected = 1;
     manager_event(EVENT_FLAG_SYSTEM, “ModemStatus”, “Status: Connect\r\nDevice: %s\r\n”, pvt->id);
     if (option_verbose > 2)
      ast_verbose(VERBOSE_PREFIX_3 “Modem Device %s has connected.\n”, pvt->id);
    }
   }
   cat = ast_category_browse(cfg, cat);
  }
  
  cat = ast_category_browse(cfg, NULL);
  device_cat = ast_category_browse(devices_cfg, device_cat);
 }
 ast_config_destroy(cfg);
 ast_config_destroy(devices_cfg);
 return 1;
}

Leemos el fichero de configuracion sebi.conf. Vamos analizando cada uno de los modems configurados y comparando el imei de sebi.conf con el de sebi_devices.conf. Si coinciden continuamos el proceso de inicializacion del modem, recogiendo los puertos de sebi_devices.conf y el resto de configuración de sebi.conf. Haciendo esto se garantiza que siempre aplicamos los valores (pin, csca, etc) al modem que hemos elegido, y no a otro. Si no lo hicieramos así podríamos bloquear una tarjeta SIM intentando aplicarle un pin incorrecto por ejemplo. Despues de eso intentamos abrir los puertos de voz y comunicaciones (si no tiene el usuario que corre asterisk los permisos correctos no podrá abrir los puertos y el dispositivo no se cargará. Despues de comprobar que todo está ok arrancamos el thread de comunicaciones “start_monitor(pvt)”. Este thread es el que se encarga de leer y escribir los eventos del modem.
Y pasamos al monitor, que es el corazon del modulo en tiempo de ejecucion.

static void *do_monitor_phone(void *data)
{

 struct modem_pvt *pvt = (struct modem_pvt *)data;
 struct ast_channel *chn;
 char monitor = 1;
 char buf[256];
 char cid_num[AST_MAX_EXTENSION], *pcids, *pcide;
 int s, t, i, j, smsi;
 int send_pin;
 char brsf, nsmode, *p, *p1;
 char sms_src[13];
 char sms_txt[160];
 char sms_tmp_txt[160];

 send_pin = 0;

 if (!data_write(pvt, “ATE0\r”)) //First of all test write & disable echo…
  monitor = 0;

 while (monitor) {

  if (pvt->state == MODEM_STATE_DIAL1)
   t = pvt->dial_timeout;
  else if (pvt->state == MODEM_STATE_HANGUP)
   t = 2;
  else if (pvt->state == MODEM_STATE_OUTSMS1)
   t = 2;
  else if (pvt->state == MODEM_STATE_OUTSMS2)
   t = 15;
  else
   t = 1;

  s = data_read(pvt, buf, 0, t);

  if ((s > 0) && (buf[0] != 0x0) && (buf[0] != ‘\r’) && (!strstr(buf, “^BOOT:”))) {
   
   ast_log(LOG_DEBUG, “data_read() (%s) [%s]\n”, pvt->id, buf);
   switch (pvt->state) {
   case MODEM_STATE_INIT: // Echoing off state
    if (strstr(buf, “OK”)) {
     data_write(pvt, “AT+CGSN\r”);
     pvt->state++;;
    } else { // Hmmm, garbage…. Let’s try again ATE0
     if (!data_write(pvt, “ATE0\r”)) monitor = 0;
    }
    break;

   case MODEM_STATE_INIT1: // IMEI Checking – Is this the right modem ??
    if  (!(strstr(buf, “OK”))) {
     if (!strstr(buf, pvt->imei)){
      ast_log(LOG_ERROR, “IMEI in conf file for %s (%s) is different from device (%s). Disconnecting device.\n”, pvt->id, pvt->imei, buf);
      ast_copy_string(pvt->imei, “*–*INVALID*–*”, sizeof(pvt->imei));
      monitor = 0;
     }else{
      data_write(pvt, “AT+CPIN?\r”);
      pvt->state++;
     }
    }
    break;

   case MODEM_STATE_INIT2: // Pin Checking
    if (strstr(buf, “+CPIN: READY”)) { // OK
     pvt->state++;
     data_write(pvt, “AT^CARDLOCK?\r”);
    }else if (strstr(buf, “+CPIN: SIM PUK”)){ // SIM Locked
     ast_log(LOG_ERROR, “Device %s – PIN Locked. Disconnecting device. Unlock it & retry.\n”, pvt->id);
     ast_copy_string(pvt->imei, “** PIN Locked**”, sizeof(pvt->imei));
     monitor = 0;
    } else if ((pvt->pin) && (strstr(buf,”+CPIN: SIM PIN”))){
     if (send_pin == 0){ //Send SIM
      snprintf(buf, sizeof(buf), “AT+CPIN=%s\r”, pvt->pin);
      data_write(pvt, buf);
      send_pin =1;
      sleep(1);
      data_write(pvt, “AT+CPIN?\r”);
     }else{ // SIM Error
      ast_log(LOG_ERROR, “Device %s – Wrong PIN. Disconnecting device.\n”, pvt->id);
      ast_copy_string(pvt->imei, “** PIN Error **”, sizeof(pvt->imei));
      monitor = 0;
     }
    }
    break;

   case MODEM_STATE_INIT3: // NetLock Checking
     if (strstr(buf, “^CARDLOCK: 2”)) {
      data_write(pvt, “AT+CLIP=1\r”);
      pvt->state++;
     }else if (strstr(buf, “^CARDLOCK: 1”)){
      ast_log(LOG_ERROR, “Device %s – Net Locked. Disconnecting device.\n”, pvt->id);
      ast_copy_string(pvt->imei, “** Net Lock **”, sizeof(pvt->imei));
      monitor = 0;
     }
    break;
   case MODEM_STATE_INIT4:
    if (strstr(buf, “OK”)) {
     data_write(pvt, “AT+CREG?\r”); // Network registered??
     pvt->state++;
    }
    break;
   case MODEM_STATE_INIT5:
    if (strstr(buf, “+CREG: 0,1”)) { // Network OK
     data_write(pvt, “AT+COPS=3,0\r”);
     pvt->state++;
    }else if (strstr(buf, “+CREG: 0,2”)) { // Not network – request again …
     pvt->state–;
     sleep(5); // wait a little, please…
     data_write(pvt, “ATE0\r”); //we need OK in previous state
    }
    break;
   case MODEM_STATE_INIT6:
    if (strstr(buf, “OK”)) {
     data_write(pvt, “AT+COPS?\r”);
     pvt->state++;
    }
    break;
   case MODEM_STATE_INIT7:
    if (strstr(buf, “+COPS: “)) { // Get Network Provider friendly name
     memset(pvt->net_name, 0x00, sizeof(pvt->net_name));
     if ((p = strchr(buf, ‘”‘))) {
      if (*(++p) != ‘”‘)
       p++;
      if ((p1 = strchr(p, ‘”‘))) {
       if (*(–p1) != ‘”‘)
        p1–;
       memset(pvt->net_name, 0x00, sizeof(pvt->net_name));
       strncpy(pvt->net_name, p – 1, p1 – p + 3);
      }
     } //Modem is online – Now starting Voice-SMS init phase, see timeouts
     if (pvt->type == MODEM_TYPE_VOICE){
      pvt->state++;
     }else{
      pvt->state = MODEM_STATE_INIT_SMS;
     }
    }
    break;
   case MODEM_STATE_INIT_VOICE:
    snprintf(buf, sizeof(buf), “AT+CLVL=%d\r”, pvt->volume); // Adjust Volume
    data_write(pvt, buf);
    pvt->state++;
    break;
   case MODEM_STATE_INIT_VOICE1:
    if (strstr(buf, “OK”)){
     data_write(pvt, “AT^DDSETEX=2\r”); // Pre-Voice Command
     pvt->state++;
    }
    break;
   case MODEM_STATE_INIT_VOICE2:
    if (strstr(buf, “OK”)){
     data_write(pvt, “AT^CVOICE=?\r”); // Check Voice support
     pvt->state++;
    }else if (strstr(buf, “COMMAND NOT SUPPORT”)){
     pvt->has_voice = 0;
     pvt->state = MODEM_STATE_INIT_SMS;
    }
    break;
   case MODEM_STATE_INIT_VOICE3:
    if (strstr(buf, “^CVOICE:”)){
     pvt->has_voice = 1;
     pvt->state++;
    }else if (strstr(buf, “COMMAND NOT SUPPORT”)){
     pvt->has_voice = 0;
     pvt->state++;
    }
    break;
   case MODEM_STATE_INIT_SMS:
    if (pvt->sms_enabled){
     data_write(pvt,”AT+CMGF=1;+CPMS=\”ME\”,\”ME\”,\”ME\”;+CNMI=1,1,0,1,0\r”); // Enable SMS Info
     pvt->state++;
    }else{
     pvt->has_sms = 0;
     pvt->state = MODEM_STATE_PREIDLE;
    }
    break;
   case MODEM_STATE_INIT_SMS1:
    if (strstr(buf, “OK”)){
     snprintf(buf, sizeof(buf), “AT+CSCA=\”%s\”,145\r”, pvt->csca);
     data_write(pvt, buf);
     pvt->state++;
    }else if ((strstr(buf, “ERROR”)) || (strstr(buf, “COMMAND NOT SUPPORT”))){
     pvt->has_sms = 0;
     pvt->state = MODEM_STATE_PREIDLE;
    }
    break;
   case MODEM_STATE_INIT_SMS2:
    if (strstr(buf, “OK”)){
     pvt->has_sms = 1;
     pvt->state++;
    }else if ((strstr(buf, “ERROR”)) || (strstr(buf, “COMMAND NOT SUPPORT”))){
     pvt->has_sms = 0;
     pvt->state++;
    }
    break;
   case MODEM_STATE_PREIDLE: // Nothing handled here, wait for timeout, then off we go… //
    break;
   case MODEM_STATE_IDLE:
    if ((strstr(buf, “RING”)) && (pvt->has_voice)) {
     pvt->state = MODEM_STATE_RING;
    }
    break;

   case MODEM_STATE_DIAL: // Nothing handled here, we need to wait for a timeout
    break;
   case MODEM_STATE_DIAL1:
    if (strstr(buf, “OK”)) {
     if (pvt->no_callsetup) {
      ast_queue_control(pvt->owner, AST_CONTROL_ANSWER);
     } else {
      ast_setstate(pvt->owner, AST_STATE_RINGING);
     }
     pvt->state = MODEM_STATE_OUTGOING;
    }
    break;
   case MODEM_STATE_OUTGOING:
    if (strstr(buf, “^CEND”)) ast_queue_control(pvt->owner, AST_CONTROL_HANGUP);
    if (strstr(buf, “^CONN”)) ast_queue_control(pvt->owner, AST_CONTROL_ANSWER);
    if (strstr(buf, “^CONF”)) ast_queue_control(pvt->owner, AST_CONTROL_RINGING);

    break;

   case MODEM_STATE_RING:
    cid_num[0] = 0x00;
    if ((pcids = strstr(buf, “+CLIP:”))) {
     if ((pcids = strchr(pcids, ‘”‘))) {
      if ((pcide = strchr(pcids+1, ‘”‘))) {
       strncpy(cid_num, pcids+1, pcide – pcids – 1);
       cid_num[pcide – pcids – 1] = 0x00;
      }
     }
     chn = modem_new(AST_STATE_RING, pvt, cid_num);
     if (chn) {
      if (ast_pbx_start(chn)) {
       ast_log(LOG_ERROR, “Unable to start pbx on incoming call.\n”);
       ast_hangup(chn);
      } else
       pvt->state = MODEM_STATE_RING3;
     } else {
      ast_log(LOG_ERROR, “Unable to allocate channel for incoming call.\n”);
      data_write(pvt, “AT+CHUP\r”);
      pvt->state = MODEM_STATE_IDLE;
     }
    }
    break;
   case MODEM_STATE_RING2:
    chn = modem_new(AST_STATE_RING, pvt, cid_num);
    if (chn) {
     if (ast_pbx_start(chn)) {
      ast_log(LOG_ERROR, “Unable to start pbx on incoming call.\n”);
      ast_hangup(chn);
     } else
      pvt->state = MODEM_STATE_RING3;
    } else {
     ast_log(LOG_ERROR, “Unable to allocate channel for incoming call.\n”);
     data_write(pvt, “AT+CHUP\r”);
     pvt->state = MODEM_STATE_IDLE;
    }
    break;
   case MODEM_STATE_RING3:
    if (strstr(buf, “^CEND”)) { // Caller disconnected
     ast_queue_control(pvt->owner, AST_CONTROL_HANGUP);
    }
    break;
   case MODEM_STATE_INCOMING:
    if (strstr(buf, “^CEND”)) {
     pvt->do_hangup = 0;
     ast_queue_control(pvt->owner, AST_CONTROL_HANGUP);
    }
    break;
   case MODEM_STATE_HANGUP:
    if (strstr(buf, “OK”)) {
     if (pvt->voice_socket > -1){
      close(pvt->voice_socket);
      pvt->voice_socket = -1;
     }
     pvt->state = MODEM_STATE_IDLE;
    }
    break;
   case MODEM_STATE_INSMS:
    if (strstr(buf, “+CMGR:”)) {
     ast_copy_string(sms_txt, “”, sizeof(sms_txt));
     memset(sms_src, 0x00, sizeof(sms_src));
     if ((p = strchr(buf, ‘,’))) {
      if (*(++p) == ‘”‘)
       p++;
      if ((p1 = strchr(p, ‘,’))) {
       if (*(–p1) == ‘”‘)
        p1–;
       memset(sms_src, 0x00, sizeof(sms_src));
       strncpy(sms_src, p, p1 – p + 1);
      }
     }
    } else if (strstr(buf, “OK”)) {
     chn = modem_new(AST_STATE_DOWN, pvt, NULL);
     strcpy(chn->exten, “sms”);
     pbx_builtin_setvar_helper(chn, “SMSSRC”, sms_src);
     pbx_builtin_setvar_helper(chn, “SMSTXT”, sms_txt);
     ast_log(LOG_DEBUG, “sms received(%s) From(%s) Text[%s]\n”, pvt->id, sms_src, sms_txt);
     if (ast_pbx_start(chn))
      ast_log(LOG_ERROR, “Unable to start pbx on incoming sms.\n”);
     
     snprintf(buf, sizeof(buf), “AT+CMGD=%d\r”, smsi);
     data_write(pvt, buf); // delete sms
     pvt->state = MODEM_STATE_IDLE;
    } else {
     ast_log(LOG_DEBUG, “sms line received(%s) From(%s) Text[%s] Buf [%s]\n”, pvt->id, sms_src, sms_txt, buf);
     snprintf(sms_tmp_txt,sizeof(sms_tmp_txt),sms_txt);
     snprintf(sms_txt, sizeof(sms_txt), “%s%s\n”, sms_tmp_txt, buf);
    }
    break;
   case MODEM_STATE_OUTSMS:
    break;
   case MODEM_STATE_OUTSMS1:
    break;
   case MODEM_STATE_OUTSMS2:
    if (strstr(buf, “OK”)) {
     pvt->state = MODEM_STATE_IDLE;
    }
    break;
   }
   // Unsolicited responses

    if (strstr(buf, “+CMTI:”)) { // SMS Incoming…
    if ((p = strchr(buf, ‘,’))) {
     p++;
     smsi = atoi(p);
     if (pvt->has_sms) {
      snprintf(buf, sizeof(buf), “AT+CMGR=%d\r”, smsi);
      data_write(pvt, buf);
      pvt->state = MODEM_STATE_INSMS;
     }
    }
   }

  } else if (s == 0) { // Timeouts if (!data_write(pvt, “ATE0\r”)) monitor = 0;
   if (pvt->state == MODEM_STATE_INIT){
    if (!data_write(pvt, “ATE0\r”)) monitor = 0;
   }else if (pvt->state == MODEM_STATE_PREIDLE) {
    pvt->connected = 1;
    ast_verbose(VERBOSE_PREFIX_3 “Modem Device %s initialised and ready.\n”, pvt->id);
    pvt->state = MODEM_STATE_IDLE;
   } else if (pvt->state == MODEM_STATE_DIAL) {
    if (pvt->has_voice){
     snprintf(buf, sizeof(buf), “ATD%s;\r”, pvt->dial_number);
     if (!data_write(pvt, buf)) {
      ast_log(LOG_ERROR, “Dial failed on %s state %d\n”, pvt->owner->name, pvt->state);
      ast_queue_control(pvt->owner, AST_CONTROL_CONGESTION);
      pvt->state = MODEM_STATE_IDLE;
     } else {
      pvt->state = MODEM_STATE_DIAL1;
     }
    }else {
     ast_log(LOG_ERROR, “Dial failed on %s state %d\n”, pvt->owner->name, pvt->state);
     ast_queue_control(pvt->owner, AST_CONTROL_CONGESTION);
     pvt->state = MODEM_STATE_IDLE;
    }
   } else if (pvt->state == MODEM_STATE_DIAL1) {
    ast_log(LOG_ERROR, “Dial failed on %s state %d\n”, pvt->owner->name, pvt->state);
    ast_queue_control(pvt->owner, AST_CONTROL_CONGESTION);
    ast_queue_control(pvt->owner, AST_CONTROL_HANGUP);
    pvt->state = MODEM_STATE_IDLE;
   } else if (pvt->state == MODEM_STATE_RING) { // No CLIP?, bump it
    pvt->state = MODEM_STATE_RING2;
   } else if (pvt->state == MODEM_STATE_HANGUP) {
    if (pvt->do_hangup) {
     if (pvt->hangup_count == 6) {
      ast_log(LOG_DEBUG, “Device %s failed to hangup after 6 tries, disconnecting.\n”, pvt->id);
      monitor = 0;
     }
     data_write(pvt, “AT+CHUP\r”);
     data_write(pvt, “AT^DDSETEX=2\r”);
     pvt->hangup_count++;
    } else
     pvt->state = MODEM_STATE_IDLE;
   } else if (pvt->state == MODEM_STATE_OUTSMS) {
    snprintf(buf, sizeof(buf), “AT+CMGS=\”%s\”\r”, pvt->dial_number);
    data_write(pvt, buf);
    pvt->state = MODEM_STATE_OUTSMS1;
   } else if (pvt->state == MODEM_STATE_OUTSMS1) {
    if (pvt->data_buf[0] == ‘>’) {
     snprintf(buf, sizeof(buf), “%s%c”, pvt->sms_txt, 0x1a);
     data_write(pvt, buf);
     pvt->state = MODEM_STATE_OUTSMS2;
    } else {
     ast_log(LOG_ERROR, “Failed to send SMS to %s on device %s\n”, pvt->dial_number, pvt->id);
     pvt->state = MODEM_STATE_IDLE;
    }
   }
  } else if (s == -1) {
   if (option_verbose > 2)
    ast_verbose(VERBOSE_PREFIX_3  “Modem Device %s has disconnected, reason (%d).\n”, pvt->id, errno);
   monitor = 0;
  }

 }

 if (pvt->data_socket > -1)
  close(pvt->data_socket);
 if (pvt->voice_socket > -1)
  close(pvt->voice_socket);
 pvt->voice_socket = -1;
 pvt->data_socket = -1;
 pvt->connected = 0;
 pvt->monitor_thread = AST_PTHREADT_NULL;

 manager_event(EVENT_FLAG_SYSTEM, “SebiStatus”, “Status: Disconnect\r\nDevice: %s\r\n”, pvt->id);

 return NULL;

}

Vayamos por partes aqui, ya que la funcion es bastante extensa.
Case_modem_init_xxxx. Todas estas rutinas van leyendo y escribiendo datos al modem para ponerlo en disposicion de trabajar y comprobar que la configuración es correcta y coherente. Una simple lectura detallada aclara bastante que es lo que se va haciendo al inicializarlo.
Despues de haberlo inicializado tenemos otra seccion donde nos dedicamos a “escuchar al modem” (llamada entrante o sms entrante al recibir un ring o un cmti respectivamente del modem) y una tercera zona donde gestionamos las llamadas y el envio/recepcion de sms

Intentaré seguir con la gestión del audio cuando pueda. Un saludo desde Qosqo (Cusco)

Posted in Asterisk, E169 - K3520, Gateway, GSM, Huawei, Modem 3G | 3 Comments »

chan_sebi (1) Que, como y por que

Posted by odicha en 30 junio 2009

Hola.

Ahora que parece que lo de chan_sebi ya está más o menos controlado voy a comentar un poco como y por que surgió la idea de trabajar en este ámbito.

Primero explicar que es chan_sebi. Pues es un canal para Asterisk, que utiliza modems Huawei para proveer de gateways gsm de voz y/o  sms.

En principio se ha hecho pensando en los E169/K3520 de Huawei, modems estos que tienen capacidades de llamada de voz. Internamente utilizan un chipset de Qualcomm (de la serie MSM7200). Con un modem usb de estos modelos tenemos un gateway de voz que además no tiene que hacer conversiones de audio como las que se hacen con un FXO analogico ni los problemas de señalización inherentes a los canales analógicos, ya que toda la gestión de audio y de señalización se hace de forma digital (bien a través del canal de audio, bien a través del canal de datos), pudiendo ser comparado a un router RDSI=>GSM de un puerto

La idea surge de la posibilidad de aprovechar las capacidades de voz de estos modems para tener un gateway gsm a un costo muy economico. La mayor parte de los operadores los dan a costo cero cumpliendo unos mínimos de permanencia o consumo, como cualquier otro terminal telefónico.

Técnicamente estos modems son vistos por el S.O. como un “hub” usb con varios dispositivos. En principio tres puertos seriales y un puerto de almacenamiento con dos dispositivos (un cd y un lector de memoria). Asi los subid 00,01 y 02 serán los puertos de comunicacion, cada uno con distintas funciones (audio, datos y gestion general) y el subid 03 que nos dará control sobre el cd virtual y el lector de tarjetas.

A partir de esta base se construye un controlador de canal que toma como punto de partida el chan_mobile que ya existe para Asterisk.

El firmware para los E169 K3520 que yo uso está disponible aqui http://www.vodafone.de/downloadarea/FW_Update_K3520_11.314.12.02.00.zip

Para descargar asterisk-addons con chan_sebi svn export http://asterisk-es-rsp.irontec.com/svn/team/Odicha/asterisk-addons-1.4.7 asterisk-addons 

Y pasamos ya a lo que es instalacion y configuracion de chan_sebi

A nivel de descarga e instalación comentar cuales son sus prerequisitos. El S.O. debe ser capaz de montar los subid 00, 01 y 02 del modem huawei como usbTTYx. Los kernels antiguos (por debajo de 2.6.20 generalmente) no son capaces de hacerlo automaticamente por lo que habrá que modificar algunas cosas para que se detecten de la forma adecuada y sean cargados por usbserial. Normalmente es suficiente con /sbin/modprobe usbserial vendor=0x12d1 product=0x1003  o bien /sbin/modprobe usbserial vendor=0x12d1 product=0x1001 dependiendo de si el modem es 1001 (169,k3520,e270,etc…) o es 1003 (e220…). A veces también entran en conflicto con el driver de option resolviendose añadiendo al blacklist el modulo “option”. Si aun asi sigue intentando cargar puede intentar resolverse eliminando el fichero option.ko y ejecutando un depmod despues.

Los puertos ttyUSBx que se crean al conectar el modem tienen que tener permiso de lectura y escritura para el usuario que ejecuta Asterisk, si no es así no será posible que Asterisk se comunique con el modem. Si no se ejecuta asterisk como root lo mejor es agregar el usuario que corre asterisk al grupo propietario de los puertos que en funcion de distros y kernels puede variar (en Fedora es uucp, por ej.). Algo como esto por ej.

chmod 660 /dev/ttyUSB*
chown asterisk:uucp /dev/ttyUSB*
echo ‘KERNEL==”ttyUSB[0-9*]”, MODE=”0660″, OWNER=”asterisk”, GROUP=”uucp”‘  >>  /etc/udev/rules.d/92-sebi.rules

Cumpliendo esto podremos instalar asterisk-addons sobre nuestro asterisk 1.4.x con chan_sebi incluido.

Si lo instalamos y arrancamos, si tenemos todo correcto, el canal analizará los dispositivos usb que tenemos conectados y generará un fichero /etc/asterisk/sebi_devices.conf que contendrá algo similar a esto:

[2-1]
data = /dev/ttyUSB2
voice = /dev/ttyUSB1
imei = 35413702XXXXXXX

 A partir de aqui sabemos que el modem está siendo detectado correctamente y pasaremos a generar el fichero de configuración /etc/asterisk/sebi.conf. Si hicimos un make samples al instalar asterisk-addons ya tendremos un fichero sebi.conf que tendremos que adaptar.

[huawei1]
imei=35XXXXXXXXXXX  ; imei of modem
;pin=1234   ; pin (use only if sim card has pin request enabled)
csca=+34607003110  ; SMS centre number (Vodafone ES – example)
sms=yes    ; Enable sms send/receive
voice=yes   ; Enable voice for calls
context=from-pstn  ; dialplan context for incoming calls
group=1    ; this phone is in channel group 1
volume=5   ; Modem Volume (1-5)
language=es   ; Channel language
;nocallsetup=yes  ; set this only if you want override call status notifications

El campo imei del canal es el que combina la información de configuración del modem con el equipo físico. No olviden que el numero de puerto ttyUSB depende del orden en que se cargan o de la cantidad de dispositivos usb que están conectados en cada momento. Los demas campos de configuracion creo que están claros, practicamente se describen solos. Es interesante hacer notar que el nombre del canal lo asignamos nosotros libremente en el identificador de seccion (en el ejemplo [huawei1])

Si reiniciamos asterisk y todo ha ido bien tras cargar ejecutaremos el comando sebi show devices y obtendremos algo parecido a esto:

CentOS-5*CLI> sebi show devices

ID                      IMEI                 Group           Provider        Connected State Voice SMS
huawei1         3541370xxxxxxxx   1    Vodafone ES               Yes       Free  Yes    Yes

CentOS-5*CLI>

A partir de aquí podemos usar el modem como una canal de voz realizando y recibiendo llamadas y como gateway sms. Cabe observar que en los modelos que no soporten voz el resultado será como este:

CentOS-5*CLI> sebi show devices

ID                      IMEI                 Group           Provider        Connected State Voice SMS
huawei1         3541370xxxxxxxx   1    Vodafone ES               Yes       Free  No    Yes

CentOS-5*CLI>

Nos dirá que no tenemos servicios de voz en ese dispositivo, y lo usaremos solo como gateway sms.

Para enviar y recibir sms, usaremos convenciones muy similares a las de chan_mobile. Para recibir un sms y guardarlo en la base de datos tendremos las instruccciones correspondientes en el dialplan (por cierto, solo procesa sms recibidos mientras está activo, por el momento)

[from-pstn]
exten => sms,1,Verbose(Incoming SMS from ${SMSSRC} ${SMSTXT})
exten => sms,n,Set(DB(SMS/${SMSSRC})=${SMSTXT})      
exten => sms,n,Hangup

 Para enviar un sms:

originate sip/109 application SebiSendSMS huawei1|647324xxx|”texto de prueba del sms”

Para revisar si el canal está libre antes de enviar el sms tenemos la aplicación correspondiente

SebiStatus(Device,Variable)
Device – Id del dispositivo en sebi.conf
Variable – Variable donde almacenamos el estado (1-3) 1-Desconectado, 2-Disponible, 3-Ocupado

Y en principio no tiene más complejidades. En un próximo post intentaré analizar un poco el código, para los que deseen modificar cosas puedan ir a “tiro hecho”.

Posted in Asterisk, E169 - K3520, Gateway, GSM, Huawei, Modem 3G | 61 Comments »

asterisk-es-rsp

Posted by odicha en 30 junio 2009

Hola. Aprovechando que ando de vacaciones, me siento un rato a escribir, que me han echado un par de “rasques” por ahi y con razón además de que esto anda muy desactualizado.

En primer lugar comentarles que se ha creado un grupo asterisk-es-rsp, en el que colaboro, no todo lo activamente que me gustaría, en el que hemos intentado mantener una distribucion operativa y lo mas estable posible. La web del grupo es http://www.asterisk-es-rsp.org 

3598137787_2d8927679e

La parte referente a Dahdi (el odistuff) ha sido incluida totalmente en el branch principal y se encuentra en produccion en variados entornos sin ningun tipo de problemas

Hay toda clase de información en la web del grupo sobre como realizar una instalación completa, y no resulta complicado leyendo posts anteriores de este blog realizar una instalacion de asterisk-es-rsp con freepbx, practicamente solo hay que cambiar la ruta del svn de donde descargamos los fuentes.

Dejo por aquí de todos modos, un “chuletin” para seguir paso a paso de instalacion sobre Fedora/CentOs con una B200P y una A800P de OpenVox con 8 placas de extensiones + asterisk-es-rsp + freepbx + voces en español (sobre todo para los que hacen sus pinitos en asterisk // linux y se les hace a veces complicado conseguir que las cosas funcionen). Yo he instalado CentOs y Fedoras en 32 y 64 bits sin problemas siguiendo la chuleta… Tengan en cuenta que hay cosas que dependen del hardware que se tenga y que las líneas con comillas simples o dobles no se pueden pegar directamente en una consola (cosas de WordPress…). Sería interesante ir viendo las diferencias y problemas que hayan y conseguir tener un manual de instalación “para Odis” (o sea para torpes como yo…)

Partimos de un Fedora 10 o un CentOs 5.3 recién instalado (con las opciones del post  https://odicha.wordpress.com/2009/02/27/asterisk_ojos_rojos/ )

1.- Nos conectamos via consola local o remota, validamos y ahora instalaremos algunos paquetes necesarios antes de Asterisk en si mismo

yum install libtiff-devel php-pear php-pear-DB php-gd php-mysql php-pdo audiofile-devel mysql-devel perl-DateManip

2.- Seguimos descargando cosas

cd /usr/src

wget http://kent.dl.sourceforge.net/sourceforge/lame/lame-398-2.tar.gz

wget http://kent.dl.sourceforge.net/sourceforge/amportal/freepbx-2.5.1.tar.gz

svn export http://asterisk-es-rsp.irontec.com/svn/asterisk-es-rsp/branches asterisk-es-rsp

-Si queremos asterisk-addons con chan_sebi

svn export http://asterisk-es-rsp.irontec.com/svn/asterisk-es-rsp/team/Odicha/1_4/asterisk-addons-1.4.9  asterisk-addons

-Si queremos asterisk-addons original

svn export http://svn.digium.com/svn/asterisk-addons/branches/1.4/ asterisk-addons

tar zxvf freepbx-2.5.1.tar.gz

tar zxvf lame-398-2.tar.gz

3.- Instalamos lame

cd lame-398-2

./configure

make

make install

cd /usr/src

4.- Instalamos libpri

cd asterisk-es-rsp/libpri/1.4.10

make

make install

cd /usr/src

5.- Instalamos dahdi-linux

cd asterisk-es-rsp/dahdi-linux/2.1.0.4

make

make install

cd /usr/src

6.- Instalamos dahdi-tools

cd asterisk-es-rsp/dahdi-tools/2.1.0.2

./configure

make

make install

make config

cd /usr/src

7.- Previo a la instalación a asterisk

useradd -c “Asterisk PBX” -d /var/lib/asterisk asterisk

mkdir /var/run/asterisk

mkdir /var/log/asterisk

chown -R asterisk:asterisk /var/run/asterisk

chown -R asterisk:asterisk /var/log/asterisk

chown -R asterisk:asterisk /var/lib/php/session/

nano +231 /etc/httpd/conf/httpd.conf

8.- Cambiamos en el fichero el usuario apache y grupo apache a usuario asterisk y grupo asterisk.

Bajamos unas líneas y encontramos AllowOverride None. Lo cambiamos por AllowOverride All

Pulsamos control+x para guardar el fichero. Le decimos que si (si no para que editarlo?) y cerramos.

9.- Instalamos asterisk

cd /usr/src/asterisk-es-rsp/asterisk/1.4.24/

./configure

make

make install

y las voces en español (opcional)

make es-sounds

10.- Preparamos las bases de datos para FreePBX

/etc/init.d/mysqld start

cd /usr/src/freepbx-2.5.1

mysqladmin create asterisk

mysqladmin create asteriskcdrdb

mysql asterisk < SQL/newinstall.sql

mysql asteriskcdrdb < SQL/cdr_mysql_table.sql

mysql

GRANT ALL PRIVILEGES ON asteriskcdrdb.* TO asteriskuser@localhost IDENTIFIED BY ‘PASSWORD’;

GRANT ALL PRIVILEGES ON asterisk.* TO asteriskuser@localhost IDENTIFIED BY ‘PASSWORD’;

flush privileges;

\q

mysqladmin -u root password ‘PASSWORD’

11.- Instalamos asterisk-addons

cd /usr/src/asterisk-addons

./configure

make

make install

12.-Y ahora FreePBX

cd /usr/src/freepbx-2.5.1

./start_asterisk start

yum install –y php-pear-DB

yum install –y php-mysql

./install_amp ––username=asteriskuser ––password=PASSWORD

13.- le damos a enter aceptando los valores propuestos

14.- Autoarranque activo para httpd mysql y amportal

echo “/usr/local/sbin/amportal start” >> /etc/rc.local

chkconfig httpd on

chkconfig mysqld on

ln -s /var/lib/asterisk/moh /var/lib/asterisk/mohmp3

nano +73 /var/www/html/recordings/includes/main.conf.php

15.-modificamos la línea

$ari_admin_password = “PASSWORD”;

guardamos el fichero

16.- quitamos el driver de la b400p que trae fedora (saltar en CentOs)

vim /etc/modprobe.d/dahdi.blacklist

Agregamos al final

blacklist hfcmulti

17.- editamos

vi /etc/dahdi/modules

Añadimos # a las lineas que no están editadas excepto a wcb4xxp y opvxa1200 si son las tarjetas que tenemos conectadas

# Digium B410P: 4 NT/TE BRI ports

wcb4xxp

# OpenVox A1200P/A800P

opvxa1200

18.- Ejecutamos estos comandos para inicializar DAHDI

rmmod hfcmulti   (solo Fedora)

service dahdi start

dahdi_genconf

dahdi_cfg -vv

19.-Editamos /etc/amportal.conf y agregamos al final la siguiente línea

ZAP2DAHDICOMPAT=true

20.-Asterisk interpreta los canales a través del fichero de configuración chan_dahdi.conf. Este fichero no existe, así que lo crearemos nosotros.

vi /etc/asterisk/chan_dahdi.conf

pegamos esto

[channels]

language=es

resetinterval=never

pridialplan=unknown

#include dahdi-channels.conf

#include chan_dahdi_additional.conf

Guardamos

21.- creamos el fichero chan_dahdi_additional.conf

touch chan_dahdi_additional.conf

22.- Reiniciamos

23.- Actualizamos freepbx

nos vamos a Module Admin

>Pulsamos Check for Updates Online

>Download All

>Upgrade All

>Proccess

>Confirmamos

Repetimos el proceso hasta que no quede nada por instalar/actualizar

24.- Instalación de Custom Contexts.

Es un módulo no oficial de FreePBX que sirve para controlar el acceso a las líneas salientes (al estilo de las PBX tradicionales). Más adelante veremos como funciona.

Descargamos http://mirror.freepbx.org/modules/release/contributed_modules/customcontexts-0.3.4.tgz

Lo renombramos a customcontexts.tar.gz

Desde FreePBX > Module Admin > Upload Module > Seleccionamos customcontexts.tar.gz > Lo subimos > Manage Local Modules > Custom Contexts > Install

25.- damos de alta en freepbx enlaces y extensiones

Pues tenemos extensiones del 7 al 14 y canales RDSI del 1 al 6. Nos vamos a FreePBX desde el explorador y agregamos los enlaces del 1 al 6 (1-2 , 4-5). Pestaña Trunks -> Add ZAP Trunk (Dahdi compat..) Ahí, por ahora, simplemente añadimos el número del enlace (del 1 al 5) , uno por cada Trunk que añadimos Y borramos el enlace ZAP/G0 que aparece por defecto en la instalación. Aplicamos los cambios y nos vamos a la pestaña extensiones. Ahí añadiremos las extensiones. Yo para una pequeña central como esta usare un plan de tres cifras para las extensiones locales (101,102, etc). Seleccionamos “Add generic ZAP Device” y pulsamos Add. Ahi colocaremos por ahora el numero de extension en los campos “User extension” y “Display Name” y el numero de canal zap en “Channel”, repitiendo el proceso para cada extensión.

26.Rutas de salida

Comenzaremos con las llamadas al exterior. Tenemos 4 canales RDSI en dos puertos. El 1 y el 2 pertenecientes al puerto 1 de la B200P y el 4 y 5 al puerto 2. Supongamos que tenemos un S0 de un operador nacional fijo en el puerto 1 y un enlace RDSI de telefonía móvil en el 2, y que tenemos un plan de numeración privado de 4 cifras para los móviles. Crearemos tres rutas salientes: Fijos para las llamadas a números nacionales e internacionales, Móviles para las llamadas a móviles y Corporativo para las llamadas a nuestros móviles de la empresa. Nos vamos pues a la pestaña Outbound Routes en FreePBX y añadimos una nueva ruta. Primero le damos un nombre (Fijos). Ponemos los patrones de marcado que la ruta cumplirá 0. // 1.// 8. //9.  0 para internacionales 1,8 y 9 para llamadas nacionales. Así si marcamos por ejemplo 928123456 como coincide con el patrón 9. (el punto es “lo que quieras marcar detrás de esto) intentará marcar 928123456 por uno de los enlaces externos que marcamos en Trunk sequence (en el caso de los fijos el 1 y el 2). Repetimos la operación con 6. – Moviles – usando los trunks 4 y 5, y creamos por último el corporativo. Aquí una diferencia. 2xxx 3xxx 4xxx 5xxx 7xxx. El sistema reconocerá que es un nímero de 4 cifras en vez de usar el punto como en los otros. Por último eliminamos la ruta 9 outside que viene creada por defecto y ordenamos las rutas dejando como primera la de los corporativos. Aplicamos los cambios.

27.ruta de entrada

Creamos un queue numero 600 para las extensiones de recepcion de llamadas. Creamos una ruta de entrada (inbound) para la 600 y añadimos las extensiones en las que queremos que suenen las llamadas entrantes. Es simple

Posted in Asterisk, DAHDI, HFC-4S, HFC-8S | 8 Comments »

Odistuff rc1

Posted by odicha en 13 abril 2009

 

Nota

Esta entrada es obsoleta.

Para una version operativa descargar del svn http://asterisk-es-rsp.irontec.com/svn/branches/

Saludos

 

Hola.

Pues ya hay algo tangible en la idea de dar soporte Dahdi a nuestras tarjetas BRI.

Vamos por pasos

Que hace.

Soporta las tarjetas basadas en HFC-4S, HFC-8S y HFC-S de Cologne Chips con Dahdi + Asterisk.

Como lo hace.

Modificacion del driver wcb4xxp (hecho para la B410P) para que soporte todas las HFC-4S y HFC-8S

Port del driver de bristuff de las HFC-S a Dahdi. (No tiene señalizacion de alarmas Dahdi, pero es plenamente operativo)

Ademas para el trunk 1.4 agregamos soporte para señalizacion BRI (desarrollado para la 1.6.x pero no implementado en 1.4.x).

Los ficheros:

odi-dahdi-linux-2.1.0.4.diff     Para dahdi-linux-2.1.0.4

odi-dahdi-tools-2.1.0.2.diff   Para dahdi-tools-2.1.0.2

odi-asterisk-1.4.24.1.diff  Para la rama 1.4 de Asterisk (version 1.4.24.1)

Notas importantes.

Necesitan libpri (probado con libpri-1.4.9).

Para que no tengamos problemas con lineas RDSI con gestion de energia (telefonica por ej.) añadir a chan_dahdi.conf dentro de la seccion channels y antes de cualquier include la linea  resetinterval=never. Asi podremos llamar sin problema a traves de cualquier canal o grupo. Tambien se puede hacer span por span, por si tenemos algun primario que necesite resetear lineas de cuando en cuando.

En el parche para la version 1.4 agregamos además un parametro bri_l1_check. Si lo ponemos =no evitara que recibamos en consola los mensajes Pri d channel up / down cada 15 segundos. si lo ponemos  =yes o lo omitimos recibiremos los mensajes de aviso

Asimismo hay unos rpms de pruebas para la distro 1.5.2 de Elastix. Para mas informacion ver este post. http://www.elastix.org/index.php?option=com_fireboard&Itemid=55&func=view&catid=51&id=19880 (Instrucciones detalladas a mitad del hilo)

Están probados sobre CentOs y Debian en Asterisk 1.4.24.1 y 1.6.1rc4. (con dahdi-linux-2.4.0.1, dahdi-tools-2.1.0.2 y libpri-1.4.9).

Espero que les den una oportunidad. Personalmente con las HFC-4S y 8S en 1.4.x que es donde más pruebas he hecho cambia a mejor el rendimiento con respecto a mISDN, se nota. No hay que usar comandos adicionales de mISDN para las líneas y el dialplan lo agradece.

En cuanto a la lista de agradecimientos, es larga, espero que no se me quede nadie atras

A Florian Zumbiehl que ha hecho la mayor parte del port que uso de las HFC-S (yo solo he añadido unos pocos detalles para darle mejor operatividad con Dahdi)

A Tzafrir Cohen de Xorcom, con el tema de las HFC-4S y 8S

A la gente de OpenVox por ver la idea y ayudarme en el desarrollo

A Juan Carlos Valero y Xavier Jimenez de Capatres, colaboradores infatigables

A la lista de Asterisk-ES que ha colaborado y especialmente a Ramon Lozano (Ramoncio) que ha servido de “alpha-tester”. Sin el no estaría esto donde está.

A Ramses II de Asterisk-ES por ponerle nombre a este injerto…

Y por supuesto a mi familia que me permite gastar mis horas libres en estas cosillas

Posted in Asterisk, CentOs, DAHDI, HFC-4S, HFC-8S | 8 Comments »

Los Odi-Parches. Del chiste a la práctica…

Posted by odicha en 8 abril 2009

Hola.

Pues por aquí ando revolviendo un poco otra vez. Ya tengo un par de ficheros organizados para poder trabajar como mandan los cánones con nuestras tarjetas y nuestras líneas BRI sobre Asterisk. En principio son tres ficheros, dos que afectan a DahDi, y un tercero que es para aplicar a Asterisk. Para tratarlos como siempre, lo mejor es un entorno limpio, un CentOs 5.2 por ejemplo. Un poco de que es lo que llevan dentro los diff.

Descargar odi-dahdi-linux-2.1.0.4.diff

Pues este fichero parchea dahdi-linux. 
1) base.c y wcb4xxp.h Estos son los ficheros que nativamente soportaban las b410p de Digium para soportar toda la gama de HFC-4S y HFC-8S, lease OpenVox B200 B400 y B800 asi como Beronet Junghanns y alguna más compatible .
2) Crea dos ficheros nuevos zaphfc.c y zaphfc.h que son los drivers de bristuff portados a Dahdi para las tarjetas basadas en HFC-S, es decir las OpenVox HFC-S de un puerto, y todas las BIllion, Asus, etc etc que andan por ahi. Si la soportaba bristuff con este parche andará con Dahdi.
3)También modificamos un par de funciones de Dahdi para adaptarlas al funcionamiento de las HFC-S (bristuffing…)

Descargar odi-dahdi-tools-2.1.0.2.diff

En este parche modificamos dahdi-tools para que funcionen correctmante dahdi_hardware y dahdi_genconf con las nuevas tarjetas que soporta dahdi-tools (ids de harware y demás)

Descargar odi-asterisk-1.4.24.1.diff

Y este se usa solo en caso de querer correr Dahdi sobre la rama 1.4.x de Asterisk. Este está preparado para la 1.4.24.1, aunque posiblemente funcionaría sobre la 1.4.23 e incluso la 22 ya que no hay modificaciones en chan_dahdi desde esa versión en el branch 1.4.x  Con este fichero agregamos señalización BRI a asterisk para que pueda comunicarse con las tarjetas RDSI.

Y esto es todo amigos, unas pocas líneas de código tomadas de aquí y de allá, que espero que me hagan (y a ustedes también) la vida un poco más fácil con Asterisk y nuestras europeas RDSis…

Posted in Asterisk, DAHDI, HFC-4S, HFC-8S | Leave a Comment »

DAHDI y NT PTMP para canales BRI sobre HFC-4S y HFC-8S

Posted by odicha en 13 marzo 2009

Hola!

Un poco más de lata con las cosillas de Asterisk. Hoy le toca a chan_dahdi.c. Ahí va un diff para habilitar NT en modo Punto a MultiPunto. Sobre Asterisk 1.6.1. Usando los parches para wcb4xxp (Vease  https://odicha.wordpress.com/2009/03/12/dahdi-para-hfc-4s-y-hfc-8s-mision-cumplida/)

 — /usr/src/originales/chan_dahdi.c    2009-03-13 12:31:51.000000000 +0000
+++ /usr/src/asterisk-1.6.1/channels/chan_dahdi.c       2009-03-13 12:33:38.000000000 +0000
@@ -14549,7 +14549,8 @@ static int process_dahdi(struct dahdi_ch
                                        confp->chan.sig = SIG_BRI_PTMP;
                                        confp->pri.nodetype = PRI_CPE;
                                } else if (!strcasecmp(v->value, “bri_net_ptmp”)) {
–                                       ast_log(LOG_WARNING, “How cool would it be if someone implemented this mode!  For now, sucks for you. (line %d)\n”, v->lineno);
+                                       confp->chan.sig = SIG_BRI_PTMP;
+                                       confp->pri.nodetype = PRI_NETWORK;
                                } else if (!strcasecmp(v->value, “gr303fxoks_net”)) {
                                        confp->chan.sig = SIG_GR303FXOKS;
                                        confp->pri.nodetype = PRI_NETWORK;

En principio probado con una OpenVox B200P y funcionando. Habrá que insistir más en las pruebas por si acaso…

Posted in Asterisk, DAHDI, HFC-4S, HFC-8S | Leave a Comment »

DAHDI para HFC-4S y HFC-8S – Misión cumplida…

Posted by odicha en 12 marzo 2009

Hola a todos el tema de dar soporte sobre DAHDI a las trajetas basadas en los chips HFC-4S y HFC-8S va tocando a su fin. Ya tengo algunos reportes correctos para tarjetas de 2,4 y 8 puertos. Asi que ya podemos casi hablar de un estado terminado.
Ahora mismo el parche soporta las siguientes tarjetas:

OPENVOX (probadas incluyendo señalizacion).
B200P
B400P
B800P

Junghanns
duoBri
quadBri
octoBri

BeroNet
BN2S0
BN4S0
BN8S0

Los diff para descargar estan aqui.
* Obsoleto * http://bugs.digium.com/file_download.php?file_id=21900&type=bug&download  

http://bugs.digium.com/file_download.php?file_id=21961&type=bug&download   -> diff para dahdi-linux 2.1.0.4 (16 de Marzo)
http://bugs.digium.com/file_download.php?file_id=21901&type=bug&download   -> diff para dahdi-tools 2.1.0.2

 

 

Y no olviden que necesita libpri 1.4.9 y Asterisk 1.6.x

Posted in Asterisk, DAHDI, HFC-4S, HFC-8S | 3 Comments »

wcb4xxp – Historia de un parche

Posted by odicha en 8 marzo 2009

Pues ya es medianamente operativo el parche para soportar a las tarjetas HFC-4S y HFC-8S.

Voy a intentar resumir que he hecho y por qué.

En primer lugar contarles por qué me he metido en esto. Pues resulta que en la empresa donde trabajo se decidió comenzar a renovar las centrales trelefónicas y a mí se me ocurrió desenpolvar mis apuntes de Asterisk de hace 5 años, que había andado mirando algunas cosas.  Unas cuantas búsquedas de información por aquí y por allá y resulta que la opción mas económica de renovación es montar centrales sobre Asterisk.

Pues nada, tras la primera decisión, lo siguiente es preparar una documentación clara y un elegir sistema para que los técnicos del departamento, que de Linux y Asterisk andan como el panda de Kung-Fu (nivel cero), puedan reparar o restaurar una central en un tiempo adecuado.

Y en esa andaba cuando llegó mi hardware (unas Openvox B200P y A800P) de Capatres. Bueno, primeras pruebas. Y primer concepto esclarecedor sobre Asterisk. La documentación (5 años después) sigue siendo el gran ignorado en Asterisk. Manuales de instalación, pocos, con complementos aún menos, explicaciones prácticas, cero. Así que toca empezar a “buscarse la vida”.

  • 1- El planteamiento de las centrales que andamos montando no es primariamente el de dar servicios VoIP. Es el de sustituir “cacharros” de la era Panasonic analógica o “DKDA”, por ejemplo. La idea inicial es aprovechar el cambio para ganar funcionalidades.
  • 2- Las centrales andan desperdigadas por esos mundos de Dios a por lo menos un avión o barco de distancia una de otra, así que es premisa ineludible, que lo que se instale sea del estilo “montar y olvidar”.
  • 3- No soy muy partidario de andar actualizando sistemas que cargan con servicios críticos (como los de voz) salvo que sea estrictamente necesario, por lo que mi idea primitiva es tener una caja “cerrada”, si no usando una distribución tipo Elastix o TrixBox, usando un sistema sencillo que esté medianamente bien documentado.

Y resulta que para empezar, tras las primeras pruebas voy descubriendo que ni los drivers para las tarjetas son tan estables como parecen, que hay que olvidarse de usar distribuciones predefinidas, porque en todas han olvidado un pequeño detalle, que es que tu no quieres que del teléfono del patio se hagan llamadas internacionales (por poner un ejemplo, porque se olvidaron de más cosas…), y que de la última versión de Dahdi (sustituto de Zaptel, controlador e interface de gestión de las tarjetas) no hay soporte para nada de lo que he comprado (y eso que es de lo más estandar).

¿Que toca? Remangarse las mangas y ponerse el mono.  Hay que hacerlo todo empezando de cero, como el otro que dice. Las tarjetas ya están compradas así que toca resolver.

HFC-4Sy HFC-8S. Cologne Chips. El corazón de las tarjetas. Pues resulta que hay un driver para una tarjeta que tiene este chip bajo Dahdi, wcb4xxp.  ¿Y por qué la mía no funciona, si tiene el mismo chip?

Bueno, hablemos un poco de qué hay por ahí…  La Digium B410P es una tarjeta ISDN basada en el chip HFC-4S, que además integra un cancelador de eco por hardware, cosa que las demás tarjetas de 4 puertos no tienen (salvo algún que otro clon de la 410P), y mi OpenVox B200P además solo tiene dos puertos, aunque usa el mismo chip. La parte buena es que llevan el mismo chip, y la parte mala es que a priori se parecen como un huevo a una castaña.

Tras unas cuantas búsquedas localizo un bug de la web de Digium donde parece que algo han intentado avanzar, pero lo primero que veo es que en el mismo asunto dice que seguramente las de dos puertos no funcionarán (bien vamos). Y así es como en resumen me meto en este tinglado, sin habermelo planteado mucho.

Tras haber expuesto un poco los antecedentes del engendro, vamos a ver en sí que hace el código y que se ha cambiado.

El driver original wcb4xxp.h y base.c, en dahdi-linux.2.1.0.4. Está planteado para dar servicio a las B410P, así que en el código no se ha pensado en otras variantes. Veamos que cambiamos analizando un poco porciones del último fichero diff que he colgado en el bug.

En primer lugar el driver original no usa otro id de hardware que no sea el propio de las B410P, así que agregamos unos cuantos.

…código original base.c

  static struct pci_device_id b4xx_ids[] __devinitdata =
 {
  { 0xd161, 0xb410, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (unsigned long)&wcb4xxp },
– { 0, }

…agregamos

+ { 0x1397, 0x16b8, 0x1397, 0xe552, 0, 0, (unsigned long)&hfc8s },
+ { 0x1397, 0x08b4, 0x1397, 0xb520, 0, 0, (unsigned long)&hfc4s },
+ { 0x1397, 0x08b4, 0x1397, 0xb556, 0, 0, (unsigned long)&hfc2s },
+ { 0x1397, 0x08b4, 0x1397, 0xe884, 0, 0, (unsigned long)&hfc2s_OV },
+ { 0x1397, 0x08b4, 0x1397, 0xe888, 0, 0, (unsigned long)&hfc4s_OV },
+ { 0x1397, 0x16b8, 0x1397, 0xe998, 0, 0, (unsigned long)&hfc8s_OV },

Para que a la hora de cargar el driver podamos reconocer otras tarjetas (en concreto las mías). También creamos las estructuras para gestionarlas.

…código original

 struct devtype {
  char *desc;
  unsigned int flags;
 };
-static struct devtype wcb4xxp = { “Wildcard B410P”, 0 };

…agregamos nuestro código y queda así

 struct devtype {
  char *desc;
  unsigned int flags;
+ int ports;    /* Number of ports the card has */
  Nos sirve para controlar cuantos puertos tiene la tarjeta
+ int has_ec;   /* Does the card have an Echo Canceller  */  Si tiene cancelador de eco o no
+ enum cards_ids card_type; /* Card type – Digium B410P, … */  Y para poder discriminar que tipo de tarjeta tenemos en uso
 };
 
-static struct devtype wcb4xxp = { “Wildcard B410P”, 0 };

+static struct devtype wcb4xxp = { “Wildcard B410P”, .ports = 4, .has_ec = 1, .card_type = B410P  };
+static struct devtype hfc2s = { “HFC-2S Junghanns.NET duoBRI PCI”, .ports = 2, .has_ec = 0, .card_type = B200P_HFC };
+static struct devtype hfc4s = { “HFC-4S Junghanns.NET quadBRI PCI”, .ports = 4, .has_ec = 0, .card_type = B400P_HFC };
+static struct devtype hfc8s = { “HFC-4S Junghanns.NET octoBRI PCI”, .ports = 8, .has_ec = 0, .card_type = B800P_HFC };
+static struct devtype hfc2s_OV ={ “OpenVox B200P”, .ports = 2, .has_ec = 0, .card_type = B200P_OV };
+static struct devtype hfc4s_OV ={ “OpenVox B400P”, .ports = 4, .has_ec = 0, .card_type = B400P_OV };
+static struct devtype hfc8s_OV ={ “OpenVox B800P”, .ports = 8, .has_ec = 0, .card_type = B800P_OV };
 

Por otro lado…

 /* TODO: determine whether this is a 2, 4 or 8 port card */
– b4->numspans = 4;

cambiamos….
+ b4->numspans = dt->ports;
+ vpmsupport = dt->has_ec;

A partir de aquí, ya tenemos control de varios parámetros que el controlador original no realiza. Nº de puertos, cancelador de eco, marca y modelo (Nótese que el card_type está un poco basado en el nomenclator de OpenVox, o sea que el primero que llega gana, jeje).

Decía antes que la mayoría de las tarjetas no tienen cancelador de eco. Entonces no se debe inicializar lo que no se tiene

static void ec_init(struct b4xxp *b4)
  unsigned char b;
  unsigned int i, j, mask;
 
+ if (! b4->has_ec)   /* Avoid Echo Cancelling for non hardware echo canceller cards */ Si no hay cancelador de eco, nos vamos.
+  return;  

 Y otra diferencia “interesante” entre la B410P y las demás tarjetas. El oscilador. El reloj de las B410P funciona a la mitad de frecuencia que el resto de tarjetas, así que tendremos que explicarle al driver que trabaje en función del reloj que tenemos.

  * set up the clock controller
  * we have a 24.576MHz crystal, so the PCM clock is 2x the incoming clock.
En el caso de las B410P, claro está. Las OpenVox y las Junghanns tienen el cuarzo a 49.152
  */
– b4xxp_setreg8(b4, R_BRG_PCM_CFG, 0x02);

Modificamos y queda tal que así 
+ if (b4->card_type == B410P) /* Per card config…. J.A. Deniz */
+     b4xxp_setreg8(b4, R_BRG_PCM_CFG,0x02);
+ else
+     b4xxp_setreg8(b4, R_BRG_PCM_CFG, V_PCM_CLK);

Así que tengamos lo que tengamos, tenemos el reloj PCM correctamente configurado. Otra de las diferencias entre Digiun y el resto de los fabricantes es la señalización TE/NT.

–  nt = ((gpio & (1 << (i + 4))) == 0);  /* GPIO=0 = NT mode */

modificamos …

+  /* The way the Digium B410P card reads the NT/TE mode
+   * jumper is the oposite of how other HFC-4S cards do:
+   * – In B410P: GPIO=0: NT
+   * – In Junghanns: GPIO=0: TE
+   */
+  if (b4->card_type == B410P) /* Per card basis – J.A.Deniz */
+   nt = ((gpio & (1 << (i + 4))) == 0); 

+  else
+   nt = ((gpio & (1 << (i + 4))) != 0);
Señalizamos de una forma u otra según quien es el padre de la criatura
+
   s->te_mode = !nt;

Más cosas que cambian. En principio el driver estaba hecho para las B410P, así que para que liarnos con una variable, si son siempre 4 puertos. Pero no siempre son 4 puertos en las demás tarjetas, así que toca cambiar más código.

–  for (j=0; j < 4; j++) {
+  for (j=0; j < b4->numspans; j++) {

De estos cambios hay unos cuantos a lo largo  del código, ya que hay varios puntos donde se puso 4 tal cual. Con esto ya tenemos las HFC-4S funcionando (tanto de dos como de cuatro puertos, pero áún nos falta algo más. Los leds de señalización andan “living la vida loca”. Si claro, también gestionan los leds de distinta manera… Así que creamos una función nueva para gestión de leds para no estorbarnos con las funciones de la B410P.

+static void b4xxp_update_leds_hfc(struct b4xxp *b4)
+{
+ int i, leds;
+ int led[4]; /* FIXME: Could an eight ports card work???? Better making its own function in per card basis */
+ struct b4xxp_span *bspan;

+ b4->blinktimer++;
+ for (i=0; i < b4->numspans; i++) {
+  bspan = &b4->spans[i];
+
+  if (bspan->span.flags & DAHDI_FLAG_RUNNING) {
+   if (bspan->span.alarms) {
+    if (b4->blinktimer == (led_fader_table[b4->alarmpos] >> 1))
+     led[i] = 2;
+
+    if (b4->blinktimer == 0xf)
+     led[i] = 0;
+   } else if (bspan->span.mainttimer || bspan->span.maintstat) {
+    if (b4->blinktimer == (led_fader_table[b4->alarmpos] >> 1))
+     led[i] = 1;
+
+    if (b4->blinktimer == 0xf)
+     led[i] = 0;
+   } else {
+    /* No Alarm */
+    led[i] = 1;
+   }
+  }
+  else
+   led[i] = 0;
+ }
+
+ /* FIXME: Each card manage leds in a different way. So one section per card’s variety
+  * So we can support partially a card without led writing. Looking card status via software
+  * We might add leds signalling only for cards reported leds working ok
+  * Better no leds than breaking system with wrong instructions – J.A. Deniz */

+ if (b4->card_type == B400P_OV) {
+  leds = ((led[0] > 0) << 0) | ((led[1] > 0) << 1) |
+   ((led[2] > 0) << 2) | ((led[3] > 0) << 3) |
+   ((led[0] & 1) << 4) | ((led[1] & 1) << 5) |
+   ((led[2] & 1) << 6) | ((led[3] & 1) << 7); /* changed by james.zhu to set the leds of B400P */
+  b4xxp_setreg8(b4, R_GPIO_EN1, leds & 0x0f);
+  b4xxp_setreg8(b4, R_GPIO_OUT1, leds  >> 4);
+ }
+  
+ else if (b4->card_type  == B200P_OV) {
+  b4xxp_setreg8(b4, R_GPIO_SEL,0x20 | 0x10);
+  b4xxp_setreg8(b4, R_GPIO_EN1,0xf);
+  b4xxp_setreg8(b4, R_GPIO_OUT1,
+    (led[0] | (led[1] << 1) | (led[2] << 2) |
+     (led[3] << 3))); /*  Tested by J.A. Deniz */
+ }
+
+ else if (b4->card_type  == B200P_HFC) {
+  b4xxp_setreg8(b4, R_GPIO_SEL,0x20 | 0x10);
+  b4xxp_setreg8(b4, R_GPIO_EN1,0xf);
+  b4xxp_setreg8(b4, R_GPIO_OUT1,
+    (led[0] | (led[1] << 1) | (led[2] << 2) |
+     (led[3] << 3))); /* UNTESTED */
+ }
+
+ else if (b4->card_type  == B400P_HFC) {
+  leds = ((led[0] > 0) << 0) | ((led[1] > 0) << 1) |
+   ((led[2] > 0) << 2) | ((led[3] > 0) << 3) |
+   ((led[0] & 1) << 4) | ((led[1] & 1) << 5) |
+   ((led[2] & 1) << 6) | ((led[3] & 1) << 7); /* UNTESTED */
+  b4xxp_setreg8(b4, R_GPIO_EN1, leds & 0x0f);
+  b4xxp_setreg8(b4, R_GPIO_OUT1, leds  >> 4);
+ }
+
+ if (b4->blinktimer == 0xf) {
+  b4->blinktimer = -1;
+  b4->alarmpos++;
+  if (b4->alarmpos >= (sizeof(led_fader_table) /
+     sizeof(led_fader_table[0])))
+   b4->alarmpos = 0;
+ }
+
+}

Pues nada nos creamos una función que gestione los leds en función de cada tipo de tarjeta HFC-4S que tengamos diferente a la B410P y lo llamamos desde la rutina de señalización

+  if (b4->card_type != B410P) {
+   /* Use the alternative function for non-Digium cards: */
+   b4xxp_update_leds_hfc(b4);
+   return;
+  }
+

Con eso más o menos tenemos resuelta la parte de gestión de las HFC-4S. Así que tenemos un driver Dahdi medianamente operativo. Y ahora empezamos con la parte de las HFC-8S. Aquí la cosa cambia un poco, un chip diferente, aunque con muchas similitudes al HFC-4S. Tantas que la mayor parte de cosas que tocamos es para modificar código que no fue escrito pensando en una cantidad de puertos diferente a cuatro. El manual de los chips, por cierto, está disponible aquí. En primer lugar, el chip es diferente así que le decimos al driver que también “es de los nuestros”

  x = b4xxp_getreg8(b4, R_CHIP_ID);
– if (x != 0xc0) {    /* wrong chip? */
+ if ((x != 0xc0) && ( x != 0x80)) {  /* wrong chip? */
Agregamos soporte para las HFC-8S: 0xc0->HFC-4S y 0x80 es HFC-8S
   dev_err(&pdev->dev, “Unknown/unsupported controller detected (R_CHIP_ID = 0x%02x)\n”, x);
   goto err_out_free_mem;

Ahora que ya tenemos soportado el chip, vamos a ver como varía la inicialización del mismo. En la sección 1.3.2 del datasheet de los chips vemos que una de las principales diferencias entre el HFC-4S y el HFC-8S es que determinados punteros quedan libres (obvio, porque los puertos del 5 al 8 no existen). Veamos como adaptar esa circunstancia a nuestro código.

static void hfc_gpio_init(struct b4xxp *
 
  mb();
 
– b4xxp_setreg8(b4, R_GPIO_SEL, 0xf0); /* GPIO0..7 S/T, 8..15 GPIO */

modificamos…

static void hfc_gpio_init(struct b4xxp *
 
  mb();
 
– b4xxp_setreg8(b4, R_GPIO_SEL, 0xf0); /* GPIO0..7 S/T, 8..15 GPIO */
+ if ((b4->card_type == B800P_HFC) || (b4->card_type == B800P_OV))
+     {
+     b4xxp_setreg8(b4, R_GPIO_SEL, 0x00); /* GPIO0..15 S/T – HFC-8S uses GPIO8-15 for S/T ports 5-8 */
+     }
+     else
+     {
+     b4xxp_setreg8(b4, R_GPIO_SEL, 0xf0); /* GPIO0..7 S/T, 8..15 GPIO */
+     }

En la versión de 8 puertos necesitamos todas esas salidas asignadas a los S/T, ya que si no lo hacemos simplemente no tenemos información de los puertos altos. Y el otro gran cambio, que va en función de como se programó el conjunto de FIFOs en el driver. Veamos la información del mismo.

/*
 * set up the flow controller.
 * B channel map:
 * FIFO 0 connects Port 1 B0 using HFC channel 16 and PCM timeslots 0/1.
 * FIFO 1 connects Port 1 B1 using HFC channel 17 and PCM timeslots 4/5.
 * FIFO 2 connects Port 2 B0 using HFC channel 20 and PCM timeslots 8/9.
 * FIFO 3 connects Port 2 B1 using HFC channel 21 and PCM timeslots 12/13.
 * FIFO 4 connects Port 3 B0 using HFC channel 24 and PCM timeslots 16/17.
 * FIFO 5 connects Port 3 B1 using HFC channel 25 and PCM timeslots 20/21.
 * FIFO 6 connects Port 4 B0 using HFC channel 28 and PCM timeslots 24/25.
 * FIFO 7 connects Port 4 B1 using HFC channel 29 and PCM timeslots 28/29.
 *
 * All B channel FIFOs have their HDLC controller in transparent mode,
 * and only the FIFO for B0 on each port has its interrupt operational.
 *
 * D channels are handled by FIFOs 8-11.
 * FIFO 8 connects Port 1 D using HFC channel 3
 * FIFO 9 connects Port 1 D using HFC channel 7
 * FIFO 10 connects Port 1 D using HFC channel 11
 * FIFO 11 connects Port 1 D using HFC channel 15
 *
 * D channel FIFOs are operated in HDLC mode and interrupt on end of frame.

Precioso. Dos cosas. 1. Si se utiliza el modo No-EC (sin cancelador de eco por hardware) esto no es realmente cierto, pero se olvidaron de documentarlo… El fútbol es así.. no hay rival pequeño…son once contra once… Un clásico, jeje. El que quiera verlo mas en profundidad puede analizar hfc_assign_bchan_fifo_ec y hfc_assign_bchan_fifo_noec y ver las diferencias entre ambas. Así que sin cancelador de eco (que es la estructura que a nosotros nos interesa) quedaría tal que así:

/*
 * set up the flow controller.
 * B channel map:
 * FIFO 0 connects Port 1 B0 using HFC channel 1 and PCM timeslots 0/1.
 * FIFO 1 connects Port 1 B1 using HFC channel 2 and PCM timeslots 4/5.
 * FIFO 2 connects Port 2 B0 using HFC channel 4 and PCM timeslots 8/9.
 * FIFO 3 connects Port 2 B1 using HFC channel 5 and PCM timeslots 12/13.
 * FIFO 4 connects Port 3 B0 using HFC channel 8 and PCM timeslots 16/17.
 * FIFO 5 connects Port 3 B1 using HFC channel 9 and PCM timeslots 20/21.
 * FIFO 6 connects Port 4 B0 using HFC channel 12 and PCM timeslots 24/25.
 * FIFO 7 connects Port 4 B1 using HFC channel 13 and PCM timeslots 28/29.
 *
 * All B channel FIFOs have their HDLC controller in transparent mode,
 * and only the FIFO for B0 on each port has its interrupt operational.
 *
 * D channels are handled by FIFOs 8-11.
 * FIFO 8 connects Port 1 D using HFC channel 3
 * FIFO 9 connects Port 1 D using HFC channel 7
 * FIFO 10 connects Port 1 D using HFC channel 11
 * FIFO 11 connects Port 1 D using HFC channel 15
 *
 * D channel FIFOs are operated in HDLC mode and interrupt on end of frame
.

De todos modos es el problema es el mismo. El FIFO 8 ( del canal D del puerto 1) se superpone con el del B0 del canal 5. A grandes males grandes remedios… Recompongamos la tabla FIFO con los menores cambios posibles. Ahí vamos. Primero subimos los canales FIFO de señalización si tenemos una HFC-8S para que en vez del 8 al 11 se situen del 16 al 23.

static void hfc_assign_dchan_fifo(struct  int fifo, hfc_chan;
  unsigned long irq_flags;
 
– fifo = port + 8;

cambiamos el código…

+ if ((b4->card_type == B800P_OV) || (b4->card_type == B800P_HFC))
+     {
+     fifo = port + 16;
Si es una HFC-8S motamos los FiFOs de los channels D a partir del 16
+     }
+     else
+     {
+     fifo = port + 8;
+     }

Y después modificamos aquellos sitios donde se dice que los FIFOs de los d-chan van del 8 al 11.

static void b4xxp_bottom_half(unsigned long data)
 {
  struct b4xxp *b4 = (struct b4xxp *)data;
– int i, j, k, gotrxfifo, fifo;
+ int i, j, k, gotrxfifo, fifo, fifo_low, fifo_high;
  unsigned char b, b2;
 
  if (b4->shutdown)
   return;
 
  gotrxfifo = 0;
+ if ( b4->numspans == 8 )  /* HFC-4S d-chan fifos 8-11 *** HFC-8S d-chan fifos 16-23 */
+     {
+     fifo_low = 16;
+     fifo_high = 23;
+     }
+     else
+     {
+     fifo_low = 8;
+     fifo_high = 11;
+     }
+
 
  for (i=0; i < 8; i++) {
   b = b2 = b4->fifo_irqstatus[i];
@@ -2260,7 +2457,7 @@ static void b4xxp_bottom_half(unsigned l
    fifo = i*4 + j;
 
    if (b & V_IRQ_FIFOx_TX) {
–    if (fifo >=8 && fifo <= 11) {  /* d-chan fifo */
+    if (fifo >= fifo_low && fifo <= fifo_high) {  /* d-chan fifo Los canales FIFO se calculan en función del tipo de chip….

………………………………………………….

static int b4xxp_proc_read_one(char *buf
  sprintf(sBuf, “Card %d, PCI identifier %s, IRQ %d\n”, b4->cardno + 1, b4->dev->bus_id, b4->irq);
 
  strcat(sBuf,”Tx:\n”);
– for (j=0; j<8; j++) {
–  for (i=0; i<12; i++) {
—–modificamos….
+ for (j=0; j<(b4->numspans * 2) ; j++) {   /* B Channels */  La cantidad de canales B depende obviamente de cuantos puertos tenemos…
+  for (i=0; i<(b4->numspans * 3) ; i++) {  /* All Channels */
    chan = b4->spans[i/3].chans[i%3];
    sprintf(str, “%02x “, chan->writechunk[j]);
    strcat(sBuf, str);

 Algunos otros puntos donde se modifican estos valores están también en el fichero diff. Creo que no es necesario repetirlos todos uno por uno. Agregamos las secciones necesarias para controlar la señálización led en las HFC-8S y listo.

+static void b4xxp_update_leds_hfc_8s(struct b4xxp *b4)
+{
+ unsigned long lled;
+ unsigned long leddw;
+ int i;
+ struct b4xxp_span *bspan;
+ lled = 0;

+ b4->blinktimer++;
+ for (i=0; i < 8; i++) {
+  bspan = &b4->spans[i];
+  if (bspan->span.flags & DAHDI_FLAG_RUNNING) {
+   if (bspan->span.alarms) {
+    if (b4->blinktimer == (led_fader_table[b4->alarmpos] >> 1))
+     lled |= 1 << i;
+    if (b4->blinktimer == 0xf)
+     lled |= 1 << i;
+   } else if (bspan->span.mainttimer || bspan->span.maintstat) {
+    if (b4->blinktimer == (led_fader_table[b4->alarmpos] >> 1))
+    lled |= 1 << i;
+    if (b4->blinktimer == 0xf)
+     lled |= 1 << i;
+   } else {
+    /* No Alarm */
+    lled |= 1 << i;
+   }
+  }
+  else
+   lled |= 1 << i;
+ }
+  /* Write Leds…*/
+  leddw = lled << 24 | lled << 16 | lled << 8 | lled;
+  b4xxp_setreg8(b4, R_BRG_PCM_CFG, 0x21);
+  iowrite16(0x4000, b4->ioaddr + 4);
+  iowrite32(leddw, b4->ioaddr);
+  b4xxp_setreg8(b4, R_BRG_PCM_CFG, 0x20);

+ if (b4->blinktimer == 0xf) {
+  b4->blinktimer = -1;
+  b4->alarmpos++;
+  if (b4->alarmpos >= (sizeof(led_fader_table) /
+     sizeof(led_fader_table[0])))
+   b4->alarmpos = 0;
+ }
+} 
 static void b4xxp_update_leds(struct b4xxp *b4)
 {
  int i;
  struct b4xxp_span *bspan;
 
+ if (b4->numspans == 8) {
+      b4xxp_update_leds_hfc_8s(b4); /* Alternative function for 8 ports cards */
+  return;
+ }
+

Y estos son, en resumen, los cambios que lleva el driver wcb4xxp para funcionar con “las parientas” de la B410P. Pendiente queda aún revisar la señalización y los cambios que puedan producirse después de probarse a fondo, que todavía aparece algo.

Posted in Asterisk, DAHDI, HFC-4S, HFC-8S | 4 Comments »

Un parchecito – Soporte DAHDI para tarjetas basadas en HFC-4S

Posted by odicha en 4 marzo 2009

Pues hoy publiqué en el grupo de Asterisk-es una entrada a ver si consigo que algun voluntario que disponga de alguna tarjeta de éstas colabore un poco y así entre todos se añade algo más de soporte a las olvidadas tarjetas RDSI que andan un poco de acá para allá en Asterisk.

Hay un request abierto en Digium con el nº 13987

A ver si aparece algún conejillo de indias que eche una mano. Por ahora tengo claro como hacerlas funcionar, es más, ya funcionan  la B200P y la B400P de OpenVox. Ahora falta completar el abanico de tarjetas y, lo que me parece que será más largo, conseguir que se termine integrando en el trunk de dahdi-linux. La esperanza es lo último que se pierde, dicen. Y si no, la Ley de Ellis: “El progreso es cambiar una incomodidad por otra” (bienaventurado sea Murphy)

Y si no aparece nadie ya tengo mis tarjetas en marcha…

Posted in Asterisk, DAHDI, HFC-4S | Leave a Comment »

Asterisk 1.6 + Dahdi + FreePBX – Configurando Lineas y extensiones

Posted by odicha en 4 marzo 2009

En el post anterior me quedé hablando con la señorita inglesa. Pues hoy empezaremos configurando las RDSI y las extensiones.

Empezamos por las lineas. Echamos un vistazo via consola a los canales registrados en Dahdi.

asterisk*CLI> dahdi show channels
   Chan Extension  Context         Language   MOH Interpret        Blocked    State
 pseudo            default                    default                         In Service
      1            from-internal              default                         In Service
      2            from-internal              default                         In Service
      3            from-internal              default                         In Service
      4            from-internal              default                         In Service
      5            from-internal              default                         In Service
      6            from-internal              default                         In Service
      7            from-internal              default                         In Service
     13            from-pstn                  default                         In Service
     14            from-pstn                  default                         In Service
     16            from-pstn                  default                         In Service
     17            from-pstn                  default                         In Service

Pues tenemos extensiones del 1 al 7 y canales RDSI del 13 al 17.

Nos vamos a FreePBX desde el explorador y agregamos los enlaces del 13 al 17.

Pestaña Trunks -> Add ZAP Trunk (Dahdi compat..)

Ahí, por ahora, simplemente añadimos el número del enlace (del 13 al 17) , uno por cada Trunk que añadimos

anadir-enlaces

Y borramos el enlace ZAP/G0 que aparece por defecto en la instalación

Aplicamos los cambios y nos vamos a la pestaña extensiones.

Ahí añadiremos las extensiones. Yo para una pequeña central como esta usare un plan de tres cifras para las extensiones locales (101,102, etc)

Seleccionamos “Add generic ZAP Device” y pulsamos Add

Ahi colocaremos por ahora el numero de extension en los campos “User extension” y “Display Name” y el numero de canal zap en “Channel”, repitiendo el proceso para cada extensión.

extensiones

Ya tenemos las extensiones y las líneas. Ahora crearemos unas reglas muy básicas de llamadas.

Comenzaremos con las llamadas al exterior. Tenemos 4 canales RDSI en dos puertos. El 13 y el 14 pertenecientes al puerto 1 de la B200P y el 16 y 17 al puerto 2. Supongamos que tenemos un S0 de un operador nacional fijo en el puerto 1 y un enlace RDSI de telefonía móvil en el 2, y que tenemos un plan de numeración privado de 4 cifras para los móviles. Crearemos tres rutas salientes: Fijos para las llamadas a números nacionales e internacionales, Móviles para las llamadas a móviles y Corporativo para las llamadas a nuestros móviles de la empresa. Nos vamos pues a la pestaña Outbound Routes en FreePBX y añadimos una nueva ruta.

nueva-ruta1ruta-entrante1

Bueno, que hacemos aquí. Primero le damos un nombre (Fijos). Ponemos los patrones de marcado que la ruta cumplirá 0|0. // 0|1.// 0|8. //0|9. ¿Y esto que significa?  0| es el prefijo. Así al igual que en una central tradicional marcamos el cero para “coger” línea desde nuestras extensiones comenzaremos marcando el cero que será descartado al marcar en la línea. 0. 1. …. pues las reglas que han de cumplirse. 0 para internacionales 1,8 y 9 para llamadas nacionales. Así si marcamos por ejemplo 0928123456 el sistema descartará el cero inicial y como coincide con el patrón 9. (el punto es “lo que quieras marcar detrás de esto) intentará marcar 928123456 por uno de los enlaces externos que marcamos en Trunk sequence (en el caso de los fijos el 13 y el 14)

Repetimos la operación con 0|6. – Moviles – usando los trunks 16 y 17, y creamos por último el corporativo. Aquí una diferencia. 0|2xxx 0|3xxx 0|4xxx 0|5xxx 0|7xxx. El sistema reconocerá que es un nímero de 4 cifras en vez de usar el punto como en los otros.

Por último eliminamos la ruta 9 outside que viene creada por defecto y ordenamos las rutas dejando como primera la de los corporativos. Luego veremos por qué. Aplicamos los cambios y probamos la configuración llamando a un número fijo un móvil y un corporativo y comprobando que los números que nos llaman son los que esperamos.

Y ahora, tendremos que recibir alguna llamada. Así que creamos una regla Catch All. Todas las llamadas que recibamos irán a los operadores, que están en las extensiones 101 y 102. Por ahora (despúes iremos haciendo cambios) creamos un grupo de llamada llamado Recepcion que contiene las extensiones 101 y 102. Ring Groups -> Add Group en FreePBX.

ring-group

Lo parametrizamos por ahora tal y como vemos en la imagen superior. (nombre Recepcion, Ring time 60, extensiones 101 y 102) Guardamos los cambios y creamos una regla para llamadas entrantes. En FreePBX vamos a la pestaña “Inbound Routes”. Agregamos una nueva ruta. En descripción ponemos “General” por ejemplo y en destino el grupo de llamada que acabamos de crear “600(Recepcion)”.

ruta-entrante1

Guardamos los cambios y probamos que suenen las extensiones al recibir una llamada entrante.

Ya tenemos una funcionalidad básica en nuestra centralita (más o menos la misma que cualquier central básica tradicional de las que se ven por ahí)

Ahora tocaría enredar un poco con el encaminamiento de llamadas entrantes, captura de extensiones, etc. pero por hoy queda aquí.

 

Posted in Asterisk, DAHDI, FreePBX, HFC-4S | 5 Comments »