]>
Commit | Line | Data |
---|---|---|
cc9653de MP |
1 | /* |
2 | * This file is part of the libsigrok project. | |
3 | * | |
4 | * Copyright (C) 2023 Mathieu Pilato <pilato.mathieu@free.fr> | |
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 <config.h> | |
a5800e90 | 21 | #include <string.h> |
cc9653de MP |
22 | #include "protocol.h" |
23 | ||
a5800e90 MP |
24 | /* Duration of scan. */ |
25 | #define ATORCH_PROBE_TIMEOUT_MS 10000 | |
26 | ||
27 | /* | |
28 | * Message layout: | |
29 | * 2 magic header bytes | |
30 | * 1 message type byte | |
31 | * N payload bytes, determined by message type | |
32 | */ | |
33 | ||
34 | /* Position of message type byte in a message. */ | |
35 | #define HEADER_MSGTYPE_IDX 2 | |
36 | #define PAYLOAD_START_IDX 3 | |
37 | ||
38 | /* Length of each message type. */ | |
39 | #define MSGLEN_REPORT (4 + 32) | |
40 | #define MSGLEN_REPLY (4 + 4) | |
41 | #define MSGLEN_COMMAND (4 + 6) | |
42 | ||
43 | /* Minimal length of a valid message. */ | |
44 | #define MSGLEN_MIN 4 | |
45 | ||
46 | static const uint8_t header_magic[] = { | |
47 | 0xff, 0x55, | |
48 | }; | |
49 | ||
50 | static const struct atorch_channel_desc atorch_dc_power_meter_channels[] = { | |
51 | { "V", { 4, BVT_BE_UINT24, }, { 100, 1e3, }, 1, SR_MQ_VOLTAGE, SR_UNIT_VOLT, SR_MQFLAG_DC, }, | |
52 | { "I", { 7, BVT_BE_UINT24, }, { 1, 1e3, }, 3, SR_MQ_CURRENT, SR_UNIT_AMPERE, SR_MQFLAG_DC, }, | |
53 | { "C", { 10, BVT_BE_UINT24, }, { 10, 1e3, }, 2, SR_MQ_ENERGY, SR_UNIT_AMPERE_HOUR, 0, }, | |
54 | { "E", { 13, BVT_BE_UINT32, }, { 10, 1, }, -2, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR, 0, }, | |
55 | { "T", { 24, BVT_BE_UINT16, }, { 1, 1, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS, 0, }, | |
56 | }; | |
57 | ||
58 | static const struct atorch_channel_desc atorch_usb_power_meter_channels[] = { | |
59 | { "V", { 4, BVT_BE_UINT24, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT, SR_MQFLAG_DC, }, | |
60 | { "I", { 7, BVT_BE_UINT24, }, { 10, 1e3, }, 2, SR_MQ_CURRENT, SR_UNIT_AMPERE, SR_MQFLAG_DC, }, | |
61 | { "C", { 10, BVT_BE_UINT24, }, { 1, 1e3, }, 3, SR_MQ_ENERGY, SR_UNIT_AMPERE_HOUR, 0, }, | |
62 | { "E", { 13, BVT_BE_UINT32, }, { 10, 1e3, }, 2, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR, 0, }, | |
63 | { "D-", { 17, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT, SR_MQFLAG_DC, }, | |
64 | { "D+", { 19, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT, SR_MQFLAG_DC, }, | |
65 | { "T", { 21, BVT_BE_UINT16, }, { 1, 1, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS, 0, }, | |
66 | }; | |
67 | ||
68 | static const struct atorch_device_profile atorch_profiles[] = { | |
69 | { 0x02, "DC Meter", ARRAY_AND_SIZE(atorch_dc_power_meter_channels), }, | |
70 | { 0x03, "USB Meter", ARRAY_AND_SIZE(atorch_usb_power_meter_channels), }, | |
71 | }; | |
72 | ||
73 | static size_t get_length_for_msg_type(uint8_t msg_type) | |
74 | { | |
75 | switch (msg_type) { | |
76 | case MSG_REPORT: | |
77 | return MSGLEN_REPORT; | |
78 | case MSG_REPLY: | |
79 | return MSGLEN_REPLY; | |
80 | case MSG_COMMAND: | |
81 | return MSGLEN_COMMAND; | |
82 | default: | |
83 | return 0; | |
84 | } | |
85 | } | |
86 | ||
87 | static void log_atorch_msg(const uint8_t *buf, size_t len) | |
88 | { | |
89 | GString *text; | |
90 | ||
91 | if (sr_log_loglevel_get() < SR_LOG_DBG) | |
92 | return; | |
93 | ||
94 | text = sr_hexdump_new(buf, len); | |
95 | sr_dbg("Atorch msg: %s", text->str); | |
96 | sr_hexdump_free(text); | |
97 | } | |
98 | ||
99 | static const uint8_t *locate_next_valid_msg(struct dev_context *devc) | |
100 | { | |
101 | uint8_t *valid_msg_ptr; | |
102 | size_t valid_msg_len; | |
103 | uint8_t *msg_ptr; | |
104 | ||
105 | /* Enough byte to make a message? */ | |
106 | while (devc->rd_idx + MSGLEN_MIN <= devc->wr_idx) { | |
107 | /* Look for header magic. */ | |
108 | msg_ptr = devc->buf + devc->rd_idx; | |
109 | if (memcmp(msg_ptr, header_magic, sizeof(header_magic)) != 0) { | |
110 | devc->rd_idx += 1; | |
111 | continue; | |
112 | } | |
113 | ||
114 | /* Determine msg type and length. */ | |
115 | valid_msg_len = get_length_for_msg_type(msg_ptr[HEADER_MSGTYPE_IDX]); | |
116 | if (!valid_msg_len) { | |
117 | devc->rd_idx += 2; | |
118 | continue; | |
119 | } | |
120 | ||
121 | /* Do we have the complete message? */ | |
122 | if (devc->rd_idx + valid_msg_len <= devc->wr_idx) { | |
123 | valid_msg_ptr = msg_ptr; | |
124 | devc->rd_idx += valid_msg_len; | |
125 | log_atorch_msg(valid_msg_ptr, valid_msg_len); | |
126 | return valid_msg_ptr; | |
127 | } | |
128 | ||
129 | return NULL; | |
130 | } | |
131 | return NULL; | |
132 | } | |
133 | ||
134 | static const uint8_t *receive_msg(struct sr_serial_dev_inst *serial, | |
135 | struct dev_context *devc) | |
136 | { | |
137 | size_t len; | |
138 | const uint8_t *valid_msg_ptr; | |
139 | ||
140 | while (1) { | |
141 | /* Remove bytes already processed. */ | |
142 | if (devc->rd_idx > 0) { | |
143 | len = devc->wr_idx - devc->rd_idx; | |
144 | memmove(devc->buf, devc->buf + devc->rd_idx, len); | |
145 | devc->wr_idx -= devc->rd_idx; | |
146 | devc->rd_idx = 0; | |
147 | } | |
148 | ||
149 | /* Read more bytes to process. */ | |
150 | len = ATORCH_BUFSIZE - devc->wr_idx; | |
151 | len = serial_read_nonblocking(serial, devc->buf + devc->wr_idx, len); | |
152 | if (len <= 0) | |
153 | return NULL; | |
154 | devc->wr_idx += len; | |
155 | ||
156 | /* Locate next start of message. */ | |
157 | valid_msg_ptr = locate_next_valid_msg(devc); | |
158 | if (valid_msg_ptr) | |
159 | return valid_msg_ptr; | |
160 | } | |
161 | } | |
162 | ||
163 | static const struct atorch_device_profile *find_profile_for_device_type(uint8_t dev_type) | |
164 | { | |
165 | size_t i; | |
166 | ||
167 | for (i = 0; i < ARRAY_SIZE(atorch_profiles); i++) { | |
168 | if (atorch_profiles[i].device_type == dev_type) | |
169 | return &atorch_profiles[i]; | |
170 | } | |
171 | return NULL; | |
172 | } | |
173 | ||
174 | static void parse_report_msg(struct sr_dev_inst *sdi, const uint8_t *report_ptr) | |
175 | { | |
176 | struct dev_context *devc; | |
177 | float val; | |
178 | size_t i; | |
179 | ||
180 | devc = sdi->priv; | |
181 | ||
182 | std_session_send_df_frame_begin(sdi); | |
183 | ||
184 | for (i = 0; i < devc->profile->channel_count; i++) { | |
185 | bv_get_value(&val, &devc->profile->channels[i].spec, report_ptr); | |
f40d8479 | 186 | feed_queue_analog_submit_one(devc->feeds[i], val, 1); |
a5800e90 MP |
187 | } |
188 | ||
189 | std_session_send_df_frame_end(sdi); | |
190 | ||
191 | sr_sw_limits_update_frames_read(&devc->limits, 1); | |
192 | if (sr_sw_limits_check(&devc->limits)) | |
193 | sr_dev_acquisition_stop(sdi); | |
194 | } | |
195 | ||
196 | SR_PRIV int atorch_probe(struct sr_serial_dev_inst *serial, struct dev_context *devc) | |
cc9653de | 197 | { |
a5800e90 MP |
198 | int64_t deadline_us; |
199 | const struct atorch_device_profile *p; | |
200 | const uint8_t *msg_ptr; | |
201 | ||
202 | devc->wr_idx = 0; | |
203 | devc->rd_idx = 0; | |
204 | ||
205 | deadline_us = g_get_monotonic_time(); | |
206 | deadline_us += ATORCH_PROBE_TIMEOUT_MS * 1000; | |
207 | while (g_get_monotonic_time() <= deadline_us) { | |
208 | msg_ptr = receive_msg(serial, devc); | |
209 | if (msg_ptr && msg_ptr[HEADER_MSGTYPE_IDX] == MSG_REPORT) { | |
210 | p = find_profile_for_device_type(msg_ptr[PAYLOAD_START_IDX]); | |
211 | if (p) { | |
212 | devc->profile = p; | |
213 | return SR_OK; | |
214 | } | |
215 | sr_err("Unrecognized device type (0x%.4" PRIx8 ").", | |
216 | devc->buf[PAYLOAD_START_IDX]); | |
217 | return SR_ERR; | |
218 | } | |
219 | g_usleep(100 * 1000); | |
220 | } | |
221 | return SR_ERR; | |
222 | } | |
223 | ||
224 | SR_PRIV int atorch_receive_data_callback(int fd, int revents, void *cb_data) | |
225 | { | |
226 | struct sr_dev_inst *sdi; | |
cc9653de | 227 | struct dev_context *devc; |
a5800e90 | 228 | const uint8_t *msg_ptr; |
cc9653de MP |
229 | |
230 | (void)fd; | |
231 | ||
232 | sdi = cb_data; | |
cc9653de | 233 | devc = sdi->priv; |
a5800e90 MP |
234 | |
235 | if (!sdi || !devc) | |
cc9653de MP |
236 | return TRUE; |
237 | ||
a5800e90 MP |
238 | if (revents & G_IO_IN) { |
239 | while ((msg_ptr = receive_msg(sdi->conn, devc))) { | |
240 | if (msg_ptr[HEADER_MSGTYPE_IDX] == MSG_REPORT) | |
241 | parse_report_msg(sdi, msg_ptr); | |
242 | } | |
cc9653de MP |
243 | } |
244 | ||
245 | return TRUE; | |
246 | } |