BOROSOFTWAREAll work →
scantool/scantool_cli.c 474 lines
1/*
2 * freediag - Vehicle Diagnostic Utility
3 *
4 *
5 * Copyright (C) 2001 Richard Almeida & Ibex Ltd ([email protected])
6 *
7 * Copyright (C) 2015 Tomasz Kaźmierczak ([email protected])
8 * - added command completion
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 *
24 *************************************************************************
25 *
26 *
27 * Mostly ODBII Compliant Scan Tool (as defined in SAE J1978)
28 *
29 * CLI routines
30 *
31 */
32
33
34#include <time.h>
35#include <ctype.h>
36#include <assert.h>
37#include <stdlib.h>
38#include <string.h>
39
40#include "diag.h"
41#include "diag_os.h"
42
43#include "libcli.h"
44#include "scantool_cli.h"
45#include "scantool_diag.h"
46
47const char *progname;
48const char projname[]=PROJECT_NAME;
49
50int diag_cli_debug; //debug level
51
52FILE *global_logfp; /* Monitor log output file pointer */
53unsigned long global_log_tstart; /* timestamp datum (in ms) of beginning of log */
54#define LOG_FORMAT "FREEDIAG log format 0.2"
55
56
57//ugly, global data. Could be struct-ed together eventually
58struct diag_l2_conn *global_l2_conn;
59struct diag_l3_conn *global_l3_conn;
60enum globstate global_state = STATE_IDLE;
61struct diag_l0_device *global_dl0d;
62
63/* ROOT main menu */
64
65/* Main menu commands */
66
67static enum cli_retval cmd_help(int argc, char **argv);
68static enum cli_retval cmd_log(int argc, char **argv);
69static enum cli_retval cmd_stoplog(int argc, char **argv);
70
71UNUSED(static enum cli_retval cmd_play(int argc, char **argv));
72
73static enum cli_retval cmd_date(int argc, char **argv);
74static enum cli_retval cmd_rem(int argc, char **argv);
75
76
77/* this table is appended to the "extra" cmdtable to construct the whole root cmd table */
78static const struct cmd_tbl_entry basic_cmd_table[] = {
79 { "log", "log <filename>", "Log monitor data to <filename>",
80 cmd_log, CLI_CMD_FILEARG, NULL},
81 { "stoplog", "stoplog", "Stop logging", cmd_stoplog, 0, NULL},
82
83 { "play", "play filename", "Play back data from <filename>",
84 cmd_play, CLI_CMD_HIDDEN | CLI_CMD_FILEARG, NULL},
85
86 { "set", "set <parameter value>",
87 "Sets/displays parameters, \"set help\" for more info", NULL,
88 0, set_cmd_table},
89
90 { "test", "test <command [params]>",
91 "Perform various tests, \"test help\" for more info", NULL,
92 0, test_cmd_table},
93
94 { "diag", "diag <command [params]>",
95 "Extended diagnostic functions, \"diag help\" for more info", NULL,
96 0, diag_cmd_table},
97
98 { "vw", "vw <command [params]",
99 "VW diagnostic protocol functions, \"vw help\" for more info", NULL,
100 0, vag_cmd_table},
101
102 { "850", "850 <command [params]>",
103 "'96-'98 Volvo 850/S70/V70/etc functions, \"850 help\" for more info", NULL,
104 0, v850_cmd_table},
105
106 { "dyno", "dyno <command [params]",
107 "Dyno functions, \"dyno help\" for more info", NULL,
108 0, dyno_cmd_table},
109
110 { "debug", "debug [parameter = debug]",
111 "Sets/displays debug data and flags, \"debug help\" for available commands", NULL,
112 0, debug_cmd_table},
113
114 { "date", "date", "Prints date & time", cmd_date, CLI_CMD_HIDDEN, NULL},
115 { "#", "#", "Does nothing", cmd_rem, CLI_CMD_HIDDEN, NULL},
116 { "source", "source <file>", "Read commands from a file", cmd_source, CLI_CMD_FILEARG, NULL},
117
118 { "help", "help [command]", "Gives help for a command", cmd_help, 0, NULL },
119 { "?", "? [command]", "Gives help for a command", cmd_help, CLI_CMD_HIDDEN, NULL },
120 CLI_TBL_BUILTINS,
121 CLI_TBL_END
122};
123
124struct cmd_tbl_entry *combined_table = NULL;
125
126static enum cli_retval cmd_help(int argc, char **argv) {
127 assert(combined_table);
128 return cli_help_basic(argc, argv, combined_table);
129}
130
131static enum cli_retval cmd_date(UNUSED(int argc), UNUSED(char **argv)) {
132 struct tm *tm;
133 time_t now;
134 char str[256];
135
136 now = time(NULL);
137 tm = localtime(&now);
138 if (strftime(str, sizeof(str), "%a %b %d %H:%M:%S %Y", tm) == 0) {
139 printf("unable to format timestamp");
140 } else {
141 printf("%s", str);
142 }
143
144
145 return CMD_OK;
146}
147
148
149static enum cli_retval cmd_rem(UNUSED(int argc), UNUSED(char **argv)) {
150 return CMD_OK;
151}
152
153
154void log_timestamp(const char *prefix) {
155 unsigned long tv;
156
157 tv = diag_os_getms() - global_log_tstart;
158
159 fprintf(global_logfp, "%s %04lu.%03lu ", prefix, tv / 1000, tv % 1000);
160}
161
162static void scantool_atexit(void) {
163 cmd_diag_disconnect(0, NULL);
164 return;
165}
166
167static void log_command(int argc, char **argv) {
168 int i;
169
170 if (!global_logfp) {
171 return;
172 }
173
174 log_timestamp(">");
175 for (i = 0; i < argc; i++) {
176 fprintf(global_logfp, " %s", argv[i]);
177 }
178 fprintf(global_logfp, "\n");
179}
180
181static enum cli_retval cmd_log(int argc, char **argv) {
182 char autofilename[20]="";
183 char *file;
184 time_t now;
185 char timestr[256];
186 int i;
187
188 file=autofilename;
189 if (global_logfp != NULL) {
190 printf("Already logging\n");
191 return CMD_FAILED;
192 }
193
194 /* Turn on logging */
195 if (argc > 1) {
196 file = argv[1]; //if a file name was specified, use that
197 } else {
198 //else, generate an auto log file
199 for (i = 0; i < 100; i++) {
200 FILE *testexist;
201 sprintf(autofilename,"log.%02d",i);
202 testexist = fopen(autofilename, "r");
203 if (testexist == NULL) {
204 //file name is free: use that
205 break;
206 }
207 fclose(testexist);
208 }
209 if (i == 100) {
210 printf("Can't create log.%d; remember to clean old auto log files\n",i);
211 return CMD_FAILED;
212 }
213 }
214
215 global_logfp = fopen(file, "a"); //add to end of log or create file
216
217 if (global_logfp == NULL) {
218 printf("Failed to create log file %s\n", file);
219 return CMD_FAILED;
220 }
221
222 now = time(NULL);
223 //reset timestamp reference:
224 global_log_tstart=diag_os_getms();
225
226 fprintf(global_logfp, "%s\n", LOG_FORMAT);
227 log_timestamp("#");
228 if (strftime(timestr, sizeof(timestr), "%a %b %d %H:%M:%S %Y", localtime(&now)) == 0) {
229 fprintf(global_logfp, "unable to format timestamp");
230 } else {
231 fprintf(global_logfp, "logging started at %s", timestr);
232 }
233 printf("Logging to file %s\n", file);
234 return CMD_OK;
235}
236
237
238static enum cli_retval cmd_stoplog(UNUSED(int argc), UNUSED(char **argv)) {
239 /* Turn off logging */
240 if (global_logfp == NULL) {
241 printf("Logging was not on\n");
242 return CMD_FAILED;
243 }
244
245 fclose(global_logfp);
246 global_logfp = NULL;
247
248 return CMD_OK;
249}
250
251static enum cli_retval cmd_play(int argc, char **argv) {
252 FILE *fp;
253 //int linenr;
254
255 /* Turn on logging for monitor mode */
256 if (argc < 2) {
257 return CMD_USAGE;
258 }
259
260 fp = fopen(argv[1], "r");
261
262 if (fp == NULL) {
263 printf("Failed to open log file %s\n", argv[1]);
264 return CMD_FAILED;
265 }
266
267 //linenr = 0; //not used yet ?
268
269 /* Read data file in */
270 /* XXX logging */
271
272 /* Loop and call display routines */
273 while (1) {
274 int ch;
275 printf("Warning : incomplete code");
276 printf("DATE:\t+/- to step, S/E to goto start or end, Q to quit\n");
277 ch = getc(stdin);
278 switch (ch) {
279 case '-':
280 case '+':
281 case 'E':
282 case 'e':
283 case 'S':
284 case 's':
285 case 'Q':
286 case 'q':
287 break;
288 }
289
290 }
291 fclose(fp);
292
293 return CMD_OK;
294}
295
296
297char *find_rcfile(void) {
298
299#ifdef USE_RCFILE
300 char *rchomeinit;
301 char *homedir;
302 homedir = getenv("HOME");
303 FILE *newrcfile;
304
305 if (homedir) {
306 /* we add "/." and "rc" ... 4 characters */
307 if (diag_malloc(&rchomeinit, strlen(homedir) + strlen(projname) + 5)) {
308 return NULL;
309 }
310 strcpy(rchomeinit, homedir);
311 strcat(rchomeinit, "/.");
312 strcat(rchomeinit, projname);
313 strcat(rchomeinit, "rc");
314
315 newrcfile=fopen(rchomeinit,"r");
316 if (newrcfile) {
317 fclose(newrcfile);
318 return rchomeinit;
319 } else {
320 fprintf(stderr, FLFMT "Could not open %s : ignoring", FL, rchomeinit);
321 free(rchomeinit);
322 //try INIFILE next, if enabled
323 }
324 }
325#endif
326
327
328#ifdef USE_INIFILE
329 char *inihomeinit;
330 FILE *inifile;
331 if (diag_malloc(&inihomeinit, strlen(progname) + strlen(".ini") + 1)) {
332 return NULL;
333 }
334
335 strcpy(inihomeinit, progname);
336 strcat(inihomeinit, ".ini");
337
338 inifile=fopen(inihomeinit,"r");
339 if (inifile) {
340 fclose(inifile);
341 return inihomeinit;
342 } else {
343 fprintf(stderr, FLFMT "Could not open %s : ignoring", FL, inihomeinit);
344 free(inihomeinit);
345 }
346#endif
347
348 return NULL;
349}
350
351
352/** temporary enter_cli() wrapper
353 *
354 * combines basic_table with extra_cmdtable before calling enter_cli.
355 *
356 */
357void scantool_cli(const char *prompt, const char *initscript, const struct cmd_tbl_entry *extra_cmdtable) {
358
359 assert(extra_cmdtable);
360
361 global_logfp = NULL;
362
363 // alloc a new table to append extra table
364 int i = 0;
365 const struct cmd_tbl_entry *ctp_iter;
366 struct cmd_tbl_entry *ctp;
367 for (ctp_iter = extra_cmdtable; ctp_iter && ctp_iter->command; ctp_iter++) {
368 i++;
369 }
370 assert(i); //need at least 1 entry...
371
372 diag_calloc(&ctp, i + ARRAY_SIZE(basic_cmd_table));
373 if (!ctp) {
374 return;
375 }
376 memcpy(ctp, extra_cmdtable, i * sizeof(struct cmd_tbl_entry));
377 memcpy(&ctp[i], basic_cmd_table, sizeof(basic_cmd_table));
378 combined_table = ctp;
379
380 struct cli_callbacks cbs = {
381 .cli_logcmd = log_command,
382 .cli_atexit = scantool_atexit,
383 };
384 cli_set_callbacks(&cbs);
385 cli_enter(prompt, initscript, combined_table);
386 free(combined_table);
387 combined_table = NULL;
388 return;
389}
390
391
392/*
393 * ************
394 * Useful, non specific routines
395 * ************
396 */
397
398/*
399 * Decimal/Octal/Hex to integer routine
400 * formats:
401 * [-]0[0-7] : octal
402 * [-]0x[0-9,A-F,a-f] : hex
403 * [-]$[0-9,A-F,a-f] : hex
404 * [-][0-9] : dec
405 * Returns 0 if unable to decode.
406 */
407int htoi(char *buf) {
408 /* Hex text to int */
409 int rv = 0;
410 int base = 10;
411 int sign=0; //1 = positive; 0 =neg
412
413 if (*buf != '-') {
414 //change sign
415 sign=1;
416 } else {
417 buf++;
418 }
419
420 if (*buf == '$') {
421 base = 16;
422 buf++;
423 } else if (*buf == '0') {
424 buf++;
425 if (tolower(*buf) == 'x') {
426 base = 16;
427 buf++;
428 } else {
429 base = 8;
430 }
431 }
432
433 while (*buf) {
434 char upp = toupper(*buf);
435 int val;
436
437 if ((upp >= '0') && (upp <= '9')) {
438 val = ((*buf) - '0');
439 } else if ((upp >= 'A') && (upp <= 'F')) {
440 val = (upp - 'A' + 10);
441 } else {
442 return 0;
443 }
444 if (val >= base) { /* Value too big for this base */
445 return 0;
446 }
447 rv *= base;
448 rv += val;
449
450 buf++;
451 }
452 return sign? rv:-rv;
453}
454
455/*
456 * Wait until ENTER is pressed
457 */
458void wait_enter(const char *message) {
459 printf("%s", message);
460 while (1) {
461 int ch = getc(stdin);
462 if (ch == '\n') {
463 break;
464 }
465 }
466}
467
468/*
469 * Determine whether ENTER has been pressed
470 */
471int pressed_enter() {
472 return diag_os_ipending();
473}
474