#include #include #include #include #include #include #include #include #include #include #include "data.h" #include "trace.h" #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) static int separate_entries(char *buf, char *entries[], int max_entries) { int i = 0; entries[i] = buf; i++; while (*buf && i< max_entries) { if (*buf != ';') { buf++; continue; } *buf = 0; buf++; entries[i] = buf; i++; } return i; } static void init_data(struct charger_data *data) { int i; bzero(data, sizeof(*data)); for (i = 0; i < MAX_CELLS; i++) data->cell_voltage[i] = NAN; data->timestamp = NAN; data->input_voltage = NAN; data->charging_voltage = NAN; data->charging_current = NAN; data->total_charge = NAN; data->int_temp = NAN; data->ext_temp = NAN; } /* * Convert one log line into charger_data structure. Supports at least * iCharger 6 and 10 cell chargers. Others are not tested. * * Returns negative on incorrect data, zero on success */ static int parse_logline(const char *buf, struct charger_data *data) { int i, j, entry_count; int ret = -1; int max_cells; int d; char *str = strdup(buf); char *entries[64]; entry_count = separate_entries(str, entries, ARRAY_SIZE(entries)); pr_debug("Entry count: %d\n", entry_count); init_data(data); if (entries[0][0] != '$') { pr_debug("Discarding malformed data entry\n"); goto out; } for (i = 0; i < entry_count; i++) pr_debug("Entry %d: data: %s\n", i, entries[i]); i = 0; entries[i]++; /* discard the dollar sign */ data->channel = atoi(entries[i++]); data->state = atoi(entries[i++]); /* Timestamp is optional */ if (strlen(entries[1]) > 0) data->timestamp = atof(entries[i++]); data->input_voltage = atof(entries[i++]) / 1000.0; data->charging_voltage = atof(entries[i++]) / 1000.0; data->charging_current = atof(entries[i++]) / 100.0; #define ASSIGN_OR_NAN(data, val) \ do { \ if ((val) == 0) \ (data) = NAN; \ else \ (data) = val; \ } while(0); max_cells = entry_count - 10; pr_debug("max_cells: %d\n", max_cells); for (j = 0; j < max_cells; j++, i++) { d = atoi(entries[i]); ASSIGN_OR_NAN(data->cell_voltage[j], d / 1000.0); } d = atoi(entries[i++]); ASSIGN_OR_NAN(data->int_temp, d / 10.0); d = atoi(entries[i++]); ASSIGN_OR_NAN(data->ext_temp, d / 10.0); data->total_charge = atof(entries[i++]); ret = 0; out: free(str); return ret; } static void dump_data(struct charger_data *data) { int i; pr_debug("channel %d\n", data->channel); pr_debug("state %d\n", data->state); pr_debug("timestamp %.1f\n", data->timestamp); pr_debug("input_voltage %.3f\n", data->input_voltage); pr_debug("charging_voltage %.3f\n", data->charging_voltage); pr_debug("charging_current %.3f\n", data->charging_current); for (i = 0; i < MAX_CELLS; i++) { if (isnan(data->cell_voltage[i])) continue; pr_debug("cell_voltage[%d] %f\n", i, data->cell_voltage[i]); } pr_debug("total_charge %.0f\n", data->total_charge); pr_debug("int_temp %.1f\n", data->int_temp); pr_debug("ext_temp %.1f\n", data->ext_temp); } static void print_status_line(struct charger_data *data) { int i, active_cells = 0; double cell_avg = 0; char time_str[16]; if (data->timestamp > 3600) { snprintf(time_str, sizeof(time_str), "%d:%02d:%02d", (int)(data->timestamp / 3600), (int)((int)data->timestamp % 3600) / 60, (int)data->timestamp % 60); } else { snprintf(time_str, sizeof(time_str), "%2d:%02d", ((int)data->timestamp % 3600) / 60, (int)data->timestamp % 60); } for (i = 0; i < MAX_CELLS; i++) { if (!isnan(data->cell_voltage[i])) { cell_avg += data->cell_voltage[i]; active_cells++; } } cell_avg /= (double)active_cells; pr_info("\r\033[K%8s Ubat: %.3fV Ucell avg: %.3fV " "Current: %.2fA Charge %.0fmAh ", time_str, data->charging_voltage, cell_avg, data->charging_current, data->total_charge); fflush(stdout); } /** * Read data from a slow device * * return 1 when a complete NULL terminated line has been read * * return 0 when a partial line has been read and appended to the * buffer at @offset * * return negative on error */ static int read_log_line(int infd, char *buf, size_t bufsize, int *offset) { int ret; int i; ret = read(infd, buf + *offset, bufsize - *offset - 1); if (ret < 0) { pr_err("read: %m\n"); return -1; } if (ret == 0) { pr_err("Read EOF, stopping\n"); return -1; } buf[*offset + ret] = 0; for (i = 0; i < ret; i++) { if (buf[i + *offset] == '\n' || buf[i + *offset] == '\r') { /* * Got a complete line when there is a newline * at the end. Remove the newline and possible * other junk, such as '\r' */ buf[i + *offset] = 0; *offset = 0; return 1; } /* * Fixme! Nothing guarantees that there isn't actually * more data (a part of a new log entry perhaps) after * the newline. So in rare cases (we are prevented * from reading the serial line in very long time) we * might lose data from the stream.. */ } *offset += ret; return 0; } int read_data(int infd, int outfd) { struct epoll_event ev; time_t start_time = 0, cur_time; int epoll_fd; int ret; char buf[256]; int offset = 0; epoll_fd = epoll_create(1); if (epoll_fd < 0) { pr_err("Failed to create epoll socket: %m\n"); return -1; } ev.events = EPOLLIN; ev.data.fd = infd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, infd, &ev) == -1) { pr_err("epoll_ctl: %m\n"); return 1; } while (1) { struct charger_data data; char str[320]; int len; ret = epoll_wait(epoll_fd, &ev, 1, -1); if (ret == 0) continue; if (ret < 0) { pr_err("epoll: %m\n"); return -1; } ret = read_log_line(infd, buf, sizeof(buf), &offset); if (ret < 0) return ret; if (ret == 0) continue; if (strlen(buf) < 5) { pr_debug("discarding truncated log entry\n"); offset = 0; continue; } if (!start_time) start_time = time(NULL); cur_time = time(NULL); parse_logline(buf, &data); /* Fill in possibly missing timestamp */ if (isnan(data.timestamp) || data.timestamp == 0); data.timestamp = cur_time - start_time; print_status_line(&data); dump_data(&data); if (!outfd) continue; len = snprintf(str, sizeof(str), "%d;%d;%.1f;" "%.3f;%.3f;%.3f;" "%.3f;%.3f;%.3f;%.3f;%.3f;%.3f;%.3f;%.3f;%.3f;%.3f;" "%.f;%.1f;%.1f\n", /* mAh, and temp */ data.channel, data.state, data.timestamp, data.input_voltage, data.charging_voltage, data.charging_current, data.cell_voltage[0], data.cell_voltage[1], data.cell_voltage[2], data.cell_voltage[3], data.cell_voltage[4], data.cell_voltage[5], data.cell_voltage[6], data.cell_voltage[7], data.cell_voltage[8], data.cell_voltage[9], data.int_temp, data.ext_temp, data.total_charge); ret = write(outfd, str, len); if (ret < 0) { pr_err("write: %m\n"); break; } } return 0; }