]> sigrok.org Git - libsigrok.git/blob - src/dmm/asycii.c
dmm: introduce support for ASYC-II 16-byte protocol in PRINT mode
[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 *sensitive* comparison.
128  * This would break correct operation. It's essential that e.g. "Vac"
129  * gets split into the "V" unit and the "ac" flag, not into "VA" and
130  * the unknown "c" flag! In the absence of separators or fixed
131  * positions and with ambiguous text (when abbreviated), order of
132  * comparison matters, too.
133  *
134  * @param[in]   buf The text buffer received from the DMM.
135  * @param[out]  info Broken down measurement details.
136  */
137 static void parse_flags(const char *buf, struct asycii_info *info)
138 {
139         int i, cnt;
140         char unit[8 + 1];
141         const char *u;
142
143         /* Bytes 0-6: Number value, see parse_value(). */
144
145         /* Strip spaces from bytes 7-14. */
146         cnt = 0;
147         for (i = 7; i < 15; i++) {
148                 if (buf[i] != ' ')
149                         unit[cnt++] = buf[i];
150         }
151         unit[cnt] = '\0';
152         u = &unit[0];
153         sr_spew("%s(): unit/flag buffer [%s]", __func__, u);
154
155         /* Scan for the scale factor. */
156         sr_spew("%s(): scanning factor, buffer [%s]", __func__, u);
157         if (*u == 'p') {
158                 u++;
159                 info->is_pico = TRUE;
160         } else if (*u == 'n') {
161                 u++;
162                 info->is_nano = TRUE;
163         } else if (*u == 'u') {
164                 u++;
165                 info->is_micro = TRUE;
166         } else if (*u == 'm') {
167                 u++;
168                 info->is_milli = TRUE;
169         } else if (*u == ' ') {
170                 u++;
171         } else if (*u == 'k') {
172                 u++;
173                 info->is_kilo = TRUE;
174         } else if (*u == 'M') {
175                 u++;
176                 info->is_mega = TRUE;
177         } else {
178                 /* Absence of a scale modifier is perfectly fine. */
179         }
180
181         /* Scan for the measurement unit. */
182         sr_spew("%s(): scanning unit, buffer [%s]", __func__, u);
183         if (strncmp(u, "A", strlen("A")) == 0) {
184                 u += strlen("A");
185                 info->is_ampere = TRUE;
186         } else if (strncmp(u, "VA", strlen("VA")) == 0) {
187                 u += strlen("VA");
188                 info->is_volt_ampere = TRUE;
189         } else if (strncmp(u, "V", strlen("V")) == 0) {
190                 u += strlen("V");
191                 info->is_volt = TRUE;
192         } else if (strncmp(u, "ohm", strlen("ohm")) == 0) {
193                 u += strlen("ohm");
194                 info->is_resistance = TRUE;
195                 info->is_ohm = TRUE;
196         } else if (strncmp(u, "F", strlen("F")) == 0) {
197                 u += strlen("F");
198                 info->is_capacitance = TRUE;
199                 info->is_farad = TRUE;
200         } else if (strncmp(u, "dB", strlen("dB")) == 0) {
201                 u += strlen("dB");
202                 info->is_gain = TRUE;
203                 info->is_decibel = TRUE;
204         } else if (strncmp(u, "Hz", strlen("Hz")) == 0) {
205                 u += strlen("Hz");
206                 info->is_frequency = TRUE;
207                 info->is_hertz = TRUE;
208         } else if (strncmp(u, "%", strlen("%")) == 0) {
209                 u += strlen("%");
210                 info->is_duty_cycle = TRUE;
211                 if (*u == '+') {
212                         u++;
213                         info->is_duty_pos = TRUE;
214                 } else if (*u == '-') {
215                         u++;
216                         info->is_duty_neg = TRUE;
217                 } else {
218                         info->is_invalid = TRUE;
219                 }
220         } else if (strncmp(u, "Cnt", strlen("Cnt")) == 0) {
221                 u += strlen("Cnt");
222                 info->is_pulse_count = TRUE;
223                 info->is_unitless = TRUE;
224                 if (*u == '+') {
225                         u++;
226                         info->is_count_pos = TRUE;
227                 } else if (*u == '-') {
228                         u++;
229                         info->is_count_neg = TRUE;
230                 } else {
231                         info->is_invalid = TRUE;
232                 }
233         } else if (strncmp(u, "s", strlen("s")) == 0) {
234                 u += strlen("s");
235                 info->is_pulse_width = TRUE;
236                 info->is_seconds = TRUE;
237                 if (*u == '+') {
238                         u++;
239                         info->is_period_pos = TRUE;
240                 } else if (*u == '-') {
241                         u++;
242                         info->is_period_neg = TRUE;
243                 } else {
244                         info->is_invalid = TRUE;
245                 }
246         } else {
247                 /* Not strictly illegal, but unknown/unsupported. */
248                 sr_spew("%s(): measurement: unsupported", __func__);
249                 info->is_invalid = TRUE;
250         }
251
252         /* Scan for additional flags. */
253         sr_spew("%s(): scanning flags, buffer [%s]", __func__, u);
254         if (strncmp(u, "ac+dc", strlen("ac+dc")) == 0) {
255                 u += strlen("ac+dc");
256                 info->is_ac_and_dc = TRUE;
257         } else if (strncmp(u, "ac", strlen("ac")) == 0) {
258                 u += strlen("ac");
259                 info->is_ac = TRUE;
260         } else if (strncmp(u, "dc", strlen("dc")) == 0) {
261                 u += strlen("dc");
262                 info->is_dc = TRUE;
263         } else if (strncmp(u, "d", strlen("d")) == 0) {
264                 u += strlen("d");
265                 info->is_diode = TRUE;
266         } else if (strncmp(u, "Pk", strlen("Pk")) == 0) {
267                 u += strlen("Pk");
268                 if (*u == '+') {
269                         u++;
270                         info->is_peak_max = TRUE;
271                 } else if (*u == '-') {
272                         u++;
273                         info->is_peak_min = TRUE;
274                 } else {
275                         info->is_invalid = TRUE;
276                 }
277         } else if (strcmp(u, "") == 0) {
278                 /* Absence of any flags is acceptable. */
279         } else {
280                 /* Presence of unknown flags is not. */
281                 sr_dbg("%s(): flag: unknown", __func__);
282                 info->is_invalid = TRUE;
283         }
284
285         /* Was all of the received data consumed? */
286         if (*u != '\0')
287                 info->is_invalid = TRUE;
288
289         /*
290          * Note:
291          * - The protocol does not distinguish between "resistance"
292          *   and "continuity".
293          * - Relative measurement and hold cannot get recognized.
294          */
295 }
296
297 /**
298  * Fill in a datafeed from previously parsed measurement details.
299  *
300  * @param[out]  analog The datafeed which gets filled in.
301  * @param[in]   floatval The number value of the measurement.
302  * @param[in]   exponent Augments the number value.
303  * @param[in]   info Scale and unit and other attributes.
304  */
305 static void handle_flags(struct sr_datafeed_analog *analog, float *floatval,
306                          int *exponent, const struct asycii_info *info)
307 {
308         int factor = 0;
309
310         /* Factors */
311         if (info->is_pico)
312                 factor -= 12;
313         if (info->is_nano)
314                 factor -= 9;
315         if (info->is_micro)
316                 factor -= 6;
317         if (info->is_milli)
318                 factor -= 3;
319         if (info->is_kilo)
320                 factor += 3;
321         if (info->is_mega)
322                 factor += 6;
323         *floatval *= powf(10, factor);
324         *exponent += factor;
325
326         /* Measurement modes */
327         if (info->is_volt) {
328                 analog->meaning->mq = SR_MQ_VOLTAGE;
329                 analog->meaning->unit = SR_UNIT_VOLT;
330         }
331         if (info->is_volt_ampere) {
332                 analog->meaning->mq = SR_MQ_POWER;
333                 analog->meaning->unit = SR_UNIT_VOLT_AMPERE;
334         }
335         if (info->is_ampere) {
336                 analog->meaning->mq = SR_MQ_CURRENT;
337                 analog->meaning->unit = SR_UNIT_AMPERE;
338         }
339         if (info->is_frequency) {
340                 analog->meaning->mq = SR_MQ_FREQUENCY;
341                 analog->meaning->unit = SR_UNIT_HERTZ;
342         }
343         if (info->is_duty_cycle) {
344                 analog->meaning->mq = SR_MQ_DUTY_CYCLE;
345                 analog->meaning->unit = SR_UNIT_PERCENTAGE;
346         }
347         if (info->is_pulse_width) {
348                 analog->meaning->mq = SR_MQ_PULSE_WIDTH;
349                 analog->meaning->unit = SR_UNIT_SECOND;
350         }
351         if (info->is_pulse_count) {
352                 analog->meaning->mq = SR_MQ_COUNT;
353                 analog->meaning->unit = SR_UNIT_UNITLESS;
354         }
355         if (info->is_resistance) {
356                 analog->meaning->mq = SR_MQ_RESISTANCE;
357                 analog->meaning->unit = SR_UNIT_OHM;
358         }
359         if (info->is_capacitance) {
360                 analog->meaning->mq = SR_MQ_CAPACITANCE;
361                 analog->meaning->unit = SR_UNIT_FARAD;
362         }
363         if (info->is_diode) {
364                 analog->meaning->mq = SR_MQ_VOLTAGE;
365                 analog->meaning->unit = SR_UNIT_VOLT;
366         }
367         if (info->is_gain) {
368                 analog->meaning->mq = SR_MQ_GAIN;
369                 analog->meaning->unit = SR_UNIT_DECIBEL_VOLT;
370         }
371
372         /* Measurement related flags */
373         if (info->is_ac)
374                 analog->meaning->mqflags |= SR_MQFLAG_AC;
375         if (info->is_ac_and_dc)
376                 analog->meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_DC;
377         if (info->is_dc)
378                 analog->meaning->mqflags |= SR_MQFLAG_DC;
379         if (info->is_diode)
380                 analog->meaning->mqflags |= SR_MQFLAG_DIODE;
381         if (info->is_peak_max)
382                 analog->meaning->mqflags |= SR_MQFLAG_MAX;
383         if (info->is_peak_min)
384                 analog->meaning->mqflags |= SR_MQFLAG_MIN;
385 }
386
387 /**
388  * Check measurement details for consistency and validity.
389  *
390  * @param[in]   info The previously parsed details.
391  *
392  * @return      TRUE on success, FALSE otherwise.
393  */
394 static gboolean flags_valid(const struct asycii_info *info)
395 {
396         int count;
397
398         /* Have previous checks raised the "invalid" flag? */
399         if (info->is_invalid) {
400                 sr_dbg("Previous parse raised \"invalid\" flag for packet.");
401                 return FALSE;
402         }
403
404         /* Does the packet have more than one multiplier? */
405         count = 0;
406         count += (info->is_pico) ? 1 : 0;
407         count += (info->is_nano) ? 1 : 0;
408         count += (info->is_micro) ? 1 : 0;
409         count += (info->is_milli) ? 1 : 0;
410         count += (info->is_kilo) ? 1 : 0;
411         count += (info->is_mega) ? 1 : 0;
412         if (count > 1) {
413                 sr_dbg("More than one multiplier detected in packet.");
414                 return FALSE;
415         }
416
417         /* Does the packet "measure" more than one type of value? */
418         count = 0;
419         count += (info->is_volt || info->is_diode) ? 1 : 0;
420         count += (info->is_volt_ampere) ? 1 : 0;
421         count += (info->is_ampere) ? 1 : 0;
422         count += (info->is_gain) ? 1 : 0;
423         count += (info->is_resistance) ? 1 : 0;
424         count += (info->is_capacitance) ? 1 : 0;
425         count += (info->is_frequency) ? 1 : 0;
426         count += (info->is_duty_cycle) ? 1 : 0;
427         count += (info->is_pulse_width) ? 1 : 0;
428         count += (info->is_pulse_count) ? 1 : 0;
429         if (count > 1) {
430                 sr_dbg("More than one measurement type detected in packet.");
431                 return FALSE;
432         }
433
434         /* Are conflicting AC and DC flags set? */
435         count = 0;
436         count += (info->is_ac) ? 1 : 0;
437         count += (info->is_ac_and_dc) ? 1 : 0;
438         count += (info->is_dc) ? 1 : 0;
439         if (count > 1) {
440                 sr_dbg("Conflicting AC and DC flags detected in packet.");
441                 return FALSE;
442         }
443
444         return TRUE;
445 }
446
447 #ifdef HAVE_LIBSERIALPORT
448 /**
449  * Arrange for the reception of another measurement from the DMM.
450  *
451  * This routine is unused in the currently implemented PRINT mode,
452  * where the meter sends measurements to the PC in pre-set intervals,
453  * without the PC's intervention.
454  *
455  * @param[in]   serial The serial connection.
456  *
457  * @private
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 = (struct asycii_info *)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 }