Initial commit of `nedfp` as a front panel for `nedsim`.
[ned1] / nedfp / nedfp.c
diff --git a/nedfp/nedfp.c b/nedfp/nedfp.c
new file mode 100644 (file)
index 0000000..30f7e6b
--- /dev/null
@@ -0,0 +1,298 @@
+/*
+ * © 2018 Aaron Taylor <ataylor at subgeniuskitty dot com>
+ * See LICENSE.txt file for copyright and license details.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_ttf.h>
+
+#define VERSION 1
+
+#define DEFAULT_PORT    "4999"
+#define MAX_STRING      255
+#define BITSPERWORD     32
+
+#define WINDOW_HEIGHT   1000
+#define WINDOW_WIDTH    1000
+#define DOT_HEIGHT      (WINDOW_HEIGHT / 140)
+#define DOT_WIDTH       (WINDOW_WIDTH / 140)
+#define CHUNK_HEIGHT    (DOT_HEIGHT * 4)
+#define CHUNK_WIDTH     (DOT_WIDTH * 8)
+#define FONT_WIDTH      (DOT_WIDTH * 4)
+#define BORDER          (DOT_WIDTH * 3)
+
+void
+print_usage(char ** argv)
+{
+    printf( "NED Front Panel v%d (www.subgeniuskitty.com)\n"
+            "Usage: %s\n"
+            "  -h                      Help (prints this message)\n"
+            "  -f <path, optional>     Path to a TTF file.\n"
+            "                          Default is './liberation_mono.tff'.\n"
+            "  -p <port, optional>     Port to listen on for NED snapshots.\n"
+            "                          Default is 4999.\n"
+            , VERSION, argv[0]
+    );
+}
+
+void
+word_to_leds(uint32_t voffset, uint32_t hoffset, uint32_t word, SDL_Renderer * renderer)
+{
+    SDL_Rect r;
+
+    for (int i=0; i<8; i++) {
+        if (i%2) {
+            SDL_SetRenderDrawColor(renderer, 100, 150, 100, SDL_ALPHA_OPAQUE);
+        } else {
+            SDL_SetRenderDrawColor(renderer, 100, 100, 150, SDL_ALPHA_OPAQUE);
+        }
+        r.x = ((CHUNK_WIDTH * i) + hoffset);
+        r.y = voffset;
+        r.h = CHUNK_HEIGHT;
+        r.w = CHUNK_WIDTH;
+        SDL_RenderFillRect(renderer, &r);
+    }
+
+    for (int i=0; i<BITSPERWORD; i++) {
+        r.x = ((((2 * (BITSPERWORD - i)) * DOT_WIDTH) - (1.5 * DOT_WIDTH)) + hoffset);
+        r.y = (voffset + (2 * DOT_HEIGHT));
+        r.w = DOT_WIDTH;
+        r.h = DOT_HEIGHT;
+        if (((word >> i)) & 1) {
+            SDL_SetRenderDrawColor(renderer, 255, 255, 255, SDL_ALPHA_OPAQUE);
+        } else {
+            SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
+        }
+        SDL_RenderFillRect(renderer, &r);
+    }
+}
+
+void
+print_text(uint32_t voffset, uint32_t hoffset, char * str, TTF_Font * font, SDL_Renderer * renderer)
+{
+    SDL_Rect r;
+
+    r.x = hoffset;
+    r.y = voffset;
+    r.h = CHUNK_HEIGHT;
+    r.w = (FONT_WIDTH * strnlen(str, MAX_STRING));
+
+    SDL_Color white = {255,255,255};
+
+    SDL_Surface * surface = TTF_RenderText_Solid(font, str, white);
+    SDL_Texture * texture = SDL_CreateTextureFromSurface(renderer, surface);
+    SDL_RenderCopy(renderer, texture, NULL, &r);
+
+    SDL_DestroyTexture(texture);
+    SDL_FreeSurface(surface);
+}
+
+void
+sdl_init(SDL_Window ** window, SDL_Renderer ** renderer, TTF_Font ** font, char * font_path)
+{
+    /* Initialize SDL subsystems. */
+    /* We require the VIDEO subsystem for display. */
+    /* The EVENTS and FILE I/O subsystems are initialized by default. */
+    SDL_Init(SDL_INIT_VIDEO);
+    if(TTF_Init() == -1) printf("TTF_Init: %s\n", TTF_GetError());
+
+    /* Create an SDL window and renderer, and populate the pointers. */
+    SDL_CreateWindowAndRenderer(WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, window, renderer);
+
+    /* Clear window so it appears normal to user. */
+    SDL_SetRenderDrawColor(*renderer, 128, 128, 128, SDL_ALPHA_OPAQUE);
+    SDL_RenderClear(*renderer);
+    SDL_RenderPresent(*renderer);
+
+    *font = TTF_OpenFont(font_path, 24);
+    if (font == NULL) printf("TTF_OpenFont: %s\n", TTF_GetError());
+}
+
+void
+sigint_handler(int sig)
+{
+    exit(EXIT_SUCCESS);
+}
+
+bool
+valid_sequence_number(uint32_t new_sn)
+{
+    static uint32_t old_sn;
+
+    /* Comparison is "new_sn > old_sn" per RFC1982: Serial Number Arithmetic. */
+    if (
+            ((new_sn < old_sn) && ((old_sn - new_sn) > (0x80000000)))
+            ||
+            ((new_sn > old_sn) && ((new_sn - old_sn) < (0x80000000)))
+        ) {
+        old_sn = new_sn;
+        return true;
+    } else {
+        return false;
+    }
+}
+
+int
+main(int argc, char ** argv)
+{
+    /*
+     * Process command line arguments
+     */
+    int c;
+    char * font_path = NULL;
+    char * fp_port = NULL;
+    while ((c = getopt(argc,argv,"hp:f:")) != -1) {
+        switch (c) {
+            case 'f':
+                // TODO: What do I want to consider valid input?
+                font_path = optarg;
+                break;
+            case 'p':
+                // TODO: What do I want to consider valid input?
+                fp_port = optarg;
+                break;
+            case 'h':
+                print_usage(argv);
+                exit(EXIT_SUCCESS);
+                break;
+            default:
+                break;
+        }
+    }
+
+    /*
+     * Initialization
+     */
+    if (font_path == NULL) asprintf(&font_path, "./liberation_mono.ttf");
+    if (fp_port == NULL) asprintf(&fp_port, "4999");
+
+    signal(SIGINT, sigint_handler);
+
+    SDL_Window * window;
+    SDL_Renderer * renderer;
+    TTF_Font * font;
+
+    sdl_init(&window, &renderer, &font, font_path);
+
+    int sockfd;
+    struct addrinfo hints;
+    struct addrinfo * servinfo;
+    struct addrinfo * p;
+    int rv;
+
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = AF_INET;
+    hints.ai_socktype = SOCK_DGRAM;
+    hints.ai_flags = AI_PASSIVE;
+    if ((rv = getaddrinfo(NULL, fp_port, &hints, &servinfo)) != 0) {
+        fprintf(stderr, "ERROR: getaddrinfo: %s\n", gai_strerror(rv));
+        exit(EXIT_FAILURE);
+    }
+    for (p = servinfo; p != NULL; p = p->ai_next) {
+        if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
+            continue;
+        }
+        if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
+            close(sockfd);
+            continue;
+        }
+        break;
+    }
+    if (p == NULL) {
+        fprintf(stderr, "ERROR: Failed to bind to socket.\n");
+        exit(EXIT_FAILURE);
+    }
+    freeaddrinfo(servinfo);
+
+    while(1) {
+        /*
+         * Datagram format (all entries are 4 bytes):
+         *   Sequence number (See RFC1982)
+         *   Currently executing word
+         *   PC
+         *   SC
+         *   PSW
+         *   SP
+         *   Top 16 stack entries
+         *   RAM start address
+         *   Top 16 RAM words from start address
+         */
+
+        uint32_t word[39];
+        socklen_t addr_len;
+        int numbytes;
+        struct sockaddr_storage their_addr;
+        char * msg;
+
+        addr_len = sizeof their_addr;
+        if ((numbytes = recvfrom(sockfd, word, 156, 0,
+                (struct sockaddr *) &their_addr, &addr_len)) == -1
+            ) {
+            fprintf(stderr, "WARNING: Unable to receive matching snapshot packet.\n");
+        }
+
+        if ((numbytes == 156) && valid_sequence_number(word[0])) {
+            /* Clear the screen */
+            SDL_SetRenderDrawColor(renderer, 128, 128, 128, SDL_ALPHA_OPAQUE);
+            SDL_RenderClear(renderer);
+    
+            /* Write the PC */
+            asprintf(&msg, "PC: 0x%08x", word[2]);
+            print_text((1 * CHUNK_HEIGHT), BORDER, msg, font, renderer);
+            free(msg);
+            word_to_leds((2 * CHUNK_HEIGHT), BORDER, word[2], renderer);
+    
+            /* Write the SC */
+            asprintf(&msg, "SC: 0x%08x", word[3]);
+            print_text((4 * CHUNK_HEIGHT), BORDER, msg, font, renderer);
+            free(msg);
+            word_to_leds((5 * CHUNK_HEIGHT), BORDER, word[3], renderer);
+    
+            /* Write the stack */
+            asprintf(&msg, "TOS@%u", word[5]);
+            print_text((7 * CHUNK_HEIGHT), BORDER, msg, font, renderer);
+            free(msg);
+            for (int i=0; i<16; i++) {
+                word_to_leds(((i+8) * CHUNK_HEIGHT), BORDER, word[(i+6)], renderer);
+            }
+    
+            /* Write the CW */
+            asprintf(&msg, "CW: 0x%08x", word[1]);
+            print_text((1 * CHUNK_HEIGHT), ((WINDOW_WIDTH / 2) + BORDER), msg, font, renderer);
+            free(msg);
+            word_to_leds((2 * CHUNK_HEIGHT), ((WINDOW_WIDTH / 2) + BORDER), word[1], renderer);
+    
+            /* Write the PSW */
+            asprintf(&msg, "PSW: 0x%08x", word[4]);
+            print_text((4 * CHUNK_HEIGHT), ((WINDOW_WIDTH / 2) + BORDER), msg, font, renderer);
+            free(msg);
+            word_to_leds((5 * CHUNK_HEIGHT), ((WINDOW_WIDTH / 2) + BORDER), word[4], renderer);
+    
+            /* Write the RAM dump */
+            asprintf(&msg, "RAM: 0x%08x", word[22]);
+            print_text((7 * CHUNK_HEIGHT), ((WINDOW_WIDTH / 2) + BORDER), msg, font, renderer);
+            free(msg);
+            for (int i=0; i<16; i++) {
+                word_to_leds(((i+8) * CHUNK_HEIGHT), ((WINDOW_WIDTH / 2) + BORDER), word[(i+23)], renderer);
+            }
+    
+            /* Render */
+            SDL_RenderPresent(renderer);
+        }
+    }
+
+    close(sockfd);
+    exit(EXIT_SUCCESS);
+}