643 lines
17 KiB
C
643 lines
17 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#include <sys/time.h>
|
|
|
|
#include <hexagram/can.h>
|
|
#include <hexagram/window.h>
|
|
|
|
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,
|
|
double redline) {
|
|
int i;
|
|
|
|
cairo_set_source_rgb(cr, 1, 1, 1);
|
|
|
|
cairo_arc(cr, x, y, r, 0, 2*M_PI);
|
|
cairo_stroke(cr);
|
|
|
|
cairo_move_to(cr, x - 0.8 * r, y - 0.1 * r);
|
|
cairo_show_text(cr, "0");
|
|
|
|
cairo_move_to(cr, x - 0.15 * r, y - 0.5 * r);
|
|
cairo_show_text(cr, "1/2");
|
|
|
|
cairo_move_to(cr, x + 0.65 * r, y - 0.1 * r);
|
|
cairo_show_text(cr, "1");
|
|
|
|
/*
|
|
* Draw gauge graduations
|
|
*/
|
|
for (i=0; i<=16; i++) {
|
|
if (i <= (16.0 * redline)) {
|
|
cairo_set_source_rgb(cr, 1, 0, 0);
|
|
} else {
|
|
cairo_set_source_rgb(cr, 1, 1, 1);
|
|
}
|
|
|
|
if (i % 4 == 0) {
|
|
draw_face_mark(cr, x, y,
|
|
0.70 * r,
|
|
0.90 * r,
|
|
300 * (M_PI/180),
|
|
420 * (M_PI/180),
|
|
(double)i / 16.0);
|
|
} else {
|
|
draw_face_mark(cr, x, y,
|
|
0.80 * r,
|
|
0.90 * r,
|
|
300 * (M_PI/180),
|
|
420 * (M_PI/180),
|
|
(double)i / 16.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
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.9 * 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,
|
|
0.125);
|
|
|
|
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;
|
|
}
|