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