diff options
author | ThomasintAnker <thomasintanker1@gmail.com> | 2024-06-24 14:59:56 +0200 |
---|---|---|
committer | ThomasintAnker <thomasintanker1@gmail.com> | 2024-06-24 14:59:56 +0200 |
commit | a0c664908b9112306c5858ccb106d1a0e5555df7 (patch) | |
tree | 8ca77d1210d1683a97f4da131c6ffac8123d4375 /client | |
parent | 381149dd7a1f4d5f48dd5ac07186c73371ff3c04 (diff) | |
parent | ec7f5e970ed03acb33eb5dc3b67f3d52af52e6dc (diff) |
merge main into wip/mc
Diffstat (limited to 'client')
-rw-r--r-- | client/cmd.cpp | 2 | ||||
-rw-r--r-- | client/cmd.h | 55 | ||||
-rw-r--r-- | client/i2c.cpp | 4 | ||||
-rw-r--r-- | client/i2c.h | 29 | ||||
-rw-r--r-- | client/parse.h | 14 | ||||
-rw-r--r-- | client/readme.md | 32 | ||||
-rw-r--r-- | client/rl.cpp | 17 | ||||
-rw-r--r-- | client/rl.h | 43 | ||||
-rw-r--r-- | client/sock.cpp | 2 | ||||
-rw-r--r-- | client/sock.h | 43 | ||||
-rw-r--r-- | client/xxd.h | 9 |
11 files changed, 209 insertions, 41 deletions
diff --git a/client/cmd.cpp b/client/cmd.cpp index 10d53e3..062fefa 100644 --- a/client/cmd.cpp +++ b/client/cmd.cpp @@ -28,7 +28,7 @@ void cmd_test(char*) { void cmd_help(char*) { printf("List of available commands:\n"); for (size_t i = 0; i < cmds_length; i++) { - struct cmd cmd = cmds[i]; + cmd_t cmd = cmds[i]; printf(" %-*s", 10, cmd.name); if (cmd.info != NULL) printf(" %s", cmd.info); diff --git a/client/cmd.h b/client/cmd.h index 961ef89..4f77d50 100644 --- a/client/cmd.h +++ b/client/cmd.h @@ -1,17 +1,49 @@ #pragma once +/** + * \ingroup pbc + * \defgroup pbc_cmd Commands + * \brief Commands within \ref pbc + * + * \note A manpage is available containing end-user usage instructions inside + * the \ref client folder in the source code repository. This page contains the + * internal code documentation for the commands defined in \c pbc. + * + * \{ + */ + #include <stddef.h> -typedef void cmd_handle_t(char *); -typedef char** cmd_complete_t(const char*, int, int); +/** + * \internal + * \brief Command handler function + * + * \param line Remaining text after command name on command line + */ +typedef void cmd_handle_t(char * line); +/** + * \internal + * \brief Command completion function + * + * \param text Current word to complete + * \param start Index in \c rl_line_buffer of cursor position + * \param end End index of \p text in \c rl_line_buffer + * + * \return Array of \c char* with suggestions. The array is terminated by a + * NULL pointer. + */ +typedef char** cmd_complete_t(const char* text, int start, int end); -struct cmd { - cmd_handle_t * handle; - const char* name; - const char* info; - cmd_complete_t * complete; -}; -typedef struct cmd cmd_t; +/** + * \internal + * \brief Command definition struct + */ +typedef struct { + cmd_handle_t * handle; //!< Handler function (required) + const char* name; //!< Command name (required) + const char* info; //!< Command info (shown in help command) (optional = NULL) + cmd_complete_t * complete; //!< Completion function (optional = NULL) +} cmd_t; cmd_handle_t cmd_exit; cmd_handle_t cmd_test; @@ -23,6 +55,7 @@ cmd_handle_t cmd_skip; cmd_handle_t cmd_dump; cmd_complete_t cmd_dump_complete; +//! Commands static const cmd_t cmds[] = { { .handle = cmd_exit, @@ -68,5 +101,9 @@ static const cmd_t cmds[] = { }, #endif }; + +//! Number of commands defined in \c cmds static const size_t cmds_length = sizeof(cmds) / sizeof(cmds[0]); +/// \} + diff --git a/client/i2c.cpp b/client/i2c.cpp index 78e5585..3655191 100644 --- a/client/i2c.cpp +++ b/client/i2c.cpp @@ -35,9 +35,9 @@ void i2c_send(uint16_t addr, const char * data, size_t data_size) { static void i2c_handle_cmd_read(uint16_t, const char *, size_t); -void i2c_recv(uint16_t addr, const char * data, size_t data_size) { +void i2c_recv(const char * data, size_t data_size) { if (i2c_dump_recv) { - printf("[%s] addr(0x%02x) data(0x%02lx):\n", __FUNCTION__, addr, data_size); + printf("[%s] data(0x%02lx):\n", __FUNCTION__, data_size); xxd(data, data_size); } } diff --git a/client/i2c.h b/client/i2c.h index f9f58f9..d2cfa9a 100644 --- a/client/i2c.h +++ b/client/i2c.h @@ -3,6 +3,33 @@ #include <stdint.h> #include <stddef.h> +/** + * \ingroup pbc + * \defgroup pbc_i2c I2C + * \brief I2C abstraction functions + * \{ + */ + +/** + * \brief Fake I2C send function + * + * This function sends an I2C message to the main controller over TCP using + * \ref i2ctcp. + * + * \param addr I2C address + * \param data Data to send + * \param data_size size of \p data + */ void i2c_send(uint16_t addr, const char * data, size_t data_size); -void i2c_recv(uint16_t addr, const char * data, size_t data_size); +/** + * \brief Fake I2C receive handler + * + * This function is called for I2C messages received by the main controller and + * forwarded to \ref pbc. + * + * \param data Received data + * \param data_size size of \p data + */ +void i2c_recv(const char * data, size_t data_size); +/// \} diff --git a/client/parse.h b/client/parse.h index 94afe70..1beb714 100644 --- a/client/parse.h +++ b/client/parse.h @@ -2,11 +2,23 @@ #include <stddef.h> +/** + * \ingroup pbc + * \defgroup pbc_parse Parse + * \brief Debug \c send command parser utilities + * \{ + */ + +//! Internal field separator (i.e. whitespace delimiter) #define IFS " \t\n" +//! Octal digit character set #define SET_OCT "01234567" +//! Decimal digit character set #define SET_DEC "0123456789" +//! Hexadecimal digit character set #define SET_HEX SET_DEC"abcdefABCDEF" +//! (Hexadecimal) byte string character set #define SET_HEX_STR SET_HEX":" /** @@ -40,3 +52,5 @@ char* consume_token(char * token, const char * ifs); */ int strtodata(const char * str, char ** data, size_t * size); +/// \} + diff --git a/client/readme.md b/client/readme.md index fcde40d..b9e0b09 100644 --- a/client/readme.md +++ b/client/readme.md @@ -1,3 +1,6 @@ +\defgroup pbc pbc +\brief Puzzle box client + # puzzle box client This folder contains the source code for the puzzle box client (pbc). This is a @@ -7,30 +10,21 @@ game operator to control and monitor the state of a puzzle box, but is also a useful debugging tool when developing puzzle modules, as it allows you to send arbitrary data over the puzzle bus. -## WIP TODO - -- cleanup - - separate ../shared/pb/moddrv.c into a puzzle module specific and 'common' bit - - use the common bit in i2c.cpp instead - - cast to structs in ../shared/pb/moddrv.c -- functionality - - print pretty tree of connected puzzle modules - - add enum to string functions in CLIENT ONLY - ## Features - List detected puzzle modules - Reset puzzle modules (individually or all to reset the box) - Skip puzzle modules (individually or all) - Request puzzle box state - -Debug only: -- Send arbitrary messages +- Debug: send arbitrary messages ## Building -PBC is a standard CMake project, but a [makefile](./makefile) is provided for -convenience (still requires CMake and Ninja are installed). +PBC is a standard CMake project. + +## Using + +See ./pbc.1 for usage. ## Send data @@ -45,8 +39,10 @@ send 0x39 68:65:6c:6c:6f 44 0x20 'world' 33 The data is concatenated, and may contain mixed types of literals -## known bugs (TODO) -- tab completion for `dump` seems to print garbage sometimes -- the send command with an address but no data causes a segmentation fault +## WIP TODO + +- add enum to string functions in CLIENT ONLY +- bug: tab completion for `dump` seems to print garbage sometimes +- bug: the send command with an address but no data causes a segmentation fault diff --git a/client/rl.cpp b/client/rl.cpp index fa44bf4..2e74e5f 100644 --- a/client/rl.cpp +++ b/client/rl.cpp @@ -57,7 +57,7 @@ static char* rl_completion_entries(const char *text, int state) { if (state == 0) i = 0; while (i < cmds_length) { - struct cmd cmd = cmds[i]; + cmd_t cmd = cmds[i]; i++; if (strncmp(text, cmd.name, strlen(text)) == 0) { return strdup(cmd.name); @@ -119,24 +119,25 @@ int rl_word(const char * line, int cursor) { return word; } +/// \internal typedef struct { const char * word; - const char ** suggestions; + const char ** options; } __rl_complete_list_data_t; -char** rl_complete_list(const char * word, const char ** suggestions) { +char** rl_complete_list(const char * word, const char ** options) { __rl_complete_list_data_t data = { .word = word, - .suggestions = suggestions, + .options = options, }; return rl_completion_matches((char *) &data, [](const char * text, int state) -> char * { __rl_complete_list_data_t data = *(__rl_complete_list_data_t *) text; static size_t i = 0; if (state == 0) i = 0; - while (data.suggestions[i] != NULL) { - const char * suggestion = data.suggestions[i++]; - if (strncmp(data.word, suggestion, strlen(data.word)) == 0) - return strdup(suggestion); + while (data.options[i] != NULL) { + const char * option = data.options[i++]; + if (strncmp(data.word, option, strlen(data.word)) == 0) + return strdup(option); } return NULL; }); diff --git a/client/rl.h b/client/rl.h index ab31ddb..d2f8612 100644 --- a/client/rl.h +++ b/client/rl.h @@ -1,12 +1,53 @@ #pragma once +/** + * \ingroup pbc + * \defgroup pbc_rl rl + * \brief GNU Readline related functions + * \{ + */ + +//! Reset color (ANSI sequence) #define COLOR_OFF "\x1b[0m" +//! Set font to bold (ANSI sequence) #define COLOR_BOLD "\x1b[1m" +//! Prompt text #define CLI_PROMPT "(" COLOR_BOLD "pbc" COLOR_OFF ") " +//! CLI entrypoint int cli_main(); + +/** + * \brief Print format string to stdout without disturbing the readline prompt + * + * This function saves and restores the current readline prompt before/after + * calling printf. This function is not required for commands that print output + * synchronously, as the prompt is only shown after a command handler + * completes. + */ void rl_printf(const char * fmt, ...); +/** + * \brief Get the index of the word currently under the cursor + * + * \param line Command line contents + * \param cursor Index of cursor position + * + * This function returns the index of the word from an array made by splitting + * \p line on consecutive occurrences of \c IFS. + * + * \return Index of word + */ int rl_word(const char * line, int cursor); -char ** rl_complete_list(const char * word, const char * suggestions[]); +/** + * \brief Create a completion suggestion string array for readline + * + * \param word Word to complete + * \param options List of possible choices (NULL terminated array of strings) + * + * \return Suggestions matching \p word + */ +char ** rl_complete_list(const char * word, const char * options[]); + +/// \} diff --git a/client/sock.cpp b/client/sock.cpp index 3490586..e33a3dc 100644 --- a/client/sock.cpp +++ b/client/sock.cpp @@ -101,7 +101,7 @@ void PBSocket::sock_task() { if (ret > 0) continue; // message read completely! - i2c_recv(input.addr, input.data, input.length); + i2c_recv(input.data, input.length); free(input.data); } diff --git a/client/sock.h b/client/sock.h index 0dee09e..792123e 100644 --- a/client/sock.h +++ b/client/sock.h @@ -3,29 +3,72 @@ #include <cstdint> #include <thread> +/** + * \ingroup pbc + * \defgroup pbc_sock Socket + * \brief TCP socket handling + * \{ + */ + +/** + * \brief Asynchronous puzzle box socket connection + * \note Once connected, this class will call \c i2c_recv() when a complete I2C + * message has been received + */ class PBSocket { public: PBSocket(); PBSocket(const char * addr, uint16_t port); virtual ~PBSocket(); + //! Configure target server void set_server(const char * addr, uint16_t port); + //! Attempt to connect to server and start \c sock_task() in a thread void sock_connect(); + /** + * \brief Send data over the TCP connection + * + * \param buf Data to send + * \param buf_sz Size of \p buf in bytes + */ void send(const char * buf, size_t buf_sz); private: + /** + * \brief Continously read from the TCP socket and read \ref i2ctcp messages + * using \c i2ctcp_read(). + * + * Once a complete message has been parsed, \c i2c_recv() is called with the + * complete message. This message is automatically free'd after \c i2c_recv() + * returns. + * + * \note This function is run in a separate thread + */ void sock_task(); + //! Close the socket void sock_close(); + //! Pointer to thread running \c sock_task() std::thread* _thread = nullptr; + /** + * \brief IP address of server to connect to + * + * \note This member must contain an IP address, as no hostname resolution is + * done in pbc. + */ const char * _addr = NULL; + //! Port number of server to connect to uint16_t _port = 0; + //! Unix file descriptor of opened socket int _fd = -1; }; +//! Singleton \c PBSocket instance extern PBSocket* sock; +/// \} + diff --git a/client/xxd.h b/client/xxd.h index fb28bb1..ede9fff 100644 --- a/client/xxd.h +++ b/client/xxd.h @@ -7,10 +7,19 @@ extern "C" { #endif /** + * \ingroup pbc + * \defgroup pbc_xxd xxd + * \brief Utility hexdump + * \{ + */ + +/** * \brief utility function that prints hexdump of data */ void xxd(const char * data, size_t size); +/// \} + #ifdef __cplusplus } #endif |