From 4982b556540b403a1411436ed398cc523653d82f Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 2 Mar 2019 23:07:30 +1300 Subject: Initial working dump --- .gitignore | 1 + Makefile | 4 + config.mk | 2 + test.c | 397 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 404 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 config.mk create mode 100644 test.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +test diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..328ae10 --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +include config.mk + +all: test + diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..c1d0221 --- /dev/null +++ b/config.mk @@ -0,0 +1,2 @@ +CFLAGS += -I/usr/include/SDL2 +LDFLAGS += -lSDL2 -lSDL2_ttf -lpthread diff --git a/test.c b/test.c new file mode 100644 index 0000000..c8d8c74 --- /dev/null +++ b/test.c @@ -0,0 +1,397 @@ +#include +#include +#include +#include +#include +#include +#include + +enum ALIGN_MODE { + ALIGN_MODE_LEFT, + ALIGN_MODE_CENTRE, + ALIGN_MODE_RIGHT, +}; + +#define WINDOW_TITLE "Clock Display" +#define SCREEN_WIDTH 800 +#define SCREEN_HEIGHT 480 + +#define TIME_X 0 +#define TIME_Y -50 + +#define NO_LOCK_Y 150 + +#define DATE_1_X 20 +#define DATE_1_Y 295 +#define DATE_2_X 20 +#define DATE_2_Y 350 + +#define SECONDS_X SCREEN_WIDTH - 80 +#define SECONDS_Y SCREEN_HEIGHT - 55 + +#define NTP_INFO_X 240 +#define NTP_INFO_Y SCREEN_HEIGHT - 55 +#define SAT_INFO_X 20 +#define SAT_INFO_Y NTP_INFO_Y + +#define SUN_INFO_X SCREEN_WIDTH -20 +#define SUN_INFO_1_Y DATE_1_Y +#define SUN_INFO_2_Y DATE_2_Y + +/* Common colours used */ +#define COLOUR_WHITE ((SDL_Color){255,255,255,255}) +#define COLOUR_YELLOW ((SDL_Color){255,191, 32,255}) +#define COLOUR_40GRAY ((SDL_Color){100,100,100,255}) + +static sem_t data_lock; +struct clock_data { + int is_unread; + char sunrise_time[8]; + char sunset_time[8]; + int sat_seen; + int sat_used; + int leap_second; + int offset; + char offset_units[8]; + int no_gps_lock; +}; + +static volatile struct clock_data shared_data; + +/** + * Publish a struct clock_data to the volatile global clock data object. + * Caller MUST already hold data_lock + */ +void publish_clock_data(struct clock_data *l) +{ + char *d = (char*)&shared_data; + char *s = (char*)l; + size_t i = 0; + for (i = 0; i < sizeof(struct clock_data); i++) { + d[i] = s[i]; + } + shared_data.is_unread = 1; +} + +/** + * Copy the published struct clock_data to a non-volatile clock data object. + * Caller MUST already hold data_lock + */ +void get_clock_data(struct clock_data *l) +{ + char *d = (char*)l; + char *s = (char*)&shared_data; + size_t i = 0; + for (i = 0; i < sizeof(struct clock_data); i++) { + d[i] = s[i]; + } +} + +/** + * Read clock's statistics from files and push them into shared data area for + * display thread to pick up + */ +void *read_stats(void* unused) +{ + char buffer[32]; + char *next = buffer; + FILE *f = NULL; + struct clock_data l; + memset(&l, 0, sizeof(l)); + + while (1) { + /* Read sunrise, sunset */ + if (f = fopen("/tmp/stats-sun", "r")) { + if (fgets(buffer, sizeof(buffer), f)) { + /* format is %d/%d\n, first is used sats, second is total seen */ + if (next = strchr(buffer, ',')) { + *(next++) = '\0'; + next[strcspn(next, "\n\r\t")] = '\0'; + strncpy(l.sunrise_time, buffer, sizeof(l.sunrise_time)); + strncpy(l.sunset_time, next, sizeof(l.sunset_time)); + } + } + fclose(f); + } + + /* Read satellite stats */ + if (f = fopen("/tmp/stats-sats", "r")) { + if (fgets(buffer, sizeof(buffer), f)) { + /* format is %d,%d\n, first is used sats, second is total seen */ + if (next = strchr(buffer, '/')) { + *(next++) = '\0'; + l.sat_used = strtod(buffer, NULL); + l.sat_seen = strtod(next, NULL); + } + } + fclose(f); + } + + /* read leap second status file - also helps determine lock */ + if (f = fopen("/tmp/stats-leap", "r")) { + if (fgets(buffer, sizeof(buffer), f)) { + buffer[strcspn(buffer, "\n\r\t")] = '\0'; + l.no_gps_lock = !strcmp(buffer, "Not synchronised"); + l.leap_second = strcmp(buffer, "Normal"); + } + fclose(f); + } + + if (f = fopen("/tmp/stats-clock", "r")) { + if (fgets(buffer, sizeof(buffer), f)) { + const char *units[] = { "s", "ms", "µs", "ns", "ps", "fs" }; + int i = 0; + double offset = fabs(strtod(buffer, NULL)); + while (offset < 1 && i < sizeof(units)/sizeof(units[0]) - 1) { + i++; + offset *= 1000; + } + l.offset = offset; + strncpy(l.offset_units, units[i], sizeof(l.offset_units)); + } + fclose(f); + } + sem_wait(&data_lock); + publish_clock_data(&l); + sem_post(&data_lock); + sleep(5); + } +} + +const char *get_th_suffix(unsigned int day) +{ + if (11 <= day && day <= 19) { + return "th"; + } else { + switch (day % 10) { + case 1: return "st"; + case 2: return "nd"; + case 3: return "rd"; + default: /* 0, 4-9 */ + return "th"; + } + } +} + +TTF_Font *loadFont(const char *file, int size) +{ + TTF_Font *font = TTF_OpenFont(file, size); + if (!font) { + printf("Error: TTF_OpenFont: %s\n", TTF_GetError()); + return NULL; + } + return font; +} + +void renderTexture(SDL_Texture *tex, SDL_Renderer *ren, int x, int y){ + //Setup the destination rectangle to be at the position we want + SDL_Rect dst; + dst.x = x; + dst.y = y; + //Query the texture to get its width and height to use + SDL_QueryTexture(tex, NULL, NULL, &dst.w, &dst.h); + SDL_RenderCopy(ren, tex, NULL, &dst); +} + + +SDL_Texture* renderText(const char *message, TTF_Font *font, SDL_Color color, SDL_Renderer *renderer) +{ + //We need to first render to a surface as that's what TTF_RenderText + //returns, then load that surface into a texture + SDL_Surface *surf = TTF_RenderUTF8_Blended(font, message, color); + if (!surf){ + printf("Error in TTF_RenderUTF8_Blended\n"); + return NULL; + } + SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surf); + if (!texture){ + printf("Error in SDL_CreateTextureFromSurface\n"); + } + //Clean up the surface and font + SDL_FreeSurface(surf); + return texture; +} + +int drawText(const char *text, TTF_Font *font, SDL_Color color, SDL_Renderer *renderer, int x, int y, enum ALIGN_MODE mode) { + /* FIXME does this need better error detection */ + int text_w; + SDL_Texture *image; + image = renderText(text, font, color, renderer); + SDL_QueryTexture(image, NULL, NULL, &text_w, NULL); + + if (mode == ALIGN_MODE_CENTRE) { + x = SCREEN_WIDTH / 2 - text_w / 2; + } else if (mode == ALIGN_MODE_RIGHT) { + x -= text_w; + } + renderTexture(image, renderer, x, y); + SDL_DestroyTexture(image); + return x + (mode == ALIGN_MODE_RIGHT? 0 : 1) * text_w; +} + +int main(void) +{ + struct clock_data l; + + /* initial unread indicator value */ + shared_data.is_unread = 0; + + strncpy(l.sunrise_time, "??:??", sizeof(l.sunrise_time)); + strncpy(l.sunset_time, "??:??", sizeof(l.sunset_time)); + l.sat_seen = 0; + l.sat_used = 0; + l.leap_second = 0; + l.offset = 0; + strncpy(l.offset_units, "?", sizeof(l.offset_units)); + l.no_gps_lock = 1; + + if (sem_init(&data_lock, 0, 1)) { + perror("sem_init"); + return 1; + } + + int stop = 0; + SDL_Event e; + SDL_Window *window; + + SDL_Init(SDL_INIT_VIDEO); + TTF_Init(); + + TTF_Font *mainTimeFont = loadFont("/usr/share/fonts/TTF/RobotoMono-Regular.ttf", 325); + TTF_Font *noLockFont = loadFont("/usr/share/fonts/TTF/Roboto-Regular.ttf", 130); + TTF_Font *secondsFont = loadFont("/usr/share/fonts/TTF/RobotoMono-Regular.ttf", 30); + + TTF_Font *dateFont = loadFont("/usr/share/fonts/TTF/Roboto-Regular.ttf", 50); + TTF_Font *dateSymbolFont = loadFont("/usr/share/fonts/TTF/Symbola.ttf", 50); + + TTF_Font *ntpInfoFont= loadFont("/usr/share/fonts/TTF/Roboto-Regular.ttf", 30); + TTF_Font *ntpSymbolFont = loadFont("/usr/share/fonts/TTF/Symbola.ttf", 30); + if ( !ntpSymbolFont + || !dateSymbolFont + || !mainTimeFont + || !dateFont + || !secondsFont + || !ntpInfoFont) { + return 1; + } + + /* launch pthread */ + pthread_t thread; + if (pthread_create(&thread, NULL, read_stats, NULL) != 0) { + perror("pthread_create"); + return 1; + } + + window = SDL_CreateWindow(WINDOW_TITLE, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_OPENGL); + if (!window) { + printf("Could not create window: %s\n", SDL_GetError()); + return 1; + } + + SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (!renderer) { + SDL_DestroyWindow(window); + printf("SDL_CreateRenderer Error\n"); + SDL_Quit(); + return 1; + } + + while (!stop) + { + while (SDL_PollEvent(&e)){ + if ( e.type == SDL_QUIT + || e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_q) { + stop = 1; + } + } + char text[64]; + time_t t; + struct tm *time_s; + + + SDL_Texture *image; + int text_w; + + SDL_RenderClear(renderer); + + t = time(NULL); + if ((time_s = localtime(&t)) == NULL) { + /* FIXME error */ + continue; + } + + int next_x = 0; + + sem_wait(&data_lock); + if (shared_data.is_unread) { + get_clock_data(&l); + shared_data.is_unread = 0; + } + sem_post(&data_lock); + + if (l.sat_seen == 0 || l.sat_used == 0 || l.no_gps_lock) { + drawText("No GPS Lock", noLockFont, COLOUR_WHITE, renderer, 0, NO_LOCK_Y, ALIGN_MODE_CENTRE); + } else { + /* Have GPS lock, go for it */ + /**/ + strftime(text, sizeof(text), "%H%M", time_s); + drawText(text, mainTimeFont, COLOUR_WHITE, renderer, TIME_X, TIME_Y, ALIGN_MODE_CENTRE); + /**/ + + /********************/ + /* Draws a date string, normally like: + * Monday the 25th + * February, 2019 + */ + strftime(text, sizeof(text), "%A", time_s); + next_x = drawText(text, dateFont, COLOUR_WHITE, renderer, DATE_1_X, DATE_1_Y, ALIGN_MODE_LEFT); + + next_x = drawText(" the ", dateFont, COLOUR_40GRAY, renderer, next_x, DATE_1_Y, ALIGN_MODE_LEFT); + + snprintf(text, sizeof(text), "%d%s", time_s->tm_mday, get_th_suffix(time_s->tm_mday)); + next_x = drawText(text, dateFont, COLOUR_WHITE, renderer, next_x, DATE_1_Y, ALIGN_MODE_LEFT); + next_x = drawText(".", dateFont, COLOUR_40GRAY, renderer, next_x, DATE_1_Y, ALIGN_MODE_LEFT); + + strftime(text, sizeof(text), "%B", time_s); + next_x = drawText(text, dateFont, COLOUR_WHITE, renderer, DATE_2_X, DATE_2_Y, ALIGN_MODE_LEFT); + + next_x = drawText(", ", dateFont, COLOUR_40GRAY, renderer, next_x, DATE_2_Y, ALIGN_MODE_LEFT); + + strftime(text, sizeof(text), "%Y", time_s); + next_x = drawText(text, dateFont, COLOUR_WHITE, renderer, next_x, DATE_2_Y, ALIGN_MODE_LEFT); + /********************/ + + /**/ + strftime(text, sizeof(text), ":%S", time_s); + next_x = drawText(text, secondsFont, COLOUR_WHITE, renderer, SECONDS_X, SECONDS_Y, ALIGN_MODE_LEFT); + /**/ + + /**/ + next_x = drawText("⏰ ", ntpSymbolFont, COLOUR_40GRAY, renderer, NTP_INFO_X, NTP_INFO_Y, ALIGN_MODE_LEFT); + snprintf(text, sizeof(text), " RMS %d %s%s ", l.offset, l.offset_units, + l.leap_second ? ", Leap second coming!" : ""); + next_x = drawText(text, ntpInfoFont, COLOUR_40GRAY, renderer, next_x, NTP_INFO_Y, ALIGN_MODE_LEFT); + /**/ + + /**/ + next_x = drawText(l.sunrise_time, dateFont, COLOUR_WHITE, renderer, SUN_INFO_X, SUN_INFO_1_Y, ALIGN_MODE_RIGHT); + next_x = drawText("☀⬆", dateSymbolFont, COLOUR_YELLOW, renderer, next_x, SUN_INFO_1_Y, ALIGN_MODE_RIGHT); + next_x = drawText(l.sunset_time, dateFont, COLOUR_WHITE, renderer, SUN_INFO_X, SUN_INFO_2_Y, ALIGN_MODE_RIGHT); + next_x = drawText("☀⬇", dateSymbolFont, COLOUR_YELLOW, renderer, next_x, SUN_INFO_2_Y, ALIGN_MODE_RIGHT); + // next_x = drawText("⭡05:45 ↓20:13", dateFont, COLOUR_WHITE, renderer, SUN_INFO_X, SUN_INFO_Y); + /**/ + } + /**/ + next_x = drawText("🛰 ", ntpSymbolFont, COLOUR_40GRAY, renderer, SAT_INFO_X, SAT_INFO_Y, ALIGN_MODE_LEFT); + snprintf(text, sizeof(text), "%d (%d used)", l.sat_seen, l.sat_used); + next_x = drawText(text, ntpInfoFont, COLOUR_40GRAY, renderer, next_x, NTP_INFO_Y, ALIGN_MODE_LEFT); + /**/ + SDL_RenderPresent(renderer); + SDL_Delay(100); + } + + SDL_DestroyWindow(window); + SDL_Quit(); + return 0; +} + -- cgit v1.1