]> sigrok.org Git - libsigrok.git/blame - src/dmm/bm86x.c
dmm/eev121gw: visibility nits (single display parse routine)
[libsigrok.git] / src / dmm / bm86x.c
CommitLineData
0759e2f5
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 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 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
29 */
30
31#include <config.h>
32#include <math.h>
33#include <libsigrok/libsigrok.h>
34#include "libsigrok-internal.h"
35#include <string.h>
36
37#define LOG_PREFIX "brymen-bm86x"
38
39#ifdef HAVE_SERIAL_COMM
40SR_PRIV int sr_brymen_bm86x_packet_request(struct sr_serial_dev_inst *serial)
41{
42 static const uint8_t request[] = { 0x00, 0x00, 0x86, 0x66, };
43
44 sr_spew("%s() sending request", __func__);
45 serial_write_nonblocking(serial, request, sizeof(request));
46
47 return SR_OK;
48}
49#endif
50
51SR_PRIV gboolean sr_brymen_bm86x_packet_valid(const uint8_t *buf)
52{
53 GString *text;
54
55 if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
56 text = sr_hexdump_new(buf, BRYMEN_BM86X_PACKET_SIZE);
57 sr_spew("%s() checking DMM packet: %s", __func__, text->str);
58 sr_hexdump_free(text);
59 }
60
61 /*
62 * "Model ID3" (3rd HID report, byte 3) is the only documented
63 * fixed value, and must be 0x86. All other positions either depend
64 * on the meter's function, or the measurement's value, or are not
65 * documented by the vendor (are marked as "don't care", no fixed
66 * values are listed). There is nothing else we can check reliably.
67 */
68 if (buf[19] != 0x86)
69 return FALSE;
70
71 return TRUE;
72}
73
74/*
75 * Data bytes in the DMM packet encode LCD segments in an unusual order
76 * (bgcdafe) and in an unusual position (bits 7:1 within the byte). The
77 * decimal point (bit 0) for one digit resides in the _next_ digit's byte.
78 *
79 * These routines convert LCD segments to characters, and a section of the
80 * DMM packet (which corresponds to the primary or secondary display) to
81 * the text representation of the measurement's value, before regular text
82 * to number conversion is applied. The first byte of the passed in block
83 * contains indicators, the value's digits start at the second byte.
84 */
85
86static char brymen_bm86x_parse_digit(uint8_t b)
87{
88 switch (b >> 1) {
89 /* Sign. */
90 case 0x20: return '-';
91 /* Decimal digits. */
92 case 0x5f: return '0';
93 case 0x50: return '1';
94 case 0x6d: return '2';
95 case 0x7c: return '3';
96 case 0x72: return '4';
97 case 0x3e: return '5';
98 case 0x3f: return '6';
99 case 0x54: return '7';
100 case 0x7f: return '8';
101 case 0x7e: return '9';
102 /* Temperature units. */
103 case 0x0f: return 'C';
104 case 0x27: return 'F';
105 /* OL condition, and diode mode. */
106 case 0x0b: return 'L';
107 case 0x79: return 'd';
108 case 0x10: return 'i';
109 case 0x39: return 'o';
110 /* Blank digit. */
111 case 0x00: return '\0';
112 /* Invalid or unknown segment combination. */
113 default:
114 sr_warn("Unknown encoding for digit: 0x%02x.", b);
115 return '\0';
116 }
117}
118
119static int brymen_bm86x_parse_digits(const uint8_t *pkt, size_t pktlen,
120 char *txtbuf, float *value, char *temp_unit, int *digits, int signflag)
121{
122 uint8_t byte;
123 char *txtptr, txtchar;
124 size_t pos;
125 int ret;
126
127 txtptr = txtbuf;
128 if (digits)
129 *digits = INT_MIN;
130
131 if (pkt[0] & signflag)
132 *txtptr++ = '-';
133 for (pos = 0; pos < pktlen; pos++) {
134 byte = pkt[1 + pos];
135 if (pos && pos < 5 && (byte & 0x01)) {
136 *txtptr++ = '.';
137 if (digits)
138 *digits = 0;
139 }
140 txtchar = brymen_bm86x_parse_digit(byte);
141 if (pos == 5 && (txtchar == 'C' || txtchar == 'F')) {
142 if (temp_unit)
143 *temp_unit = txtchar;
144 } else if (txtchar) {
145 *txtptr++ = txtchar;
146 if (digits)
147 (*digits)++;
148 }
149 }
150 *txtptr = '\0';
151
152 if (digits && *digits < 0)
153 *digits = 0;
154
155 ret = value ? sr_atof_ascii(txtbuf, value) : SR_OK;
156 if (ret != SR_OK) {
157 sr_dbg("invalid float string: '%s'", txtbuf);
158 return ret;
159 }
160
161 return SR_OK;
162}
163
164/*
165 * Extract the measurement value and its properties for one of the
166 * meter's displays from the DMM packet.
167 */
168static void brymen_bm86x_parse(const uint8_t *buf, float *floatval,
169 struct sr_datafeed_analog *analog, size_t ch_idx)
170{
171 char txtbuf[16], temp_unit;
172 int ret, digits, is_diode, over_limit, scale;
173 uint8_t ind1, ind15;
174
175 if (ch_idx == 0) {
176 /*
177 * Main display. Note that _some_ of the second display's
178 * indicators are involved in the inspection of the _first_
179 * display's measurement value. So we have to get the
180 * second display's text buffer here, too.
181 */
182 (void)brymen_bm86x_parse_digits(&buf[9], 4, txtbuf,
183 NULL, NULL, NULL, 0);
184 is_diode = strcmp(txtbuf, "diod") == 0;
185 ret = brymen_bm86x_parse_digits(&buf[2], 6, txtbuf,
186 floatval, &temp_unit, &digits, 0x80);
187 over_limit = strstr(txtbuf, "0L") || strstr(txtbuf, "0.L");
188 if (ret != SR_OK && !over_limit)
189 return;
190
191 /* SI unit. */
192 if (buf[8] & 0x01) {
193 analog->meaning->mq = SR_MQ_VOLTAGE;
194 analog->meaning->unit = SR_UNIT_VOLT;
195 if (is_diode) {
196 analog->meaning->mqflags |= SR_MQFLAG_DIODE;
197 analog->meaning->mqflags |= SR_MQFLAG_DC;
198 }
199 } else if (buf[14] & 0x80) {
200 analog->meaning->mq = SR_MQ_CURRENT;
201 analog->meaning->unit = SR_UNIT_AMPERE;
202 } else if (buf[14] & 0x20) {
203 analog->meaning->mq = SR_MQ_CAPACITANCE;
204 analog->meaning->unit = SR_UNIT_FARAD;
205 } else if (buf[14] & 0x10) {
206 analog->meaning->mq = SR_MQ_CONDUCTANCE;
207 analog->meaning->unit = SR_UNIT_SIEMENS;
208 } else if (buf[15] & 0x01) {
209 analog->meaning->mq = SR_MQ_FREQUENCY;
210 analog->meaning->unit = SR_UNIT_HERTZ;
211 } else if (buf[10] & 0x01) {
212 analog->meaning->mq = SR_MQ_CONTINUITY;
213 analog->meaning->unit = SR_UNIT_OHM;
214 } else if (buf[15] & 0x10) {
215 analog->meaning->mq = SR_MQ_RESISTANCE;
216 analog->meaning->unit = SR_UNIT_OHM;
217 } else if (buf[15] & 0x02) {
218 analog->meaning->mq = SR_MQ_POWER;
219 analog->meaning->unit = SR_UNIT_DECIBEL_MW;
220 } else if (buf[15] & 0x80) {
221 analog->meaning->mq = SR_MQ_DUTY_CYCLE;
222 analog->meaning->unit = SR_UNIT_PERCENTAGE;
223 } else if (buf[ 2] & 0x0a) {
224 analog->meaning->mq = SR_MQ_TEMPERATURE;
225 if (temp_unit == 'F')
226 analog->meaning->unit = SR_UNIT_FAHRENHEIT;
227 else
228 analog->meaning->unit = SR_UNIT_CELSIUS;
229 }
230
231 /*
232 * Remove the MIN/MAX/AVG indicators when all of them
233 * are shown at the same time.
234 */
235 ind1 = buf[1];
236 if ((ind1 & 0xe0) == 0xe0)
237 ind1 &= ~0xe0;
238
239 /* AC/DC/Auto flags. */
240 if (buf[1] & 0x10)
241 analog->meaning->mqflags |= SR_MQFLAG_DC;
242 if (buf[2] & 0x01)
243 analog->meaning->mqflags |= SR_MQFLAG_AC;
244 if (buf[1] & 0x01)
245 analog->meaning->mqflags |= SR_MQFLAG_AUTORANGE;
246 if (buf[1] & 0x08)
247 analog->meaning->mqflags |= SR_MQFLAG_HOLD;
248 if (ind1 & 0x20)
249 analog->meaning->mqflags |= SR_MQFLAG_MAX;
250 if (ind1 & 0x40)
251 analog->meaning->mqflags |= SR_MQFLAG_MIN;
252 if (ind1 & 0x80)
253 analog->meaning->mqflags |= SR_MQFLAG_AVG;
254 if (buf[3] & 0x01)
255 analog->meaning->mqflags |= SR_MQFLAG_RELATIVE;
256
257 /*
258 * Remove the "dBm" indication's "m" indicator before the
259 * SI unit's prefixes get inspected. To avoid an interaction
260 * with the "milli" prefix.
261 */
262 ind15 = buf[15];
263 if (ind15 & 0x02)
264 ind15 &= ~0x04;
265
266 /* SI prefix. */
267 scale = 0;
268 if (buf[14] & 0x40) /* n */
269 scale = -9;
270 if (buf[15] & 0x08) /* u */
271 scale = -6;
272 if (ind15 & 0x04) /* m */
273 scale = -3;
274 if (buf[15] & 0x40) /* k */
275 scale = +3;
276 if (buf[15] & 0x20) /* M */
277 scale = +6;
278 if (scale) {
279 *floatval *= pow(10, scale);
280 digits += -scale;
281 }
282
283 if (over_limit)
284 *floatval = INFINITY;
285
286 analog->encoding->digits = digits;
287 analog->spec->spec_digits = digits;
288 } else if (ch_idx == 1) {
289 /* Secondary display. */
290 ret = brymen_bm86x_parse_digits(&buf[9], 4, txtbuf,
291 floatval, &temp_unit, &digits, 0x10);
292
293 /* SI unit. */
294 if (buf[14] & 0x08) {
295 analog->meaning->mq = SR_MQ_VOLTAGE;
296 analog->meaning->unit = SR_UNIT_VOLT;
297 } else if (buf[9] & 0x04) {
298 analog->meaning->mq = SR_MQ_CURRENT;
299 analog->meaning->unit = SR_UNIT_AMPERE;
300 } else if (buf[9] & 0x08) {
301 analog->meaning->mq = SR_MQ_CURRENT;
302 analog->meaning->unit = SR_UNIT_PERCENTAGE;
303 } else if (buf[14] & 0x04) {
304 analog->meaning->mq = SR_MQ_FREQUENCY;
305 analog->meaning->unit = SR_UNIT_HERTZ;
306 } else if (buf[9] & 0x40) {
307 analog->meaning->mq = SR_MQ_TEMPERATURE;
308 if (temp_unit == 'F')
309 analog->meaning->unit = SR_UNIT_FAHRENHEIT;
310 else
311 analog->meaning->unit = SR_UNIT_CELSIUS;
312 }
313
314 /* AC flag. */
315 if (buf[9] & 0x20)
316 analog->meaning->mqflags |= SR_MQFLAG_AC;
317
318 /* SI prefix. */
319 scale = 0;
320 if (buf[ 9] & 0x01) /* u */
321 scale = -6;
322 if (buf[ 9] & 0x02) /* m */
323 scale = -3;
324 if (buf[14] & 0x02) /* k */
325 scale = +3;
326 if (buf[14] & 0x01) /* M */
327 scale = +6;
328 if (scale) {
329 *floatval *= pow(10, scale);
330 digits += -scale;
331 }
332
333 analog->encoding->digits = digits;
334 analog->spec->spec_digits = digits;
335 }
336
337 if (buf[9] & 0x80)
338 sr_warn("Battery is low.");
339}
340
341SR_PRIV int sr_brymen_bm86x_parse(const uint8_t *buf, float *val,
342 struct sr_datafeed_analog *analog, void *info)
343{
344 struct brymen_bm86x_info *info_local;
345 size_t ch_idx;
346
347 /*
348 * Scan a portion of the received DMM packet which corresponds
349 * to the caller's specified display. Then prepare to scan a
350 * different portion of the packet for another display. This
351 * routine gets called multiple times for one received packet.
352 */
353 info_local = info;
354 ch_idx = info_local->ch_idx;
355 brymen_bm86x_parse(buf, val, analog, ch_idx);
356 info_local->ch_idx = ch_idx + 1;
357
358 return SR_OK;
359}