Initial commit of `nedfp` as a front panel for `nedsim`.
[ned1] / nedfp / nedfp.c
/*
* © 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);
}