2 * This file is part of the libsigrok project.
4 * Copyright (C) 2012-2013 Uwe Hermann <uwe@hermann-uwe.de>
5 * Copyright (C) 2016 Gerhard Sittig <gerhard.sittig@gmx.net>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, see <http://www.gnu.org/licenses/>.
22 * Parser for the ASYC-II 16-bytes ASCII protocol (PRINT).
24 * This should work for various multimeters which use this kind of protocol,
25 * even though there is some variation in which modes each DMM supports.
27 * This implementation was developed for and tested with a Metrix MX56C,
28 * which is identical to the BK Precision 5390.
29 * See the metex14.c implementation for the 14-byte protocol used by many
40 #include <libsigrok/libsigrok.h>
41 #include "libsigrok-internal.h"
43 #define LOG_PREFIX "asycii"
46 * Parse sign and value from text buffer, byte 0-6.
48 * The first character always is the sign (' ' or '-'). Subsequent
49 * positions contain digits, dots, or spaces. Overflow / open inputs
50 * are signalled with several magic literals that cannot get interpreted
51 * as a number, either with 'X' characters in them, or with several
54 * @param[in] buf The text buffer received from the DMM.
55 * @param[out] result A floating point number value.
56 * @param[out] exponent Augments the number value.
58 static int parse_value(const char *buf, struct asycii_info *info,
59 float *result, int *exponent)
67 * Strip all spaces from bytes 0-6. By copying all
68 * non-space characters into a buffer.
71 for (i = 0; i < 7; i++) {
73 valstr[cnt++] = buf[i];
77 sr_spew("%s(), number buffer [%s]", __func__, valp);
80 * Check for "over limit" conditions. Depending on the meter's
81 * selected mode, the textual representation might differ. Test
82 * all known variations.
85 is_ol += (g_ascii_strcasecmp(valp, ".OL") == 0) ? 1 : 0;
86 is_ol += (g_ascii_strcasecmp(valp, "O.L") == 0) ? 1 : 0;
87 is_ol += (g_ascii_strcasecmp(valp, "-.OL") == 0) ? 1 : 0;
88 is_ol += (g_ascii_strcasecmp(valp, "-O.L") == 0) ? 1 : 0;
89 is_ol += (g_ascii_strncasecmp(valp, "X", 1) == 0) ? 1 : 0;
90 is_ol += (g_ascii_strncasecmp(valp, "-X", 2) == 0) ? 1 : 0;
92 sr_spew("%s(), over limit", __func__);
98 * Convert the textual number representation to a float, and
101 if (sr_atof_ascii(valp, result) != SR_OK) {
102 info->is_invalid = TRUE;
103 sr_spew("%s(), cannot convert number", __func__);
106 dot_pos = g_strstr_len(valstr, -1, ".");
108 *exponent = -(valstr + strlen(valstr) - dot_pos - 1);
111 sr_spew("%s(), display value is %f, exponent %d",
112 __func__, *result, *exponent);
117 * Parse unit and flags from text buffer, bytes 7-14.
119 * The unit and flags optionally follow the number value for the
120 * measurement. Either can be present or absent. The scale factor
121 * is always at index 7. The unit starts at index 8, and is of
122 * variable length. Flags immediately follow the unit. The remainder
123 * of the text buffer is SPACE padded, and terminated with CR.
125 * Notice the implementation detail of case @b sensitive comparison.
126 * Since the measurement unit and flags are directly adjacent and are
127 * not separated from each other, case insensitive comparison would
128 * yield wrong results. It's essential that e.g. "Vac" gets split into
129 * the "V" unit and the "ac" flag, not into "VA" and the unknown "c"
132 * Notice, too, that order of comparison matters in the absence of
133 * separators or fixed positions and with ambiguous text (note that we do
134 * partial comparison). It's essential to e.g. correctly tell "VA" from "V".
136 * @param[in] buf The text buffer received from the DMM.
137 * @param[out] info Broken down measurement details.
139 static void parse_flags(const char *buf, struct asycii_info *info)
145 /* Bytes 0-6: Number value, see parse_value(). */
147 /* Strip spaces from bytes 7-14. */
149 for (i = 7; i < 15; i++) {
151 unit[cnt++] = buf[i];
155 sr_spew("%s(): unit/flag buffer [%s]", __func__, u);
157 /* Scan for the scale factor. */
158 sr_spew("%s(): scanning factor, buffer [%s]", __func__, u);
161 info->is_pico = TRUE;
162 } else if (*u == 'n') {
164 info->is_nano = TRUE;
165 } else if (*u == 'u') {
167 info->is_micro = TRUE;
168 } else if (*u == 'm') {
170 info->is_milli = TRUE;
171 } else if (*u == ' ') {
173 } else if (*u == 'k') {
175 info->is_kilo = TRUE;
176 } else if (*u == 'M') {
178 info->is_mega = TRUE;
180 /* Absence of a scale modifier is perfectly fine. */
183 /* Scan for the measurement unit. */
184 sr_spew("%s(): scanning unit, buffer [%s]", __func__, u);
185 if (g_str_has_prefix(u, "A")) {
187 info->is_ampere = TRUE;
188 } else if (g_str_has_prefix(u, "VA")) {
190 info->is_volt_ampere = TRUE;
191 } else if (g_str_has_prefix(u, "V")) {
193 info->is_volt = TRUE;
194 } else if (g_str_has_prefix(u, "ohm")) {
196 info->is_resistance = TRUE;
198 } else if (g_str_has_prefix(u, "F")) {
200 info->is_capacitance = TRUE;
201 info->is_farad = TRUE;
202 } else if (g_str_has_prefix(u, "dB")) {
204 info->is_gain = TRUE;
205 info->is_decibel = TRUE;
206 } else if (g_str_has_prefix(u, "Hz")) {
208 info->is_frequency = TRUE;
209 info->is_hertz = TRUE;
210 } else if (g_str_has_prefix(u, "%")) {
212 info->is_duty_cycle = TRUE;
215 info->is_duty_pos = TRUE;
216 } else if (*u == '-') {
218 info->is_duty_neg = TRUE;
220 info->is_invalid = TRUE;
222 } else if (g_str_has_prefix(u, "Cnt")) {
224 info->is_pulse_count = TRUE;
225 info->is_unitless = TRUE;
228 info->is_count_pos = TRUE;
229 } else if (*u == '-') {
231 info->is_count_neg = TRUE;
233 info->is_invalid = TRUE;
235 } else if (g_str_has_prefix(u, "s")) {
237 info->is_pulse_width = TRUE;
238 info->is_seconds = TRUE;
241 info->is_period_pos = TRUE;
242 } else if (*u == '-') {
244 info->is_period_neg = TRUE;
246 info->is_invalid = TRUE;
249 /* Not strictly illegal, but unknown/unsupported. */
250 sr_spew("%s(): measurement: unsupported", __func__);
251 info->is_invalid = TRUE;
254 /* Scan for additional flags. */
255 sr_spew("%s(): scanning flags, buffer [%s]", __func__, u);
256 if (g_str_has_prefix(u, "ac+dc")) {
257 u += strlen("ac+dc");
258 info->is_ac_and_dc = TRUE;
259 } else if (g_str_has_prefix(u, "ac")) {
262 } else if (g_str_has_prefix(u, "dc")) {
265 } else if (g_str_has_prefix(u, "d")) {
267 info->is_diode = TRUE;
268 } else if (g_str_has_prefix(u, "Pk")) {
272 info->is_peak_max = TRUE;
273 } else if (*u == '-') {
275 info->is_peak_min = TRUE;
277 info->is_invalid = TRUE;
279 } else if (*u == '\0') {
280 /* Absence of any flags is acceptable. */
282 /* Presence of unknown flags is not. */
283 sr_dbg("%s(): flag: unknown", __func__);
284 info->is_invalid = TRUE;
287 /* Was all of the received data consumed? */
289 info->is_invalid = TRUE;
293 * - The protocol does not distinguish between "resistance"
295 * - Relative measurement and hold cannot get recognized.
300 * Fill in a datafeed from previously parsed measurement details.
302 * @param[out] analog The datafeed which gets filled in.
303 * @param[in] floatval The number value of the measurement.
304 * @param[in] exponent Augments the number value.
305 * @param[in] info Scale and unit and other attributes.
307 static void handle_flags(struct sr_datafeed_analog *analog, float *floatval,
308 int *exponent, const struct asycii_info *info)
325 *floatval *= powf(10, factor);
328 /* Measurement modes */
330 analog->meaning->mq = SR_MQ_VOLTAGE;
331 analog->meaning->unit = SR_UNIT_VOLT;
333 if (info->is_volt_ampere) {
334 analog->meaning->mq = SR_MQ_POWER;
335 analog->meaning->unit = SR_UNIT_VOLT_AMPERE;
337 if (info->is_ampere) {
338 analog->meaning->mq = SR_MQ_CURRENT;
339 analog->meaning->unit = SR_UNIT_AMPERE;
341 if (info->is_frequency) {
342 analog->meaning->mq = SR_MQ_FREQUENCY;
343 analog->meaning->unit = SR_UNIT_HERTZ;
345 if (info->is_duty_cycle) {
346 analog->meaning->mq = SR_MQ_DUTY_CYCLE;
347 analog->meaning->unit = SR_UNIT_PERCENTAGE;
349 if (info->is_pulse_width) {
350 analog->meaning->mq = SR_MQ_PULSE_WIDTH;
351 analog->meaning->unit = SR_UNIT_SECOND;
353 if (info->is_pulse_count) {
354 analog->meaning->mq = SR_MQ_COUNT;
355 analog->meaning->unit = SR_UNIT_UNITLESS;
357 if (info->is_resistance) {
358 analog->meaning->mq = SR_MQ_RESISTANCE;
359 analog->meaning->unit = SR_UNIT_OHM;
361 if (info->is_capacitance) {
362 analog->meaning->mq = SR_MQ_CAPACITANCE;
363 analog->meaning->unit = SR_UNIT_FARAD;
365 if (info->is_diode) {
366 analog->meaning->mq = SR_MQ_VOLTAGE;
367 analog->meaning->unit = SR_UNIT_VOLT;
370 analog->meaning->mq = SR_MQ_GAIN;
371 analog->meaning->unit = SR_UNIT_DECIBEL_VOLT;
374 /* Measurement related flags */
376 analog->meaning->mqflags |= SR_MQFLAG_AC;
377 if (info->is_ac_and_dc)
378 analog->meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_DC;
380 analog->meaning->mqflags |= SR_MQFLAG_DC;
382 analog->meaning->mqflags |= SR_MQFLAG_DIODE | SR_MQFLAG_DC;
383 if (info->is_peak_max)
384 analog->meaning->mqflags |= SR_MQFLAG_MAX;
385 if (info->is_peak_min)
386 analog->meaning->mqflags |= SR_MQFLAG_MIN;
390 * Check measurement details for consistency and validity.
392 * @param[in] info The previously parsed details.
394 * @return TRUE on success, FALSE otherwise.
396 static gboolean flags_valid(const struct asycii_info *info)
400 /* Have previous checks raised the "invalid" flag? */
401 if (info->is_invalid) {
402 sr_dbg("Previous parse raised \"invalid\" flag for packet.");
406 /* Does the packet have more than one multiplier? */
408 count += (info->is_pico) ? 1 : 0;
409 count += (info->is_nano) ? 1 : 0;
410 count += (info->is_micro) ? 1 : 0;
411 count += (info->is_milli) ? 1 : 0;
412 count += (info->is_kilo) ? 1 : 0;
413 count += (info->is_mega) ? 1 : 0;
415 sr_dbg("More than one multiplier detected in packet.");
419 /* Does the packet "measure" more than one type of value? */
421 count += (info->is_volt || info->is_diode) ? 1 : 0;
422 count += (info->is_volt_ampere) ? 1 : 0;
423 count += (info->is_ampere) ? 1 : 0;
424 count += (info->is_gain) ? 1 : 0;
425 count += (info->is_resistance) ? 1 : 0;
426 count += (info->is_capacitance) ? 1 : 0;
427 count += (info->is_frequency) ? 1 : 0;
428 count += (info->is_duty_cycle) ? 1 : 0;
429 count += (info->is_pulse_width) ? 1 : 0;
430 count += (info->is_pulse_count) ? 1 : 0;
432 sr_dbg("More than one measurement type detected in packet.");
436 /* Are conflicting AC and DC flags set? */
438 count += (info->is_ac) ? 1 : 0;
439 count += (info->is_ac_and_dc) ? 1 : 0;
440 count += (info->is_dc) ? 1 : 0;
442 sr_dbg("Conflicting AC and DC flags detected in packet.");
449 #ifdef HAVE_LIBSERIALPORT
451 * Arrange for the reception of another measurement from the DMM.
453 * This routine is unused in the currently implemented PRINT mode,
454 * where the meter sends measurements to the PC in pre-set intervals,
455 * without the PC's intervention.
457 * @param[in] serial The serial connection.
461 SR_PRIV int sr_asycii_packet_request(struct sr_serial_dev_inst *serial)
464 * The current implementation assumes that the user pressed
465 * the PRINT button. It has no support to query/trigger packet
466 * reception from the meter.
469 sr_spew("NOT requesting DMM packet.");
475 * Check whether a received frame is valid.
477 * @param[in] buf The text buffer with received data.
479 * @return TRUE upon success, FALSE otherwise.
481 SR_PRIV gboolean sr_asycii_packet_valid(const uint8_t *buf)
483 struct asycii_info info;
485 /* First check whether we are in sync with the packet stream. */
489 /* Have the received packet content parsed. */
490 memset(&info, 0x00, sizeof(info));
491 parse_flags((const char *)buf, &info);
492 if (!flags_valid(&info))
499 * Parse a protocol packet.
501 * @param[in] buf Buffer containing the protocol packet. Must not be NULL.
502 * @param[out] floatval Pointer to a float variable. That variable will
503 * be modified in-place depending on the protocol packet.
505 * @param[out] analog Pointer to a struct sr_datafeed_analog. The struct
506 * will be filled with data according to the protocol packet.
508 * @param[out] info Pointer to a struct asycii_info. The struct will be
509 * filled with data according to the protocol packet. Must
512 * @return SR_OK upon success, SR_ERR upon failure. Upon errors, the
513 * 'analog' variable contents are undefined and should not
516 SR_PRIV int sr_asycii_parse(const uint8_t *buf, float *floatval,
517 struct sr_datafeed_analog *analog, void *info)
520 struct asycii_info *info_local;
524 /* Don't print byte 15. That one contains the carriage return. */
525 sr_dbg("DMM packet: \"%.15s\"", buf);
527 memset(info_local, 0x00, sizeof(*info_local));
530 ret = parse_value((const char *)buf, info_local, floatval, &exponent);
532 sr_dbg("Error parsing value: %d.", ret);
536 parse_flags((const char *)buf, info_local);
537 handle_flags(analog, floatval, &exponent, info_local);
539 analog->encoding->digits = -exponent;
540 analog->spec->spec_digits = -exponent;