device.c \
session.c \
input.c \
+ output.c \
decode.c \
sigrok-cli.h \
parsers.c \
##
## 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
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
# Python
File "${CROSS}/python34.dll"
File "${CROSS}/python34.zip"
+ File "${CROSS}/*.pyd"
SetOutPath "$INSTDIR\share"
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.
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,
GSList *optl;
GVariant *gvar;
gint64 val_int;
+ double val_dbl;
int ret;
char *val_str, *conv;
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));
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;
}
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",
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);
}
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;
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:
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
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++) {
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;
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;
}
-.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"
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
.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
.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
.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
.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
.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
$
.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"
.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.
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
#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;
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");
}
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)
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) {
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) {
}
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);
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);
}
}
}
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;
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)
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;
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;
show_pd_annotations, NULL) != SRD_OK)
goto done;
}
+ show_pd_prepare();
}
#endif
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)
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
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;
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;
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
}
CHECK_ONCE(opt_drv)
-CHECK_ONCE(opt_config)
CHECK_ONCE(opt_input_format)
CHECK_ONCE(opt_output_format)
CHECK_ONCE(opt_transform_module)
CHECK_ONCE(opt_time)
CHECK_ONCE(opt_samples)
CHECK_ONCE(opt_frames)
-CHECK_ONCE(opt_get)
#undef CHECK_STR_ONCE
"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},
"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,
"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}
};
--- /dev/null
+/*
+ * 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;
+}
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;
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");
}
}
- 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.");
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;
}
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.");
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);
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;
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;
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.");
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) {
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.");
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. */
case SR_DF_TRIGGER:
g_debug("cli: Received SR_DF_TRIGGER.");
+ if (do_props) {
+ props->triggered++;
+ break;
+ }
triggered = 1;
break;
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;
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;
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) {
/*
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;
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.",
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;
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.");
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.");
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) {
#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),
#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, "—");
+ 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;
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);
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;
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;
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. */
* 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. */
} 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");
&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);
} 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) {
} 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) {
}
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);
+}
/* 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);
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
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);
/* 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;
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;
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);