]> sigrok.org Git - sigrok-cli.git/commitdiff
Backport recent changes from mainline.
authorUwe Hermann <redacted>
Wed, 24 Feb 2021 19:55:13 +0000 (20:55 +0100)
committerUwe Hermann <redacted>
Mon, 1 Mar 2021 17:30:44 +0000 (18:30 +0100)
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.

13 files changed:
Makefile.am
contrib/sigrok-cli_cross.nsi.in
decode.c
device.c
doc/sigrok-cli.1
input.c
main.c
options.c
output.c [new file with mode: 0644]
parsers.c
session.c
show.c
sigrok-cli.h

index 84f94b49afbad809ecb58de3b60dacd7766b7d47..d5cc780c49c2609a3da5737b9322dde9f5850206 100644 (file)
@@ -30,6 +30,7 @@ sigrok_cli_SOURCES = \
        device.c \
        session.c \
        input.c \
+       output.c \
        decode.c \
        sigrok-cli.h \
        parsers.c \
index 196a10740a599eed437c71484d23f801ca673118..6f60540fa19cfd31969dbacff3b624403cf3b4a6 100644 (file)
@@ -1,7 +1,7 @@
 ##
 ## This file is part of the sigrok-cli project.
 ##
-## Copyright (C) 2011-2014 Uwe Hermann <uwe@hermann-uwe.de>
+## Copyright (C) 2011-2020 Uwe Hermann <uwe@hermann-uwe.de>
 ##
 ## 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.
index 2b95f2346fb2d22d3fd8b5bf4199aa980ba0bd88..4ddf34afbbaf00db414a041f4ef2e61ddc27bc0f 100644 (file)
--- 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
index a159ff003c7f07f7b2669885e87fdbb44d5730b7..7da744431154c134b5bee897bc24f817cc774e46 100644 (file)
--- 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;
 }
index c87d383e8be74b5fb74b5886eed5dcf11662c6f6..4258009bd7a507b282911d78a2bdf6d2e50a82c5 100644 (file)
@@ -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 <drivername>
-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 " <deviceoption>
 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=<name>
+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 " <filename>
 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 " <triggerlist>
 A comma-separated list of triggers to use, of the form
@@ -300,14 +346,14 @@ Example:
 .sp
 .TP
 .BR "\-A, \-\-protocol\-decoder\-annotations " <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 <file.sr> \-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 <file.sr> \-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 <file.sr> \-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 <file.sr> \-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 <file.sr> \-\-show
+ $
+.B "sigrok\-cli \-\-input\-file <file.vcd> \-\-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 <variable>
 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=<name>"
+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 8eeb94c869b57679eb75ca4494248740c52633a3..6c149722588db3a88ff0f7ca3618a4f213f77426 100644 (file)
--- 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 689c2d57496ba5b4506698165318a88459ea7e2e..b128c8800e2fca733db67aa77a5769338ba56f4c 100644 (file)
--- 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
index b9e149433a0862426a23179558ccbb3eb6300461..2573bd4c17b6cb861be37239070ff6e7bc8e07ef 100644 (file)
--- a/options.c
+++ b/options.c
 
 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 (file)
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 <devan.lai@gmail.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+
+#ifdef _WIN32
+#include <io.h>
+#include <fcntl.h>
+#endif
+
+#include <glib.h>
+#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;
+}
index 5ec655f78ad3dce8a05f01186a2a7ec61febb437..d92218f8664ed5a8858e32a6b631b8d4c68633aa 100644 (file)
--- 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: <key>=<val>[:<key>=<val>]*
+ * Generic list of key-value pairs, all items being equal. Mere set.
+ *
+ * ID form: <id>[:<key>=<val>]*
+ * 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: [<sel>=<id>][:<key>=<val>]*
+ * 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");
index 152f9790b818bcffc94a1e81b1796852abbe8ce4..0e426cdd1957268102797f37f0c95a5d981de7fb 100644 (file)
--- 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 533ab1e55d8c65a3d4277c2d9ce105f1e66a2482..9ce35b4a61b4360e39dcc03cbc6c985371a580bb 100644 (file)
--- a/show.c
+++ b/show.c
 #include <string.h>
 #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("<!-- Generated via sigrok-cli --list-supported-wiki. -->\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, "&mdash;");
+               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, "&mdash;");
+               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);
+}
index f564448f13da785563f1d1851a360983d5800034..2c0fbdcf57d2358b9864cce8676b39e1e7bba1f0 100644 (file)
@@ -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);