diff options
Diffstat (limited to 'client')
| -rw-r--r-- | client/commands.c | 55 | ||||
| -rw-r--r-- | client/commands.h | 12 | ||||
| -rw-r--r-- | client/errcatch.c | 18 | ||||
| -rw-r--r-- | client/i18n.h | 6 | ||||
| -rw-r--r-- | client/i18n/en_us.h | 19 | ||||
| -rw-r--r-- | client/main.c | 34 | ||||
| -rw-r--r-- | client/main.h | 20 | ||||
| -rw-r--r-- | client/makefile | 10 | ||||
| -rw-r--r-- | client/readme.md | 42 | ||||
| -rw-r--r-- | client/serial.c | 53 | ||||
| -rw-r--r-- | client/serial.h | 20 | ||||
| -rw-r--r-- | client/serial_linux.c | 97 | ||||
| -rw-r--r-- | client/serial_win32.c | 21 | ||||
| -rw-r--r-- | client/setup.c | 44 | ||||
| -rw-r--r-- | client/setup.h | 3 | ||||
| -rw-r--r-- | client/strings.c | 16 | ||||
| -rw-r--r-- | client/strings.h | 10 | ||||
| -rw-r--r-- | client/time.c | 3 | ||||
| -rw-r--r-- | client/time.h | 12 | ||||
| -rw-r--r-- | client/time_linux.c | 19 | ||||
| -rw-r--r-- | client/time_windows.c | 15 | ||||
| -rw-r--r-- | client/ui.c | 71 | ||||
| -rw-r--r-- | client/ui.h | 27 | ||||
| -rw-r--r-- | client/ui_dirc.c | 112 | 
24 files changed, 712 insertions, 27 deletions
| diff --git a/client/commands.c b/client/commands.c new file mode 100644 index 0000000..778e9c1 --- /dev/null +++ b/client/commands.c @@ -0,0 +1,55 @@ +#include <stdlib.h> + +#include "../shared/bin.h" +#include "../shared/modes.h" +#include "../shared/protocol.h" +#include "commands.h" +#include "main.h" +#include "time.h" + +void w2_send_bin(w2_s_bin *data) { +	w2_serial_write("\xff", 1); +	for (uint8_t i = 0; i < data->bytes; i++) { +		uint8_t byte = data->data[i]; +		byte == 0xff ? w2_serial_write("\xff\xff", 2) : w2_serial_write((char *)&byte, 1); +	} +} + +void w2_send_info() { +	W2_CREATE_MSG_BIN(w2_s_cmd_info_rx, msg, msg_bin); +	msg->opcode = W2_CMD_INFO | W2_CMDDIR_RX; + +	w2_send_bin(msg_bin); +	free(msg_bin); +} + +void w2_send_dirc(uint16_t left, uint16_t right) { +	W2_CREATE_MSG_BIN(w2_s_cmd_dirc_rx, msg, msg_bin); +	msg->opcode = W2_CMD_DIRC | W2_CMDDIR_RX; +	msg->left	= w2_bin_hton16(left); +	msg->right	= w2_bin_hton16(right); + +	w2_send_bin(msg_bin); +	free(msg_bin); +} + +void w2_send_ping() { +	g_w2_state.ping_id = (uint8_t)rand(); +	W2_CREATE_MSG_BIN(w2_s_cmd_ping_rx, msg, msg_bin); +	msg->opcode = W2_CMD_PING | W2_CMDDIR_RX; +	msg->id		= g_w2_state.ping_id; + +	w2_send_bin(msg_bin); +	free(msg_bin); + +	w2_timer_start(W2_TIMER_PING); +} + +void w2_send_mode(w2_e_mode mode) { +	W2_CREATE_MSG_BIN(w2_s_cmd_mode_rx, msg, msg_bin); +	msg->opcode = W2_CMD_MODE | W2_CMDDIR_RX; +	msg->mode	= mode; + +	w2_send_bin(msg_bin); +	free(msg_bin); +} diff --git a/client/commands.h b/client/commands.h new file mode 100644 index 0000000..02ae313 --- /dev/null +++ b/client/commands.h @@ -0,0 +1,12 @@ +#pragma once + +#include "../shared/bin.h" +#include "../shared/modes.h" +#include "serial.h" + +void w2_send_bin(w2_s_bin *data); + +void w2_send_info(); +void w2_send_ping(); +void w2_send_mode(w2_e_mode mode); +void w2_send_dirc(uint16_t left, uint16_t right); diff --git a/client/errcatch.c b/client/errcatch.c new file mode 100644 index 0000000..5e39b00 --- /dev/null +++ b/client/errcatch.c @@ -0,0 +1,18 @@ +#include "../shared/errcatch.h" + +void w2_errcatch_handle_error(w2_s_error *error) { +	// TODO: handle more error types +	switch (error->code) { +		case W2_E_WARN_UNCAUGHT_ERROR: { +			break; +		} +		default: { +			g_w2_error_uncaught = true; +#ifdef W2_SIM +			simwarn("Uncaught/unhandled error found with code 0x%02x\n", error->code); +#endif +		} +	} + +	return; +} diff --git a/client/i18n.h b/client/i18n.h new file mode 100644 index 0000000..06febee --- /dev/null +++ b/client/i18n.h @@ -0,0 +1,6 @@ +#pragma once + +#ifndef W2_LANG_DEFAULT +#define W2_LANG_DEFAULT +#include "i18n/en_us.h" +#endif diff --git a/client/i18n/en_us.h b/client/i18n/en_us.h new file mode 100644 index 0000000..5547db5 --- /dev/null +++ b/client/i18n/en_us.h @@ -0,0 +1,19 @@ +#pragma once + +#define W2_UI_CLI_USAGE "usage: %s <serial port>\n" +#define W2_UI_CLI_SERPORT_ERROR "serial port open fout\n" +#define W2_UI_CLI_INITSCR_FAIL "ncurses initscr() failed\n" +#define W2_UI_CONN_STAT_CONNECTED "connected" +#define W2_UI_CONN_STAT_DISCONNECTED "disconnected" +#define W2_UI_CONN_STAT_PING "ping" +#define W2_UI_BATT_STAT_BATTERY "battery" +#define W2_UI_EXPT_STAT_WARNINGS "warning(s)" +#define W2_UI_EXPT_STAT_ERRORS "error(s)" +#define W2_UI_MODE_CHRG "charging station" +#define W2_UI_MODE_DIRC "direct control" +#define W2_UI_MODE_GRID "grid" +#define W2_UI_MODE_HALT "emergency stop" +#define W2_UI_MODE_LCAL "line calibration" +#define W2_UI_MODE_MAZE "maze" +#define W2_UI_MODE_SCAL "sensor calibration" +#define W2_UI_MODE_SPIN "wet floor simulation" diff --git a/client/main.c b/client/main.c index 76e8197..b5af0e8 100644 --- a/client/main.c +++ b/client/main.c @@ -1 +1,33 @@ -int main() { return 0; } +#include "main.h" +#include "../shared/consts.h" +#include "../shared/errcatch.h" +#include "commands.h" +#include "serial.h" +#include "setup.h" +#include "time.h" +#include "ui.h" + +w2_s_client_state g_w2_state = {.ping_received = true}; + +int main(int argc, char **argv) { +	w2_client_setup(argc, argv); + +	while (1) { +		w2_serial_main(); +		w2_errcatch_main(); +		w2_ui_main(); + +		if (!g_w2_state.ping_received && w2_timer_end(W2_TIMER_PING) > W2_PING_TIMEOUT) { +			g_w2_state.ping_timeout = true; +			g_w2_state.connected	= false; +			w2_errcatch_throw(W2_E_WARN_PING_TIMEOUT); +		} + +		if ((g_w2_state.ping_received && w2_timer_end(W2_TIMER_PING) > W2_PING_FREQUENCY) || +			g_w2_state.ping_timeout) { +			g_w2_state.ping_timeout	 = false; +			g_w2_state.ping_received = false; +			w2_send_ping(); +		} +	} +} diff --git a/client/main.h b/client/main.h new file mode 100644 index 0000000..cc4c728 --- /dev/null +++ b/client/main.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../shared/protocol.h" + +typedef struct { +	unsigned int ping; +	uint8_t ping_id; +	bool ping_received; +	bool ping_timeout; + +	bool connected; +	uint8_t battery_level; + +	uint8_t mode; + +	w2_s_cmd_info_tx info; +	w2_s_cmd_sens_tx io; +} w2_s_client_state; + +extern w2_s_client_state g_w2_state; diff --git a/client/makefile b/client/makefile index f79dd11..a23ce4f 100644 --- a/client/makefile +++ b/client/makefile @@ -2,20 +2,26 @@ CC = gcc  LD = gcc  RM = rm -f  CFLAGS = +LDFLAGS = -lncursesw  EXECNAME = main +include ../shared/os.mk +  all: $(EXECNAME)  SOURCES := $(wildcard *.c)  HEADERS := $(wildcard *.h) -# include ../shared/makefile +include ../shared/makefile + +CFLAGS += $(if $(WIN32),-I/mingw64/include/ncursesw,) +  OBJECTS := $(patsubst %.c,%.o, $(SOURCES))  .o:  	$(CC) -c $(CFLAGS) $<  $(EXECNAME): $(OBJECTS) -	$(CC) $(OBJECTS) -o $(EXECNAME) +	$(CC) $(OBJECTS) -o $(EXECNAME) $(LDFLAGS)  clean::  	$(RM) $(EXECNAME) *.o diff --git a/client/readme.md b/client/readme.md index 8b4baee..63cd086 100644 --- a/client/readme.md +++ b/client/readme.md @@ -6,19 +6,19 @@ this page is WIP  ## features -|feature|due|status|author|description| -|-|-|-|-|-| -|view warnings / errors| -|direct control| -|configure map| -|input orders| -|enable/disable emergency mode| -|enable/disable sensor calibration mode| -|enable/disable wet floor mode| -|read sensor values| -|set display contents|optional -|play music|optional -|control leds|optional +|feature|status|author|description| +|-|-|-|-| +|view warnings / errors|unimplemented||see a log of parsed warnings/errors +|direct control|done|Loek|directly control the robot with tank-style controls +|configure map|unimplemented||set map width/height and define entry/exitpoints +|input orders|unimplemented||type orders with lists of coordinates to visit +|enable/disable emergency mode|unimplemented||self-explanatory +|enable/disable sensor calibration mode|unimplemented||self-explanatory +|enable/disable wet floor mode|unimplemented||self-explanatory +|read sensor values|unimplemented||dashboard that displays all i/o as bar graphs +|set display contents|unimplemented||send text to display on lcd +|play music|unimplemented||play tunes +|control leds|unimplemented||turn on/off underside leds  ## interface @@ -27,7 +27,8 @@ the robot in various ways. it primarily works in a bios-like way, with the user  having access to multiple tabs containing options or custom interface elements.  to start the interface, the user should run `./main <com-port>`. the interface -could look something like this (with colored text for element seperation): +could look something like this (with colored text for element seperation, [see +on figma](https://www.figma.com/file/vZ6rQp2G1HBAmbdrkxIZJ3/terminal-app)):  ```  verbonden, 2ms ping           (0.0.2-11-g92c394b)                  batterij 100% @@ -57,9 +58,9 @@ sneltoetsen:  ```  the top status bar is always supposed to be visible, and is sort of inspired by -[ncmpcpp](https://github.com/ncmpcpp/ncmpcpp). because the client software -should use no libraries if possible, a custom renderer needs to be implemented, -though it doesn't matter if it's very primitive. +[ncmpcpp](https://github.com/ncmpcpp/ncmpcpp). we're using +[ncurses](https://invisible-mirror.net/ncurses/ncurses.html) to assist in +creating a cross-platform terminal ui.  going from top-left in reading order the status bar contains: connection  status, ping time, robot version number, robot battery info, current logic @@ -81,10 +82,3 @@ modules are executed after each other:  - status bar paint  - current tab paint -## notes on ascii escape codes - -- color codes -- terminal echo codes -- how to read terminal (re)size -- cursor movement(?) - diff --git a/client/serial.c b/client/serial.c new file mode 100644 index 0000000..112fd81 --- /dev/null +++ b/client/serial.c @@ -0,0 +1,53 @@ +#include <memory.h> + +#include "../shared/protocol.h" +#include "../shared/serial_parse.h" +#include "commands.h" +#include "main.h" +#include "serial.h" +#include "time.h" + +void w2_serial_main() { +	int temp; +	while ((temp = w2_serial_read()) != -1) w2_serial_parse(temp); +} + +void w2_cmd_ping_rx(w2_s_bin *data) { +	W2_CAST_BIN(w2_s_cmd_ping_tx, data, cast); + +	if (g_w2_state.ping_received) return; +	if (g_w2_state.ping_id != cast->id) return; + +	g_w2_state.ping			 = w2_timer_end(W2_TIMER_PING); +	g_w2_state.ping_received = true; +	g_w2_state.ping_timeout	 = false; +	g_w2_state.connected	 = true; +} + +void w2_cmd_ping_tx(w2_s_bin *data) { w2_send_bin(data); } + +void w2_cmd_expt_tx(w2_s_bin *data) {} +void w2_cmd_mode_tx(w2_s_bin *data) { +	W2_CAST_BIN(w2_s_cmd_mode_tx, data, cast); +	g_w2_state.mode = cast->mode; +} +void w2_cmd_cord_tx(w2_s_bin *data) {} +void w2_cmd_bomd_tx(w2_s_bin *data) {} +void w2_cmd_sens_tx(w2_s_bin *data) {} + +void w2_cmd_info_tx(w2_s_bin *data) { +	memcpy(&g_w2_state.info, data->data, sizeof(w2_s_cmd_info_tx)); +} + +void w2_cmd_mode_rx(w2_s_bin *data) { return; } +void w2_cmd_sped_rx(w2_s_bin *data) { return; } +void w2_cmd_dirc_rx(w2_s_bin *data) { return; } +void w2_cmd_cord_rx(w2_s_bin *data) { return; } +void w2_cmd_bomd_rx(w2_s_bin *data) { return; } +void w2_cmd_sres_rx(w2_s_bin *data) { return; } +void w2_cmd_mcfg_rx(w2_s_bin *data) { return; } +void w2_cmd_sens_rx(w2_s_bin *data) { return; } +void w2_cmd_info_rx(w2_s_bin *data) { return; } +void w2_cmd_disp_rx(w2_s_bin *data) { return; } +void w2_cmd_play_rx(w2_s_bin *data) { return; } +void w2_cmd_cled_rx(w2_s_bin *data) { return; } diff --git a/client/serial.h b/client/serial.h new file mode 100644 index 0000000..b29dfb5 --- /dev/null +++ b/client/serial.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../shared/bool.h" + +/** @file serial.h */ + +/** + * read byte from serial port + * + * @return -1 if read fails, else char read + */ +int w2_serial_read(); +/** write `data` with length `length` to serial port */ +bool w2_serial_write(char *data, uint8_t length); +/** open serial port */ +bool w2_serial_open(const char *port_name); +/** close serial port */ +void w2_serial_close(); + +void w2_serial_main(); diff --git a/client/serial_linux.c b/client/serial_linux.c new file mode 100644 index 0000000..2497fac --- /dev/null +++ b/client/serial_linux.c @@ -0,0 +1,97 @@ +#ifdef W2_HOST_LINUX + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <termios.h> +#include <unistd.h> + +#include "../shared/consts.h" +#include "serial.h" + +struct termios g_w2_tty; +struct termios g_w2_tty_old; +int g_w2_serial_handle; + +char g_w2_serial_buffer[W2_SERIAL_READ_BUFFER_SIZE]; +uint8_t g_w2_serial_buffer_index; +uint8_t g_w2_serial_buffer_head; + +speed_t w2_baud_map(int baud) { +	switch (baud) { +		case 9600: +			return B9600; +		case 19200: +			return B19200; +		case 38400: +			return B38400; +		case 57600: +			return B57600; +		case 115200: +			return B115200; +		case 230400: +			return B230400; +		case 460800: +			return B460800; +		case 500000: +			return B500000; +		case 576000: +			return B576000; +		case 921600: +			return B921600; +		case 1000000: +			return B1000000; +		case 1152000: +			return B1152000; +		case 1500000: +			return B1500000; +		case 2000000: +			return B2000000; +		case 2500000: +			return B2500000; +		case 3000000: +			return B3000000; +		case 3500000: +			return B3500000; +		case 4000000: +			return B4000000; +		default: +			return B0; +	} +} + +int w2_serial_read() { +	int return_val; +	int bytes = read(g_w2_serial_handle, &return_val, 1); +	return bytes != 1 ? -1 : (uint8_t)return_val; +} + +bool w2_serial_write(char *data, uint8_t length) { +	return write(g_w2_serial_handle, data, length) == length; +} + +bool w2_serial_open(const char *port_name) { +	g_w2_serial_handle = open(port_name, O_RDWR | O_NONBLOCK); +	if (g_w2_serial_handle < 0 || tcgetattr(g_w2_serial_handle, &g_w2_tty) != 0) return false; + +	g_w2_tty_old = g_w2_tty; + +	speed_t baud = w2_baud_map(W2_SERIAL_BAUD); +	cfsetospeed(&g_w2_tty, baud); +	cfsetispeed(&g_w2_tty, baud); + +	g_w2_tty.c_cc[VMIN]	 = 0; +	g_w2_tty.c_cc[VTIME] = 0; + +	cfmakeraw(&g_w2_tty); + +	tcflush(g_w2_serial_handle, TCIFLUSH); + +	if (tcsetattr(g_w2_serial_handle, TCSANOW, &g_w2_tty) != 0) return false; + +	return true; +} + +void w2_serial_close() { close(g_w2_serial_handle); } + +#endif diff --git a/client/serial_win32.c b/client/serial_win32.c new file mode 100644 index 0000000..edc9db1 --- /dev/null +++ b/client/serial_win32.c @@ -0,0 +1,21 @@ +#ifdef W2_HOST_WIN32 + +#include "serial.h" + +int w2_serial_read() { +    return 0x00; +} + +bool w2_serial_write(char *data, uint8_t length) { +    return true; +} + +bool w2_serial_open(const char *port_name) { +    return true; +} + +void w2_serial_close() { +    return; +} + +#endif diff --git a/client/setup.c b/client/setup.c new file mode 100644 index 0000000..fd37c13 --- /dev/null +++ b/client/setup.c @@ -0,0 +1,44 @@ +#include <ncurses.h> +#include <stdio.h> +#include <stdlib.h> + +#include "../shared/bin.h" +#include "../shared/protocol.h" +#include "commands.h" +#include "serial.h" +#include "setup.h" +#include "strings.h" +#include "ui.h" + +// pointers for endianness check +static const uint16_t _test	 = 1; +static const uint8_t *_ptest = (uint8_t *)&_test; +uint8_t g_w2_endianness; + +void w2_client_setup(int argc, char **argv) { +	if (argc < 2) { +		printf(W2_UI_CLI_USAGE, argv[0]); +		exit(1); +	} + +	if (w2_serial_open(argv[1]) == 0) { +		printf(W2_UI_CLI_SERPORT_ERROR); +		exit(1); +	} + +	if ((g_w2_ui_win = initscr()) == NULL) { +		printf(W2_UI_CLI_INITSCR_FAIL); +		exit(1); +	} +	noecho(); +	curs_set(false); +	nodelay(g_w2_ui_win, true); + +	w2_strings_init(); +	w2_cmd_setup_handlers(); + +	w2_send_info(); + +	// check endianness +	g_w2_endianness = *_ptest; +} diff --git a/client/setup.h b/client/setup.h new file mode 100644 index 0000000..bcad11e --- /dev/null +++ b/client/setup.h @@ -0,0 +1,3 @@ +#pragma once + +void w2_client_setup(int argc, char **argv); diff --git a/client/strings.c b/client/strings.c new file mode 100644 index 0000000..b97d4b2 --- /dev/null +++ b/client/strings.c @@ -0,0 +1,16 @@ +#include "strings.h" + +char *g_w2_mode_strings[W2_MODE_COUNT]; + +void w2_strings_modes_init() { +	g_w2_mode_strings[W2_M_CHRG] = W2_UI_MODE_CHRG; +	g_w2_mode_strings[W2_M_DIRC] = W2_UI_MODE_DIRC; +	g_w2_mode_strings[W2_M_GRID] = W2_UI_MODE_GRID; +	g_w2_mode_strings[W2_M_HALT] = W2_UI_MODE_HALT; +	g_w2_mode_strings[W2_M_LCAL] = W2_UI_MODE_LCAL; +	g_w2_mode_strings[W2_M_MAZE] = W2_UI_MODE_MAZE; +	g_w2_mode_strings[W2_M_SCAL] = W2_UI_MODE_SCAL; +	g_w2_mode_strings[W2_M_SPIN] = W2_UI_MODE_SPIN; +} + +void w2_strings_init() { w2_strings_modes_init(); } diff --git a/client/strings.h b/client/strings.h new file mode 100644 index 0000000..0085228 --- /dev/null +++ b/client/strings.h @@ -0,0 +1,10 @@ +#pragma once + +#include "../shared/modes.h" +#include "i18n.h" + +#define W2_STRINGS_MODE_MAP_BUFFER_SIZE 32 + +extern char *g_w2_mode_strings[W2_MODE_COUNT]; + +void w2_strings_init(); diff --git a/client/time.c b/client/time.c new file mode 100644 index 0000000..d48be64 --- /dev/null +++ b/client/time.c @@ -0,0 +1,3 @@ +#include "time.h" + +unsigned long g_w2_client_timers[W2_CLIENT_TIMER_COUNT] = {0}; diff --git a/client/time.h b/client/time.h new file mode 100644 index 0000000..a989c5c --- /dev/null +++ b/client/time.h @@ -0,0 +1,12 @@ +#pragma once + +/** amount of parallel timers */ +#define W2_CLIENT_TIMER_COUNT (4) +extern unsigned long g_w2_client_timers[W2_CLIENT_TIMER_COUNT]; +typedef enum { +	W2_TIMER_PING	= 0, +	W2_TIMER_UPDATE = 1, +} w2_e_client_timers; + +void w2_timer_start(w2_e_client_timers label); +unsigned long w2_timer_end(w2_e_client_timers label); diff --git a/client/time_linux.c b/client/time_linux.c new file mode 100644 index 0000000..f6d50d8 --- /dev/null +++ b/client/time_linux.c @@ -0,0 +1,19 @@ +#ifdef W2_HOST_LINUX + +#include <time.h> + +#include "time.h" + +unsigned long w2_get_time() { +	struct timespec now; +	clock_gettime(CLOCK_MONOTONIC, &now); +	return ((now.tv_sec * 1000) + (now.tv_nsec / 1000000)); +} + +void w2_timer_start(w2_e_client_timers label) { g_w2_client_timers[label] = w2_get_time(); } + +unsigned long w2_timer_end(w2_e_client_timers label) { +	return w2_get_time() - g_w2_client_timers[label]; +} + +#endif diff --git a/client/time_windows.c b/client/time_windows.c new file mode 100644 index 0000000..f9082d6 --- /dev/null +++ b/client/time_windows.c @@ -0,0 +1,15 @@ +#ifdef W2_HOST_WIN32 + +#include "time.h" + +unsigned long w2_get_time() { +	return 0; +} + +void w2_timer_start(w2_e_client_timers label) { g_w2_client_timers[label] = w2_get_time(); } + +unsigned long w2_timer_end(w2_e_client_timers label) { +	return w2_get_time() - g_w2_client_timers[label]; +} + +#endif diff --git a/client/ui.c b/client/ui.c new file mode 100644 index 0000000..1cde52f --- /dev/null +++ b/client/ui.c @@ -0,0 +1,71 @@ +#include <ncurses.h> +#include <string.h> + +#include "../shared/bin.h" +#include "../shared/util.h" +#include "i18n.h" +#include "main.h" +#include "strings.h" +#include "term.h" +#include "time.h" +#include "ui.h" + +WINDOW *g_w2_ui_win; +unsigned int g_w2_ui_width				= 0; +unsigned int g_w2_ui_height				= 0; +void (*g_w2_ui_current_tab)(bool first) = &w2_ui_dirc; +void (*g_w2_ui_last_tab)(bool first)	= NULL; + +void w2_ui_main() { +	g_w2_ui_width  = getmaxx(g_w2_ui_win); +	g_w2_ui_height = getmaxy(g_w2_ui_win); + +	w2_ui_paint(); +} + +void w2_ui_paint() { +	w2_ui_paint_statusbar(); +	if (w2_timer_end(W2_TIMER_UPDATE) >= (1000 / W2_UI_UPDATE_FPS)) { +		(*g_w2_ui_current_tab)(g_w2_ui_last_tab != g_w2_ui_current_tab); +		g_w2_ui_last_tab = g_w2_ui_current_tab; +		w2_timer_start(W2_TIMER_UPDATE); +	} +	refresh(); +} + +void w2_ui_paint_statusbar() { +	char temp[g_w2_ui_width]; + +	for (unsigned int i = 0; i < g_w2_ui_width; i++) temp[i] = ' '; +	mvaddnstr(0, 0, temp, g_w2_ui_width); +	mvaddnstr(1, 0, temp, g_w2_ui_width); +	mvaddnstr(2, 0, temp, g_w2_ui_width); + +	sprintf(temp, "%s, %ims %s", +			g_w2_state.connected ? W2_UI_CONN_STAT_CONNECTED : W2_UI_CONN_STAT_DISCONNECTED, +			g_w2_state.ping, W2_UI_CONN_STAT_PING); +	mvaddstr(0, 0, temp); + +	sprintf(temp, "(%s)", g_w2_state.info.build_str); +	mvaddstr(0, g_w2_ui_width / 2 - strlen(temp) / 2, temp); + +	sprintf(temp, "%s %i%%", W2_UI_BATT_STAT_BATTERY, g_w2_state.battery_level); +	mvaddstr(0, g_w2_ui_width - strlen(temp), temp); + +	sprintf(temp, "[%s]", g_w2_mode_strings[g_w2_state.mode]); +	mvaddstr(1, 0, temp); + +	sprintf(temp, "%i %s, %i %s", 0, W2_UI_EXPT_STAT_WARNINGS, 0, W2_UI_EXPT_STAT_ERRORS); +	mvaddstr(1, g_w2_ui_width - strlen(temp), temp); + +	w2_ui_paint_tabbar(); + +	for (unsigned int i = 0; i < g_w2_ui_width; i++) temp[i] = '-'; +	mvaddnstr(3, 0, temp, g_w2_ui_width); +} + +void w2_ui_paint_tabbar() { +	char temp[g_w2_ui_width]; +	sprintf(temp, "-- tab bar here --"); +	mvaddstr(2, g_w2_ui_width / 2 - strlen(temp) / 2, temp); +} diff --git a/client/ui.h b/client/ui.h new file mode 100644 index 0000000..0d375de --- /dev/null +++ b/client/ui.h @@ -0,0 +1,27 @@ +#pragma once + +#include <ncurses.h> +#include <stdint.h> + +#define W2_UI_UPDATE_FPS (60) + +extern WINDOW *g_w2_ui_win; +extern unsigned int g_w2_ui_width; +extern unsigned int g_w2_ui_height; +extern void (*g_w2_ui_current_tab)(bool first); + +/** update terminal props */ +void w2_ui_update(); +/** clear screen */ +void w2_ui_clear(); +/** draw complete ui */ +void w2_ui_paint(); +/** update and paint */ +void w2_ui_main(); + +/** draw status bar */ +void w2_ui_paint_statusbar(); +/** draw tab bar */ +void w2_ui_paint_tabbar(); + +void w2_ui_dirc(bool first); diff --git a/client/ui_dirc.c b/client/ui_dirc.c new file mode 100644 index 0000000..ed69cd2 --- /dev/null +++ b/client/ui_dirc.c @@ -0,0 +1,112 @@ +#include "../shared/protocol.h" +#include "../shared/util.h" +#include "commands.h" +#include "ui.h" + +/** decay modifier */ +#define W2_DIRC_MOD ((double)0.95) +/** add value per key press */ +#define W2_DIRC_ADD ((double)17.0) +/** padding */ +#define W2_DIRC_PAD ((double)3.00) +/** average samples */ +#define W2_DIRC_SPL ((unsigned int)14) +/** steering padding */ +#define W2_DIRC_STP ((double)0.2) + +int w2_avg(int *samples, unsigned int sample_count) { +	double total = 0; +	for (int i = 0; i < sample_count; i++) { +		total += (double)samples[i] / (double)sample_count; +	} +	return (int)total; +} + +#define W2_DIRC_MOTOR_DRIVER(name)                                                                 \ +	int w2_dirc_motor_##name(unsigned int forward, unsigned int backward) {                        \ +		static unsigned int idx = 0;                                                               \ +                                                                                                   \ +		static double drive				  = 0.f;                                                   \ +		static int drive_avg[W2_DIRC_SPL] = {0};                                                   \ +                                                                                                   \ +		drive *= W2_DIRC_MOD;                                                                      \ +		drive += W2_DIRC_ADD * forward + -W2_DIRC_ADD * backward;                                  \ +		drive = W2_RANGE(-254, drive, 255);                                                        \ +                                                                                                   \ +		idx			   = (idx + 1) % W2_DIRC_SPL;                                                  \ +		drive_avg[idx] = (int)W2_RANGE(-254, drive * W2_DIRC_PAD, 255);                            \ +                                                                                                   \ +		return w2_avg(drive_avg, W2_DIRC_SPL);                                                     \ +	} + +W2_DIRC_MOTOR_DRIVER(l); +W2_DIRC_MOTOR_DRIVER(r); + +void w2_ui_dirc_init() { w2_send_mode(W2_M_DIRC); } + +void w2_ui_bar_graph(unsigned int y, unsigned int x, unsigned int width, double value) { +	char temp[width]; +	temp[0]			= '|'; +	temp[width - 1] = '|'; +	for (unsigned int i = 0; i < width - 2; i++) temp[i + 1] = i < width * value ? '*' : ' '; + +	mvaddnstr(y, x, temp, width); +} + +void w2_ui_bar_graph_pm(unsigned int y, unsigned int x, unsigned int width, double value) { +	char temp[width]; +	temp[0]			= '|'; +	temp[width - 1] = '|'; +	width -= 2; +	unsigned int hw = width / 2; +	if (value >= 0) { +		for (unsigned int i = 0; i < width; i++) +			temp[i + 1] = i < hw ? ' ' : (i - hw) < (hw * value) ? '*' : ' '; +	} else { +		for (unsigned int i = 0; i < width; i++) +			temp[i + 1] = i > hw ? ' ' : (hw - i) < -(hw * value) ? '*' : ' '; +	} + +	mvaddnstr(y, x, temp, width + 2); +} + +void w2_ui_dirc_paint(int left, int right) { +	mvaddstr(4, 0, "left drive:  "); +	w2_ui_bar_graph_pm(4, 13, g_w2_ui_width - 13, (double)left / 255); +	mvaddstr(5, 0, "right drive: "); +	w2_ui_bar_graph_pm(5, 13, g_w2_ui_width - 13, (double)right / 255); + +	mvaddstr(7, 0, +			 "             controls:\n" +			 "\n" +			 " <q>      <w>       <e>   forward\n" +			 " <a>      <s>       <d>   backward\n" +			 "left     both      right\n" +			 "\n" +			 "<space> send dirc mode command"); +} + +void w2_ui_dirc(bool first) { +	if (first) w2_ui_dirc_init(); +	int ch			= 0; +	unsigned int lb = 0; +	unsigned int lf = 0; +	unsigned int rb = 0; +	unsigned int rf = 0; +	while ((ch = getch()) != -1) { +		if (ch == 'e' || ch == 'w') lf++; +		if (ch == 'd' || ch == 's') lb++; +		if (ch == 'q' || ch == 'w') rf++; +		if (ch == 'a' || ch == 's') rb++; +		if (ch == ' ') w2_send_mode(W2_M_DIRC); +	} + +	int drive_l = w2_dirc_motor_l(lf, lb); +	int drive_r = w2_dirc_motor_r(rf, rb); + +	drive_l += drive_r * W2_DIRC_STP; +	drive_r += drive_l * W2_DIRC_STP; + +	w2_send_dirc(drive_l, drive_r); +	w2_ui_dirc_paint(drive_l, drive_r); +} |