]> sigrok.org Git - libsigrok.git/blame - src/dmm/bm52x.c
dmm/metex14: unbreak packet request helper return code
[libsigrok.git] / src / dmm / bm52x.c
CommitLineData
400bc4ff
GS
1/*
2 * This file is part of the libsigrok project.
3 *
4 * Copyright (C) 2014 Aurelien Jacobs <aurel@gnuage.org>
5 * Copyright (C) 2019-2020 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 3 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 * @file
23 *
24 * Brymen BM52x serial protocol parser. The USB protocol (for the cable)
25 * and the packet description (for the meter) were retrieved from:
26 * http://brymen.com/product-html/Download2.html
27 * http://brymen.com/product-html/PD02BM520s_protocolDL.html
28 * http://brymen.com/product-html/images/DownloadList/ProtocolList/BM520-BM520s_List/BM520-BM520s-10000-count-professional-dual-display-mobile-logging-DMMs-protocol.zip
29 */
30
31/*
32 * TODO
33 * - This DMM packet parser exclusively supports live readings (vendor
34 * documentation refers to it as "real-time download" aka RTD). A HID
35 * report is sent which results in three HID reports in response which
36 * carry 24 bytes with LCD indicator bitfields (and a few literals to
37 * synchronize to the byte stream). Reading back previous recordings
38 * ("memory data sets" in the vendor documentation) involve different
39 * types of requests, and several of them, and result in a different
40 * number of response reports while their interpretation differs, too,
41 * of course. None of this fits the serial-dmm approach, and needs to
42 * get addressed later.
43 * - Configurable sample rate, range 20/s rate up to 600s period.
44 * - Multiple sessions, one function per session, up to 999 "session
45 * pages" (recordings with their sequence of measurement values).
46 * - Up to 87000 (single display) or 43500 (dual display) measurements
47 * total on BM525s.
48 * - Request 0x00, 0x00, 0x52, 0x88 to request the HEAD of recordings.
49 * Request 0x00, 0x00, 0x52, 0x89 to request the NEXT memory chunk.
50 * Request 0x00, 0x00, 0x52, 0x8a to re-request the CURR memory chunk
51 * (repetition when transmission failed detectably?).
52 * - All these HID report requests result in four HID responses which
53 * carry 32 bytes (24 bytes of payload data, and a checksum) where
54 * application's fields can cross the boundary of HID reports and
55 * even response chunks.
56 * - Some of the meter's functions and indications cannot get expressed
57 * by means of sigrok MQ and flags terms. Some indicator's meaning is
58 * unknown or uncertain, and thus their state is not evaluated.
59 * - MAX-MIN, the span between extreme values, referred to as Vp-p.
60 * - AVG is not available in BM525s and BM521s.
61 * - LoZ, eliminating ghost voltages.
62 * - LPF, low pass filter.
63 * - dBm is a BM829s feature only, not available in BM525s.
64 * - low battery, emits sr_warn() but isn't seen in the feed.
65 * - @, 4-20mA loop, % (main display, left hand side), Hi/Lo. Some of
66 * these are in the vendor's documentation for the DMM packet but not
67 * supported by the BM525s device which motivated the creation of the
68 * parser's and was used to test its operation.
69 * - It's a guess that the many undocumented bits (44 of them) are
70 * related to the bargraph (40 ticks, overflow, sign, 6/10 scale).
71 * - Should T1-T2 have a delta ("relative") decoration? But the meter's
72 * "relative" feature is flexible, accepts any display value as the
73 * reference, including min/max/diff when displayed upon activation.
74 * - The "beep jack" displays "InEr" in the secondary display. This is
75 * not caught here, no PC side message gets emitted.
76 */
77
78#include <config.h>
79#include <libsigrok/libsigrok.h>
80#include "libsigrok-internal.h"
81#include <math.h>
82#include <string.h>
83
84#define LOG_PREFIX "brymen-bm52x"
85
86#ifdef HAVE_SERIAL_COMM
87SR_PRIV int sr_brymen_bm52x_packet_request(struct sr_serial_dev_inst *serial)
88{
89 static const uint8_t request[] = { 0x00, 0x00, 0x52, 0x66, };
90
91 serial_write_nonblocking(serial, request, sizeof(request));
92
93 return SR_OK;
94}
95#endif
96
97SR_PRIV gboolean sr_brymen_bm52x_packet_valid(const uint8_t *buf)
98{
99 if (buf[16] != 0x52)
100 return FALSE;
101 if (buf[17] != 0x52)
102 return FALSE;
103 if (buf[18] != 0x52)
104 return FALSE;
105 if (buf[19] != 0x52)
106 return FALSE;
107
108 return TRUE;
109}
110
111/*
112 * Data bytes in the DMM packet encode LCD segments in an unusual order
113 * (bgcpafed) and in an unusual position (bit 4 being the decimal point
114 * for some digits, an additional indicator for others). Fortunately all
115 * eight digits encode their segments in identical ways across the bytes.
116 *
117 * These routines convert LCD segments to characters, and a section of the
118 * DMM packet (which corresponds to the primary or secondary display) to
119 * the text representation of the measurement's value, before regular text
120 * to number conversion is applied, and SI units and their prefixes get
121 * derived from more indicators. It's important to keep in mind similar
122 * indicators exist for main and secondary displays in different locations.
123 */
124
125static char brymen_bm52x_parse_digit(uint8_t b)
126{
127 switch (b & ~0x10) {
128 /* Sign. */
129 case 0x40: /* ------g */ return '-';
130 /* Decimal digits. */
131 case 0xaf: /* abcdef- */ return '0';
132 case 0xa0: /* -bc---- */ return '1';
133 case 0xcb: /* ab-de-g */ return '2';
134 case 0xe9: /* abcd--g */ return '3';
135 case 0xe4: /* -bc--fg */ return '4';
136 case 0x6d: /* a-cd-fg */ return '5';
137 case 0x6f: /* a-cdefg */ return '6';
138 case 0xa8: /* abc---- */ return '7';
139 case 0xef: /* abcdefg */ return '8';
140 case 0xed: /* abcd-fg */ return '9';
141 /* Temperature units. */
142 case 0x0f: /* a--def- */ return 'C';
143 case 0x4e: /* a---efg */ return 'F';
144 /* OL condition, and diode and "Auto" modes. */
145 case 0x07: /* ---def- */ return 'L';
146 case 0xe3: /* -bcde-g */ return 'd';
147 case 0x20: /* --c---- */ return 'i';
148 case 0x63: /* --cde-g */ return 'o';
149 case 0xee: /* abc-efg */ return 'A';
150 case 0x23: /* --cde-- */ return 'u';
151 case 0x47: /* ---defg */ return 't';
152 /* Blank digit. */
153 case 0x00: /* ------- */ return '\0';
154 /* Invalid or unknown segment combination. */
155 default:
156 sr_warn("Unknown encoding for digit: 0x%02x.", b);
157 return '\0';
158 }
159}
160
161static int brymen_bm52x_parse_digits(const uint8_t *pkt, size_t pktlen,
162 char *txtbuf, float *value, char *temp_unit, int *digits, int signflag)
163{
164 uint8_t byte;
165 char *txtptr, txtchar;
166 size_t pos;
167 int ret;
168
169 txtptr = txtbuf;
170 if (digits)
171 *digits = INT_MIN;
172
173 if (pkt[0] & signflag)
174 *txtptr++ = '-';
175 for (pos = 0; pos < pktlen; pos++) {
176 byte = pkt[1 + pos];
177 txtchar = brymen_bm52x_parse_digit(byte);
178 if (pos == 3 && (txtchar == 'C' || txtchar == 'F')) {
179 if (temp_unit)
180 *temp_unit = txtchar;
181 } else if (txtchar) {
182 *txtptr++ = txtchar;
183 if (digits)
184 (*digits)++;
185 }
186 if (pos < 3 && (byte & 0x10)) {
187 *txtptr++ = '.';
188 if (digits)
189 *digits = 0;
190 }
191 }
192 *txtptr = '\0';
193
194 if (digits && *digits < 0)
195 *digits = 0;
196
197 ret = value ? sr_atof_ascii(txtbuf, value) : SR_OK;
198 if (ret != SR_OK) {
199 sr_dbg("invalid float string: '%s'", txtbuf);
200 return ret;
201 }
202
203 return SR_OK;
204}
205
206/*
207 * Extract the measurement value and its properties for one of the
208 * meter's displays from the DMM packet.
209 */
210static void brymen_bm52x_parse(const uint8_t *buf, float *floatval,
211 struct sr_datafeed_analog *analog, size_t ch_idx)
212{
213 char txtbuf[16], temp_unit;
214 int ret, digits, scale;
215 int is_diode, is_auto, is_no_temp, is_ol, is_db, is_main_milli;
216 int is_mm_max, is_mm_min, is_mm_avg, is_mm_dash;
217
218 temp_unit = '\0';
219 if (ch_idx == 0) {
220 /*
221 * Main display. Note that _some_ of the second display's
222 * indicators are involved in the inspection of the _first_
223 * display's measurement value. So we have to get the
224 * second display's text buffer here, too.
225 */
226 (void)brymen_bm52x_parse_digits(&buf[7], 4, txtbuf,
227 NULL, NULL, NULL, 0);
228 is_diode = strcmp(txtbuf, "diod") == 0;
229 is_auto = strcmp(txtbuf, "Auto") == 0;
230 ret = brymen_bm52x_parse_digits(&buf[2], 4, txtbuf,
231 floatval, &temp_unit, &digits, 0x80);
232 is_ol = strstr(txtbuf, "0L") || strstr(txtbuf, "0.L");
233 is_no_temp = strcmp(txtbuf, "---C") == 0;
234 is_no_temp |= strcmp(txtbuf, "---F") == 0;
235 if (ret != SR_OK && !is_ol)
236 return;
237
238 /* SI unit, derived from meter's current function. */
239 is_db = buf[6] & 0x10;
240 is_main_milli = buf[14] & 0x40;
241 if (buf[14] & 0x20) {
242 analog->meaning->mq = SR_MQ_VOLTAGE;
243 analog->meaning->unit = SR_UNIT_VOLT;
244 if (is_diode) {
245 analog->meaning->mqflags |= SR_MQFLAG_DIODE;
246 analog->meaning->mqflags |= SR_MQFLAG_DC;
247 }
248 } else if (buf[14] & 0x10) {
249 analog->meaning->mq = SR_MQ_CURRENT;
250 analog->meaning->unit = SR_UNIT_AMPERE;
251 } else if (buf[14] & 0x01) {
252 analog->meaning->mq = SR_MQ_CAPACITANCE;
253 analog->meaning->unit = SR_UNIT_FARAD;
254 } else if (buf[14] & 0x02) {
255 analog->meaning->mq = SR_MQ_CONDUCTANCE;
256 analog->meaning->unit = SR_UNIT_SIEMENS;
257 } else if (buf[13] & 0x10) {
258 analog->meaning->mq = SR_MQ_FREQUENCY;
259 analog->meaning->unit = SR_UNIT_HERTZ;
260 } else if (buf[7] & 0x01) {
261 analog->meaning->mq = SR_MQ_CONTINUITY;
262 analog->meaning->unit = SR_UNIT_OHM;
263 } else if (buf[13] & 0x20) {
264 analog->meaning->mq = SR_MQ_RESISTANCE;
265 analog->meaning->unit = SR_UNIT_OHM;
266 } else if (is_db && is_main_milli) {
267 analog->meaning->mq = SR_MQ_POWER;
268 analog->meaning->unit = SR_UNIT_DECIBEL_MW;
269 } else if (buf[14] & 0x04) {
270 analog->meaning->mq = SR_MQ_DUTY_CYCLE;
271 analog->meaning->unit = SR_UNIT_PERCENTAGE;
272 } else if ((buf[2] & 0x09) && temp_unit) {
273 if (is_no_temp)
274 return;
275 analog->meaning->mq = SR_MQ_TEMPERATURE;
276 if (temp_unit == 'F')
277 analog->meaning->unit = SR_UNIT_FAHRENHEIT;
278 else
279 analog->meaning->unit = SR_UNIT_CELSIUS;
280 }
281
282 /*
283 * Remove the MIN/MAX/AVG indicators when all of them
284 * are shown at the same time (indicating that recording
285 * is active, but live readings are shown). This also
286 * removes the MAX-MIN (V p-p) indication which cannot
287 * get represented by SR_MQFLAG_* means.
288 *
289 * Keep the check conditions separate to simplify future
290 * maintenance when Vp-p gets added. Provide the value of
291 * currently unsupported modes just without flags (show
292 * the maximum amount of LCD content on screen that we
293 * can represent in sigrok).
294 */
295 is_mm_max = buf[1] & 0x01;
296 is_mm_min = buf[1] & 0x08;
297 is_mm_avg = buf[1] & 0x02;
298 is_mm_dash = buf[1] & 0x04;
299 if (is_mm_max && is_mm_min && is_mm_avg)
300 is_mm_max = is_mm_min = is_mm_avg = 0;
301 if (is_mm_max && is_mm_min && is_mm_dash)
302 is_mm_max = is_mm_min = 0;
303 if (is_mm_max && is_mm_min && !is_mm_dash)
304 is_mm_max = is_mm_min = 0;
305
306 /* AC/DC/Auto flags. Hold/Min/Max/Rel etc flags. */
307 if (buf[1] & 0x20)
308 analog->meaning->mqflags |= SR_MQFLAG_DC;
309 if (buf[1] & 0x10)
310 analog->meaning->mqflags |= SR_MQFLAG_AC;
311 if (buf[20] & 0x10)
312 analog->meaning->mqflags |= SR_MQFLAG_AUTORANGE;
313 if (buf[20] & 0x80)
314 analog->meaning->mqflags |= SR_MQFLAG_HOLD;
315 if (is_mm_max)
316 analog->meaning->mqflags |= SR_MQFLAG_MAX;
317 if (is_mm_min)
318 analog->meaning->mqflags |= SR_MQFLAG_MIN;
319 if (is_mm_avg)
320 analog->meaning->mqflags |= SR_MQFLAG_AVG;
321 if (buf[2] & 0x40)
322 analog->meaning->mqflags |= SR_MQFLAG_RELATIVE;
323
324 /*
325 * Remove the "dBm" indication's "m" indicator before the
326 * SI unit's prefixes get inspected. Avoids an interaction
327 * with the "milli" prefix. Strictly speaking BM525s does
328 * not support dBm, but other models do and we may want
329 * to share the protocol parser.
330 */
331 if (is_db)
332 is_main_milli = 0;
333
334 /* SI prefix. */
335 scale = 0;
336 if (buf[14] & 0x08) /* n */
337 scale = -9;
338 if (buf[14] & 0x80) /* u */
339 scale = -6;
340 if (is_main_milli) /* m */
341 scale = -3;
342 if (buf[13] & 0x80) /* k */
343 scale = +3;
344 if (buf[13] & 0x40) /* M */
345 scale = +6;
346 if (scale) {
347 *floatval *= pow(10, scale);
348 digits += -scale;
349 }
350
351 if (is_ol)
352 *floatval = INFINITY;
353
354 analog->encoding->digits = digits;
355 analog->spec->spec_digits = digits;
356 } else if (ch_idx == 1) {
357 /*
358 * Secondary display. Also inspect _some_ primary display
359 * data, to determine the secondary display's validity.
360 */
361 (void)brymen_bm52x_parse_digits(&buf[2], 4, txtbuf,
362 NULL, &temp_unit, NULL, 0x80);
363 ret = brymen_bm52x_parse_digits(&buf[7], 4, txtbuf,
364 floatval, NULL, &digits, 0x20);
365 is_diode = strcmp(txtbuf, "diod") == 0;
366 is_auto = strcmp(txtbuf, "Auto") == 0;
367 is_no_temp = strcmp(txtbuf, "---C") == 0;
368 is_no_temp |= strcmp(txtbuf, "---F") == 0;
369 if (is_diode || is_auto)
370 return;
371 if (is_no_temp)
372 return;
373
374 /* SI unit. */
375 if (buf[12] & 0x10) {
376 analog->meaning->mq = SR_MQ_VOLTAGE;
377 analog->meaning->unit = SR_UNIT_VOLT;
378 } else if (buf[12] & 0x20) {
379 analog->meaning->mq = SR_MQ_CURRENT;
380 if (buf[11] & 0x10)
381 analog->meaning->unit = SR_UNIT_PERCENTAGE;
382 else
383 analog->meaning->unit = SR_UNIT_AMPERE;
384 } else if (buf[13] & 0x02) {
385 analog->meaning->mq = SR_MQ_RESISTANCE;
386 analog->meaning->unit = SR_UNIT_OHM;
387 } else if (buf[12] & 0x02) {
388 analog->meaning->mq = SR_MQ_CONDUCTANCE;
389 analog->meaning->unit = SR_UNIT_SIEMENS;
390 } else if (buf[12] & 0x01) {
391 analog->meaning->mq = SR_MQ_CAPACITANCE;
392 analog->meaning->unit = SR_UNIT_FARAD;
393 } else if (buf[7] & 0x06) {
394 if (strstr(txtbuf, "---"))
395 return;
396 analog->meaning->mq = SR_MQ_TEMPERATURE;
397 if (temp_unit == 'F')
398 analog->meaning->unit = SR_UNIT_FAHRENHEIT;
399 else
400 analog->meaning->unit = SR_UNIT_CELSIUS;
401 } else if (buf[13] & 0x01) {
402 analog->meaning->mq = SR_MQ_FREQUENCY;
403 analog->meaning->unit = SR_UNIT_HERTZ;
404 } else if (buf[11] & 0x08) {
405 analog->meaning->mq = SR_MQ_DUTY_CYCLE;
406 analog->meaning->unit = SR_UNIT_PERCENTAGE;
407 }
408
409 /* DC/AC flags. */
410 if (buf[7] & 0x80)
411 analog->meaning->mqflags |= SR_MQFLAG_DC;
412 if (buf[7] & 0x40)
413 analog->meaning->mqflags |= SR_MQFLAG_AC;
414
415 /* SI prefix. */
416 scale = 0;
417 if (buf[12] & 0x04) /* n */
418 scale = -9;
419 if (buf[12] & 0x40) /* u */
420 scale = -6;
421 if (buf[12] & 0x80) /* m */
422 scale = -3;
423 if (buf[13] & 0x04) /* k */
424 scale = +3;
425 if (buf[13] & 0x08) /* M */
426 scale = +6;
427 if (scale) {
428 *floatval *= pow(10, scale);
429 digits += -scale;
430 }
431
432 analog->encoding->digits = digits;
433 analog->spec->spec_digits = digits;
434 }
435
436 if (buf[7] & 0x08)
437 sr_warn("Battery is low.");
438}
439
440SR_PRIV int sr_brymen_bm52x_parse(const uint8_t *buf, float *val,
441 struct sr_datafeed_analog *analog, void *info)
442{
443 struct brymen_bm52x_info *info_local;
444 size_t ch_idx;
445
446 /*
447 * Scan a portion of the received DMM packet which corresponds
448 * to the caller's specified display. Then prepare to scan a
449 * different portion of the packet for another display. This
450 * routine gets called multiple times for one received packet.
451 */
452 info_local = info;
453 ch_idx = info_local->ch_idx;
454 brymen_bm52x_parse(buf, val, analog, ch_idx);
455 info_local->ch_idx = ch_idx + 1;
456
457 return SR_OK;
458}