]> sigrok.org Git - libsigrok.git/blob - src/dmm/asycii.c
asyc-ii: Unobfuscate a comment on packet parse constraints
[libsigrok.git] / src / dmm / asycii.c
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  */
58 static 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  *
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".
137  *
138  * @param[in]   buf The text buffer received from the DMM.
139  * @param[out]  info Broken down measurement details.
140  */
141 static 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  */
309 static 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  */
398 static 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  */
463 SR_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  */
483 SR_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  */
518 SR_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 }