diff --git a/Makefile b/Makefile index cd7ae3f..c662faf 100644 --- a/Makefile +++ b/Makefile @@ -2,16 +2,20 @@ # # sudo apt-get install libmariadb-dev # # mariadb_config --libs # -CFLAGS=-g -Wall -lm -L/usr/lib/arm-linux-gnueabihf -lmariadb -I/usr/include/mariadb +CFLAGS=-g -Wall -lm +DBFLAGS=-L/usr/lib/arm-linux-gnueabihf -lmariadb -I/usr/include/mariadb -fbdash: fbdash.c fblib.o fbfont.o - gcc -o $@ fblib.o fbfont.o $< $(CFLAGS) +fbdash: fbdash.c fblib.o fbfont.o fbdata.o + gcc -o $@ fblib.o fbfont.o fbdata.o $< $(CFLAGS) $(DBFLAGS) fblib.o: fblib.c fblib.h - gcc -c fblib.c + gcc -c fblib.c $(CFLAGS) fbfont.o: fbfont.c fbfont.h - gcc -c fbfont.c + gcc -c fbfont.c $(CFLAGS) + +fbdata.o: fbdata.c fbdata.h + gcc -c fbdata.c $(CFLAGS) $(DBFLAGS) clean: - rm -f fbdash fblib.o fbfont.o + rm -f fbdash fblib.o fbfont.o fbdata.o diff --git a/fbdash.c b/fbdash.c index e88c260..546f14b 100644 --- a/fbdash.c +++ b/fbdash.c @@ -3,8 +3,10 @@ * * Adapted by Florian Klemenz for use in the fb_dash project * - * Source and all credit goes to + * Credit for most of the framebuffer-related source code goes to * http://raspberrycompote.blogspot.com/ + * Thanks for providing an excellent tutorial! + * -------------------------------------------------------------------------------------------- * * http://raspberrycompote.blogspot.ie/2014/03/low-level-graphics-on-raspberry-pi-part_14.html @@ -27,164 +29,80 @@ #include #include -#include - #include "fblib.h" +#include "fbdata.h" +#define DEVICE "/dev/fb1" #define WIDTH 240 #define HEIGHT 320 +#define BITS_PER_PIXEL 16 -char *fbp = 0; - -struct s_color { - unsigned char r; - unsigned char g; - unsigned char b; -}; -typedef struct s_color color; - -struct s_sourcedata { - char* text; - char* temperature; - char* humidity; -}; -typedef struct s_sourcedata sourcedata; - - -void finish_with_error(MYSQL *con) { - fprintf(stderr, "%s\n", mysql_error(con)); - mysql_close(con); - exit(1); +void updateSlot(screen fb, sourcedata data, int x_offset, int y_offset, color c) { + render_string(fb, ubuntu_mono_24, data.text, x_offset, y_offset, c.r, c.g, c.b); + + if (data.temperature) render_string(fb, ubuntu_mono_48, data.temperature, x_offset + 115, y_offset - 9, c.r, c.g, c.b); + render_string(fb, ubuntu_mono_48, "C", x_offset + 195, y_offset - 9, c.r, c.g, c.b); + + if (data.humidity) render_string(fb, ubuntu_mono_48, data.humidity, x_offset + 115, y_offset + 26, c.r, c.g, c.b); + render_string(fb, ubuntu_mono_48, "%", x_offset + 195, y_offset + 26, c.r, c.g, c.b); } -void updateSlot(char *fbp, sourcedata data, int x_offset, int y_offset, color c) { - render_string(fbp, ubuntu_mono_24, data.text, x_offset, y_offset, c.r, c.g, c.b); - - render_string(fbp, ubuntu_mono_48, data.temperature, x_offset + 115, y_offset - 9, c.r, c.g, c.b); - render_string(fbp, ubuntu_mono_48, "C", x_offset + 195, y_offset - 9, c.r, c.g, c.b); - - render_string(fbp, ubuntu_mono_48, data.humidity, x_offset + 115, y_offset + 26, c.r, c.g, c.b); - render_string(fbp, ubuntu_mono_48, "%", x_offset + 195, y_offset + 26, c.r, c.g, c.b); -} - -void updateData(char *fbp, int slot_height) { - // query database for latest values - printf("MySQL client version: %s\n", mysql_get_client_info()); - MYSQL *con = mysql_init(NULL); - if (con == NULL) { - fprintf(stderr, "%s\n", mysql_error(con)); - exit(1); - } - if (mysql_real_connect(con, "web-pi", "grafanaReader", "grafanaReader", "grafanaData", 0, NULL, 0) == NULL) { - fprintf(stderr, "%s\n", mysql_error(con)); - mysql_close(con); - exit(1); - } - char* query = "\ -SELECT \ - time, \ - source, \ - metric, \ - value \ -FROM readings AS r \ -INNER JOIN sources AS s ON r.source_id = s.id \ -INNER JOIN metrics AS m ON r.metric_id = m.id \ -WHERE \ - time > (NOW() - 60 * 15) AND \ - metric IN ('Luftfeuchte','Temperatur') AND \ - source IN ('Esszimmer','Wohnzimmer', 'Schlafzimmer', 'Abstellkammer') \ -ORDER BY time DESC \ -LIMIT 100;"; - - //printf("%s\n",query); - printf("Fetching data ...\n"); - - if (mysql_query(con, query)) { - finish_with_error(con); - } - MYSQL_RES *result = mysql_store_result(con); - if (result == NULL) { - finish_with_error(con); - } - - // define what to display - sourcedata data[4] = { - { "Esszimmer", NULL, NULL }, - { "Wohnzimmer", NULL, NULL }, - { "Schlafzimmer", NULL, NULL }, - { "Abstellkammer", NULL, NULL } - }; - - int num_fields = mysql_num_fields(result); - if (num_fields > 3) { - MYSQL_ROW row; - while ((row = mysql_fetch_row(result))) { - if (strlen(row[3]) > 4) row[3][4] = '\0'; // loose the secnod and third decimal ... - - // data binding - for (int i = 0; i < 4; i++) { - if (strcmp(row[1], data[i].text) == 0) { - if (strcmp(row[2], "Temperatur") == 0 && data[i].temperature == NULL) data[i].temperature = row[3]; - else if (strcmp(row[2], "Luftfeuchte") == 0 && data[i].humidity == NULL) data[i].humidity = row[3]; - } - } - - // debugging - //for(int i = 0; i < num_fields; i++) printf("%s ", row[i] ? row[i] : "NULL"); - //printf("\n"); - } - } - - printf("Updating dashboard...\n"); - for (int i = 0; i < 4; i++) { - color c = {255,125 + 25*i,25 + 25*i}; - updateSlot(fbp, data[i], 15, 7 + slot_height * i, c); - } - - - printf("Done!\n"); - - mysql_free_result(result); - mysql_close(con); - -} - - - - -void draw() { - clear_screen(fbp); +void draw(screen fb) { + clear_screen(fb); + + int slot_height = fb.height / 4; // horizontal spacersint x_space = 10; - int y_step_width = HEIGHT / 4; for (int i = 1; i < 4; i++) { int x0 = 15; - int x1 = WIDTH - x0; - int y = y_step_width * i; + int x1 = fb.width - x0; + int y = slot_height * i; int r = 225; int g = 32; int b = 32; - draw_line(fbp, x0, y-1, x1, y-1, r, g, b); - draw_line(fbp, x0, y , x1, y , r, g, b); - draw_line(fbp, x0, y+1, x1, y+1, r, g, b); + draw_line(fb, x0, y-1, x1, y-1, r, g, b); + draw_line(fb, x0, y , x1, y , r, g, b); + draw_line(fb, x0, y+1, x1, y+1, r, g, b); } + + // define what to display + sourcedata data[4] = { + // text | temperature | humidity + { "Esszimmer" , NULL, NULL }, + { "Wohnzimmer" , NULL, NULL }, + { "Schlafzimmer" , NULL, NULL }, + { "Abstellkammer" , NULL, NULL } + }; + // fetch data from database and update screen - updateData(fbp, y_step_width); + printf("Fetching data...\n"); + updateData(data); + + // update dashboard + printf("Updating dashboard...\n"); + for (int i = 0; i < 4; i++) { + color c = {255,125 + 25*i,25 + 25*i}; + updateSlot(fb, data[i], 15, 7 + slot_height * i, c); + + // release allocated memory for values + free(data[i].temperature); + free(data[i].humidity); + } } // application entry point -int main(int argc, char* argv[]) -{ +int main(int argc, char* argv[]) { + char *fbp = 0; int fbfd = 0; long int screensize = 0; // Open the framebuffer file for reading and writing - fbfd = open("/dev/fb1", O_RDWR); + fbfd = open(DEVICE, O_RDWR); if (fbfd == -1) { printf("Error: cannot open framebuffer device.\n"); return(1); @@ -192,7 +110,7 @@ int main(int argc, char* argv[]) printf("The framebuffer device was opened successfully.\n"); // map fb to user mem - screensize = WIDTH*HEIGHT*2; + screensize = WIDTH * HEIGHT * (BITS_PER_PIXEL / 8); fbp = (char*)mmap(0, screensize, PROT_READ | PROT_WRITE, @@ -200,12 +118,24 @@ int main(int argc, char* argv[]) fbfd, 0); + if ((int)fbp == -1) { printf("Failed to mmap.\n"); } else { + // define the screen object + screen fb = { + .buffer = fbp, + .width = WIDTH, + .height = HEIGHT, + .bpp = BITS_PER_PIXEL, + .step = (BITS_PER_PIXEL / 8), + .line = (BITS_PER_PIXEL / 8) * WIDTH, + .screensize = screensize, + }; + // draw... - draw(); + draw(fb); } // cleanup diff --git a/fbdata.c b/fbdata.c new file mode 100644 index 0000000..37d892a --- /dev/null +++ b/fbdata.c @@ -0,0 +1,74 @@ + +#include "fbdata.h" + + +void finish_with_error(MYSQL *con) { + fprintf(stderr, "%s\n", mysql_error(con)); + mysql_close(con); + exit(1); +} + + +void updateData(sourcedata data[]) { + + // query database for latest values + char query[] = "\ +SELECT time, source, metric, value \ +FROM readings AS r \ +INNER JOIN (SELECT MAX(time) AS maxtime, source_id, metric_id FROM readings GROUP BY source_id,metric_id) AS mr \ + ON mr.maxtime = r.time AND mr.source_id = r.source_id AND mr.metric_id = r.metric_id \ +INNER JOIN sources AS s \ + ON r.source_id = s.id \ +INNER JOIN metrics AS m \ + ON r.metric_id = m.id;\ +"; + + + printf("Connecting to database ...\n"); + printf("MySQL client version: %s\n", mysql_get_client_info()); + + MYSQL *con; + if (!(con = mysql_init(NULL))) finish_with_error(con); + if (!mysql_real_connect(con, "web-pi", "grafanaReader", "grafanaReader", "grafanaData", 0, NULL, 0)) finish_with_error(con); + + //printf("%s\n",query); + printf("Running query ...\n"); + //printf("Query: %s\n", query); + if (mysql_query(con, query)) finish_with_error(con); + printf("Finished.\n"); + + MYSQL_RES *result; + if (!(result = mysql_store_result(con))) finish_with_error(con); + int num_fields = mysql_num_fields(result); + if (num_fields > 3) { // sanity check to honor bounds + MYSQL_ROW row; + while ((row = mysql_fetch_row(result))) { + unsigned long *length = mysql_fetch_lengths (result); // only valid for current row + int l = *(length + 3) - 2; + //printf("%s [l = %d]\n", row[3], l); + + // data binding + for (int i = 0; i < 4; i++) { + if (strcmp(row[1], data[i].text) == 0) { + if (strcmp(row[2], "Temperatur") == 0 && data[i].temperature == NULL) { + data[i].temperature = calloc((l + 1),sizeof(char)); // +1 for null-termination + memcpy(data[i].temperature, row[3], l * sizeof(char)); + } + else if (strcmp(row[2], "Luftfeuchte") == 0 && data[i].humidity == NULL){ + data[i].humidity = calloc((l + 1),sizeof(char)); // +1 for null-termination + memcpy(data[i].humidity, row[3], l * sizeof(char)); + } + } + } + + // debugging + //for(int i = 0; i < num_fields; i++) printf("%s ", row[i] ? row[i] : "NULL"); + //printf("\n"); + } + } + // free memory after dashboard was updated + mysql_free_result(result); + mysql_close(con); + printf("Done!\n"); +} + diff --git a/fbdata.h b/fbdata.h new file mode 100644 index 0000000..0e53199 --- /dev/null +++ b/fbdata.h @@ -0,0 +1,18 @@ + + +#include +#include +#include +#include + +struct s_sourcedata { + char* text; + char* temperature; + char* humidity; +}; +typedef struct s_sourcedata sourcedata; + + +void finish_with_error(MYSQL *con); +void updateData(sourcedata data[]); + diff --git a/fblib.c b/fblib.c index f21cc99..ef34285 100644 --- a/fblib.c +++ b/fblib.c @@ -1,136 +1,30 @@ -/* - * fblib.c - * - * Adapted by Florian Klemenz for use in the fb_dash project - * - * Source and all credit goes to - * http://raspberrycompote.blogspot.ie/2014/03/low-level-graphics-on-raspberry-pi-part_14.html - * - * Original work by J-P Rosti (a.k.a -rst- and 'Raspberry Compote') - * - * Licensed under the Creative Commons Attribution 3.0 Unported License - * (http://creativecommons.org/licenses/by/3.0/deed.en_US) - * - * Distributed in the hope that this will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - */ - #include "fblib.h" // helper function to 'plot' a pixel in given color -void put_pixel(char *fbp, int x, int y, int r, int g, int b) +void put_pixel(screen fb, int x, int y, int r, int g, int b) { // calculate the pixel's byte offset inside the buffer // note: x * 2 as every pixel is 2 consecutive bytes //unsigned int pix_offset = x * 2 + y * finfo.line_length; - unsigned int pix_offset = x * 2 + y * (WIDTH * 2); + unsigned int pix_offset = x * fb.step + y * fb.line; // now this is about the same as 'fbp[pix_offset] = value' // but a bit more complicated for RGB565 //unsigned short c = ((r / 8) << 11) + ((g / 4) << 5) + (b / 8); unsigned short c = ((r / 8) * 2048) + ((g / 4) * 32) + (b / 8); // write 'two bytes at once' - *((unsigned short*)(fbp + pix_offset)) = c; - -} - -// helper function to draw a rectangle in given color -void fill_rect(char *fbp, int x, int y, int w, int h, int r, int g, int b) { - int cx, cy; - for (cy = 0; cy < h; cy++) { - for (cx = 0; cx < w; cx++) { - put_pixel(fbp, x + cx, y + cy, r, g, b); - } - } -} - -// helper function to draw a rectangle outline in given color -void draw_rect(char *fbp, int x0, int y0, int w, int h, int r, int g, int b) { - draw_line(fbp, x0, y0, x0 + w, y0, r, g, b); // top - draw_line(fbp, x0, y0, x0, y0 + h, r, g, b); // left - draw_line(fbp, x0, y0 + h, x0 + w, y0 + h, r, g, b); // bottom - draw_line(fbp, x0 + w, y0, x0 + w, y0 + h, r, g, b); // right -} - -// helper function to draw a circle outline in given color -// (uses Bresenham's circle algorithm) -void draw_circle(char *fbp, int x0, int y0, int radius, int r, int g, int b) -{ - int x = radius; - int y = 0; - int radiusError = 1 - x; - - while(x >= y) - { - // top left - put_pixel(fbp, -y + x0, -x + y0, r, g, b); - // top right - put_pixel(fbp, y + x0, -x + y0, r, g, b); - // upper middle left - put_pixel(fbp, -x + x0, -y + y0, r, g, b); - // upper middle right - put_pixel(fbp, x + x0, -y + y0, r, g, b); - // lower middle left - put_pixel(fbp, -x + x0, y + y0, r, g, b); - // lower middle right - put_pixel(fbp, x + x0, y + y0, r, g, b); - // bottom left - put_pixel(fbp, -y + x0, x + y0, r, g, b); - // bottom right - put_pixel(fbp, y + x0, x + y0, r, g, b); - - y++; - if (radiusError < 0) - { - radiusError += 2 * y + 1; - } else { - x--; - radiusError+= 2 * (y - x + 1); - } - } -} - -// helper function to draw a filled circle in given color -// (uses Bresenham's circle algorithm) -void fill_circle(char *fbp, int x0, int y0, int radius, int r, int g, int b) { - int x = radius; - int y = 0; - int radiusError = 1 - x; - - while(x >= y) - { - // top - draw_line(fbp, -y + x0, -x + y0, y + x0, -x + y0, r, g, b); - // upper middle - draw_line(fbp, -x + x0, -y + y0, x + x0, -y + y0, r, g, b); - // lower middle - draw_line(fbp, -x + x0, y + y0, x + x0, y + y0, r, g, b); - // bottom - draw_line(fbp, -y + x0, x + y0, y + x0, x + y0, r, g, b); - - y++; - if (radiusError < 0) - { - radiusError += 2 * y + 1; - } else { - x--; - radiusError+= 2 * (y - x + 1); - } - } + *((unsigned short*)(fb.buffer + pix_offset)) = c; } // helper function to clear the screen - fill whole // screen with given color -void clear_screen(char *fbp) { - //memset(fbp, 0, vinfo.xres * vinfo.yres * 2); // 16Bit = 8Bit * 2 - memset(fbp, 0, WIDTH * HEIGHT * 2); // 16Bit = 8Bit * 2 +void clear_screen(screen fb) { + memset(fb.buffer, 0, fb.screensize); } // helper function to draw a line in given color // (uses Bresenham's line algorithm) -void draw_line(char *fbp, int x0, int y0, int x1, int y1, int r, int g, int b) { +void draw_line(screen fb, int x0, int y0, int x1, int y1, int r, int g, int b) { int dx = x1 - x0; dx = (dx >= 0) ? dx : -dx; // abs() int dy = y1 - y0; @@ -149,7 +43,7 @@ void draw_line(char *fbp, int x0, int y0, int x1, int y1, int r, int g, int b) { int e2; int done = 0; while (!done) { - put_pixel(fbp, x0, y0, r, g, b); + put_pixel(fb, x0, y0, r, g, b); if ((x0 == x1) && (y0 == y1)) done = 1; else { @@ -166,7 +60,93 @@ void draw_line(char *fbp, int x0, int y0, int x1, int y1, int r, int g, int b) { } } -void render_string(char *fbp, fbfont font, char *text, int x, int y, int r, int g, int b) +// helper function to draw a rectangle in given color +void fill_rect(screen fb, int x, int y, int w, int h, int r, int g, int b) { + int cx, cy; + for (cy = 0; cy < h; cy++) { + for (cx = 0; cx < w; cx++) { + put_pixel(fb, x + cx, y + cy, r, g, b); + } + } +} + +// helper function to draw a rectangle outline in given color +void draw_rect(screen fb, int x0, int y0, int w, int h, int r, int g, int b) { + draw_line(fb, x0, y0, x0 + w, y0, r, g, b); // top + draw_line(fb, x0, y0, x0, y0 + h, r, g, b); // left + draw_line(fb, x0, y0 + h, x0 + w, y0 + h, r, g, b); // bottom + draw_line(fb, x0 + w, y0, x0 + w, y0 + h, r, g, b); // right +} + +// helper function to draw a circle outline in given color +// (uses Bresenham's circle algorithm) +void draw_circle(screen fb, int x0, int y0, int radius, int r, int g, int b) +{ + int x = radius; + int y = 0; + int radiusError = 1 - x; + + while(x >= y) + { + // top left + put_pixel(fb, -y + x0, -x + y0, r, g, b); + // top right + put_pixel(fb, y + x0, -x + y0, r, g, b); + // upper middle left + put_pixel(fb, -x + x0, -y + y0, r, g, b); + // upper middle right + put_pixel(fb, x + x0, -y + y0, r, g, b); + // lower middle left + put_pixel(fb, -x + x0, y + y0, r, g, b); + // lower middle right + put_pixel(fb, x + x0, y + y0, r, g, b); + // bottom left + put_pixel(fb, -y + x0, x + y0, r, g, b); + // bottom right + put_pixel(fb, y + x0, x + y0, r, g, b); + + y++; + if (radiusError < 0) + { + radiusError += 2 * y + 1; + } else { + x--; + radiusError+= 2 * (y - x + 1); + } + } +} + +// helper function to draw a filled circle in given color +// (uses Bresenham's circle algorithm) +void fill_circle(screen fb, int x0, int y0, int radius, int r, int g, int b) { + int x = radius; + int y = 0; + int radiusError = 1 - x; + + while(x >= y) + { + // top + draw_line(fb, -y + x0, -x + y0, y + x0, -x + y0, r, g, b); + // upper middle + draw_line(fb, -x + x0, -y + y0, x + x0, -y + y0, r, g, b); + // lower middle + draw_line(fb, -x + x0, y + y0, x + x0, y + y0, r, g, b); + // bottom + draw_line(fb, -y + x0, x + y0, y + x0, x + y0, r, g, b); + + y++; + if (radiusError < 0) + { + radiusError += 2 * y + 1; + } else { + x--; + radiusError+= 2 * (y - x + 1); + } + } +} + + +void render_string(screen fb, fbfont font, char *text, int x, int y, int r, int g, int b) { int text_length = strlen(text); //printf("text_length = %d\n", text_length); @@ -199,7 +179,7 @@ void render_string(char *fbp, fbfont font, char *text, int x, int y, int r, int //printf("x"); int x_pos = x_offset + byte_num * 8 + bit_number; - put_pixel(fbp, x_pos, y_pos, r, g, b); + put_pixel(fb, x_pos, y_pos, r, g, b); } else { // leave empty (or maybe plot 'text backgr color') diff --git a/fblib.h b/fblib.h index 306bb06..07b0c59 100644 --- a/fblib.h +++ b/fblib.h @@ -1,23 +1,3 @@ -/* - * fblib.h - * - * Adapted by Florian Klemenz for use in the fb_dash project - * - * Source and all credit goes to - * http://raspberrycompote.blogspot.ie/2014/03/low-level-graphics-on-raspberry-pi-part_14.html - * - * Original work by J-P Rosti (a.k.a -rst- and 'Raspberry Compote') - * - * Licensed under the Creative Commons Attribution 3.0 Unported License - * (http://creativecommons.org/licenses/by/3.0/deed.en_US) - * - * Distributed in the hope that this will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - */ - - #ifndef FBLIB_H #define FBLIB_H @@ -31,18 +11,35 @@ #define WIDTH 240 #define HEIGHT 320 +struct s_color { + unsigned char r; + unsigned char g; + unsigned char b; +}; +typedef struct s_color color; -void put_pixel(char *fbp, int x, int y, int r, int g, int b); -void clear_screen(char *fbp); +struct s_screen { + char *buffer; + unsigned int width; + unsigned int height; + unsigned char bpp; // bits per pixel + unsigned char step; // byte-wise step per pixel + unsigned int line; // line length + unsigned int screensize; +}; +typedef struct s_screen screen; -void draw_line(char *fbp, int x0, int y0, int x1, int y1, int r, int g, int b); +void put_pixel(screen fb, int x, int y, int r, int g, int b); +void clear_screen(screen fb); -void draw_rect(char *fbp, int x0, int y0, int w, int h, int r, int g, int b); -void fill_rect(char *fbp, int x, int y, int w, int h, int r, int g, int b); +void draw_line(screen fb, int x0, int y0, int x1, int y1, int r, int g, int b); -void draw_circle(char *fbp, int x0, int y0, int radius, int r, int g, int b); -void fill_circle(char *fbp, int x0, int y0, int radius, int r, int g, int b); +void draw_rect(screen fb, int x0, int y0, int w, int h, int r, int g, int b); +void fill_rect(screen fb, int x, int y, int w, int h, int r, int g, int b); -void render_string(char *fbp, fbfont font, char *text, int x, int y, int r, int g, int b); +void draw_circle(screen fb, int x0, int y0, int radius, int r, int g, int b); +void fill_circle(screen fb, int x0, int y0, int radius, int r, int g, int b); + +void render_string(screen fb, fbfont font, char *text, int x, int y, int r, int g, int b); #endif