From f7c2e86f4becd99fe7af875a41de2ebff8df8830 Mon Sep 17 00:00:00 2001 From: file Date: Thu, 18 May 2006 15:46:01 +0000 Subject: Majorly improve fxotune with lots of cool new features. (issue #7136 reported and created by trumpetinc) git-svn-id: http://svn.digium.com/svn/zaptel/trunk@1075 5390a7c7-147a-4af0-8ec9-7488f05a26cb --- fxotune.c | 953 ++++++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 771 insertions(+), 182 deletions(-) (limited to 'fxotune.c') diff --git a/fxotune.c b/fxotune.c index 18d1540..7d8c508 100644 --- a/fxotune.c +++ b/fxotune.c @@ -21,6 +21,7 @@ #include #include #include +#include #ifdef STANDALONE_ZAPATA #include "zaptel.h" @@ -31,8 +32,8 @@ #include "fxotune.h" #define TEST_DURATION 2000 /* 4000 samples (or 500 ms) of test */ -#define BUFFER_LENGTH 4000 /* 4000 sample buffers */ -#define SKIP_BYTES 800 +#define BUFFER_LENGTH (2 * TEST_DURATION) /* 4000 sample buffers */ +#define SKIP_SAMPLES 800 /* skip first 100 ms of test when computing echo powers - gives the system time to acquire data */ static const float loudness = 16384.0; @@ -40,30 +41,173 @@ static char *zappath = "/dev/zap"; static char *configfile = "/etc/fxotune.conf"; static char *usage = -"Usage: fxotune\n" -" -i : Tests for FXO modules and sets echo coefficients, dialing to clear dialtone\n" -" -s : Sets the FXO modules echo coefficients on your system to the best settings\n"; +"Usage: fxotune [-v[vv] (-s | -i | -d )\n" +"\n" +" -s : set priviously calibrated echo settings\n" +" -i : calibrate echo settings\n" +" options : [] [-t ]\n" +" [-b ][-e ]\n" +" [-n ][-l ][-m ]\n" +" -d : dump input and output waveforms to ./fxotune_dump.vals\n" +" options : [-b ][-w ]\n" +" [-n ][-l ][-m ]\n" +" -v : more output (-vv, -vvv also)\n" +"\n" +" - type of calibration\n" +" (default 2, old method 1)\n" +" \n" +" - defines a range of devices to test\n" +" (default: 1-252)\n" +" - string to dial to clear the line\n" +" (default 5)\n" +" - seconds to wait for line to clear (default 0)\n" +" - seconds before line will no longer be clear\n" +" (default 18)\n" +" - the device to perform waveform dump on\n" +" (default 1)\n" +" - -1 for multitone waveform, or frequency of\n" +" single tone (default -1)\n"; + #define OUT_OF_BOUNDS(x) ((x) < 0 || (x) > 255) +struct silence_info{ + char *dialstr; + /** fd of device we are working with */ + int device; + /** seconds we should wait after dialing the dialstring before we know for sure we'll have silence */ + int initial_delay; + /** seconds after which a reset should occur */ + int reset_after; + /** time of last reset */ + struct timeval last_reset; +}; + static short outbuf[TEST_DURATION]; static int debug = 0; +/** + * Makes sure that the line is clear. + * Right now, we do this by relying on the user to specify how long after dialing the + * dialstring we can rely on the line being silent (before the telco complains about + * the user not hitting the next digit). + * + * A more robust way to do this would be to actually measure the sound levels on the line, + * but that's a lot more complicated, and this should work. + * + * @return 0 if succesful (no errors), 1 if unsuccesful + */ +static int ensure_silence(struct silence_info *info) +{ + struct timeval tv; + long elapsedms; + + gettimeofday(&tv, NULL); + elapsedms = ((tv.tv_sec - info->last_reset.tv_sec) * 1000L + (tv.tv_usec - info->last_reset.tv_usec)); + + if (elapsedms < info->reset_after * 1000) + return 0; + + if (debug > 1){ + fprintf(stdout, "Resetting line\n"); + } + + /* do a line reset */ + /* prepare line for silence */ + /* Do line hookstate reset */ + int x = ZT_ONHOOK; + + if (ioctl(info->device, ZT_HOOK, &x)) { + fprintf(stderr, "Unable to hang up fd %d\n", info->device); + return -1; + } + + sleep(2); + x = ZT_OFFHOOK; + if (ioctl(info->device, ZT_HOOK, &x)) { + fprintf(stderr, "Cannot bring fd %d off hook\n", info->device); + return -1; + } + sleep(2); /* Added to ensure that dial can actually takes place */ + + struct zt_dialoperation dop; + memset(&dop, 0, sizeof(dop)); + dop.op = ZT_DIAL_OP_REPLACE; + dop.dialstr[0] = 'T'; + strncpy(dop.dialstr + 1, info->dialstr, sizeof(dop.dialstr) - 1); -/* Generates a tone of hz frequency. Index is the current sample - * to begenerated. For a normal waveform you need to increment - * this every time you execute the function. - * Returns a 16bit slinear sample. */ + if (ioctl(info->device, ZT_DIAL, &dop)) { + fprintf(stderr, "Unable to dial!\n"); + return -1; + } + sleep(1); + sleep(info->initial_delay); + + + gettimeofday(&info->last_reset, NULL); + + + return 0; +} + +/** + * Generates a tone of specified frequency. + * + * @param hz the frequency of the tone to be generated + * @param index the current sample + * to begenerated. For a normal waveform you need to increment + * this every time you execute the function. + * + * @return 16bit slinear sample for the specified index + */ static short inline gentone(int hz, int index) { return loudness * sin((index * 2.0 * M_PI * hz)/8000); } -/* Returns the power of the buffer of samples in 16bit slinear format. - * power function = (sum of squares) - (square of sums). */ +/** + * Generates a waveform of several frequencies. + * + * @param index the current sample + * to begenerated. For a normal waveform you need to increment + * this every time you execute the function. + * + * @return 16bit slinear sample for the specified index + */ +static short inline genwaveform(int index) +{ + int freqs[] = {300, 600, 900, 1004, 1300, 1600}; /* chose 1004 Hz to include the milliwatt test tone frequency - but there was no particular reson to do so */ + int freqcount = 6; + int i = 0; + float response = (float)0; + for (i = 0; i < freqcount; i++){ + response += sin((index * 2.0 * M_PI * freqs[i])/8000); + } + + + return loudness * response / freqcount; +} + + +/** + * Calculates the RMS of the waveform buffer of samples in 16bit slinear format. + * prebuf the buffer of either shorts or floats + * bufsize the number of elements in the prebuf buffer (not the number of bytes!) + * short_format 1 if prebuf points to an array of shorts, 0 if it points to an array of floats + * + * Formula for RMS (http://en.wikipedia.org/wiki/Root_mean_square): + * + * Xrms = sqrt(1/N Sum(x1^2, x2^2, ..., xn^2)) + * + * Note: this isn't really a power calculation - but it gives a good measure of the level of the response + * + * @param prebuf the buffer containing the values to compute + * @param bufsize the size of the buffer + * @param short_format 1 if prebuf contains short values, 0 if it contains float values + */ static float power_of(void *prebuf, int bufsize, int short_format) { - float sum_of_squares = 0, square_of_sums = 0; + float sum_of_squares = 0; int numsamples = 0; float finalanswer = 0; short *sbuf = (short*)prebuf; @@ -75,58 +219,317 @@ static float power_of(void *prebuf, int bufsize, int short_format) if (bufsize <= 0) return -1; - if (bufsize % 2 != 0) - return -2; - - numsamples = bufsize / 2; + numsamples = bufsize; /* Got rid of divide by 2 - the bufsize parameter should give the number of samples (that's what it does for the float computation, and it should do it here as well) */ for (i = 0; i < numsamples; i++) { - sum_of_squares += ((float)sbuf[i] * (float)sbuf[i])/(float)loudness; - if (sbuf[i] > 0) { - square_of_sums += (float)sbuf[i]; - } else { - sbuf[i] *= -1; - square_of_sums += (float)sbuf[i]; - } + sum_of_squares += ((float)sbuf[i] * (float)sbuf[i]); } } else { /* Version for float inputs */ for (i = 0; i < bufsize; i++) { sum_of_squares += (fbuf[i] * fbuf[i]); - square_of_sums += fbuf[i]; } } - if (debug) printf("sum_of_squares = %f\n", sum_of_squares); - - square_of_sums *= square_of_sums; /* sums ^ 2 */ - - if (debug) printf("square_of_sums = %f\n", square_of_sums); - - finalanswer = square_of_sums - sum_of_squares; - - if (debug) printf("finalanswer = %f\n", finalanswer); + finalanswer = sum_of_squares/(float)bufsize; /* need to divide by the number of elements in the sample for RMS calc */ if (finalanswer < 0) { printf("Error: Final answer negative number %f\n", finalanswer); return -3; } -#if 0 - finalanswer = finalanswer * (float)-1; -#endif return sqrtf(finalanswer); } -/* Tune the line impedance. Look for best response range */ -static int acim_tune(int whichzap, char *dialstr) +/** + * dumps input and output buffer contents for the echo test - used to see exactly what's going on + */ +static int maptone(int whichzap, int freq, char *dialstr, int delayuntilsilence) +{ + int i = 0; + int res = 0, x = 0; + struct zt_bufferinfo bi; + short inbuf[TEST_DURATION]; /* changed from BUFFER_LENGTH - this buffer is for short values, so it should be allocated using the length of the test */ + FILE *outfile = NULL; + + + outfile = fopen("fxotune_dump.vals", "w"); + if (!outfile) { + fprintf(stdout, "Cannot create fxotune_dump.vals\n"); + return -1; + } + + x = 1; + if (ioctl(whichzap, ZT_SETLINEAR, &x)) { + fprintf(stderr, "Unable to set channel to signed linear mode.\n"); + return -1; + } + + memset(&bi, 0, sizeof(bi)); + if (ioctl(whichzap, ZT_GET_BUFINFO, &bi)) { + fprintf(stderr, "Unable to get buffer information!\n"); + return -1; + } + bi.numbufs = 2; + bi.bufsize = TEST_DURATION; /* KD - changed from BUFFER_LENGTH; */ + bi.txbufpolicy = ZT_POLICY_IMMEDIATE; + bi.rxbufpolicy = ZT_POLICY_IMMEDIATE; + if (ioctl(whichzap, ZT_SET_BUFINFO, &bi)) { + fprintf(stderr, "Unable to set buffer information!\n"); + return -1; + } + + /* Fill the output buffers */ + int leadin = 50; + int trailout = 100; + for (i = 0; i < leadin; i++) + outbuf[i] = 0; + for (; i < TEST_DURATION - trailout; i++){ + outbuf[i] = freq > 0 ? gentone(freq, i) : genwaveform(i); /* if frequency is negative, use a multi-part waveform instead of a single frequency */ + } + for (; i < TEST_DURATION; i++) + outbuf[i] = 0; + + /* Make sure the line is clear */ + struct silence_info sinfo; + memset(&sinfo, 0, sizeof(sinfo)); + sinfo.device = whichzap; + sinfo.dialstr = dialstr; + sinfo.initial_delay = delayuntilsilence; + sinfo.reset_after = 4; /* doesn't matter - we are only running one test */ + + if (ensure_silence(&sinfo)){ + fprintf(stderr, "Unable to get a clear outside line\n"); + return -1; + } + + /* Flush buffers */ + x = ZT_FLUSH_READ | ZT_FLUSH_WRITE | ZT_FLUSH_EVENT; + if (ioctl(whichzap, ZT_FLUSH, &x)) { + fprintf(stderr, "Unable to flush I/O: %s\n", strerror(errno)); + return -1; + } + + /* send data out on line */ + res = write(whichzap, outbuf, BUFFER_LENGTH); /* we are sending a TEST_DURATION length array of shorts (which are 2 bytes each) */ + if (res != BUFFER_LENGTH) { + fprintf(stderr, "Could not write all data to line\n"); + return -1; + } + + /* read return response */ + res = read(whichzap, inbuf, BUFFER_LENGTH); + if (res != BUFFER_LENGTH) { + fprintf(stderr, "Could not fill input buffer\n"); + return -1; + } + + /* write content of output buffer to debug file */ + float power_result = power_of(inbuf, TEST_DURATION, 1); + float power_waveform = power_of(outbuf, TEST_DURATION, 1); + float echo = power_result/power_waveform; + + fprintf(outfile, "Buffers, freq=%d, outpower=%0.0f, echo=%0.4f\n", freq, power_result, echo); + fprintf(outfile, "Sample, Input (received from the line), Output (sent to the line)\n"); + for (i = 0; i < TEST_DURATION; i++){ + fprintf(outfile, "%d, %d, %d\n", + i, + inbuf[i], + outbuf[i] + ); + } + + fclose(outfile); + + fprintf(stdout, "echo ratio = %0.4f (%0.1f / %0.1f)\n", echo, power_result, power_waveform); + + return 0; +} + + + +/** + * Perform calibration type 2 on the specified device + * + * Determine optimum echo coefficients for the specified device + * + * New tuning strategy. If we have a number that we can dial that will result in silence from the + * switch, the tune will be *much* faster (we don't have to keep hanging up and dialing a digit, etc...) + * The downside is that the user needs to actually find a 'no tone' phone number at their CO's switch - but for + * really fixing echo problems, this is what it takes. + * + * Also, for the purposes of optimizing settings, if we pick a single frequency and test with that, + * we can try a whole bunch of impedence/echo coefficients. This should give better results than trying + * a bunch of frequencies, and we can always do a a frequency sweep to pick between the best 3 or 4 + * impedence/coefficients configurations. + * + * Note: It may be possible to take this even further and do some pertubation analysis on the echo coefficients + * themselves (maybe use the 64 entry sweep to find some settings that are close to working well, then + * deviate the coefficients a bit to see if we can improve things). A better way to do this would be to + * use the optimization strategy from silabs. For reference, here is an application note that describes + * the echo coefficients (and acim values): + * + * http://www.silabs.com/public/documents/tpub_doc/anote/Wireline/Silicon_DAA/en/an84.pdf + * + */ +static int acim_tune2(int whichzap, int freq, char *dialstr, int delayuntilsilence, int silencegoodfor, struct wctdm_echo_coefs *coefs_out) +{ + int i = 0; + int res = 0, x = 0; + int lowesttry = -1; + float lowesttryresult = 999999999999.0; + float lowestecho = 999999999999.0;; + struct zt_bufferinfo bi; + + short inbuf[TEST_DURATION]; + FILE *outfile = NULL; + + + if (debug) { + outfile = fopen("fxotune.vals", "w"); + if (!outfile) { + fprintf(stdout, "Cannot create fxotune.vals\n"); + return -1; + } + } + + /* Set echo settings */ + if (ioctl(whichzap, WCTDM_SET_ECHOTUNE, &echo_trys[0])) { + fprintf(stderr, "Unable to set impedance on fd %d\n", whichzap); + return -1; + } + + x = 1; + if (ioctl(whichzap, ZT_SETLINEAR, &x)) { + fprintf(stderr, "Unable to set channel to signed linear mode.\n"); + return -1; + } + + memset(&bi, 0, sizeof(bi)); + if (ioctl(whichzap, ZT_GET_BUFINFO, &bi)) { + fprintf(stderr, "Unable to get buffer information!\n"); + return -1; + } + bi.numbufs = 2; + bi.bufsize = BUFFER_LENGTH; + bi.txbufpolicy = ZT_POLICY_IMMEDIATE; + bi.rxbufpolicy = ZT_POLICY_IMMEDIATE; + if (ioctl(whichzap, ZT_SET_BUFINFO, &bi)) { + fprintf(stderr, "Unable to set buffer information!\n"); + return -1; + } + x = ZT_OFFHOOK; + if (ioctl(whichzap, ZT_HOOK, &x)) { + fprintf(stderr, "Cannot bring fd %d off hook", whichzap); + return -1; + } + + + /* Set up silence settings */ + struct silence_info sinfo; + memset(&sinfo, 0, sizeof(sinfo)); + sinfo.device = whichzap; + sinfo.dialstr = dialstr; + sinfo.initial_delay = delayuntilsilence; + sinfo.reset_after = silencegoodfor; + + /* Fill the output buffers */ + for (i = 0; i < TEST_DURATION; i++) + outbuf[i] = freq > 0 ? gentone(freq, i) : genwaveform(i); /* if freq is negative, use a multi-frequency waveform */ + + /* compute power of input (so we can later compute echo levels relative to input) */ + float waveform_power = power_of(outbuf+SKIP_SAMPLES, TEST_DURATION-SKIP_SAMPLES, 1); + + + /* sweep through the various coefficient settings and see how our responses look */ + + int echo_trys_size = 64; + int trys = 0; + for (trys = 0; trys < echo_trys_size; trys++){ + + /* ensure silence on the line */ + if (ensure_silence(&sinfo)){ + fprintf(stderr, "Unable to get a clear outside line\n"); + return -1; + } + + if (ioctl(whichzap, WCTDM_SET_ECHOTUNE, &echo_trys[trys])) { + fprintf(stderr, "Unable to set echo coefficients on fd %d\n", whichzap); + return -1; + } + + /* Flush buffers */ + x = ZT_FLUSH_READ | ZT_FLUSH_WRITE | ZT_FLUSH_EVENT; + if (ioctl(whichzap, ZT_FLUSH, &x)) { + fprintf(stderr, "Unable to flush I/O: %s\n", strerror(errno)); + return -1; + } + + /* send data out on line */ + res = write(whichzap, outbuf, BUFFER_LENGTH); + if (res != BUFFER_LENGTH) { + fprintf(stderr, "Could not write all data to line\n"); + return -1; + } + + /* read return response */ + res = read(whichzap, inbuf, BUFFER_LENGTH); + if (res != BUFFER_LENGTH) { + fprintf(stderr, "Could not fill input buffer - got %d bytes, expected %d bytes\n", res, BUFFER_LENGTH); + return -1; + } + + /* calculate RMS of response */ + + float freq_result = power_of(inbuf+SKIP_SAMPLES, TEST_DURATION-SKIP_SAMPLES, 1); + float echo = freq_result/waveform_power; + + if (freq_result < lowesttryresult){ + lowesttry = trys; + lowesttryresult = freq_result; + lowestecho = echo; + } + if (debug) { + char result[256]; + snprintf(result, sizeof(result), "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%f,%f", + echo_trys[trys].acim, + echo_trys[trys].coef1, + echo_trys[trys].coef2, + echo_trys[trys].coef3, + echo_trys[trys].coef4, + echo_trys[trys].coef5, + echo_trys[trys].coef6, + echo_trys[trys].coef7, + echo_trys[trys].coef8, + freq, + freq_result, + echo + ); + + fprintf(outfile, "%s\n", result); + if (debug > 1) + fprintf(stdout, "%s\n", result); + } + } + + if (debug > 0) + fprintf(stdout, "Config with lowest response = %d, power = %0.0f, echo = %0.4f\n", lowesttry, lowesttryresult, lowestecho); + + memcpy(coefs_out, &echo_trys[lowesttry], sizeof(struct wctdm_echo_coefs)); + + return 0; +} + +/** + * Perform calibration type 1 on the specified device. Only tunes the line impedance. Look for best response range + */ +static int acim_tune(int whichzap, char *dialstr, int delayuntilsilence, int silencegoodfor, struct wctdm_echo_coefs *coefs_out) { int i = 0, freq = 0, acim = 0; int res = 0, x = 0; struct zt_bufferinfo bi; - struct zt_dialoperation dop; struct wctdm_echo_coefs coefs; - short inbuf[BUFFER_LENGTH]; + short inbuf[TEST_DURATION]; /* changed from BUFFER_LENGTH - this buffer is for short values, so it should be allocated using the length of the test */ int lowest = 0; FILE *outfile = NULL; float acim_results[16]; @@ -140,6 +543,14 @@ static int acim_tune(int whichzap, char *dialstr) } } + /* Set up silence settings */ + struct silence_info sinfo; + memset(&sinfo, 0, sizeof(sinfo)); + sinfo.device = whichzap; + sinfo.dialstr = dialstr; + sinfo.initial_delay = delayuntilsilence; + sinfo.reset_after = silencegoodfor; + /* Set echo settings */ memset(&coefs, 0, sizeof(coefs)); if (ioctl(whichzap, WCTDM_SET_ECHOTUNE, &coefs)) { @@ -166,15 +577,9 @@ static int acim_tune(int whichzap, char *dialstr) fprintf(stderr, "Unable to set buffer information!\n"); return -1; } - x = ZT_OFFHOOK; - if (ioctl(whichzap, ZT_HOOK, &x)) { - fprintf(stderr, "Cannot bring fd %d off hook", whichzap); - return -1; - } for (acim = 0; acim < 16; acim++) { float freq_results[15]; - int needtoreset = 0; coefs.acim = acim; if (ioctl(whichzap, WCTDM_SET_ECHOTUNE, &coefs)) { @@ -182,41 +587,17 @@ static int acim_tune(int whichzap, char *dialstr) return -1; } - for (freq = 200; freq <=3000; freq+=200, needtoreset++) { + for (freq = 200; freq <=3000; freq+=200) { /* Fill the output buffers */ for (i = 0; i < TEST_DURATION; i++) outbuf[i] = gentone(freq, i); - /* Prepare line for data */ - if (needtoreset > 8) { - /* Do line hookstate reset */ - x = ZT_ONHOOK; - - if (ioctl(whichzap, ZT_HOOK, &x)) { - fprintf(stderr, "Unable to hang up fd %d\n", whichzap); - return -1; - } - - sleep(2); - x = ZT_OFFHOOK; - if (ioctl(whichzap, ZT_HOOK, &x)) { - fprintf(stderr, "Cannot bring fd %d off hook", whichzap); - return -1; - } - - memset(&dop, 0, sizeof(dop)); - dop.op = ZT_DIAL_OP_REPLACE; - dop.dialstr[0] = 'T'; - strncpy(dop.dialstr + 1, dialstr, sizeof(dop.dialstr) - 1); - printf("."); - if (ioctl(whichzap, ZT_DIAL, &dop)) { - fprintf(stderr, "Unable to dial!\n"); - return -1; - } - sleep(2); - - needtoreset = 0; + /* Make sure line is ready for next test iteration */ + if (ensure_silence(&sinfo)){ + fprintf(stderr, "Unable to get a clear line\n"); + return -1; } + /* Flush buffers */ x = ZT_FLUSH_READ | ZT_FLUSH_WRITE | ZT_FLUSH_EVENT; @@ -241,7 +622,7 @@ static int acim_tune(int whichzap, char *dialstr) /* calculate power of response */ - freq_results[(freq/200)-1] = power_of(inbuf+SKIP_BYTES, BUFFER_LENGTH-SKIP_BYTES, 1); + freq_results[(freq/200)-1] = power_of(inbuf+SKIP_SAMPLES, TEST_DURATION-SKIP_SAMPLES, 1); /* changed from inbuf+SKIP_BYTES, BUFFER_LENGTH-SKIP_BYTES, 1 */ if (debug) fprintf(outfile, "%d,%d,%f\n", acim, freq, freq_results[(freq/200)-1]); } acim_results[acim] = power_of(freq_results, 15, 0); @@ -259,141 +640,349 @@ static int acim_tune(int whichzap, char *dialstr) } } - return lowest; + coefs_out->acim = lowest; + coefs_out->coef1 = 0; + coefs_out->coef2 = 0; + coefs_out->coef3 = 0; + coefs_out->coef4 = 0; + coefs_out->coef5 = 0; + coefs_out->coef6 = 0; + coefs_out->coef7 = 0; + coefs_out->coef8 = 0; + + return 0; } + +/** + * Reads echo register settings from the configuration file and pushes them into + * the appropriate devices + * + * @param configfilename the path of the file that the calibration results should be written to + * + * @return 0 if successful, !0 otherwise + */ +static int do_set(char *configfilename) +{ + FILE *fp = NULL; + int res = 0; + int fd = 0; + + fp = fopen(configfile, "r"); -int main (int argc , char **argv) + if (!fp) { + fprintf(stdout, "Cannot open %s!\n",configfile); + return -1; + } + + + while (res != EOF) { + struct wctdm_echo_coefs mycoefs; + char completezappath[56] = ""; + int myzap,myacim,mycoef1,mycoef2,mycoef3,mycoef4,mycoef5,mycoef6,mycoef7,mycoef8; + + + res = fscanf(fp, "%d=%d,%d,%d,%d,%d,%d,%d,%d,%d",&myzap,&myacim,&mycoef1, + &mycoef2,&mycoef3,&mycoef4,&mycoef5,&mycoef6,&mycoef7, + &mycoef8); + + if (res == EOF) { + break; + } + + /* Check to be sure conversion is done correctly */ + if (OUT_OF_BOUNDS(myacim) || OUT_OF_BOUNDS(mycoef1)|| + OUT_OF_BOUNDS(mycoef2)|| OUT_OF_BOUNDS(mycoef3)|| + OUT_OF_BOUNDS(mycoef4)|| OUT_OF_BOUNDS(mycoef5)|| + OUT_OF_BOUNDS(mycoef6)|| OUT_OF_BOUNDS(mycoef7)|| OUT_OF_BOUNDS(mycoef8)) { + + fprintf(stdout, "Bounds check error on inputs from %s:%d\n", configfile, myzap); + return -1; + } + + mycoefs.acim = myacim; + mycoefs.coef1 = mycoef1; + mycoefs.coef2 = mycoef2; + mycoefs.coef3 = mycoef3; + mycoefs.coef4 = mycoef4; + mycoefs.coef5 = mycoef5; + mycoefs.coef6 = mycoef6; + mycoefs.coef7 = mycoef7; + mycoefs.coef8 = mycoef8; + + snprintf(completezappath, sizeof(completezappath), "%s/%d", zappath, myzap); + fd = open(completezappath, O_RDWR); + + if (fd < 0) { + fprintf(stdout, "open error on %s: %s\n", completezappath, strerror(errno)); + return -1; + } + + if (ioctl(fd, WCTDM_SET_ECHOTUNE, &mycoefs)) { + fprintf(stdout, "%s: %s\n", completezappath, strerror(errno)); + return -1; + } + + close(fd); + } + + fclose(fp); + + fprintf(stdout, "fxotune: successfully set echo coeffecients on FXO modules\n"); + return 0; +} + +/** + * Output waveform information from a single test + * + * Clears the line, then sends a single waveform (multi-tone, or single tone), and listens + * for the response on the line. Output is written to fxotune_dump.vals + * + * @param startdev the device to test + * @param dialstr the string that should be dialed to clear the dialtone from the line + * @param delayuntilsilence the number of seconds to wait after dialing dialstr before starting the test + * @param silencegoodfor the number of seconds that the test can run before having to reset the line again + * (this is basically the amount of time it takes before the 'if you'd like to make a call...' message + * kicks in after you dial dialstr. This test is so short that the value is pretty much ignored. + * @param waveformtype the type of waveform to use - -1 = multi-tone waveform, otherwise the specified value + * is used as the frequency of a single tone. A value of 0 will output silence. + */ +static int do_dump(int startdev, char* dialstr, int delayuntilsilence, int silencegoodfor, int waveformtype) { - char zapdev[80] = ""; - int i = 0; - int fd; int res = 0; - int configfd; - FILE *fp = NULL; + int fd; + char zapdev[80] = ""; + + int zapmodule = startdev; + snprintf(zapdev, sizeof(zapdev), "%s/%d", zappath, zapmodule); - if ((argc < 2) || (argc > 3)) { - /* Show usage */ - fputs(usage, stdout); + fd = open(zapdev, O_RDWR); + if (fd < 0) { + fprintf(stdout, "%s absent: %s\n", zapdev, strerror(errno)); return -1; } - if (!strcasecmp(argv[1], "-s")) { -set: + fprintf(stdout, "Dumping module %s\n", zapdev); + res = maptone(fd, waveformtype, dialstr, delayuntilsilence); - fp = fopen(configfile, "r"); + close(fd); - if (!fp) { - fprintf(stdout, "Cannot open %s!\n",configfile); - return -1; - } + if (res) { + fprintf(stdout, "Failure!\n"); + return res; + } else { + fprintf(stdout, "Done!\n"); + return 0; + } - for (i = 0;res != EOF; i++) { - struct wctdm_echo_coefs mycoefs; - char completezappath[56] = ""; - int myzap,myacim,mycoef1,mycoef2,mycoef3,mycoef4,mycoef5,mycoef6,mycoef7,mycoef8; - - - res = fscanf(fp, "%d=%d,%d,%d,%d,%d,%d,%d,%d,%d",&myzap,&myacim,&mycoef1, - &mycoef2,&mycoef3,&mycoef4,&mycoef5,&mycoef6,&mycoef7, - &mycoef8); - - if (res == EOF) { - break; - } - - /* Check to be sure conversion is done correctly */ - if (OUT_OF_BOUNDS(myacim) || OUT_OF_BOUNDS(mycoef1)|| - OUT_OF_BOUNDS(mycoef2)|| OUT_OF_BOUNDS(mycoef3)|| - OUT_OF_BOUNDS(mycoef4)|| OUT_OF_BOUNDS(mycoef5)|| - OUT_OF_BOUNDS(mycoef6)|| OUT_OF_BOUNDS(mycoef7)|| OUT_OF_BOUNDS(mycoef8)) { - - fprintf(stdout, "Bounds check error on inputs from %s:%d\n", configfile, i+1); - return -1; - } +} + +/** + * Performs calibration on all specified devices + * + * @param startdev the first device to check + * @param enddev the last device to check + * @param calibtype the type of calibration to perform. 1=old style (loops through individual frequencies + * doesn't optimize echo coefficients. 2=new style (uses multi-tone and optimizes echo coefficients + * and acim setting) + * @param configfilename the path of the file that the calibration results should be written to + * @param dialstr the string that should be dialed to clear the dialtone from the line + * @param delayuntilsilence the number of seconds to wait after dialing dialstr before starting the test + * @param silencegoodfor the number of seconds that the test can run before having to reset the line again + * (this is basically the amount of time it takes before the 'if you'd like to make a call...' message + * kicks in after you dial dialstr + * + * @return 0 if successful, !0 otherwise + */ +static int do_calibrate(int startdev, int enddev, int calibtype, char* configfilename, char* dialstr, int delayuntilsilence, int silencegoodfor) +{ + int problems = 0; + int res = 0; + int configfd, fd; + int devno = 0; + char zapdev[80] = ""; + struct wctdm_echo_coefs coefs; - mycoefs.acim = myacim; - mycoefs.coef1 = mycoef1; - mycoefs.coef2 = mycoef2; - mycoefs.coef3 = mycoef3; - mycoefs.coef4 = mycoef4; - mycoefs.coef5 = mycoef5; - mycoefs.coef6 = mycoef6; - mycoefs.coef7 = mycoef7; - mycoefs.coef8 = mycoef8; + configfd = open(configfile, O_CREAT|O_TRUNC|O_WRONLY); + + if (configfd < 0) { + fprintf(stderr, "open: %s\n", strerror(errno)); + return -1; + } + + for (devno = startdev; devno <= enddev; devno++) { + snprintf(zapdev, sizeof(zapdev), "%s/%d", zappath, devno); + + fd = open(zapdev, O_RDWR); + if (fd < 0) { + fprintf(stdout, "%s absent: %s\n", zapdev, strerror(errno)); + continue; + } + + fprintf(stdout, "Tuning module %s\n", zapdev); - snprintf(completezappath, sizeof(completezappath), "%s/%d", zappath, myzap); - fd = open(completezappath, O_RDWR); + if (1 == calibtype) + res = acim_tune(fd, dialstr, delayuntilsilence, silencegoodfor, &coefs); + else + res = acim_tune2(fd, -1, dialstr, delayuntilsilence, silencegoodfor, &coefs); - if (fd < 0) { - fprintf(stdout, "open error on %s: %s\n", completezappath, strerror(errno)); - return -1; - } + close(fd); + + if (res) { + fprintf(stdout, "Failure!\n"); + problems++; + } else { + fprintf(stdout, "Done!\n"); + } - if (ioctl(fd, WCTDM_SET_ECHOTUNE, &mycoefs)) { - fprintf(stdout, "%s: %s\n", completezappath, strerror(errno)); + if (res == 0) { + + /* Do output to file */ + int len = 0; + static char output[255] = ""; + + snprintf(output, sizeof(output), "%d=%d,%d,%d,%d,%d,%d,%d,%d,%d\n", + devno, + coefs.acim, + coefs.coef1, + coefs.coef2, + coefs.coef3, + coefs.coef4, + coefs.coef5, + coefs.coef6, + coefs.coef7, + coefs.coef8 + ); + + if (debug) + fprintf(stdout, "Found best echo coefficients: %s\n", output); + + len = strlen(output); + res = write(configfd, output, strlen(output)); + if (res != len) { + fprintf(stdout, "Unable to write line \"%s\" to file.\n", output); return -1; } - - close(fd); } + } - fclose(fp); + close(configfd); - fprintf(stdout, "fxotune: successfully set echo coeffecients on FXO modules\n"); - return 0; - } + if (problems) + fprintf(stdout, "Unable to tune %d devices, even though those devices are present\n", problems); - if (!strcasecmp(argv[1], "-i")) { - if (argc != 3) { + return problems; +} + +int main (int argc , char **argv) +{ + int startdev = 1; /* -b */ + int stopdev = 252; /* -e */ + int calibtype = 2; /* -t */ + int waveformtype = -1; /* -w multi-tone by default. If > 0, single tone of specified frequency */ + int delaytosilence = 0; /* -l */ + int silencegoodfor = 18; /* -m */ + + char* dialstr = "5"; /* -n */ + + int res = 0; + + int doset = 0; /* -s */ + int docalibrate = 0; /* -i */ + int dodump = 0; /* -d */ + + int i = 0; + + for (i = 1; i < argc; i++){ + if (!(argv[i][0] == '-' || argv[i][0] == '/') || (strlen(argv[i]) <= 1)){ + fprintf(stdout, "Unknown option : %s\n", argv[i]); /* Show usage */ fputs(usage, stdout); return -1; } - configfd = open(configfile, O_CREAT|O_TRUNC|O_WRONLY); - - if (configfd < 0) { - fprintf(stderr, "open: %s\n", strerror(errno)); - return -1; - } - - for (i = 0; i < 252; i++) { - snprintf(zapdev, sizeof(zapdev), "%s/%d", zappath, i+1); - - fd = open(zapdev, O_RDWR); - if (fd < 0) { - fprintf(stdout, "%s absent: %s\n", zapdev, strerror(errno)); + int moreargs = (i < argc - 1); + + switch(argv[i][1]){ + case 's': + doset=1; continue; - } - - printf("Tuning module %d", i + 1); - res = acim_tune(fd, argv[2]); /* Shouldn't matter what digit we press */ - if (res < 0) - printf("Failure!\n"); - else - printf("Done!\n"); - - close(fd); - if (res > -1) { - /* Do output to file */ - int len = 0; - static char output[255] = ""; - snprintf(output, sizeof(output), "%d=%d,%d,%d,%d,%d,%d,%d,%d,%d\n", i+1, res, - 0, 0, 0, 0, 0, 0, 0, 0); - len = strlen(output); - res = write(configfd, output, strlen(output)); - if (res != len) { - fprintf(stdout, "Unable to write line \"%s\" to file.\n", output); - return -1; + case 'i': + docalibrate = 1; + if (moreargs){ /* we need to check for a value after 'i' for backwards compatability with command line options of old fxotune */ + if (argv[i+1][0] != '-' && argv[i+1][0] != '/') + dialstr = argv[++i]; } - } + continue; + case 'd': + dodump = 1; + continue; + case 'b': + startdev = moreargs ? atoi(argv[++i]) : startdev; + break; + case 'e': + stopdev = moreargs ? atoi(argv[++i]) : stopdev; + break; + case 't': + calibtype = moreargs ? atoi(argv[++i]) : calibtype; + break; + case 'w': + waveformtype = moreargs ? atoi(argv[++i]) : waveformtype; + break; + case 'l': + delaytosilence = moreargs ? atoi(argv[++i]) : delaytosilence; + break; + case 'm': + silencegoodfor = moreargs ? atoi(argv[++i]) : silencegoodfor; + break; + case 'n': + dialstr = moreargs ? argv[++i] : dialstr; + break; + case 'v': + debug = strlen(argv[i])-1; + break; + default: + fprintf(stdout, "Unknown option : %s\n", argv[i]); + /* Show usage */ + fputs(usage, stdout); + return -1; + } + } + + if (debug > 3){ + fprintf(stdout, "Running with parameters:\n"); + fprintf(stdout, "\tdoset=%d\n", doset); + fprintf(stdout, "\tdocalibrate=%d\n", docalibrate); + fprintf(stdout, "\tdodump=%d\n", dodump); + fprintf(stdout, "\tstartdev=%d\n", startdev); + fprintf(stdout, "\tstopdev=%d\n", stopdev); + fprintf(stdout, "\tcalibtype=%d\n", calibtype); + fprintf(stdout, "\twaveformtype=%d\n", calibtype); + fprintf(stdout, "\tdelaytosilence=%d\n", calibtype); + fprintf(stdout, "\tsilencegoodfor=%d\n", calibtype); + fprintf(stdout, "\tdialstr=%s\n", dialstr); + fprintf(stdout, "\tdebug=%d\n", debug); + } - close(configfd); - /* Some getto goto hackery to make this more convenient */ - goto set; + if (docalibrate){ + res = do_calibrate(startdev, stopdev, calibtype, configfile, dialstr, delaytosilence, silencegoodfor); + if (!res) + return do_set(configfile); + else + return -1; } - fputs(usage, stdout); + if (doset) + return do_set(configfile); + + if (dodump){ + res = do_dump(startdev, dialstr, delaytosilence, silencegoodfor, waveformtype); + if (!res) + return 0; + else + return -1; + } - return 0; + + fputs(usage, stdout); + return -1; } -- cgit v1.2.3