#include #include #include #include #include #include #include #include typedef struct _hexagram_cluster_state { double rpm, rps, temp, fuel; } hexagram_cluster_state; static hexagram_cluster_state state = { 0, 0, 0, 0 }; static void draw_needle(cairo_t *cr, double x, double y, double r, double min_angle, double max_angle, double value) { double angle = min_angle + ((max_angle - min_angle) * value) - (M_PI/2); cairo_move_to(cr, x, y); cairo_set_source_rgb(cr, 0, 0.25, 1.0); cairo_line_to(cr, x + r * cos(angle), y + r * sin(angle)); cairo_stroke(cr); } static void draw_face_number(cairo_t *cr, double x, double y, double r, double min_angle, double max_angle, double value, const char *text) { double angle = min_angle + ((max_angle - min_angle) * value) - 1.658; cairo_set_source_rgb(cr, 1, 1, 1); cairo_move_to(cr, x + r * cos(angle), y + r * sin(angle)); cairo_save(cr); cairo_rotate(cr, angle + 1.658); cairo_show_text(cr, text); cairo_stroke(cr); cairo_restore(cr); } static void draw_face_mark(cairo_t *cr, double x, double y, double min_r, double max_r, double min_angle, double max_angle, double value) { double angle = min_angle + ((max_angle - min_angle) * value) - (M_PI/2); cairo_move_to(cr, x + min_r * cos(angle), y + min_r * sin(angle)); cairo_line_to(cr, x + max_r * cos(angle), y + max_r * sin(angle)); cairo_stroke(cr); } static void draw_tachometer_needle(cairo_t *cr, double x, double y, double r, double rpm) { if (rpm > 8000) { rpm = 8000; } /* * Draw a tiny boi circle */ cairo_set_source_rgb(cr, 1, 1, 1); cairo_arc(cr, x, y, 0.08 * r, 0, 2*M_PI); cairo_stroke(cr); draw_needle(cr, x, y, 0.8 * r, 232 * (M_PI/180), 488 * (M_PI/180), rpm / 8000); } static void draw_tachometer(cairo_t *cr, double x, double y, double r, double redline) { int i; cairo_select_font_face(cr, "Helvetica", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, r * 0.125); cairo_set_source_rgb(cr, 1, 1, 1); cairo_arc(cr, x, y, r, 0, 2*M_PI); cairo_stroke(cr); /* * Draw face numbers */ for (i=0; i<=80; i+=10) { char text[4]; snprintf(text, 3, "%02d", i); draw_face_number(cr, x, y, 0.85 * r, 232 * (M_PI/180), 488 * (M_PI/180), i / 80.0, text); } for (i=0; i<=80; i++) { if (i * 100 >= redline) { cairo_set_source_rgb(cr, 1, 0, 0); } if (i % 5 == 0) { draw_face_mark(cr, x, y, 0.7 * r, 0.8 * r, 232 * (M_PI/180), 488 * (M_PI/180), i / 80.0); } else { draw_face_mark(cr, x, y, 0.75 * r, 0.8 * r, 232 * (M_PI/180), 488 * (M_PI/180), i / 80.0); } } } static void draw_speedometer(cairo_t *cr, double x, double y, double r) { int i; cairo_select_font_face(cr, "Helvetica", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, r * 0.125); cairo_set_source_rgb(cr, 1, 1, 1); cairo_arc(cr, x, y, r, 0, 2*M_PI); cairo_stroke(cr); /* * Draw face numbers */ for (i=0; i<=180; i+=20) { char text[5]; snprintf(text, 4, "%02d", i); draw_face_number(cr, x, y, 0.85 * r, 232 * (M_PI/180), 488 * (M_PI/180), i / 180.0, text); } for (i=0; i<=180; i+=2) { if (i % 10 == 0) { draw_face_mark(cr, x, y, 0.7 * r, 0.8 * r, 232 * (M_PI/180), 488 * (M_PI/180), i / 180.0); } else { draw_face_mark(cr, x, y, 0.75 * r, 0.8 * r, 232 * (M_PI/180), 488 * (M_PI/180), i / 180.0); } } } static void draw_speedometer_needle(cairo_t *cr, double x, double y, double r, double kph) { if (kph > 290) { kph = 290; } /* * Draw a tiny boi circle */ cairo_set_source_rgb(cr, 1, 1, 1); cairo_arc(cr, x, y, 0.08 * r, 0, 2*M_PI); cairo_stroke(cr); draw_needle(cr, x, y, 0.8 * r, 232 * (M_PI/180), 488 * (M_PI/180), kph / 290); } static void draw_thermometer(cairo_t *cr, double x, double y, double r, double redline) { int i; cairo_select_font_face(cr, "Helvetica", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, r * 0.2); cairo_set_source_rgb(cr, 1, 1, 1); cairo_arc(cr, x, y, r, 0, 2*M_PI); cairo_stroke(cr); /* * Draw face numbers */ cairo_move_to(cr, x - 0.8 * r, y - 0.1 * r); cairo_show_text(cr, "120"); cairo_move_to(cr, x - 0.15 * r, y - 0.5 * r); cairo_show_text(cr, "190"); cairo_move_to(cr, x + 0.5 * r, y - 0.1 * r); cairo_show_text(cr, "260"); /* * Draw gauge graduations */ for (i=0; i<=140; i+=7) { if (i + 120 >= redline) { cairo_set_source_rgb(cr, 1, 0, 0); } if (i % 35 == 0) { draw_face_mark(cr, x, y, 0.70 * r, 0.90 * r, 300 * (M_PI/180), 420 * (M_PI/180), i / 140.0); } else { draw_face_mark(cr, x, y, 0.80 * r, 0.90 * r, 300 * (M_PI/180), 420 * (M_PI/180), i / 140.0); } } } static void draw_thermometer_needle(cairo_t *cr, double x, double y, double r, double temp) { if (temp > 260) { temp = 260; } /* * Draw a tiny boi circle */ cairo_set_source_rgb(cr, 1, 1, 1); cairo_arc(cr, x, y, 0.08 * r, 0, 2*M_PI); cairo_stroke(cr); draw_needle(cr, x, y, 0.9 * r, 300 * (M_PI/180), 420 * (M_PI/180), temp / 260); } static void draw_fuel_gauge(cairo_t *cr, double x, double y, double r) { cairo_set_source_rgb(cr, 1, 1, 1); cairo_arc(cr, x, y, r, 0, 2*M_PI); cairo_stroke(cr); draw_face_number(cr, x, y, 0.75 * r, 300 * (M_PI/180), 420 * (M_PI/180), 0.0, "0"); draw_face_number(cr, x, y, 0.75 * r, 300 * (M_PI/180), 420 * (M_PI/180), 0.5, "1/2"); draw_face_number(cr, x, y, 0.75 * r, 300 * (M_PI/180), 420 * (M_PI/180), 1.0, "1"); } static void draw_fuel_gauge_needle(cairo_t *cr, double x, double y, double r, double level) { if (level > 1.0) { level = 1.0; } /* * Draw a tiny boi circle */ cairo_set_source_rgb(cr, 1, 1, 1); cairo_arc(cr, x, y, 0.08 * r, 0, 2*M_PI); cairo_stroke(cr); draw_needle(cr, x, y, 0.7 * r, 300 * (M_PI/180), 420 * (M_PI/180), level / 1.0); } static void draw_mfd(cairo_t *cr, double x, double y, double width, double height) { cairo_set_source_rgb(cr, 0.75, 0, 0); cairo_move_to(cr, x, y); cairo_line_to(cr, x + width, y); cairo_line_to(cr, x + width, y + height); cairo_line_to(cr, x, y + height); cairo_line_to(cr, x, y); cairo_fill(cr); cairo_set_source_rgb(cr, 1, 1, 1); cairo_move_to(cr, x, y); cairo_line_to(cr, x + width, y); cairo_line_to(cr, x + width, y + height); cairo_line_to(cr, x, y + height); cairo_line_to(cr, x, y); cairo_stroke(cr); } static void cluster_draw_bg(cairo_t *cr, double width, double height) { /* * Paint canvas black */ cairo_set_source_rgb(cr, 0, 0, 0); cairo_paint(cr); draw_tachometer(cr, width * 0.2, height * 0.5, height * 0.4, 6500); draw_speedometer(cr, width * 0.8, height * 0.5, height * 0.4); draw_thermometer(cr, width * 0.43, height * 0.76, height * 0.13, 240.0); draw_fuel_gauge(cr, width * 0.57, height * 0.76, height * 0.13); draw_mfd(cr, width * 0.42, height * 0.1, width * 0.16, height * 0.5); } static void cluster_update(struct can_frame *frame) { switch (frame->can_id) { case 0x280: { state.rpm = 0.25 * (double)(frame->data[2] | (frame->data[3] << 8)); break; } case 0x288: { state.temp = (double)(frame->data[1] - 48 * 0.75); break; } case 0x320: { state.fuel = (double)(frame->data[2] & 0xf) / 16.0; break; } case 0x5a0: { state.rps = 0.001 * (double)(frame->data[1] | (frame->data[2] << 8)); break; } } } static void cluster_draw(cairo_t *cr, double x, double y, double width, double height) { draw_tachometer_needle(cr, x + width * 0.2, y + height * 0.5, height * 0.4, state.rpm); draw_speedometer_needle(cr, width * 0.8, height * 0.5, height * 0.4, (2.032 * state.rps * 3600) / 1000.0); draw_thermometer_needle(cr, width * 0.43, height * 0.76, height * 0.13, state.temp); draw_fuel_gauge_needle(cr, width * 0.57, height * 0.76, height * 0.13, state.fuel); } static int usage(int argc, char **argv, const char *message, ...) { if (message) { va_list args; va_start(args, message); vfprintf(stderr, message, args); fprintf(stderr, "\n"); va_end(args); } fprintf(stderr, "usage: %s canif\n", argv[0]); return 1; } int main(int argc, char **argv) { Display *display; fd_set rfds, rready; hexagram_can_if *can_if; hexagram_window *window; int fd, width = 1024, height = 480; struct timeval last = { .tv_sec = 0, .tv_usec = 0 }; cairo_t *fg, *bg; if (argc != 2) { return usage(argc, argv, "No CAN interface specified"); } if ((can_if = hexagram_can_if_open(argv[1])) == NULL) { return 1; } if ((window = hexagram_window_new_x11(NULL, width, height)) == NULL) { return 1; } display = hexagram_window_display(window); fd = hexagram_can_if_fd(can_if); /* * Set up the rendering surfaces */ fg = hexagram_window_create_fg_context(window); bg = hexagram_window_create_bg_context(window); /* * Draw the background layer */ cluster_draw_bg(bg, width, height); /* * Present the background layer */ hexagram_window_show(window); /* * Set up file descriptors to monitor */ FD_ZERO(&rfds); FD_SET(fd, &rfds); while (1) { struct timeval timeout = { .tv_sec = 0, .tv_usec = 250000 }, now; int pending, nfds = fd + 1; (void)gettimeofday(&now, NULL); while ((pending = XPending(display)) != 0) { XEvent e; XNextEvent(display, &e); switch (e.type) { case MapNotify: case Expose: hexagram_window_refresh_bg(window); cluster_draw(fg, 0, 0, width, height); hexagram_window_swap_buffer(window); break; case ButtonPress: case KeyPress: break; default: break; } } memcpy(&rready, &rfds, sizeof(rfds)); if (select(nfds, &rready, NULL, NULL, &timeout) < 0) { break; } if (FD_ISSET(fd, &rready)) { struct can_frame frame; hexagram_can_if_read(can_if, &frame); switch (frame.can_id) { case 0x280: case 0x288: case 0x320: case 0x5a0: { cluster_update(&frame); if (now.tv_sec - last.tv_sec || now.tv_usec - last.tv_usec > 16666) { hexagram_window_refresh_bg(window); cluster_draw(fg, 0, 0, width, height); hexagram_window_swap_buffer(window); (void)memcpy(&last, &now, sizeof(now)); } } } } } hexagram_window_destroy(window); return 0; }