]> sigrok.org Git - libsigrok.git/blob - hardware/brymen-dmm/parser.c
brymen-dmm: Add support for Brymen BM857
[libsigrok.git] / hardware / brymen-dmm / parser.c
1 /*
2  * This file is part of the sigrok project.
3  *
4  * Copyright (C) 2012 Alexandru Gagniuc <mr.nuke.me@gmail.com>
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include "protocol.h"
21 #include <stdlib.h>
22 #include <string.h>
23 #include <math.h>
24
25 /*
26  * Flags passed from the DMM.
27  */
28 struct brymen_flags {
29         gboolean low_batt;
30         gboolean decibel, duty_cycle, hertz, amp, beep, ohm, fahrenheit;
31         gboolean celsius, capacitance, diode, volt, dc, ac;
32 };
33
34 struct bm850_command {
35         uint8_t dle;
36         uint8_t stx;
37         uint8_t cmd;
38         uint8_t arg[2];
39         uint8_t checksum;
40         uint8_t dle2;
41         uint8_t etx;
42 };
43
44 struct brymen_header {
45         uint8_t dle;
46         uint8_t stx;
47         uint8_t cmd;
48         uint8_t len;
49 };
50
51 struct brymen_tail {
52         uint8_t checksum;
53         uint8_t dle;
54         uint8_t etx;
55 };
56
57 /*
58  * We only have one command because we only support the BM-857. However, the
59  * driver is easily extensible to support more models, as the protocols are very
60  * similar.
61  */
62 enum {
63         BM_CMD_REQUEST_READING = 0x00,
64 };
65
66
67 static int bm_send_command(uint8_t command, uint8_t arg1, uint8_t arg2,
68                            struct sr_serial_dev_inst *serial)
69 {
70         struct bm850_command cmdout = {
71                 .dle = 0x10, .stx = 0x02,
72                 .cmd = command,
73                 .arg = {arg1, arg2},
74                 .checksum = arg1^arg2, .dle2 = 0x10, .etx = 0x03};
75                 int written;
76                 
77                 /* TODO: How do we compute the checksum? Hardware seems to ignore it */
78                 
79                 /* Request reading */
80                 written = serial_write(serial, &cmdout, sizeof(cmdout));
81                 if(written != sizeof(cmdout))
82                         return SR_ERR;
83                 
84                 return SR_OK;
85 }
86
87 SR_PRIV int brymen_packet_request(struct sr_serial_dev_inst *serial)
88 {
89         return bm_send_command(BM_CMD_REQUEST_READING, 0, 0, serial);
90 }
91
92 SR_PRIV int brymen_packet_length(const uint8_t *buf, int *len)
93 {
94         struct brymen_header *hdr;
95         const int brymen_max_packet_len = 22;
96         int packet_len;
97         const size_t buflen = *len;
98         
99         hdr = (void*)buf;
100         
101         /* Did we receive a complete header yet? */
102         if (buflen < sizeof(*hdr) )
103                 return PACKET_NEED_MORE_DATA;
104         
105         if (hdr->dle != 0x10 || hdr->stx != 0x02)
106                 return PACKET_INVALID_HEADER;
107         
108         /* Our packet includes the header, the payload, and the tail */
109         packet_len = sizeof(*hdr) + hdr->len + sizeof(struct brymen_tail);
110         
111         /* In case we pick up an invalid header, limit our search */
112         if (packet_len > brymen_max_packet_len) {
113                 sr_spew("Header specifies an invalid payload length: %i.",
114                         hdr->len);
115                 return PACKET_INVALID_HEADER;
116         }
117         
118         *len = packet_len;
119         sr_spew("Expecting a %d-byte packet.", *len);
120         return PACKET_HEADER_OK;
121 }
122
123 SR_PRIV gboolean brymen_packet_is_valid(const uint8_t *buf)
124 {
125         struct brymen_header *hdr;
126         struct brymen_tail *tail;
127         int i;
128         uint8_t chksum = 0;
129         const uint8_t *payload = buf + sizeof(struct brymen_header);
130         
131         hdr = (void*)buf;
132         tail = (void*)(payload + hdr->len);
133         
134         for (i = 0; i< hdr->len; i++)
135                 chksum ^= payload[i];
136         
137         if (tail->checksum != chksum) {
138                 sr_dbg("Packet has invalid checksum 0x%.2x. Expected 0x%.2x",
139                        chksum, tail->checksum);
140                 return FALSE;
141         }
142         
143         return TRUE;
144 }
145
146 static int parse_value(const char *strbuf, const int len, float *floatval)
147 {
148         int s, d;
149         char str[32];
150
151         if (strstr(strbuf, "OL")) {
152                 sr_dbg("Overlimit.");
153                 *floatval = INFINITY;
154                 return SR_OK;
155         }
156
157         memset(str, 0, sizeof(str));
158         /* Spaces may interfere with strtod parsing the exponent. Strip them */
159         for (s = 0, d = 0; s < len; s++)
160                 if (strbuf[s] != ' ')
161                         str[d++] = strbuf[s];
162                 /* YES, it's that simple !*/
163                 *floatval = strtod(str, NULL);
164
165         return SR_OK;
166 }
167 static void parse_flags(const uint8_t *buf, struct brymen_flags *info)
168 {
169         const uint8_t * bfunc = buf + sizeof(struct brymen_header);
170
171         info->low_batt          = (bfunc[3] & (1 << 7)) != 0;
172
173         info->decibel           = (bfunc[1] & (1 << 5)) != 0;
174         info->duty_cycle        = (bfunc[1] & (1 << 3)) != 0;
175         info->hertz             = (bfunc[1] & (1 << 2)) != 0;
176         info->amp               = (bfunc[1] & (1 << 1)) != 0;
177         info->beep              = (bfunc[1] & (1 << 0)) != 0;
178
179         info->ohm               = (bfunc[0] & (1 << 7)) != 0;
180         info->fahrenheit        = (bfunc[0] & (1 << 6)) != 0;
181         info->celsius           = (bfunc[0] & (1 << 5)) != 0;
182         info->diode             = (bfunc[0] & (1 << 4)) != 0;
183         info->capacitance       = (bfunc[0] & (1 << 3)) != 0;
184         info->volt              = (bfunc[0] & (1 << 2)) != 0;
185         info->dc                = (bfunc[0] & (1 << 1)) != 0;
186         info->ac                = (bfunc[0] & (1 << 0)) != 0;
187 }
188
189 SR_PRIV int sr_brymen_parse(const uint8_t *buf, float *floatval,
190                             struct sr_datafeed_analog *analog, void *info)
191 {
192         struct brymen_flags flags;
193         struct brymen_header *hdr = (void*) buf;
194         const uint8_t *bfunc = buf + sizeof(struct brymen_header);
195         int asciilen;
196
197         (void)info;
198         analog->mqflags = 0;
199
200         /* Give some debug info about the package */
201         asciilen = hdr->len - 4;
202         sr_dbg("DMM flags: %.2x %.2x %.2x %.2x",
203                bfunc[3], bfunc[2], bfunc[1], bfunc[0]);
204         /* Value is an ASCII string */
205         sr_dbg("DMM packet: \"%.*s\"", asciilen, bfunc + 4);
206
207         parse_flags(buf, &flags);
208         parse_value((const char*)(bfunc + 4), asciilen, floatval);
209
210         if (flags.volt) {
211                 analog->mq = SR_MQ_VOLTAGE;
212                 analog->unit = SR_UNIT_VOLT;
213         }
214         if (flags.amp) {
215                 analog->mq = SR_MQ_CURRENT;
216                 analog->unit = SR_UNIT_AMPERE;
217         }
218         if (flags.ohm) {
219                 if (flags.beep)
220                         analog->mq = SR_MQ_CONTINUITY;
221                 else
222                         analog->mq = SR_MQ_RESISTANCE;
223                 analog->unit = SR_UNIT_OHM;
224         }
225         if (flags.hertz) {
226                 analog->mq = SR_MQ_FREQUENCY;
227                 analog->unit = SR_UNIT_HERTZ;
228         }
229         if (flags.duty_cycle) {
230                 analog->mq = SR_MQ_DUTY_CYCLE;
231                 analog->unit = SR_UNIT_PERCENTAGE;
232         }
233         if (flags.capacitance) {
234                 analog->mq = SR_MQ_CAPACITANCE;
235                 analog->unit = SR_UNIT_FARAD;
236         }
237         if (flags.fahrenheit) {
238                 analog->mq = SR_MQ_TEMPERATURE;
239                 analog->unit = SR_UNIT_FAHRENHEIT;
240         }
241         if (flags.celsius) {
242                 analog->mq = SR_MQ_TEMPERATURE;
243                 analog->unit = SR_UNIT_CELSIUS;
244         }
245         if (flags.capacitance) {
246                 analog->mq = SR_MQ_CAPACITANCE;
247                 analog->unit = SR_UNIT_FARAD;
248         }
249         /*
250          * The high-end brymen models have a configurable reference impedance.
251          * When the reference impedance is changed, the DMM sends one packet
252          * with the value of the new reference impedance. Both decibel and ohm
253          * flags are set in this case, so we must be careful to correctly
254          * identify the value as ohm, not dBmW
255          */
256         if (flags.decibel && !flags.ohm) {
257                 analog->mq = SR_MQ_POWER;
258                 analog->unit = SR_UNIT_DECIBEL_MW;
259                 /*
260                  * For some reason, dBm measurements are sent by the multimeter
261                  * with a value three orders of magnitude smaller than the
262                  * displayed value.
263                  * */
264                 *floatval *= 1000;
265         }
266
267         if (flags.diode)
268                 analog->mqflags |= SR_MQFLAG_DIODE;
269         /* We can have both AC+DC in a single measurement */
270         if (flags.ac)
271                 analog->mqflags |= SR_MQFLAG_AC;
272         if (flags.dc)
273                 analog->mqflags |= SR_MQFLAG_DC;
274
275         if (flags.low_batt)
276                 sr_info("Low battery!");
277
278         return SR_OK;
279 }