From: Uwe Hermann Date: Wed, 24 Feb 2021 19:55:13 +0000 (+0100) Subject: Backport recent changes from mainline. X-Git-Tag: sigrok-cli-0.7.2~2 X-Git-Url: https://sigrok.org/gitaction?a=commitdiff_plain;h=3d1dae3c64a7f0297f7de4d6d9945c2dc88f5c7d;p=sigrok-cli.git Backport recent changes from mainline. This includes all changes from 0bdb08fd3d1b0b410cb1d33b90f531c35008bd4d main: synchronize --get output for MQ with --show output up to dded02f515b59cd7d3bb7ada481df0c6e3ce11ed show: Only use decoder tags if they're available. --- diff --git a/Makefile.am b/Makefile.am index 84f94b4..d5cc780 100644 --- a/Makefile.am +++ b/Makefile.am @@ -30,6 +30,7 @@ sigrok_cli_SOURCES = \ device.c \ session.c \ input.c \ + output.c \ decode.c \ sigrok-cli.h \ parsers.c \ diff --git a/contrib/sigrok-cli_cross.nsi.in b/contrib/sigrok-cli_cross.nsi.in index 196a107..6f60540 100644 --- a/contrib/sigrok-cli_cross.nsi.in +++ b/contrib/sigrok-cli_cross.nsi.in @@ -1,7 +1,7 @@ ## ## This file is part of the sigrok-cli project. ## -## Copyright (C) 2011-2014 Uwe Hermann +## Copyright (C) 2011-2020 Uwe Hermann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -40,7 +40,11 @@ Name "@PACKAGE_NAME@" OutFile "@PACKAGE_NAME@-@SC_PACKAGE_VERSION@-installer.exe" # Where to install the application. -InstallDir "$PROGRAMFILES\sigrok\@PACKAGE_NAME@" +!ifdef PE64 + InstallDir "$PROGRAMFILES64\sigrok\@PACKAGE_NAME@" +!else + InstallDir "$PROGRAMFILES\sigrok\@PACKAGE_NAME@" +!endif # Request admin privileges for Windows Vista and Windows 7. # http://nsis.sourceforge.net/Docs/Chapter4.html @@ -129,6 +133,7 @@ Section "@PACKAGE_NAME@ (required)" Section1 # Python File "${CROSS}/python34.dll" File "${CROSS}/python34.zip" + File "${CROSS}/*.pyd" SetOutPath "$INSTDIR\share" @@ -236,6 +241,7 @@ Section "Uninstall" Delete "$INSTDIR\zadig_xp.exe" Delete "$INSTDIR\python34.dll" Delete "$INSTDIR\python34.zip" + Delete "$INSTDIR\*.pyd" # Delete all decoders and everything else in libsigrokdecode/. # There could be *.pyc files or __pycache__ subdirs and so on. diff --git a/decode.c b/decode.c index 2b95f23..4ddf34a 100644 --- a/decode.c +++ b/decode.c @@ -29,6 +29,8 @@ static GHashTable *pd_meta_visible = NULL; static GHashTable *pd_binary_visible = NULL; static GHashTable *pd_channel_maps = NULL; +uint64_t pd_samplerate = 0; + extern struct srd_session *srd_sess; static int opts_to_gvar(struct srd_decoder *dec, GHashTable *hash, @@ -38,6 +40,7 @@ static int opts_to_gvar(struct srd_decoder *dec, GHashTable *hash, GSList *optl; GVariant *gvar; gint64 val_int; + double val_dbl; int ret; char *val_str, *conv; @@ -53,14 +56,25 @@ static int opts_to_gvar(struct srd_decoder *dec, GHashTable *hash, if (g_variant_is_of_type(o->def, G_VARIANT_TYPE_STRING)) { gvar = g_variant_new_string(val_str); } else if (g_variant_is_of_type(o->def, G_VARIANT_TYPE_INT64)) { + conv = NULL; val_int = strtoll(val_str, &conv, 0); - if (!conv || conv == val_str) { + if (!conv || conv == val_str || *conv) { g_critical("Protocol decoder '%s' option '%s' " "requires a number.", dec->name, o->id); ret = FALSE; break; } gvar = g_variant_new_int64(val_int); + } else if (g_variant_is_of_type(o->def, G_VARIANT_TYPE_DOUBLE)) { + conv = NULL; + val_dbl = strtod(val_str, &conv); + if (!conv || conv == val_str || *conv) { + g_critical("Protocol decoder '%s' option '%s' requires a float number.", + dec->name, o->id); + ret = FALSE; + break; + } + gvar = g_variant_new_double(val_dbl); } else { g_critical("Unsupported type for option '%s' (%s)", o->id, g_variant_get_type_string(o->def)); @@ -125,7 +139,7 @@ static int register_pd(char *opt_pds, char *opt_pd_annotations) pdtokens = g_strsplit(opt_pds, ",", 0); for (pdtok = pdtokens; *pdtok; pdtok++) { - if (!(pd_opthash = parse_generic_arg(*pdtok, TRUE))) { + if (!(pd_opthash = parse_generic_arg(*pdtok, TRUE, NULL))) { g_critical("Invalid protocol decoder option '%s'.", *pdtok); break; } @@ -259,6 +273,11 @@ static void map_pd_inst_channels(void *key, void *value, void *user_data) g_hash_table_iter_init(&iter, channel_map); while (g_hash_table_iter_next(&iter, &channel_id, &channel_target)) { + if (!channel_target) { + g_printerr("cli: Channel name for \"%s\" missing.\n", + (char *)channel_id); + continue; + } ch = find_channel(channel_list, channel_target); if (!ch) { g_printerr("cli: No channel with name \"%s\" found.\n", @@ -298,46 +317,91 @@ int setup_pd_annotations(char *opt_pd_annotations) struct srd_decoder *dec; int ann_class; char **pds, **pdtok, **keyval, **annlist, **ann, **ann_descr; + const char *dec_id; + const char *ann_txt; + const char *ann_id; + const struct srd_decoder_annotation_row *row_desc; + char **ann_diag; /* Set up custom list of PDs and annotations to show. */ pds = g_strsplit(opt_pd_annotations, ",", 0); for (pdtok = pds; *pdtok && **pdtok; pdtok++) { keyval = g_strsplit(*pdtok, "=", 0); - if (!(dec = srd_decoder_get_by_id(keyval[0]))) { - g_critical("Protocol decoder '%s' not found.", keyval[0]); + dec_id = keyval[0]; + if (!(dec = srd_decoder_get_by_id(dec_id))) { + g_critical("Protocol decoder '%s' not found.", dec_id); + g_strfreev(keyval); + g_strfreev(pds); return 1; } if (!dec->annotations) { - g_critical("Protocol decoder '%s' has no annotations.", keyval[0]); + g_critical("Protocol decoder '%s' has no annotations.", dec_id); + g_strfreev(keyval); + g_strfreev(pds); return 1; } - if (g_strv_length(keyval) == 2 && keyval[1][0] != '\0') { - annlist = g_strsplit(keyval[1], ":", 0); + ann_txt = (g_strv_length(keyval) == 2) ? keyval[1] : NULL; + if (ann_txt && *ann_txt) { + annlist = g_strsplit(ann_txt, ":", 0); for (ann = annlist; *ann && **ann; ann++) { + ann_id = *ann; + g_debug("cli: Lookup decoder %s annotation %s.", dec_id, ann_id); + /* Lookup annotation class. */ ann_class = 0; for (l = dec->annotations; l; l = l->next, ann_class++) { ann_descr = l->data; - if (!canon_cmp(ann_descr[0], *ann)) + if (!canon_cmp(ann_descr[0], ann_id)) /* Found it. */ break; } - if (!l) { - g_critical("Annotation '%s' not found " - "for protocol decoder '%s'.", *ann, keyval[0]); - return 1; + if (l) { + l_ann = g_hash_table_lookup(pd_ann_visible, dec_id); + l_ann = g_slist_append(l_ann, GINT_TO_POINTER(ann_class)); + g_hash_table_replace(pd_ann_visible, g_strdup(dec_id), l_ann); + g_debug("cli: Showing protocol decoder %s annotation " + "class %d (%s).", dec_id, ann_class, ann_descr[0]); + continue; } - l_ann = g_hash_table_lookup(pd_ann_visible, keyval[0]); - l_ann = g_slist_append(l_ann, GINT_TO_POINTER(ann_class)); - g_hash_table_replace(pd_ann_visible, g_strdup(keyval[0]), l_ann); - g_debug("cli: Showing protocol decoder %s annotation " - "class %d (%s).", keyval[0], ann_class, ann_descr[0]); + /* Lookup annotation row. */ + for (l = dec->annotation_rows; l; l = l->next) { + row_desc = l->data; + if (!canon_cmp(row_desc->id, ann_id)) + break; + } + if (l) { + g_debug("cli: Showing decoder %s annotation row %s (%s).", + dec_id, row_desc->id, row_desc->desc); + l_ann = g_hash_table_lookup(pd_ann_visible, dec_id); + for (l = row_desc->ann_classes; l; l = l->next) { + /* + * This could just be: + * l_ann = g_slist_append(l_ann, l->data); + * But we are explicit for readability + * and to access details for diagnostics. + */ + ann_class = GPOINTER_TO_INT(l->data); + l_ann = g_slist_append(l_ann, GINT_TO_POINTER(ann_class)); + ann_diag = g_slist_nth_data(dec->annotations, ann_class); + g_debug("cli: Adding class %d/%s from row %s.", + ann_class, ann_diag[0], row_desc->id); + } + g_hash_table_replace(pd_ann_visible, g_strdup(dec_id), l_ann); + continue; + } + /* No match found. */ + g_critical("Annotation '%s' not found " + "for protocol decoder '%s'.", ann_id, dec_id); + g_strfreev(keyval); + g_strfreev(pds); + return 1; } } else { /* No class specified: show all of them. */ - g_hash_table_insert(pd_ann_visible, g_strdup(keyval[0]), - g_slist_append(NULL, GINT_TO_POINTER(-1))); + ann_class = -1; + l_ann = g_slist_append(NULL, GINT_TO_POINTER(ann_class)); + g_hash_table_insert(pd_ann_visible, g_strdup(dec_id), l_ann); g_debug("cli: Showing all annotation classes for protocol " - "decoder %s.", keyval[0]); + "decoder %s.", dec_id); } g_strfreev(keyval); } @@ -416,6 +480,147 @@ int setup_pd_binary(char *opt_pd_binary) return 0; } +/* + * Balance JSON object and array parentheses, and separate array items. + * Somewhat convoluted API to re-use the routine for individual items as + * well as the surrounding array and object, including deferred start of + * the output and late flush (and to keep the state strictly local to the + * routine). Some additional complexity due to JSON's inability to handle + * a trailing comma at the last item. Code phrased such that text literals + * are kept in their order of appearance in the output (where possible). + */ +static void jsontrace_open_close(gboolean is_close_req, + gboolean open_item, gboolean close_item) +{ + static gboolean is_file_open; + static gboolean is_item_open; + + if (is_close_req && is_item_open) + close_item = TRUE; + + /* Automatic file header, and array item separation. */ + if (open_item) { + if (!is_file_open) + printf("{\"traceEvents\": [\n"); + if (is_item_open) { + printf("}"); + is_item_open = FALSE; + } + if (is_file_open) { + printf(",\n"); + } + is_file_open = TRUE; + } + + /* Array item open/append/close. */ + if (open_item) { + printf("{"); + is_item_open = TRUE; + } + if (!open_item && !close_item && !is_close_req) { + printf(", "); + is_item_open = TRUE; + } + if (close_item) { + printf("}"); + is_item_open = FALSE; + } + + /* Automatic file footer on shutdown. */ + if (is_close_req && is_file_open) { + printf("\n"); + printf("]}\n"); + } + if (is_close_req) + is_file_open = FALSE; + + /* Flush at end of lines, or end of file. */ + if (close_item || is_close_req) + fflush(stdout); +} + +/* Convert uint64 sample number to double timestamp in microseconds. */ +static double jsontrace_ts_usec(uint64_t snum) +{ + double ts_usec; + + ts_usec = snum; + ts_usec *= 1e6; + ts_usec /= pd_samplerate; + return ts_usec; +} + +/* Emit two Google Trace Events (JSON) for one PD annotation (ss, es). */ +static void jsontrace_annotation(struct srd_decoder *dec, + struct srd_proto_data_annotation *pda, struct srd_proto_data *pdata) +{ + char *row_text; + GSList *lrow, *lcls; + struct srd_decoder_annotation_row *row; + int cls; + char **ann_descr; + + /* + * Search for an annotation row for this index, or use the + * annotation's descriptor. + */ + row_text = NULL; + if (dec->annotation_rows) { + for (lrow = dec->annotation_rows; lrow; lrow = lrow->next) { + row = lrow->data; + for (lcls = row->ann_classes; lcls; lcls = lcls->next) { + cls = GPOINTER_TO_INT(lcls->data); + if (cls == pda->ann_class) { + row_text = row->desc; + break; + } + } + if (row_text) + break; + } + } + if (!row_text) { + ann_descr = g_slist_nth_data(dec->annotations, pda->ann_class); + row_text = ann_descr[0]; + } + + /* + * Emit two Google Trace Events for the start and end times. + * Set the 'pid' (process ID) to the decoder name to group a + * decoder's annotations. Set the 'tid' (thread ID) to the + * annotation row's description. The 'ts' (timestamp) is in + * microseconds. Set 'name' to the longest annotation text. + * + * BEWARE of the unfortunate JSON format limitation, which + * clutters data output calls with format helper calls. + * TODO Want to introduce a cJSON dependency to delegate the + * construction of output text? + */ + jsontrace_open_close(FALSE, TRUE, FALSE); + printf("\"%s\": \"%s\"", "ph", "B"); + jsontrace_open_close(FALSE, FALSE, FALSE); + printf("\"%s\": %lf", "ts", jsontrace_ts_usec(pdata->start_sample)); + jsontrace_open_close(FALSE, FALSE, FALSE); + printf("\"%s\": \"%s\"", "pid", pdata->pdo->proto_id); + jsontrace_open_close(FALSE, FALSE, FALSE); + printf("\"%s\": \"%s\"", "tid", row_text); + jsontrace_open_close(FALSE, FALSE, FALSE); + printf("\"%s\": \"%s\"", "name", pda->ann_text[0]); + + jsontrace_open_close(FALSE, TRUE, FALSE); + printf("\"%s\": \"%s\"", "ph", "E"); + jsontrace_open_close(FALSE, FALSE, FALSE); + printf("\"%s\": %lf", "ts", jsontrace_ts_usec(pdata->end_sample)); + jsontrace_open_close(FALSE, FALSE, FALSE); + printf("\"%s\": \"%s\"", "pid", pdata->pdo->proto_id); + jsontrace_open_close(FALSE, FALSE, FALSE); + printf("\"%s\": \"%s\"", "tid", row_text); + jsontrace_open_close(FALSE, FALSE, FALSE); + printf("\"%s\": \"%s\"", "name", pda->ann_text[0]); + + jsontrace_open_close(FALSE, FALSE, TRUE); +} + void show_pd_annotations(struct srd_proto_data *pdata, void *cb_data) { struct srd_decoder *dec; @@ -450,6 +655,12 @@ void show_pd_annotations(struct srd_proto_data *pdata, void *cb_data) if (!show_ann) return; + /* Google Trace Events are rather special. Use a separate code path. */ + if (opt_pd_jsontrace) { + jsontrace_annotation(dec, pda, pdata); + return; + } + /* * Determine which fields of the annotation to display. Inspect * user specified options as well as the verbosity of the log level: @@ -537,4 +748,16 @@ void show_pd_binary(struct srd_proto_data *pdata, void *cb_data) fwrite(pdb->data, pdb->size, 1, stdout); fflush(stdout); } + +void show_pd_prepare(void) +{ + if (opt_pd_jsontrace) + jsontrace_open_close(TRUE, FALSE, FALSE); +} + +void show_pd_close(void) +{ + if (opt_pd_jsontrace) + jsontrace_open_close(TRUE, FALSE, FALSE); +} #endif diff --git a/device.c b/device.c index a159ff0..7da7444 100644 --- a/device.c +++ b/device.c @@ -35,12 +35,16 @@ GSList *device_scan(void) int i; if (opt_drv) { + /* Caller specified driver. Use it. Only this one. */ if (!parse_driver(opt_drv, &driver, &drvopts)) return NULL; devices = sr_driver_scan(driver, drvopts); g_slist_free_full(drvopts, (GDestroyNotify)free_drvopts); + } else if (opt_dont_scan) { + /* No -d choice, and -D "don't scan" requested. Do nothing. */ + devices = NULL; } else { - /* No driver specified, let them all scan on their own. */ + /* No driver specified. Scan all available drivers. */ devices = NULL; drivers = sr_driver_list(sr_ctx); for (i = 0; drivers[i]; i++) { @@ -59,16 +63,37 @@ GSList *device_scan(void) return devices; } -struct sr_channel_group *select_channel_group(struct sr_dev_inst *sdi) +/** + * Lookup a channel group from its name. + * + * Uses the caller specified channel group name, or a previously stored + * option value as a fallback. Returns a reference to the channel group + * when the lookup succeeded, or #NULL after lookup failure, as well as + * #NULL for the global channel group (the device). + * + * Accepts either #NULL pointer, or an empty string, or the "global" + * literal to address the global channel group (the device). Emits an + * error message when the lookup failed while a name was specified. + * + * @param[in] sdi Device instance. + * @param[in] cg_name Caller provided channel group name. + * + * @returns The channel group, or #NULL for failed lookup. + */ +struct sr_channel_group *lookup_channel_group(struct sr_dev_inst *sdi, + const char *cg_name) { struct sr_channel_group *cg; GSList *l, *channel_groups; - if (!opt_channel_group) + if (!cg_name) + cg_name = opt_channel_group; + if (cg_name && g_ascii_strcasecmp(cg_name, "global") == 0) + cg_name = NULL; + if (!cg_name || !*cg_name) return NULL; channel_groups = sr_dev_inst_channel_groups_get(sdi); - if (!channel_groups) { g_critical("This device does not have any channel groups."); return NULL; @@ -76,11 +101,11 @@ struct sr_channel_group *select_channel_group(struct sr_dev_inst *sdi) for (l = channel_groups; l; l = l->next) { cg = l->data; - if (!g_ascii_strcasecmp(opt_channel_group, cg->name)) { - return cg; - } + if (g_ascii_strcasecmp(cg_name, cg->name) != 0) + continue; + return cg; } - g_critical("Invalid channel group '%s'", opt_channel_group); + g_critical("Invalid channel group '%s'", cg_name); return NULL; } diff --git a/doc/sigrok-cli.1 b/doc/sigrok-cli.1 index c87d383..4258009 100644 --- a/doc/sigrok-cli.1 +++ b/doc/sigrok-cli.1 @@ -1,4 +1,4 @@ -.TH SIGROK\-CLI 1 "October 22, 2018" +.TH SIGROK\-CLI 1 "March 28, 2019" .SH "NAME" sigrok\-cli \- Command-line client for the sigrok software .SH "SYNOPSIS" @@ -28,9 +28,17 @@ version and the versions of libraries used. Show information about supported hardware drivers, input file formats, output file formats, and protocol decoders. .TP +.B "\-\-list\-supported\-wiki" +Show information about supported protocol decoders in MediaWiki syntax. +This is generally only used by developers to easily update the list of +supported protocol decoders in the sigrok wiki. +.TP \fB\-d, \-\-driver\fP -A driver must always be selected (unless doing a global scan). Use the -.BR "\-L " ( "\-\-list-supported" ")" +Unless doing a global scan, users typically select one of the available +drivers. This can speedup program start, and can avoid false matches for +ambiguous configurations. Selecting a driver also allows to pass more +driver specific options. Use the +.BR "\-L " ( "\-\-list\-supported" ")" option to get a list of available drivers. .sp Drivers can take options, in the form \fBkey=value\fP @@ -43,8 +51,10 @@ Openbench Logic Sniffer: .RB " $ " "sigrok\-cli \-\-driver=ols:conn=/dev/ttyACM0" " [...]" .sp Some USB devices don't use a unique VendorID/ProductID combination, and thus -need that specified as well. This also uses the \fBconn\fP option, using -either \fBVendorID.ProductID\fP or \fBbus.address\fP: +need that specified as well. Notice that colons are used to separate the +driver name from the \fBconn\fP option, thus colons cannot be used within the +\fBconn\fP option's argument. To select a specific USB device, use either +\fBVendorID.ProductID\fP or \fBbus.address\fP: .sp USB \fBVendorID.ProductID\fP example: .sp @@ -54,9 +64,25 @@ USB \fBbus.address\fP example: .sp .RB " $ " "sigrok\-cli \-\-driver=uni\-t\-ut61e:conn=4.6" " [...]" .TP +.B "\-D, \-\-dont\-scan" +Do not automatically scan for device drivers in the absence of a +.BR "\-d " ( "\-\-driver" ) +specification. +.TP .BR "\-c, \-\-config " A colon-separated list of device options, where each option takes the form .BR key=value . +Multiple occurances of the +.B \-\-config +option are supported. +The first item in the list of options can take the form +.B channel_group= +which would override the +.B \-\-channel\-group +specification for this list of options. Other option lists in other +.B \-\-config +occurances are not affected by this list's channel group name. +.sp For example, to set the samplerate to 1MHz on a device supported by the fx2lafw driver, you might specify .sp @@ -71,6 +97,15 @@ The following are all equivalent: .RB " $ " "sigrok\-cli \-d fx2lafw \-\-config samplerate=1m" " [...]" .sp .RB " $ " "sigrok\-cli \-d fx2lafw \-\-config \(dqsamplerate=1 MHz\(dq" " [...]" +.sp +These examples specify options within a channel group. +The first two are equivalent. +.sp +.RB " $ " "sigrok\-cli \-d demo \-\-channel\-group Logic \-\-config pattern=random [...]" +.sp +.RB " $ " "sigrok\-cli \-d demo \-\-config channel_group=Logic:pattern=random [...]" +.sp +.RB " $ " "sigrok\-cli \-d demo \-\-config samplerate=1m \-\-config channel_group=Logic:pattern=random [...]" .TP .BR "\-i, \-\-input\-file " Load input from a file instead of a hardware device. You can specify @@ -196,6 +231,17 @@ Examples: .RB " $ " "sigrok\-cli \-g CH1" " [...]" .sp .RB " $ " "sigrok\-cli \-d demo \-g Logic \-c pattern=graycode" " [...]" +.sp +Channel group specifications in +.B \-\-get +or +.B \-\-config +options take precedence over channel group names in +.B \-\-channel\-group +so that a single +.B sigrok\-cli +invocation can support the query or manipulation of multiple device options +which reside in different channel groups. .TP .BR "\-t, \-\-triggers " A comma-separated list of triggers to use, of the form @@ -300,14 +346,14 @@ Example: .sp .TP .BR "\-A, \-\-protocol\-decoder\-annotations " -By default, only the stack's topmost protocol decoder's annotation output is -shown. With this option another decoder's annotation can be selected for -display, by specifying its ID: +By default, all annotation output of all protocol decoders is +shown. With this option a specific decoder's annotations can be selected for +display, by specifying the decoder ID: .sp $ .B "sigrok\-cli \-i \-P i2c,i2cfilter,edid \-A i2c" .sp -If a protocol decoder has multiple annotations, you can also specify +If a protocol decoder has multiple annotation classes, you can also specify which one of them to show by specifying its short description like this: .sp $ @@ -315,15 +361,24 @@ which one of them to show by specifying its short description like this: .br .B " \-A i2c=data\-read" .sp -Select multiple annotations by separating them with a colon: +Select multiple annotation classes by separating them with a colon: .sp $ .B "sigrok\-cli \-i \-P i2c,i2cfilter,edid" .br .B " \-A i2c=data\-read:data\-write" .sp -You can also select multiple protocol decoders, with an optional selected -annotation each, by separating them with commas: +Annotation row names will resolve to their respective list of classes. +Row and class names can be used in combination. When names are ambiguous +then class names take precedence. +.sp + $ +.B "sigrok\-cli \-i \-P i2c" +.br +.B " \-A i2c=addr\-data:warnings" +.sp +You can also select multiple protocol decoders, with optionally selected +annotation classes each, by separating them with commas: .sp $ .B "sigrok\-cli \-i \-P i2c,i2cfilter,edid" @@ -410,6 +465,13 @@ This also works for protocol decoders, input modules and output modules: .B "sigrok\-cli \-\-input\-format csv \-\-show $ .B "sigrok\-cli \-\-output\-format bits \-\-show +.sp +This also works for input files, including optional input format specifications: +.sp + $ +.B "sigrok\-cli \-\-input\-file \-\-show + $ +.B "sigrok\-cli \-\-input\-file \-\-input\-format vcd \-\-show .TP .B "\-\-scan" Scan for devices that can be detected automatically. @@ -472,6 +534,20 @@ Sample continuously until stopped. Not all devices support this. Get the value of .B from the specified device and print it. +Multiple variable names can be specified and get separated by colon. +The list of variable names optionally can be preceeded by +.B "channel_group=" +which would override the +.B \-\-channel\-group +specification. +Multiple +.B \-\-get +occurances are supported in a single +.B sigrok\-cli +invocation. +.sp + $ +.B sigrok\-cli \-d demo \-\-get samplerate:averaging \-\-get channel_group=Logic:pattern .TP .BR "\-\-set" Set one or more variables specified with the \fB\-\-config\fP option, without diff --git a/input.c b/input.c index 8eeb94c..6c14972 100644 --- a/input.c +++ b/input.c @@ -30,7 +30,7 @@ #define CHUNK_SIZE (4 * 1024 * 1024) -static void load_input_file_module(void) +static void load_input_file_module(struct df_arg_desc *df_arg) { struct sr_session *session; const struct sr_input *in; @@ -51,7 +51,7 @@ static void load_input_file_module(void) mod_id = NULL; mod_args = NULL; if (opt_input_format) { - mod_args = parse_generic_arg(opt_input_format, TRUE); + mod_args = parse_generic_arg(opt_input_format, TRUE, NULL); mod_id = g_hash_table_lookup(mod_args, "sigrok_key"); } @@ -65,9 +65,11 @@ static void load_input_file_module(void) g_hash_table_remove(mod_args, "sigrok_key"); if ((options = sr_input_options_get(imod))) { mod_opts = generic_arg_to_opt(options, mod_args); + (void)warn_unknown_keys(options, mod_args, NULL); sr_output_options_free(options); - } else + } else { mod_opts = NULL; + } if (!(in = sr_input_new(imod, mod_opts))) g_critical("Error: failed to initialize input module."); if (mod_opts) @@ -111,7 +113,8 @@ static void load_input_file_module(void) g_critical("Error: no input module found for this file."); } sr_session_new(sr_ctx, &session); - sr_session_datafeed_callback_add(session, &datafeed_in, session); + df_arg->session = session; + sr_session_datafeed_callback_add(session, datafeed_in, df_arg); got_sdi = FALSE; while (TRUE) { @@ -143,21 +146,26 @@ static void load_input_file_module(void) sr_input_free(in); g_string_free(buf, TRUE); + df_arg->session = NULL; sr_session_destroy(session); } -void load_input_file(void) +void load_input_file(gboolean do_props) { + struct df_arg_desc df_arg; struct sr_session *session; struct sr_dev_inst *sdi; GSList *devices; GMainLoop *main_loop; int ret; + memset(&df_arg, 0, sizeof(df_arg)); + df_arg.do_props = do_props; + if (!strcmp(opt_input_file, "-")) { /* Input from stdin is never a session file. */ - load_input_file_module(); + load_input_file_module(&df_arg); } else { if ((ret = sr_session_load(sr_ctx, opt_input_file, &session)) == SR_OK) { @@ -177,7 +185,9 @@ void load_input_file(void) } main_loop = g_main_loop_new(NULL, FALSE); - sr_session_datafeed_callback_add(session, datafeed_in, session); + df_arg.session = session; + sr_session_datafeed_callback_add(session, + datafeed_in, &df_arg); sr_session_stopped_callback_set(session, (sr_session_stopped_callback)g_main_loop_quit, main_loop); @@ -185,13 +195,14 @@ void load_input_file(void) g_main_loop_run(main_loop); g_main_loop_unref(main_loop); + df_arg.session = NULL; sr_session_destroy(session); } else if (ret != SR_ERR) { /* It's a session file, but it didn't work out somehow. */ g_critical("Failed to load session file."); } else { /* Fall back on input modules. */ - load_input_file_module(); + load_input_file_module(&df_arg); } } } diff --git a/main.c b/main.c index 689c2d5..b128c88 100644 --- a/main.c +++ b/main.c @@ -106,67 +106,111 @@ int maybe_config_list(struct sr_dev_driver *driver, return SR_ERR_NA; } -static void get_option(void) +static void get_option(struct sr_dev_inst *sdi, + struct sr_channel_group *cg, const char *opt) { - struct sr_dev_inst *sdi; - struct sr_channel_group *cg; + struct sr_dev_driver *driver; const struct sr_key_info *ci; - GSList *devices; - GVariant *gvar; - GHashTable *devargs; int ret; + GVariant *gvar; + const struct sr_key_info *srci, *srmqi, *srmqfi; + uint32_t mq; + uint64_t mask, mqflags; + unsigned int j; char *s; - struct sr_dev_driver *driver; - if (!(devices = device_scan())) { + driver = sr_dev_inst_driver_get(sdi); + + ci = sr_key_info_name_get(SR_KEY_CONFIG, opt); + if (!ci) + g_critical("Unknown option '%s'", opt); + + ret = maybe_config_get(driver, sdi, cg, ci->key, &gvar); + if (ret != SR_OK) + g_critical("Failed to get '%s': %s", opt, sr_strerror(ret)); + + srci = sr_key_info_get(SR_KEY_CONFIG, ci->key); + if (srci && srci->datatype == SR_T_MQ) { + g_variant_get(gvar, "(ut)", &mq, &mqflags); + if ((srmqi = sr_key_info_get(SR_KEY_MQ, mq))) + printf("%s", srmqi->id); + else + printf("%d", mq); + for (j = 0, mask = 1; j < 32; j++, mask <<= 1) { + if (!(mqflags & mask)) + continue; + if ((srmqfi = sr_key_info_get(SR_KEY_MQFLAGS, mqflags & mask))) + printf("/%s", srmqfi->id); + else + printf("/%" PRIu64, mqflags & mask); + } + printf("\n"); + } else { + s = g_variant_print(gvar, FALSE); + printf("%s\n", s); + g_free(s); + } + + g_variant_unref(gvar); +} + +static void get_options(void) +{ + GSList *devices; + struct sr_dev_inst *sdi; + size_t get_idx; + char *get_text, *cg_name; + GHashTable *args; + GHashTableIter iter; + gpointer key, value; + struct sr_channel_group *cg; + + /* Lookup and open the device. */ + devices = device_scan(); + if (!devices) { g_critical("No devices found."); return; } sdi = devices->data; g_slist_free(devices); - driver = sr_dev_inst_driver_get(sdi); - if (sr_dev_open(sdi) != SR_OK) { g_critical("Failed to open device."); return; } - cg = select_channel_group(sdi); - if (!(ci = sr_key_info_name_get(SR_KEY_CONFIG, opt_get))) - g_critical("Unknown option '%s'", opt_get); - - if ((devargs = parse_generic_arg(opt_config, FALSE))) - set_dev_options(sdi, devargs); - else - devargs = NULL; - - if ((ret = maybe_config_get(driver, sdi, cg, ci->key, &gvar)) != SR_OK) - g_critical("Failed to get '%s': %s", opt_get, sr_strerror(ret)); - s = g_variant_print(gvar, FALSE); - printf("%s\n", s); - g_free(s); + /* Set configuration values when -c was specified. */ + set_dev_options_array(sdi, opt_configs); + + /* Get configuration values which were specified by --get. */ + for (get_idx = 0; (get_text = opt_gets[get_idx]); get_idx++) { + args = parse_generic_arg(get_text, FALSE, "channel_group"); + if (!args) + continue; + cg_name = g_hash_table_lookup(args, "sigrok_key"); + cg = lookup_channel_group(sdi, cg_name); + g_hash_table_iter_init(&iter, args); + while (g_hash_table_iter_next(&iter, &key, &value)) { + if (g_ascii_strcasecmp(key, "sigrok_key") == 0) + continue; + get_option(sdi, cg, key); + } + } - g_variant_unref(gvar); + /* Close the device. */ sr_dev_close(sdi); - if (devargs) - g_hash_table_destroy(devargs); } static void set_options(void) { struct sr_dev_inst *sdi; GSList *devices; - GHashTable *devargs; - if (!opt_config) { + if (!opt_configs) { g_critical("No setting specified."); return; } - if (!(devargs = parse_generic_arg(opt_config, FALSE))) - return; - if (!(devices = device_scan())) { g_critical("No devices found."); return; @@ -179,11 +223,9 @@ static void set_options(void) return; } - set_dev_options(sdi, devargs); + set_dev_options_array(sdi, opt_configs); sr_dev_close(sdi); - g_hash_table_destroy(devargs); - } int main(int argc, char **argv) @@ -202,6 +244,11 @@ int main(int argc, char **argv) goto done; #ifdef HAVE_SRD + if (opt_pd_binary && !opt_pds) { + g_critical("Option -B will not take effect in the absence of -P."); + goto done; + } + /* Set the loglevel (amount of messages to output) for libsigrokdecode. */ if (srd_log_loglevel_set(opt_loglevel) != SRD_OK) goto done; @@ -220,6 +267,8 @@ int main(int argc, char **argv) if (opt_pd_binary) { if (setup_pd_binary(opt_pd_binary) != 0) goto done; + if (setup_binary_stdout() != 0) + goto done; if (srd_pd_output_callback_add(srd_sess, SRD_OUTPUT_BINARY, show_pd_binary, NULL) != SRD_OK) goto done; @@ -237,6 +286,7 @@ int main(int argc, char **argv) show_pd_annotations, NULL) != SRD_OK) goto done; } + show_pd_prepare(); } #endif @@ -244,6 +294,10 @@ int main(int argc, char **argv) show_version(); else if (opt_list_supported) show_supported(); + else if (opt_list_supported_wiki) + show_supported_wiki(); + else if (opt_input_file && opt_show) + load_input_file(TRUE); else if (opt_input_format && opt_show) show_input(); else if (opt_output_format && opt_show) @@ -259,17 +313,21 @@ int main(int argc, char **argv) else if (opt_show) show_dev_detail(); else if (opt_input_file) - load_input_file(); - else if (opt_get) - get_option(); + load_input_file(FALSE); + else if (opt_gets) + get_options(); else if (opt_set) set_options(); else if (opt_samples || opt_time || opt_frames || opt_continuous) run_session(); + else if (opt_list_serial) + show_serial_ports(); else show_help(); #ifdef HAVE_SRD + if (opt_pds) + show_pd_close(); if (opt_pds) srd_exit(); #endif diff --git a/options.c b/options.c index b9e1494..2573bd4 100644 --- a/options.c +++ b/options.c @@ -23,13 +23,15 @@ gboolean opt_version = FALSE; gboolean opt_list_supported = FALSE; +gboolean opt_list_supported_wiki = FALSE; gint opt_loglevel = SR_LOG_WARN; /* Show errors+warnings by default. */ gboolean opt_scan_devs = FALSE; +gboolean opt_dont_scan = FALSE; gboolean opt_wait_trigger = FALSE; gchar *opt_input_file = NULL; gchar *opt_output_file = NULL; gchar *opt_drv = NULL; -gchar *opt_config = NULL; +gchar **opt_configs = NULL; gchar *opt_channels = NULL; gchar *opt_channel_group = NULL; gchar *opt_triggers = NULL; @@ -39,6 +41,7 @@ gchar *opt_pd_annotations = NULL; gchar *opt_pd_meta = NULL; gchar *opt_pd_binary = NULL; gboolean opt_pd_samplenum = FALSE; +gboolean opt_pd_jsontrace = FALSE; #endif gchar *opt_input_format = NULL; gchar *opt_output_format = NULL; @@ -48,8 +51,9 @@ gchar *opt_time = NULL; gchar *opt_samples = NULL; gchar *opt_frames = NULL; gboolean opt_continuous = FALSE; -gchar *opt_get = NULL; +gchar **opt_gets = NULL; gboolean opt_set = FALSE; +gboolean opt_list_serial = FALSE; /* * Defines a callback function that generates an error if an @@ -75,7 +79,6 @@ static gboolean check_ ## option \ } CHECK_ONCE(opt_drv) -CHECK_ONCE(opt_config) CHECK_ONCE(opt_input_format) CHECK_ONCE(opt_output_format) CHECK_ONCE(opt_transform_module) @@ -90,7 +93,6 @@ CHECK_ONCE(opt_pd_binary) CHECK_ONCE(opt_time) CHECK_ONCE(opt_samples) CHECK_ONCE(opt_frames) -CHECK_ONCE(opt_get) #undef CHECK_STR_ONCE @@ -102,11 +104,13 @@ static const GOptionEntry optargs[] = { "Show version", NULL}, {"list-supported", 'L', 0, G_OPTION_ARG_NONE, &opt_list_supported, "List supported devices/modules/decoders", NULL}, + {"list-supported-wiki", 0, 0, G_OPTION_ARG_NONE, &opt_list_supported_wiki, + "List supported decoders (MediaWiki)", NULL}, {"loglevel", 'l', 0, G_OPTION_ARG_INT, &opt_loglevel, "Set loglevel (5 is most verbose)", NULL}, {"driver", 'd', 0, G_OPTION_ARG_CALLBACK, &check_opt_drv, "The driver to use", NULL}, - {"config", 'c', 0, G_OPTION_ARG_CALLBACK, &check_opt_config, + {"config", 'c', 0, G_OPTION_ARG_STRING_ARRAY, &opt_configs, "Specify device configuration options", NULL}, {"input-file", 'i', 0, G_OPTION_ARG_FILENAME_ARRAY, &input_file_array, "Load input from file", NULL}, @@ -137,9 +141,13 @@ static const GOptionEntry optargs[] = { "Protocol decoder binary output to show", NULL}, {"protocol-decoder-samplenum", 0, 0, G_OPTION_ARG_NONE, &opt_pd_samplenum, "Show sample numbers in decoder output", NULL}, + {"protocol-decoder-jsontrace", 0, 0, G_OPTION_ARG_NONE, &opt_pd_jsontrace, + "Output in Google Trace Event format (JSON)", NULL}, #endif {"scan", 0, 0, G_OPTION_ARG_NONE, &opt_scan_devs, "Scan for devices", NULL}, + {"dont-scan", 'D', 0, G_OPTION_ARG_NONE, &opt_dont_scan, + "Don't auto-scan (use -d spec only)", NULL}, {"show", 0, 0, G_OPTION_ARG_NONE, &opt_show, "Show device/format/decoder details", NULL}, {"time", 0, 0, G_OPTION_ARG_CALLBACK, &check_opt_time, @@ -150,8 +158,10 @@ static const GOptionEntry optargs[] = { "Number of frames to acquire", NULL}, {"continuous", 0, 0, G_OPTION_ARG_NONE, &opt_continuous, "Sample continuously", NULL}, - {"get", 0, 0, G_OPTION_ARG_CALLBACK, &check_opt_get, "Get device options only", NULL}, + {"get", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_gets, + "Get device options only", NULL}, {"set", 0, 0, G_OPTION_ARG_NONE, &opt_set, "Set device options only", NULL}, + {"list-serial", 0, 0, G_OPTION_ARG_NONE, &opt_list_serial, "List available serial/HID/BT/BLE ports", NULL}, {NULL, 0, 0, 0, NULL, NULL, NULL} }; diff --git a/output.c b/output.c new file mode 100644 index 0000000..051ec8b --- /dev/null +++ b/output.c @@ -0,0 +1,41 @@ +/* + * This file is part of the sigrok-cli project. + * + * Copyright (C) 2019 Devan Lai + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#ifdef _WIN32 +#include +#include +#endif + +#include +#include "sigrok-cli.h" + +/* Disable newline translation on stdout when outputting binary data. */ +int setup_binary_stdout(void) +{ +#ifdef _WIN32 + int oldmode = _setmode(_fileno(stdout), _O_BINARY); + if (oldmode == -1) { + g_critical("Failed to set binary stdout mode: errno=%d", errno); + return -1; + } +#endif + return 0; +} diff --git a/parsers.c b/parsers.c index 5ec655f..d92218f 100644 --- a/parsers.c +++ b/parsers.c @@ -267,36 +267,165 @@ int parse_triggerstring(const struct sr_dev_inst *sdi, const char *s, return !error; } -GHashTable *parse_generic_arg(const char *arg, gboolean sep_first) +/** + * Split an input text into a key and value respectively ('=' separator). + * + * @param[in] text Writeable copy of the input text, gets modified. + * @param[out] key Position of the keyword. + * @param[out] val Position of the value. + * + * TODO In theory the returned key/value locations could be const pointers. + * Which even would be preferrable. Unfortunately most call sites deal with + * glib hashes, and their insert API seriously lacks the const attribute. + * So we drop it here as well to avoid clutter at callers'. + */ +static void split_key_value(char *text, char **key, char **val) +{ + char *k, *v; + char *pos; + + if (key) + *key = NULL; + if (val) + *val = NULL; + if (!text || !*text) + return; + + k = text; + v = NULL; + pos = strchr(k, '='); + if (pos) { + *pos = '\0'; + v = ++pos; + } + if (key) + *key = k; + if (val) + *val = v; +} + +/** + * Create hash table from colon separated key-value pairs input text. + * + * Accepts input text as it was specified by users. Splits the colon + * separated key-value pairs and creates a hash table from these items. + * Optionally supports special forms which are useful for different CLI + * features. + * + * Typical form: =[:=]* + * Generic list of key-value pairs, all items being equal. Mere set. + * + * ID form: [:=]* + * First item is not a key-value pair, instead it's an identifier. Used + * to specify a protocol decoder, or a device driver, or an input/output + * file format, optionally followed by more parameters' values. The ID + * part of the input spec is not optional. + * + * Optional ID: [=][:=]* + * All items are key-value pairs. The first item _may_ be an identifier, + * if its key matches a caller specified key name. Otherwise the input + * text is the above typical form, a mere list of key-value pairs while + * none of them is special. + * + * @param[in] arg Input text. + * @param[in] sep_first Boolean, whether ID form is required. + * @param[in] key_first Keyword name if optional ID is applicable. + * + * @returns A hash table which contains the key/value pairs, or #NULL + * when the input is invalid. + */ +GHashTable *parse_generic_arg(const char *arg, + gboolean sep_first, const char *key_first) { GHashTable *hash; + char **elements; int i; - char **elements, *e; + char *k, *v; if (!arg || !arg[0]) return NULL; + if (key_first && !key_first[0]) + key_first = NULL; - i = 0; - hash = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, g_free); + hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); elements = g_strsplit(arg, ":", 0); - if (sep_first) - g_hash_table_insert(hash, g_strdup("sigrok_key"), - g_strdup(elements[i++])); - for (; elements[i]; i++) { - e = strchr(elements[i], '='); - if (!e) - g_hash_table_insert(hash, g_strdup(elements[i]), NULL); - else { - *e++ = '\0'; - g_hash_table_insert(hash, g_strdup(elements[i]), g_strdup(e)); + i = 0; + if (sep_first) { + k = g_strdup("sigrok_key"); + v = g_strdup(elements[i++]); + g_hash_table_insert(hash, k, v); + } else if (key_first) { + split_key_value(elements[i], &k, &v); + if (g_ascii_strcasecmp(k, key_first) == 0) { + k = "sigrok_key"; } + k = g_strdup(k); + v = g_strdup(v); + g_hash_table_insert(hash, k, v); + i++; + } + for (; elements[i]; i++) { + split_key_value(elements[i], &k, &v); + k = g_strdup(k); + v = v ? g_strdup(v) : NULL; + g_hash_table_insert(hash, k, v); } g_strfreev(elements); return hash; } +GSList *check_unknown_keys(const struct sr_option **avail, GHashTable *used) +{ + GSList *unknown; + GHashTableIter iter; + void *key; + const char *used_id; + size_t avail_idx; + const char *avail_id, *found_id; + + /* Collect a list of used but not available keywords. */ + unknown = NULL; + g_hash_table_iter_init(&iter, used); + while (g_hash_table_iter_next(&iter, &key, NULL)) { + used_id = key; + found_id = NULL; + for (avail_idx = 0; avail[avail_idx] && avail[avail_idx]->id; avail_idx++) { + avail_id = avail[avail_idx]->id; + if (strcmp(avail_id, used_id) == 0) { + found_id = avail_id; + break; + } + } + if (!found_id) + unknown = g_slist_append(unknown, g_strdup(used_id)); + } + + /* Return the list of unknown keywords, or NULL if empty. */ + return unknown; +} + +gboolean warn_unknown_keys(const struct sr_option **avail, GHashTable *used, + const char *caption) +{ + GSList *unknown, *l; + gboolean had_unknown; + const char *s; + + if (!caption || !*caption) + caption = "Unknown keyword"; + + unknown = check_unknown_keys(avail, used); + had_unknown = unknown != NULL; + for (l = unknown; l; l = l->next) { + s = l->data; + g_warning("%s: %s.", caption, s); + } + g_slist_free_full(unknown, g_free); + + return had_unknown; +} + GHashTable *generic_arg_to_opt(const struct sr_option **opts, GHashTable *genargs) { GHashTable *hash; @@ -412,7 +541,7 @@ int parse_driver(char *arg, struct sr_dev_driver **driver, GSList **drvopts) if (!arg) return FALSE; - drvargs = parse_generic_arg(arg, TRUE); + drvargs = parse_generic_arg(arg, TRUE, NULL); drvname = g_strdup(g_hash_table_lookup(drvargs, "sigrok_key")); g_hash_table_remove(drvargs, "sigrok_key"); diff --git a/session.c b/session.c index 152f979..0e426cd 100644 --- a/session.c +++ b/session.c @@ -92,7 +92,7 @@ const struct sr_output *setup_output_format(const struct sr_dev_inst *sdi, FILE } } - fmtargs = parse_generic_arg(opt_output_format, TRUE); + fmtargs = parse_generic_arg(opt_output_format, TRUE, NULL); fmtspec = g_hash_table_lookup(fmtargs, "sigrok_key"); if (!fmtspec) g_critical("Invalid output format."); @@ -101,17 +101,25 @@ const struct sr_output *setup_output_format(const struct sr_dev_inst *sdi, FILE g_hash_table_remove(fmtargs, "sigrok_key"); if ((options = sr_output_options_get(omod))) { fmtopts = generic_arg_to_opt(options, fmtargs); + (void)warn_unknown_keys(options, fmtargs, NULL); sr_output_options_free(options); - } else + } else { fmtopts = NULL; + } o = sr_output_new(omod, fmtopts, sdi, opt_output_file); if (opt_output_file) { - if (!sr_output_test_flag(omod, SR_OUTPUT_INTERNAL_IO_HANDLING)) + if (!sr_output_test_flag(omod, SR_OUTPUT_INTERNAL_IO_HANDLING)) { *outfile = g_fopen(opt_output_file, "wb"); - else + if (!*outfile) { + g_critical("Cannot write to output file '%s'.", + opt_output_file); + } + } else { *outfile = NULL; + } } else { + setup_binary_stdout(); *outfile = stdout; } @@ -130,7 +138,7 @@ const struct sr_transform *setup_transform_module(const struct sr_dev_inst *sdi) GHashTable *fmtargs, *fmtopts; char *fmtspec; - fmtargs = parse_generic_arg(opt_transform_module, TRUE); + fmtargs = parse_generic_arg(opt_transform_module, TRUE, NULL); fmtspec = g_hash_table_lookup(fmtargs, "sigrok_key"); if (!fmtspec) g_critical("Invalid transform module."); @@ -139,9 +147,11 @@ const struct sr_transform *setup_transform_module(const struct sr_dev_inst *sdi) g_hash_table_remove(fmtargs, "sigrok_key"); if ((options = sr_transform_options_get(tmod))) { fmtopts = generic_arg_to_opt(options, fmtargs); + (void)warn_unknown_keys(options, fmtargs, NULL); sr_transform_options_free(options); - } else + } else { fmtopts = NULL; + } t = sr_transform_new(tmod, fmtopts, sdi); if (fmtopts) g_hash_table_destroy(fmtopts); @@ -150,14 +160,99 @@ const struct sr_transform *setup_transform_module(const struct sr_dev_inst *sdi) return t; } +/* Get the input stream's list of channels and their types, once. */ +static void props_get_channels(struct df_arg_desc *args, + const struct sr_dev_inst *sdi) +{ + struct input_stream_props *props; + GSList *l; + const struct sr_channel *ch; + + if (!args) + return; + props = &args->props; + if (props->channels) + return; + + props->channels = g_slist_copy(sr_dev_inst_channels_get(sdi)); + if (!props->channels) + return; + for (l = props->channels; l; l = l->next) { + ch = l->data; + if (!ch->enabled) + continue; + if (ch->type != SR_CHANNEL_ANALOG) + continue; + props->first_analog_channel = ch; + break; + } +} + +static gboolean props_chk_1st_channel(struct df_arg_desc *args, + const struct sr_datafeed_analog *analog) +{ + struct sr_channel *ch; + + if (!args || !analog || !analog->meaning) + return FALSE; + ch = g_slist_nth_data(analog->meaning->channels, 0); + if (!ch) + return FALSE; + return ch == args->props.first_analog_channel; +} + +static void props_dump_details(struct df_arg_desc *args) +{ + struct input_stream_props *props; + size_t ch_count; + GSList *l; + const struct sr_channel *ch; + const char *type; + + if (!args) + return; + props = &args->props; + if (props->samplerate) + printf("Samplerate: %" PRIu64 "\n", props->samplerate); + if (props->channels) { + ch_count = g_slist_length(props->channels); + printf("Channels: %zu\n", ch_count); + for (l = props->channels; l; l = l->next) { + ch = l->data; + if (ch->type == SR_CHANNEL_ANALOG) + type = "analog"; + else + type = "logic"; + printf("- %s: %s\n", ch->name, type); + } + } + if (props->unitsize) + printf("Logic unitsize: %zu\n", props->unitsize); + if (props->sample_count_logic) + printf("Logic sample count: %" PRIu64 "\n", props->sample_count_logic); + if (props->sample_count_analog) + printf("Analog sample count: %" PRIu64 "\n", props->sample_count_analog); + if (props->frame_count) + printf("Frame count: %" PRIu64 "\n", props->frame_count); + if (props->triggered) + printf("Trigger count: %" PRIu64 "\n", props->triggered); +} + +static void props_cleanup(struct df_arg_desc *args) +{ + struct input_stream_props *props; + + if (!args) + return; + props = &args->props; + g_slist_free(props->channels); + props->channels = NULL; + props->first_analog_channel = NULL; +} + void datafeed_in(const struct sr_dev_inst *sdi, const struct sr_datafeed_packet *packet, void *cb_data) { - const struct sr_datafeed_meta *meta; - const struct sr_datafeed_logic *logic; - const struct sr_datafeed_analog *analog; - struct sr_session *session; - struct sr_config *src; static const struct sr_output *o = NULL; static const struct sr_output *oa = NULL; static uint64_t rcvd_samples_logic = 0; @@ -165,6 +260,15 @@ void datafeed_in(const struct sr_dev_inst *sdi, static uint64_t samplerate = 0; static int triggered = 0; static FILE *outfile = NULL; + + const struct sr_datafeed_meta *meta; + const struct sr_datafeed_logic *logic; + const struct sr_datafeed_analog *analog; + struct df_arg_desc *df_arg; + int do_props; + struct input_stream_props *props; + struct sr_session *session; + struct sr_config *src; GSList *l; GString *out; GVariant *gvar; @@ -178,14 +282,34 @@ void datafeed_in(const struct sr_dev_inst *sdi, driver = sr_dev_inst_driver_get(sdi); - /* If the first packet to come in isn't a header, don't even try. */ + /* Skip all packets before the first header. */ if (packet->type != SR_DF_HEADER && !o) return; - session = cb_data; + /* Prepare to either process data, or "just" gather properties. */ + df_arg = cb_data; + session = df_arg->session; + do_props = df_arg->do_props; + props = &df_arg->props; + switch (packet->type) { case SR_DF_HEADER: g_debug("cli: Received SR_DF_HEADER."); + if (maybe_config_get(driver, sdi, NULL, SR_CONF_SAMPLERATE, + &gvar) == SR_OK) { + samplerate = g_variant_get_uint64(gvar); + g_variant_unref(gvar); + } + if (do_props) { + /* Setup variables for maximum code path re-use. */ + o = (void *)-1; + limit_samples = 0; + /* Start collecting input stream properties. */ + memset(props, 0, sizeof(*props)); + props->samplerate = samplerate; + props_get_channels(df_arg, sdi); + break; + } if (!(o = setup_output_format(sdi, &outfile))) g_critical("Failed to initialize output module."); @@ -196,12 +320,6 @@ void datafeed_in(const struct sr_dev_inst *sdi, rcvd_samples_logic = rcvd_samples_analog = 0; - if (maybe_config_get(driver, sdi, NULL, SR_CONF_SAMPLERATE, - &gvar) == SR_OK) { - samplerate = g_variant_get_uint64(gvar); - g_variant_unref(gvar); - } - #ifdef HAVE_SRD if (opt_pds) { if (samplerate) { @@ -210,6 +328,7 @@ void datafeed_in(const struct sr_dev_inst *sdi, g_critical("Failed to configure decode session."); break; } + pd_samplerate = samplerate; } if (srd_session_start(srd_sess) != SRD_OK) { g_critical("Failed to start decode session."); @@ -228,18 +347,27 @@ void datafeed_in(const struct sr_dev_inst *sdi, case SR_CONF_SAMPLERATE: samplerate = g_variant_get_uint64(src->data); g_debug("cli: Got samplerate %"PRIu64" Hz.", samplerate); + if (do_props) { + props->samplerate = samplerate; + break; + } #ifdef HAVE_SRD if (opt_pds) { if (srd_session_metadata_set(srd_sess, SRD_CONF_SAMPLERATE, g_variant_new_uint64(samplerate)) != SRD_OK) { g_critical("Failed to pass samplerate to decoder."); } + pd_samplerate = samplerate; } #endif break; case SR_CONF_SAMPLE_INTERVAL: samplerate = g_variant_get_uint64(src->data); g_debug("cli: Got sample interval %"PRIu64" ms.", samplerate); + if (do_props) { + props->samplerate = samplerate; + break; + } break; default: /* Unknown metadata is not an error. */ @@ -250,6 +378,10 @@ void datafeed_in(const struct sr_dev_inst *sdi, case SR_DF_TRIGGER: g_debug("cli: Received SR_DF_TRIGGER."); + if (do_props) { + props->triggered++; + break; + } triggered = 1; break; @@ -260,6 +392,13 @@ void datafeed_in(const struct sr_dev_inst *sdi, if (logic->length == 0) break; + if (do_props) { + props_get_channels(df_arg, sdi); + props->unitsize = logic->unitsize; + props->sample_count_logic += logic->length / logic->unitsize; + break; + } + /* Don't store any samples until triggered. */ if (opt_wait_trigger && !triggered) break; @@ -290,6 +429,15 @@ void datafeed_in(const struct sr_dev_inst *sdi, if (analog->num_samples == 0) break; + if (do_props) { + /* Only count the first analog channel. */ + props_get_channels(df_arg, sdi); + if (!props_chk_1st_channel(df_arg, analog)) + break; + props->sample_count_analog += analog->num_samples; + break; + } + if (limit_samples && rcvd_samples_analog >= limit_samples) break; @@ -302,13 +450,17 @@ void datafeed_in(const struct sr_dev_inst *sdi, case SR_DF_FRAME_END: g_debug("cli: Received SR_DF_FRAME_END."); + if (do_props) { + props->frame_count++; + break; + } break; default: break; } - if (o && !opt_pds) { + if (!do_props && o && !opt_pds) { if (sr_output_send(o, packet, &out) == SR_OK) { if (oa && !out) { /* @@ -333,6 +485,12 @@ void datafeed_in(const struct sr_dev_inst *sdi, if (packet->type == SR_DF_END) { g_debug("cli: Received SR_DF_END."); + if (do_props) { + props_dump_details(df_arg); + props_cleanup(df_arg); + o = NULL; + } + if (o) sr_output_free(o); o = NULL; @@ -496,19 +654,51 @@ int opt_to_gvar(char *key, char *value, struct sr_config *src) return ret; } +int set_dev_options_array(struct sr_dev_inst *sdi, char **opts) +{ + size_t opt_idx; + const char *opt_text; + GHashTable *args; + int ret; + + for (opt_idx = 0; opts && opts[opt_idx]; opt_idx++) { + opt_text = opts[opt_idx]; + args = parse_generic_arg(opt_text, FALSE, "channel_group"); + if (!args) + continue; + ret = set_dev_options(sdi, args); + g_hash_table_destroy(args); + if (ret != SR_OK) + return ret; + } + + return SR_OK; +} + int set_dev_options(struct sr_dev_inst *sdi, GHashTable *args) { struct sr_config src; + const char *cg_name; struct sr_channel_group *cg; GHashTableIter iter; gpointer key, value; int ret; + /* + * Not finding the 'sigrok_key' key (optional user specified + * channel group name) in the current options group's hash table + * is perfectly fine. In that case the -g selection is used, + * which defaults to "the device" (global parameters). + */ + cg_name = g_hash_table_lookup(args, "sigrok_key"); + cg = lookup_channel_group(sdi, cg_name); + g_hash_table_iter_init(&iter, args); while (g_hash_table_iter_next(&iter, &key, &value)) { + if (g_ascii_strcasecmp(key, "sigrok_key") == 0) + continue; if ((ret = opt_to_gvar(key, value, &src)) != 0) return ret; - cg = select_channel_group(sdi); if ((ret = maybe_config_set(sr_dev_inst_driver_get(sdi), sdi, cg, src.key, src.data)) != SR_OK) { g_critical("Failed to set device option '%s': %s.", @@ -522,8 +712,8 @@ int set_dev_options(struct sr_dev_inst *sdi, GHashTable *args) void run_session(void) { + struct df_arg_desc df_arg; GSList *devices, *real_devices, *sd; - GHashTable *devargs; GVariant *gvar; struct sr_session *session; struct sr_trigger *trigger; @@ -536,6 +726,9 @@ void run_session(void) const struct sr_transform *t; GMainLoop *main_loop; + memset(&df_arg, 0, sizeof(df_arg)); + df_arg.do_props = FALSE; + devices = device_scan(); if (!devices) { g_critical("No devices found."); @@ -588,7 +781,9 @@ void run_session(void) g_slist_free(real_devices); sr_session_new(sr_ctx, &session); - sr_session_datafeed_callback_add(session, datafeed_in, session); + df_arg.session = session; + sr_session_datafeed_callback_add(session, datafeed_in, &df_arg); + df_arg.session = NULL; if (sr_dev_open(sdi) != SR_OK) { g_critical("Failed to open device."); @@ -601,12 +796,9 @@ void run_session(void) return; } - if (opt_config) { - if ((devargs = parse_generic_arg(opt_config, FALSE))) { - if (set_dev_options(sdi, devargs) != SR_OK) - return; - g_hash_table_destroy(devargs); - } + if (opt_configs) { + if (set_dev_options_array(sdi, opt_configs) != SR_OK) + return; } if (select_channels(sdi) != SR_OK) { diff --git a/show.c b/show.c index 533ab1e..9ce35b4 100644 --- a/show.c +++ b/show.c @@ -22,6 +22,10 @@ #include #include "sigrok-cli.h" +#define DECODERS_HAVE_TAGS \ + ((SRD_PACKAGE_VERSION_MAJOR > 0) || \ + (SRD_PACKAGE_VERSION_MAJOR == 0) && (SRD_PACKAGE_VERSION_MINOR > 5)) + static gint sort_inputs(gconstpointer a, gconstpointer b) { return strcmp(sr_input_id_get((struct sr_input_module *)a), @@ -204,6 +208,84 @@ void show_supported(void) #endif } +void show_supported_wiki(void) +{ +#ifndef HAVE_SRD + printf("Error, libsigrokdecode support not compiled in."); +#else + const GSList *l; + GSList *sl; + struct srd_decoder *dec; + + if (srd_init(NULL) != SRD_OK) + return; + + srd_decoder_load_all(); + sl = g_slist_copy((GSList *)srd_decoder_list()); + sl = g_slist_sort(sl, sort_pds); + + printf("== Supported protocol decoders ==\n\n"); + + printf("\n\n"); + + printf("Number of currently supported protocol decoders: " + "'''%d'''.\n\n", g_slist_length(sl)); + + printf("{| border=\"0\" style=\"font-size: smaller\" " + "class=\"alternategrey sortable sigroktable\"\n" + "|-\n!Protocol\n!Tags\n!Input IDs\n!Output IDs\n!Status\n" + "!Full name\n!Description\n\n"); + + for (l = sl; l; l = l->next) { + dec = l->data; + +#if DECODERS_HAVE_TAGS + GString *tags = g_string_new(NULL); + for (GSList *t = dec->tags; t; t = t->next) + g_string_append_printf(tags, "%s, ", (char *)t->data); + if (tags->len != 0) + g_string_truncate(tags, tags->len - 2); +#endif + + GString *in = g_string_new(NULL); + for (GSList *t = dec->inputs; t; t = t->next) + g_string_append_printf(in, "%s, ", (char *)t->data); + if (in->len == 0) + g_string_append_printf(in, "—"); + else + g_string_truncate(in, in->len - 2); + + GString *out = g_string_new(NULL); + for (GSList *t = dec->outputs; t; t = t->next) + g_string_append_printf(out, "%s, ", (char *)t->data); + if (out->len == 0) + g_string_append_printf(out, "—"); + else + g_string_truncate(out, out->len - 2); + +#if DECODERS_HAVE_TAGS + printf("{{pd|%s|%s|%s|%s|%s|%s|%s|supported}}\n", + dec->id, dec->name, dec->longname, dec->desc, + tags->str, in->str, out->str); +#else + printf("{{pd|%s|%s|%s|%s|%s|%s|supported}}\n", + dec->id, dec->name, dec->longname, dec->desc, + in->str, out->str); +#endif + +#if DECODERS_HAVE_TAGS + g_string_free(tags, TRUE); +#endif + g_string_free(in, TRUE); + g_string_free(out, TRUE); + } + g_slist_free(sl); + srd_exit(); + + printf("\n|}\n"); +#endif +} + static gint sort_channels(gconstpointer a, gconstpointer b) { const struct sr_channel *pa = a, *pb = b; @@ -218,12 +300,13 @@ static void print_dev_line(const struct sr_dev_inst *sdi) GString *s; GVariant *gvar; struct sr_dev_driver *driver; - const char *vendor, *model, *version; + const char *vendor, *model, *version, *sernum; driver = sr_dev_inst_driver_get(sdi); vendor = sr_dev_inst_vendor_get(sdi); model = sr_dev_inst_model_get(sdi); version = sr_dev_inst_version_get(sdi); + sernum = sr_dev_inst_sernum_get(sdi); channels = sr_dev_inst_channels_get(sdi); s = g_string_sized_new(128); @@ -240,6 +323,8 @@ static void print_dev_line(const struct sr_dev_inst *sdi) g_string_append_printf(s, "%s ", model); if (version && version[0]) g_string_append_printf(s, "%s ", version); + if (sernum && sernum[0]) + g_string_append_printf(s, "[S/N: %s] ", sernum); if (channels) { if (g_slist_length(channels) == 1) { ch = channels->data; @@ -321,7 +406,9 @@ void show_dev_detail(void) GVariant *gvar_dict, *gvar_list, *gvar; gsize num_elements; double dlow, dhigh, dcur_low, dcur_high; - const uint64_t *uint64, p = 0, q = 0, low = 0, high = 0; + const uint64_t *uint64; + uint64_t cur_rate, rate; + uint64_t p = 0, q = 0, low = 0, high = 0; uint64_t tmp_uint64, mask, cur_low, cur_high, cur_p, cur_q; GArray *opts; const int32_t *int32; @@ -330,6 +417,9 @@ void show_dev_detail(void) unsigned int num_devices, i, j; char *tmp_str, *s, c; const char **stropts; + double tmp_flt; + gboolean have_tmp_flt; + const double *fltopts; if (parse_driver(opt_drv, &driver_from_opt, NULL)) { /* A driver was specified, report driver-wide options now. */ @@ -365,7 +455,7 @@ void show_dev_detail(void) * returned, or which values for them. */ select_channels(sdi); - channel_group = select_channel_group(sdi); + channel_group = lookup_channel_group(sdi, NULL); if (!(opts = sr_dev_options(driver, sdi, channel_group))) /* Driver supports no device instance options. */ @@ -461,6 +551,13 @@ void show_dev_detail(void) } else if (key == SR_CONF_SAMPLERATE) { /* Supported samplerates */ printf(" %s", srci->id); + cur_rate = ~0ull; + if (maybe_config_get(driver, sdi, channel_group, + SR_CONF_SAMPLERATE, &gvar) == SR_OK) { + if (g_variant_is_of_type(gvar, G_VARIANT_TYPE_UINT64)) + cur_rate = g_variant_get_uint64(gvar); + g_variant_unref(gvar); + } if (maybe_config_list(driver, sdi, channel_group, SR_CONF_SAMPLERATE, &gvar_dict) != SR_OK) { printf("\n"); @@ -472,9 +569,14 @@ void show_dev_detail(void) &num_elements, sizeof(uint64_t)); printf(" - supported samplerates:\n"); for (i = 0; i < num_elements; i++) { - if (!(s = sr_samplerate_string(uint64[i]))) + rate = uint64[i]; + s = sr_samplerate_string(rate); + if (!s) continue; - printf(" %s\n", s); + printf(" %s", s); + if (rate == cur_rate) + printf(" (current)"); + printf("\n"); g_free(s); } g_variant_unref(gvar_list); @@ -637,12 +739,34 @@ void show_dev_detail(void) } else if (srci->datatype == SR_T_FLOAT) { printf(" %s: ", srci->id); + tmp_flt = 0.0; + have_tmp_flt = FALSE; if (maybe_config_get(driver, sdi, channel_group, key, &gvar) == SR_OK) { - printf("%f\n", g_variant_get_double(gvar)); + tmp_flt = g_variant_get_double(gvar); + have_tmp_flt = TRUE; g_variant_unref(gvar); - } else + } + if (maybe_config_list(driver, sdi, channel_group, key, + &gvar) != SR_OK) { + if (have_tmp_flt) { + /* Can't list, but got a value to show. */ + printf("%f (current)", tmp_flt); + } printf("\n"); + continue; + } + fltopts = g_variant_get_fixed_array(gvar, + &num_elements, sizeof(tmp_flt)); + for (i = 0; i < num_elements; i++) { + if (i) + printf(", "); + printf("%f", fltopts[i]); + if (have_tmp_flt && fltopts[i] == tmp_flt) + printf(" (current)"); + } + printf("\n"); + g_variant_unref(gvar); } else if (srci->datatype == SR_T_RATIONAL_PERIOD || srci->datatype == SR_T_RATIONAL_VOLT) { @@ -769,6 +893,17 @@ static void show_pd_detail_single(const char *pd) } else { printf("None.\n"); } + printf("Decoder tags:\n"); +#if DECODERS_HAVE_TAGS + if (dec->tags) { + for (l = dec->tags; l; l = l->next) { + str = l->data; + printf("- %s\n", str); + } + } else { + printf("None.\n"); + } +#endif printf("Annotation classes:\n"); if (dec->annotations) { for (l = dec->annotations; l; l = l->next) { @@ -977,3 +1112,25 @@ void show_transform(void) } g_strfreev(tok); } + +static void print_serial_port(gpointer data, gpointer user_data) +{ + struct sr_serial_port *port; + + port = (void *)data; + (void)user_data; + printf(" %s\t%s\n", port->name, port->description); +} + +void show_serial_ports(void) +{ + GSList *serial_ports; + + serial_ports = sr_serial_list(NULL); + if (!serial_ports) + return; + + printf("Available serial/HID/BT/BLE ports:\n"); + g_slist_foreach(serial_ports, print_serial_port, NULL); + g_slist_free_full(serial_ports, (GDestroyNotify)sr_serial_free); +} diff --git a/sigrok-cli.h b/sigrok-cli.h index f564448..2c0fbdc 100644 --- a/sigrok-cli.h +++ b/sigrok-cli.h @@ -45,29 +45,51 @@ int maybe_config_list(struct sr_dev_driver *driver, /* show.c */ void show_version(void); void show_supported(void); +void show_supported_wiki(void); void show_dev_list(void); void show_dev_detail(void); void show_pd_detail(void); void show_input(void); void show_output(void); void show_transform(void); +void show_serial_ports(void); /* device.c */ GSList *device_scan(void); -struct sr_channel_group *select_channel_group(struct sr_dev_inst *sdi); +struct sr_channel_group *lookup_channel_group(struct sr_dev_inst *sdi, + const char *cg_name); /* session.c */ +struct df_arg_desc { + struct sr_session *session; + int do_props; + struct input_stream_props { + uint64_t samplerate; + GSList *channels; + const struct sr_channel *first_analog_channel; + size_t unitsize; + uint64_t sample_count_logic; + uint64_t sample_count_analog; + uint64_t frame_count; + uint64_t triggered; + } props; +}; void datafeed_in(const struct sr_dev_inst *sdi, const struct sr_datafeed_packet *packet, void *cb_data); int opt_to_gvar(char *key, char *value, struct sr_config *src); +int set_dev_options_array(struct sr_dev_inst *sdi, char **opts); int set_dev_options(struct sr_dev_inst *sdi, GHashTable *args); void run_session(void); /* input.c */ -void load_input_file(void); +void load_input_file(gboolean do_props); + +/* output.c */ +int setup_binary_stdout(void); /* decode.c */ #ifdef HAVE_SRD +extern uint64_t pd_samplerate; int register_pds(gchar **all_pds, char *opt_pd_annotations); int setup_pd_annotations(char *opt_pd_annotations); int setup_pd_meta(char *opt_pd_meta); @@ -75,6 +97,8 @@ int setup_pd_binary(char *opt_pd_binary); void show_pd_annotations(struct srd_proto_data *pdata, void *cb_data); void show_pd_meta(struct srd_proto_data *pdata, void *cb_data); void show_pd_binary(struct srd_proto_data *pdata, void *cb_data); +void show_pd_prepare(void); +void show_pd_close(void); void map_pd_channels(struct sr_dev_inst *sdi); #endif @@ -83,8 +107,12 @@ struct sr_channel *find_channel(GSList *channellist, const char *channelname); GSList *parse_channelstring(struct sr_dev_inst *sdi, const char *channelstring); int parse_triggerstring(const struct sr_dev_inst *sdi, const char *s, struct sr_trigger **trigger); -GHashTable *parse_generic_arg(const char *arg, gboolean sep_first); +GHashTable *parse_generic_arg(const char *arg, + gboolean sep_first, const char *key_first); GHashTable *generic_arg_to_opt(const struct sr_option **opts, GHashTable *genargs); +GSList *check_unknown_keys(const struct sr_option **avail, GHashTable *used); +gboolean warn_unknown_keys(const struct sr_option **avail, GHashTable *used, + const char *caption); int canon_cmp(const char *str1, const char *str2); int parse_driver(char *arg, struct sr_dev_driver **driver, GSList **drvopts); @@ -95,13 +123,15 @@ void clear_anykey(void); /* options.c */ extern gboolean opt_version; extern gboolean opt_list_supported; +extern gboolean opt_list_supported_wiki; extern gint opt_loglevel; extern gboolean opt_scan_devs; +extern gboolean opt_dont_scan; extern gboolean opt_wait_trigger; extern gchar *opt_input_file; extern gchar *opt_output_file; extern gchar *opt_drv; -extern gchar *opt_config; +extern gchar **opt_configs; extern gchar *opt_channels; extern gchar *opt_channel_group; extern gchar *opt_triggers; @@ -111,6 +141,7 @@ extern gchar *opt_pd_annotations; extern gchar *opt_pd_meta; extern gchar *opt_pd_binary; extern gboolean opt_pd_samplenum; +extern gboolean opt_pd_jsontrace; #endif extern gchar *opt_input_format; extern gchar *opt_output_format; @@ -120,8 +151,9 @@ extern gchar *opt_time; extern gchar *opt_samples; extern gchar *opt_frames; extern gboolean opt_continuous; -extern gchar *opt_get; +extern gchar **opt_gets; extern gboolean opt_set; +extern gboolean opt_list_serial; int parse_options(int argc, char **argv); void show_help(void);