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); +} |