cocomobile-tui/src/main.c
2026-04-11 15:34:12 +02:00

524 lines
18 KiB
C

#include <ctype.h>
#include <ncurses.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <linux/can.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <fcntl.h>
const int digit_bitmaps[10][5][3] = {
{{1,1,1},{1,0,1},{1,0,1},{1,0,1},{1,1,1}}, // 0
{{0,1,0},{1,1,0},{0,1,0},{0,1,0},{1,1,1}}, // 1
{{1,1,1},{0,0,1},{1,1,1},{1,0,0},{1,1,1}}, // 2
{{1,1,1},{0,0,1},{1,1,1},{0,0,1},{1,1,1}}, // 3
{{1,0,1},{1,0,1},{1,1,1},{0,0,1},{0,0,1}}, // 4
{{1,1,1},{1,0,0},{1,1,1},{0,0,1},{1,1,1}}, // 5
{{1,1,1},{1,0,0},{1,1,1},{1,0,1},{1,1,1}}, // 6
{{1,1,1},{0,0,1},{0,1,0},{0,1,0},{0,1,0}}, // 7
{{1,1,1},{1,0,1},{1,1,1},{1,0,1},{1,1,1}}, // 8
{{1,1,1},{1,0,1},{1,1,1},{0,0,1},{1,1,1}} // 9
};
typedef struct{
int data;
int ldata;
int hdata;
int digits;
bool high;
int height;
int width;
int starty;
int startx;
WINDOW *lwin;
char title[30];
} StInt;
typedef struct{
float data;
float ldata;
float hdata;
int digits;
bool high;
int height;
int width;
int starty;
int startx;
WINDOW *lwin;
char title[30];
} StFlt;
typedef struct{
int height;
int width;
int starty;
int startx;
WINDOW *lwin;
char title[30];
} StStr;
typedef struct {
StInt speed;
StInt rpm;
StFlt tq;
StInt power;
StFlt eff;
StInt bat;
StFlt bat_temp;
StFlt var_temp;
StFlt mot_temp;
StStr message;
} Tel;
void win_init_int(StInt *st) {
st->lwin = newwin(st->height, st->width, st->starty, st->startx);
box(st->lwin, 0, 0);
mvwprintw(st->lwin, 0, 2, "%s", st->title);
wrefresh(st->lwin);
};
void win_init_flt(StFlt *st) {
st->lwin = newwin(st->height, st->width, st->starty, st->startx);
box(st->lwin, 0, 0);
mvwprintw(st->lwin, 0, 2, "%s", st->title);
wrefresh(st->lwin);
};
void win_init_str(StStr *st) {
st->lwin = newwin(st->height, st->width, st->starty, st->startx);
box(st->lwin, 0, 0);
mvwprintw(st->lwin, 0, 2, "%s", st->title);
wrefresh(st->lwin);
};
void get_fake_data(Tel *t) {
t->speed.data = t->speed.ldata + rand() % (t->speed.hdata - t->speed.ldata + 1);
t->power.data = t->power.ldata + rand() % (t->power.hdata - t->power.ldata + 1);
t->bat.data = t->bat.ldata + rand() % (t->bat.hdata - t->bat.ldata + 1);
t->tq.data = t->tq.ldata + ((float)rand() / (float)RAND_MAX) * (float)(t->tq.hdata - t->tq.ldata);
t->rpm.data = t->rpm.ldata + rand() % (t->rpm.hdata - t->rpm.ldata + 1);
t->eff.data = t->eff.ldata + ((float)rand() / (float)RAND_MAX) * (float)(t->eff.hdata - t->eff.ldata);
t->bat_temp.data = t->bat_temp.ldata + ((float)rand() / (float)RAND_MAX) * (float)(t->bat_temp.hdata - t->bat_temp.ldata);
t->var_temp.data = t->var_temp.ldata + ((float)rand() / (float)RAND_MAX) * (float)(t->var_temp.hdata - t->var_temp.ldata);
t->mot_temp.data = t->mot_temp.ldata + ((float)rand() / (float)RAND_MAX) * (float)(t->mot_temp.hdata - t->mot_temp.ldata);
}
void read_can(Tel *t, int soc) {
struct can_frame frame;
int nbytes = read(soc, &frame, sizeof(struct can_frame));
if (nbytes > 0) {
switch (frame.can_id) {
case 0x382:
if (frame.can_dlc >= 2) {
t->rpm.data = frame.data[0] | (frame.data[1] << 8);
t->speed.data = (int)(t->rpm.data * 0.017f);
}
break;
default:
break;
}
}
}
long now_ms(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
void win_clear(WINDOW *lwin) {
int y, x;
getmaxyx(lwin, y, x);
for (int i = 1; i < y - 1; i++) {
mvwhline(lwin, i, 1, ' ', x - 2);
}
}
int smaller_of(int a, int b) {
return (a < b) ? a : b;
}
int color_high(float data, float ldata, float hdata) {
int percent = (int)((100.0f * (data - ldata)) / (hdata - ldata) + 0.5f);
if (percent >= 90) return 1;
else if (percent >= 75) return 2;
else return 3;
}
int color_low(float data, float ldata, float hdata) {
int percent = 100 - (int)((100.0f * (data - ldata)) / (hdata - ldata) + 0.5f);
if (percent >= 90) return 1;
else if (percent >= 75) return 2;
else return 3;
}
void bar_mark(WINDOW *lwin) {
int lwiny, lwinx;
int first_mark, last_mark;
getmaxyx(lwin, lwiny, lwinx);
first_mark = (75 * (lwinx - 4)) / 100;
last_mark = (90 * (lwinx - 4)) / 100;
mvwaddch(lwin, 0, first_mark + 1, '|');
mvwaddch(lwin, lwiny - 1, first_mark + 1, '|');
mvwaddch(lwin, 0, last_mark + 1, '|');
mvwaddch(lwin, lwiny - 1, last_mark + 1, '|');
}
void win_int(StInt st, bool use_color) {
int color;
if (st.high) {
color = color_high(st.data, st.ldata, st.hdata);
} else {
color = color_low(st.data, st.ldata, st.hdata);
};
int lwiny, lwinx, len;
char buf[16];
getmaxyx(st.lwin, lwiny, lwinx);
snprintf(buf , sizeof(buf), "%d", st.data);
len = strlen(buf);
int bh = 5; // bitmap height
int bw = 3; // bitmap width
int size = smaller_of((lwiny - 2) / bh, (lwinx - 2 - (st.digits - 1)) / (bw * st.digits));
int total_width = st.digits * (bw * size) + (st.digits - 1);
int startx = (lwinx - total_width) / 2;
int starty = (lwiny - bh * size) / 2;
int offset = (bw * size + 1) * (st.digits - len);
win_clear(st.lwin);
if (use_color) wattron(st.lwin, COLOR_PAIR(color));
for (int d = 0; d < len; d++) {
int digit = buf[d] - '0';
int dx = startx + d * (bw * size + 1) + offset;
for (int y = 0; y < bh; y++) {
for (int x = 0; x < bw; x++) {
if (digit_bitmaps[digit][y][x]) {
for (int yy = 0; yy < size; yy++)
for (int xx = 0; xx < size; xx++)
mvwaddch(st.lwin,
starty + y * size + yy,
dx + x * size + xx,
ACS_CKBOARD);
}
}
}
}
if (use_color) wattroff(st.lwin, COLOR_PAIR(color));
wrefresh(st.lwin);
}
void win_float(StFlt st, bool use_color) {
int color;
if (st.high) {
color = color_high(st.data, st.ldata, st.hdata);
} else {
color = color_low(st.data, st.ldata, st.hdata);
};
int lwiny, lwinx, len;
char buf[16];
getmaxyx(st.lwin, lwiny, lwinx);
snprintf(buf , sizeof(buf), "%.1f", st.data);
len = strlen(buf);
int bh = 5; // bitmap height
int bw = 3; // bitmap width
int size = smaller_of((lwiny - 2) / bh, (lwinx - 2 - (st.digits - 1)) / (bw * st.digits));
int total_width = st.digits * (bw * size) + (st.digits - 1);
int startx = (lwinx - total_width) / 2;
int starty = (lwiny - bh * size) / 2;
int offset = (bw * size + 1) * (st.digits - len);
win_clear(st.lwin);
if (use_color) wattron(st.lwin, COLOR_PAIR(color));
for (int d = 0; d < len; d++) {
int dx = startx + d * (bw * size + 1) + offset;
if (buf[d] == '.') {
for (int yy = 0; yy < size; yy++)
for (int xx = 0; xx < size; xx++)
mvwaddch(
st.lwin,
starty + (bh - 1) * size + yy,
dx + xx,
ACS_CKBOARD
);
continue;
}
int digit = buf[d] - '0';
for (int y = 0; y < bh; y++) {
for (int x = 0; x < bw; x++) {
if (digit_bitmaps[digit][y][x]) {
for (int yy = 0; yy < size; yy++)
for (int xx = 0; xx < size; xx++)
mvwaddch(st.lwin,
starty + y * size + yy,
dx + x * size + xx,
ACS_CKBOARD);
}
}
}
}
if (use_color) wattroff(st.lwin, COLOR_PAIR(color));
wrefresh(st.lwin);
}
void win_bar(StInt st, bool use_color) {
int lwiny, lwinx;
getmaxyx(st.lwin, lwiny, lwinx);
win_clear(st.lwin);
int bar_width = lwinx - 4;
int bar_height = lwiny - 2;
int filled = (st.data * bar_width) / st.hdata;
int yellow = (75 * bar_width) / 100;
int red = (90 * bar_width) / 100;
for (int y = 1; y <= bar_height; y++) {
for (int x = 0; x < filled; x++) {
if (use_color) {
if (x < yellow)
wattron(st.lwin, COLOR_PAIR(3));
else if (x < red)
wattron(st.lwin, COLOR_PAIR(2));
else
wattron(st.lwin, COLOR_PAIR(1));
}
mvwaddch(st.lwin, y, 2 + x, ACS_CKBOARD);
}
}
if (use_color) {
wattroff(st.lwin, COLOR_PAIR(3));
wattroff(st.lwin, COLOR_PAIR(2));
wattroff(st.lwin, COLOR_PAIR(1));
}
if (use_color) {
wattroff(st.lwin, COLOR_PAIR(0));
}
char buf[16];
int len;
snprintf(buf , sizeof(buf), "%d", st.data);
len = strlen(buf);
int bh = 5; // bitmap height
int bw = 3; // bitmap width
int size = smaller_of((lwiny - 2) / bh, (lwinx - 2 - (st.digits - 1)) / (bw * st.digits));
int total_width = st.digits * (bw * size) + (st.digits - 1);
int startx = (lwinx - total_width) / 3;
int starty = (lwiny - bh * size) / 2;
int offset = (bw * size + 1) * (st.digits - len);
for (int d = 0; d < len; d++) {
int digit = buf[d] - '0';
int dx = startx + d * (bw * size + 1) + offset;
for (int y = 0; y < bh; y++) {
for (int x = 0; x < bw; x++) {
if (digit_bitmaps[digit][y][x]) {
for (int yy = 0; yy < size; yy++)
for (int xx = 0; xx < size; xx++)
mvwaddch(st.lwin,
starty + y * size + yy,
dx + x * size + xx,
ACS_CKBOARD);
}
}
}
}
wrefresh(st.lwin);
if (use_color) {
wattroff(st.lwin, COLOR_PAIR(0));
}
}
int main(int argc, char **argv) {
int option;
int delay = 1;
int fake_data = 0;
int use_can = 0;
int soc = 0;
struct sockaddr_can addr;
struct ifreq ifr;
while ((option = getopt(argc, argv, "d:hfc")) !=-1) {
switch (option) {
case 'd' :
delay = atoi(optarg);
if (delay < 0) {
delay = 0;
}
break;
case 'f' :
fake_data = 1;
break;
case 'c' :
use_can = 1;
break;
case 'h' :
printf("Usage: ./cocomobile-tui [-d int DELAY by how much to slow down] [-h HELP] [-f generate fake data] [-c enable can]\n");
return(0);
}
}
if (use_can) {
soc = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (soc < 0) {
perror("socket error");
return 1;
}
fcntl(soc, F_SETFL, O_NONBLOCK);
strcpy(ifr.ifr_name, "can0");
if (ioctl(soc, SIOCGIFINDEX, &ifr) < 0) {
perror("ioctl error");
return 1;
}
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(soc, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind error");
close(soc);
return 1;
}
}
initscr();
noecho();
cbreak();
curs_set(0);
nodelay(stdscr, TRUE);
refresh();
int y,x;
getmaxyx(stdscr, y, x);
if (y < 42 || x < 100) {
endwin();
perror("terminal window too small");
return 1;
}
bool use_color = 0;
if (has_colors()) {
use_color = 1;
start_color();
use_default_colors();
init_pair(1, COLOR_RED, -1);
init_pair(2, COLOR_YELLOW, -1);
init_pair(3, COLOR_GREEN, -1);
}
int x3 = x / 3;
int x3r = x - 2 * x3;
int y2 = y / 2;
int y2r = y - y2;
int y4 = y2 / 2;
int y4r = y2 - y4;
int y6 = y2r / 3;
int y6r = y2r - 2 * y6;
Tel tel;
tel.speed = (StInt){0 , 0 , 200 , 3, 1, y2 , x3 , 0 , 0 , NULL, "speed (km/h)" };
tel.rpm = (StInt){0 , 0 , 6000 , 4, 1, y2 , x3 , 0 , x3 , NULL, "rpm (tr/min)" };
tel.tq = (StFlt){0.0f, 1.8f, 3.5f , 3, 1, y4r, x3r , y4 , x-x3r , NULL, "torque (N/m)" };
tel.power = (StInt){0 , 0 , 1300 , 4, 1, y6 , 2*x3, y2 , 0 , NULL, "power (W)" };
tel.eff = (StFlt){0.0f, 0.0f, 300.0f, 5, 1, y6 , x3r , y2 , 2*x3 , NULL, "efficiency (Wh/Km)" };
tel.bat = (StInt){0 , 0 , 100 , 3, 0, y4 , x3r , 0 , x-x3r , NULL, "batteries (%)" };
tel.bat_temp = (StFlt){0.0f, 0.0f, 150.0f, 5, 1, y6 , x3 , y2+y6, 0 , NULL, "batteries temperature (deg C)" };
tel.var_temp = (StFlt){0.0f, 0.0f, 150.0f, 5, 1, y6 , x3 , y2+y6, x3 , NULL, "variator temperature (deg C)" };
tel.mot_temp = (StFlt){0.0f, 0.0f, 150.0f, 5, 1, y6 , x3r , y2+y6, x-x3r , NULL, "motor temperature (deg C)" };
tel.message = (StStr){ y6r, x , y-y6r, 0 , NULL, "warnings" };
win_init_int(&tel.speed);
win_init_int(&tel.rpm);
win_init_flt(&tel.tq);
win_init_int(&tel.power);
win_init_flt(&tel.eff);
win_init_int(&tel.bat);
win_init_flt(&tel.bat_temp);
win_init_flt(&tel.var_temp);
win_init_flt(&tel.mot_temp);
win_init_str(&tel.message);
bar_mark(tel.power.lwin);
long t100 = 0, t1000 = 0;
int ch = ERR;
while(1) {
ch = tolower(getch());
switch (ch) {
case 'q' :
goto end;
case ERR :
default :
if (fake_data) {
get_fake_data(&tel);
} else {
if (use_can) read_can(&tel, soc);
}
break;
}
long now = now_ms();
if (now - t100 >= 100 * delay) {
win_int(tel.speed, use_color);
win_int(tel.rpm, use_color);
win_float(tel.tq, use_color);
win_bar(tel.power, use_color);
win_float(tel.eff, use_color);
t100 = now;
}
if (now - t1000 >= 1000 * delay) {
win_int(tel.bat, use_color);
win_float(tel.bat_temp, use_color);
win_float(tel.var_temp, use_color);
win_float(tel.mot_temp, use_color);
t1000 = now;
}
//message;
napms(10);
};
end:
delwin(tel.speed.lwin);
delwin(tel.rpm.lwin);
delwin(tel.tq.lwin);
delwin(tel.power.lwin);
delwin(tel.eff.lwin);
delwin(tel.bat.lwin);
delwin(tel.bat_temp.lwin);
delwin(tel.var_temp.lwin);
delwin(tel.mot_temp.lwin);
delwin(tel.message.lwin);
endwin();
if (use_can) close(soc);
return 0;
}