]>
Commit | Line | Data |
---|---|---|
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://www.brymen.com/Download2.html | |
27 | * http://www.brymen.com/PD02BM520s_protocolDL.html | |
28 | * http://www.brymen.com/images/DownloadList/ProtocolList/BM520-BM520s_List/BM520-BM520s-10000-count-professional-dual-display-mobile-logging-DMMs-protocol.zip | |
29 | * | |
30 | * This parser was initially created for BM520s devices and tested with | |
31 | * BM525s. The Brymen BM820s family of devices uses the same protocol, | |
32 | * with just 0x82 instead of 0x52 in request packets and in the fixed | |
33 | * fields of the responses. Which means that the packet parser can get | |
34 | * shared among the BM520s and BM820s devices, but validity check needs | |
35 | * to be individual, and the "wrong" packet request will end up without | |
36 | * a response. Compared to BM520s the BM820s has dBm (in the protocol) | |
37 | * and NCV (not seen in the protocol) and is non-logging (live only). | |
38 | * BM820s support was tested with BM829s. | |
39 | * | |
40 | * The parser implementation was tested with a Brymen BM525s meter. Some | |
41 | * of the responses differ from the vendor's documentation: | |
42 | * - Recording session total byte counts don't start after the byte count | |
43 | * field, but instead include this field and the model ID (spans _every_ | |
44 | * byte in the stream). | |
45 | * - Recording session start/end markers are referred to as DLE, STX, | |
46 | * and ETX. Observed traffic instead sends 0xee, 0xa0, and 0xc0. | |
47 | */ | |
48 | ||
49 | /* | |
50 | * TODO | |
51 | * - Some of the meter's functions and indications cannot get expressed | |
52 | * by means of sigrok MQ and flags terms. Some indicator's meaning is | |
53 | * unknown or uncertain, and thus their state is not evaluated. | |
54 | * - MAX-MIN, the span between extreme values, referred to as Vp-p. | |
55 | * - AVG is not available in BM525s and BM521s. | |
56 | * - LoZ, eliminating ghost voltages. | |
57 | * - LPF, low pass filter. | |
58 | * - low battery, emits sr_warn() but isn't seen in the feed. | |
59 | * - @, 4-20mA loop, % (main display, left hand side), Hi/Lo. Some of | |
60 | * these are in the vendor's documentation for the DMM packet but not | |
61 | * supported by the BM525s device which motivated the creation of the | |
62 | * parser's and was used to test its operation. | |
63 | * - It's a guess that the many undocumented bits (44 of them) are | |
64 | * related to the bargraph (40 ticks, overflow, sign, 6/10 scale). | |
65 | * - Should T1-T2 have a delta ("relative") decoration? But the meter's | |
66 | * "relative" feature is flexible, accepts any display value as the | |
67 | * reference, including min/max/diff when displayed upon activation. | |
68 | * - The "beep jack" displays "InEr" in the secondary display. This is | |
69 | * not caught here, no PC side message gets emitted. | |
70 | * - Support for recordings is mostly untested. It was written to the | |
71 | * letter of the vendor documentation, but was not verified to work | |
72 | * for all of the many meter's modes including ranges. Inspection of | |
73 | * the full byte stream is necessary on one hand since random access | |
74 | * is not available, and useful on the other hand for consistency | |
75 | * checks. | |
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 | #include <strings.h> | |
84 | ||
85 | #define LOG_PREFIX "brymen-bm52x" | |
86 | ||
87 | /* | |
88 | * DMM specific device options, and state keeping. All of it is related | |
89 | * to recorded information in contrast to live readings. There also are | |
90 | * four types of requesting HID reports that need to be sent. | |
91 | */ | |
92 | ||
93 | static const uint32_t devopts[] = { | |
94 | SR_CONF_CONTINUOUS, | |
95 | SR_CONF_LIMIT_SAMPLES | SR_CONF_SET, | |
96 | SR_CONF_LIMIT_MSEC | SR_CONF_SET, | |
97 | SR_CONF_DATA_SOURCE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, | |
98 | }; | |
99 | ||
100 | struct brymen_bm52x_state { | |
101 | size_t sess_idx; | |
102 | struct { | |
103 | uint8_t buff[2 * 32]; | |
104 | size_t fill_pos; | |
105 | size_t read_pos; | |
106 | size_t remain; | |
107 | } rsp; | |
108 | const struct sr_dev_inst *sdi; | |
109 | }; | |
110 | ||
111 | enum bm52x_reqtype { | |
112 | REQ_LIVE_READ_520, | |
113 | REQ_LIVE_READ_820, | |
114 | REQ_REC_HEAD, | |
115 | REQ_REC_NEXT, | |
116 | REQ_REC_CURR, | |
117 | }; | |
118 | ||
119 | #ifdef HAVE_SERIAL_COMM | |
120 | static int bm52x_send_req(struct sr_serial_dev_inst *serial, enum bm52x_reqtype t) | |
121 | { | |
122 | static const uint8_t req_live_520[] = { 0x00, 0x00, 0x52, 0x66, }; | |
123 | static const uint8_t req_live_820[] = { 0x00, 0x00, 0x82, 0x66, }; | |
124 | static const uint8_t req_head[] = { 0x00, 0x00, 0x52, 0x88, }; | |
125 | static const uint8_t req_next[] = { 0x00, 0x00, 0x52, 0x89, }; | |
126 | static const uint8_t req_curr[] = { 0x00, 0x00, 0x52, 0x8a, }; | |
127 | static const uint8_t *req_bytes[] = { | |
128 | [REQ_LIVE_READ_520] = req_live_520, | |
129 | [REQ_LIVE_READ_820] = req_live_820, | |
130 | [REQ_REC_HEAD] = req_head, | |
131 | [REQ_REC_NEXT] = req_next, | |
132 | [REQ_REC_CURR] = req_curr, | |
133 | }; | |
134 | static const size_t req_len = ARRAY_SIZE(req_live_520); | |
135 | ||
136 | const uint8_t *p; | |
137 | size_t l; | |
138 | int ret; | |
139 | ||
140 | if (t >= ARRAY_SIZE(req_bytes)) | |
141 | return SR_ERR_ARG; | |
142 | p = req_bytes[t]; | |
143 | l = req_len; | |
144 | ret = serial_write_nonblocking(serial, p, l); | |
145 | if (ret < 0) | |
146 | return ret; | |
147 | if ((size_t)ret != l) | |
148 | return SR_ERR_IO; | |
149 | ||
150 | return SR_OK; | |
151 | } | |
152 | ||
153 | SR_PRIV int sr_brymen_bm52x_packet_request(struct sr_serial_dev_inst *serial) | |
154 | { | |
155 | return bm52x_send_req(serial, REQ_LIVE_READ_520); | |
156 | } | |
157 | ||
158 | SR_PRIV int sr_brymen_bm82x_packet_request(struct sr_serial_dev_inst *serial) | |
159 | { | |
160 | return bm52x_send_req(serial, REQ_LIVE_READ_820); | |
161 | } | |
162 | #endif | |
163 | ||
164 | /* | |
165 | * The following code interprets live readings ("real-time download") | |
166 | * which arrive in the "traditional" bitmap for LCD segments. Reading | |
167 | * previously recorded measurements ("memory data sets") differs a lot | |
168 | * and is handled in other code paths. | |
169 | */ | |
170 | ||
171 | SR_PRIV gboolean sr_brymen_bm52x_packet_valid(const uint8_t *buf) | |
172 | { | |
173 | if (buf[16] != 0x52) | |
174 | return FALSE; | |
175 | if (buf[17] != 0x52) | |
176 | return FALSE; | |
177 | if (buf[18] != 0x52) | |
178 | return FALSE; | |
179 | if (buf[19] != 0x52) | |
180 | return FALSE; | |
181 | ||
182 | return TRUE; | |
183 | } | |
184 | ||
185 | SR_PRIV gboolean sr_brymen_bm82x_packet_valid(const uint8_t *buf) | |
186 | { | |
187 | if (buf[16] != 0x82) | |
188 | return FALSE; | |
189 | if (buf[17] != 0x82) | |
190 | return FALSE; | |
191 | if (buf[18] != 0x82) | |
192 | return FALSE; | |
193 | if (buf[19] != 0x82) | |
194 | return FALSE; | |
195 | ||
196 | return TRUE; | |
197 | } | |
198 | ||
199 | /* | |
200 | * Data bytes in the DMM packet encode LCD segments in an unusual order | |
201 | * (bgcpafed) and in an unusual position (bit 4 being the decimal point | |
202 | * for some digits, an additional indicator for others). Fortunately all | |
203 | * eight digits encode their segments in identical ways across the bytes. | |
204 | * | |
205 | * These routines convert LCD segments to characters, and a section of the | |
206 | * DMM packet (which corresponds to the primary or secondary display) to | |
207 | * the text representation of the measurement's value, before regular text | |
208 | * to number conversion is applied, and SI units and their prefixes get | |
209 | * derived from more indicators. It's important to keep in mind similar | |
210 | * indicators exist for main and secondary displays in different locations. | |
211 | */ | |
212 | ||
213 | static char brymen_bm52x_parse_digit(uint8_t b) | |
214 | { | |
215 | switch (b & ~0x10) { | |
216 | /* Sign. */ | |
217 | case 0x40: /* ------g */ return '-'; | |
218 | /* Decimal digits. */ | |
219 | case 0xaf: /* abcdef- */ return '0'; | |
220 | case 0xa0: /* -bc---- */ return '1'; | |
221 | case 0xcb: /* ab-de-g */ return '2'; | |
222 | case 0xe9: /* abcd--g */ return '3'; | |
223 | case 0xe4: /* -bc--fg */ return '4'; | |
224 | case 0x6d: /* a-cd-fg */ return '5'; | |
225 | case 0x6f: /* a-cdefg */ return '6'; | |
226 | case 0xa8: /* abc---- */ return '7'; | |
227 | case 0xef: /* abcdefg */ return '8'; | |
228 | case 0xed: /* abcd-fg */ return '9'; | |
229 | /* Temperature units. */ | |
230 | case 0x0f: /* a--def- */ return 'C'; | |
231 | case 0x4e: /* a---efg */ return 'F'; | |
232 | /* OL condition, and diode and "Auto" modes. */ | |
233 | case 0x07: /* ---def- */ return 'L'; | |
234 | case 0xe3: /* -bcde-g */ return 'd'; | |
235 | case 0x20: /* --c---- */ return 'i'; | |
236 | case 0x63: /* --cde-g */ return 'o'; | |
237 | case 0xee: /* abc-efg */ return 'A'; | |
238 | case 0x23: /* --cde-- */ return 'u'; | |
239 | case 0x47: /* ---defg */ return 't'; | |
240 | /* Blank digit. */ | |
241 | case 0x00: /* ------- */ return '\0'; | |
242 | /* Invalid or unknown segment combination. */ | |
243 | default: | |
244 | sr_warn("Unknown encoding for digit: 0x%02x.", b); | |
245 | return '\0'; | |
246 | } | |
247 | } | |
248 | ||
249 | static int brymen_bm52x_parse_digits(const uint8_t *pkt, size_t pktlen, | |
250 | char *txtbuf, float *value, char *temp_unit, int *digits, int signflag) | |
251 | { | |
252 | uint8_t byte; | |
253 | char *txtptr, txtchar; | |
254 | size_t pos; | |
255 | int ret; | |
256 | ||
257 | txtptr = txtbuf; | |
258 | if (digits) | |
259 | *digits = INT_MIN; | |
260 | ||
261 | if (pkt[0] & signflag) | |
262 | *txtptr++ = '-'; | |
263 | for (pos = 0; pos < pktlen; pos++) { | |
264 | byte = pkt[1 + pos]; | |
265 | txtchar = brymen_bm52x_parse_digit(byte); | |
266 | if (pos == 3 && (txtchar == 'C' || txtchar == 'F')) { | |
267 | if (temp_unit) | |
268 | *temp_unit = txtchar; | |
269 | } else if (txtchar) { | |
270 | *txtptr++ = txtchar; | |
271 | if (digits) | |
272 | (*digits)++; | |
273 | } | |
274 | if (pos < 3 && (byte & 0x10)) { | |
275 | *txtptr++ = '.'; | |
276 | if (digits) | |
277 | *digits = 0; | |
278 | } | |
279 | } | |
280 | *txtptr = '\0'; | |
281 | ||
282 | if (digits && *digits < 0) | |
283 | *digits = 0; | |
284 | ||
285 | ret = value ? sr_atof_ascii(txtbuf, value) : SR_OK; | |
286 | if (ret != SR_OK) { | |
287 | sr_dbg("invalid float string: '%s'", txtbuf); | |
288 | return ret; | |
289 | } | |
290 | ||
291 | return SR_OK; | |
292 | } | |
293 | ||
294 | /* | |
295 | * Extract the measurement value and its properties for one of the | |
296 | * meter's displays from the DMM packet. | |
297 | */ | |
298 | static void brymen_bm52x_parse(const uint8_t *buf, float *floatval, | |
299 | struct sr_datafeed_analog *analog, size_t ch_idx) | |
300 | { | |
301 | char txtbuf[16], temp_unit; | |
302 | int ret, digits, scale; | |
303 | int is_diode, is_auto, is_no_temp, is_ol, is_db, is_main_milli; | |
304 | int is_mm_max, is_mm_min, is_mm_avg, is_mm_dash; | |
305 | ||
306 | temp_unit = '\0'; | |
307 | if (ch_idx == 0) { | |
308 | /* | |
309 | * Main display. Note that _some_ of the second display's | |
310 | * indicators are involved in the inspection of the _first_ | |
311 | * display's measurement value. So we have to get the | |
312 | * second display's text buffer here, too. | |
313 | */ | |
314 | (void)brymen_bm52x_parse_digits(&buf[7], 4, txtbuf, | |
315 | NULL, NULL, NULL, 0); | |
316 | is_diode = strcmp(txtbuf, "diod") == 0; | |
317 | is_auto = strcmp(txtbuf, "Auto") == 0; | |
318 | ret = brymen_bm52x_parse_digits(&buf[2], 4, txtbuf, | |
319 | floatval, &temp_unit, &digits, 0x80); | |
320 | is_ol = strstr(txtbuf, "0L") || strstr(txtbuf, "0.L"); | |
321 | is_no_temp = strcmp(txtbuf, "---C") == 0; | |
322 | is_no_temp |= strcmp(txtbuf, "---F") == 0; | |
323 | if (ret != SR_OK && !is_ol) | |
324 | return; | |
325 | ||
326 | /* SI unit, derived from meter's current function. */ | |
327 | is_db = buf[6] & 0x10; | |
328 | is_main_milli = buf[14] & 0x40; | |
329 | if (buf[14] & 0x20) { | |
330 | analog->meaning->mq = SR_MQ_VOLTAGE; | |
331 | analog->meaning->unit = SR_UNIT_VOLT; | |
332 | if (is_diode) { | |
333 | analog->meaning->mqflags |= SR_MQFLAG_DIODE; | |
334 | analog->meaning->mqflags |= SR_MQFLAG_DC; | |
335 | } | |
336 | } else if (buf[14] & 0x10) { | |
337 | analog->meaning->mq = SR_MQ_CURRENT; | |
338 | analog->meaning->unit = SR_UNIT_AMPERE; | |
339 | } else if (buf[14] & 0x01) { | |
340 | analog->meaning->mq = SR_MQ_CAPACITANCE; | |
341 | analog->meaning->unit = SR_UNIT_FARAD; | |
342 | } else if (buf[14] & 0x02) { | |
343 | analog->meaning->mq = SR_MQ_CONDUCTANCE; | |
344 | analog->meaning->unit = SR_UNIT_SIEMENS; | |
345 | } else if (buf[13] & 0x10) { | |
346 | analog->meaning->mq = SR_MQ_FREQUENCY; | |
347 | analog->meaning->unit = SR_UNIT_HERTZ; | |
348 | } else if (buf[7] & 0x01) { | |
349 | analog->meaning->mq = SR_MQ_CONTINUITY; | |
350 | analog->meaning->unit = SR_UNIT_OHM; | |
351 | } else if (buf[13] & 0x20) { | |
352 | analog->meaning->mq = SR_MQ_RESISTANCE; | |
353 | analog->meaning->unit = SR_UNIT_OHM; | |
354 | } else if (is_db && is_main_milli) { | |
355 | analog->meaning->mq = SR_MQ_POWER; | |
356 | analog->meaning->unit = SR_UNIT_DECIBEL_MW; | |
357 | } else if (buf[14] & 0x04) { | |
358 | analog->meaning->mq = SR_MQ_DUTY_CYCLE; | |
359 | analog->meaning->unit = SR_UNIT_PERCENTAGE; | |
360 | } else if ((buf[2] & 0x09) && temp_unit) { | |
361 | if (is_no_temp) | |
362 | return; | |
363 | analog->meaning->mq = SR_MQ_TEMPERATURE; | |
364 | if (temp_unit == 'F') | |
365 | analog->meaning->unit = SR_UNIT_FAHRENHEIT; | |
366 | else | |
367 | analog->meaning->unit = SR_UNIT_CELSIUS; | |
368 | } | |
369 | ||
370 | /* | |
371 | * Remove the MIN/MAX/AVG indicators when all of them | |
372 | * are shown at the same time (indicating that recording | |
373 | * is active, but live readings are shown). This also | |
374 | * removes the MAX-MIN (V p-p) indication which cannot | |
375 | * get represented by SR_MQFLAG_* means. | |
376 | * | |
377 | * Keep the check conditions separate to simplify future | |
378 | * maintenance when Vp-p gets added. Provide the value of | |
379 | * currently unsupported modes just without flags (show | |
380 | * the maximum amount of LCD content on screen that we | |
381 | * can represent in sigrok). | |
382 | */ | |
383 | is_mm_max = buf[1] & 0x01; | |
384 | is_mm_min = buf[1] & 0x08; | |
385 | is_mm_avg = buf[1] & 0x02; | |
386 | is_mm_dash = buf[1] & 0x04; | |
387 | if (is_mm_max && is_mm_min && is_mm_avg) | |
388 | is_mm_max = is_mm_min = is_mm_avg = 0; | |
389 | if (is_mm_max && is_mm_min && is_mm_dash) | |
390 | is_mm_max = is_mm_min = 0; | |
391 | if (is_mm_max && is_mm_min && !is_mm_dash) | |
392 | is_mm_max = is_mm_min = 0; | |
393 | ||
394 | /* AC/DC/Auto flags. Hold/Min/Max/Rel etc flags. */ | |
395 | if (buf[1] & 0x20) | |
396 | analog->meaning->mqflags |= SR_MQFLAG_DC; | |
397 | if (buf[1] & 0x10) | |
398 | analog->meaning->mqflags |= SR_MQFLAG_AC; | |
399 | if (buf[20] & 0x10) | |
400 | analog->meaning->mqflags |= SR_MQFLAG_AUTORANGE; | |
401 | if (buf[20] & 0x80) | |
402 | analog->meaning->mqflags |= SR_MQFLAG_HOLD; | |
403 | if (is_mm_max) | |
404 | analog->meaning->mqflags |= SR_MQFLAG_MAX; | |
405 | if (is_mm_min) | |
406 | analog->meaning->mqflags |= SR_MQFLAG_MIN; | |
407 | if (is_mm_avg) | |
408 | analog->meaning->mqflags |= SR_MQFLAG_AVG; | |
409 | if (buf[2] & 0x40) | |
410 | analog->meaning->mqflags |= SR_MQFLAG_RELATIVE; | |
411 | ||
412 | /* | |
413 | * Remove the "dBm" indication's "m" indicator before the | |
414 | * SI unit's prefixes get inspected. Avoids an interaction | |
415 | * with the "milli" prefix. Strictly speaking BM525s does | |
416 | * not support dBm, but other models do and we may want | |
417 | * to share the protocol parser. | |
418 | */ | |
419 | if (is_db) | |
420 | is_main_milli = 0; | |
421 | ||
422 | /* SI prefix. */ | |
423 | scale = 0; | |
424 | if (buf[14] & 0x08) /* n */ | |
425 | scale = -9; | |
426 | if (buf[14] & 0x80) /* u */ | |
427 | scale = -6; | |
428 | if (is_main_milli) /* m */ | |
429 | scale = -3; | |
430 | if (buf[13] & 0x80) /* k */ | |
431 | scale = +3; | |
432 | if (buf[13] & 0x40) /* M */ | |
433 | scale = +6; | |
434 | if (scale) { | |
435 | *floatval *= pow(10, scale); | |
436 | digits += -scale; | |
437 | } | |
438 | ||
439 | if (is_ol) | |
440 | *floatval = INFINITY; | |
441 | ||
442 | analog->encoding->digits = digits; | |
443 | analog->spec->spec_digits = digits; | |
444 | } else if (ch_idx == 1) { | |
445 | /* | |
446 | * Secondary display. Also inspect _some_ primary display | |
447 | * data, to determine the secondary display's validity. | |
448 | */ | |
449 | (void)brymen_bm52x_parse_digits(&buf[2], 4, txtbuf, | |
450 | NULL, &temp_unit, NULL, 0x80); | |
451 | ret = brymen_bm52x_parse_digits(&buf[7], 4, txtbuf, | |
452 | floatval, NULL, &digits, 0x20); | |
453 | is_diode = strcmp(txtbuf, "diod") == 0; | |
454 | is_auto = strcmp(txtbuf, "Auto") == 0; | |
455 | is_no_temp = strcmp(txtbuf, "---C") == 0; | |
456 | is_no_temp |= strcmp(txtbuf, "---F") == 0; | |
457 | if (is_diode || is_auto) | |
458 | return; | |
459 | if (is_no_temp) | |
460 | return; | |
461 | ||
462 | /* SI unit. */ | |
463 | if (buf[12] & 0x10) { | |
464 | analog->meaning->mq = SR_MQ_VOLTAGE; | |
465 | analog->meaning->unit = SR_UNIT_VOLT; | |
466 | } else if (buf[12] & 0x20) { | |
467 | analog->meaning->mq = SR_MQ_CURRENT; | |
468 | if (buf[11] & 0x10) | |
469 | analog->meaning->unit = SR_UNIT_PERCENTAGE; | |
470 | else | |
471 | analog->meaning->unit = SR_UNIT_AMPERE; | |
472 | } else if (buf[13] & 0x02) { | |
473 | analog->meaning->mq = SR_MQ_RESISTANCE; | |
474 | analog->meaning->unit = SR_UNIT_OHM; | |
475 | } else if (buf[12] & 0x02) { | |
476 | analog->meaning->mq = SR_MQ_CONDUCTANCE; | |
477 | analog->meaning->unit = SR_UNIT_SIEMENS; | |
478 | } else if (buf[12] & 0x01) { | |
479 | analog->meaning->mq = SR_MQ_CAPACITANCE; | |
480 | analog->meaning->unit = SR_UNIT_FARAD; | |
481 | } else if (buf[7] & 0x06) { | |
482 | if (strstr(txtbuf, "---")) | |
483 | return; | |
484 | analog->meaning->mq = SR_MQ_TEMPERATURE; | |
485 | if (temp_unit == 'F') | |
486 | analog->meaning->unit = SR_UNIT_FAHRENHEIT; | |
487 | else | |
488 | analog->meaning->unit = SR_UNIT_CELSIUS; | |
489 | } else if (buf[13] & 0x01) { | |
490 | analog->meaning->mq = SR_MQ_FREQUENCY; | |
491 | analog->meaning->unit = SR_UNIT_HERTZ; | |
492 | } else if (buf[11] & 0x08) { | |
493 | analog->meaning->mq = SR_MQ_DUTY_CYCLE; | |
494 | analog->meaning->unit = SR_UNIT_PERCENTAGE; | |
495 | } | |
496 | ||
497 | /* DC/AC flags. */ | |
498 | if (buf[7] & 0x80) | |
499 | analog->meaning->mqflags |= SR_MQFLAG_DC; | |
500 | if (buf[7] & 0x40) | |
501 | analog->meaning->mqflags |= SR_MQFLAG_AC; | |
502 | ||
503 | /* SI prefix. */ | |
504 | scale = 0; | |
505 | if (buf[12] & 0x04) /* n */ | |
506 | scale = -9; | |
507 | if (buf[12] & 0x40) /* u */ | |
508 | scale = -6; | |
509 | if (buf[12] & 0x80) /* m */ | |
510 | scale = -3; | |
511 | if (buf[13] & 0x04) /* k */ | |
512 | scale = +3; | |
513 | if (buf[13] & 0x08) /* M */ | |
514 | scale = +6; | |
515 | if (scale) { | |
516 | *floatval *= pow(10, scale); | |
517 | digits += -scale; | |
518 | } | |
519 | ||
520 | analog->encoding->digits = digits; | |
521 | analog->spec->spec_digits = digits; | |
522 | } | |
523 | ||
524 | if (buf[7] & 0x08) | |
525 | sr_warn("Battery is low."); | |
526 | } | |
527 | ||
528 | SR_PRIV int sr_brymen_bm52x_parse(const uint8_t *buf, float *val, | |
529 | struct sr_datafeed_analog *analog, void *info) | |
530 | { | |
531 | struct brymen_bm52x_info *info_local; | |
532 | size_t ch_idx; | |
533 | ||
534 | /* | |
535 | * Scan a portion of the received DMM packet which corresponds | |
536 | * to the caller's specified display. Then prepare to scan a | |
537 | * different portion of the packet for another display. This | |
538 | * routine gets called multiple times for one received packet. | |
539 | */ | |
540 | info_local = info; | |
541 | ch_idx = info_local->ch_idx; | |
542 | brymen_bm52x_parse(buf, val, analog, ch_idx); | |
543 | info_local->ch_idx = ch_idx + 1; | |
544 | ||
545 | return SR_OK; | |
546 | } | |
547 | ||
548 | /* | |
549 | * The above code paths support live readings ("real-time download"). | |
550 | * The below code paths support recordings ("memory data sets") which | |
551 | * use different requests and responses and measurement representation | |
552 | * which feels like "a different meter". | |
553 | */ | |
554 | ||
555 | /* | |
556 | * Developer notes, example data for recorded sessions. | |
557 | * | |
558 | * model | |
559 | * 01 | |
560 | * total bytes | |
561 | * e6 02 00 | |
562 | * session count | |
563 | * 01 00 | |
564 | * "DLE/STX" marker | |
565 | * ee a0 | |
566 | * PS/NS addresses | |
567 | * 8a 03 a0 60 03 a0 | |
568 | * func/sel/stat (DC-V, single display) | |
569 | * 02 00 00 | |
570 | * session page length in bytes (3 * 240) | |
571 | * d0 02 00 | |
572 | * main[/secondary] display data | |
573 | * 00 00 00 00 | |
574 | * checksums and padding | |
575 | * 7c 05 00 00 00 00 00 00 | |
576 | * 00 00 80 00 00 80 00 00 80 00 00 80 00 00 00 00 00 80 00 00 80 00 00 80 80 03 00 00 00 00 00 00 | |
577 | * 00 00 00 00 00 00 00 00 80 00 00 80 00 00 80 00 00 80 00 00 80 00 00 80 00 03 00 00 00 00 00 00 | |
578 | * ... | |
579 | * 00 00 80 00 00 00 00 00 00 00 00 80 00 00 80 00 00 80 00 00 80 00 00 80 00 03 00 00 00 00 00 00 | |
580 | * 00 00 80 00 00 80 00 00 80 00 00 80 00 00 80 00 00 80 00 00 | |
581 | * "DLE/ETX" marker | |
582 | * ee c0 | |
583 | * ae 04 00 00 00 00 00 00 | |
584 | * | |
585 | * - Checksum in bytes[25:24] is the mere sum of bytes[0:23]. | |
586 | * - Model ID is 0 or 1 -- does this translate to BM521s and BM525s? | |
587 | * - Total byte count _includes_ everything starting at model ID. | |
588 | * - There is no measurements count for a session page, but its length | |
589 | * in bytes, and a dual display flag, which lets us derive the count. | |
590 | * - STX/ETX/DLE markers don't use the expected ASCII codes. | |
591 | */ | |
592 | ||
593 | /* | |
594 | * See vendor doc table 3.1 "Logging interval". Includes sub-1Hz rates, | |
595 | * but also sub-1s intervals. Let's keep both presentations at hand. | |
596 | */ | |
597 | static const struct { | |
598 | unsigned int ival_secs; | |
599 | unsigned int freq_rate; | |
600 | } bm52x_rec_ivals[] = { | |
601 | [ 0] = { 0, 20, }, | |
602 | [ 1] = { 0, 10, }, | |
603 | [ 2] = { 0, 2, }, | |
604 | [ 3] = { 1, 1, }, | |
605 | [ 4] = { 2, 0, }, | |
606 | [ 5] = { 3, 0, }, | |
607 | [ 6] = { 4, 0, }, | |
608 | [ 7] = { 5, 0, }, | |
609 | [ 8] = { 10, 0, }, | |
610 | [ 9] = { 15, 0, }, | |
611 | [10] = { 30, 0, }, | |
612 | [11] = { 60, 0, }, | |
613 | [12] = { 120, 0, }, | |
614 | [13] = { 180, 0, }, | |
615 | [14] = { 300, 0, }, | |
616 | [15] = { 600, 0, }, | |
617 | }; | |
618 | ||
619 | /* | |
620 | * See vendor doc table 6 "Range bits". Temperature is not listed there | |
621 | * but keeping it here unifies the processing code paths. | |
622 | */ | |
623 | static const int bm52x_ranges_volt[16] = { 3, 2, 1, 0, }; | |
624 | static const int bm52x_ranges_millivolt[16] = { 5, 4, }; | |
625 | static const int bm52x_ranges_freq[16] = { 3, 2, 1, 0, -1, -2, -3, }; | |
626 | static const int bm52x_ranges_duty[16] = { 2, 1, }; | |
627 | static const int bm52x_ranges_ohm[16] = { 1, 0, -1, -2, -3, -4, }; | |
628 | static const int bm52x_ranges_cond[16] = { 11, }; | |
629 | static const int bm52x_ranges_cap[16] = { 11, 10, 9, 8, 7, 6, 5, }; | |
630 | static const int bm52x_ranges_diode[16] = { 3, }; | |
631 | static const int bm52x_ranges_temp[16] = { 0, }; | |
632 | static const int bm52x_ranges_amp[16] = { 3, 2, }; | |
633 | static const int bm52x_ranges_milliamp[16] = { 5, 4, }; | |
634 | static const int bm52x_ranges_microamp[16] = { 7, 6, }; | |
635 | ||
636 | /** Calculate checksum of four-HID-report responses (recordings). */ | |
637 | static uint16_t bm52x_rec_checksum(const uint8_t *b, size_t l) | |
638 | { | |
639 | uint16_t cs; | |
640 | ||
641 | cs = 0; | |
642 | while (l--) | |
643 | cs += *b++; | |
644 | ||
645 | return cs; | |
646 | } | |
647 | ||
648 | /** | |
649 | * Retrieve the first/next chunk of recording information. | |
650 | * Support for live readings is theoretical, and unused/untested. | |
651 | */ | |
652 | #ifdef HAVE_SERIAL_COMM | |
653 | static int bm52x_rec_next_rsp(struct sr_serial_dev_inst *serial, | |
654 | enum bm52x_reqtype req, struct brymen_bm52x_state *state) | |
655 | { | |
656 | uint8_t *b; | |
657 | size_t l; | |
658 | int ret; | |
659 | ||
660 | /* Seed internal state when sending the HEAD request. */ | |
661 | if (req == REQ_REC_HEAD || req == REQ_LIVE_READ_520) | |
662 | memset(&state->rsp, 0, sizeof(state->rsp)); | |
663 | ||
664 | /* Move unprocessed content to the front. */ | |
665 | if (state->rsp.read_pos) { | |
666 | b = &state->rsp.buff[0]; | |
667 | l = state->rsp.fill_pos - state->rsp.read_pos; | |
668 | if (l) | |
669 | memmove(&b[0], &b[state->rsp.read_pos], l); | |
670 | state->rsp.fill_pos -= state->rsp.read_pos; | |
671 | state->rsp.read_pos = 0; | |
672 | } | |
673 | ||
674 | /* Avoid queries for non-existing data. Limit NEXT requests. */ | |
675 | if (req == REQ_REC_NEXT && !state->rsp.remain) | |
676 | return SR_ERR_IO; | |
677 | ||
678 | /* Add another response chunk to the read buffer. */ | |
679 | b = &state->rsp.buff[state->rsp.fill_pos]; | |
680 | l = req == REQ_LIVE_READ_520 ? 24 : 32; | |
681 | if (sizeof(state->rsp.buff) - state->rsp.fill_pos < l) | |
682 | return SR_ERR_BUG; | |
683 | ret = bm52x_send_req(serial, req); | |
684 | if (ret != SR_OK) | |
685 | return ret; | |
686 | ret = serial_read_blocking(serial, b, l, 1000); | |
687 | if (ret < 0) | |
688 | return ret; | |
689 | if ((size_t)ret != l) | |
690 | return SR_ERR_IO; | |
691 | state->rsp.fill_pos += l; | |
692 | ||
693 | /* Devel support: dump the new receive data. */ | |
694 | if (sr_log_loglevel_get() >= SR_LOG_SPEW) { | |
695 | GString *text; | |
696 | const char *req_text; | |
697 | ||
698 | req_text = (req == REQ_LIVE_READ_520) ? "LIVE" : | |
699 | (req == REQ_REC_HEAD) ? "MEM HEAD" : | |
700 | (req == REQ_REC_NEXT) ? "MEM NEXT" : | |
701 | (req == REQ_REC_CURR) ? "MEM CURR" : | |
702 | "<inv>"; | |
703 | text = sr_hexdump_new(b, l); | |
704 | sr_spew("%s: %s", req_text, text->str); | |
705 | sr_hexdump_free(text); | |
706 | } | |
707 | ||
708 | /* Verify checksum. No CURR repetition is attempted here. */ | |
709 | if (l > 24) { | |
710 | uint16_t calc, rcvd; | |
711 | ||
712 | calc = bm52x_rec_checksum(b, 24); | |
713 | rcvd = read_u16le(&b[24]); | |
714 | if (calc != rcvd) | |
715 | return SR_ERR_DATA; | |
716 | state->rsp.fill_pos -= 32 - 24; | |
717 | } | |
718 | ||
719 | /* Seed amount of total available data from HEAD response. */ | |
720 | if (req == REQ_REC_HEAD) { | |
721 | const uint8_t *rdptr; | |
722 | ||
723 | rdptr = &state->rsp.buff[0]; | |
724 | (void)read_u8_inc(&rdptr); /* model ID */ | |
725 | state->rsp.remain = read_u24le_inc(&rdptr); /* byte count */ | |
726 | } | |
727 | ||
728 | return SR_OK; | |
729 | } | |
730 | #else /* have serial comm */ | |
731 | static int bm52x_rec_next_rsp(struct sr_serial_dev_inst *serial, | |
732 | enum bm52x_reqtype req, struct brymen_bm52x_state *state) | |
733 | { | |
734 | (void)serial; | |
735 | (void)req; | |
736 | (void)state; | |
737 | (void)bm52x_rec_checksum; | |
738 | return SR_ERR_NA; | |
739 | } | |
740 | #endif /* have serial comm */ | |
741 | ||
742 | /** Make sure a minimum amount of response data is available. */ | |
743 | static const uint8_t *bm52x_rec_ensure(struct sr_serial_dev_inst *serial, | |
744 | size_t min_count, struct brymen_bm52x_state *state) | |
745 | { | |
746 | size_t got; | |
747 | const uint8_t *read_ptr; | |
748 | int ret; | |
749 | ||
750 | got = state->rsp.fill_pos - state->rsp.read_pos; | |
751 | if (got >= min_count) { | |
752 | read_ptr = &state->rsp.buff[state->rsp.read_pos]; | |
753 | return read_ptr; | |
754 | } | |
755 | ret = bm52x_rec_next_rsp(serial, REQ_REC_NEXT, state); | |
756 | if (ret < 0) | |
757 | return NULL; | |
758 | read_ptr = &state->rsp.buff[state->rsp.read_pos]; | |
759 | return read_ptr; | |
760 | } | |
761 | ||
762 | /** Get a u8 quantity of response data, with auto-fetch and position increment. */ | |
763 | static uint8_t bm52x_rec_get_u8(struct sr_serial_dev_inst *serial, | |
764 | struct brymen_bm52x_state *state) | |
765 | { | |
766 | const uint8_t *read_ptr; | |
767 | uint8_t value; | |
768 | size_t length; | |
769 | ||
770 | length = sizeof(value); | |
771 | if (length > state->rsp.remain) { | |
772 | state->rsp.remain = 0; | |
773 | return 0; | |
774 | } | |
775 | read_ptr = bm52x_rec_ensure(serial, length, state); | |
776 | if (!read_ptr) | |
777 | return 0; | |
778 | value = read_u8(read_ptr); | |
779 | state->rsp.read_pos += length; | |
780 | state->rsp.remain -= length; | |
781 | return value; | |
782 | } | |
783 | ||
784 | /** Get a u16 quantity of response data, with auto-fetch and position increment. */ | |
785 | static uint16_t bm52x_rec_get_u16(struct sr_serial_dev_inst *serial, | |
786 | struct brymen_bm52x_state *state) | |
787 | { | |
788 | const uint8_t *read_ptr; | |
789 | uint16_t value; | |
790 | size_t length; | |
791 | ||
792 | length = sizeof(value); | |
793 | if (length > state->rsp.remain) { | |
794 | state->rsp.remain = 0; | |
795 | return 0; | |
796 | } | |
797 | read_ptr = bm52x_rec_ensure(serial, length, state); | |
798 | if (!read_ptr) | |
799 | return 0; | |
800 | value = read_u16le(read_ptr); | |
801 | state->rsp.read_pos += length; | |
802 | state->rsp.remain -= length; | |
803 | return value; | |
804 | } | |
805 | ||
806 | /** Get a u24 quantity of response data, with auto-fetch and position increment. */ | |
807 | static uint32_t bm52x_rec_get_u24(struct sr_serial_dev_inst *serial, | |
808 | struct brymen_bm52x_state *state) | |
809 | { | |
810 | const uint8_t *read_ptr; | |
811 | uint32_t value; | |
812 | size_t length; | |
813 | ||
814 | length = 24 / sizeof(uint8_t) / 8; | |
815 | if (length > state->rsp.remain) { | |
816 | state->rsp.remain = 0; | |
817 | return 0; | |
818 | } | |
819 | read_ptr = bm52x_rec_ensure(serial, length, state); | |
820 | if (!read_ptr) | |
821 | return 0; | |
822 | value = read_u24le(read_ptr); | |
823 | state->rsp.read_pos += length; | |
824 | state->rsp.remain -= length; | |
825 | return value; | |
826 | } | |
827 | ||
828 | /** Get the HEAD chunk of recording data, determine session page count. */ | |
829 | static int bm52x_rec_get_count(struct brymen_bm52x_state *state, | |
830 | struct sr_serial_dev_inst *serial) | |
831 | { | |
832 | int ret; | |
833 | size_t byte_count, sess_count; | |
834 | ||
835 | memset(&state->rsp, 0, sizeof(state->rsp)); | |
836 | ret = bm52x_rec_next_rsp(serial, REQ_REC_HEAD, state); | |
837 | if (ret != SR_OK) | |
838 | return ret; | |
839 | ||
840 | (void)bm52x_rec_get_u8(serial, state); /* model ID */ | |
841 | byte_count = bm52x_rec_get_u24(serial, state); /* total bytes count */ | |
842 | sess_count = bm52x_rec_get_u16(serial, state); /* session count */ | |
843 | sr_dbg("bytes %zu, sessions %zu", byte_count, sess_count); | |
844 | ||
845 | return sess_count; | |
846 | } | |
847 | ||
848 | static double bm52x_rec_get_value(uint32_t raw, const int *ranges, int *digits) | |
849 | { | |
850 | uint16_t val_digs; | |
851 | gboolean is_neg, is_ol, low_batt; | |
852 | uint8_t range; | |
853 | double value; | |
854 | int decimals; | |
855 | ||
856 | val_digs = raw >> 8; | |
857 | is_neg = raw & (1u << 7); | |
858 | is_ol = raw & (1u << 6); | |
859 | low_batt = raw & (1u << 5); | |
860 | range = raw & 0x0f; | |
861 | sr_dbg("item: %s%u, %s %s, range %01x", | |
862 | is_neg ? "-" : "+", val_digs, | |
863 | is_ol ? "OL" : "ol", low_batt ? "BATT" : "batt", | |
864 | range); | |
865 | ||
866 | /* Convert to number. OL takes precedence. */ | |
867 | *digits = 0; | |
868 | value = val_digs; | |
869 | if (ranges && ranges[range]) { | |
870 | decimals = ranges[range]; | |
871 | value /= pow(10, decimals); | |
872 | *digits = decimals; | |
873 | } | |
874 | if (is_ol) | |
875 | value = INFINITY; | |
876 | if (is_neg) | |
877 | value *= -1; | |
878 | ||
879 | /* | |
880 | * Implementor's note: "Low battery" conditions are worth a | |
881 | * warning since the reading could be incorrect. Rate limiting | |
882 | * is not needed since the Brymen DMM will stop recording in | |
883 | * that case, so at most the last sample in the session page | |
884 | * could be affected. | |
885 | */ | |
886 | if (low_batt) | |
887 | sr_warn("Recording was taken when battery was low."); | |
888 | ||
889 | return value; | |
890 | } | |
891 | ||
892 | static int bm52x_rec_prep_feed(uint8_t bfunc, uint8_t bsel, uint8_t bstat, | |
893 | struct sr_datafeed_analog *analog1, struct sr_datafeed_analog *analog2, | |
894 | double *value1, double *value2, const int **ranges1, const int **ranges2, | |
895 | const struct sr_dev_inst *sdi) | |
896 | { | |
897 | struct sr_channel *ch; | |
898 | gboolean is_amp, is_deg_f; | |
899 | enum sr_mq *mq1, *mq2; | |
900 | enum sr_unit *unit1, *unit2; | |
901 | enum sr_mqflag *mqf1, *mqf2; | |
902 | enum sr_unit unit_c_f; | |
903 | const int *r_a_ma; | |
904 | ||
905 | /* Prepare general submission on first channel. */ | |
906 | analog1->data = value1; | |
907 | analog1->encoding->unitsize = sizeof(*value1); | |
908 | analog1->num_samples = 1; | |
909 | ch = g_slist_nth_data(sdi->channels, 0); | |
910 | analog1->meaning->channels = g_slist_append(NULL, ch); | |
911 | *ranges1 = NULL; | |
912 | mq1 = &analog1->meaning->mq; | |
913 | mqf1 = &analog1->meaning->mqflags; | |
914 | unit1 = &analog1->meaning->unit; | |
915 | ||
916 | /* Prepare general submission on second channel. */ | |
917 | analog2->data = value2; | |
918 | analog2->encoding->unitsize = sizeof(*value2); | |
919 | analog2->num_samples = 1; | |
920 | ch = g_slist_nth_data(sdi->channels, 1); | |
921 | analog2->meaning->channels = g_slist_append(NULL, ch); | |
922 | *ranges2 = NULL; | |
923 | mq2 = &analog2->meaning->mq; | |
924 | mqf2 = &analog2->meaning->mqflags; | |
925 | unit2 = &analog2->meaning->unit; | |
926 | ||
927 | /* Derive main/secondary display functions from bfunc/bsel/bstat. */ | |
928 | is_amp = bstat & (1u << 5); | |
929 | is_deg_f = bstat & (1u << 4); | |
930 | switch (bfunc) { | |
931 | case 1: /* AC V */ | |
932 | switch (bsel) { | |
933 | case 0: /* AC volt, Hz */ | |
934 | *ranges1 = bm52x_ranges_volt; | |
935 | *mq1 = SR_MQ_VOLTAGE; | |
936 | *mqf1 |= SR_MQFLAG_AC; | |
937 | *unit1 = SR_UNIT_VOLT; | |
938 | *ranges2 = bm52x_ranges_freq; | |
939 | *mq2 = SR_MQ_FREQUENCY; | |
940 | *unit2 = SR_UNIT_HERTZ; | |
941 | break; | |
942 | case 1: /* Hz, AC volt */ | |
943 | *ranges1 = bm52x_ranges_freq; | |
944 | *mq1 = SR_MQ_FREQUENCY; | |
945 | *unit1 = SR_UNIT_HERTZ; | |
946 | *ranges2 = bm52x_ranges_volt; | |
947 | *mq2 = SR_MQ_VOLTAGE; | |
948 | *mqf2 |= SR_MQFLAG_AC; | |
949 | *unit2 = SR_UNIT_VOLT; | |
950 | break; | |
951 | default: | |
952 | return SR_ERR_DATA; | |
953 | } | |
954 | break; | |
955 | case 2: /* DC V */ | |
956 | switch (bsel) { | |
957 | case 0: /* DC V, - */ | |
958 | *ranges1 = bm52x_ranges_volt; | |
959 | *mq1 = SR_MQ_VOLTAGE; | |
960 | *mqf1 |= SR_MQFLAG_DC; | |
961 | *unit1 = SR_UNIT_VOLT; | |
962 | break; | |
963 | case 1: /* DC V, AC V */ | |
964 | *ranges1 = bm52x_ranges_volt; | |
965 | *mq1 = SR_MQ_VOLTAGE; | |
966 | *mqf1 |= SR_MQFLAG_DC; | |
967 | *unit1 = SR_UNIT_VOLT; | |
968 | *ranges2 = bm52x_ranges_volt; | |
969 | *mq2 = SR_MQ_VOLTAGE; | |
970 | *mqf2 |= SR_MQFLAG_AC; | |
971 | *unit2 = SR_UNIT_VOLT; | |
972 | break; | |
973 | case 2: /* DC+AC V, AC V */ | |
974 | *ranges1 = bm52x_ranges_volt; | |
975 | *mq1 = SR_MQ_VOLTAGE; | |
976 | *mqf1 |= SR_MQFLAG_DC; | |
977 | *mqf1 |= SR_MQFLAG_AC; | |
978 | *unit1 = SR_UNIT_VOLT; | |
979 | *ranges2 = bm52x_ranges_volt; | |
980 | *mq2 = SR_MQ_VOLTAGE; | |
981 | *mqf2 |= SR_MQFLAG_AC; | |
982 | *unit2 = SR_UNIT_VOLT; | |
983 | break; | |
984 | default: | |
985 | return SR_ERR_DATA; | |
986 | } | |
987 | break; | |
988 | case 3: /* DC mV */ | |
989 | switch (bsel) { | |
990 | case 0: /* DC mV, - */ | |
991 | *ranges1 = bm52x_ranges_millivolt; | |
992 | *mq1 = SR_MQ_VOLTAGE; | |
993 | *mqf1 |= SR_MQFLAG_DC; | |
994 | *unit1 = SR_UNIT_VOLT; | |
995 | break; | |
996 | case 1: /* DC mV, AC mV */ | |
997 | *ranges1 = bm52x_ranges_millivolt; | |
998 | *mq1 = SR_MQ_VOLTAGE; | |
999 | *mqf1 |= SR_MQFLAG_DC; | |
1000 | *unit1 = SR_UNIT_VOLT; | |
1001 | *ranges2 = bm52x_ranges_millivolt; | |
1002 | *mq2 = SR_MQ_VOLTAGE; | |
1003 | *mqf2 |= SR_MQFLAG_AC; | |
1004 | *unit2 = SR_UNIT_VOLT; | |
1005 | break; | |
1006 | case 2: /* DC+AC mV, AC mV */ | |
1007 | *ranges1 = bm52x_ranges_millivolt; | |
1008 | *mq1 = SR_MQ_VOLTAGE; | |
1009 | *mqf1 |= SR_MQFLAG_DC; | |
1010 | *mqf1 |= SR_MQFLAG_AC; | |
1011 | *unit1 = SR_UNIT_VOLT; | |
1012 | *ranges2 = bm52x_ranges_millivolt; | |
1013 | *mq2 = SR_MQ_VOLTAGE; | |
1014 | *mqf2 |= SR_MQFLAG_AC; | |
1015 | *unit2 = SR_UNIT_VOLT; | |
1016 | break; | |
1017 | case 3: /* Hz, - */ | |
1018 | *ranges1 = bm52x_ranges_freq; | |
1019 | *mq1 = SR_MQ_FREQUENCY; | |
1020 | *unit1 = SR_UNIT_HERTZ; | |
1021 | break; | |
1022 | case 4: /* %, - */ | |
1023 | *ranges1 = bm52x_ranges_duty; | |
1024 | *mq1 = SR_MQ_DUTY_CYCLE; | |
1025 | *unit1 = SR_UNIT_PERCENTAGE; | |
1026 | break; | |
1027 | default: | |
1028 | return SR_ERR_DATA; | |
1029 | } | |
1030 | break; | |
1031 | case 4: /* AC mV */ | |
1032 | switch (bsel) { | |
1033 | case 0: /* AC mV, Hz */ | |
1034 | *ranges1 = bm52x_ranges_millivolt; | |
1035 | *mq1 = SR_MQ_VOLTAGE; | |
1036 | *mqf1 |= SR_MQFLAG_AC; | |
1037 | *unit1 = SR_UNIT_VOLT; | |
1038 | *ranges2 = bm52x_ranges_freq; | |
1039 | *mq2 = SR_MQ_FREQUENCY; | |
1040 | *unit2 = SR_UNIT_HERTZ; | |
1041 | break; | |
1042 | case 1: /* Hz, AC mV */ | |
1043 | *ranges1 = bm52x_ranges_freq; | |
1044 | *mq1 = SR_MQ_FREQUENCY; | |
1045 | *unit1 = SR_UNIT_HERTZ; | |
1046 | *ranges2 = bm52x_ranges_millivolt; | |
1047 | *mq2 = SR_MQ_VOLTAGE; | |
1048 | *mqf2 |= SR_MQFLAG_AC; | |
1049 | *unit2 = SR_UNIT_VOLT; | |
1050 | break; | |
1051 | default: | |
1052 | return SR_ERR_DATA; | |
1053 | } | |
1054 | break; | |
1055 | case 5: /* Res/Cond/Cont */ | |
1056 | switch (bsel) { | |
1057 | case 0: /* Resistance */ | |
1058 | *ranges1 = bm52x_ranges_ohm; | |
1059 | *mq1 = SR_MQ_RESISTANCE; | |
1060 | *unit1 = SR_UNIT_OHM; | |
1061 | break; | |
1062 | case 1: /* Siemens */ | |
1063 | *ranges1 = bm52x_ranges_cond; | |
1064 | *mq1 = SR_MQ_CONDUCTANCE; | |
1065 | *unit1 = SR_UNIT_SIEMENS; | |
1066 | break; | |
1067 | case 2: /* Continuity */ | |
1068 | *ranges1 = bm52x_ranges_ohm; | |
1069 | *mq1 = SR_MQ_CONTINUITY; | |
1070 | *unit1 = SR_UNIT_OHM; | |
1071 | break; | |
1072 | default: | |
1073 | return SR_ERR_DATA; | |
1074 | } | |
1075 | break; | |
1076 | case 6: /* Temperature */ | |
1077 | unit_c_f = is_deg_f ? SR_UNIT_FAHRENHEIT : SR_UNIT_CELSIUS; | |
1078 | switch (bsel) { | |
1079 | case 0: /* T1, - */ | |
1080 | *ranges1 = bm52x_ranges_temp; | |
1081 | *mq1 = SR_MQ_TEMPERATURE; | |
1082 | *unit1 = unit_c_f; | |
1083 | break; | |
1084 | case 1: /* T2, - */ | |
1085 | *ranges1 = bm52x_ranges_temp; | |
1086 | *mq1 = SR_MQ_TEMPERATURE; | |
1087 | *unit1 = unit_c_f; | |
1088 | break; | |
1089 | case 2: /* T1, T2 */ | |
1090 | *ranges1 = bm52x_ranges_temp; | |
1091 | *mq1 = SR_MQ_TEMPERATURE; | |
1092 | *unit1 = unit_c_f; | |
1093 | *ranges2 = bm52x_ranges_temp; | |
1094 | *mq2 = SR_MQ_TEMPERATURE; | |
1095 | *unit2 = unit_c_f; | |
1096 | break; | |
1097 | case 3: /* T1-T2, T2 */ | |
1098 | *ranges1 = bm52x_ranges_temp; | |
1099 | *mq1 = SR_MQ_TEMPERATURE; | |
1100 | *unit1 = unit_c_f; | |
1101 | *ranges2 = bm52x_ranges_temp; | |
1102 | *mq2 = SR_MQ_TEMPERATURE; | |
1103 | *unit2 = unit_c_f; | |
1104 | break; | |
1105 | default: | |
1106 | return SR_ERR_DATA; | |
1107 | } | |
1108 | break; | |
1109 | case 7: /* Cap/Diode */ | |
1110 | switch (bsel) { | |
1111 | case 0: /* Capacitance, - */ | |
1112 | *ranges1 = bm52x_ranges_cap; | |
1113 | *mq1 = SR_MQ_CAPACITANCE; | |
1114 | *unit1 |= SR_UNIT_FARAD; | |
1115 | break; | |
1116 | case 1: /* Diode voltage, - */ | |
1117 | *ranges1 = bm52x_ranges_diode; | |
1118 | *mq1 = SR_MQ_VOLTAGE; | |
1119 | *mqf1 |= SR_MQFLAG_DC; | |
1120 | *mqf1 |= SR_MQFLAG_DIODE; | |
1121 | *unit1 |= SR_UNIT_VOLT; | |
1122 | break; | |
1123 | default: | |
1124 | return SR_ERR_DATA; | |
1125 | } | |
1126 | break; | |
1127 | case 8: /* DC A/mA */ | |
1128 | r_a_ma = is_amp ? bm52x_ranges_amp : bm52x_ranges_milliamp; | |
1129 | switch (bsel) { | |
1130 | case 0: /* DC A/mA, - */ | |
1131 | *ranges1 = r_a_ma; | |
1132 | *mq1 = SR_MQ_CURRENT; | |
1133 | *mqf1 |= SR_MQFLAG_DC; | |
1134 | *unit1 = SR_UNIT_AMPERE; | |
1135 | break; | |
1136 | case 1: /* DC A/mA, AC A/mA */ | |
1137 | *ranges1 = r_a_ma; | |
1138 | *mq1 = SR_MQ_CURRENT; | |
1139 | *mqf1 |= SR_MQFLAG_DC; | |
1140 | *unit1 = SR_UNIT_AMPERE; | |
1141 | *ranges2 = r_a_ma; | |
1142 | *mq2 = SR_MQ_CURRENT; | |
1143 | *mqf2 |= SR_MQFLAG_AC; | |
1144 | *unit2 = SR_UNIT_AMPERE; | |
1145 | break; | |
1146 | case 2: /* DC+AC A/mA, AC A/mA */ | |
1147 | *ranges1 = r_a_ma; | |
1148 | *mq1 = SR_MQ_CURRENT; | |
1149 | *mqf1 |= SR_MQFLAG_DC; | |
1150 | *mqf1 |= SR_MQFLAG_AC; | |
1151 | *unit1 = SR_UNIT_AMPERE; | |
1152 | *ranges2 = r_a_ma; | |
1153 | *mq2 = SR_MQ_CURRENT; | |
1154 | *mqf2 |= SR_MQFLAG_AC; | |
1155 | *unit2 = SR_UNIT_AMPERE; | |
1156 | break; | |
1157 | case 3: /* AC A/mA, Hz */ | |
1158 | *ranges1 = r_a_ma; | |
1159 | *mq1 = SR_MQ_CURRENT; | |
1160 | *mqf1 |= SR_MQFLAG_AC; | |
1161 | *unit1 = SR_UNIT_AMPERE; | |
1162 | *ranges2 = bm52x_ranges_freq; | |
1163 | *mq2 = SR_MQ_FREQUENCY; | |
1164 | *unit2 = SR_UNIT_HERTZ; | |
1165 | break; | |
1166 | default: | |
1167 | return SR_ERR_DATA; | |
1168 | } | |
1169 | break; | |
1170 | case 9: /* DC uA */ | |
1171 | switch (bsel) { | |
1172 | case 0: /* DC uA, - */ | |
1173 | *ranges1 = bm52x_ranges_microamp; | |
1174 | *mq1 = SR_MQ_CURRENT; | |
1175 | *mqf1 |= SR_MQFLAG_DC; | |
1176 | *unit1 = SR_UNIT_AMPERE; | |
1177 | break; | |
1178 | case 1: /* DC uA, AC uA */ | |
1179 | *ranges1 = bm52x_ranges_microamp; | |
1180 | *mq1 = SR_MQ_CURRENT; | |
1181 | *mqf1 |= SR_MQFLAG_DC; | |
1182 | *unit1 = SR_UNIT_AMPERE; | |
1183 | *ranges2 = bm52x_ranges_microamp; | |
1184 | *mq2 = SR_MQ_CURRENT; | |
1185 | *mqf2 |= SR_MQFLAG_AC; | |
1186 | *unit2 = SR_UNIT_AMPERE; | |
1187 | break; | |
1188 | case 2: /* DC+AC uA, AC uA */ | |
1189 | *ranges1 = bm52x_ranges_microamp; | |
1190 | *mq1 = SR_MQ_CURRENT; | |
1191 | *mqf1 |= SR_MQFLAG_DC; | |
1192 | *mqf1 |= SR_MQFLAG_AC; | |
1193 | *unit1 = SR_UNIT_AMPERE; | |
1194 | *ranges2 = bm52x_ranges_microamp; | |
1195 | *mq2 = SR_MQ_CURRENT; | |
1196 | *mqf2 |= SR_MQFLAG_AC; | |
1197 | *unit2 = SR_UNIT_AMPERE; | |
1198 | break; | |
1199 | case 3: /* AC uA, Hz */ | |
1200 | *ranges1 = bm52x_ranges_microamp; | |
1201 | *mq1 = SR_MQ_CURRENT; | |
1202 | *mqf1 |= SR_MQFLAG_AC; | |
1203 | *unit1 = SR_UNIT_AMPERE; | |
1204 | *ranges2 = bm52x_ranges_freq; | |
1205 | *mq2 = SR_MQ_FREQUENCY; | |
1206 | *unit2 = SR_UNIT_HERTZ; | |
1207 | break; | |
1208 | default: | |
1209 | return SR_ERR_DATA; | |
1210 | } | |
1211 | break; | |
1212 | default: | |
1213 | return SR_ERR_DATA; | |
1214 | } | |
1215 | ||
1216 | return SR_OK; | |
1217 | } | |
1218 | ||
1219 | /** Traverse one recorded session page, optionally feed session bus. */ | |
1220 | static int bm52x_rec_read_page_int(const struct sr_dev_inst *sdi, | |
1221 | struct brymen_bm52x_state *state, struct sr_serial_dev_inst *serial, | |
1222 | gboolean skip) | |
1223 | { | |
1224 | uint8_t bfunc, bsel, bstat; | |
1225 | uint8_t ival; | |
1226 | gboolean has_sec_disp; | |
1227 | size_t page_len, meas_len, meas_count; | |
1228 | uint32_t meas_data; | |
1229 | struct sr_datafeed_packet packet; | |
1230 | struct sr_datafeed_analog analog1, analog2; | |
1231 | struct sr_analog_encoding encoding1, encoding2; | |
1232 | struct sr_analog_meaning meaning1, meaning2; | |
1233 | struct sr_analog_spec spec1, spec2; | |
1234 | int digits, ret; | |
1235 | double values[2]; | |
1236 | const int *ranges1, *ranges2; | |
1237 | enum sr_configkey key; | |
1238 | uint64_t num; | |
1239 | ||
1240 | sr_dbg("progress: %s, %s", __func__, skip ? "skip" : "feed"); | |
1241 | ||
1242 | /* Get the header information of the session page (raw). */ | |
1243 | if (bm52x_rec_get_u8(serial, state) != 0xee) /* "DLE" */ | |
1244 | return SR_ERR_DATA; | |
1245 | if (bm52x_rec_get_u8(serial, state) != 0xa0) /* "STX" */ | |
1246 | return SR_ERR_DATA; | |
1247 | (void)bm52x_rec_get_u24(serial, state); /* prev page addr */ | |
1248 | (void)bm52x_rec_get_u24(serial, state); /* next page addr */ | |
1249 | bfunc = bm52x_rec_get_u8(serial, state); /* meter function */ | |
1250 | bsel = bm52x_rec_get_u8(serial, state); /* fun selection */ | |
1251 | bstat = bm52x_rec_get_u8(serial, state); /* status */ | |
1252 | page_len = bm52x_rec_get_u24(serial, state); /* page length */ | |
1253 | sr_dbg("page head: func/sel/state %02x/%02x/%02x, len %zu", | |
1254 | bfunc, bsel, bstat, page_len); | |
1255 | ||
1256 | /* Interpret the header information of the session page. */ | |
1257 | ival = bstat & 0x0f; | |
1258 | has_sec_disp = bstat & (1u << 7); | |
1259 | meas_len = (has_sec_disp ? 2 : 1) * 3; | |
1260 | if (page_len % meas_len) | |
1261 | return SR_ERR_DATA; | |
1262 | meas_count = page_len / meas_len; | |
1263 | sr_dbg("page head: ival %u, %s, samples %zu", | |
1264 | ival, has_sec_disp ? "dual" : "main", meas_count); | |
1265 | ||
1266 | /* Prepare feed to the sigrok session. Send rate/interval. */ | |
1267 | sr_analog_init(&analog1, &encoding1, &meaning1, &spec1, 0); | |
1268 | sr_analog_init(&analog2, &encoding2, &meaning2, &spec2, 0); | |
1269 | ret = bm52x_rec_prep_feed(bfunc, bsel, bstat, | |
1270 | &analog1, &analog2, &values[0], &values[1], | |
1271 | &ranges1, &ranges2, sdi); | |
1272 | if (ret != SR_OK) | |
1273 | return SR_ERR_DATA; | |
1274 | if (!skip) { | |
1275 | memset(&packet, 0, sizeof(packet)); | |
1276 | packet.type = SR_DF_ANALOG; | |
1277 | ||
1278 | if (bm52x_rec_ivals[ival].freq_rate) { | |
1279 | sr_dbg("rate: %u", bm52x_rec_ivals[ival].freq_rate); | |
1280 | key = SR_CONF_SAMPLERATE; | |
1281 | num = bm52x_rec_ivals[ival].freq_rate; | |
1282 | (void)sr_session_send_meta(sdi, | |
1283 | key, g_variant_new_uint64(num)); | |
1284 | } | |
1285 | if (bm52x_rec_ivals[ival].ival_secs) { | |
1286 | sr_dbg("ival: %u", bm52x_rec_ivals[ival].ival_secs); | |
1287 | key = SR_CONF_SAMPLE_INTERVAL; | |
1288 | num = bm52x_rec_ivals[ival].ival_secs * 1000; /* in ms */ | |
1289 | (void)sr_session_send_meta(sdi, | |
1290 | key, g_variant_new_uint64(num)); | |
1291 | } | |
1292 | } | |
1293 | ||
1294 | /* | |
1295 | * Implementor's note: | |
1296 | * Software limits require devc access, which is an internal | |
1297 | * detail of the serial-dmm driver, which this bm52x parser | |
1298 | * is not aware of. So we always provide the complete set of | |
1299 | * recorded samples. Should be acceptable. Duplicating limit | |
1300 | * support in local config get/set is considered undesirable. | |
1301 | */ | |
1302 | while (meas_count--) { | |
1303 | meas_data = bm52x_rec_get_u24(serial, state); | |
1304 | values[0] = bm52x_rec_get_value(meas_data, ranges1, &digits); | |
1305 | if (!skip) { | |
1306 | analog1.encoding->digits = digits; | |
1307 | analog1.spec->spec_digits = digits; | |
1308 | packet.payload = &analog1; | |
1309 | ret = sr_session_send(sdi, &packet); | |
1310 | if (ret != SR_OK) | |
1311 | return ret; | |
1312 | } | |
1313 | ||
1314 | if (!has_sec_disp) | |
1315 | continue; | |
1316 | meas_data = bm52x_rec_get_u24(serial, state); | |
1317 | values[1] = bm52x_rec_get_value(meas_data, ranges2, &digits); | |
1318 | if (!skip) { | |
1319 | analog2.encoding->digits = digits; | |
1320 | analog2.spec->spec_digits = digits; | |
1321 | packet.payload = &analog2; | |
1322 | ret = sr_session_send(sdi, &packet); | |
1323 | if (ret != SR_OK) | |
1324 | return ret; | |
1325 | } | |
1326 | } | |
1327 | ||
1328 | /* Check termination of the session page. */ | |
1329 | if (bm52x_rec_get_u8(serial, state) != 0xee) /* "DLE" */ | |
1330 | return SR_ERR_DATA; | |
1331 | if (bm52x_rec_get_u8(serial, state) != 0xc0) /* "ETX" */ | |
1332 | return SR_ERR_DATA; | |
1333 | ||
1334 | return SR_OK; | |
1335 | } | |
1336 | ||
1337 | /** Skip one recorded session page. */ | |
1338 | static int bm52x_rec_skip_page(const struct sr_dev_inst *sdi, | |
1339 | struct brymen_bm52x_state *state, struct sr_serial_dev_inst *serial) | |
1340 | { | |
1341 | return bm52x_rec_read_page_int(sdi, state, serial, TRUE); | |
1342 | } | |
1343 | ||
1344 | /** Forward one recorded session page. */ | |
1345 | static int bm52x_rec_read_page(const struct sr_dev_inst *sdi, | |
1346 | struct brymen_bm52x_state *state, struct sr_serial_dev_inst *serial) | |
1347 | { | |
1348 | return bm52x_rec_read_page_int(sdi, state, serial, FALSE); | |
1349 | } | |
1350 | ||
1351 | SR_PRIV void *brymen_bm52x_state_init(void) | |
1352 | { | |
1353 | return g_malloc0(sizeof(struct brymen_bm52x_state)); | |
1354 | } | |
1355 | ||
1356 | SR_PRIV void brymen_bm52x_state_free(void *state) | |
1357 | { | |
1358 | g_free(state); | |
1359 | } | |
1360 | ||
1361 | SR_PRIV int brymen_bm52x_config_get(void *st, uint32_t key, GVariant **data, | |
1362 | const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) | |
1363 | { | |
1364 | struct brymen_bm52x_state *state; | |
1365 | char text[32]; | |
1366 | ||
1367 | state = st; | |
1368 | ||
1369 | if (!sdi) | |
1370 | return SR_ERR_NA; | |
1371 | (void)cg; | |
1372 | ||
1373 | switch (key) { | |
1374 | case SR_CONF_DATA_SOURCE: | |
1375 | if (!state) | |
1376 | return SR_ERR_ARG; | |
1377 | if (state->sess_idx == 0) | |
1378 | snprintf(text, sizeof(text), "Live"); | |
1379 | else | |
1380 | snprintf(text, sizeof(text), "Rec-%zu", state->sess_idx); | |
1381 | *data = g_variant_new_string(text); | |
1382 | return SR_OK; | |
1383 | default: | |
1384 | return SR_ERR_NA; | |
1385 | } | |
1386 | } | |
1387 | ||
1388 | SR_PRIV int brymen_bm52x_config_set(void *st, uint32_t key, GVariant *data, | |
1389 | const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) | |
1390 | { | |
1391 | struct brymen_bm52x_state *state; | |
1392 | const char *s; | |
1393 | int ret, nr; | |
1394 | ||
1395 | state = st; | |
1396 | ||
1397 | if (!sdi) | |
1398 | return SR_ERR_NA; | |
1399 | (void)cg; | |
1400 | ||
1401 | switch (key) { | |
1402 | case SR_CONF_DATA_SOURCE: | |
1403 | s = g_variant_get_string(data, NULL); | |
1404 | if (!s || !*s) | |
1405 | return SR_ERR_ARG; | |
1406 | if (strcasecmp(s, "Live") == 0) { | |
1407 | state->sess_idx = 0; | |
1408 | return SR_OK; | |
1409 | } | |
1410 | if (strncasecmp(s, "Rec-", strlen("Rec-")) != 0) | |
1411 | return SR_ERR_ARG; | |
1412 | s += strlen("Rec-"); | |
1413 | ret = sr_atoi(s, &nr); | |
1414 | if (ret != SR_OK || nr <= 0 || nr > 999) | |
1415 | return SR_ERR_ARG; | |
1416 | state->sess_idx = nr; | |
1417 | return SR_OK; | |
1418 | default: | |
1419 | return SR_ERR_NA; | |
1420 | } | |
1421 | } | |
1422 | ||
1423 | SR_PRIV int brymen_bm52x_config_list(void *st, uint32_t key, GVariant **data, | |
1424 | const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) | |
1425 | { | |
1426 | struct brymen_bm52x_state *state; | |
1427 | struct sr_serial_dev_inst *serial; | |
1428 | int ret; | |
1429 | size_t count, idx; | |
1430 | GVariantBuilder gvb; | |
1431 | char name[32]; | |
1432 | ||
1433 | /* | |
1434 | * Have common keys handled by caller's common code. | |
1435 | * ERR N/A results in the caller's logic handling the request. | |
1436 | * Only handle strictly local properties here in this code path. | |
1437 | */ | |
1438 | switch (key) { | |
1439 | case SR_CONF_SCAN_OPTIONS: | |
1440 | /* Scan options. Common property. */ | |
1441 | return SR_ERR_NA; | |
1442 | case SR_CONF_DEVICE_OPTIONS: | |
1443 | if (!sdi) | |
1444 | /* Driver options. Common property. */ | |
1445 | return SR_ERR_NA; | |
1446 | if (cg) | |
1447 | /* Channel group's devopts. Common error path. */ | |
1448 | return SR_ERR_NA; | |
1449 | /* List meter's local device options. Overrides common data. */ | |
1450 | *data = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, | |
1451 | devopts, ARRAY_SIZE(devopts), sizeof(uint32_t)); | |
1452 | return SR_OK; | |
1453 | case SR_CONF_DATA_SOURCE: | |
1454 | state = st; | |
1455 | if (!sdi) | |
1456 | return SR_ERR_ARG; | |
1457 | serial = sdi->conn; | |
1458 | ret = bm52x_rec_get_count(state, serial); | |
1459 | if (ret < 0) | |
1460 | return ret; | |
1461 | count = ret; | |
1462 | g_variant_builder_init(&gvb, G_VARIANT_TYPE_ARRAY); | |
1463 | for (idx = 0; idx <= count; idx++) { | |
1464 | if (idx == 0) | |
1465 | snprintf(name, sizeof(name), "Live"); | |
1466 | else | |
1467 | snprintf(name, sizeof(name), "Rec-%zu", idx); | |
1468 | g_variant_builder_add(&gvb, "s", name); | |
1469 | } | |
1470 | *data = g_variant_builder_end(&gvb); | |
1471 | return SR_OK; | |
1472 | default: | |
1473 | return SR_ERR_NA; | |
1474 | } | |
1475 | } | |
1476 | ||
1477 | /** | |
1478 | * BM520s specific receive routine for recorded measurements. | |
1479 | * | |
1480 | * @param[in] fd File descriptor. | |
1481 | * @param[in] revents Mask of pending events. | |
1482 | * @param[in] cb_data Call back data (receive handler state). | |
1483 | * | |
1484 | * @return TRUE when the routine needs to get re-invoked, FALSE when the | |
1485 | * routine is done and no further invocations are required. | |
1486 | * | |
1487 | * @private | |
1488 | * | |
1489 | * It's an implementation detail that a single invocation will carry out | |
1490 | * all the work that is involved in reading back recorded measurements. | |
1491 | */ | |
1492 | static int bm52x_rec_receive_data(int fd, int revents, void *cb_data) | |
1493 | { | |
1494 | struct brymen_bm52x_state *state; | |
1495 | const struct sr_dev_inst *sdi; | |
1496 | struct sr_dev_inst *sdi_rw; | |
1497 | struct sr_serial_dev_inst *serial; | |
1498 | int ret; | |
1499 | size_t count, idx; | |
1500 | ||
1501 | (void)fd; | |
1502 | (void)revents; | |
1503 | state = cb_data; | |
1504 | ||
1505 | sdi = state->sdi; | |
1506 | serial = sdi->conn; | |
1507 | ||
1508 | ret = bm52x_rec_get_count(state, serial); | |
1509 | if (ret < 0) | |
1510 | return FALSE; | |
1511 | count = ret; | |
1512 | ||
1513 | /* Un-const 'sdi' since sr_dev_acquisition_stop() needs that. */ | |
1514 | sdi_rw = (struct sr_dev_inst *)sdi; | |
1515 | ||
1516 | /* | |
1517 | * Immediate (silent, zero data) stop for non-existent sessions. | |
1518 | * Early exit is an arbitrary implementation detail, in theory | |
1519 | * the loop below would transparently handle the situation when | |
1520 | * users request non-existing session pages. | |
1521 | */ | |
1522 | if (state->sess_idx > count) { | |
1523 | sr_dev_acquisition_stop(sdi_rw); | |
1524 | return FALSE; | |
1525 | } | |
1526 | ||
1527 | /* Iterate all session pages, forward the one of interest. */ | |
1528 | for (idx = 1; idx <= count; idx++) { | |
1529 | if (idx == state->sess_idx) | |
1530 | ret = bm52x_rec_read_page(sdi, state, serial); | |
1531 | else | |
1532 | ret = bm52x_rec_skip_page(sdi, state, serial); | |
1533 | if (ret != SR_OK) | |
1534 | break; | |
1535 | } | |
1536 | ||
1537 | sr_dev_acquisition_stop(sdi_rw); | |
1538 | return FALSE; | |
1539 | } | |
1540 | ||
1541 | /** | |
1542 | * BM520s specific acquisition start callback. | |
1543 | * | |
1544 | * @param[in] st DMM parser state. | |
1545 | * @param[in] sdi Device instance. | |
1546 | * @param[out] cb Receive callback for the acquisition. | |
1547 | * @param[out] cb_data Callback data for receive callback. | |
1548 | * | |
1549 | * @returns SR_OK upon success. | |
1550 | * @returns SR_ERR* upon failure. | |
1551 | * | |
1552 | * @private | |
1553 | * | |
1554 | * The BM520s protocol parser uses common logic and the packet parser | |
1555 | * for live acquisition, but runs a different set of requests and a | |
1556 | * different response layout interpretation for recorded measurements. | |
1557 | */ | |
1558 | SR_PRIV int brymen_bm52x_acquire_start(void *st, const struct sr_dev_inst *sdi, | |
1559 | sr_receive_data_callback *cb, void **cb_data) | |
1560 | { | |
1561 | struct brymen_bm52x_state *state; | |
1562 | ||
1563 | if (!sdi || !st) | |
1564 | return SR_ERR_ARG; | |
1565 | state = st; | |
1566 | ||
1567 | /* Read live measurements. No local override required. */ | |
1568 | if (state->sess_idx == 0) | |
1569 | return SR_OK; | |
1570 | ||
1571 | /* Arrange to read back recorded session. */ | |
1572 | sr_dbg("session page requested: %zu", state->sess_idx); | |
1573 | state->sdi = sdi; | |
1574 | *cb = bm52x_rec_receive_data; | |
1575 | *cb_data = state; | |
1576 | return SR_OK; | |
1577 | } |