]> sigrok.org Git - libsigrok.git/blob - src/dmm/asycii.c
output/csv: use intermediate time_t var, silence compiler warning
[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;
64         const char *dot_pos;
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.
100          */
101         if (sr_atof_ascii(valp, result) != SR_OK) {
102                 info->is_invalid = TRUE;
103                 sr_spew("%s(), cannot convert number", __func__);
104                 return SR_ERR_DATA;
105         }
106         dot_pos = g_strstr_len(valstr, -1, ".");
107         if (dot_pos)
108                 *exponent = -(valstr + strlen(valstr) - dot_pos - 1);
109         else
110                 *exponent = 0;
111         sr_spew("%s(), display value is %f, exponent %d",
112                 __func__, *result, *exponent);
113         return SR_OK;
114 }
115
116 /**
117  * Parse unit and flags from text buffer, bytes 7-14.
118  *
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.
124  *
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"
130  * flag!
131  *
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".
135  *
136  * @param[in]   buf The text buffer received from the DMM.
137  * @param[out]  info Broken down measurement details.
138  */
139 static void parse_flags(const char *buf, struct asycii_info *info)
140 {
141         int i, cnt;
142         char unit[8 + 1];
143         const char *u;
144
145         /* Bytes 0-6: Number value, see parse_value(). */
146
147         /* Strip spaces from bytes 7-14. */
148         cnt = 0;
149         for (i = 7; i < 15; i++) {
150                 if (buf[i] != ' ')
151                         unit[cnt++] = buf[i];
152         }
153         unit[cnt] = '\0';
154         u = &unit[0];
155         sr_spew("%s(): unit/flag buffer [%s]", __func__, u);
156
157         /* Scan for the scale factor. */
158         sr_spew("%s(): scanning factor, buffer [%s]", __func__, u);
159         if (*u == 'p') {
160                 u++;
161                 info->is_pico = TRUE;
162         } else if (*u == 'n') {
163                 u++;
164                 info->is_nano = TRUE;
165         } else if (*u == 'u') {
166                 u++;
167                 info->is_micro = TRUE;
168         } else if (*u == 'm') {
169                 u++;
170                 info->is_milli = TRUE;
171         } else if (*u == ' ') {
172                 u++;
173         } else if (*u == 'k') {
174                 u++;
175                 info->is_kilo = TRUE;
176         } else if (*u == 'M') {
177                 u++;
178                 info->is_mega = TRUE;
179         } else {
180                 /* Absence of a scale modifier is perfectly fine. */
181         }
182
183         /* Scan for the measurement unit. */
184         sr_spew("%s(): scanning unit, buffer [%s]", __func__, u);
185         if (g_str_has_prefix(u, "A")) {
186                 u += strlen("A");
187                 info->is_ampere = TRUE;
188         } else if (g_str_has_prefix(u, "VA")) {
189                 u += strlen("VA");
190                 info->is_volt_ampere = TRUE;
191         } else if (g_str_has_prefix(u, "V")) {
192                 u += strlen("V");
193                 info->is_volt = TRUE;
194         } else if (g_str_has_prefix(u, "ohm")) {
195                 u += strlen("ohm");
196                 info->is_resistance = TRUE;
197                 info->is_ohm = TRUE;
198         } else if (g_str_has_prefix(u, "F")) {
199                 u += strlen("F");
200                 info->is_capacitance = TRUE;
201                 info->is_farad = TRUE;
202         } else if (g_str_has_prefix(u, "dB")) {
203                 u += strlen("dB");
204                 info->is_gain = TRUE;
205                 info->is_decibel = TRUE;
206         } else if (g_str_has_prefix(u, "Hz")) {
207                 u += strlen("Hz");
208                 info->is_frequency = TRUE;
209                 info->is_hertz = TRUE;
210         } else if (g_str_has_prefix(u, "%")) {
211                 u += strlen("%");
212                 info->is_duty_cycle = TRUE;
213                 if (*u == '+') {
214                         u++;
215                         info->is_duty_pos = TRUE;
216                 } else if (*u == '-') {
217                         u++;
218                         info->is_duty_neg = TRUE;
219                 } else {
220                         info->is_invalid = TRUE;
221                 }
222         } else if (g_str_has_prefix(u, "Cnt")) {
223                 u += strlen("Cnt");
224                 info->is_pulse_count = TRUE;
225                 info->is_unitless = TRUE;
226                 if (*u == '+') {
227                         u++;
228                         info->is_count_pos = TRUE;
229                 } else if (*u == '-') {
230                         u++;
231                         info->is_count_neg = TRUE;
232                 } else {
233                         info->is_invalid = TRUE;
234                 }
235         } else if (g_str_has_prefix(u, "s")) {
236                 u += strlen("s");
237                 info->is_pulse_width = TRUE;
238                 info->is_seconds = TRUE;
239                 if (*u == '+') {
240                         u++;
241                         info->is_period_pos = TRUE;
242                 } else if (*u == '-') {
243                         u++;
244                         info->is_period_neg = TRUE;
245                 } else {
246                         info->is_invalid = TRUE;
247                 }
248         } else {
249                 /* Not strictly illegal, but unknown/unsupported. */
250                 sr_spew("%s(): measurement: unsupported", __func__);
251                 info->is_invalid = TRUE;
252         }
253
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")) {
260                 u += strlen("ac");
261                 info->is_ac = TRUE;
262         } else if (g_str_has_prefix(u, "dc")) {
263                 u += strlen("dc");
264                 info->is_dc = TRUE;
265         } else if (g_str_has_prefix(u, "d")) {
266                 u += strlen("d");
267                 info->is_diode = TRUE;
268         } else if (g_str_has_prefix(u, "Pk")) {
269                 u += strlen("Pk");
270                 if (*u == '+') {
271                         u++;
272                         info->is_peak_max = TRUE;
273                 } else if (*u == '-') {
274                         u++;
275                         info->is_peak_min = TRUE;
276                 } else {
277                         info->is_invalid = TRUE;
278                 }
279         } else if (*u == '\0') {
280                 /* Absence of any flags is acceptable. */
281         } else {
282                 /* Presence of unknown flags is not. */
283                 sr_dbg("%s(): flag: unknown", __func__);
284                 info->is_invalid = TRUE;
285         }
286
287         /* Was all of the received data consumed? */
288         if (*u != '\0')
289                 info->is_invalid = TRUE;
290
291         /*
292          * Note:
293          * - The protocol does not distinguish between "resistance"
294          *   and "continuity".
295          * - Relative measurement and hold cannot get recognized.
296          */
297 }
298
299 /**
300  * Fill in a datafeed from previously parsed measurement details.
301  *
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.
306  */
307 static void handle_flags(struct sr_datafeed_analog *analog, float *floatval,
308                          int *exponent, const struct asycii_info *info)
309 {
310         int factor = 0;
311
312         /* Factors */
313         if (info->is_pico)
314                 factor -= 12;
315         if (info->is_nano)
316                 factor -= 9;
317         if (info->is_micro)
318                 factor -= 6;
319         if (info->is_milli)
320                 factor -= 3;
321         if (info->is_kilo)
322                 factor += 3;
323         if (info->is_mega)
324                 factor += 6;
325         *floatval *= powf(10, factor);
326         *exponent += factor;
327
328         /* Measurement modes */
329         if (info->is_volt) {
330                 analog->meaning->mq = SR_MQ_VOLTAGE;
331                 analog->meaning->unit = SR_UNIT_VOLT;
332         }
333         if (info->is_volt_ampere) {
334                 analog->meaning->mq = SR_MQ_POWER;
335                 analog->meaning->unit = SR_UNIT_VOLT_AMPERE;
336         }
337         if (info->is_ampere) {
338                 analog->meaning->mq = SR_MQ_CURRENT;
339                 analog->meaning->unit = SR_UNIT_AMPERE;
340         }
341         if (info->is_frequency) {
342                 analog->meaning->mq = SR_MQ_FREQUENCY;
343                 analog->meaning->unit = SR_UNIT_HERTZ;
344         }
345         if (info->is_duty_cycle) {
346                 analog->meaning->mq = SR_MQ_DUTY_CYCLE;
347                 analog->meaning->unit = SR_UNIT_PERCENTAGE;
348         }
349         if (info->is_pulse_width) {
350                 analog->meaning->mq = SR_MQ_PULSE_WIDTH;
351                 analog->meaning->unit = SR_UNIT_SECOND;
352         }
353         if (info->is_pulse_count) {
354                 analog->meaning->mq = SR_MQ_COUNT;
355                 analog->meaning->unit = SR_UNIT_UNITLESS;
356         }
357         if (info->is_resistance) {
358                 analog->meaning->mq = SR_MQ_RESISTANCE;
359                 analog->meaning->unit = SR_UNIT_OHM;
360         }
361         if (info->is_capacitance) {
362                 analog->meaning->mq = SR_MQ_CAPACITANCE;
363                 analog->meaning->unit = SR_UNIT_FARAD;
364         }
365         if (info->is_diode) {
366                 analog->meaning->mq = SR_MQ_VOLTAGE;
367                 analog->meaning->unit = SR_UNIT_VOLT;
368         }
369         if (info->is_gain) {
370                 analog->meaning->mq = SR_MQ_GAIN;
371                 analog->meaning->unit = SR_UNIT_DECIBEL_VOLT;
372         }
373
374         /* Measurement related flags */
375         if (info->is_ac)
376                 analog->meaning->mqflags |= SR_MQFLAG_AC;
377         if (info->is_ac_and_dc)
378                 analog->meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_DC;
379         if (info->is_dc)
380                 analog->meaning->mqflags |= SR_MQFLAG_DC;
381         if (info->is_diode)
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;
387 }
388
389 /**
390  * Check measurement details for consistency and validity.
391  *
392  * @param[in]   info The previously parsed details.
393  *
394  * @return      TRUE on success, FALSE otherwise.
395  */
396 static gboolean flags_valid(const struct asycii_info *info)
397 {
398         int count;
399
400         /* Have previous checks raised the "invalid" flag? */
401         if (info->is_invalid) {
402                 sr_dbg("Previous parse raised \"invalid\" flag for packet.");
403                 return FALSE;
404         }
405
406         /* Does the packet have more than one multiplier? */
407         count = 0;
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;
414         if (count > 1) {
415                 sr_dbg("More than one multiplier detected in packet.");
416                 return FALSE;
417         }
418
419         /* Does the packet "measure" more than one type of value? */
420         count = 0;
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;
431         if (count > 1) {
432                 sr_dbg("More than one measurement type detected in packet.");
433                 return FALSE;
434         }
435
436         /* Are conflicting AC and DC flags set? */
437         count = 0;
438         count += (info->is_ac) ? 1 : 0;
439         count += (info->is_ac_and_dc) ? 1 : 0;
440         count += (info->is_dc) ? 1 : 0;
441         if (count > 1) {
442                 sr_dbg("Conflicting AC and DC flags detected in packet.");
443                 return FALSE;
444         }
445
446         return TRUE;
447 }
448
449 #ifdef HAVE_SERIAL_COMM
450 /**
451  * Arrange for the reception of another measurement from the DMM.
452  *
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.
456  *
457  * @param[in]   serial The serial connection.
458  */
459 SR_PRIV int sr_asycii_packet_request(struct sr_serial_dev_inst *serial)
460 {
461         /*
462          * The current implementation assumes that the user pressed
463          * the PRINT button. It has no support to query/trigger packet
464          * reception from the meter.
465          */
466         (void)serial;
467         sr_spew("NOT requesting DMM packet.");
468         return SR_OK;
469 }
470 #endif
471
472 /**
473  * Check whether a received frame is valid.
474  *
475  * @param[in]   buf The text buffer with received data.
476  *
477  * @return      TRUE upon success, FALSE otherwise.
478  */
479 SR_PRIV gboolean sr_asycii_packet_valid(const uint8_t *buf)
480 {
481         struct asycii_info info;
482
483         /* First check whether we are in sync with the packet stream. */
484         if (buf[15] != '\r')
485                 return FALSE;
486
487         /* Have the received packet content parsed. */
488         memset(&info, 0x00, sizeof(info));
489         parse_flags((const char *)buf, &info);
490         if (!flags_valid(&info))
491                 return FALSE;
492
493         return TRUE;
494 }
495
496 /**
497  * Parse a protocol packet.
498  *
499  * @param[in]   buf Buffer containing the protocol packet. Must not be NULL.
500  * @param[out]  floatval Pointer to a float variable. That variable will
501  *              be modified in-place depending on the protocol packet.
502  *              Must not be NULL.
503  * @param[out]  analog Pointer to a struct sr_datafeed_analog. The struct
504  *              will be filled with data according to the protocol packet.
505  *              Must not be NULL.
506  * @param[out]  info Pointer to a struct asycii_info. The struct will be
507  *              filled with data according to the protocol packet. Must
508  *              not be NULL.
509  *
510  * @return      SR_OK upon success, SR_ERR upon failure. Upon errors, the
511  *              'analog' variable contents are undefined and should not
512  *              be used.
513  */
514 SR_PRIV int sr_asycii_parse(const uint8_t *buf, float *floatval,
515                             struct sr_datafeed_analog *analog, void *info)
516 {
517         int ret, exponent;
518         struct asycii_info *info_local;
519
520         info_local = info;
521
522         /* Don't print byte 15. That one contains the carriage return. */
523         sr_dbg("DMM packet: \"%.15s\"", buf);
524
525         memset(info_local, 0x00, sizeof(*info_local));
526
527         exponent = 0;
528         ret = parse_value((const char *)buf, info_local, floatval, &exponent);
529         if (ret != SR_OK) {
530                 sr_dbg("Error parsing value: %d.", ret);
531                 return ret;
532         }
533
534         parse_flags((const char *)buf, info_local);
535         handle_flags(analog, floatval, &exponent, info_local);
536
537         analog->encoding->digits = -exponent;
538         analog->spec->spec_digits = -exponent;
539
540         return SR_OK;
541 }