]> sigrok.org Git - libsigrok.git/blame - src/dmm/asycii.c
asyc-ii: Unobfuscate a comment on packet parse constraints
[libsigrok.git] / src / dmm / asycii.c
CommitLineData
4ba4d52a
GS
1/*
2 * This file is part of the libsigrok project.
3 *
4 * Copyright (C) 2012-2013 Uwe Hermann <uwe@hermann-uwe.de>
5 * Copyright (C) 2016 Gerhard Sittig <gerhard.sittig@gmx.net>
6 *
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.
11 *
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.
16 *
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/>.
19 */
20
21/*
22 * Parser for the ASYC-II 16-bytes ASCII protocol (PRINT).
23 *
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.
26 *
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
30 * other models.
31 */
32
33#include <config.h>
34#include <ctype.h>
35#include <glib.h>
36#include <math.h>
37#include <stdlib.h>
38#include <string.h>
39#include <strings.h>
40#include <libsigrok/libsigrok.h>
41#include "libsigrok-internal.h"
42
43#define LOG_PREFIX "asycii"
44
45/**
46 * Parse sign and value from text buffer, byte 0-6.
47 *
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
52 * forms of "OL".
53 *
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.
57 */
58static int parse_value(const char *buf, struct asycii_info *info,
59 float *result, int *exponent)
60{
61 char valstr[7 + 1];
62 const char *valp;
63 int i, cnt, is_ol, dot_pos;
64 char *endp;
65
66 /*
67 * Strip all spaces from bytes 0-6. By copying all
68 * non-space characters into a buffer.
69 */
70 cnt = 0;
71 for (i = 0; i < 7; i++) {
72 if (buf[i] != ' ')
73 valstr[cnt++] = buf[i];
74 }
75 valstr[cnt] = '\0';
76 valp = &valstr[0];
77 sr_spew("%s(), number buffer [%s]", __func__, valp);
78
79 /*
80 * Check for "over limit" conditions. Depending on the meter's
81 * selected mode, the textual representation might differ. Test
82 * all known variations.
83 */
84 is_ol = 0;
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;
91 if (is_ol) {
92 sr_spew("%s(), over limit", __func__);
93 *result = INFINITY;
94 return SR_OK;
95 }
96
97 /*
98 * Convert the textual number representation to a float, and
99 * an exponent. Apply sanity checks (optional sign, digits and
100 * dot expected here, exclusively).
101 */
102 endp = NULL;
103 *result = strtof(valp, &endp);
104 if (endp == NULL || *endp != '\0') {
105 info->is_invalid = TRUE;
106 sr_spew("%s(), cannot convert number", __func__);
107 return SR_ERR_DATA;
108 }
109 dot_pos = strcspn(valstr, ".");
110 if (dot_pos < cnt)
111 *exponent = -(cnt - dot_pos - 1);
112 else
113 *exponent = 0;
114 sr_spew("%s(), display value is %f", __func__, *result);
115 return SR_OK;
116}
117
118/**
119 * Parse unit and flags from text buffer, bytes 7-14.
120 *
121 * The unit and flags optionally follow the number value for the
122 * measurement. Either can be present or absent. The scale factor
123 * is always at index 7. The unit starts at index 8, and is of
124 * variable length. Flags immediately follow the unit. The remainder
125 * of the text buffer is SPACE padded, and terminated with CR.
126 *
c2debda6
GS
127 * Notice the implementation detail of case @b sensitive comparison.
128 * Since the measurement unit and flags are directly adjacent and are
129 * not separated from each other, case insensitive comparison would
130 * yield wrong results. It's essential that e.g. "Vac" gets split into
131 * the "V" unit and the "ac" flag, not into "VA" and the unknown "c"
132 * flag!
133 *
134 * Notice, too, that order of comparison matters in the absence of
135 * separators or fixed positions and with ambiguous text (note that we do
136 * partial comparison). It's essential to e.g. correctly tell "VA" from "V".
4ba4d52a
GS
137 *
138 * @param[in] buf The text buffer received from the DMM.
139 * @param[out] info Broken down measurement details.
140 */
141static void parse_flags(const char *buf, struct asycii_info *info)
142{
143 int i, cnt;
144 char unit[8 + 1];
145 const char *u;
146
147 /* Bytes 0-6: Number value, see parse_value(). */
148
149 /* Strip spaces from bytes 7-14. */
150 cnt = 0;
151 for (i = 7; i < 15; i++) {
152 if (buf[i] != ' ')
153 unit[cnt++] = buf[i];
154 }
155 unit[cnt] = '\0';
156 u = &unit[0];
157 sr_spew("%s(): unit/flag buffer [%s]", __func__, u);
158
159 /* Scan for the scale factor. */
160 sr_spew("%s(): scanning factor, buffer [%s]", __func__, u);
161 if (*u == 'p') {
162 u++;
163 info->is_pico = TRUE;
164 } else if (*u == 'n') {
165 u++;
166 info->is_nano = TRUE;
167 } else if (*u == 'u') {
168 u++;
169 info->is_micro = TRUE;
170 } else if (*u == 'm') {
171 u++;
172 info->is_milli = TRUE;
173 } else if (*u == ' ') {
174 u++;
175 } else if (*u == 'k') {
176 u++;
177 info->is_kilo = TRUE;
178 } else if (*u == 'M') {
179 u++;
180 info->is_mega = TRUE;
181 } else {
182 /* Absence of a scale modifier is perfectly fine. */
183 }
184
185 /* Scan for the measurement unit. */
186 sr_spew("%s(): scanning unit, buffer [%s]", __func__, u);
187 if (strncmp(u, "A", strlen("A")) == 0) {
188 u += strlen("A");
189 info->is_ampere = TRUE;
190 } else if (strncmp(u, "VA", strlen("VA")) == 0) {
191 u += strlen("VA");
192 info->is_volt_ampere = TRUE;
193 } else if (strncmp(u, "V", strlen("V")) == 0) {
194 u += strlen("V");
195 info->is_volt = TRUE;
196 } else if (strncmp(u, "ohm", strlen("ohm")) == 0) {
197 u += strlen("ohm");
198 info->is_resistance = TRUE;
199 info->is_ohm = TRUE;
200 } else if (strncmp(u, "F", strlen("F")) == 0) {
201 u += strlen("F");
202 info->is_capacitance = TRUE;
203 info->is_farad = TRUE;
204 } else if (strncmp(u, "dB", strlen("dB")) == 0) {
205 u += strlen("dB");
206 info->is_gain = TRUE;
207 info->is_decibel = TRUE;
208 } else if (strncmp(u, "Hz", strlen("Hz")) == 0) {
209 u += strlen("Hz");
210 info->is_frequency = TRUE;
211 info->is_hertz = TRUE;
212 } else if (strncmp(u, "%", strlen("%")) == 0) {
213 u += strlen("%");
214 info->is_duty_cycle = TRUE;
215 if (*u == '+') {
216 u++;
217 info->is_duty_pos = TRUE;
218 } else if (*u == '-') {
219 u++;
220 info->is_duty_neg = TRUE;
221 } else {
222 info->is_invalid = TRUE;
223 }
224 } else if (strncmp(u, "Cnt", strlen("Cnt")) == 0) {
225 u += strlen("Cnt");
226 info->is_pulse_count = TRUE;
227 info->is_unitless = TRUE;
228 if (*u == '+') {
229 u++;
230 info->is_count_pos = TRUE;
231 } else if (*u == '-') {
232 u++;
233 info->is_count_neg = TRUE;
234 } else {
235 info->is_invalid = TRUE;
236 }
237 } else if (strncmp(u, "s", strlen("s")) == 0) {
238 u += strlen("s");
239 info->is_pulse_width = TRUE;
240 info->is_seconds = TRUE;
241 if (*u == '+') {
242 u++;
243 info->is_period_pos = TRUE;
244 } else if (*u == '-') {
245 u++;
246 info->is_period_neg = TRUE;
247 } else {
248 info->is_invalid = TRUE;
249 }
250 } else {
251 /* Not strictly illegal, but unknown/unsupported. */
252 sr_spew("%s(): measurement: unsupported", __func__);
253 info->is_invalid = TRUE;
254 }
255
256 /* Scan for additional flags. */
257 sr_spew("%s(): scanning flags, buffer [%s]", __func__, u);
258 if (strncmp(u, "ac+dc", strlen("ac+dc")) == 0) {
259 u += strlen("ac+dc");
260 info->is_ac_and_dc = TRUE;
261 } else if (strncmp(u, "ac", strlen("ac")) == 0) {
262 u += strlen("ac");
263 info->is_ac = TRUE;
264 } else if (strncmp(u, "dc", strlen("dc")) == 0) {
265 u += strlen("dc");
266 info->is_dc = TRUE;
267 } else if (strncmp(u, "d", strlen("d")) == 0) {
268 u += strlen("d");
269 info->is_diode = TRUE;
270 } else if (strncmp(u, "Pk", strlen("Pk")) == 0) {
271 u += strlen("Pk");
272 if (*u == '+') {
273 u++;
274 info->is_peak_max = TRUE;
275 } else if (*u == '-') {
276 u++;
277 info->is_peak_min = TRUE;
278 } else {
279 info->is_invalid = TRUE;
280 }
281 } else if (strcmp(u, "") == 0) {
282 /* Absence of any flags is acceptable. */
283 } else {
284 /* Presence of unknown flags is not. */
285 sr_dbg("%s(): flag: unknown", __func__);
286 info->is_invalid = TRUE;
287 }
288
289 /* Was all of the received data consumed? */
290 if (*u != '\0')
291 info->is_invalid = TRUE;
292
293 /*
294 * Note:
295 * - The protocol does not distinguish between "resistance"
296 * and "continuity".
297 * - Relative measurement and hold cannot get recognized.
298 */
299}
300
301/**
302 * Fill in a datafeed from previously parsed measurement details.
303 *
304 * @param[out] analog The datafeed which gets filled in.
305 * @param[in] floatval The number value of the measurement.
306 * @param[in] exponent Augments the number value.
307 * @param[in] info Scale and unit and other attributes.
308 */
309static void handle_flags(struct sr_datafeed_analog *analog, float *floatval,
310 int *exponent, const struct asycii_info *info)
311{
312 int factor = 0;
313
314 /* Factors */
315 if (info->is_pico)
316 factor -= 12;
317 if (info->is_nano)
318 factor -= 9;
319 if (info->is_micro)
320 factor -= 6;
321 if (info->is_milli)
322 factor -= 3;
323 if (info->is_kilo)
324 factor += 3;
325 if (info->is_mega)
326 factor += 6;
327 *floatval *= powf(10, factor);
328 *exponent += factor;
329
330 /* Measurement modes */
331 if (info->is_volt) {
332 analog->meaning->mq = SR_MQ_VOLTAGE;
333 analog->meaning->unit = SR_UNIT_VOLT;
334 }
335 if (info->is_volt_ampere) {
336 analog->meaning->mq = SR_MQ_POWER;
337 analog->meaning->unit = SR_UNIT_VOLT_AMPERE;
338 }
339 if (info->is_ampere) {
340 analog->meaning->mq = SR_MQ_CURRENT;
341 analog->meaning->unit = SR_UNIT_AMPERE;
342 }
343 if (info->is_frequency) {
344 analog->meaning->mq = SR_MQ_FREQUENCY;
345 analog->meaning->unit = SR_UNIT_HERTZ;
346 }
347 if (info->is_duty_cycle) {
348 analog->meaning->mq = SR_MQ_DUTY_CYCLE;
349 analog->meaning->unit = SR_UNIT_PERCENTAGE;
350 }
351 if (info->is_pulse_width) {
352 analog->meaning->mq = SR_MQ_PULSE_WIDTH;
353 analog->meaning->unit = SR_UNIT_SECOND;
354 }
355 if (info->is_pulse_count) {
356 analog->meaning->mq = SR_MQ_COUNT;
357 analog->meaning->unit = SR_UNIT_UNITLESS;
358 }
359 if (info->is_resistance) {
360 analog->meaning->mq = SR_MQ_RESISTANCE;
361 analog->meaning->unit = SR_UNIT_OHM;
362 }
363 if (info->is_capacitance) {
364 analog->meaning->mq = SR_MQ_CAPACITANCE;
365 analog->meaning->unit = SR_UNIT_FARAD;
366 }
367 if (info->is_diode) {
368 analog->meaning->mq = SR_MQ_VOLTAGE;
369 analog->meaning->unit = SR_UNIT_VOLT;
370 }
371 if (info->is_gain) {
372 analog->meaning->mq = SR_MQ_GAIN;
373 analog->meaning->unit = SR_UNIT_DECIBEL_VOLT;
374 }
375
376 /* Measurement related flags */
377 if (info->is_ac)
378 analog->meaning->mqflags |= SR_MQFLAG_AC;
379 if (info->is_ac_and_dc)
380 analog->meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_DC;
381 if (info->is_dc)
382 analog->meaning->mqflags |= SR_MQFLAG_DC;
383 if (info->is_diode)
384 analog->meaning->mqflags |= SR_MQFLAG_DIODE;
385 if (info->is_peak_max)
386 analog->meaning->mqflags |= SR_MQFLAG_MAX;
387 if (info->is_peak_min)
388 analog->meaning->mqflags |= SR_MQFLAG_MIN;
389}
390
391/**
392 * Check measurement details for consistency and validity.
393 *
394 * @param[in] info The previously parsed details.
395 *
396 * @return TRUE on success, FALSE otherwise.
397 */
398static gboolean flags_valid(const struct asycii_info *info)
399{
400 int count;
401
402 /* Have previous checks raised the "invalid" flag? */
403 if (info->is_invalid) {
404 sr_dbg("Previous parse raised \"invalid\" flag for packet.");
405 return FALSE;
406 }
407
408 /* Does the packet have more than one multiplier? */
409 count = 0;
410 count += (info->is_pico) ? 1 : 0;
411 count += (info->is_nano) ? 1 : 0;
412 count += (info->is_micro) ? 1 : 0;
413 count += (info->is_milli) ? 1 : 0;
414 count += (info->is_kilo) ? 1 : 0;
415 count += (info->is_mega) ? 1 : 0;
416 if (count > 1) {
417 sr_dbg("More than one multiplier detected in packet.");
418 return FALSE;
419 }
420
421 /* Does the packet "measure" more than one type of value? */
422 count = 0;
423 count += (info->is_volt || info->is_diode) ? 1 : 0;
424 count += (info->is_volt_ampere) ? 1 : 0;
425 count += (info->is_ampere) ? 1 : 0;
426 count += (info->is_gain) ? 1 : 0;
427 count += (info->is_resistance) ? 1 : 0;
428 count += (info->is_capacitance) ? 1 : 0;
429 count += (info->is_frequency) ? 1 : 0;
430 count += (info->is_duty_cycle) ? 1 : 0;
431 count += (info->is_pulse_width) ? 1 : 0;
432 count += (info->is_pulse_count) ? 1 : 0;
433 if (count > 1) {
434 sr_dbg("More than one measurement type detected in packet.");
435 return FALSE;
436 }
437
438 /* Are conflicting AC and DC flags set? */
439 count = 0;
440 count += (info->is_ac) ? 1 : 0;
441 count += (info->is_ac_and_dc) ? 1 : 0;
442 count += (info->is_dc) ? 1 : 0;
443 if (count > 1) {
444 sr_dbg("Conflicting AC and DC flags detected in packet.");
445 return FALSE;
446 }
447
448 return TRUE;
449}
450
451#ifdef HAVE_LIBSERIALPORT
452/**
453 * Arrange for the reception of another measurement from the DMM.
454 *
455 * This routine is unused in the currently implemented PRINT mode,
456 * where the meter sends measurements to the PC in pre-set intervals,
457 * without the PC's intervention.
458 *
459 * @param[in] serial The serial connection.
460 *
461 * @private
462 */
463SR_PRIV int sr_asycii_packet_request(struct sr_serial_dev_inst *serial)
464{
465 /*
466 * The current implementation assumes that the user pressed
467 * the PRINT button. It has no support to query/trigger packet
468 * reception from the meter.
469 */
470 (void)serial;
471 sr_spew("NOT requesting DMM packet.");
472 return SR_OK;
473}
474#endif
475
476/**
477 * Check whether a received frame is valid.
478 *
479 * @param[in] buf The text buffer with received data.
480 *
481 * @return TRUE upon success, FALSE otherwise.
482 */
483SR_PRIV gboolean sr_asycii_packet_valid(const uint8_t *buf)
484{
485 struct asycii_info info;
486
487 /* First check whether we are in sync with the packet stream. */
488 if (buf[15] != '\r')
489 return FALSE;
490
491 /* Have the received packet content parsed. */
492 memset(&info, 0x00, sizeof(info));
493 parse_flags((const char *)buf, &info);
494 if (!flags_valid(&info))
495 return FALSE;
496
497 return TRUE;
498}
499
500/**
501 * Parse a protocol packet.
502 *
503 * @param[in] buf Buffer containing the protocol packet. Must not be NULL.
504 * @param[out] floatval Pointer to a float variable. That variable will
505 * be modified in-place depending on the protocol packet.
506 * Must not be NULL.
507 * @param[out] analog Pointer to a struct sr_datafeed_analog. The struct
508 * will be filled with data according to the protocol packet.
509 * Must not be NULL.
510 * @param[out] info Pointer to a struct asycii_info. The struct will be
511 * filled with data according to the protocol packet. Must
512 * not be NULL.
513 *
514 * @return SR_OK upon success, SR_ERR upon failure. Upon errors, the
515 * 'analog' variable contents are undefined and should not
516 * be used.
517 */
518SR_PRIV int sr_asycii_parse(const uint8_t *buf, float *floatval,
519 struct sr_datafeed_analog *analog, void *info)
520{
521 int ret, exponent;
522 struct asycii_info *info_local;
523
524 info_local = (struct asycii_info *)info;
525
526 /* Don't print byte 15. That one contains the carriage return. */
527 sr_dbg("DMM packet: \"%.15s\"", buf);
528
529 memset(info_local, 0x00, sizeof(*info_local));
530
531 exponent = 0;
532 ret = parse_value((const char *)buf, info_local, floatval, &exponent);
533 if (ret != SR_OK) {
534 sr_dbg("Error parsing value: %d.", ret);
535 return ret;
536 }
537
538 parse_flags((const char *)buf, info_local);
539 handle_flags(analog, floatval, &exponent, info_local);
540
541 analog->encoding->digits = -exponent;
542 analog->spec->spec_digits = -exponent;
543
544 return SR_OK;
545}