From 573643a1d3220830de47c810cb1a6be629ce7abd Mon Sep 17 00:00:00 2001 From: lonkaars Date: Fri, 10 May 2024 14:49:36 +0200 Subject: WIP protobuf hello world --- client/.gitignore | 1 + client/CMakeLists.txt | 14 ++++++++++++++ client/compile_commands.json | 1 + client/main.cpp | 25 +++++++++++++++++++++++++ client/makefile | 2 ++ 5 files changed, 43 insertions(+) create mode 100644 client/.gitignore create mode 100644 client/CMakeLists.txt create mode 120000 client/compile_commands.json create mode 100644 client/main.cpp create mode 100644 client/makefile (limited to 'client') diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 0000000..ba2906d --- /dev/null +++ b/client/.gitignore @@ -0,0 +1 @@ +main diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt new file mode 100644 index 0000000..9e433b1 --- /dev/null +++ b/client/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.29) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS 1) + +include(../proto/include.cmake) + +project(puzzlebox_client C CXX) + +add_executable(main + main.cpp + ) + diff --git a/client/compile_commands.json b/client/compile_commands.json new file mode 120000 index 0000000..25eb4b2 --- /dev/null +++ b/client/compile_commands.json @@ -0,0 +1 @@ +build/compile_commands.json \ No newline at end of file diff --git a/client/main.cpp b/client/main.cpp new file mode 100644 index 0000000..7a05049 --- /dev/null +++ b/client/main.cpp @@ -0,0 +1,25 @@ +#include +#include + +#include "puzbusv1.pb.h" + +int main() { + GOOGLE_PROTOBUF_VERIFY_VERSION; + + puzbus::I2CMsg test_msg; + + test_msg.set_address(0x39); + test_msg.set_data("Test message data!"); + + std::string output; + test_msg.SerializeToString(&output); + + printf("output[%lu]:\n", output.size()); + for (size_t i = 0; i < output.size(); i++) { + printf("%02x ", output[i]); + } + printf("\n"); + + return 0; +} + diff --git a/client/makefile b/client/makefile new file mode 100644 index 0000000..8352615 --- /dev/null +++ b/client/makefile @@ -0,0 +1,2 @@ +include ../lazy.mk + -- cgit v1.2.3 From 1152ec32b6086c153dc41da59c9c451aa4465995 Mon Sep 17 00:00:00 2001 From: lonkaars Date: Sat, 18 May 2024 20:11:44 +0200 Subject: WIP send/receive w/ msgpack --- client/CMakeLists.txt | 10 +++++++-- client/lib | 1 + client/main.cpp | 61 +++++++++++++++++++++++++++++++++++++-------------- main/sock.c | 8 +++---- main/sock.h | 6 +++++ proto/include.cmake | 3 +++ proto/puzbusv1.c | 21 +++++++++++++++++- proto/puzbusv1.h | 2 +- 8 files changed, 88 insertions(+), 24 deletions(-) create mode 120000 client/lib (limited to 'client') diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 9e433b1..bcef4c0 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -4,11 +4,17 @@ set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) set(CMAKE_EXPORT_COMPILE_COMMANDS 1) -include(../proto/include.cmake) - project(puzzlebox_client C CXX) +include(../proto/include.cmake) + add_executable(main main.cpp ) +target_link_libraries(main + puzbus + mpack + ) + + diff --git a/client/lib b/client/lib new file mode 120000 index 0000000..dc598c5 --- /dev/null +++ b/client/lib @@ -0,0 +1 @@ +../lib \ No newline at end of file diff --git a/client/main.cpp b/client/main.cpp index 7a05049..30d7045 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -1,25 +1,54 @@ #include -#include - -#include "puzbusv1.pb.h" - -int main() { - GOOGLE_PROTOBUF_VERIFY_VERSION; - - puzbus::I2CMsg test_msg; +#include +#include +#include + +#include "puzbusv1.h" + +int send_message() { + const char* data = "Test message data!"; + struct pb_msg output = { + .addr = 0x39, + .data = (char*) data, + .length = strlen(data), + }; + + char* packed; + size_t size; + if (!pb_write(&output, &packed, &size)) { + printf("error writing!\n"); + return 1; + } - test_msg.set_address(0x39); - test_msg.set_data("Test message data!"); + fwrite(packed, sizeof(packed[0]), size, stdout); + fflush(stdout); - std::string output; - test_msg.SerializeToString(&output); + return 0; +} - printf("output[%lu]:\n", output.size()); - for (size_t i = 0; i < output.size(); i++) { - printf("%02x ", output[i]); +int read_message() { + freopen(NULL, "rb", stdin); // allow binary on stdin + struct pb_msg input; + + char buf[8]; // extremely small buffer to test chunked message parsing + size_t bytes = 0; + while ((bytes = fread(buf, sizeof(buf[0]), sizeof(buf), stdin)) > 0) { + if (!pb_read(&input, buf, bytes)) continue; + + printf("address: 0x%02x\n", input.addr); + printf("data: \"%.*s\"\n", input.length, input.data); + free(input.data); + return 0; } - printf("\n"); + return 1; +} + +int main() { + if (!isatty(fileno(stdout))) return send_message(); + if (!isatty(fileno(stdin))) return read_message(); + + printf("please pipe some data in or out to use this program\n"); return 0; } diff --git a/main/sock.c b/main/sock.c index 7064de7..dac62af 100644 --- a/main/sock.c +++ b/main/sock.c @@ -5,9 +5,10 @@ #include #include "init.h" - #include "config.h" +struct netconn* current_connection = NULL; + void recv_handler(struct netconn* conn, struct netbuf* buf) { void *data; uint16_t len; @@ -21,17 +22,16 @@ void recv_handler(struct netconn* conn, struct netbuf* buf) { } void accept_handler(struct netconn* conn) { - printf("new connection!\n"); + current_connection = conn; struct netbuf* buf; - while (netconn_recv(conn, &buf) == ERR_OK) recv_handler(conn, buf); netconn_close(conn); netconn_delete(conn); - printf("connection closed!\n"); + current_connection = NULL; } void serve_task() { diff --git a/main/sock.h b/main/sock.h index dd7fc61..2a73418 100644 --- a/main/sock.h +++ b/main/sock.h @@ -1,5 +1,11 @@ #pragma once +#include +#include + /** \brief start listening for TCP socket requests */ void serve_task(); +void i2c_send(uint16_t addr, char* data, size_t data_size); +void i2c_recv(uint16_t addr, char* data, size_t data_size); + diff --git a/proto/include.cmake b/proto/include.cmake index c8a90b6..ac1305e 100644 --- a/proto/include.cmake +++ b/proto/include.cmake @@ -1,4 +1,7 @@ include_directories(${CMAKE_CURRENT_LIST_DIR}) +add_library(puzbus STATIC + ${CMAKE_CURRENT_LIST_DIR}/puzbusv1.c + ) # mpack include_directories(${CMAKE_CURRENT_LIST_DIR}/lib/mpack/src/mpack) diff --git a/proto/puzbusv1.c b/proto/puzbusv1.c index 9d1335e..3ff7c63 100644 --- a/proto/puzbusv1.c +++ b/proto/puzbusv1.c @@ -1,13 +1,32 @@ #include +#include #include "puzbusv1.h" int pb_read(struct pb_msg* target, char* buf, size_t buf_sz) { mpack_reader_t reader; + printf("read %lu bytes...\n", buf_sz); + + mpack_reader_init_data(&reader, buf, buf_sz); + + uint16_t address = mpack_expect_u16(&reader); + char data_buf[80]; + size_t data_size = mpack_expect_bin_buf(&reader, data_buf, sizeof(data_buf)); + + printf("0x%02x\n", address); + printf("\"%.*s\"\n", data_size, data_buf); return 0; } -void pb_free(struct pb_msg* msg); +int pb_write(struct pb_msg* target, char** buf, size_t* buf_sz) { + mpack_writer_t writer; + mpack_writer_init_growable(&writer, buf, buf_sz); + mpack_write_u16(&writer, target->addr); + mpack_write_bin(&writer, target->data, target->length); + + // finish writing + return mpack_writer_destroy(&writer) == mpack_ok; +} diff --git a/proto/puzbusv1.h b/proto/puzbusv1.h index 071c887..116dbf9 100644 --- a/proto/puzbusv1.h +++ b/proto/puzbusv1.h @@ -14,7 +14,7 @@ struct pb_msg { }; int pb_read(struct pb_msg* target, char* buf, size_t buf_sz); -void pb_free(struct pb_msg* msg); +int pb_write(struct pb_msg* target, char** buf, size_t* buf_sz); #ifdef __cplusplus } -- cgit v1.2.3 From 9ff66bfe22c5f378584db2a57160d00b585a77ce Mon Sep 17 00:00:00 2001 From: lonkaars Date: Mon, 20 May 2024 10:25:21 +0200 Subject: make puzbus slightly more robust --- client/main.cpp | 27 ++++++++++++++++++++------- proto/puzbusv1.c | 33 ++++++++++++++++++++++++--------- proto/puzbusv1.h | 23 ++++++++++++++++++----- 3 files changed, 62 insertions(+), 21 deletions(-) (limited to 'client') diff --git a/client/main.cpp b/client/main.cpp index 30d7045..dcc965b 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -17,31 +17,44 @@ int send_message() { size_t size; if (!pb_write(&output, &packed, &size)) { printf("error writing!\n"); - return 1; + return EXIT_FAILURE; } fwrite(packed, sizeof(packed[0]), size, stdout); fflush(stdout); - return 0; + return EXIT_SUCCESS; } int read_message() { freopen(NULL, "rb", stdin); // allow binary on stdin struct pb_msg input; - char buf[8]; // extremely small buffer to test chunked message parsing + char buf[4]; // extremely small buffer to test chunked message parsing size_t bytes = 0; + while ((bytes = fread(buf, sizeof(buf[0]), sizeof(buf), stdin)) > 0) { - if (!pb_read(&input, buf, bytes)) continue; + int ret = pb_read(&input, buf, bytes); + + // header read error + if (ret < 0) { + printf("error reading!\n"); + return EXIT_FAILURE; + } + + // continue reading if more bytes needed... + if (ret > 0) continue; + // message read completely! printf("address: 0x%02x\n", input.addr); printf("data: \"%.*s\"\n", input.length, input.data); free(input.data); - return 0; + return EXIT_SUCCESS; } - return 1; + // if we reach this point, data was read but it did not contain a complete + // message, and is thus considered a failure + return EXIT_FAILURE; } int main() { @@ -49,6 +62,6 @@ int main() { if (!isatty(fileno(stdin))) return read_message(); printf("please pipe some data in or out to use this program\n"); - return 0; + return EXIT_SUCCESS; } diff --git a/proto/puzbusv1.c b/proto/puzbusv1.c index d6cd597..3be4939 100644 --- a/proto/puzbusv1.c +++ b/proto/puzbusv1.c @@ -7,21 +7,32 @@ #include "puzbusv1.h" -bool pb_read(struct pb_msg* target, char* buf, size_t buf_sz) { - // remaining bytes to be read to target->data; this is the only variable that - // needs to persist between buffer blocks, and is therefore static - static size_t rdata = 0; +/** + * \brief Remaining bytes to be read to target->data + * + * This is the only variable that needs to persist between buffer blocks. It is + * declared in the global scope to allow resetting using the \c pb_read_reset() + * function. + * + * \note \p rdata may be reset by calling \c pb_read_reset() + */ +static size_t rdata = 0; +int pb_read(struct pb_msg* target, char* buf, size_t buf_sz) { // a new reader is used per buffer block passed to this function mpack_reader_t reader; mpack_reader_init_data(&reader, buf, buf_sz); // at start of message if (rdata == 0) { - // NOTE: This approach will crash and burn when target->addr can be read - // and target->length is past the end of the current buffer block. This is - // a highly unlikely scenario, as pb_read is called for each chunk of a TCP - // frame, and frames (should) include only one puzzle bus message. + // NOTE: The entire start of a message needs to be readable from the buffer + // at this point. When target->addr can be read and target->length is past + // the end of the current buffer block, this function will crash and burn. + // This is a highly unlikely scenario, as pb_read is called for each chunk + // of a TCP frame, and frames (should) include only one puzzle bus message. + // The check here is kind of optional. + if (buf_sz < 4) return -1; + target->addr = mpack_expect_u16(&reader); target->length = rdata = mpack_expect_bin(&reader); target->data = (char*) malloc(target->length); @@ -35,7 +46,11 @@ bool pb_read(struct pb_msg* target, char* buf, size_t buf_sz) { rdata -= to_read; // if rdata = 0, the message was completely read - return rdata == 0; + return rdata; +} + +void pb_read_reset() { + rdata = 0; } bool pb_write(struct pb_msg* target, char** buf, size_t* buf_sz) { diff --git a/proto/puzbusv1.h b/proto/puzbusv1.h index 814bc93..95130c9 100644 --- a/proto/puzbusv1.h +++ b/proto/puzbusv1.h @@ -24,13 +24,26 @@ struct pb_msg { * \param buf pointer to input stream data chunk * \param buf_sz size of \p buf * - * \returns boolean true if a message was completely read, false if more data - * is required + * \returns Integer representing amount of bytes required to finish message, or + * -1 if the message header could not be read. If this function returns 0, the + * message in \p target is complete. * - * \note target->data will automatically be allocated by this function, and - * must be `free()`d by the caller when finished + * \note target->data will automatically be allocated by this function, even if + * the message is not fully parsed. This variable must be `free()`d by the + * caller after each complete message to prevent memory leaks. */ -bool pb_read(struct pb_msg* target, char* buf, size_t buf_sz); +int pb_read(struct pb_msg* target, char* buf, size_t buf_sz); + +/** + * \brief reset the remaining message data counter + * + * Calling this function has the effect of forcing \c pb_read() to parse the + * next buffer chunk as the start of a new message. This function may be called + * before reading a TCP frame's data to mitigate any synchronization issues + * arising from earlier corrupt or otherwise malformed messages. + */ +void pb_read_reset(); + /** * \brief Allocate and write a msgpack-formatted message to \p buf * -- cgit v1.2.3 From b854c4d6ac06c4a39006a086766deb90096c2998 Mon Sep 17 00:00:00 2001 From: lonkaars Date: Mon, 20 May 2024 11:44:29 +0200 Subject: WIP CLI --- client/CMakeLists.txt | 2 + client/examples/puzbus-hello-world.cpp | 67 +++++++++++++++++++++++++++++++++ client/main.cpp | 68 ++++++---------------------------- client/rl.c | 55 +++++++++++++++++++++++++++ client/rl.h | 18 +++++++++ 5 files changed, 153 insertions(+), 57 deletions(-) create mode 100644 client/examples/puzbus-hello-world.cpp create mode 100644 client/rl.c create mode 100644 client/rl.h (limited to 'client') diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index bcef4c0..d77b65b 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -10,11 +10,13 @@ include(../proto/include.cmake) add_executable(main main.cpp + rl.c ) target_link_libraries(main puzbus mpack + readline # this is such a common library that I did not bother adding it as a submodule ) diff --git a/client/examples/puzbus-hello-world.cpp b/client/examples/puzbus-hello-world.cpp new file mode 100644 index 0000000..dcc965b --- /dev/null +++ b/client/examples/puzbus-hello-world.cpp @@ -0,0 +1,67 @@ +#include +#include +#include +#include + +#include "puzbusv1.h" + +int send_message() { + const char* data = "Test message data!"; + struct pb_msg output = { + .addr = 0x39, + .data = (char*) data, + .length = strlen(data), + }; + + char* packed; + size_t size; + if (!pb_write(&output, &packed, &size)) { + printf("error writing!\n"); + return EXIT_FAILURE; + } + + fwrite(packed, sizeof(packed[0]), size, stdout); + fflush(stdout); + + return EXIT_SUCCESS; +} + +int read_message() { + freopen(NULL, "rb", stdin); // allow binary on stdin + struct pb_msg input; + + char buf[4]; // extremely small buffer to test chunked message parsing + size_t bytes = 0; + + while ((bytes = fread(buf, sizeof(buf[0]), sizeof(buf), stdin)) > 0) { + int ret = pb_read(&input, buf, bytes); + + // header read error + if (ret < 0) { + printf("error reading!\n"); + return EXIT_FAILURE; + } + + // continue reading if more bytes needed... + if (ret > 0) continue; + + // message read completely! + printf("address: 0x%02x\n", input.addr); + printf("data: \"%.*s\"\n", input.length, input.data); + free(input.data); + return EXIT_SUCCESS; + } + + // if we reach this point, data was read but it did not contain a complete + // message, and is thus considered a failure + return EXIT_FAILURE; +} + +int main() { + if (!isatty(fileno(stdout))) return send_message(); + if (!isatty(fileno(stdin))) return read_message(); + + printf("please pipe some data in or out to use this program\n"); + return EXIT_SUCCESS; +} + diff --git a/client/main.cpp b/client/main.cpp index dcc965b..3d3a68c 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -1,67 +1,21 @@ #include #include -#include -#include +#include -#include "puzbusv1.h" +#include "rl.h" -int send_message() { - const char* data = "Test message data!"; - struct pb_msg output = { - .addr = 0x39, - .data = (char*) data, - .length = strlen(data), - }; - - char* packed; - size_t size; - if (!pb_write(&output, &packed, &size)) { - printf("error writing!\n"); +int main(int argc, char** argv) { + if (argc < 2) { + printf("usage: %s addr [port]\n", argv[0]); return EXIT_FAILURE; } - fwrite(packed, sizeof(packed[0]), size, stdout); - fflush(stdout); - - return EXIT_SUCCESS; -} - -int read_message() { - freopen(NULL, "rb", stdin); // allow binary on stdin - struct pb_msg input; - - char buf[4]; // extremely small buffer to test chunked message parsing - size_t bytes = 0; - - while ((bytes = fread(buf, sizeof(buf[0]), sizeof(buf), stdin)) > 0) { - int ret = pb_read(&input, buf, bytes); - - // header read error - if (ret < 0) { - printf("error reading!\n"); - return EXIT_FAILURE; - } - - // continue reading if more bytes needed... - if (ret > 0) continue; - - // message read completely! - printf("address: 0x%02x\n", input.addr); - printf("data: \"%.*s\"\n", input.length, input.data); - free(input.data); - return EXIT_SUCCESS; - } - - // if we reach this point, data was read but it did not contain a complete - // message, and is thus considered a failure - return EXIT_FAILURE; -} + // parse arguments + char* addr_str = argv[1]; + uint16_t port = 9191; + if (argc >= 3) port = atoi(argv[2]); -int main() { - if (!isatty(fileno(stdout))) return send_message(); - if (!isatty(fileno(stdin))) return read_message(); - - printf("please pipe some data in or out to use this program\n"); - return EXIT_SUCCESS; + // enter main CLI (using GNU readline for comfyness) + return cli_main(); } diff --git a/client/rl.c b/client/rl.c new file mode 100644 index 0000000..fb26057 --- /dev/null +++ b/client/rl.c @@ -0,0 +1,55 @@ +#include +#include +#include +#include + +#include +#include + +#include "rl.h" + +void rl_printf(const char *fmt, ...) { + // save line + char* saved_line = rl_copy_text(0, rl_end); + int saved_point = rl_point; + int saved_end = rl_end; + + // clear line + rl_save_prompt(); + rl_replace_line("", 0); + rl_redisplay(); + + // printf + va_list args; + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + + // restore line + rl_restore_prompt(); + rl_replace_line(saved_line, 0); + rl_point = saved_point; + rl_end = saved_end; + rl_redisplay(); + + free(saved_line); +} + +int cli_main() { + char* input = NULL; + while (1) { + if (input != NULL) free(input); + input = readline(CLI_PROMPT); + + // exit on ^D or ^C (EOF) + if (input == NULL) return EXIT_SUCCESS; + + // add non-empty line to history + if (*input) add_history(input); + + if (strcmp(input, "exit") == 0) return EXIT_SUCCESS; + } + + return EXIT_SUCCESS; +} + diff --git a/client/rl.h b/client/rl.h new file mode 100644 index 0000000..7eef4da --- /dev/null +++ b/client/rl.h @@ -0,0 +1,18 @@ +#pragma once + +#define COLOR_OFF "\x1B[0m" +#define COLOR_BLUE "\x1B[0;94m" + +#define CLI_PROMPT COLOR_BLUE "pbc" COLOR_OFF "% " + +#ifdef __cplusplus +extern "C" { +#endif + +int cli_main(); +void rl_printf(const char *fmt, ...); + +#ifdef __cplusplus +} +#endif + -- cgit v1.2.3 From 5876e74fa32881b41478cd67c5b0895161fbdc9c Mon Sep 17 00:00:00 2001 From: lonkaars Date: Mon, 20 May 2024 11:55:50 +0200 Subject: add socket class + test async prompt messages --- client/CMakeLists.txt | 1 + client/main.cpp | 6 +++++- client/sock.cpp | 29 +++++++++++++++++++++++++++++ client/sock.h | 21 +++++++++++++++++++++ 4 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 client/sock.cpp create mode 100644 client/sock.h (limited to 'client') diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index d77b65b..e4990d7 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -11,6 +11,7 @@ include(../proto/include.cmake) add_executable(main main.cpp rl.c + sock.cpp ) target_link_libraries(main diff --git a/client/main.cpp b/client/main.cpp index 3d3a68c..6aad0e3 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -3,6 +3,7 @@ #include #include "rl.h" +#include "sock.h" int main(int argc, char** argv) { if (argc < 2) { @@ -11,10 +12,13 @@ int main(int argc, char** argv) { } // parse arguments - char* addr_str = argv[1]; + char* addr = argv[1]; uint16_t port = 9191; if (argc >= 3) port = atoi(argv[2]); + // connect to TCP socket (automatically spawns thread) + PBSocket sock(addr, port); + // enter main CLI (using GNU readline for comfyness) return cli_main(); } diff --git a/client/sock.cpp b/client/sock.cpp new file mode 100644 index 0000000..703ee24 --- /dev/null +++ b/client/sock.cpp @@ -0,0 +1,29 @@ +#include +#include + +#include + +#include "sock.h" +#include "rl.h" + +PBSocket::PBSocket() { + printf("Init PBSocket!\n"); +} + +PBSocket::PBSocket(char* addr, uint16_t port) : PBSocket() { + connect(addr, port); +} + +void PBSocket::connect(char* addr, uint16_t port) { + printf("Connect to %s on port %d\n", addr, port); + + this->_thread = std::thread(&PBSocket::sock_task, this); +} + +void PBSocket::sock_task() { + while(1) { + sleep(3); + rl_printf("Testing asynchronous messages in prompt...\n"); + } +} + diff --git a/client/sock.h b/client/sock.h new file mode 100644 index 0000000..e3d7ec8 --- /dev/null +++ b/client/sock.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +class PBSocket { +public: + PBSocket(char* addr, uint16_t port); + + void connect(char* addr, uint16_t port); + +private: + PBSocket(); + + void sock_task(); + + std::thread _thread; + +}; + + -- cgit v1.2.3 From 41ed6fa61a65432843feb596726026bc5772ae19 Mon Sep 17 00:00:00 2001 From: lonkaars Date: Mon, 20 May 2024 13:24:13 +0200 Subject: socket connect working (sorta) --- client/main.cpp | 10 ++++++-- client/rl.h | 6 ++--- client/sock.cpp | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++------- client/sock.h | 17 +++++++++---- 4 files changed, 89 insertions(+), 19 deletions(-) (limited to 'client') diff --git a/client/main.cpp b/client/main.cpp index 6aad0e3..36fc7bb 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "rl.h" #include "sock.h" @@ -16,8 +17,13 @@ int main(int argc, char** argv) { uint16_t port = 9191; if (argc >= 3) port = atoi(argv[2]); - // connect to TCP socket (automatically spawns thread) - PBSocket sock(addr, port); + try { + // connect to TCP socket (automatically spawns thread) + PBSocket sock(addr, port); + } catch (const std::exception& e) { + printf("error: %s\n", e.what()); + return EXIT_FAILURE; + } // enter main CLI (using GNU readline for comfyness) return cli_main(); diff --git a/client/rl.h b/client/rl.h index 7eef4da..313a8fe 100644 --- a/client/rl.h +++ b/client/rl.h @@ -1,9 +1,9 @@ #pragma once -#define COLOR_OFF "\x1B[0m" -#define COLOR_BLUE "\x1B[0;94m" +#define COLOR_OFF "\x1b[0m" +#define COLOR_BOLD "\x1b[1m" -#define CLI_PROMPT COLOR_BLUE "pbc" COLOR_OFF "% " +#define CLI_PROMPT "(" COLOR_BOLD "pbc" COLOR_OFF ") " #ifdef __cplusplus extern "C" { diff --git a/client/sock.cpp b/client/sock.cpp index 703ee24..17d9e35 100644 --- a/client/sock.cpp +++ b/client/sock.cpp @@ -1,29 +1,86 @@ +#include +#include +#include #include #include +#include +#include +#include +#include #include #include "sock.h" #include "rl.h" -PBSocket::PBSocket() { - printf("Init PBSocket!\n"); -} +using std::logic_error; +using std::thread; +PBSocket::PBSocket() { } PBSocket::PBSocket(char* addr, uint16_t port) : PBSocket() { - connect(addr, port); + set_server(addr, port); + sock_connect(); +} + +PBSocket::~PBSocket() { + // stop TCP listen thread + if (_thread != nullptr) { + _thread->detach(); + delete _thread; + } + + sock_close(); +} + +void PBSocket::set_server(char* addr, uint16_t port) { + _addr = addr; + _port = port; } -void PBSocket::connect(char* addr, uint16_t port) { - printf("Connect to %s on port %d\n", addr, port); +void PBSocket::sock_connect() { + if (_addr == NULL) throw logic_error("no server address defined"); + if (_port == 0) throw logic_error("no server port defined"); - this->_thread = std::thread(&PBSocket::sock_task, this); + if (_thread != nullptr) throw logic_error("already connected"); + + rl_printf("connecting to %s on port %d...\n", _addr, _port); + + _fd = socket(AF_INET, SOCK_STREAM, 0); + if (_fd < 0) throw logic_error("socket create failed"); + + struct sockaddr_in server = { + .sin_family = AF_INET, + .sin_port = htons(_port), + .sin_addr = { + .s_addr = inet_addr(_addr), + }, + }; + int ret = connect(_fd, (struct sockaddr*) &server, sizeof(server)); + if (ret != 0) throw logic_error(strerror(errno)); + + this->_thread = new thread(&PBSocket::sock_task, this); +} + +void PBSocket::sock_close() { + if (_fd < 0) return; // already closed + close(_fd); + _fd = -1; } void PBSocket::sock_task() { while(1) { - sleep(3); - rl_printf("Testing asynchronous messages in prompt...\n"); + char buf[80]; + ssize_t bytes = read(_fd, buf, sizeof(buf)); + + if (bytes == -1) { + rl_printf("error: %s (%d)\n", strerror(errno), errno); + sock_close(); + break; + } + + if (bytes > 0) { + rl_printf("received %d bytes\n", bytes); + } } } diff --git a/client/sock.h b/client/sock.h index e3d7ec8..0f9a3fc 100644 --- a/client/sock.h +++ b/client/sock.h @@ -5,17 +5,24 @@ class PBSocket { public: + PBSocket(); PBSocket(char* addr, uint16_t port); + virtual ~PBSocket(); - void connect(char* addr, uint16_t port); + void set_server(char* addr, uint16_t port); -private: - PBSocket(); + void sock_connect(); +private: void sock_task(); + void sock_close(); - std::thread _thread; + std::thread* _thread = nullptr; -}; + char* _addr = NULL; + uint16_t _port = 0; + + int _fd = -1; +}; -- cgit v1.2.3 From 27c8d89359b8d5e97c4c23ff464d5f3de7279709 Mon Sep 17 00:00:00 2001 From: lonkaars Date: Mon, 20 May 2024 13:33:25 +0200 Subject: fix bad file descriptor error --- client/main.cpp | 3 ++- client/sock.cpp | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'client') diff --git a/client/main.cpp b/client/main.cpp index 36fc7bb..c01dbb5 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -17,9 +17,10 @@ int main(int argc, char** argv) { uint16_t port = 9191; if (argc >= 3) port = atoi(argv[2]); + PBSocket sock(addr, port); try { // connect to TCP socket (automatically spawns thread) - PBSocket sock(addr, port); + sock.sock_connect(); } catch (const std::exception& e) { printf("error: %s\n", e.what()); return EXIT_FAILURE; diff --git a/client/sock.cpp b/client/sock.cpp index 17d9e35..c10fba0 100644 --- a/client/sock.cpp +++ b/client/sock.cpp @@ -19,7 +19,6 @@ using std::thread; PBSocket::PBSocket() { } PBSocket::PBSocket(char* addr, uint16_t port) : PBSocket() { set_server(addr, port); - sock_connect(); } PBSocket::~PBSocket() { -- cgit v1.2.3 From db8906d54cd9afbc57f0b40a0d618335c552f704 Mon Sep 17 00:00:00 2001 From: lonkaars Date: Mon, 20 May 2024 13:47:37 +0200 Subject: back-and-forth in C++ w/o netcat --- client/CMakeLists.txt | 2 +- client/main.cpp | 12 ++++++--- client/rl.c | 55 ----------------------------------------- client/rl.cpp | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++ client/rl.h | 8 ------ client/sock.cpp | 46 +++++++++++++++++++++++++++++++--- client/sock.h | 7 ++++++ 7 files changed, 128 insertions(+), 70 deletions(-) delete mode 100644 client/rl.c create mode 100644 client/rl.cpp (limited to 'client') diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index e4990d7..cae0111 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -10,7 +10,7 @@ include(../proto/include.cmake) add_executable(main main.cpp - rl.c + rl.cpp sock.cpp ) diff --git a/client/main.cpp b/client/main.cpp index c01dbb5..5c26107 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -6,6 +6,8 @@ #include "rl.h" #include "sock.h" +PBSocket* sock; + int main(int argc, char** argv) { if (argc < 2) { printf("usage: %s addr [port]\n", argv[0]); @@ -17,16 +19,20 @@ int main(int argc, char** argv) { uint16_t port = 9191; if (argc >= 3) port = atoi(argv[2]); - PBSocket sock(addr, port); + sock = new PBSocket(addr, port); try { // connect to TCP socket (automatically spawns thread) - sock.sock_connect(); + sock->sock_connect(); } catch (const std::exception& e) { printf("error: %s\n", e.what()); return EXIT_FAILURE; } // enter main CLI (using GNU readline for comfyness) - return cli_main(); + int ret = cli_main(); + + delete sock; + + return ret; } diff --git a/client/rl.c b/client/rl.c deleted file mode 100644 index fb26057..0000000 --- a/client/rl.c +++ /dev/null @@ -1,55 +0,0 @@ -#include -#include -#include -#include - -#include -#include - -#include "rl.h" - -void rl_printf(const char *fmt, ...) { - // save line - char* saved_line = rl_copy_text(0, rl_end); - int saved_point = rl_point; - int saved_end = rl_end; - - // clear line - rl_save_prompt(); - rl_replace_line("", 0); - rl_redisplay(); - - // printf - va_list args; - va_start(args, fmt); - vprintf(fmt, args); - va_end(args); - - // restore line - rl_restore_prompt(); - rl_replace_line(saved_line, 0); - rl_point = saved_point; - rl_end = saved_end; - rl_redisplay(); - - free(saved_line); -} - -int cli_main() { - char* input = NULL; - while (1) { - if (input != NULL) free(input); - input = readline(CLI_PROMPT); - - // exit on ^D or ^C (EOF) - if (input == NULL) return EXIT_SUCCESS; - - // add non-empty line to history - if (*input) add_history(input); - - if (strcmp(input, "exit") == 0) return EXIT_SUCCESS; - } - - return EXIT_SUCCESS; -} - diff --git a/client/rl.cpp b/client/rl.cpp new file mode 100644 index 0000000..32a4df0 --- /dev/null +++ b/client/rl.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include + +#include +#include + +#include "rl.h" +#include "sock.h" + +void rl_printf(const char *fmt, ...) { + // save line + char* saved_line = rl_copy_text(0, rl_end); + int saved_point = rl_point; + int saved_end = rl_end; + + // clear line + rl_save_prompt(); + rl_replace_line("", 0); + rl_redisplay(); + + // printf + va_list args; + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + + // restore line + rl_restore_prompt(); + rl_replace_line(saved_line, 0); + rl_point = saved_point; + rl_end = saved_end; + rl_redisplay(); + + free(saved_line); +} + +void cmd_test() { + const char* data = "Hello world!"; + i2c_send(0x39, (char*) data, strlen(data)); +} + +int cli_main() { + char* input = NULL; + while (1) { + if (input != NULL) free(input); + input = readline(CLI_PROMPT); + + // exit on ^D or ^C (EOF) + if (input == NULL) return EXIT_SUCCESS; + + // add non-empty line to history + if (*input) add_history(input); + + if (strcmp(input, "exit") == 0) return EXIT_SUCCESS; + + if (strcmp(input, "test") == 0) { + cmd_test(); + continue; + } + + printf("unknown command!\n"); + } + + return EXIT_SUCCESS; +} + diff --git a/client/rl.h b/client/rl.h index 313a8fe..503225f 100644 --- a/client/rl.h +++ b/client/rl.h @@ -5,14 +5,6 @@ #define CLI_PROMPT "(" COLOR_BOLD "pbc" COLOR_OFF ") " -#ifdef __cplusplus -extern "C" { -#endif - int cli_main(); void rl_printf(const char *fmt, ...); -#ifdef __cplusplus -} -#endif - diff --git a/client/sock.cpp b/client/sock.cpp index c10fba0..cc18a69 100644 --- a/client/sock.cpp +++ b/client/sock.cpp @@ -10,6 +10,7 @@ #include +#include "puzbusv1.h" #include "sock.h" #include "rl.h" @@ -66,20 +67,59 @@ void PBSocket::sock_close() { _fd = -1; } +void PBSocket::send(char* buf, size_t buf_sz) { + write(_fd, buf, buf_sz); +} + void PBSocket::sock_task() { + struct pb_msg input; + while(1) { char buf[80]; ssize_t bytes = read(_fd, buf, sizeof(buf)); if (bytes == -1) { rl_printf("error: %s (%d)\n", strerror(errno), errno); - sock_close(); break; } - if (bytes > 0) { - rl_printf("received %d bytes\n", bytes); + // skip empty frames + if (bytes == 0) continue; + + int ret = pb_read(&input, buf, bytes); + + // header read error + if (ret < 0) { + rl_printf("pb_read error!\n"); + break; } + + // continue reading if more bytes needed... + if (ret > 0) continue; + + // message read completely! + i2c_recv(input.addr, input.data, input.length); + free(input.data); } + + sock_close(); +} + +void i2c_send(uint16_t addr, char* data, size_t data_size) { + struct pb_msg msg = { + .addr = addr, + .data = data, + .length = data_size, + }; + + char* packed; + size_t size; + if (!pb_write(&msg, &packed, &size)) return; + + sock->send(packed, size); +} + +void i2c_recv(uint16_t addr, char* data, size_t data_size) { + rl_printf("[0x%02x]: %.*s\n", addr, data_size, data); } diff --git a/client/sock.h b/client/sock.h index 0f9a3fc..818ea72 100644 --- a/client/sock.h +++ b/client/sock.h @@ -13,6 +13,8 @@ public: void sock_connect(); + void send(char* buf, size_t buf_sz); + private: void sock_task(); void sock_close(); @@ -26,3 +28,8 @@ private: }; +extern PBSocket* sock; + +void i2c_send(uint16_t addr, char* data, size_t data_size); +void i2c_recv(uint16_t addr, char* data, size_t data_size); + -- cgit v1.2.3 From 68a5c65f9b0e1df30e9cef490d9b218b2f21f90d Mon Sep 17 00:00:00 2001 From: lonkaars Date: Tue, 21 May 2024 10:30:06 +0200 Subject: clean up puzbusv1 API --- client/sock.cpp | 12 ++++++------ client/sock.h | 13 ++++++------- main/sock.c | 10 ++++++---- main/sock.h | 4 ++-- proto/puzbusv1.c | 33 +++++++++++---------------------- proto/puzbusv1.h | 14 ++++++++------ 6 files changed, 39 insertions(+), 47 deletions(-) (limited to 'client') diff --git a/client/sock.cpp b/client/sock.cpp index cc18a69..f967f64 100644 --- a/client/sock.cpp +++ b/client/sock.cpp @@ -18,7 +18,7 @@ using std::logic_error; using std::thread; PBSocket::PBSocket() { } -PBSocket::PBSocket(char* addr, uint16_t port) : PBSocket() { +PBSocket::PBSocket(const char * addr, uint16_t port) : PBSocket() { set_server(addr, port); } @@ -32,7 +32,7 @@ PBSocket::~PBSocket() { sock_close(); } -void PBSocket::set_server(char* addr, uint16_t port) { +void PBSocket::set_server(const char * addr, uint16_t port) { _addr = addr; _port = port; } @@ -67,7 +67,7 @@ void PBSocket::sock_close() { _fd = -1; } -void PBSocket::send(char* buf, size_t buf_sz) { +void PBSocket::send(const char * buf, size_t buf_sz) { write(_fd, buf, buf_sz); } @@ -105,10 +105,10 @@ void PBSocket::sock_task() { sock_close(); } -void i2c_send(uint16_t addr, char* data, size_t data_size) { +void i2c_send(uint16_t addr, const char * data, size_t data_size) { struct pb_msg msg = { .addr = addr, - .data = data, + .data = (char *) data, .length = data_size, }; @@ -119,7 +119,7 @@ void i2c_send(uint16_t addr, char* data, size_t data_size) { sock->send(packed, size); } -void i2c_recv(uint16_t addr, char* data, size_t data_size) { +void i2c_recv(uint16_t addr, const char * data, size_t data_size) { rl_printf("[0x%02x]: %.*s\n", addr, data_size, data); } diff --git a/client/sock.h b/client/sock.h index 818ea72..42eba3b 100644 --- a/client/sock.h +++ b/client/sock.h @@ -6,14 +6,14 @@ class PBSocket { public: PBSocket(); - PBSocket(char* addr, uint16_t port); + PBSocket(const char * addr, uint16_t port); virtual ~PBSocket(); - void set_server(char* addr, uint16_t port); + void set_server(const char * addr, uint16_t port); void sock_connect(); - void send(char* buf, size_t buf_sz); + void send(const char * buf, size_t buf_sz); private: void sock_task(); @@ -21,15 +21,14 @@ private: std::thread* _thread = nullptr; - char* _addr = NULL; + const char * _addr = NULL; uint16_t _port = 0; int _fd = -1; - }; extern PBSocket* sock; -void i2c_send(uint16_t addr, char* data, size_t data_size); -void i2c_recv(uint16_t addr, char* data, size_t data_size); +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); diff --git a/main/sock.c b/main/sock.c index 705e2eb..4f50981 100644 --- a/main/sock.c +++ b/main/sock.c @@ -13,16 +13,16 @@ struct netconn* current_connection = NULL; struct pb_msg recv_msg; -void i2c_send(uint16_t addr, char* data, size_t data_size) { +void i2c_send(uint16_t addr, const char * data, size_t data_size) { if (current_connection == NULL) return; struct pb_msg send_msg = { .addr = addr, - .data = data, + .data = (char *) data, .length = data_size, }; - char* buf; + char * buf; size_t buf_sz; if (!pb_write(&send_msg, &buf, &buf_sz)) return; @@ -34,7 +34,7 @@ void i2c_send(uint16_t addr, char* data, size_t data_size) { free(buf); } -void i2c_recv(uint16_t addr, char* data, size_t data_size) { +void i2c_recv(uint16_t addr, const char * data, size_t data_size) { printf("address: 0x%02x\n", addr); printf("data: \"%.*s\"\n", data_size, data); @@ -47,6 +47,8 @@ void i2c_recv(uint16_t addr, char* data, size_t data_size) { } void recv_handler(struct netconn* conn, struct netbuf* buf) { + pb_read_reset(&recv_msg); + do { char* data; uint16_t len; diff --git a/main/sock.h b/main/sock.h index 2a73418..f2db35d 100644 --- a/main/sock.h +++ b/main/sock.h @@ -6,6 +6,6 @@ /** \brief start listening for TCP socket requests */ void serve_task(); -void i2c_send(uint16_t addr, char* data, size_t data_size); -void i2c_recv(uint16_t addr, char* data, size_t data_size); +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); diff --git a/proto/puzbusv1.c b/proto/puzbusv1.c index 3be4939..73deda5 100644 --- a/proto/puzbusv1.c +++ b/proto/puzbusv1.c @@ -7,24 +7,13 @@ #include "puzbusv1.h" -/** - * \brief Remaining bytes to be read to target->data - * - * This is the only variable that needs to persist between buffer blocks. It is - * declared in the global scope to allow resetting using the \c pb_read_reset() - * function. - * - * \note \p rdata may be reset by calling \c pb_read_reset() - */ -static size_t rdata = 0; - -int pb_read(struct pb_msg* target, char* buf, size_t buf_sz) { +int pb_read(struct pb_msg * target, const char * buf, size_t buf_sz) { // a new reader is used per buffer block passed to this function mpack_reader_t reader; mpack_reader_init_data(&reader, buf, buf_sz); // at start of message - if (rdata == 0) { + if (target->_rdata == 0) { // NOTE: The entire start of a message needs to be readable from the buffer // at this point. When target->addr can be read and target->length is past // the end of the current buffer block, this function will crash and burn. @@ -34,26 +23,26 @@ int pb_read(struct pb_msg* target, char* buf, size_t buf_sz) { if (buf_sz < 4) return -1; target->addr = mpack_expect_u16(&reader); - target->length = rdata = mpack_expect_bin(&reader); - target->data = (char*) malloc(target->length); + target->length = target->_rdata = mpack_expect_bin(&reader); + target->data = (char *) malloc(target->length); } // continue reading chunks of target->data until the amount of bytes // specified in target->length - size_t to_read = MIN(mpack_reader_remaining(&reader, NULL), rdata); - char* data = target->data + target->length - rdata; // 'ol pointer arithmetic + size_t to_read = MIN(mpack_reader_remaining(&reader, NULL), target->_rdata); + char * data = target->data + target->length - target->_rdata; mpack_read_bytes(&reader, data, to_read); - rdata -= to_read; + target->_rdata -= to_read; // if rdata = 0, the message was completely read - return rdata; + return target->_rdata; } -void pb_read_reset() { - rdata = 0; +void pb_read_reset(struct pb_msg * target) { + target->_rdata = 0; } -bool pb_write(struct pb_msg* target, char** buf, size_t* buf_sz) { +bool pb_write(const struct pb_msg * target, char ** buf, size_t * buf_sz) { mpack_writer_t writer; mpack_writer_init_growable(&writer, buf, buf_sz); diff --git a/proto/puzbusv1.h b/proto/puzbusv1.h index 95130c9..0985b2b 100644 --- a/proto/puzbusv1.h +++ b/proto/puzbusv1.h @@ -7,10 +7,12 @@ extern "C" { #endif +/** \brief Puzzle bus message (v1) */ struct pb_msg { - uint16_t addr; - char* data; - size_t length; + uint16_t addr; //!< I^2^C address + char * data; //!< message content + size_t length; //!< message size + size_t _rdata; //!< \private remaining bytes to read until message is complete }; /** @@ -32,7 +34,7 @@ struct pb_msg { * the message is not fully parsed. This variable must be `free()`d by the * caller after each complete message to prevent memory leaks. */ -int pb_read(struct pb_msg* target, char* buf, size_t buf_sz); +int pb_read(struct pb_msg * target, const char * buf, size_t buf_sz); /** * \brief reset the remaining message data counter @@ -42,7 +44,7 @@ int pb_read(struct pb_msg* target, char* buf, size_t buf_sz); * before reading a TCP frame's data to mitigate any synchronization issues * arising from earlier corrupt or otherwise malformed messages. */ -void pb_read_reset(); +void pb_read_reset(struct pb_msg * target); /** * \brief Allocate and write a msgpack-formatted message to \p buf @@ -60,7 +62,7 @@ void pb_read_reset(); * * \note the pointer stored in \p buf must be `free()`d by the caller afterwards */ -bool pb_write(struct pb_msg* target, char** buf, size_t* buf_sz); +bool pb_write(const struct pb_msg * target, char ** buf, size_t * buf_sz); #ifdef __cplusplus } -- cgit v1.2.3 From 53d27ebf10225274a50dc4a7c2343d4efce55a8a Mon Sep 17 00:00:00 2001 From: lonkaars Date: Wed, 22 May 2024 19:32:21 +0200 Subject: clean up command handling --- client/.gitignore | 1 - client/CMakeLists.txt | 5 +++-- client/cmd.cpp | 15 +++++++++++++++ client/cmd.h | 23 +++++++++++++++++++++++ client/pbc | 1 + client/readme.md | 14 ++++++++++++++ client/rl.cpp | 27 ++++++++++++--------------- 7 files changed, 68 insertions(+), 18 deletions(-) delete mode 100644 client/.gitignore create mode 100644 client/cmd.cpp create mode 100644 client/cmd.h create mode 120000 client/pbc create mode 100644 client/readme.md (limited to 'client') diff --git a/client/.gitignore b/client/.gitignore deleted file mode 100644 index ba2906d..0000000 --- a/client/.gitignore +++ /dev/null @@ -1 +0,0 @@ -main diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index cae0111..c526345 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -8,13 +8,14 @@ project(puzzlebox_client C CXX) include(../proto/include.cmake) -add_executable(main +add_executable(pbc main.cpp rl.cpp sock.cpp + cmd.cpp ) -target_link_libraries(main +target_link_libraries(pbc puzbus mpack readline # this is such a common library that I did not bother adding it as a submodule diff --git a/client/cmd.cpp b/client/cmd.cpp new file mode 100644 index 0000000..99a4dd6 --- /dev/null +++ b/client/cmd.cpp @@ -0,0 +1,15 @@ +#include +#include + +#include "cmd.h" +#include "sock.h" + +void cmd_exit(char*) { + exit(EXIT_SUCCESS); +} + +void cmd_test(char*) { + const char* data = "Hello world!"; + i2c_send(0x39, (char*) data, strlen(data)); +} + diff --git a/client/cmd.h b/client/cmd.h new file mode 100644 index 0000000..509104a --- /dev/null +++ b/client/cmd.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +typedef void cmd_fn_t(char *); + +struct cmd { + const char* name; + void (* handle)(char *); + const char* info; + // TODO: tab completion function? +}; + +cmd_fn_t cmd_exit; +cmd_fn_t cmd_test; + +static const struct cmd cmds[] = { + (struct cmd){ .name = "exit", .handle = cmd_exit, .info = NULL, }, + (struct cmd){ .name = "test", .handle = cmd_test, .info = NULL, }, +}; + +static const size_t cmds_length = sizeof(cmds) / sizeof(cmds[0]); + diff --git a/client/pbc b/client/pbc new file mode 120000 index 0000000..51eda50 --- /dev/null +++ b/client/pbc @@ -0,0 +1 @@ +build/pbc \ No newline at end of file diff --git a/client/readme.md b/client/readme.md new file mode 100644 index 0000000..9d755aa --- /dev/null +++ b/client/readme.md @@ -0,0 +1,14 @@ +# puzzle box client + +goal (in order of implementation): +``` +(pbc) help + exit exit pbc + test send a test puzbus message + help show this help + send [debug] send raw message + status show global puzzle box state (main controller state) + reset reset entire game state + ls list connected puzzle modules +``` + diff --git a/client/rl.cpp b/client/rl.cpp index 32a4df0..b016370 100644 --- a/client/rl.cpp +++ b/client/rl.cpp @@ -7,7 +7,7 @@ #include #include "rl.h" -#include "sock.h" +#include "cmd.h" void rl_printf(const char *fmt, ...) { // save line @@ -36,9 +36,13 @@ void rl_printf(const char *fmt, ...) { free(saved_line); } -void cmd_test() { - const char* data = "Hello world!"; - i2c_send(0x39, (char*) data, strlen(data)); +static bool cli_cmd(char* line) { + for (size_t i = 0; i < cmds_length; i++) { + if (strcmp(line, cmds[i].name) != 0) continue; + cmds[i].handle(line); + return true; + } + return false; } int cli_main() { @@ -47,18 +51,11 @@ int cli_main() { if (input != NULL) free(input); input = readline(CLI_PROMPT); - // exit on ^D or ^C (EOF) - if (input == NULL) return EXIT_SUCCESS; + if (input == NULL) return EXIT_SUCCESS; // exit on ^D (EOF) + if (*input == '\0') continue; // ignore empty lines + add_history(input); - // add non-empty line to history - if (*input) add_history(input); - - if (strcmp(input, "exit") == 0) return EXIT_SUCCESS; - - if (strcmp(input, "test") == 0) { - cmd_test(); - continue; - } + if (cli_cmd(input)) continue; printf("unknown command!\n"); } -- cgit v1.2.3 From 31c30df2a24a45c69a7c5c2f594fa3a9a835b1fb Mon Sep 17 00:00:00 2001 From: lonkaars Date: Fri, 24 May 2024 15:18:44 +0200 Subject: add tab completion + help function --- client/cmd.cpp | 17 +++++++++++++++++ client/cmd.h | 45 +++++++++++++++++++++++++++++++++++++++++---- client/rl.cpp | 20 +++++++++++++++++++- 3 files changed, 77 insertions(+), 5 deletions(-) (limited to 'client') diff --git a/client/cmd.cpp b/client/cmd.cpp index 99a4dd6..0a73dad 100644 --- a/client/cmd.cpp +++ b/client/cmd.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -13,3 +14,19 @@ void cmd_test(char*) { i2c_send(0x39, (char*) data, strlen(data)); } +void cmd_help(char*) { + printf("List of available commands:\n"); + for (size_t i = 0; i < cmds_length; i++) { + struct cmd cmd = cmds[i]; + printf(" %-*s", 10, cmd.name); + if (cmd.info != NULL) + printf(" %s", cmd.info); + printf("\n"); + } + + printf( + "\n" + "You can also use the TAB key to autocomplete commands\n" + ); +} + diff --git a/client/cmd.h b/client/cmd.h index 509104a..7ec67fc 100644 --- a/client/cmd.h +++ b/client/cmd.h @@ -5,19 +5,56 @@ typedef void cmd_fn_t(char *); struct cmd { - const char* name; void (* handle)(char *); + const char* name; const char* info; // TODO: tab completion function? }; cmd_fn_t cmd_exit; cmd_fn_t cmd_test; +cmd_fn_t cmd_help; +cmd_fn_t cmd_send; +cmd_fn_t cmd_status; +cmd_fn_t cmd_reset; +cmd_fn_t cmd_ls; static const struct cmd cmds[] = { - (struct cmd){ .name = "exit", .handle = cmd_exit, .info = NULL, }, - (struct cmd){ .name = "test", .handle = cmd_test, .info = NULL, }, + { + .handle = cmd_exit, + .name = "exit", + .info = "exit pbc", + }, + { + .handle = cmd_test, + .name = "test", + .info = "send a test puzbus message", + }, + { + .handle = cmd_help, + .name = "help", + .info = "show this help", + }, + // { + // .handle = cmd_send, + // .name = "send", + // .info = "[debug] send raw message", + // }, + // { + // .handle = cmd_status, + // .name = "status", + // .info = "show global puzzle box state (main controller state)", + // }, + // { + // .handle = cmd_reset, + // .name = "reset", + // .info = "reset entire game state", + // }, + // { + // .handle = cmd_ls, + // .name = "ls", + // .info = "list connected puzzle modules", + // }, }; - static const size_t cmds_length = sizeof(cmds) / sizeof(cmds[0]); diff --git a/client/rl.cpp b/client/rl.cpp index b016370..6073500 100644 --- a/client/rl.cpp +++ b/client/rl.cpp @@ -37,16 +37,34 @@ void rl_printf(const char *fmt, ...) { } static bool cli_cmd(char* line) { + char* cmd = strtok(line, " \t\n"); for (size_t i = 0; i < cmds_length; i++) { - if (strcmp(line, cmds[i].name) != 0) continue; + if (strncmp(cmds[i].name, cmd, strlen(cmd)) != 0) continue; cmds[i].handle(line); return true; } return false; } +static char* rl_completion_entries(const char *text, int state) { + static size_t i = 0; + if (state == 0) i = 0; + + while (i < cmds_length) { + struct cmd cmd = cmds[i]; + i++; + if (strncmp(text, cmd.name, strlen(text)) == 0) { + return strdup(cmd.name); + } + } + + return NULL; +} + int cli_main() { char* input = NULL; + rl_completion_entry_function = rl_completion_entries; + while (1) { if (input != NULL) free(input); input = readline(CLI_PROMPT); -- cgit v1.2.3 From 1a92ed5075aba4b41fe34422d21a2c66cdf1d4c9 Mon Sep 17 00:00:00 2001 From: lonkaars Date: Fri, 24 May 2024 17:47:00 +0200 Subject: WIP `send` command --- client/CMakeLists.txt | 1 + client/cmd.cpp | 58 ++++++++++++++++++++++++++++++++++++++------------- client/cmd.h | 10 ++++----- client/parse.cpp | 32 ++++++++++++++++++++++++++++ client/parse.h | 38 +++++++++++++++++++++++++++++++++ client/rl.cpp | 47 ++++++++++++++++++++++------------------- 6 files changed, 145 insertions(+), 41 deletions(-) create mode 100644 client/parse.cpp create mode 100644 client/parse.h (limited to 'client') diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index c526345..35a55b6 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -13,6 +13,7 @@ add_executable(pbc rl.cpp sock.cpp cmd.cpp + parse.cpp ) target_link_libraries(pbc diff --git a/client/cmd.cpp b/client/cmd.cpp index 0a73dad..4a2c8a3 100644 --- a/client/cmd.cpp +++ b/client/cmd.cpp @@ -4,6 +4,12 @@ #include "cmd.h" #include "sock.h" +#include "parse.h" + +char* consume_token(char* input, const char* ifs) { + strtok(input, ifs); + return strtok(NULL, "\0"); +} void cmd_exit(char*) { exit(EXIT_SUCCESS); @@ -11,22 +17,46 @@ void cmd_exit(char*) { void cmd_test(char*) { const char* data = "Hello world!"; - i2c_send(0x39, (char*) data, strlen(data)); + i2c_send(0x39, data, strlen(data)); } void cmd_help(char*) { - printf("List of available commands:\n"); - for (size_t i = 0; i < cmds_length; i++) { - struct cmd cmd = cmds[i]; - printf(" %-*s", 10, cmd.name); - if (cmd.info != NULL) - printf(" %s", cmd.info); - printf("\n"); - } - - printf( - "\n" - "You can also use the TAB key to autocomplete commands\n" - ); + printf("List of available commands:\n"); + for (size_t i = 0; i < cmds_length; i++) { + struct cmd cmd = cmds[i]; + printf(" %-*s", 10, cmd.name); + if (cmd.info != NULL) + printf(" %s", cmd.info); + printf("\n"); + } + + printf( + "\n" + "You can also use the TAB key to autocomplete commands\n" + ); +} + +void cmd_send(char* addr_str) { + char* data_str = consume_token(addr_str, IFS); + + char* end; + uint16_t addr = strtol(addr_str, &end, 0); + if (addr_str + strlen(addr_str) != end) { + printf("address format error\n"); + return; + } + + char* data; + size_t data_size; + if (strtodata(data_str, &data, &data_size)) { + printf("data format error at index %d:\n%s\n%*s^\n", + (int) data_size, data_str, (int) data_size, ""); + return; + } + + // printf("(0x%02x) -> \"%.*s\"\n", addr, data_size, data); + i2c_send(addr, data, data_size); + + free(data); } diff --git a/client/cmd.h b/client/cmd.h index 7ec67fc..9d20328 100644 --- a/client/cmd.h +++ b/client/cmd.h @@ -35,11 +35,11 @@ static const struct cmd cmds[] = { .name = "help", .info = "show this help", }, - // { - // .handle = cmd_send, - // .name = "send", - // .info = "[debug] send raw message", - // }, + { + .handle = cmd_send, + .name = "send", + .info = "[debug] send raw message", + }, // { // .handle = cmd_status, // .name = "status", diff --git a/client/parse.cpp b/client/parse.cpp new file mode 100644 index 0000000..d15207c --- /dev/null +++ b/client/parse.cpp @@ -0,0 +1,32 @@ +#include +#include + +#include "parse.h" + +static unsigned ifsrun(const char* input, const char* ift) { + unsigned i; + for (i = 0; input[i] != '\0' && strchr(ift, input[i]); i++); + return i; +} + +int strtodata(const char* str, char** data, size_t* size) { + const char* ifs = IFS; + *size = 0; + size_t i; + size_t str_len = strlen(str); + + // TODO: finish this parser + // for (i = 0; i < str_len; i++) { + // unsigned ifs_run = ifsrun(&str[i], ifs); + // + // } + + *size = str_len; + + *data = (char*) malloc(*size); + + memcpy(*data, str, *size); + + return 0; +} + diff --git a/client/parse.h b/client/parse.h new file mode 100644 index 0000000..ac06446 --- /dev/null +++ b/client/parse.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +#define IFS " \t\n" + +/** + * \brief modify \p token to point to the first token when broken up on \p ifs + * and return the remaining data + * + * \p token will be null-terminated to indicate the end of the first token. A + * pointer to the remaining line after the NULL byte is returned, or NULL when + * the end of the string has been reached. + * + * \param token input string + * \param ifs string containing field separators + * + * \return the remaining data after \p token and the first \p ifs + */ +char* consume_token(char* token, const char* ifs); + +/** + * \brief convert string with literals into raw data + * + * \param str input string containing literals + * \param data pointer to \c char* that will store the resulting data + * \param size size of \p data + * + * \return 0 if the string was parsed succesfully, or 1 if the string could not + * be parsed succesfully + * + * \note The pointer that \p data refers to will not be initialized by this + * function if parsing fails + * \note \p size will contain the index of \p str where the first invalid data + * was found if parsing fails + */ +int strtodata(const char* str, char** data, size_t* size); + diff --git a/client/rl.cpp b/client/rl.cpp index 6073500..3f93e99 100644 --- a/client/rl.cpp +++ b/client/rl.cpp @@ -8,13 +8,14 @@ #include "rl.h" #include "cmd.h" +#include "parse.h" void rl_printf(const char *fmt, ...) { // save line char* saved_line = rl_copy_text(0, rl_end); int saved_point = rl_point; int saved_end = rl_end; - + // clear line rl_save_prompt(); rl_replace_line("", 0); @@ -36,34 +37,38 @@ void rl_printf(const char *fmt, ...) { free(saved_line); } -static bool cli_cmd(char* line) { - char* cmd = strtok(line, " \t\n"); +static void cli_cmd(char* cmd) { + char* line = consume_token(cmd, IFS); + for (size_t i = 0; i < cmds_length; i++) { - if (strncmp(cmds[i].name, cmd, strlen(cmd)) != 0) continue; + if (strncmp(cmds[i].name, cmd, strlen(cmd)) != 0) + continue; + cmds[i].handle(line); - return true; + return; } - return false; + + printf("unknown command!\n"); } static char* rl_completion_entries(const char *text, int state) { - static size_t i = 0; - if (state == 0) i = 0; - - while (i < cmds_length) { - struct cmd cmd = cmds[i]; - i++; - if (strncmp(text, cmd.name, strlen(text)) == 0) { - return strdup(cmd.name); - } - } - - return NULL; + static size_t i = 0; + if (state == 0) i = 0; + + while (i < cmds_length) { + struct cmd cmd = cmds[i]; + i++; + if (strncmp(text, cmd.name, strlen(text)) == 0) { + return strdup(cmd.name); + } + } + + return NULL; } int cli_main() { char* input = NULL; - rl_completion_entry_function = rl_completion_entries; + rl_completion_entry_function = rl_completion_entries; while (1) { if (input != NULL) free(input); @@ -73,9 +78,7 @@ int cli_main() { if (*input == '\0') continue; // ignore empty lines add_history(input); - if (cli_cmd(input)) continue; - - printf("unknown command!\n"); + cli_cmd(input); } return EXIT_SUCCESS; -- cgit v1.2.3 From 5d5b186a5a82b7e2415eddd77ef93af851034a5b Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Sat, 25 May 2024 15:31:42 +0200 Subject: WIP send command parser --- client/cmd.cpp | 5 ++- client/parse.cpp | 111 ++++++++++++++++++++++++++++++++++++++++++++++++------- client/parse.h | 12 ++++-- client/readme.md | 7 ++++ 4 files changed, 116 insertions(+), 19 deletions(-) (limited to 'client') diff --git a/client/cmd.cpp b/client/cmd.cpp index 4a2c8a3..1ec2cb8 100644 --- a/client/cmd.cpp +++ b/client/cmd.cpp @@ -48,9 +48,10 @@ void cmd_send(char* addr_str) { char* data; size_t data_size; - if (strtodata(data_str, &data, &data_size)) { + int err = strtodata(data_str, &data, &data_size); + if (err <= 0) { printf("data format error at index %d:\n%s\n%*s^\n", - (int) data_size, data_str, (int) data_size, ""); + -err, data_str, -err, ""); return; } diff --git a/client/parse.cpp b/client/parse.cpp index d15207c..223dc5d 100644 --- a/client/parse.cpp +++ b/client/parse.cpp @@ -1,31 +1,116 @@ +#include #include #include +#include #include "parse.h" -static unsigned ifsrun(const char* input, const char* ift) { - unsigned i; - for (i = 0; input[i] != '\0' && strchr(ift, input[i]); i++); - return i; +static int parse_str(const char* str, char* data, size_t* size) { + char closing = str[0]; + char escape = false; + bool scan = data == NULL; + int i = 0; + size_t len = strlen(str); + + switch (str[i]) { + case '\'': + escape = false; + break; + case '\"': + escape = true; + break; + default: + return -i; + } + + for (i = 1; i < len && str[i] != '\0'; i++) { + char c = str[i]; + + if (c == closing) { + if (scan) printf("string%s of length %d\n", escape ? " (w/ escape)" : "", i - 1); + return i + 1; // +1 for closing quote + } + + if (scan) *size += 1; + } + + return -i; +} + +static int parse_num(const char* str, char* data, size_t* size) { + const char* ifs = IFS; + size_t len = strcspn(str, ifs); + bool scan = data == NULL; + int i = 0; + int base = 10; + bool bytestring = false; + + const char* colon = strchr(str, ':'); + if (colon != NULL && colon < str + len) { // byte string + base = 16; + bytestring = true; + } else if (len > 2 && strncmp(str, "0x", 2) == 0) { // hexadecimal prefix + base = 16; + i += 2; + }/* else if (len > 1 && strncmp(str, "0", 1) == 0) { // octal prefix + base = 8; + i += 1; + }*/ + + const char* set; + // if (base == 8) set = SET_OCT; + if (base == 10) set = SET_DEC; + if (base == 16) { + if (bytestring) set = SET_HEX_STR; + else set = SET_HEX; + } + + size_t len_ok = strspn(str + i, set) + i; + if (len != len_ok) return -len_ok; + + if (scan) { + if (base == 10) *size += 1; + else if (base == 16) { + if (!bytestring) { + *size += (len - i + 1) / 2; + } else { + for (; colon != NULL && colon < str + len; colon = strchr(str, ':')) { + *size += 1; + } + } + } + } + + if (scan) printf("number (base %d%s) of length %lu\n", base, bytestring ? " as bytestring" : "", len - i); + return len; } int strtodata(const char* str, char** data, size_t* size) { const char* ifs = IFS; *size = 0; size_t i; - size_t str_len = strlen(str); + size_t len = strlen(str); - // TODO: finish this parser - // for (i = 0; i < str_len; i++) { - // unsigned ifs_run = ifsrun(&str[i], ifs); - // - // } + for (i = 0; i < len;) { + // skip whitespace + int run; + run = strspn(&str[i], ifs); + if (run > 0) printf("skipping whitespace for %d bytes...\n", run); + i += run; + // end of string + if (str[i] == '\0') break; - *size = str_len; + if ((run = parse_str(str + i, NULL, size)) > 0) { i += run; continue; } + if ((run = parse_num(str + i, NULL, size)) > 0) { i += run; continue; } - *data = (char*) malloc(*size); + // no format detected + return -i + run; + } + printf("end of string w/o parse errors\n"); + printf("buffer size is now %lu\n", *size); + exit(0); - memcpy(*data, str, *size); + *data = (char*) malloc(*size); return 0; } diff --git a/client/parse.h b/client/parse.h index ac06446..10274e7 100644 --- a/client/parse.h +++ b/client/parse.h @@ -4,6 +4,11 @@ #define IFS " \t\n" +#define SET_OCT "01234567" +#define SET_DEC "0123456789" +#define SET_HEX SET_DEC"abcdefABCDEF" +#define SET_HEX_STR SET_HEX":" + /** * \brief modify \p token to point to the first token when broken up on \p ifs * and return the remaining data @@ -26,13 +31,12 @@ char* consume_token(char* token, const char* ifs); * \param data pointer to \c char* that will store the resulting data * \param size size of \p data * - * \return 0 if the string was parsed succesfully, or 1 if the string could not - * be parsed succesfully + * \return 0 or a negative integer representing the index where there is a + * syntax error if there was an error, or a positive integer representing the + * amount of bytes parsed from \p str * * \note The pointer that \p data refers to will not be initialized by this * function if parsing fails - * \note \p size will contain the index of \p str where the first invalid data - * was found if parsing fails */ int strtodata(const char* str, char** data, size_t* size); diff --git a/client/readme.md b/client/readme.md index 9d755aa..04471d2 100644 --- a/client/readme.md +++ b/client/readme.md @@ -12,3 +12,10 @@ goal (in order of implementation): ls list connected puzzle modules ``` + +``` +send 0x39 "Hello world!" de:ad:be:ef 0xff 5 0a 0750 + ^~~~~~~~~~~~~~ ^~~~~~~~~~~ ~^~~ ~^ ~^ ~~~~^ + STR_INTP BYTE_ARR UNSIGNED UNSIGNED UNSIGNED UNSIGNED + (hex+0x) (dec) (hex) (oct) +``` -- cgit v1.2.3 From c5295004db5970ce898a91f4147eb7bc1c40a7ed Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Sat, 25 May 2024 19:38:34 +0200 Subject: finish parser scanning part --- client/parse.cpp | 47 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 15 deletions(-) (limited to 'client') diff --git a/client/parse.cpp b/client/parse.cpp index 223dc5d..16a7afc 100644 --- a/client/parse.cpp +++ b/client/parse.cpp @@ -26,6 +26,8 @@ static int parse_str(const char* str, char* data, size_t* size) { for (i = 1; i < len && str[i] != '\0'; i++) { char c = str[i]; + // TODO: handle escaped characters + if (c == closing) { if (scan) printf("string%s of length %d\n", escape ? " (w/ escape)" : "", i - 1); return i + 1; // +1 for closing quote @@ -72,46 +74,61 @@ static int parse_num(const char* str, char* data, size_t* size) { if (base == 10) *size += 1; else if (base == 16) { if (!bytestring) { - *size += (len - i + 1) / 2; - } else { - for (; colon != NULL && colon < str + len; colon = strchr(str, ':')) { + size_t prefixless = len - i; + switch (prefixless) { + case 2: // 8-bit (2 hex characters) + case 4: // 16-bit + case 8: // 32-bit + case 16: // 64-bit + break; + default: + return -i; + } + *size += prefixless / 2; + } else { // if bytestring + size_t c = 0, field = strcspn(str, ifs); // length until end of field + while (c < field) { // count bytes in bytestring + if (strspn(str + c, SET_HEX) != 2) + return -i -c; + c += 2; *size += 1; + + if (str[c] == ':') { + c += 1; + continue; + } + break; } } } } - if (scan) printf("number (base %d%s) of length %lu\n", base, bytestring ? " as bytestring" : "", len - i); - return len; + i += len; + return i; } int strtodata(const char* str, char** data, size_t* size) { const char* ifs = IFS; *size = 0; - size_t i; + size_t i = 0; size_t len = strlen(str); - for (i = 0; i < len;) { + while (i < len) { // skip whitespace - int run; - run = strspn(&str[i], ifs); - if (run > 0) printf("skipping whitespace for %d bytes...\n", run); - i += run; + i += strspn(&str[i], ifs); // end of string if (str[i] == '\0') break; + int run; if ((run = parse_str(str + i, NULL, size)) > 0) { i += run; continue; } if ((run = parse_num(str + i, NULL, size)) > 0) { i += run; continue; } // no format detected return -i + run; } - printf("end of string w/o parse errors\n"); - printf("buffer size is now %lu\n", *size); - exit(0); *data = (char*) malloc(*size); - return 0; + return *size; } -- cgit v1.2.3 From 6cb0ea50e1829c0c6c2e0179d3c6b7573c4a1b24 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Sat, 25 May 2024 20:10:45 +0200 Subject: split up hex string parser and number parser + small refactoring --- client/parse.cpp | 136 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 78 insertions(+), 58 deletions(-) (limited to 'client') diff --git a/client/parse.cpp b/client/parse.cpp index 16a7afc..6eca774 100644 --- a/client/parse.cpp +++ b/client/parse.cpp @@ -5,7 +5,7 @@ #include "parse.h" -static int parse_str(const char* str, char* data, size_t* size) { +static int parse_string(const char* str, char* data, size_t* offset) { char closing = str[0]; char escape = false; bool scan = data == NULL; @@ -28,18 +28,49 @@ static int parse_str(const char* str, char* data, size_t* size) { // TODO: handle escaped characters - if (c == closing) { - if (scan) printf("string%s of length %d\n", escape ? " (w/ escape)" : "", i - 1); + if (c == closing) return i + 1; // +1 for closing quote - } - if (scan) *size += 1; + *offset += 1; } return -i; } -static int parse_num(const char* str, char* data, size_t* size) { +static int parse_hexstr(const char* str, char* data, size_t* offset) { + const char* ifs = IFS; + size_t len = strcspn(str, ifs); + bool scan = data == NULL; + int i = 0; + + // check if token contains at least one colon + const char* colon = strchr(str, ':'); + if (colon == NULL) return -i; + if (colon >= str + len) return -i; + + // check if token only contains allowed characters [0-9a-fA-F:] + size_t len_ok = strspn(str + i, SET_HEX_STR) + i; + if (len != len_ok) return -len_ok; + + size_t c = 0; + while (c < len) { // count bytes in bytestring + if (strspn(str + c, SET_HEX) != 2) + return -i -c; + c += 2; + *offset += 1; + + if (str[c] == ':') { + c += 1; + continue; + } + break; + } + + i += len; + return i; +} + +static int parse_number(const char* str, char* data, size_t* offset) { const char* ifs = IFS; size_t len = strcspn(str, ifs); bool scan = data == NULL; @@ -47,11 +78,7 @@ static int parse_num(const char* str, char* data, size_t* size) { int base = 10; bool bytestring = false; - const char* colon = strchr(str, ':'); - if (colon != NULL && colon < str + len) { // byte string - base = 16; - bytestring = true; - } else if (len > 2 && strncmp(str, "0x", 2) == 0) { // hexadecimal prefix + if (len > 2 && strncmp(str, "0x", 2) == 0) { // hexadecimal prefix base = 16; i += 2; }/* else if (len > 1 && strncmp(str, "0", 1) == 0) { // octal prefix @@ -62,73 +89,66 @@ static int parse_num(const char* str, char* data, size_t* size) { const char* set; // if (base == 8) set = SET_OCT; if (base == 10) set = SET_DEC; - if (base == 16) { - if (bytestring) set = SET_HEX_STR; - else set = SET_HEX; - } + if (base == 16) set = SET_HEX; size_t len_ok = strspn(str + i, set) + i; if (len != len_ok) return -len_ok; - if (scan) { - if (base == 10) *size += 1; - else if (base == 16) { - if (!bytestring) { - size_t prefixless = len - i; - switch (prefixless) { - case 2: // 8-bit (2 hex characters) - case 4: // 16-bit - case 8: // 32-bit - case 16: // 64-bit - break; - default: - return -i; - } - *size += prefixless / 2; - } else { // if bytestring - size_t c = 0, field = strcspn(str, ifs); // length until end of field - while (c < field) { // count bytes in bytestring - if (strspn(str + c, SET_HEX) != 2) - return -i -c; - c += 2; - *size += 1; - - if (str[c] == ':') { - c += 1; - continue; - } - break; - } - } + if (base == 10) *offset += 1; + else if (base == 16) { + size_t prefixless = len - i; + switch (prefixless) { + case 2: // 8-bit (2 hex characters) + case 4: // 16-bit + case 8: // 32-bit + case 16: // 64-bit + break; + default: + return -i; } + *offset += prefixless / 2; } i += len; return i; } -int strtodata(const char* str, char** data, size_t* size) { +static int _strtodata_main(const char* str, char* _data, size_t* offset) { const char* ifs = IFS; - *size = 0; - size_t i = 0; size_t len = strlen(str); + size_t i = 0; + while (i < len) { - // skip whitespace - i += strspn(&str[i], ifs); - // end of string - if (str[i] == '\0') break; + i += strspn(&str[i], ifs); // skip whitespace + if (str[i] == '\0') break; // end of string int run; - if ((run = parse_str(str + i, NULL, size)) > 0) { i += run; continue; } - if ((run = parse_num(str + i, NULL, size)) > 0) { i += run; continue; } + char* data = _data == NULL ? NULL : _data + *offset; + if ((run = parse_string(str + i, data, offset)) > 0) goto format_ok; + if ((run = parse_hexstr(str + i, data, offset)) > 0) goto format_ok; + if ((run = parse_number(str + i, data, offset)) > 0) goto format_ok; + + return -i + run; // no format detected - // no format detected - return -i + run; +format_ok: + i += run; + continue; } - *data = (char*) malloc(*size); + return i; +} + +int strtodata(const char* str, char** data, size_t* size) { + *size = 0; - return *size; + // 1st pass: check data format + int ret = _strtodata_main(str, NULL, size); + if (ret <= 0) return ret; // on error + + // 2nd pass: convert string literals into binary data + *data = (char*) malloc(*size); + size_t written = 0; + return _strtodata_main(str, *data, &written); } -- cgit v1.2.3 From c3491119759462aeb3eed4b39aa34f6f98ab8a4f Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Sun, 26 May 2024 11:15:56 +0200 Subject: WIP convert parse.cpp data into raw data --- client/CMakeLists.txt | 1 + client/cmd.cpp | 6 ++++- client/parse.cpp | 74 ++++++++++++++++++++++++++++++++------------------- client/parse.h | 4 +-- client/xxd.c | 44 ++++++++++++++++++++++++++++++ client/xxd.h | 17 ++++++++++++ 6 files changed, 116 insertions(+), 30 deletions(-) create mode 100644 client/xxd.c create mode 100644 client/xxd.h (limited to 'client') diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 35a55b6..5da93e4 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -14,6 +14,7 @@ add_executable(pbc sock.cpp cmd.cpp parse.cpp + xxd.c ) target_link_libraries(pbc diff --git a/client/cmd.cpp b/client/cmd.cpp index 1ec2cb8..78a6c3c 100644 --- a/client/cmd.cpp +++ b/client/cmd.cpp @@ -5,6 +5,7 @@ #include "cmd.h" #include "sock.h" #include "parse.h" +#include "xxd.h" char* consume_token(char* input, const char* ifs) { strtok(input, ifs); @@ -55,8 +56,11 @@ void cmd_send(char* addr_str) { return; } + printf("char data[%lu = 0x%02lx]:\n", data_size, data_size); + xxd(data, data_size); + // printf("(0x%02x) -> \"%.*s\"\n", addr, data_size, data); - i2c_send(addr, data, data_size); + // i2c_send(addr, data, data_size); free(data); } diff --git a/client/parse.cpp b/client/parse.cpp index 6eca774..5672ff2 100644 --- a/client/parse.cpp +++ b/client/parse.cpp @@ -1,14 +1,13 @@ -#include #include #include #include +#include #include "parse.h" -static int parse_string(const char* str, char* data, size_t* offset) { +static int parse_string(const char * str, char * data, size_t * offset) { char closing = str[0]; char escape = false; - bool scan = data == NULL; int i = 0; size_t len = strlen(str); @@ -23,7 +22,7 @@ static int parse_string(const char* str, char* data, size_t* offset) { return -i; } - for (i = 1; i < len && str[i] != '\0'; i++) { + for (i = 1; i < len && str[i] != '\0'; i++, *offset += 1) { char c = str[i]; // TODO: handle escaped characters @@ -31,16 +30,16 @@ static int parse_string(const char* str, char* data, size_t* offset) { if (c == closing) return i + 1; // +1 for closing quote - *offset += 1; + if (data != NULL) + data[*offset] = c; } return -i; } -static int parse_hexstr(const char* str, char* data, size_t* offset) { +static int parse_hexstr(const char * str, char * data, size_t * offset) { const char* ifs = IFS; size_t len = strcspn(str, ifs); - bool scan = data == NULL; int i = 0; // check if token contains at least one colon @@ -56,6 +55,10 @@ static int parse_hexstr(const char* str, char* data, size_t* offset) { while (c < len) { // count bytes in bytestring if (strspn(str + c, SET_HEX) != 2) return -i -c; + + if (data != NULL) + data[*offset] = strtol(str + c, NULL, 16) & 0xff; + c += 2; *offset += 1; @@ -70,10 +73,9 @@ static int parse_hexstr(const char* str, char* data, size_t* offset) { return i; } -static int parse_number(const char* str, char* data, size_t* offset) { +static int parse_number(const char * str, char * data, size_t * offset) { const char* ifs = IFS; size_t len = strcspn(str, ifs); - bool scan = data == NULL; int i = 0; int base = 10; bool bytestring = false; @@ -94,8 +96,8 @@ static int parse_number(const char* str, char* data, size_t* offset) { size_t len_ok = strspn(str + i, set) + i; if (len != len_ok) return -len_ok; - if (base == 10) *offset += 1; - else if (base == 16) { + size_t size = 1; // default integer size in bytes + if (base == 16) { size_t prefixless = len - i; switch (prefixless) { case 2: // 8-bit (2 hex characters) @@ -106,40 +108,58 @@ static int parse_number(const char* str, char* data, size_t* offset) { default: return -i; } - *offset += prefixless / 2; + size = prefixless / 2; } + if (data != NULL) { + unsigned long number = strtol(str + i, NULL, base); + long long mask = (1 << 8 * size) - 1; + number &= mask; + switch (size) { + case 1: + data[*offset] = number & 0xff; + break; + case 2: + number = htons(number); + data[*offset + 1] = (number) & 0xff; + data[*offset + 0] = (number >>= 8) & 0xff; + break; + case 4: + number = htonl(number); + data[*offset + 3] = (number) & 0xff; + data[*offset + 2] = (number >>= 8) & 0xff; + data[*offset + 1] = (number >>= 8) & 0xff; + data[*offset + 0] = (number >>= 8) & 0xff; + break; + } + } + + *offset += size; i += len; return i; } -static int _strtodata_main(const char* str, char* _data, size_t* offset) { +static int _strtodata_main(const char * str, char* data, size_t * offset) { const char* ifs = IFS; size_t len = strlen(str); - size_t i = 0; - - while (i < len) { + int i, run; + for (i = 0; i < len; i += run) { i += strspn(&str[i], ifs); // skip whitespace if (str[i] == '\0') break; // end of string - int run; - char* data = _data == NULL ? NULL : _data + *offset; - if ((run = parse_string(str + i, data, offset)) > 0) goto format_ok; - if ((run = parse_hexstr(str + i, data, offset)) > 0) goto format_ok; - if ((run = parse_number(str + i, data, offset)) > 0) goto format_ok; - - return -i + run; // no format detected + if ((run = parse_string(str + i, data, offset)) > 0) continue; + if ((run = parse_hexstr(str + i, data, offset)) > 0) continue; + if ((run = parse_number(str + i, data, offset)) > 0) continue; -format_ok: - i += run; - continue; + // no format detected + return -i + run; } return i; } -int strtodata(const char* str, char** data, size_t* size) { +int strtodata(const char * str, char ** data, size_t * size) { *size = 0; // 1st pass: check data format diff --git a/client/parse.h b/client/parse.h index 10274e7..94afe70 100644 --- a/client/parse.h +++ b/client/parse.h @@ -22,7 +22,7 @@ * * \return the remaining data after \p token and the first \p ifs */ -char* consume_token(char* token, const char* ifs); +char* consume_token(char * token, const char * ifs); /** * \brief convert string with literals into raw data @@ -38,5 +38,5 @@ char* consume_token(char* token, const char* ifs); * \note The pointer that \p data refers to will not be initialized by this * function if parsing fails */ -int strtodata(const char* str, char** data, size_t* size); +int strtodata(const char * str, char ** data, size_t * size); diff --git a/client/xxd.c b/client/xxd.c new file mode 100644 index 0000000..06b9960 --- /dev/null +++ b/client/xxd.c @@ -0,0 +1,44 @@ +#include +#include + +#include "parse.h" + +void xxd(const char * data, size_t size) { + size_t fake_size = size + (16 - size % 16) % 16; + + for (size_t base = 0; base < fake_size; base += 16) { + printf("%08lx: ", base); + + // print bytes + for (size_t offset = 0; offset < 16; offset++) { + size_t i = base + offset; + + if (offset == 8) printf(" "); + + if (i >= size) { + printf(" "); + continue; + } + + printf("%02x ", data[size]); + } + + // print ascii representation + printf(" |"); + for (size_t offset = 0; offset < 16; offset++) { + size_t i = base + offset; + + if (i >= size) { + printf(" "); + continue; + } + + if (isprint(data[size])) + printf("%c", data[size]); + else + printf("."); + } + printf("|\n"); + } +} + diff --git a/client/xxd.h b/client/xxd.h new file mode 100644 index 0000000..fb28bb1 --- /dev/null +++ b/client/xxd.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \brief utility function that prints hexdump of data + */ +void xxd(const char * data, size_t size); + +#ifdef __cplusplus +} +#endif + -- cgit v1.2.3 From cad919018ed72005d2bc110247087201b0dea7ab Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Sun, 26 May 2024 11:28:24 +0200 Subject: fix silly typo --- client/cmd.cpp | 8 ++------ client/parse.cpp | 3 +++ client/xxd.c | 8 ++++---- 3 files changed, 9 insertions(+), 10 deletions(-) (limited to 'client') diff --git a/client/cmd.cpp b/client/cmd.cpp index 78a6c3c..a098a14 100644 --- a/client/cmd.cpp +++ b/client/cmd.cpp @@ -5,7 +5,6 @@ #include "cmd.h" #include "sock.h" #include "parse.h" -#include "xxd.h" char* consume_token(char* input, const char* ifs) { strtok(input, ifs); @@ -56,11 +55,8 @@ void cmd_send(char* addr_str) { return; } - printf("char data[%lu = 0x%02lx]:\n", data_size, data_size); - xxd(data, data_size); - - // printf("(0x%02x) -> \"%.*s\"\n", addr, data_size, data); - // i2c_send(addr, data, data_size); + printf("sending char data[%lu = 0x%02lx] to 0x%02x\n", data_size, data_size, addr); + i2c_send(addr, data, data_size); free(data); } diff --git a/client/parse.cpp b/client/parse.cpp index 5672ff2..300df7c 100644 --- a/client/parse.cpp +++ b/client/parse.cpp @@ -115,12 +115,15 @@ static int parse_number(const char * str, char * data, size_t * offset) { unsigned long number = strtol(str + i, NULL, base); long long mask = (1 << 8 * size) - 1; number &= mask; + // NOTE: the hton? functions are used to convert host endianness to network + // endianness (big), and are required switch (size) { case 1: data[*offset] = number & 0xff; break; case 2: number = htons(number); + // TODO: check if the endianness is OK, or reverse these *offset indices* data[*offset + 1] = (number) & 0xff; data[*offset + 0] = (number >>= 8) & 0xff; break; diff --git a/client/xxd.c b/client/xxd.c index 06b9960..5d83635 100644 --- a/client/xxd.c +++ b/client/xxd.c @@ -1,7 +1,7 @@ #include #include -#include "parse.h" +#include "xxd.h" void xxd(const char * data, size_t size) { size_t fake_size = size + (16 - size % 16) % 16; @@ -20,7 +20,7 @@ void xxd(const char * data, size_t size) { continue; } - printf("%02x ", data[size]); + printf("%02x ", data[i] & 0xff); } // print ascii representation @@ -33,8 +33,8 @@ void xxd(const char * data, size_t size) { continue; } - if (isprint(data[size])) - printf("%c", data[size]); + if (isprint(data[i])) + printf("%c", data[i]); else printf("."); } -- cgit v1.2.3 From cd1b5097d6683355bbe2b96add8740ffc738ef8f Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Sun, 26 May 2024 11:45:53 +0200 Subject: fix another silly bug --- client/CMakeLists.txt | 1 + client/parse.cpp | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'client') diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 5da93e4..6aa4b4f 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.29) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) set(CMAKE_EXPORT_COMPILE_COMMANDS 1) +set(CMAKE_BUILD_TYPE Debug) project(puzzlebox_client C CXX) diff --git a/client/parse.cpp b/client/parse.cpp index 300df7c..f31e802 100644 --- a/client/parse.cpp +++ b/client/parse.cpp @@ -138,8 +138,7 @@ static int parse_number(const char * str, char * data, size_t * offset) { } *offset += size; - i += len; - return i; + return len; } static int _strtodata_main(const char * str, char* data, size_t * offset) { -- cgit v1.2.3 From fb37e47bc13912a8d94bf1b3b7aa8456601f317a Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Sun, 26 May 2024 11:51:18 +0200 Subject: update readme --- client/readme.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'client') diff --git a/client/readme.md b/client/readme.md index 04471d2..98cd853 100644 --- a/client/readme.md +++ b/client/readme.md @@ -12,10 +12,16 @@ goal (in order of implementation): ls list connected puzzle modules ``` +## Send data ``` -send 0x39 "Hello world!" de:ad:be:ef 0xff 5 0a 0750 - ^~~~~~~~~~~~~~ ^~~~~~~~~~~ ~^~~ ~^ ~^ ~~~~^ - STR_INTP BYTE_ARR UNSIGNED UNSIGNED UNSIGNED UNSIGNED - (hex+0x) (dec) (hex) (oct) + ADDRESS DATA + v~~~ v~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +send 0x39 68:65:6c:6c:6f 44 0x20 'world' 33 + ^~~~~~~~~~~~~~ ^~ ^~~~ ^~~~~~~ ^~ + HEXSTR NUMBER NUMBER STRING NUMBER + (binary) (dec) (hex) (literal) (dec) ``` + +The data is concatenated, and may contain mixed types of literals + -- cgit v1.2.3 From f4868604384908a7477cbb4b544c6ee7aac2a883 Mon Sep 17 00:00:00 2001 From: lonkaars Date: Mon, 27 May 2024 08:23:21 +0200 Subject: quickly implement some pseudo handlers for the remaining commands --- client/cmd.cpp | 30 ++++++++++++++++++++++++++++++ client/cmd.h | 30 +++++++++++++++--------------- proto/puzbusv1.h | 19 +++++++++++++++++++ shared/busaddr.h | 20 ++++++++++++++++++++ 4 files changed, 84 insertions(+), 15 deletions(-) create mode 100644 shared/busaddr.h (limited to 'client') diff --git a/client/cmd.cpp b/client/cmd.cpp index a098a14..736cf12 100644 --- a/client/cmd.cpp +++ b/client/cmd.cpp @@ -3,9 +3,12 @@ #include #include "cmd.h" +#include "puzbusv1.h" #include "sock.h" #include "parse.h" +#include "../shared/busaddr.h" + char* consume_token(char* input, const char* ifs) { strtok(input, ifs); return strtok(NULL, "\0"); @@ -61,3 +64,30 @@ void cmd_send(char* addr_str) { free(data); } +void cmd_status(char*) { + const char msg[] = { + PB_CMD_READ, + 0x00, // addr 0 = global state + }; + i2c_send(BUSADDR_MAIN, msg, sizeof(msg)); + // NOTE: the reply handler will automatically print the state once it's + // received +} + +void cmd_reset(char*) { + const char msg[] = { + PB_CMD_WRITE, + 0x00, + PB_GS_IDLE, + }; + i2c_send(BUSADDR_MAIN, msg, sizeof(msg)); +} + +void cmd_ls(char*) { + return; + const char msg[] = { + PB_CMD_READ, + // TODO: which address is this? + }; + i2c_send(BUSADDR_MAIN, msg, sizeof(msg)); +} diff --git a/client/cmd.h b/client/cmd.h index 9d20328..30bcbeb 100644 --- a/client/cmd.h +++ b/client/cmd.h @@ -40,21 +40,21 @@ static const struct cmd cmds[] = { .name = "send", .info = "[debug] send raw message", }, - // { - // .handle = cmd_status, - // .name = "status", - // .info = "show global puzzle box state (main controller state)", - // }, - // { - // .handle = cmd_reset, - // .name = "reset", - // .info = "reset entire game state", - // }, - // { - // .handle = cmd_ls, - // .name = "ls", - // .info = "list connected puzzle modules", - // }, + { + .handle = cmd_status, + .name = "status", + .info = "show global puzzle box state (main controller state)", + }, + { + .handle = cmd_reset, + .name = "reset", + .info = "reset entire game state", + }, + { + .handle = cmd_ls, + .name = "ls", + .info = "list connected puzzle modules", + }, }; static const size_t cmds_length = sizeof(cmds) / sizeof(cmds[0]); diff --git a/proto/puzbusv1.h b/proto/puzbusv1.h index 0985b2b..9f4f8e5 100644 --- a/proto/puzbusv1.h +++ b/proto/puzbusv1.h @@ -64,6 +64,25 @@ void pb_read_reset(struct pb_msg * target); */ bool pb_write(const struct pb_msg * target, char ** buf, size_t * buf_sz); +/** + * \brief I^2^C puzzle bus command types + * + * The first byte of a puzzle bus message's data indicates the command type. + */ +enum pb_cmd { + PB_CMD_READ, //!< read a puzzle module property + PB_CMD_WRITE, //!< write to a puzzle module property + // PB_CMD_UPDATE, //!< request an update +}; + +/** \brief Puzzle bus global states */ +enum pb_global_state { + PB_GS_NOINIT, //!< uninitialized (only used by puzzle modules) + PB_GS_IDLE, //!< puzzle not started yet + PB_GS_PLAYING, //!< puzzle actively being solved + PB_GS_SOLVED, //!< puzzle completed +}; + #ifdef __cplusplus } #endif diff --git a/shared/busaddr.h b/shared/busaddr.h new file mode 100644 index 0000000..5879afe --- /dev/null +++ b/shared/busaddr.h @@ -0,0 +1,20 @@ +#pragma once + +/** \file bus address reference */ + +// Adafruit NeoTrellis modules +#define BUSADDR_ADA_NEO_1 0x2E +#define BUSADDR_ADA_NEO_2 0x2F +#define BUSADDR_ADA_NEO_3 0x30 +#define BUSADDR_ADA_NEO_4 0x32 + +// TODO: ??? +#define BUSADDR_MOD_NEOTRELLIS 0 +#define BUSADDR_MOD_SOFTWARE 0 +#define BUSADDR_MOD_HARDWARE 0 +#define BUSADDR_MOD_VAULT 0 +#define BUSADDR_MOD_AUTOMATION 0 + +// main controller +#define BUSADDR_MAIN 0x00 + -- cgit v1.2.3 From 25a4f905a3f93645aee79157f30867b287871163 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Tue, 28 May 2024 11:28:22 +0200 Subject: separate the i2c over tcp from puzzle bus libraries --- client/CMakeLists.txt | 4 +- client/cmd.cpp | 4 +- client/examples/puzbus-hello-world.cpp | 67 ------------------------- client/readme.md | 12 ----- client/sock.cpp | 12 ++--- i2ctcp/i2ctcpv1.c | 54 +++++++++++++++++++++ i2ctcp/i2ctcpv1.h | 71 +++++++++++++++++++++++++++ i2ctcp/include.cmake | 16 ++++++ i2ctcp/lib | 1 + main/CMakeLists.txt | 4 +- main/sock.c | 12 ++--- proto/include.cmake | 16 ------ proto/lib | 1 - proto/puzbusv1.c | 55 --------------------- proto/puzbusv1.h | 89 ---------------------------------- shared/busaddr.h | 20 -------- shared/puzbus.h | 39 +++++++++++++++ 17 files changed, 199 insertions(+), 278 deletions(-) delete mode 100644 client/examples/puzbus-hello-world.cpp create mode 100644 i2ctcp/i2ctcpv1.c create mode 100644 i2ctcp/i2ctcpv1.h create mode 100644 i2ctcp/include.cmake create mode 120000 i2ctcp/lib delete mode 100644 proto/include.cmake delete mode 120000 proto/lib delete mode 100644 proto/puzbusv1.c delete mode 100644 proto/puzbusv1.h delete mode 100644 shared/busaddr.h create mode 100644 shared/puzbus.h (limited to 'client') diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 6aa4b4f..73c703d 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_BUILD_TYPE Debug) project(puzzlebox_client C CXX) -include(../proto/include.cmake) +include(../i2ctcp/include.cmake) add_executable(pbc main.cpp @@ -19,7 +19,7 @@ add_executable(pbc ) target_link_libraries(pbc - puzbus + i2ctcp mpack readline # this is such a common library that I did not bother adding it as a submodule ) diff --git a/client/cmd.cpp b/client/cmd.cpp index 736cf12..a26de13 100644 --- a/client/cmd.cpp +++ b/client/cmd.cpp @@ -3,11 +3,11 @@ #include #include "cmd.h" -#include "puzbusv1.h" +#include "i2ctcpv1.h" #include "sock.h" #include "parse.h" -#include "../shared/busaddr.h" +#include "../shared/puzbus.h" char* consume_token(char* input, const char* ifs) { strtok(input, ifs); diff --git a/client/examples/puzbus-hello-world.cpp b/client/examples/puzbus-hello-world.cpp deleted file mode 100644 index dcc965b..0000000 --- a/client/examples/puzbus-hello-world.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include -#include -#include -#include - -#include "puzbusv1.h" - -int send_message() { - const char* data = "Test message data!"; - struct pb_msg output = { - .addr = 0x39, - .data = (char*) data, - .length = strlen(data), - }; - - char* packed; - size_t size; - if (!pb_write(&output, &packed, &size)) { - printf("error writing!\n"); - return EXIT_FAILURE; - } - - fwrite(packed, sizeof(packed[0]), size, stdout); - fflush(stdout); - - return EXIT_SUCCESS; -} - -int read_message() { - freopen(NULL, "rb", stdin); // allow binary on stdin - struct pb_msg input; - - char buf[4]; // extremely small buffer to test chunked message parsing - size_t bytes = 0; - - while ((bytes = fread(buf, sizeof(buf[0]), sizeof(buf), stdin)) > 0) { - int ret = pb_read(&input, buf, bytes); - - // header read error - if (ret < 0) { - printf("error reading!\n"); - return EXIT_FAILURE; - } - - // continue reading if more bytes needed... - if (ret > 0) continue; - - // message read completely! - printf("address: 0x%02x\n", input.addr); - printf("data: \"%.*s\"\n", input.length, input.data); - free(input.data); - return EXIT_SUCCESS; - } - - // if we reach this point, data was read but it did not contain a complete - // message, and is thus considered a failure - return EXIT_FAILURE; -} - -int main() { - if (!isatty(fileno(stdout))) return send_message(); - if (!isatty(fileno(stdin))) return read_message(); - - printf("please pipe some data in or out to use this program\n"); - return EXIT_SUCCESS; -} - diff --git a/client/readme.md b/client/readme.md index 98cd853..1b4cc34 100644 --- a/client/readme.md +++ b/client/readme.md @@ -1,17 +1,5 @@ # puzzle box client -goal (in order of implementation): -``` -(pbc) help - exit exit pbc - test send a test puzbus message - help show this help - send [debug] send raw message - status show global puzzle box state (main controller state) - reset reset entire game state - ls list connected puzzle modules -``` - ## Send data ``` diff --git a/client/sock.cpp b/client/sock.cpp index f967f64..2d5787d 100644 --- a/client/sock.cpp +++ b/client/sock.cpp @@ -10,7 +10,7 @@ #include -#include "puzbusv1.h" +#include "i2ctcpv1.h" #include "sock.h" #include "rl.h" @@ -72,7 +72,7 @@ void PBSocket::send(const char * buf, size_t buf_sz) { } void PBSocket::sock_task() { - struct pb_msg input; + i2ctcp_msg_t input; while(1) { char buf[80]; @@ -86,11 +86,11 @@ void PBSocket::sock_task() { // skip empty frames if (bytes == 0) continue; - int ret = pb_read(&input, buf, bytes); + int ret = i2ctcp_read(&input, buf, bytes); // header read error if (ret < 0) { - rl_printf("pb_read error!\n"); + rl_printf("i2ctcp_read error!\n"); break; } @@ -106,7 +106,7 @@ void PBSocket::sock_task() { } void i2c_send(uint16_t addr, const char * data, size_t data_size) { - struct pb_msg msg = { + i2ctcp_msg_t msg = { .addr = addr, .data = (char *) data, .length = data_size, @@ -114,7 +114,7 @@ void i2c_send(uint16_t addr, const char * data, size_t data_size) { char* packed; size_t size; - if (!pb_write(&msg, &packed, &size)) return; + if (!i2ctcp_write(&msg, &packed, &size)) return; sock->send(packed, size); } diff --git a/i2ctcp/i2ctcpv1.c b/i2ctcp/i2ctcpv1.c new file mode 100644 index 0000000..36a5dbd --- /dev/null +++ b/i2ctcp/i2ctcpv1.c @@ -0,0 +1,54 @@ +#include +#include + +// MIN() macro +#include + +#include "i2ctcpv1.h" + +int i2ctcp_read(i2ctcp_msg_t * target, const char * buf, size_t buf_sz) { + // a new reader is used per buffer block passed to this function + mpack_reader_t reader; + mpack_reader_init_data(&reader, buf, buf_sz); + + // at start of message + if (target->_rdata == 0) { + // NOTE: The entire start of a message needs to be readable from the buffer + // at this point. When target->addr can be read and target->length is past + // the end of the current buffer block, this function will crash and burn. + // This is a highly unlikely scenario, as i2ctcp_read is called for each + // chunk of a TCP frame, and frames (should) include only one puzzle bus + // message. The check here is kind of optional. + if (buf_sz < 4) return -1; + + target->addr = mpack_expect_u16(&reader); + target->length = target->_rdata = mpack_expect_bin(&reader); + target->data = (char *) malloc(target->length); + } + + // continue reading chunks of target->data until the amount of bytes + // specified in target->length + size_t to_read = MIN(mpack_reader_remaining(&reader, NULL), target->_rdata); + char * data = target->data + target->length - target->_rdata; + mpack_read_bytes(&reader, data, to_read); + target->_rdata -= to_read; + + // if rdata = 0, the message was completely read + return target->_rdata; +} + +void i2ctcp_read_reset(i2ctcp_msg_t * target) { + target->_rdata = 0; +} + +bool i2ctcp_write(const i2ctcp_msg_t * target, char ** buf, size_t * buf_sz) { + mpack_writer_t writer; + mpack_writer_init_growable(&writer, buf, buf_sz); + + mpack_write_u16(&writer, target->addr); + mpack_write_bin(&writer, target->data, target->length); + + // finish writing + return mpack_writer_destroy(&writer) == mpack_ok; +} + diff --git a/i2ctcp/i2ctcpv1.h b/i2ctcp/i2ctcpv1.h new file mode 100644 index 0000000..799b668 --- /dev/null +++ b/i2ctcp/i2ctcpv1.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** \brief I2C over TCP message (v1) */ +struct i2ctcp_msg { + uint16_t addr; //!< I^2^C address + char * data; //!< message content + size_t length; //!< message size + size_t _rdata; //!< \private remaining bytes to read until message is complete +}; +typedef struct i2ctcp_msg i2ctcp_msg_t; + +/** + * \brief Read chunk of input stream, and store resulting message in \p target + * + * This function is called for each chunk of data from an input stream, and + * will parse the next puzzle bus message into \p target. The input stream is + * assumed to only contain messages encoded by \p i2ctcp_write() + * + * \param target pointer to struct that will contain the finished message data + * \param buf pointer to input stream data chunk + * \param buf_sz size of \p buf + * + * \returns Integer representing amount of bytes required to finish message, or + * -1 if the message header could not be read. If this function returns 0, the + * message in \p target is complete. + * + * \note target->data will automatically be allocated by this function, even if + * the message is not fully parsed. This variable must be `free()`d by the + * caller after each complete message to prevent memory leaks. + */ +int i2ctcp_read(i2ctcp_msg_t * target, const char * buf, size_t buf_sz); + +/** + * \brief reset the remaining message data counter + * + * Calling this function has the effect of forcing \c i2ctcp_read() to parse + * the next buffer chunk as the start of a new message. This function may be + * called before reading a TCP frame's data to mitigate any synchronization + * issues arising from earlier corrupt or otherwise malformed messages. + */ +void i2ctcp_read_reset(i2ctcp_msg_t * target); + +/** + * \brief Allocate and write a msgpack-formatted message to \p buf + * + * This function allocates a buffer large enough to fit the message specified + * in \p target, and encodes the data in \p target in a format that can be + * decoded later using \p i2ctcp_read() + * + * \param target pointer to struct that contains the message data + * \param buf pointer to \c char* that will contain the formatted message + * \param buf_sz pointer to \c size_t that will represent the final size of \p buf + * + * \returns boolean true if a the message could be encoded successfully, false + * if there was some kind of error + * + * \note the pointer stored in \p buf must be `free()`d by the caller afterwards + */ +bool i2ctcp_write(const i2ctcp_msg_t * target, char ** buf, size_t * buf_sz); + +#ifdef __cplusplus +} +#endif + diff --git a/i2ctcp/include.cmake b/i2ctcp/include.cmake new file mode 100644 index 0000000..d755b57 --- /dev/null +++ b/i2ctcp/include.cmake @@ -0,0 +1,16 @@ +include_directories(${CMAKE_CURRENT_LIST_DIR}) +add_library(i2ctcp STATIC + ${CMAKE_CURRENT_LIST_DIR}/i2ctcpv1.c + ) + +# mpack +include_directories(${CMAKE_CURRENT_LIST_DIR}/lib/mpack/src/mpack) +add_library(mpack STATIC + ${CMAKE_CURRENT_LIST_DIR}/lib/mpack/src/mpack/mpack-common.c + ${CMAKE_CURRENT_LIST_DIR}/lib/mpack/src/mpack/mpack-expect.c + ${CMAKE_CURRENT_LIST_DIR}/lib/mpack/src/mpack/mpack-node.c + ${CMAKE_CURRENT_LIST_DIR}/lib/mpack/src/mpack/mpack-platform.c + ${CMAKE_CURRENT_LIST_DIR}/lib/mpack/src/mpack/mpack-reader.c + ${CMAKE_CURRENT_LIST_DIR}/lib/mpack/src/mpack/mpack-writer.c + ) + diff --git a/i2ctcp/lib b/i2ctcp/lib new file mode 120000 index 0000000..dc598c5 --- /dev/null +++ b/i2ctcp/lib @@ -0,0 +1 @@ +../lib \ No newline at end of file diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 30685a4..6390d7c 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -7,7 +7,7 @@ set(PICO_BOARD pico_w) include(lib/pico-sdk/pico_sdk_init.cmake) include(lib/FreeRTOS-Kernel/portable/ThirdParty/GCC/RP2040/FreeRTOS_Kernel_import.cmake) -include(../proto/include.cmake) +include(../i2ctcp/include.cmake) project(puzzlebox_main C CXX ASM) @@ -33,7 +33,7 @@ target_link_libraries(main hardware_i2c FreeRTOS-Kernel FreeRTOS-Kernel-Heap4 - puzbus + i2ctcp mpack ) diff --git a/main/sock.c b/main/sock.c index 4f50981..fe932bb 100644 --- a/main/sock.c +++ b/main/sock.c @@ -7,16 +7,16 @@ #include "init.h" #include "config.h" -#include "puzbusv1.h" +#include "i2ctcpv1.h" #include "sock.h" struct netconn* current_connection = NULL; -struct pb_msg recv_msg; +i2ctcp_msg_t recv_msg; void i2c_send(uint16_t addr, const char * data, size_t data_size) { if (current_connection == NULL) return; - struct pb_msg send_msg = { + i2ctcp_msg_t send_msg = { .addr = addr, .data = (char *) data, .length = data_size, @@ -25,7 +25,7 @@ void i2c_send(uint16_t addr, const char * data, size_t data_size) { char * buf; size_t buf_sz; - if (!pb_write(&send_msg, &buf, &buf_sz)) return; + if (!i2ctcp_write(&send_msg, &buf, &buf_sz)) return; // NOTE: netconn does return an error code, but the data needs to be freed // whether netconn throws an error or not, so it remains unused @@ -47,7 +47,7 @@ void i2c_recv(uint16_t addr, const char * data, size_t data_size) { } void recv_handler(struct netconn* conn, struct netbuf* buf) { - pb_read_reset(&recv_msg); + i2ctcp_read_reset(&recv_msg); do { char* data; @@ -55,7 +55,7 @@ void recv_handler(struct netconn* conn, struct netbuf* buf) { netbuf_data(buf, (void**)&data, &len); // continue early if more data is needed to complete message - if (!pb_read(&recv_msg, data, len)) continue; + if (!i2ctcp_read(&recv_msg, data, len)) continue; // forward received message to puzzle bus i2c_recv(recv_msg.addr, recv_msg.data, recv_msg.length); diff --git a/proto/include.cmake b/proto/include.cmake deleted file mode 100644 index ac1305e..0000000 --- a/proto/include.cmake +++ /dev/null @@ -1,16 +0,0 @@ -include_directories(${CMAKE_CURRENT_LIST_DIR}) -add_library(puzbus STATIC - ${CMAKE_CURRENT_LIST_DIR}/puzbusv1.c - ) - -# mpack -include_directories(${CMAKE_CURRENT_LIST_DIR}/lib/mpack/src/mpack) -add_library(mpack STATIC - ${CMAKE_CURRENT_LIST_DIR}/lib/mpack/src/mpack/mpack-common.c - ${CMAKE_CURRENT_LIST_DIR}/lib/mpack/src/mpack/mpack-expect.c - ${CMAKE_CURRENT_LIST_DIR}/lib/mpack/src/mpack/mpack-node.c - ${CMAKE_CURRENT_LIST_DIR}/lib/mpack/src/mpack/mpack-platform.c - ${CMAKE_CURRENT_LIST_DIR}/lib/mpack/src/mpack/mpack-reader.c - ${CMAKE_CURRENT_LIST_DIR}/lib/mpack/src/mpack/mpack-writer.c - ) - diff --git a/proto/lib b/proto/lib deleted file mode 120000 index dc598c5..0000000 --- a/proto/lib +++ /dev/null @@ -1 +0,0 @@ -../lib \ No newline at end of file diff --git a/proto/puzbusv1.c b/proto/puzbusv1.c deleted file mode 100644 index 73deda5..0000000 --- a/proto/puzbusv1.c +++ /dev/null @@ -1,55 +0,0 @@ -#include -#include - -// MIN() macro -#include -// TODO: check if this works on pico as well - -#include "puzbusv1.h" - -int pb_read(struct pb_msg * target, const char * buf, size_t buf_sz) { - // a new reader is used per buffer block passed to this function - mpack_reader_t reader; - mpack_reader_init_data(&reader, buf, buf_sz); - - // at start of message - if (target->_rdata == 0) { - // NOTE: The entire start of a message needs to be readable from the buffer - // at this point. When target->addr can be read and target->length is past - // the end of the current buffer block, this function will crash and burn. - // This is a highly unlikely scenario, as pb_read is called for each chunk - // of a TCP frame, and frames (should) include only one puzzle bus message. - // The check here is kind of optional. - if (buf_sz < 4) return -1; - - target->addr = mpack_expect_u16(&reader); - target->length = target->_rdata = mpack_expect_bin(&reader); - target->data = (char *) malloc(target->length); - } - - // continue reading chunks of target->data until the amount of bytes - // specified in target->length - size_t to_read = MIN(mpack_reader_remaining(&reader, NULL), target->_rdata); - char * data = target->data + target->length - target->_rdata; - mpack_read_bytes(&reader, data, to_read); - target->_rdata -= to_read; - - // if rdata = 0, the message was completely read - return target->_rdata; -} - -void pb_read_reset(struct pb_msg * target) { - target->_rdata = 0; -} - -bool pb_write(const struct pb_msg * target, char ** buf, size_t * buf_sz) { - mpack_writer_t writer; - mpack_writer_init_growable(&writer, buf, buf_sz); - - mpack_write_u16(&writer, target->addr); - mpack_write_bin(&writer, target->data, target->length); - - // finish writing - return mpack_writer_destroy(&writer) == mpack_ok; -} - diff --git a/proto/puzbusv1.h b/proto/puzbusv1.h deleted file mode 100644 index 9f4f8e5..0000000 --- a/proto/puzbusv1.h +++ /dev/null @@ -1,89 +0,0 @@ -#pragma once - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** \brief Puzzle bus message (v1) */ -struct pb_msg { - uint16_t addr; //!< I^2^C address - char * data; //!< message content - size_t length; //!< message size - size_t _rdata; //!< \private remaining bytes to read until message is complete -}; - -/** - * \brief Read chunk of input stream, and store resulting message in \p target - * - * This function is called for each chunk of data from an input stream, and - * will parse the next puzzle bus message into \p target. The input stream is - * assumed to only contain messages encoded by \p pb_write() - * - * \param target pointer to struct that will contain the finished message data - * \param buf pointer to input stream data chunk - * \param buf_sz size of \p buf - * - * \returns Integer representing amount of bytes required to finish message, or - * -1 if the message header could not be read. If this function returns 0, the - * message in \p target is complete. - * - * \note target->data will automatically be allocated by this function, even if - * the message is not fully parsed. This variable must be `free()`d by the - * caller after each complete message to prevent memory leaks. - */ -int pb_read(struct pb_msg * target, const char * buf, size_t buf_sz); - -/** - * \brief reset the remaining message data counter - * - * Calling this function has the effect of forcing \c pb_read() to parse the - * next buffer chunk as the start of a new message. This function may be called - * before reading a TCP frame's data to mitigate any synchronization issues - * arising from earlier corrupt or otherwise malformed messages. - */ -void pb_read_reset(struct pb_msg * target); - -/** - * \brief Allocate and write a msgpack-formatted message to \p buf - * - * This function allocates a buffer large enough to fit the message specified - * in \p target, and encodes the data in \p target in a format that can be - * decoded later using \p pb_read() - * - * \param target pointer to struct that contains the message data - * \param buf pointer to \c char* that will contain the formatted message - * \param buf_sz pointer to \c size_t that will represent the final size of \p buf - * - * \returns boolean true if a the message could be encoded successfully, false - * if there was some kind of error - * - * \note the pointer stored in \p buf must be `free()`d by the caller afterwards - */ -bool pb_write(const struct pb_msg * target, char ** buf, size_t * buf_sz); - -/** - * \brief I^2^C puzzle bus command types - * - * The first byte of a puzzle bus message's data indicates the command type. - */ -enum pb_cmd { - PB_CMD_READ, //!< read a puzzle module property - PB_CMD_WRITE, //!< write to a puzzle module property - // PB_CMD_UPDATE, //!< request an update -}; - -/** \brief Puzzle bus global states */ -enum pb_global_state { - PB_GS_NOINIT, //!< uninitialized (only used by puzzle modules) - PB_GS_IDLE, //!< puzzle not started yet - PB_GS_PLAYING, //!< puzzle actively being solved - PB_GS_SOLVED, //!< puzzle completed -}; - -#ifdef __cplusplus -} -#endif - diff --git a/shared/busaddr.h b/shared/busaddr.h deleted file mode 100644 index 5879afe..0000000 --- a/shared/busaddr.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -/** \file bus address reference */ - -// Adafruit NeoTrellis modules -#define BUSADDR_ADA_NEO_1 0x2E -#define BUSADDR_ADA_NEO_2 0x2F -#define BUSADDR_ADA_NEO_3 0x30 -#define BUSADDR_ADA_NEO_4 0x32 - -// TODO: ??? -#define BUSADDR_MOD_NEOTRELLIS 0 -#define BUSADDR_MOD_SOFTWARE 0 -#define BUSADDR_MOD_HARDWARE 0 -#define BUSADDR_MOD_VAULT 0 -#define BUSADDR_MOD_AUTOMATION 0 - -// main controller -#define BUSADDR_MAIN 0x00 - diff --git a/shared/puzbus.h b/shared/puzbus.h new file mode 100644 index 0000000..59a8867 --- /dev/null +++ b/shared/puzbus.h @@ -0,0 +1,39 @@ +#pragma once + +/** \file bus address reference */ + +// Adafruit NeoTrellis modules +#define BUSADDR_ADA_NEO_1 0x2E +#define BUSADDR_ADA_NEO_2 0x2F +#define BUSADDR_ADA_NEO_3 0x30 +#define BUSADDR_ADA_NEO_4 0x32 + +// TODO: ??? +#define BUSADDR_MOD_NEOTRELLIS 0 +#define BUSADDR_MOD_SOFTWARE 0 +#define BUSADDR_MOD_HARDWARE 0 +#define BUSADDR_MOD_VAULT 0 +#define BUSADDR_MOD_AUTOMATION 0 + +// main controller +#define BUSADDR_MAIN 0x00 + +/** + * \brief puzzle bus command types + * + * The first byte of a puzzle bus message's data indicates the command type. + */ +enum pb_cmd { + PB_CMD_READ, //!< read a puzzle module property + PB_CMD_WRITE, //!< write to a puzzle module property + // PB_CMD_UPDATE, //!< request an update +}; + +/** \brief Puzzle bus global states */ +enum pb_global_state { + PB_GS_NOINIT, //!< uninitialized (only used by puzzle modules) + PB_GS_IDLE, //!< puzzle not started yet + PB_GS_PLAYING, //!< puzzle actively being solved + PB_GS_SOLVED, //!< puzzle completed +}; + -- cgit v1.2.3 From b6abd84b9930ab398f0402058e56a480e80799cc Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Tue, 28 May 2024 11:53:06 +0200 Subject: update readmes --- client/CMakeLists.txt | 3 +++ client/cmd.cpp | 1 + client/cmd.h | 14 ++++++++------ client/readme.md | 22 ++++++++++++++++++++++ readme.md | 39 +++++++++++++++++++++++++++------------ 5 files changed, 61 insertions(+), 18 deletions(-) (limited to 'client') diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 73c703d..57a2447 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -3,7 +3,10 @@ cmake_minimum_required(VERSION 3.29) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) set(CMAKE_EXPORT_COMPILE_COMMANDS 1) + +# enable debug features set(CMAKE_BUILD_TYPE Debug) +add_compile_definitions(DEBUG) project(puzzlebox_client C CXX) diff --git a/client/cmd.cpp b/client/cmd.cpp index a26de13..ab101e9 100644 --- a/client/cmd.cpp +++ b/client/cmd.cpp @@ -91,3 +91,4 @@ void cmd_ls(char*) { }; i2c_send(BUSADDR_MAIN, msg, sizeof(msg)); } + diff --git a/client/cmd.h b/client/cmd.h index 30bcbeb..932f3a2 100644 --- a/client/cmd.h +++ b/client/cmd.h @@ -14,10 +14,10 @@ struct cmd { cmd_fn_t cmd_exit; cmd_fn_t cmd_test; cmd_fn_t cmd_help; -cmd_fn_t cmd_send; cmd_fn_t cmd_status; cmd_fn_t cmd_reset; cmd_fn_t cmd_ls; +cmd_fn_t cmd_send; static const struct cmd cmds[] = { { @@ -35,11 +35,6 @@ static const struct cmd cmds[] = { .name = "help", .info = "show this help", }, - { - .handle = cmd_send, - .name = "send", - .info = "[debug] send raw message", - }, { .handle = cmd_status, .name = "status", @@ -55,6 +50,13 @@ static const struct cmd cmds[] = { .name = "ls", .info = "list connected puzzle modules", }, +#ifdef DEBUG + { + .handle = cmd_send, + .name = "send", + .info = "[debug] send raw message", + }, +#endif }; static const size_t cmds_length = sizeof(cmds) / sizeof(cmds[0]); diff --git a/client/readme.md b/client/readme.md index 1b4cc34..ea3e034 100644 --- a/client/readme.md +++ b/client/readme.md @@ -1,5 +1,27 @@ # puzzle box client +This folder contains the source code for the puzzle box client (pbc). This is a +desktop application that communicates with the main controller over TCP to +send/receive I2C messages. This application is not only used by a +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. + +## 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 + +## Building + +PBC is a standard CMake project, but a [makefile](./makefile) is provided for +convenience (still requires CMake and Ninja are installed). + ## Send data ``` diff --git a/readme.md b/readme.md index 7802f5c..ac703b7 100644 --- a/readme.md +++ b/readme.md @@ -1,25 +1,34 @@ -# puzzle box +# Puzzle box -Avans University of Applied Sciences project puzzle box. +This repository contains the source code for the puzzle framework designed and +implemented during the 2023-2024 run of the Puzzlebox project. This year's run +of the project consists of only software students, and was developed using the +hardware from the 21-22 run of the project. -## tidyness +Improved hardware was designed but not realised during the 22-23 run of the +project. This hardware is recommended for future groups participating in the +project. The software in this repository should be easily portable to various +other microcontrollers, and a recommendation is made in the [design +document](docs/design.adoc). + +## Tidyness Please keep this repository tidy by being aware of the following conventions! -### folder structure +### Folder structure |folder|contains| |-|-| |`/client`|Desktop PC application for controlling the puzzle box |`/docs`|Project documentation in AsciiDoc(tor) format +|`/i2ctcp`|I2C over TCP protocol functions (used by main and client) |`/lib`|Libraries (tracked as [submodules](#submodules)) |`/main`|Main controller (RPi pico) software -|`/proto`|Puzzle bus TCP protocol functions (used by main and client) |`/puzzle/`|Puzzle sources, each puzzle has its own subdirectory -|`/shared`|Auxiliary shared code +|`/shared`|Shared code |`/test`|Unit test framework (currently unutilized) -### code style +### Code style An `.editorconfig` file is provided in this repository. Please install the [EditorConfig](https://editorconfig.org/) plugin for your text editor of choice @@ -28,7 +37,7 @@ to automatically use these. Currently, no linter/formatter is configured for maintaining consistent code style. -## submodules +## Submodules This repository tracks (most) dependencies via git submodules. @@ -40,6 +49,7 @@ git submodule update --init --recursive --depth 1 until your problems go away. + + +## ESP SDK setup -## ESP -1. Install ESP-IDF extension in vscode +1. Install ESP-IDF extension in Visual Studio Code 2. Install using 'express' option 3. Install ESP-IDF v5.2.1 (release version) -4. For windows: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/get-started/windows-setup.html#get-started-windows-first-steps -5. For Linux: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/get-started/linux-macos-setup.html#get-started-linux-macos-first-steps + + Additional help: + - [For windows](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/get-started/windows-setup.html#get-started-windows-first-steps) + - [For Linux](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/get-started/linux-macos-setup.html#get-started-linux-macos-first-steps) + -- cgit v1.2.3