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