--- chan_skinny.c.47622 2006-11-15 06:56:57.000000000 +1100 +++ chan_skinny.c 2006-12-12 06:56:29.000000000 +1100 @@ -905,6 +905,7 @@ /* time_t lastouttime; */ /* Unused */ int progress; int ringing; + int ringtype; int onhold; /* int lastout; */ /* Unused */ int cxmode; @@ -914,6 +915,8 @@ struct skinny_subchannel *next; struct skinny_line *parent; + struct skinny_device *ringdevice; + //struct skinny_device *active_device; }; struct skinny_line { @@ -946,7 +949,6 @@ int hidecallerid; int amaflags; int type; - int instance; int group; int needdestroy; int capability; @@ -956,12 +958,16 @@ int immediate; int hookstate; int nat; + int sharedline; + int ringing; + int status; struct ast_codec_pref prefs; struct skinny_subchannel *sub; struct skinny_line *next; - struct skinny_device *parent; -}; + struct skinny_device_line *devices; + struct skinny_device *active_device; +} *lines; struct skinny_speeddial { ast_mutex_t lock; @@ -981,6 +987,16 @@ struct skinny_device *parent; }; +struct skinny_device_line { + /* The link between devices and lines, allows for multiple to multiple relationship */ + struct skinny_device *device; + struct skinny_line *line; + struct skinny_device_line *next_line_on_device; + struct skinny_device_line *next_device_on_line; + struct skinny_device_line *next; + int instance; +} *device_lines = NULL; + static struct skinny_device { /* A device containing one or more lines */ char name[80]; @@ -991,15 +1007,19 @@ int lastlineinstance; int lastcallreference; int capability; + int hookstate; struct sockaddr_in addr; struct in_addr ourip; - struct skinny_line *lines; + struct skinny_device_line *lines; struct skinny_speeddial *speeddials; struct skinny_addon *addons; struct ast_codec_pref prefs; struct ast_ha *ha; struct skinnysession *session; struct skinny_device *next; + struct skinny_line *ringingline; + struct skinny_line *active_line; + struct skinny_subchannel *active_sub; } *devices = NULL; struct skinny_paging_device { @@ -1076,10 +1096,10 @@ case SKINNY_DEVICE_12SP: case SKINNY_DEVICE_12: /* 6 rows, 2 columns */ - for (i = 0; i < 2; i++) + for (i = 0; i < 3; i++) (btn++)->buttonDefinition = BT_LINE; (btn++)->buttonDefinition = BT_REDIAL; - for (i = 0; i < 3; i++) + for (i = 0; i < 2; i++) (btn++)->buttonDefinition = BT_SPEEDDIAL; (btn++)->buttonDefinition = BT_HOLD; (btn++)->buttonDefinition = BT_TRANSFER; @@ -1180,23 +1200,24 @@ static struct skinny_line *find_line_by_instance(struct skinny_device *d, int instance) { - struct skinny_line *l; + struct skinny_device_line *dl; - for (l = d->lines; l; l = l->next) { - if (l->instance == instance) + for (dl = d->lines; dl; dl = dl->next_line_on_device) { + if (dl->instance == instance) break; } - if (!l) { + if (!dl) { ast_log(LOG_WARNING, "Could not find line with instance '%d' on device '%s'\n", instance, d->name); } - return l; + return dl->line; } static struct skinny_line *find_line_by_name(const char *dest) { - struct skinny_line *l; struct skinny_device *d; + struct skinny_device_line *dl; + struct skinny_line *l; char line[256]; char *at; char *device; @@ -1204,22 +1225,30 @@ ast_copy_string(line, dest, sizeof(line)); at = strchr(line, '@'); if (!at) { - ast_log(LOG_NOTICE, "Device '%s' has no @ (at) sign!\n", dest); - return NULL; + /* No @, so we are looking just for a line */ + l = lines; + while (l && strcasecmp(l->name, dest)) { + l = l->next; + } + return l; + /* ast_log(LOG_NOTICE, "Device '%s' has no @ (at) sign!\n", dest); + return NULL; */ } *at++ = '\0'; device = at; ast_mutex_lock(&devicelock); for (d = devices; d; d = d->next) { + ast_verbose("Searching for %s in %s\n", device, d->name); if (!strcasecmp(d->name, device)) { if (skinnydebug) ast_verbose("Found device: %s\n", d->name); /* Found the device */ - for (l = d->lines; l; l = l->next) { + for (dl = d->lines; dl; dl = dl->next_line_on_device) { + ast_verbose("Searching for %s in %s\n", line, dl->line->name); /* Search for the right line */ - if (!strcasecmp(l->name, line)) { + if (!strcasecmp(dl->line->name, line)) { ast_mutex_unlock(&devicelock); - return l; + return dl->line; } } } @@ -1253,11 +1282,11 @@ /* Find the subchannel when we only have the callid - this shouldn't happen often */ static struct skinny_subchannel *find_subchannel_by_reference(struct skinny_device *d, int reference) { - struct skinny_line *l; + struct skinny_device_line *dl; struct skinny_subchannel *sub = NULL; - for (l = d->lines; l; l = l->next) { - for (sub = l->sub; sub; sub = sub->next) { + for (dl = d->lines; dl; dl = dl->next_line_on_device) { + for (sub = dl->line->sub; sub; sub = sub->next) { if (sub->callid == reference) break; } @@ -1265,16 +1294,36 @@ break; } - if (!l) { + if (!dl) { ast_log(LOG_WARNING, "Could not find any lines that contained a subchannel with reference '%d' on device '%s'\n", reference, d->name); } else { if (!sub) { - ast_log(LOG_WARNING, "Could not find subchannel with reference '%d' on '%s@%s'\n", reference, l->name, d->name); + ast_log(LOG_WARNING, "Could not find subchannel with reference '%d' on '%s@%s'\n", reference, dl->line->name, d->name); } } return sub; } +static void skinny_destroy_sub (struct skinny_subchannel *sub) +{ + struct skinny_subchannel *tmpsub; + /* Name is a bit of a misnomer, we will just issue the command to destroy the channel and unlink */ + sub->alreadygone = 1; + if (sub->parent->sub == sub) { + sub->parent->sub = sub->next; + } else { + for (tmpsub = sub->parent->sub; tmpsub; tmpsub = tmpsub->next) { + if (tmpsub->next == sub) { + tmpsub->next = sub->next; + break; + } + } + } + if (sub->owner) { + ast_queue_hangup(sub->owner); + } +} + static struct skinny_speeddial *find_speeddial_by_instance(struct skinny_device *d, int instance) { struct skinny_speeddial *sd; @@ -1290,6 +1339,17 @@ return sd; } +static struct skinny_device_line *find_dl(struct skinny_device *d, struct skinny_line *l) +{ + struct skinny_device_line *dl; + for (dl = d->lines; dl; dl = dl->next_line_on_device) { + if (dl->line == l) { + break; + } + } + return dl; +} + static int codec_skinny2ast(enum skinny_codecs skinnycodec) { switch (skinnycodec) { @@ -1395,7 +1455,7 @@ memset(s->outbuf,0,sizeof(s->outbuf)); memcpy(s->outbuf, req, skinny_header_size); - memcpy(s->outbuf+skinny_header_size, &req->data, sizeof(union skinny_data)); + memcpy(s->outbuf+skinny_header_size, &req->data, letohl(req->len)); res = write(s->fd, s->outbuf, letohl(req->len)+8); @@ -1442,9 +1502,9 @@ if (!(req = req_alloc(sizeof(struct call_state_message), CALL_STATE_MESSAGE))) return; - if (state == SKINNY_ONHOOK) { +/* if (state == SKINNY_ONHOOK) { transmit_speaker_mode(s, SKINNY_SPEAKEROFF); - } + }*/ req->data.callstate.callState = htolel(state); req->data.callstate.lineInstance = htolel(instance); req->data.callstate.callReference = htolel(callid); @@ -1567,6 +1627,9 @@ if (!(req = req_alloc(sizeof(struct set_lamp_message), SET_LAMP_MESSAGE))) return; + if (skinnydebug) + ast_verbose("Setting lamp device '%s'(%d) to '%d'.\n", s->device->name, instance, indication); + req->data.setlamp.stimulus = htolel(stimulus); req->data.setlamp.stimulusInstance = htolel(instance); req->data.setlamp.deviceStimulus = htolel(indication); @@ -1578,7 +1641,7 @@ struct skinny_req *req; if (skinnydebug) - ast_verbose("Setting ringer mode to '%d'.\n", mode); + ast_verbose("Setting ringer mode on '%s' to '%d'.\n", s->device->name, mode); if (!(req = req_alloc(sizeof(struct set_ringer_message), SET_RINGER_MESSAGE))) return; @@ -1893,22 +1956,21 @@ static int skinny_show_devices(int fd, int argc, char *argv[]) { struct skinny_device *d; - struct skinny_line *l; + struct skinny_device_line *dl; int numlines = 0; if (argc != 3) { return RESULT_SHOWUSAGE; } ast_mutex_lock(&devicelock); - + ast_cli(fd, "Name DeviceId IP Type R NL\n"); ast_cli(fd, "-------------------- ---------------- --------------- --------------- - --\n"); for (d = devices; d; d = d->next) { numlines = 0; - for (l = d->lines; l; l = l->next) { + for (dl = d->lines; dl; dl = dl->next_line_on_device) { numlines++; } - ast_cli(fd, "%-20s %-16s %-15s %-15s %c %2d\n", d->name, d->id, @@ -1923,23 +1985,80 @@ static int skinny_show_lines(int fd, int argc, char *argv[]) { - struct skinny_device *d; struct skinny_line *l; + struct skinny_device_line *dl; if (argc != 3) { return RESULT_SHOWUSAGE; } ast_mutex_lock(&devicelock); - ast_cli(fd, "Device Name Instance Name Label \n"); - ast_cli(fd, "-------------------- -------- -------------------- --------------------\n"); + ast_cli(fd, "Device Name Instance Name Label Active Device\n"); + ast_cli(fd, "-------------------- -------- -------------------- -------------------- -------------\n"); + for (l = lines; l; l = l->next) { + for (dl = l->devices; dl; dl = dl->next_device_on_line) { + ast_cli(fd, "%-20s %8d %-20s %-20s %-20s\n", + dl->device->name, + dl->instance, + dl->line->name, + dl->line->label, + dl->line->active_device?dl->line->active_device->name:"-"); + } + } + + ast_mutex_unlock(&devicelock); + return RESULT_SUCCESS; +} + +static int skinny_show_status(int fd, int argc, char *argv[]) +{ + struct skinny_device *d; + struct skinny_line *l; + struct skinny_device_line *dl; + struct skinny_subchannel *sub; + int numlines = 0; + + if (argc != 3) { + return RESULT_SHOWUSAGE; + } + ast_mutex_lock(&devicelock); + + ast_cli(fd, "Devices:\n"); + ast_cli(fd, "Name Hook Ringing Line Sub R NL\n"); + ast_cli(fd, "---------- ----- ---------- ---------- ---------- - --\n"); for (d = devices; d; d = d->next) { - for (l = d->lines; l; l = l->next) { - ast_cli(fd, "%-20s %8d %-20s %-20s\n", + numlines = 0; + for (dl = d->lines; dl; dl = dl->next_line_on_device) { + numlines++; + } + ast_cli(fd, "%-10s %-5s %-10s %-10s %-10s %c %2d\n", d->name, - l->instance, - l->name, - l->label); + d->hookstate==SKINNY_ONHOOK?"On":"Off", + d->ringingline?d->ringingline->name:"-", + d->active_line?d->active_line->name:"-", + d->active_sub?d->active_sub->parent->name:"-", + d->registered?'Y':'N', + numlines); + } + + ast_cli(fd, "\nLines: Subs:\n"); + ast_cli(fd, "Line Device Hook Shrd Ring Device Hold\n"); + ast_cli(fd, "---------- ---------- ---- ---- ---- ---------- ----\n"); + for (l = lines; l; l = l->next) { + ast_cli(fd, "%-10s %-10s %-4s %-4s %-4s %-10s %-4s\n", + l->name, + l->active_device?l->active_device->name:"-", + l->hookstate?"On":"Off", + l->sharedline?"Yes":"No", + l->ringing?"Yes":"No", + l->sub?(l->sub->ringdevice?l->sub->ringdevice->name:"-"):"-", + l->sub?(l->sub->onhold==1?"Yes":"No"):"-"); + if (l->sub) { + for (sub = l->sub->next; sub; sub = sub->next) { + ast_cli(fd, " %-10s %-4s\n", + l->sub?(l->sub->ringdevice?l->sub->ringdevice->name:"-"):"-", + l->sub?(l->sub->onhold==1?"Yes":"No"):"-"); + } } } @@ -1955,6 +2074,10 @@ "Usage: skinny show lines\n" " Lists all lines known to the Skinny subsystem.\n"; +static char show_status_usage[] = +"Usage: skinny show status\n" +" Lists status of devices and lines on Skinny subsystem.\n"; + static char debug_usage[] = "Usage: skinny set debug\n" " Enables dumping of Skinny packets for debugging purposes\n"; @@ -1976,6 +2099,10 @@ skinny_show_lines, "List defined Skinny lines per device", show_lines_usage }, + { { "skinny", "show", "status", NULL }, + skinny_show_status, "Lists status of devices and lines", + show_status_usage }, + { { "skinny", "set", "debug", NULL }, skinny_do_debug, "Enable Skinny debugging", debug_usage }, @@ -1999,6 +2126,7 @@ static struct skinny_device *build_device(const char *cat, struct ast_variable *v) { struct skinny_device *d; + struct skinny_device_line *dl, *dltmp; struct skinny_line *l; struct skinny_speeddial *sd; struct skinny_addon *a; @@ -2011,6 +2139,7 @@ } else { ast_copy_string(d->name, cat, sizeof(d->name)); d->lastlineinstance = 1; + d->hookstate = SKINNY_ONHOOK; d->capability = default_capability; d->prefs = default_prefs; while(v) { @@ -2109,9 +2238,20 @@ d->addons = a; } } else if (!strcasecmp(v->name, "trunk") || !strcasecmp(v->name, "line")) { - if (!(l = ast_calloc(1, sizeof(struct skinny_line)))) { + + if (!(dl = ast_calloc(1, sizeof(struct skinny_device_line)))) { return NULL; - } else { + } + dl->next = device_lines; + device_lines = dl; + l = find_line_by_name(v->value); + + if (!l) { + /* line needs creating */ + if (!(l = ast_calloc(1, sizeof(struct skinny_line)))) { + /* destroy the dl created */ + return NULL; + } ast_mutex_init(&l->lock); ast_copy_string(l->name, v->value, sizeof(l->name)); @@ -2132,7 +2272,6 @@ l->msgstate = -1; l->capability = d->capability; l->prefs = d->prefs; - l->parent = d; if (!strcasecmp(v->name, "trunk")) { l->type = TYPE_TRUNK; } else { @@ -2148,13 +2287,32 @@ l->threewaycalling = threewaycalling; l->mwiblink = mwiblink; l->onhooktime = time(NULL); - l->instance = lineInstance++; + l->sharedline = 0; /* ASSUME we're onhook at this point */ l->hookstate = SKINNY_ONHOOK; l->nat = nat; - - l->next = d->lines; - d->lines = l; + l->ringing = 0; + l->next = lines; + lines = l; + } + /* line existed or has been created, so link */ + dl->device = d; + dl->line = l; + dl->instance = lineInstance++; + /* Add lines on device in correct order for pecking order */ + if (d->lines) { + dltmp = d->lines; + while (dltmp->next_line_on_device) { + dltmp = dltmp->next_line_on_device; + } + dltmp->next_line_on_device = dl; + } else { + d->lines = dl; + } + dl->next_device_on_line = l->devices; + l->devices = dl; + if (l->devices->next_device_on_line) { + l->sharedline = 1; } } else { ast_log(LOG_WARNING, "Don't know keyword '%s' at line %d\n", v->name, v->lineno); @@ -2187,7 +2345,7 @@ static void start_rtp(struct skinny_subchannel *sub) { struct skinny_line *l = sub->parent; - struct skinny_device *d = l->parent; + struct skinny_device *d = l->active_device; int hasvideo = 0; ast_mutex_lock(&sub->lock); @@ -2224,7 +2382,7 @@ struct ast_channel *c = data; struct skinny_subchannel *sub = c->tech_pvt; struct skinny_line *l = sub->parent; - struct skinny_device *d = l->parent; + struct skinny_device *d = l->active_device; struct skinnysession *s = d->session; int res = 0; @@ -2247,7 +2405,7 @@ struct ast_channel *c = data; struct skinny_subchannel *sub = c->tech_pvt; struct skinny_line *l = sub->parent; - struct skinny_device *d = l->parent; + struct skinny_device *d = l->active_device; struct skinnysession *s = d->session; char exten[AST_MAX_EXTENSION] = ""; int len = 0; @@ -2329,22 +2487,165 @@ return NULL; } +#define LINESTATUS_EMPTY 0 +#define LINESTATUS_RINGING 1 +#define LINESTATUS_CONNECTED 2 +#define LINESTATUS_ONHOLD 3 +#define LINESTATUS_HANGINGUP 4 + +static void skinny_change_line_status(struct skinny_line *l, struct skinny_subchannel *sub, struct ast_channel *ast, int newlinestatus) +{ + struct skinny_device_line *dl; + struct skinny_device *d; + struct skinnysession *s; + int tone; + + if (!l) { + ast_log(LOG_DEBUG, "No line provided, returning\n"); + return; + } + + if (skinnydebug) { + ast_verbose("Changing line status on '%s' from %d to %d\n",l->name, l->status, newlinestatus); + } + switch (newlinestatus) { + case LINESTATUS_EMPTY: + if (l->status == LINESTATUS_RINGING) { + l->ringing = 0; + l->status = LINESTATUS_EMPTY; + for (dl = l->devices; dl; dl = dl->next_device_on_line) { + if (dl->device->registered) { + s = dl->device->session; + transmit_ringer_mode(s, SKINNY_RING_OFF); + transmit_lamp_indication(s, STIMULUS_LINE, dl->instance, SKINNY_LAMP_OFF); + if (dl->device->ringingline == l) { + dl->device->ringingline = NULL; + transmit_displaymessage(s, NULL); + // search for other lines to start ringing on this device + } + //transmit_tone(s, SKINNY_SILENCE); + } + } + } else { + l->status = LINESTATUS_EMPTY; + for (dl = l->devices; dl; dl = dl->next_device_on_line) { + if (dl->device->registered) { + s = dl->device->session; + transmit_lamp_indication(s, STIMULUS_LINE, dl->instance, SKINNY_LAMP_OFF); + if (dl->device->active_line == l) { + transmit_displaymessage(s, NULL); + // search for other lines to start ringing on this device + } + } + } + } + break; + case LINESTATUS_RINGING: + if (l->status == LINESTATUS_EMPTY) { + /* Ringing line */ + l->status = LINESTATUS_RINGING; + l->ringing = 1; + for (dl=l->devices; dl; dl=dl->next_device_on_line) { + if (dl->device->registered) { + d = dl->device; + s = d->session; + transmit_lamp_indication(s, STIMULUS_LINE, dl->instance, SKINNY_LAMP_BLINK); + transmit_selectsoftkeys(s, dl->instance, sub->callid, KEYDEF_RINGIN); + if (!sub->ringdevice || (sub->ringdevice == d)) { + switch (d->hookstate) { + case SKINNY_OFFHOOK: + tone = SKINNY_CALLWAITTONE; + transmit_tone(s, tone); + break; + case SKINNY_ONHOOK: + tone = SKINNY_ALERT; + transmit_ringer_mode(s, sub->ringtype); + transmit_tone(s, tone); + transmit_callinfo(s, ast->cid.cid_name, ast->cid.cid_num, l->cid_name, l->cid_num, dl->instance, sub->callid, 1); + transmit_callstate(s, dl->instance, SKINNY_RINGIN, sub->callid); + transmit_displaypromptstatus(s, "Ring-In", 0, dl->instance, sub->callid); + d->ringingline = l; + break; + default: + ast_log(LOG_ERROR, "Don't know how to deal with hookstate %d\n", l->hookstate); + break; + } + } + } + } + } else { + ast_log(LOG_DEBUG, "Shouldn't be able to go from %d to RINGING\n", l->status); + } + break; + case LINESTATUS_CONNECTED: + if (l->status == LINESTATUS_RINGING || l->status == LINESTATUS_EMPTY) { + /* Answering a ringing line, or creating new channel*/ + l->status = LINESTATUS_CONNECTED; + l->ringing = 0; + for (dl = l->devices; dl; dl = dl->next_device_on_line) { + if (dl->device->registered) { + s = dl->device->session; + transmit_ringer_mode(s, SKINNY_RING_OFF); + transmit_lamp_indication(s, STIMULUS_LINE, dl->instance, SKINNY_LAMP_ON); + if (dl->device->ringingline == l) { + dl->device->ringingline = NULL; + if (!(dl->device->active_line == l)) { + transmit_displaymessage(s, NULL); + } + } + //transmit_tone(s, SKINNY_SILENCE); + } + } + } else if (l->status == LINESTATUS_ONHOLD) { + /* Taking line off hold */ + l->status = LINESTATUS_CONNECTED; + for (dl = l->devices; dl; dl = dl->next_device_on_line) { + if (dl->device->registered) { + s = dl->device->session; + transmit_lamp_indication(s, STIMULUS_LINE, dl->instance, SKINNY_LAMP_ON); + } + } + } else { + ast_log(LOG_DEBUG, "Shouldn't be able to go from %d to CONNECTED\n", l->status); + } + break; + case LINESTATUS_ONHOLD: + if (l->status == LINESTATUS_CONNECTED) { + /* Putting line on hold */ + l->status = LINESTATUS_ONHOLD; + for (dl = l->devices; dl; dl = dl->next_device_on_line) { + if (dl->device->registered) { + s = dl->device->session; + transmit_lamp_indication(s, STIMULUS_LINE, dl->instance, SKINNY_LAMP_WINK); + } + } + } else { + ast_log(LOG_DEBUG, "Shouldn't be able to go from %d to ONHOLD\n", l->status); + } + break; + default: + ast_log(LOG_DEBUG, "Don't know how to handle %d\n", newlinestatus); + } +} static int skinny_call(struct ast_channel *ast, char *dest, int timeout) { int res = 0; - int tone = 0; struct skinny_subchannel *sub = ast->tech_pvt; struct skinny_line *l = sub->parent; - struct skinny_device *d = l->parent; - struct skinnysession *s = d->session; + struct skinny_device_line *dl; - if (!d->registered) { - ast_log(LOG_ERROR, "Device not registered, cannot call %s\n", dest); + for (dl=l->devices; dl; dl=dl->next_device_on_line) { + if (dl->device->registered) { + break; + } + } + if (!dl) { + ast_log(LOG_ERROR, "No devices registered, cannot call %s\n", dest); return -1; } - + if ((ast->_state != AST_STATE_DOWN) && (ast->_state != AST_STATE_RESERVED)) { ast_log(LOG_WARNING, "skinny_call called on %s, neither down nor reserved\n", ast->name); return -1; @@ -2353,35 +2654,17 @@ if (skinnydebug) ast_verbose(VERBOSE_PREFIX_3 "skinny_call(%s)\n", ast->name); - if (l->dnd) { + if (l->dnd) { /* should change to per device dnd */ ast_queue_control(ast, AST_CONTROL_BUSY); return -1; } - switch (l->hookstate) { - case SKINNY_OFFHOOK: - tone = SKINNY_CALLWAITTONE; - break; - case SKINNY_ONHOOK: - tone = SKINNY_ALERT; - break; - default: - ast_log(LOG_ERROR, "Don't know how to deal with hookstate %d\n", l->hookstate); - break; - } - - transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_BLINK); - transmit_ringer_mode(s, SKINNY_RING_INSIDE); - - transmit_tone(s, tone); - transmit_callinfo(s, ast->cid.cid_name, ast->cid.cid_num, l->cid_name, l->cid_num, l->instance, sub->callid, 1); - transmit_callstate(s, l->instance, SKINNY_RINGIN, sub->callid); - transmit_displaypromptstatus(s, "Ring-In", 0, l->instance, sub->callid); - transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_RINGIN); + skinny_change_line_status(l, sub, ast, LINESTATUS_RINGING); ast_setstate(ast, AST_STATE_RINGING); ast_queue_control(ast, AST_CONTROL_RINGING); sub->outgoing = 1; + return res; } @@ -2389,8 +2672,6 @@ { struct skinny_subchannel *sub = ast->tech_pvt; struct skinny_line *l; - struct skinny_device *d; - struct skinnysession *s; if (!sub) { if (option_debug) @@ -2398,26 +2679,17 @@ return 0; } l = sub->parent; - d = l->parent; - s = d->session; if (skinnydebug) - ast_verbose("skinny_hangup(%s) on %s@%s\n", ast->name, l->name, d->name); + ast_verbose("skinny_hangup(%s) on %s\n", ast->name, l->name); - if (d->registered) { - if ((l->type = TYPE_LINE) && (l->hookstate == SKINNY_OFFHOOK)) { - l->hookstate = SKINNY_ONHOOK; - transmit_callstate(s, l->instance, SKINNY_ONHOOK, sub->callid); - transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_OFF); - transmit_speaker_mode(s, SKINNY_SPEAKEROFF); - } else if ((l->type = TYPE_LINE) && (l->hookstate == SKINNY_ONHOOK)) { - transmit_callstate(s, l->instance, SKINNY_ONHOOK, sub->callid); - transmit_speaker_mode(s, SKINNY_SPEAKEROFF); - transmit_ringer_mode(s, SKINNY_RING_OFF); - transmit_tone(s, SKINNY_SILENCE); - transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_OFF); - do_housekeeping(s); - } + skinny_change_line_status(l, sub, NULL, LINESTATUS_EMPTY); + + if (l->active_device) { + /* There's an active device that we should hangup and disconnect */ + l->active_device->active_line = NULL; + l->active_device->active_sub = NULL; } + ast_mutex_lock(&sub->lock); sub->owner = NULL; ast->tech_pvt = NULL; @@ -2427,6 +2699,7 @@ ast_rtp_destroy(sub->rtp); sub->rtp = NULL; } + skinny_destroy_sub(sub); ast_mutex_unlock(&sub->lock); return 0; } @@ -2436,7 +2709,8 @@ int res = 0; struct skinny_subchannel *sub = ast->tech_pvt; struct skinny_line *l = sub->parent; - struct skinny_device *d = l->parent; + struct skinny_device *d = l->active_device; + struct skinny_device_line *dl = find_dl(d, l); struct skinnysession *s = d->session; sub->cxmode = SKINNY_CX_SENDRECV; @@ -2453,9 +2727,9 @@ /* order matters here... for some reason, transmit_callinfo must be before transmit_callstate, or you won't get keypad messages in some situations. */ - transmit_callinfo(s, ast->cid.cid_name, ast->cid.cid_num, ast->exten, ast->exten, l->instance, sub->callid, 2); - transmit_callstate(s, l->instance, SKINNY_CONNECTED, sub->callid); - transmit_displaypromptstatus(s, "Connected", 0, l->instance, sub->callid); + transmit_callinfo(s, ast->cid.cid_name, ast->cid.cid_num, ast->exten, ast->exten, dl->instance, sub->callid, 2); + transmit_callstate(s, dl->instance, SKINNY_CONNECTED, sub->callid); + transmit_displaypromptstatus(s, "Connected", 0, dl->instance, sub->callid); return res; } @@ -2630,7 +2904,8 @@ { struct skinny_subchannel *sub = ast->tech_pvt; struct skinny_line *l = sub->parent; - struct skinny_device *d = l->parent; + struct skinny_device *d = l->active_device; + struct skinny_device_line *dl = find_dl(d, l); struct skinnysession *s = d->session; if (skinnydebug) @@ -2640,10 +2915,10 @@ if (ast->_state != AST_STATE_UP) { if (!sub->progress) { transmit_tone(s, SKINNY_ALERT); - transmit_callstate(s, l->instance, SKINNY_RINGOUT, sub->callid); - transmit_dialednumber(s, ast->exten, l->instance, sub->callid); - transmit_displaypromptstatus(s, "Ring Out", 0, l->instance, sub->callid); - transmit_callinfo(s, ast->cid.cid_name, ast->cid.cid_num, ast->exten, ast->exten, l->instance, sub->callid, 2); /* 2 = outgoing from phone */ + transmit_callstate(s, dl->instance, SKINNY_RINGOUT, sub->callid); + transmit_dialednumber(s, ast->exten, dl->instance, sub->callid); + transmit_displaypromptstatus(s, "Ring Out", 0, dl->instance, sub->callid); + transmit_callinfo(s, ast->cid.cid_name, ast->cid.cid_num, ast->exten, ast->exten, dl->instance, sub->callid, 2); /* 2 = outgoing from phone */ sub->ringing = 1; break; } @@ -2652,7 +2927,7 @@ case AST_CONTROL_BUSY: if (ast->_state != AST_STATE_UP) { transmit_tone(s, SKINNY_BUSYTONE); - transmit_callstate(s, l->instance, SKINNY_BUSY, sub->callid); + transmit_callstate(s, dl->instance, SKINNY_BUSY, sub->callid); sub->alreadygone = 1; ast_softhangup_nolock(ast, AST_SOFTHANGUP_DEV); break; @@ -2661,7 +2936,7 @@ case AST_CONTROL_CONGESTION: if (ast->_state != AST_STATE_UP) { transmit_tone(s, SKINNY_REORDER); - transmit_callstate(s, l->instance, SKINNY_CONGESTION, sub->callid); + transmit_callstate(s, dl->instance, SKINNY_CONGESTION, sub->callid); sub->alreadygone = 1; ast_softhangup_nolock(ast, AST_SOFTHANGUP_DEV); break; @@ -2670,9 +2945,9 @@ case AST_CONTROL_PROGRESS: if ((ast->_state != AST_STATE_UP) && !sub->progress && !sub->outgoing) { transmit_tone(s, SKINNY_ALERT); - transmit_callstate(s, l->instance, SKINNY_PROGRESS, sub->callid); - transmit_displaypromptstatus(s, "Call Progress", 0, l->instance, sub->callid); - transmit_callinfo(s, ast->cid.cid_name, ast->cid.cid_num, ast->exten, ast->exten, l->instance, sub->callid, 2); /* 2 = outgoing from phone */ + transmit_callstate(s, dl->instance, SKINNY_PROGRESS, sub->callid); + transmit_displaypromptstatus(s, "Call Progress", 0, dl->instance, sub->callid); + transmit_callinfo(s, ast->cid.cid_name, ast->cid.cid_num, ast->exten, ast->exten, dl->instance, sub->callid, 2); /* 2 = outgoing from phone */ sub->progress = 1; break; } @@ -2695,14 +2970,19 @@ return 0; } -static struct ast_channel *skinny_new(struct skinny_line *l, int state) +static struct ast_channel *skinny_new(struct skinny_line *l, struct skinny_device *d, int state) { struct ast_channel *tmp; struct skinny_subchannel *sub; - struct skinny_device *d = l->parent; + struct skinny_device_line *dl; int fmt; - tmp = ast_channel_alloc(1, state, l->cid_num, l->cid_name, "Skinny/%s@%s-%d", l->name, d->name, callnums); + if (l->sharedline && l->sub) { + ast_verbose("Shared line already has a subchannel\n"); + return NULL; + } + + tmp = ast_channel_alloc(1, state, l->cid_num, l->cid_name, "Skinny/%s-%d", l->name, callnums); if (!tmp) { ast_log(LOG_WARNING, "Unable to allocate channel structure\n"); return NULL; @@ -2713,16 +2993,17 @@ return NULL; } else { ast_mutex_init(&sub->lock); - sub->owner = tmp; sub->callid = callnums++; - d->lastlineinstance = l->instance; - d->lastcallreference = sub->callid; + if (d) { + dl = find_dl(d,l); + d->lastlineinstance = dl->instance; + d->lastcallreference = sub->callid; + } sub->cxmode = SKINNY_CX_INACTIVE; sub->nat = l->nat; sub->parent = l; sub->onhold = 0; - sub->next = l->sub; l->sub = sub; } @@ -2783,13 +3064,14 @@ static int skinny_hold(struct skinny_subchannel *sub) { struct skinny_line *l = sub->parent; - struct skinny_device *d = l->parent; + struct skinny_device *d = l->active_device; + struct skinny_device_line *dl = find_dl(d, l); struct skinnysession *s = d->session; struct skinny_req *req; /* Channel needs to be put on hold */ if (skinnydebug) - ast_verbose("Putting on Hold(%d)\n", l->instance); + ast_verbose("Holding line '%s' from device '%s'\n", dl->line->name, dl->device->name); ast_queue_control_data(sub->owner, AST_CONTROL_HOLD, S_OR(l->mohsuggest, NULL), @@ -2798,7 +3080,7 @@ if (!(req = req_alloc(sizeof(struct activate_call_plane_message), ACTIVATE_CALL_PLANE_MESSAGE))) return 0; - req->data.activatecallplane.lineInstance = htolel(l->instance); + req->data.activatecallplane.lineInstance = htolel(dl->instance); transmit_response(s, req); if (!(req = req_alloc(sizeof(struct close_receive_channel_message), CLOSE_RECEIVE_CHANNEL_MESSAGE))) @@ -2814,8 +3096,7 @@ req->data.stopmedia.conferenceId = htolel(0); req->data.stopmedia.passThruPartyId = htolel(sub->callid); transmit_response(s, req); - - transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_BLINK); + skinny_change_line_status(l, sub, NULL, LINESTATUS_ONHOLD); sub->onhold = 1; return 1; } @@ -2823,25 +3104,27 @@ static int skinny_unhold(struct skinny_subchannel *sub) { struct skinny_line *l = sub->parent; - struct skinny_device *d = l->parent; + struct skinny_device *d = l->active_device; + struct skinny_device_line *dl = find_dl(d, l); struct skinnysession *s = d->session; struct skinny_req *req; /* Channel is on hold, so we will unhold */ if (skinnydebug) - ast_verbose("Taking off Hold(%d)\n", l->instance); + ast_verbose("Unholding line '%s' from device '%s'\n", dl->line->name, dl->device->name); ast_queue_control(sub->owner, AST_CONTROL_UNHOLD); if (!(req = req_alloc(sizeof(struct activate_call_plane_message), ACTIVATE_CALL_PLANE_MESSAGE))) return 0; - req->data.activatecallplane.lineInstance = htolel(l->instance); + req->data.activatecallplane.lineInstance = htolel(dl->instance); transmit_response(s, req); transmit_connect(s, sub); - transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_ON); + skinny_change_line_status(l, sub, NULL, LINESTATUS_CONNECTED); sub->onhold = 0; + d->active_sub = sub; return 1; } @@ -2977,6 +3260,7 @@ { struct skinny_device *d = s->device; struct skinny_line *l; + struct skinny_device_line *dl; struct skinny_subchannel *sub; /*struct skinny_speeddial *sd;*/ struct ast_channel *c; @@ -3002,6 +3286,8 @@ } else { l = sub->parent; } + + dl=find_dl(d,l); switch(event) { case STIMULUS_REDIAL: @@ -3009,12 +3295,12 @@ ast_verbose("Received Stimulus: Redial(%d)\n", instance); #if 0 - c = skinny_new(l, AST_STATE_DOWN); + c = skinny_new(l, d, AST_STATE_DOWN); if(!c) { ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name); } else { sub = c->tech_pvt; - transmit_callstate(s, l->instance, SKINNY_OFFHOOK, sub->callid); + transmit_callstate(s, dl->instance, SKINNY_OFFHOOK, sub->callid); if (skinnydebug) ast_verbose("Attempting to Clear display on Skinny %s@%s\n", l->name, d->name); transmit_displaymessage(s, NULL); /* clear display */ @@ -3044,11 +3330,12 @@ return 0; } - c = skinny_new(l, AST_STATE_DOWN); + c = skinny_new(l, d, AST_STATE_DOWN); if(c) { sub = c->tech_pvt; l = sub->parent; - transmit_callstate(s, l->instance, SKINNY_OFFHOOK, sub->callid); + transmit_callstate(s, dl->instance, SKINNY_OFFHOOK, sub->callid); + d->hookstate = SKINNY_OFFHOOK; if (skinnydebug) ast_verbose("Attempting to Clear display on Skinny %s@%s\n", l->name, d->name); transmit_displaymessage(s, NULL); /* clear display */ @@ -3073,7 +3360,11 @@ case STIMULUS_HOLD: if (skinnydebug) ast_verbose("Received Stimulus: Hold(%d)\n", instance); - + sub = d->active_sub; + if (!sub) { + ast_verbose("No sub so nothing to do\n"); + break; + } if (sub->onhold) { skinny_unhold(sub); } else { @@ -3146,47 +3437,97 @@ if (!l) { return 0; } - - /* turn the speaker on */ - transmit_speaker_mode(s, SKINNY_SPEAKERON); - transmit_ringer_mode(s, SKINNY_RING_OFF); - transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_ON); - + + if (l->active_device && !(l->active_device == d)) { + ast_verbose("Tried to grab a line used by another device"); + /* may need to hangup if device comes offhook */ + return 0; + } + + if (s->device->hookstate == SKINNY_ONHOOK) { + /* line button while on hook, set and wait for offhook message */ + s->device->active_line = l; + l->active_device = s->device; + transmit_speaker_mode(s, SKINNY_SPEAKERON); + return 1; + } + + if (s->device->active_sub && !(s->device->active_line == l)) { /* Hang up active sub unless sub of line being pushed */ + sub = s->device->active_sub; + if (sub->onhold) { + sub->parent->active_device = NULL; + } else { + ast_verbose("Sub needs destroying"); + if (sub->owner) { + ast_verbose("Device %s inititiated destroying subchannel on line %s\n", d->name, sub->parent->name); + skinny_destroy_sub(sub); + sub->alreadygone = 1; + } else { + ast_log(LOG_WARNING, "Skinny(%s@%s-%d) channel already destroyed\n", + l->name, d->name, sub->callid); + } + skinny_change_line_status(l, sub, NULL, LINESTATUS_EMPTY); + sub->parent->hookstate = SKINNY_ONHOOK; + d->active_sub = NULL; + sub->parent->active_device = NULL; + d->active_line = NULL; + } + } + + /* turn the speaker on??? it's already on */ + /* transmit_speaker_mode(s, SKINNY_SPEAKERON); */ + skinny_change_line_status(l, sub, NULL, LINESTATUS_CONNECTED); + d->hookstate = SKINNY_OFFHOOK; l->hookstate = SKINNY_OFFHOOK; + l->active_device = d; + d->active_line = l; + dl=find_dl(d,l); /* Can probably remove and change dl->inst to inst for STIMULUS_LINE */ + sub = l->sub; - if (sub && sub->outgoing) { - /* We're answering a ringing call */ - ast_queue_control(sub->owner, AST_CONTROL_ANSWER); - transmit_callstate(s, l->instance, SKINNY_OFFHOOK, sub->callid); - transmit_tone(s, SKINNY_SILENCE); - transmit_callstate(s, l->instance, SKINNY_CONNECTED, sub->callid); - transmit_displaypromptstatus(s, "Connected", 0, l->instance, sub->callid); - transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_CONNECTED); - start_rtp(sub); - ast_setstate(sub->owner, AST_STATE_UP); - } else { - if (sub && sub->owner) { + if (sub) { + d->active_sub = sub; + if (sub->onhold) { /* We're grabbing an onhold call */ + transmit_tone(s, SKINNY_SILENCE); + transmit_callstate(s, dl->instance, SKINNY_CONNECTED, sub->callid); + transmit_displaypromptstatus(s, "Connected", 0, dl->instance, sub->callid); + transmit_selectsoftkeys(s, dl->instance, sub->callid, KEYDEF_CONNECTED); + skinny_unhold(sub); + } else if (sub->outgoing) { /* We're answering a ringing call */ + ast_queue_control(sub->owner, AST_CONTROL_ANSWER); + transmit_callstate(s, dl->instance, SKINNY_OFFHOOK, sub->callid); + transmit_tone(s, SKINNY_SILENCE); + transmit_callstate(s, dl->instance, SKINNY_CONNECTED, sub->callid); + transmit_displaypromptstatus(s, "Connected", 0, dl->instance, sub->callid); + transmit_selectsoftkeys(s, dl->instance, sub->callid, KEYDEF_CONNECTED); + start_rtp(sub); + ast_setstate(sub->owner, AST_STATE_UP); + } else if (sub && sub->owner) { if (option_debug) ast_log(LOG_DEBUG, "Current subchannel [%s] already has owner\n", sub->owner->name); } else { - c = skinny_new(l, AST_STATE_DOWN); - if(c) { - sub = c->tech_pvt; - transmit_callstate(s, l->instance, SKINNY_OFFHOOK, sub->callid); - if (skinnydebug) - ast_verbose("Attempting to Clear display on Skinny %s@%s\n", l->name, d->name); - transmit_displaymessage(s, NULL); /* clear display */ - transmit_tone(s, SKINNY_DIALTONE); - transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_OFFHOOK); + if (option_debug) + ast_log(LOG_DEBUG, "Don't know what to do with subchannel on line '%s'\n", sub->owner->name); + } + } else { + c = skinny_new(l, d, AST_STATE_DOWN); + if(c) { + sub = c->tech_pvt; + d->active_sub = sub; + transmit_callstate(s, dl->instance, SKINNY_OFFHOOK, sub->callid); + if (skinnydebug) + ast_verbose("Attempting to Clear display on Skinny %s@%s\n", l->name, d->name); + transmit_displaymessage(s, NULL); /* clear display */ + transmit_tone(s, SKINNY_DIALTONE); + transmit_selectsoftkeys(s, dl->instance, sub->callid, KEYDEF_OFFHOOK); - /* start the switch thread */ - if (ast_pthread_create(&t, NULL, skinny_ss, c)) { - ast_log(LOG_WARNING, "Unable to create switch thread: %s\n", strerror(errno)); - ast_hangup(c); - } - } else { - ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name); + /* start the switch thread */ + if (ast_pthread_create(&t, NULL, skinny_ss, c)) { + ast_log(LOG_WARNING, "Unable to create switch thread: %s\n", strerror(errno)); + ast_hangup(c); + d->active_sub = NULL; } + } else { + ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name); } } break; @@ -3202,70 +3543,118 @@ { struct skinny_device *d = s->device; struct skinny_line *l; - struct skinny_subchannel *sub; + struct skinny_device_line *dl; + struct skinny_subchannel *sub = NULL; struct ast_channel *c; pthread_t t; int unknown1; int unknown2; + if (skinnydebug) + ast_verbose("Begin sub handle_off_hook on device '%s'\n", s->device->name); + unknown1 = letohl(req->data.offhook.unknown1); unknown2 = letohl(req->data.offhook.unknown2); + + if (d->hookstate == SKINNY_OFFHOOK) { + ast_verbose("Device '%s' is already offhook, this should not happen\n", d->name); + return 1; + } - sub = find_subchannel_by_instance_reference(d, d->lastlineinstance, d->lastcallreference); - - if (!sub) { + if (d->active_line) { + /* connect to the active line */ + l = d->active_line; + ast_verbose("Device '%s' picking up active line '%s'\n", d->name, l->name); + } else if (d->ringingline) { + /* connect to the ringing line */ + l = d->ringingline; + ast_verbose("Device '%s' picking up ringing line '%s'\n", d->name, l->name); + } else { + /* grab a line for a new call */ l = find_line_by_instance(d, d->lastlineinstance); - if (!l) { - return 0; + if ((l->active_device && !(l->active_device == d)) || l->sub) { + if (skinnydebug) + ast_verbose("Line is busy, running through instances and grabbing first available\n"); + l = NULL; + for (dl = d->lines; dl; dl = dl->next_line_on_device) { + if (!dl->line->active_device && !dl->line->sub) { + l = dl->line; + break; + } + } + if (!l) { + ast_verbose("No free line found\n"); + return -1; + } } - } else { - l = sub->parent; } - + dl = find_dl(d, l); + sub = l->sub; + d->active_line = l; + if (sub && sub->onhold) { transmit_ringer_mode(s, SKINNY_RING_OFF); l->hookstate = SKINNY_OFFHOOK; + d->hookstate = SKINNY_OFFHOOK; + skinny_unhold(sub); return 1; } - transmit_ringer_mode(s, SKINNY_RING_OFF); - transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_ON); l->hookstate = SKINNY_OFFHOOK; + d->hookstate = SKINNY_OFFHOOK; if (sub && sub->outgoing) { /* We're answering a ringing call */ + if (skinnydebug) + ast_log(LOG_DEBUG, "Answering incoming call on %s", l->name); ast_queue_control(sub->owner, AST_CONTROL_ANSWER); - transmit_callstate(s, l->instance, SKINNY_OFFHOOK, sub->callid); + d->active_sub = l->sub; + l->active_device = d; + skinny_change_line_status(l, sub, NULL, LINESTATUS_CONNECTED); + + /*transmit_callstate(s, dl->instance, SKINNY_OFFHOOK, sub->callid); transmit_tone(s, SKINNY_SILENCE); - transmit_callstate(s, l->instance, SKINNY_CONNECTED, sub->callid); - transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_CONNECTED); + transmit_callstate(s, dl->instance, SKINNY_CONNECTED, sub->callid); + transmit_selectsoftkeys(s, dl->instance, sub->callid, KEYDEF_CONNECTED); */ + start_rtp(sub); ast_setstate(sub->owner, AST_STATE_UP); } else { + if (skinnydebug) + ast_verbose("Creating outbound channel on %s\n", l->name); if (sub && sub->owner) { if (option_debug) - ast_log(LOG_DEBUG, "Current sub [%s] already has owner\n", sub->owner->name); + ast_verbose("Current sub [%s] already has owner\n", sub->owner->name); } else { - c = skinny_new(l, AST_STATE_DOWN); + c = skinny_new(l, d, AST_STATE_DOWN); + if(c) { sub = c->tech_pvt; - transmit_callstate(s, l->instance, SKINNY_OFFHOOK, sub->callid); + l->active_device = d; + d->active_sub = l->sub; + skinny_change_line_status(l, sub, NULL, LINESTATUS_CONNECTED); + transmit_callstate(s, dl->instance, SKINNY_OFFHOOK, sub->callid); if (skinnydebug) ast_verbose("Attempting to Clear display on Skinny %s@%s\n", l->name, d->name); transmit_displaymessage(s, NULL); /* clear display */ transmit_tone(s, SKINNY_DIALTONE); - transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_OFFHOOK); + transmit_selectsoftkeys(s, dl->instance, sub->callid, KEYDEF_OFFHOOK); /* start the switch thread */ if (ast_pthread_create(&t, NULL, skinny_ss, c)) { ast_log(LOG_WARNING, "Unable to create switch thread: %s\n", strerror(errno)); ast_hangup(c); + d->active_sub = NULL; } } else { ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name); } } } + + if (skinnydebug) + ast_verbose("End sub handle_off_hook on device '%s'\n", s->device->name); + return 1; } @@ -3273,34 +3662,56 @@ { struct skinny_device *d = s->device; struct skinny_line *l; + struct skinny_device_line *dl; struct skinny_subchannel *sub; int unknown1; int unknown2; + if (skinnydebug) + ast_verbose("Begin sub handle_on_hook on device '%s'\n", s->device->name); + unknown1 = letohl(req->data.onhook.unknown1); unknown2 = letohl(req->data.onhook.unknown2); - sub = find_subchannel_by_instance_reference(d, d->lastlineinstance, d->lastcallreference); + sub = d->active_sub; if (!sub) { + d->active_line = NULL; + d->active_sub = NULL; + d->hookstate = SKINNY_ONHOOK; + ast_verbose("No active sub, so just changing device status on '%s'\n", s->device->name); return 0; } l = sub->parent; + dl = find_dl(d,l); if (sub->onhold) { + l->active_device = NULL; + d->active_line = NULL; + d->active_sub = NULL; l->hookstate = SKINNY_ONHOOK; + d->hookstate = SKINNY_ONHOOK; return 0; } + skinny_change_line_status(l, sub, NULL, LINESTATUS_EMPTY); + if (l->hookstate == SKINNY_ONHOOK) { /* Something else already put us back on hook */ + l->active_device = NULL; + d->active_line = NULL; + d->active_sub = NULL; + d->hookstate = SKINNY_ONHOOK; return 0; } sub->cxmode = SKINNY_CX_RECVONLY; l->hookstate = SKINNY_ONHOOK; - transmit_callstate(s, l->instance, l->hookstate, sub->callid); + d->hookstate = SKINNY_ONHOOK; + d->active_line = NULL; + d->active_sub = NULL; + transmit_callstate(s, dl->instance, l->hookstate, sub->callid); if (skinnydebug) - ast_verbose("Skinny %s@%s went on hook\n", l->name, d->name); + ast_verbose("%s@%s went on hook\n", l->name, d->name); if (l->transfer && (sub->owner && sub->next && sub->next->owner) && ((!sub->outgoing) || (sub->next && !sub->next->outgoing))) { /* We're allowed to transfer, we have two active calls and we made at least one of the calls. Let's try and transfer */ @@ -3308,8 +3719,9 @@ #if 0 if ((res = attempt_transfer(p)) < 0) { if (sub->next && sub->next->owner) { - sub->next->alreadygone = 1; - ast_queue_hangup(sub->next->owner,1); + skinny_destroy_sub(sub->next); + /* sub->next->alreadygone = 1; + ast_queue_hangup(sub->next->owner,1); */ } } else if (res) { ast_log(LOG_WARNING, "Transfer attempt failed\n"); @@ -3319,9 +3731,11 @@ } else { /* Hangup the current call */ /* If there is another active call, skinny_hangup will ring the phone with the other call */ + sub->parent->active_device = NULL; if (sub->owner) { + ast_verbose("Device %s inititiated destroying subchannel on line %s\n", d->name, l->name); + skinny_destroy_sub(sub); sub->alreadygone = 1; - ast_queue_hangup(sub->owner); } else { ast_log(LOG_WARNING, "Skinny(%s@%s-%d) channel already destroyed\n", l->name, d->name, sub->callid); @@ -3330,13 +3744,17 @@ if ((l->hookstate == SKINNY_ONHOOK) && (sub->next && !sub->next->rtp)) { do_housekeeping(s); } + + if (skinnydebug) + ast_verbose("End sub handle_on_hook on device '%s'\n", s->device->name); + return 1; } static int handle_capabilities_res_message(struct skinny_req *req, struct skinnysession *s) { struct skinny_device *d = s->device; - struct skinny_line *l; + struct skinny_device_line *dl; int count = 0; int codecs = 0; int i; @@ -3355,10 +3773,12 @@ d->capability &= codecs; ast_verbose("Device capability set to '%d'\n", d->capability); - for (l = d->lines; l; l = l->next) { - ast_mutex_lock(&l->lock); - l->capability = d->capability; - ast_mutex_unlock(&l->lock); + /* FIXME: need for common capabilities on a line across all devices registered to that line. + Should probably "&" previous capabilities. */ + for (dl = d->lines; dl; dl = dl->next_line_on_device) { + ast_mutex_lock(&dl->line->lock); + dl->line->capability = d->capability; + ast_mutex_unlock(&dl->line->lock); } return 1; @@ -3443,7 +3863,7 @@ static int handle_button_template_req_message(struct skinny_req *req, struct skinnysession *s) { struct skinny_device *d = s->device; - struct skinny_line *l; + struct skinny_device_line *dl; int i; struct skinny_speeddial *sd; @@ -3467,8 +3887,8 @@ req->data.buttontemplate.definition[i].buttonDefinition = BT_NONE; req->data.buttontemplate.definition[i].instanceNumber = htolel(0); - for (l = d->lines; l; l = l->next) { - if (l->instance == lineInstance) { + for (dl = d->lines; dl; dl = dl->next_line_on_device) { + if (dl->instance == lineInstance) { ast_verbose("Adding button: %d, %d\n", BT_LINE, lineInstance); req->data.buttontemplate.definition[i].buttonDefinition = BT_LINE; req->data.buttontemplate.definition[i].instanceNumber = htolel(lineInstance); @@ -3497,8 +3917,8 @@ req->data.buttontemplate.definition[i].buttonDefinition = htolel(BT_NONE); req->data.buttontemplate.definition[i].instanceNumber = htolel(0); - for (l = d->lines; l; l = l->next) { - if (l->instance == lineInstance) { + for (dl = d->lines; dl; dl = dl->next_line_on_device) { + if (dl->instance == lineInstance) { ast_verbose("Adding button: %d, %d\n", BT_LINE, lineInstance); req->data.buttontemplate.definition[i].buttonDefinition = BT_LINE; req->data.buttontemplate.definition[i].instanceNumber = htolel(lineInstance); @@ -3611,7 +4031,9 @@ sin.sin_addr.s_addr = addr; sin.sin_port = htons(port); - sub = find_subchannel_by_reference(d, passthruid); + /* FIXME: This is not good, should still find channel */ + /* sub = find_subchannel_by_reference(d, passthruid); */ + sub = d->active_sub; if (!sub) return 0; @@ -3689,6 +4111,7 @@ { struct skinny_device *d = s->device; struct skinny_line *l; + struct skinny_device_line *dl; struct skinny_subchannel *sub = NULL; struct ast_channel *c; pthread_t t; @@ -3716,6 +4139,7 @@ ast_verbose("Received Softkey Event: %d(%d)\n", event, instance); return 0; } + dl = find_dl(d,l); switch(event) { case SOFTKEY_NONE: @@ -3728,7 +4152,7 @@ #if 0 if (!sub || !sub->owner) { - c = skinny_new(l, AST_STATE_DOWN); + c = skinny_new(l, d, AST_STATE_DOWN); } else { c = sub->owner; } @@ -3737,7 +4161,7 @@ ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name); } else { sub = c->tech_pvt; - transmit_callstate(s, l->instance, SKINNY_OFFHOOK, sub->callid); + transmit_callstate(s, dl->instance, SKINNY_OFFHOOK, sub->callid); if (skinnydebug) ast_verbose("Attempting to Clear display on Skinny %s@%s\n", l->name, d->name); transmit_displaymessage(s, NULL); /* clear display */ @@ -3763,19 +4187,18 @@ if (skinnydebug) ast_verbose("Received Softkey Event: New Call(%d)\n", instance); - transmit_ringer_mode(s,SKINNY_RING_OFF); - transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_ON); - - l->hookstate = SKINNY_OFFHOOK; - if (sub) { - transmit_callstate(s, l->instance, SKINNY_OFFHOOK, sub->callid); + skinny_change_line_status(l, sub, NULL, LINESTATUS_CONNECTED); + l->hookstate = SKINNY_OFFHOOK; + d->hookstate = SKINNY_OFFHOOK; + l->active_device = d; + transmit_callstate(s, dl->instance, SKINNY_OFFHOOK, sub->callid); if (skinnydebug) ast_verbose("Attempting to Clear display on Skinny %s@%s\n", l->name, d->name); transmit_displaymessage(s, NULL); /* clear display */ transmit_tone(s, SKINNY_DIALTONE); - transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_OFFHOOK); - c = skinny_new(l, AST_STATE_DOWN); + transmit_selectsoftkeys(s, dl->instance, sub->callid, KEYDEF_OFFHOOK); + c = skinny_new(l, d, AST_STATE_DOWN); if(c) { /* start the switch thread */ if (ast_pthread_create(&t, NULL, skinny_ss, c)) { @@ -3849,7 +4272,8 @@ if (sub) { sub->cxmode = SKINNY_CX_RECVONLY; l->hookstate = SKINNY_ONHOOK; - transmit_callstate(s, l->instance, l->hookstate, sub->callid); + d->hookstate = SKINNY_ONHOOK; + transmit_callstate(s, dl->instance, l->hookstate, sub->callid); if (skinnydebug) ast_verbose("Skinny %s@%s went on hook\n", l->name, d->name); if (l->transfer && (sub->owner && sub->next && sub->next->owner) && ((!sub->outgoing) || (sub->next && !sub->next->outgoing))) { @@ -3859,8 +4283,9 @@ #if 0 if ((res = attempt_transfer(p)) < 0) { if (sub->next && sub->next->owner) { - sub->next->alreadygone = 1; - ast_queue_hangup(sub->next->owner, 1); + skinny_destroy_sub(sub->next); + /* sub->next->alreadygone = 1; + ast_queue_hangup(sub->next->owner, 1); */ } } else if (res) { ast_log(LOG_WARNING, "Transfer attempt failed\n"); @@ -3871,8 +4296,9 @@ /* Hangup the current call */ /* If there is another active call, skinny_hangup will ring the phone with the other call */ if (sub->owner) { - sub->alreadygone = 1; - ast_queue_hangup(sub->owner); + skinny_destroy_sub(sub); + /* sub->alreadygone = 1; + ast_queue_hangup(sub->owner); */ } else { ast_log(LOG_WARNING, "Skinny(%s@%s-%d) channel already destroyed\n", l->name, d->name, sub->callid); @@ -3891,18 +4317,17 @@ if (skinnydebug) ast_verbose("Received Softkey Event: Answer(%d)\n", instance); - transmit_ringer_mode(s,SKINNY_RING_OFF); - transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_ON); - - l->hookstate = SKINNY_OFFHOOK; - if (sub && sub->outgoing) { /* We're answering a ringing call */ + skinny_change_line_status(l, sub, NULL, LINESTATUS_CONNECTED); + l->hookstate = SKINNY_OFFHOOK; + d->hookstate = SKINNY_OFFHOOK; ast_queue_control(sub->owner, AST_CONTROL_ANSWER); - transmit_callstate(s, l->instance, SKINNY_OFFHOOK, sub->callid); + l->active_device = d; + transmit_callstate(s, dl->instance, SKINNY_OFFHOOK, sub->callid); transmit_tone(s, SKINNY_SILENCE); - transmit_callstate(s, l->instance, SKINNY_CONNECTED, sub->callid); - transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_CONNECTED); + transmit_callstate(s, dl->instance, SKINNY_CONNECTED, sub->callid); + transmit_selectsoftkeys(s, dl->instance, sub->callid, KEYDEF_CONNECTED); start_rtp(sub); ast_setstate(sub->owner, AST_STATE_UP); } @@ -4004,29 +4429,33 @@ break; case KEYPAD_BUTTON_MESSAGE: if (skinnydebug) - ast_verbose("Collected digit: [%d]\n", letohl(req->data.keypad.button)); + ast_verbose("Collected digit: [%d] on '%s'\n", letohl(req->data.keypad.button), s->device->name); res = handle_keypad_button_message(req, s); break; case STIMULUS_MESSAGE: + if (skinnydebug) + ast_verbose("Received Message Stimulus from '%s'\n",s->device->name); res = handle_stimulus_message(req, s); break; case OFFHOOK_MESSAGE: + if (skinnydebug) + ast_verbose("Received Message Offhook from '%s'\n",s->device->name); res = handle_offhook_message(req, s); break; case ONHOOK_MESSAGE: + if (skinnydebug) + ast_verbose("Received Message Onhook from '%s'\n",s->device->name); res = handle_onhook_message(req, s); break; case CAPABILITIES_RES_MESSAGE: if (skinnydebug) - ast_verbose("Received CapabilitiesRes\n"); - + ast_verbose("Received Message CapabilitiesRes from '%s'\n",s->device->name); res = handle_capabilities_res_message(req, s); break; case SPEED_DIAL_STAT_REQ_MESSAGE: if (skinnydebug) - ast_verbose("Received SpeedDialStatRequest\n"); - + ast_verbose("Received Message SpeedDialStatRequest from '%s'\n",s->device->name); res = handle_speed_dial_stat_req_message(req, s); break; case LINE_STATE_REQ_MESSAGE: @@ -4034,26 +4463,22 @@ break; case TIME_DATE_REQ_MESSAGE: if (skinnydebug) - ast_verbose("Received Time/Date Request\n"); - + ast_verbose("Received Message Time/Date Request from '%s'\n",s->device->name); res = handle_time_date_req_message(req, s); break; case BUTTON_TEMPLATE_REQ_MESSAGE: if (skinnydebug) - ast_verbose("Buttontemplate requested\n"); - + ast_verbose("Received Message Buttontemplate request from '%s'\n",s->device->name); res = handle_button_template_req_message(req, s); break; case VERSION_REQ_MESSAGE: if (skinnydebug) - ast_verbose("Version Request\n"); - + ast_verbose("Received Message Version Request from '%s'\n",s->device->name); res = handle_version_req_message(req, s); break; case SERVER_REQUEST_MESSAGE: if (skinnydebug) - ast_verbose("Received Server Request\n"); - + ast_verbose("Received Message Server Request from '%s'\n",s->device->name); res = handle_server_request_message(req, s); break; case ALARM_MESSAGE: @@ -4061,8 +4486,7 @@ break; case OPEN_RECEIVE_CHANNEL_ACK_MESSAGE: if (skinnydebug) - ast_verbose("Received Open Receive Channel Ack\n"); - + ast_verbose("Received Message Open Receive Channel Ack from '%s'\n",s->device->name); res = handle_open_receive_channel_ack_message(req, s); break; case SOFT_KEY_SET_REQ_MESSAGE: @@ -4365,34 +4789,70 @@ struct skinny_line *l; struct ast_channel *tmpc = NULL; + struct skinny_device *d; + struct skinny_subchannel *sub; char tmp[256]; char *dest = data; + char dest2[256]; + char *cur_opt, *prev_opt; oldformat = format; if (!(format &= ((AST_FORMAT_MAX_AUDIO << 1) - 1))) { ast_log(LOG_NOTICE, "Asked to get a channel of unsupported format '%d'\n", format); return NULL; - } + } - ast_copy_string(tmp, dest, sizeof(tmp)); + ast_copy_string(dest2, dest, sizeof(dest2)); + + cur_opt = dest2; + prev_opt = strsep(&cur_opt, "/"); + ast_copy_string(tmp, prev_opt, strlen(prev_opt)+1); + if (ast_strlen_zero(tmp)) { ast_log(LOG_NOTICE, "Skinny channels require a device\n"); return NULL; } l = find_line_by_name(tmp); if (!l) { - ast_log(LOG_NOTICE, "No available lines on: %s\n", dest); + ast_log(LOG_NOTICE, "No available lines on: %s\n", tmp); return NULL; } if (option_verbose > 2) { ast_verbose(VERBOSE_PREFIX_3 "skinny_request(%s)\n", tmp); } - tmpc = skinny_new(l, AST_STATE_DOWN); + tmpc = skinny_new(l, NULL, AST_STATE_DOWN); if (!tmpc) { ast_log(LOG_WARNING, "Unable to make channel for '%s'\n", tmp); + return NULL; + } + + sub = tmpc->tech_pvt; + + if (strchr(tmp, '@')) { /* set to ring a single device */ + for (d = devices; d; d = d->next) { + ast_verbose("Comparing %s to %s\n", d->name, tmp); + if (!strcasecmp(d->name, tmp)) { + sub->ringdevice = d; + } + } + } + + sub->ringtype = SKINNY_RING_INSIDE; + while (cur_opt) { + /* Provides for dial options. Only 'silent' implemented, but should be easy to add others including autoanswer etc */ + /* Format in extensions.conf is 'dial(SKINNY/Line/Option1/Option2)' */ + prev_opt = strsep(&cur_opt, "/"); + if (!strcasecmp(prev_opt, "silent")) { + sub->ringtype = SKINNY_RING_OFF; + ast_verbose("Setting subchannel ring type to %d\n", sub->ringtype); + } else { + ast_verbose("Don't know '%s' dial option, len %d, compared %d\n", prev_opt, strlen(prev_opt), strlen("silent")); + } } + restart_monitor(); + return tmpc; } @@ -4540,38 +5000,64 @@ return 1; } -static void delete_devices(void) +static void delete_device_children(struct skinny_device *d) { - struct skinny_device *d, *dlast; - struct skinny_line *l, *llast; + struct skinny_device_line *dl, *dltmp, *dllast; struct skinny_speeddial *sd, *sdlast; struct skinny_addon *a, *alast; + /* Delete all lines for this device */ + for (dl=d->lines;dl;) { + dltmp=device_lines; + while (dltmp) { + if (dltmp->next == dl) { + dltmp->next = dl->next; + } + if (dltmp->next_line_on_device == dl) { + dltmp->next_line_on_device = dl->next_line_on_device; + } + if (dltmp->next_device_on_line == dl) { + dltmp->next_device_on_line = dl->next_device_on_line; + } + dltmp = dltmp->next; + } + if (dltmp) { + if (dl->line->devices == dl) { + dl->line->devices = dl->next_device_on_line; + if (!dl->line->devices) { + ast_mutex_destroy(&dl->line->lock); + free(dl->line); + } + } + } + dllast = dl; + dl = dl->next_line_on_device; + free(dllast); + } + /* Delete all speeddials for this device */ + for (sd=d->speeddials;sd;) { + sdlast = sd; + sd = sd->next; + ast_mutex_destroy(&sdlast->lock); + free(sdlast); + } + /* Delete all addons for this device */ + for (a=d->addons;a;) { + alast = a; + a = a->next; + ast_mutex_destroy(&alast->lock); + free(alast); + } +} + +static void delete_devices(void) +{ + struct skinny_device *d, *dlast; ast_mutex_lock(&devicelock); /* Delete all devices */ for (d=devices;d;) { - /* Delete all lines for this device */ - for (l=d->lines;l;) { - llast = l; - l = l->next; - ast_mutex_destroy(&llast->lock); - free(llast); - } - /* Delete all speeddials for this device */ - for (sd=d->speeddials;sd;) { - sdlast = sd; - sd = sd->next; - ast_mutex_destroy(&sdlast->lock); - free(sdlast); - } - /* Delete all addons for this device */ - for (a=d->addons;a;) { - alast = a; - a = a->next; - ast_mutex_destroy(&alast->lock); - free(alast); - } + delete_device_children(d); dlast = d; d = d->next; free(dlast); @@ -4633,7 +5119,7 @@ { struct skinnysession *s, *slast; struct skinny_device *d; - struct skinny_line *l; + struct skinny_device_line *dl; struct skinny_subchannel *sub; ast_mutex_lock(&sessionlock); @@ -4643,9 +5129,9 @@ slast = s; s = s->next; for (d = slast->device; d; d = d->next) { - for (l = d->lines; l; l = l->next) { - ast_mutex_lock(&l->lock); - for (sub = l->sub; sub; sub = sub->next) { + for (dl = d->lines; dl; dl = dl->next_line_on_device) { + ast_mutex_lock(&dl->line->lock); + for (sub = dl->line->sub; sub; sub = sub->next) { ast_mutex_lock(&sub->lock); if (sub->owner) { sub->alreadygone = 1; @@ -4653,7 +5139,7 @@ } ast_mutex_unlock(&sub->lock); } - ast_mutex_unlock(&l->lock); + ast_mutex_unlock(&dl->line->lock); } } if (slast->fd > -1)