2 * This file is part of the libsigrok project.
4 * Copyright (C) 2014 Aurelien Jacobs <aurel@gnuage.org>
5 * Copyright (C) 2019 Gerhard Sittig <gerhard.sittig@gmx.net>
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.
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.
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/>.
24 * Brymen BM86x 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/PD02BM860s_protocolDL.html
28 * http://brymen.com/product-html/images/DownloadList/ProtocolList/BM860-BM860s_List/BM860-BM860s-500000-count-dual-display-DMMs-protocol.pdf
33 #include <libsigrok/libsigrok.h>
34 #include "libsigrok-internal.h"
37 #define LOG_PREFIX "brymen-bm86x"
39 #ifdef HAVE_SERIAL_COMM
40 SR_PRIV int sr_brymen_bm86x_packet_request(struct sr_serial_dev_inst *serial)
42 static const uint8_t request[] = { 0x00, 0x00, 0x86, 0x66, };
44 sr_spew("%s() sending request", __func__);
45 serial_write_nonblocking(serial, request, sizeof(request));
51 SR_PRIV gboolean sr_brymen_bm86x_packet_valid(const uint8_t *buf)
54 * "Model ID3" (3rd HID report, byte 3) is the only documented
55 * fixed value, and must be 0x86. All other positions either depend
56 * on the meter's function, or the measurement's value, or are not
57 * documented by the vendor (are marked as "don't care", no fixed
58 * values are listed). There is nothing else we can check reliably.
67 * Data bytes in the DMM packet encode LCD segments in an unusual order
68 * (bgcdafe) and in an unusual position (bits 7:1 within the byte). The
69 * decimal point (bit 0) for one digit resides in the _next_ digit's byte.
71 * These routines convert LCD segments to characters, and a section of the
72 * DMM packet (which corresponds to the primary or secondary display) to
73 * the text representation of the measurement's value, before regular text
74 * to number conversion is applied. The first byte of the passed in block
75 * contains indicators, the value's digits start at the second byte.
78 static char brymen_bm86x_parse_digit(uint8_t b)
82 case 0x20: return '-';
84 case 0x5f: return '0';
85 case 0x50: return '1';
86 case 0x6d: return '2';
87 case 0x7c: return '3';
88 case 0x72: return '4';
89 case 0x3e: return '5';
90 case 0x3f: return '6';
91 case 0x54: return '7';
92 case 0x7f: return '8';
93 case 0x7e: return '9';
94 /* Temperature units. */
95 case 0x0f: return 'C';
96 case 0x27: return 'F';
97 /* OL condition, and diode mode. */
98 case 0x0b: return 'L';
99 case 0x79: return 'd';
100 case 0x10: return 'i';
101 case 0x39: return 'o';
103 case 0x00: return '\0';
104 /* Invalid or unknown segment combination. */
106 sr_warn("Unknown encoding for digit: 0x%02x.", b);
111 static int brymen_bm86x_parse_digits(const uint8_t *pkt, size_t pktlen,
112 char *txtbuf, float *value, char *temp_unit, int *digits, int signflag)
115 char *txtptr, txtchar;
123 if (pkt[0] & signflag)
125 for (pos = 0; pos < pktlen; pos++) {
127 if (pos && pos < 5 && (byte & 0x01)) {
132 txtchar = brymen_bm86x_parse_digit(byte);
133 if (pos == 5 && (txtchar == 'C' || txtchar == 'F')) {
135 *temp_unit = txtchar;
136 } else if (txtchar) {
144 if (digits && *digits < 0)
147 ret = value ? sr_atof_ascii(txtbuf, value) : SR_OK;
149 sr_dbg("invalid float string: '%s'", txtbuf);
157 * Extract the measurement value and its properties for one of the
158 * meter's displays from the DMM packet.
160 static void brymen_bm86x_parse(const uint8_t *buf, float *floatval,
161 struct sr_datafeed_analog *analog, size_t ch_idx)
163 char txtbuf[16], temp_unit;
164 int ret, digits, is_diode, over_limit, scale;
170 * Main display. Note that _some_ of the second display's
171 * indicators are involved in the inspection of the _first_
172 * display's measurement value. So we have to get the
173 * second display's text buffer here, too.
175 (void)brymen_bm86x_parse_digits(&buf[9], 4, txtbuf,
176 NULL, NULL, NULL, 0);
177 is_diode = strcmp(txtbuf, "diod") == 0;
178 ret = brymen_bm86x_parse_digits(&buf[2], 6, txtbuf,
179 floatval, &temp_unit, &digits, 0x80);
180 over_limit = strstr(txtbuf, "0L") || strstr(txtbuf, "0.L");
181 if (ret != SR_OK && !over_limit)
186 analog->meaning->mq = SR_MQ_VOLTAGE;
187 analog->meaning->unit = SR_UNIT_VOLT;
189 analog->meaning->mqflags |= SR_MQFLAG_DIODE;
190 analog->meaning->mqflags |= SR_MQFLAG_DC;
192 } else if (buf[14] & 0x80) {
193 analog->meaning->mq = SR_MQ_CURRENT;
194 analog->meaning->unit = SR_UNIT_AMPERE;
195 } else if (buf[14] & 0x20) {
196 analog->meaning->mq = SR_MQ_CAPACITANCE;
197 analog->meaning->unit = SR_UNIT_FARAD;
198 } else if (buf[14] & 0x10) {
199 analog->meaning->mq = SR_MQ_CONDUCTANCE;
200 analog->meaning->unit = SR_UNIT_SIEMENS;
201 } else if (buf[15] & 0x01) {
202 analog->meaning->mq = SR_MQ_FREQUENCY;
203 analog->meaning->unit = SR_UNIT_HERTZ;
204 } else if (buf[10] & 0x01) {
205 analog->meaning->mq = SR_MQ_CONTINUITY;
206 analog->meaning->unit = SR_UNIT_OHM;
207 } else if (buf[15] & 0x10) {
208 analog->meaning->mq = SR_MQ_RESISTANCE;
209 analog->meaning->unit = SR_UNIT_OHM;
210 } else if (buf[15] & 0x02) {
211 analog->meaning->mq = SR_MQ_POWER;
212 analog->meaning->unit = SR_UNIT_DECIBEL_MW;
213 } else if (buf[15] & 0x80) {
214 analog->meaning->mq = SR_MQ_DUTY_CYCLE;
215 analog->meaning->unit = SR_UNIT_PERCENTAGE;
216 } else if ((buf[2] & 0x0a) && temp_unit) {
217 analog->meaning->mq = SR_MQ_TEMPERATURE;
218 if (temp_unit == 'F')
219 analog->meaning->unit = SR_UNIT_FAHRENHEIT;
221 analog->meaning->unit = SR_UNIT_CELSIUS;
225 * Remove the MIN/MAX/AVG indicators when all of them
226 * are shown at the same time.
229 if ((ind1 & 0xe0) == 0xe0)
232 /* AC/DC/Auto flags. */
234 analog->meaning->mqflags |= SR_MQFLAG_DC;
236 analog->meaning->mqflags |= SR_MQFLAG_AC;
238 analog->meaning->mqflags |= SR_MQFLAG_AUTORANGE;
240 analog->meaning->mqflags |= SR_MQFLAG_HOLD;
242 analog->meaning->mqflags |= SR_MQFLAG_MAX;
244 analog->meaning->mqflags |= SR_MQFLAG_MIN;
246 analog->meaning->mqflags |= SR_MQFLAG_AVG;
248 analog->meaning->mqflags |= SR_MQFLAG_RELATIVE;
251 * Remove the "dBm" indication's "m" indicator before the
252 * SI unit's prefixes get inspected. To avoid an interaction
253 * with the "milli" prefix.
261 if (buf[14] & 0x40) /* n */
263 if (buf[15] & 0x08) /* u */
265 if (ind15 & 0x04) /* m */
267 if (buf[15] & 0x40) /* k */
269 if (buf[15] & 0x20) /* M */
272 *floatval *= pow(10, scale);
277 *floatval = INFINITY;
279 analog->encoding->digits = digits;
280 analog->spec->spec_digits = digits;
281 } else if (ch_idx == 1) {
283 * Secondary display. Also inspect _some_ primary display
284 * data, to determine the secondary display's validity.
286 (void)brymen_bm86x_parse_digits(&buf[2], 6, txtbuf,
287 NULL, &temp_unit, NULL, 0x80);
288 ret = brymen_bm86x_parse_digits(&buf[9], 4, txtbuf,
289 floatval, NULL, &digits, 0x10);
292 if (buf[14] & 0x08) {
293 analog->meaning->mq = SR_MQ_VOLTAGE;
294 analog->meaning->unit = SR_UNIT_VOLT;
295 } else if (buf[9] & 0x04) {
296 analog->meaning->mq = SR_MQ_CURRENT;
297 analog->meaning->unit = SR_UNIT_AMPERE;
298 } else if (buf[9] & 0x08) {
299 analog->meaning->mq = SR_MQ_CURRENT;
300 analog->meaning->unit = SR_UNIT_PERCENTAGE;
301 } else if (buf[14] & 0x04) {
302 analog->meaning->mq = SR_MQ_FREQUENCY;
303 analog->meaning->unit = SR_UNIT_HERTZ;
304 } else if ((buf[9] & 0x40) && temp_unit) {
305 analog->meaning->mq = SR_MQ_TEMPERATURE;
306 if (temp_unit == 'F')
307 analog->meaning->unit = SR_UNIT_FAHRENHEIT;
309 analog->meaning->unit = SR_UNIT_CELSIUS;
314 analog->meaning->mqflags |= SR_MQFLAG_AC;
318 if (buf[ 9] & 0x01) /* u */
320 if (buf[ 9] & 0x02) /* m */
322 if (buf[14] & 0x02) /* k */
324 if (buf[14] & 0x01) /* M */
327 *floatval *= pow(10, scale);
331 analog->encoding->digits = digits;
332 analog->spec->spec_digits = digits;
336 sr_warn("Battery is low.");
339 SR_PRIV int sr_brymen_bm86x_parse(const uint8_t *buf, float *val,
340 struct sr_datafeed_analog *analog, void *info)
342 struct brymen_bm86x_info *info_local;
346 * Scan a portion of the received DMM packet which corresponds
347 * to the caller's specified display. Then prepare to scan a
348 * different portion of the packet for another display. This
349 * routine gets called multiple times for one received packet.
352 ch_idx = info_local->ch_idx;
353 brymen_bm86x_parse(buf, val, analog, ch_idx);
354 info_local->ch_idx = ch_idx + 1;