aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Phillips <david@yeah.nah.nz>2019-03-02 23:07:30 +1300
committerDavid Phillips <david@yeah.nah.nz>2019-03-02 23:07:30 +1300
commit4982b556540b403a1411436ed398cc523653d82f (patch)
tree86692927264f0a4e4afa3c6f4e330d99ccb74a49
downloadclock-disp-4982b556540b403a1411436ed398cc523653d82f.tar.xz
Initial working dump
-rw-r--r--.gitignore1
-rw-r--r--Makefile4
-rw-r--r--config.mk2
-rw-r--r--test.c397
4 files changed, 404 insertions, 0 deletions
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 <SDL.h>
+#include <SDL_ttf.h>
+#include <stdio.h>
+#include <time.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <semaphore.h>
+
+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;
+}
+