]> git.itanic.dy.fi Git - log-plotter/blob - data.c
plotter scripts: Add automatic x-axis tick scaling
[log-plotter] / data.c
1 #include <unistd.h>
2 #include <stdio.h>
3 #include <string.h>
4 #include <time.h>
5 #include <stdlib.h>
6 #include <sys/epoll.h>
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <string.h>
10 #include <math.h>
11
12 #include "data.h"
13 #include "debug.h"
14
15 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
16
17 static int separate_entries(char *buf, char *entries[], int max_entries)
18 {
19         int i = 0;
20
21         entries[i] = buf;
22         i++;
23
24         while (*buf && i< max_entries) {
25                 if (*buf != ';') {
26                         buf++;
27                         continue;
28                 }
29
30                 *buf = 0;
31                 buf++;
32                 entries[i] = buf;
33                 i++;
34         }
35
36         return i;
37 }
38
39 static void init_data(struct charger_data *data)
40 {
41         int i;
42
43         bzero(data, sizeof(*data));
44         for (i = 0; i < MAX_CELLS; i++)
45                 data->cell_voltage[i] = NAN;
46
47         data->timestamp = NAN;
48         data->input_voltage = NAN;
49         data->charging_voltage = NAN;
50         data->charging_current = NAN;
51         data->total_charge = NAN;
52         data->int_temp = NAN;
53         data->ext_temp = NAN;
54 }
55
56 /*
57  * Convert one log line into charger_data structure. Supports at least
58  * iCharger 6 and 10 cell chargers. Others are not tested.
59  *
60  * Returns negative on incorrect data, zero on success
61  */
62 static int parse_logline(const char *buf, struct charger_data *data)
63 {
64         int i, j, entry_count;
65         int ret = -1;
66         int max_cells;
67         int d;
68         char *str = strdup(buf);
69         char *entries[64];
70
71         entry_count = separate_entries(str, entries, ARRAY_SIZE(entries));
72
73         pr_debug("Entry count: %d\n", entry_count);
74
75         init_data(data);
76
77         if (entries[0][0] != '$') {
78                 pr_debug("Discarding malformed data entry\n");
79                 goto out;
80         }
81
82         for (i = 0; i < entry_count; i++)
83                 pr_debug("Entry %d: data: %s\n", i, entries[i]);
84
85         i = 0;
86         entries[i]++; /* discard the dollar sign */
87         data->channel = atoi(entries[i++]);
88
89         data->state = atoi(entries[i++]);
90
91         /* Timestamp is optional */
92         if (strlen(entries[1]) > 0)
93                 data->timestamp = atof(entries[i++]);
94
95         data->input_voltage = atof(entries[i++]) / 1000.0;
96         data->charging_voltage = atof(entries[i++]) / 1000.0;
97         data->charging_current = atof(entries[i++]) / 100.0;
98
99 #define ASSIGN_OR_NAN(data, val)                \
100         do {                                    \
101                 if ((val) == 0)                 \
102                         (data) = NAN;           \
103                 else                            \
104                         (data) = val;           \
105         } while(0);
106
107         max_cells = entry_count - 10;
108         pr_debug("max_cells: %d\n", max_cells);
109
110         for (j = 0; j < max_cells; j++, i++) {
111                 d = atoi(entries[i]);
112                 ASSIGN_OR_NAN(data->cell_voltage[j], d / 1000.0);
113         }
114
115         d = atoi(entries[i++]);
116         ASSIGN_OR_NAN(data->int_temp, d / 10.0);
117
118         d = atoi(entries[i++]);
119         ASSIGN_OR_NAN(data->ext_temp, d / 10.0);
120
121         data->total_charge = atof(entries[i++]);
122
123         ret = 0;
124 out:
125         free(str);
126         return ret;
127 }
128
129 static void dump_data(struct charger_data *data)
130 {
131         int i;
132
133         pr_debug("channel %d\n", data->channel);
134         pr_debug("state %d\n", data->state);
135         pr_debug("timestamp %.1f\n", data->timestamp);
136         pr_debug("input_voltage %.3f\n", data->input_voltage);
137         pr_debug("charging_voltage %.3f\n", data->charging_voltage);
138         pr_debug("charging_current %.3f\n", data->charging_current);
139
140         for (i = 0; i < MAX_CELLS; i++) {
141                 if (isnan(data->cell_voltage[i]))
142                         continue;
143
144                 pr_debug("cell_voltage[%d] %f\n", i,
145                         data->cell_voltage[i]);
146         }
147
148         pr_debug("total_charge %.0f\n", data->total_charge);
149         pr_debug("int_temp %.1f\n", data->int_temp);
150         pr_debug("ext_temp %.1f\n", data->ext_temp);
151 }
152
153 static void print_status_line(struct charger_data *data)
154 {
155         int i, active_cells = 0;
156         double cell_avg = 0;
157         char time_str[16];
158
159         if (data->timestamp > 3600) {
160                 snprintf(time_str, sizeof(time_str), "%d:%02d:%02d",
161                         (int)(data->timestamp / 3600),
162                         (int)((int)data->timestamp % 3600) / 60,
163                         (int)data->timestamp % 60);
164         } else {
165                 snprintf(time_str, sizeof(time_str), "%2d:%02d",
166                         ((int)data->timestamp % 3600) / 60,
167                         (int)data->timestamp % 60);
168         }
169
170         for (i = 0; i < MAX_CELLS; i++) {
171                 if (!isnan(data->cell_voltage[i])) {
172                         cell_avg += data->cell_voltage[i];
173                         active_cells++;
174                 }
175         }
176         cell_avg /= (double)active_cells;
177
178         pr_info("\r\033[K%8s Ubat: %.3fV Ucell avg: %.3fV ",
179                 time_str, data->charging_voltage, cell_avg);
180
181         fflush(stdout);
182
183 }
184
185 /**
186  * Read data from a slow device
187  *
188  * return 1 when a complete NULL terminated line has been read
189  *
190  * return 0 when a partial line has been read and appended to the
191  *               buffer at @offset
192  *
193  * return negative on error
194  */
195 static int read_log_line(int infd, char *buf, size_t bufsize, int *offset)
196 {
197         int ret;
198         int i;
199
200         ret = read(infd, buf + *offset, bufsize - *offset - 1);
201         if (ret < 0) {
202                 pr_err("read: %m\n");
203                 return -1;
204         }
205
206         if (ret == 0) {
207                 pr_err("Read EOF, stopping\n");
208                 return -1;
209         }
210         buf[*offset + ret] = 0;
211
212         for (i = 0; i < ret; i++) {
213                 if (buf[i + *offset] == '\n' ||
214                     buf[i + *offset] == '\r') {
215                         /*
216                          * Got a complete line when there is a newline
217                          * at the end. Remove the newline and possible
218                          * other junk, such as '\r'
219                          */
220                         buf[i + *offset] = 0;
221                         *offset = 0;
222
223                         return 1;
224                 }
225
226                 /*
227                  * Fixme! Nothing guarantees that there isn't actually
228                  * more data (a part of a new log entry perhaps) after
229                  * the newline. So in rare cases (we are prevented
230                  * from reading the serial line in very long time) we
231                  * might lose data from the stream..
232                  */
233         }
234
235         *offset += ret;
236         return 0;
237 }
238
239 int read_data(int infd, int outfd)
240 {
241         struct epoll_event ev;
242         time_t start_time = 0, cur_time;
243         int epoll_fd;
244         int ret;
245         char buf[256];
246         int offset = 0;
247
248         epoll_fd = epoll_create(1);
249         if (epoll_fd < 0) {
250                 pr_err("Failed to create epoll socket: %m\n");
251                 return -1;
252         }
253
254         ev.events = EPOLLIN;
255         ev.data.fd = infd;
256         if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, infd, &ev) == -1) {
257                 pr_err("epoll_ctl: %m\n");
258                 return 1;
259         }
260
261         while (1) {
262                 struct charger_data data;
263                 char str[320];
264                 int len;
265
266                 ret = epoll_wait(epoll_fd, &ev, 1, -1);
267                 if (ret == 0)
268                         continue;
269
270                 if (ret < 0) {
271                         pr_err("epoll: %m\n");
272                         return -1;
273                 }
274
275                 ret = read_log_line(infd, buf, sizeof(buf), &offset);
276                 if (ret < 0)
277                         return ret;
278
279                 if (ret == 0)
280                         continue;
281
282                 if (strlen(buf) < 5) {
283                         pr_debug("discarding truncated log entry\n");
284                         offset = 0;
285                         continue;
286                 }
287
288                 if (!start_time)
289                         start_time = time(NULL);
290
291                 cur_time = time(NULL);
292
293                 parse_logline(buf, &data);
294
295                 /* Fill in possibly missing timestamp */
296                 if (isnan(data.timestamp) || data.timestamp == 0);
297                         data.timestamp = cur_time - start_time;
298
299                 print_status_line(&data);
300
301                 dump_data(&data);
302
303                 if (!outfd)
304                         continue;
305
306                 len = snprintf(str, sizeof(str),
307                         "%d;%d;%.1f;"
308                         "%.3f;%.3f;%.3f;"
309                         "%.3f;%.3f;%.3f;%.3f;%.3f;%.3f;%.3f;%.3f;%.3f;%.3f;"
310                         "%.f;%.1f;%.1f\n", /* mAh, and temp */
311                         data.channel,
312                         data.state,
313                         data.timestamp,
314
315                         data.input_voltage,
316                         data.charging_voltage,
317                         data.charging_current,
318
319                         data.cell_voltage[0],
320                         data.cell_voltage[1],
321                         data.cell_voltage[2],
322                         data.cell_voltage[3],
323                         data.cell_voltage[4],
324                         data.cell_voltage[5],
325                         data.cell_voltage[6],
326                         data.cell_voltage[7],
327                         data.cell_voltage[8],
328                         data.cell_voltage[9],
329                         data.int_temp,
330                         data.ext_temp,
331                         data.total_charge);
332
333                 ret = write(outfd, str, len);
334                 if (ret < 0) {
335                         pr_err("write: %m\n");
336                         break;
337                 }
338         }
339
340         return 0;
341 }
342