1 /* 2 * @(#)$Id: file.c,v 2.9 2009/01/17 02:31:16 baccala Exp $ 3 * 4 * Copyright (C) 1996 - 2000 Tim Witham <twitham@quiknet.com> 5 * 6 * (see the files README and COPYING for more details) 7 * 8 * This file implements the file and command-line I/O 9 * 10 */ 11 12 #include <stdio.h> 13 #include <stdlib.h> 14 #include <string.h> 15 #include "oscope.h" /* program defaults */ 16 #include "display.h" /* display routines */ 17 #include "func.h" /* signal math functions */ 18 19 int backwards_compat_1_10 = 0; /* TRUE if parsing a pre-1.10 save file */ 20 int backwards_compat_2_0 = 0; /* TRUE if parsing a pre-2.0 save file */ 21 22 /* force num to stay within the range lo - hi */ 23 int 24 limit(int num, int lo, int hi) 25 { 26 if (num < lo) 27 num = lo; 28 if (num > hi) 29 num = hi; 30 return(num); 31 } 32 33 /* parse command-line or file opt character, using given optarg string 34 * 35 * optarg can have a trailing newline, if we're reading from a file 36 */ 37 38 void 39 handle_opt(int opt, char *optarg) 40 { 41 char *p, *q; 42 char buf[16]; 43 Channel *s; 44 45 switch (opt) { 46 case 'D': /* data source selection */ 47 if ((p = index(optarg, '\n')) != NULL) *p = '\0'; 48 if (!datasrc_byname(optarg)) { 49 fprintf(stderr, "Couldn't find data source %s\n\n", optarg); 50 usage(1); 51 } 52 break; 53 case 'o': /* data source specific option */ 54 case 'O': 55 /* Older data file format used -o for BitScope only, and printed 56 * BitScope options even if BitScope wasn't enabled, so if we're 57 * reading an older file, only process -o if data source is BitScope 58 */ 59 if ((datasrc != NULL) && 60 (!backwards_compat_1_10 || !strcasecmp(datasrc->name, "BitScope"))) { 61 if ((datasrc->set_option == NULL) 62 || (datasrc->set_option(optarg) == 0)) { 63 fprintf(stderr, "Couldn't set option %s\n\n", optarg); 64 usage(1); 65 } 66 } 67 break; 68 case 'r': /* soundcard sample rate - deprecated */ 69 case 'R': 70 if ((datasrc != NULL) && !strcasecmp(datasrc->name, "Soundcard") 71 && (datasrc->set_option != NULL)) { 72 snprintf(buf, sizeof(buf), "rate=%s", optarg); 73 datasrc->set_option(buf); 74 } 75 break; 76 case 's': /* scale (zoom) */ 77 case 'S': 78 scope.scale = limit(strtol(p = optarg, NULL, 0), 1, 1000); 79 if ((q = strchr(p, '/')) != NULL) 80 scope.div = limit(strtol(++q, NULL, 0), 1, 2000); 81 break; 82 case 't': /* trigger */ 83 case 'T': 84 scope.trig = limit(strtol(p = optarg, NULL, 0), 0, 255); 85 if ((q = strchr(p, ':')) != NULL) { 86 scope.trige = limit(strtol(++q, NULL, 0), 0, 2); 87 p = q; 88 } 89 if ((q = strchr(p, ':')) != NULL) { 90 if (*(++q) == 'x' || *q == 'X') 91 scope.trigch = 0; 92 else if (*q == 'y' || *q == 'Y') 93 scope.trigch = 1; 94 else 95 scope.trigch = strtol(q, NULL, 0); 96 } 97 if (datasrc && datasrc->set_trigger 98 && datasrc->set_trigger(scope.trigch, 99 &scope.trig, scope.trige) == 0) { 100 /* Unable to set trigger, so clear it */ 101 if (datasrc && datasrc->clear_trigger) datasrc->clear_trigger(); 102 scope.trige = 0; 103 } 104 break; 105 case 'l': /* cursor lines */ 106 case 'L': 107 scope.curs = 1; 108 scope.cursa = limit(strtol(p = optarg, NULL, 0), 1, MAXWID); 109 if ((q = strchr(p, ':')) != NULL) { 110 scope.cursb = limit(strtol(++q, NULL, 0), 1, MAXWID); 111 p = q; 112 } 113 if ((q = strchr(p, ':')) != NULL) 114 scope.curs = limit(strtol(++q, NULL, 0), 0, 1); 115 break; 116 case 'd': /* dma divisor (backwards compatibility) */ 117 if (datasrc && !strcasecmp(datasrc->name, "Soundcard")) { 118 snprintf(buf, sizeof(buf), "dma=%s", optarg); 119 handle_opt('o', buf); 120 } 121 break; 122 case 'f': /* font name */ 123 case 'F': 124 strcpy(fontname, optarg); 125 break; 126 case 'p': /* plotting mode */ 127 case 'P': 128 scope.mode = limit(strtol(optarg, NULL, 0), 0, 5); 129 break; 130 case 'g': /* graticule on/off */ 131 case 'G': 132 scope.grat = limit(strtol(optarg, NULL, 0), 0, 2); 133 break; 134 case 'b': /* behind/front */ 135 case 'B': 136 scope.behind = !DEF_B; 137 break; 138 case 'v': /* verbose display */ 139 case 'V': 140 scope.verbose = !DEF_V; 141 break; 142 case 'i': /* minimum update interval */ 143 case 'I': 144 scope.min_interval = strtol(optarg, NULL, 0); 145 break; 146 case 'x': /* sound card (backwards compatibility) */ 147 case 'X': 148 case 'y': 149 case 'Y': 150 break; 151 case 'z': /* serial scope (backwards compatibility) */ 152 case 'Z': 153 break; 154 case 'a': /* Active (selected) channel */ 155 case 'A': 156 scope.select = limit(strtol(optarg, NULL, 0) - 1, 0, CHANNELS - 1); 157 break; 158 case 'h': /* help */ 159 case 'H': 160 usage(0); 161 break; 162 case ':': /* unknown option */ 163 case '?': 164 usage(1); 165 break; 166 default: /* channel settings */ 167 if (opt >= '1' && opt <= '0' + CHANNELS) { 168 s = &ch[opt - '1']; 169 p = optarg; 170 s->show = 1; 171 if (*p == '+') { 172 s->show = 0; 173 p++; 174 } 175 176 /* Prior to the 2.0 release, signal position was stored as the 177 * number of y pixels to offset. Now it's stored as 100 times 178 * the decimal fraction s->pos, which is a floating point number 179 * ranging nominally from -1 (bottom of screen) to +1 (top). 180 * The data part of the display used to be 160 pixels less than 181 * v_points, so for a 640x480 display (the most common), the 182 * data part was 320 pixels tall, so we divide by 160, negative 183 * since the old number's zero point was at the top. 184 */ 185 s->pos = limit(-strtol(p, NULL, 0), -1280, 1280); 186 if (backwards_compat_2_0) s->pos /= -160; 187 else s->pos /= 100; 188 189 if ((q = strchr(p, '.')) != NULL) { 190 s->bits = limit(strtol(++q, NULL, 0), 0, 16); 191 p = q; 192 } 193 if ((q = strchr(p, ':')) != NULL) { 194 s->target_mult = limit(strtol(++q, NULL, 0), 1, 100); 195 p = q; 196 } 197 if ((q = strchr(p, '/')) != NULL) { 198 s->target_div = limit(strtol(++q, NULL, 0), 1, 100); 199 p = q; 200 } 201 if ((q = strchr(p, ':')) != NULL) { 202 if (*++q >= '0' && *q <= '9') { 203 if (*q > '0') { 204 function_bynum_on_channel(strtol(q, NULL, 0), s); 205 } 206 } else if (*q >= 'a' && *q <= 'z' 207 && (*(q + 1) == '\0' || *(q + 1) == '\n')) { 208 209 /* Older versions used x, y, z for data channels, now we 210 * use a, b, c, etc. Probescope used z exclusively 211 */ 212 213 if (datasrc && !backwards_compat_1_10 214 && ((*q - 'a') <= datasrc->nchans())) { 215 recall_on_channel(datasrc->chan(*q - 'a'), s); 216 } else if (datasrc && backwards_compat_1_10 && (*q == 'z') 217 && !strcasecmp(datasrc->name, "ProbeScope")) { 218 recall_on_channel(datasrc->chan(0), s); 219 } else if (datasrc && backwards_compat_1_10 && (*q >= 'x')) { 220 recall_on_channel(datasrc->chan(*q - 'x'), s); 221 } else { 222 /* Might have a (slight) problem here if we're 223 * reading an older format file that has a data source 224 * and memory saved on 'a', for example. Then we'll 225 * end up recalling mem 'a' here, even though 'a' 226 * is now used as a data channel. 227 */ 228 recall_on_channel(&mem[*q - 'a'], s); 229 } 230 231 } else { 232 start_command_on_channel(q, s); 233 } 234 } 235 } 236 break; 237 } 238 } 239 240 /* write scope settings and memory buffers to given filename */ 241 void 242 writefile(char *filename) 243 { 244 FILE *file; 245 int i, j, k = 0, l = 0, chan[26]; 246 char *s; 247 Channel *p; 248 249 if ((file = fopen(filename, "w")) == NULL) { 250 sprintf(error, "%s: can't write %s", progname, filename); 251 message(error); 252 return; 253 } 254 fprintf(file, "# %s, version %s\n\ 255 #\n\ 256 # -D %s\n", progname, version, datasrc->name); 257 258 if (datasrc->save_option != NULL) { 259 for (i=0; (s = datasrc->save_option(i)) != NULL; i++) { 260 if (s[0] != '\0') fprintf(file, "# -o %s\n", s); 261 } 262 } 263 264 fprintf(file, "# -a %d\n\ 265 # -s %d/%d\n\ 266 # -t %d:%d:%d\n\ 267 # -l %d:%d:%d\n\ 268 # -p %d\n\ 269 # -g %d\n\ 270 %s%s", 271 scope.select + 1, 272 scope.scale, scope.div, 273 scope.trig - 128, scope.trige, scope.trigch, 274 scope.cursa, scope.cursb, scope.curs, 275 scope.mode, 276 scope.grat, 277 scope.behind ? "# -b\n" : "", 278 scope.verbose ? "# -v\n" : ""); 279 for (i = 0 ; i < CHANNELS ; i++) { 280 p = &ch[i]; 281 if (p->signal) { 282 fprintf(file, "# -%d %s%d.%d:%d/%d:%s\n", i + 1, p->show ? "" : "+", 283 -(int)(p->pos*100), p->bits, p->target_mult, p->target_div, 284 p->signal->savestr); 285 } 286 } 287 /* XXX code need to be carefully checked out */ 288 for (i = 0 ; i < 26 ; i++) { 289 if (mem[i].num > 0) 290 chan[k++] = i; 291 if (mem[i].num > l) 292 l = mem[i].num; 293 } 294 if (k) { 295 for (i = 0 ; i < k ; i++) { 296 /* XXX color written is channel color (it can't be changed) */ 297 #if 0 298 fprintf(file, "%s%c(%02d)", i ? "\t" : "# ", 299 chan[i] + 'a', roloc[mem[chan[i]].color]); 300 #else 301 fprintf(file, "%s%c(%02d)", i ? "\t" : "# ", 302 chan[i] + 'a', 15); 303 #endif 304 } 305 for (i = 0 ; i < k ; i++) { 306 fprintf(file, "%s%d", i ? "\t" : "\n#:", mem[chan[i]].rate); 307 } 308 for (i = 0 ; i < k ; i++) { 309 fprintf(file, "%s%d", i ? "\t" : "\n#=", mem[chan[i]].volts); 310 } 311 fprintf(file, "\n"); 312 for (j = 0 ; j < l ; j++) { 313 for (i = 0 ; i < k ; i++) { 314 fprintf(file, "%s%d", i ? "\t" : "", mem[chan[i]].data[j]); 315 } 316 fprintf(file, "\n"); 317 } 318 } 319 fclose(file); 320 sprintf(error, "wrote %s", filename); 321 message(error); 322 } 323 324 /* Backwards compatibility with older file format that didn't 325 * explicitly specify a data source, so we make another pass over the 326 * entire file to try and figure out / guess our source before 327 * continuing to parse the file. Basically, an "-x" suggests 328 * either BitScope or ProbeScope, and a "-z" suggests either 329 * soundcard or ESD. Neither suggests Soundcard and ProbeScope 330 * together. Both suggest no input device at all. 331 */ 332 333 void 334 guess_input_device_pre_1_10(char *filename) 335 { 336 FILE *file; 337 char buff[256]; 338 int xseen=0, zseen=0; 339 340 if ((file = fopen(filename, "r")) == NULL) { 341 sprintf(error, "%s: can't read %s", progname, filename); 342 message(error); 343 return; 344 } 345 346 while (fgets(buff, sizeof(buff), file)) { 347 if (!strncmp("# -x", buff, 4)) xseen=1; 348 else if (!strncmp("# -z", buff, 4)) zseen=1; 349 } 350 351 if (!zseen && xseen) { 352 /* BitScope or ProbeScope */ 353 if (!datasrc_byname("Bitscope") && !datasrc_byname("Probescope")) { 354 fprintf(stderr, "Couldn't find either a Bitscope or Probescope\n"); 355 } 356 } else if (!zseen && !xseen) { 357 if (!datasrc_byname("Probescope") 358 && !datasrc_byname("ESD") 359 && !datasrc_byname("Soundcard")) { 360 fprintf(stderr, "Couldn't find either a Probescope or sound device\n"); 361 } 362 /* ESD/Soundcard/ProbeScope */ 363 } else if (zseen && !xseen) { 364 /* ESD/Soundcard */ 365 if (!datasrc_byname("ESD") && !datasrc_byname("Soundcard")) { 366 fprintf(stderr, "Couldn't find a sound device\n"); 367 } 368 } else { 369 /* None - don't have a NULL input device (maybe we should) */ 370 } 371 } 372 373 /* read scope settings and memory buffers from given filename */ 374 void 375 readfile(char *filename) 376 { 377 FILE *file; 378 char c, *p, *q, buff[256]; 379 int version[2]; 380 int i = 0, j = 0, k, valid = 0, chan[26] = 381 {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 382 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; 383 384 if ((file = fopen(filename, "r")) == NULL) { 385 sprintf(error, "%s: can't read %s", progname, filename); 386 message(error); 387 return; 388 } 389 init_scope(); /* reset everything */ 390 init_channels(); 391 init_math(); 392 while (fgets(buff, 256, file)) { 393 if (buff[0] == '#') { 394 if (sscanf(buff, "# %*s version %d.%d", &version[0], &version[1]) == 2) { 395 if ((version[0] <= 1) && (version[1] < 10)) { 396 guess_input_device_pre_1_10(filename); 397 backwards_compat_1_10 = 1; 398 } 399 if (version[0] == 1) { 400 backwards_compat_2_0 = 1; 401 } 402 valid = 1; 403 } else if (!strncmp("# -", buff, 3)) { 404 /* valid = 1; */ 405 handle_opt(buff[3], &buff[5]); 406 } else if (valid && buff[3] == '(' && buff[6] == ')') { 407 j = 0; 408 q = buff + 2; 409 while (j < 26 && (sscanf(q, "%c(%d) ", &c, &k) == 2)) { 410 if (c >= 'a' && c <= 'z' && k >= 0 && k <= 255) { 411 chan[j++] = c - 'a'; 412 //XXX 'k' is color and it is ignored 413 //mem[c - 'a'].color = color[k]; 414 mem[c - 'a'].frame ++; 415 /* Guess some multiple of two here for our data size. 416 * We'll adjust this up if needed. 417 */ 418 mem[c - 'a'].data = malloc(16 * sizeof(short)); 419 mem[c - 'a'].width = 16; 420 } 421 q += 6; 422 } 423 } else if (!strncmp("#:", buff, 2)) { 424 j = 0; 425 q = buff + 2; 426 while (q && j < 26 && (sscanf(q, "%d ", &k) == 1)) { 427 mem[chan[j++]].rate = k; 428 q = strchr(++q, '\t'); 429 } 430 } else if (!strncmp("#=", buff, 2)) { 431 j = 0; 432 q = buff + 2; 433 while (q && j < 26 && (sscanf(q, "%d ", &k) == 1)) { 434 mem[chan[j++]].volts = k; 435 q = strchr(++q, '\t'); 436 } 437 } 438 } else if (valid && 439 ((buff[0] >= '0' && buff[0] <= '9') || buff[0] == '-')) { 440 441 /* This is a line of memory data. Integer values, separated by 442 * tabs, and we already read into the chan[] array the mapping 443 * between the tab-separated fields and memory channels. Take 444 * care to dynamically resize the memory channel data arrays. 445 */ 446 447 j = 0; /* Field number (starts at 0) */ 448 p = buff; /* Pointer to tab that starts the field (or buff) */ 449 450 while (j < 26 && (!j || ((p = strchr(p, '\t')) != NULL))) { 451 if (chan[j] > -1) { 452 if (mem[chan[j]].width <= i) { 453 while (mem[chan[j]].width <= i) { 454 mem[chan[j]].width *= 2; 455 } 456 mem[chan[j]].data = realloc(mem[chan[j]].data, 457 mem[chan[j]].width * sizeof(short)); 458 } 459 mem[chan[j]].data[i] = strtol(p, NULL, 0); 460 mem[chan[j]].num = i + 1; 461 462 p ++; 463 j ++; 464 } 465 } 466 i++; 467 } 468 } 469 fclose(file); 470 471 /* If we read any memory channels, set their widths to be exactly 472 * the number of data values in them. We do this so we get an 473 * accurate display indication of the number of samples. 474 */ 475 476 for (j=0; j<26; j++) { 477 if (chan[j] != -1) { 478 mem[chan[j]].width = mem[chan[j]].num; 479 } 480 } 481 482 backwards_compat_1_10 = 0; /* in case we set these during the parse */ 483 backwards_compat_2_0 = 0; 484 if (valid) { 485 clear(); /* XXX not sure if we need this */ 486 sprintf(error, "loaded %s", filename); 487 message(error); 488 } else { 489 sprintf(error, "invalid format: %s", filename); 490 message(error); 491 } 492 } 493