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 From 81cbd31a1a6a0e521ab262492245ec109c25ec8b Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Wed, 29 May 2024 12:09:19 +0200 Subject: sort out completion functions --- client/cmd.cpp | 14 +++++++++++++- client/cmd.h | 33 +++++++++++++++++++++------------ client/parse.cpp | 9 +++------ client/pbc.1 | 0 client/rl.cpp | 25 ++++++++++++++++++++++++- client/rl.h | 2 +- 6 files changed, 62 insertions(+), 21 deletions(-) create mode 100644 client/pbc.1 (limited to 'client') diff --git a/client/cmd.cpp b/client/cmd.cpp index ab101e9..2871daf 100644 --- a/client/cmd.cpp +++ b/client/cmd.cpp @@ -1,9 +1,11 @@ #include #include +#include #include #include "cmd.h" #include "i2ctcpv1.h" +#include "rl.h" #include "sock.h" #include "parse.h" @@ -35,7 +37,8 @@ void cmd_help(char*) { printf( "\n" - "You can also use the TAB key to autocomplete commands\n" + "See man pbc(1) for more info about specific commands\n" + "Hint: you can use the TAB key to autocomplete commands\n" ); } @@ -83,6 +86,15 @@ void cmd_reset(char*) { i2c_send(BUSADDR_MAIN, msg, sizeof(msg)); } +void cmd_skip(char*) { + const char msg[] = { + PB_CMD_WRITE, + 0x00, + PB_GS_SOLVED, + }; + i2c_send(BUSADDR_MAIN, msg, sizeof(msg)); +} + void cmd_ls(char*) { return; const char msg[] = { diff --git a/client/cmd.h b/client/cmd.h index 932f3a2..9c58fb6 100644 --- a/client/cmd.h +++ b/client/cmd.h @@ -2,24 +2,28 @@ #include -typedef void cmd_fn_t(char *); +typedef void cmd_handle_t(char *); +typedef char** cmd_complete_t(const char*, int, int); struct cmd { - void (* handle)(char *); + cmd_handle_t * handle; const char* name; const char* info; - // TODO: tab completion function? + cmd_complete_t * complete; }; +typedef struct cmd cmd_t; -cmd_fn_t cmd_exit; -cmd_fn_t cmd_test; -cmd_fn_t cmd_help; -cmd_fn_t cmd_status; -cmd_fn_t cmd_reset; -cmd_fn_t cmd_ls; -cmd_fn_t cmd_send; +cmd_handle_t cmd_exit; +cmd_handle_t cmd_test; +cmd_handle_t cmd_help; +cmd_complete_t cmd_help_complete; +cmd_handle_t cmd_status; +cmd_handle_t cmd_reset; +cmd_handle_t cmd_ls; +cmd_handle_t cmd_send; +cmd_handle_t cmd_skip; -static const struct cmd cmds[] = { +static const cmd_t cmds[] = { { .handle = cmd_exit, .name = "exit", @@ -43,7 +47,12 @@ static const struct cmd cmds[] = { { .handle = cmd_reset, .name = "reset", - .info = "reset entire game state", + .info = "set game state to 'idle' for one or more puzzle modules", + }, + { + .handle = cmd_skip, + .name = "skip", + .info = "set game state to 'solved' for one or more puzzle modules", }, { .handle = cmd_ls, diff --git a/client/parse.cpp b/client/parse.cpp index f31e802..56d1137 100644 --- a/client/parse.cpp +++ b/client/parse.cpp @@ -38,8 +38,7 @@ static int parse_string(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); + size_t len = strcspn(str, IFS); int i = 0; // check if token contains at least one colon @@ -74,8 +73,7 @@ static int parse_hexstr(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); + size_t len = strcspn(str, IFS); int i = 0; int base = 10; bool bytestring = false; @@ -142,12 +140,11 @@ static int parse_number(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); int i, run; for (i = 0; i < len; i += run) { - i += strspn(&str[i], ifs); // skip whitespace + i += strspn(&str[i], IFS); // skip whitespace if (str[i] == '\0') break; // end of string if ((run = parse_string(str + i, data, offset)) > 0) continue; diff --git a/client/pbc.1 b/client/pbc.1 new file mode 100644 index 0000000..e69de29 diff --git a/client/rl.cpp b/client/rl.cpp index 3f93e99..2fdd356 100644 --- a/client/rl.cpp +++ b/client/rl.cpp @@ -38,6 +38,7 @@ void rl_printf(const char *fmt, ...) { } static void cli_cmd(char* cmd) { + cmd += strspn(cmd, IFS); // skip leading whitespace char* line = consume_token(cmd, IFS); for (size_t i = 0; i < cmds_length; i++) { @@ -66,9 +67,31 @@ static char* rl_completion_entries(const char *text, int state) { return NULL; } +static char** rl_attempted_completion(const char * text, int start, int end) { + // do not suggest filenames + rl_attempted_completion_over = 1; + + // if first word in line buffer -> complete commands from cmds[] + size_t cmd_start = strspn(rl_line_buffer, IFS); + if (start == cmd_start) + return rl_completion_matches(text, rl_completion_entries); + + // else, check specialized completion functions + size_t cmd_len = strcspn(rl_line_buffer + cmd_start, IFS); + for (size_t i = 0; i < cmds_length; i++) { + cmd_t cmd = cmds[i]; + if (cmd.complete == NULL) continue; + if (strncmp(cmd.name, rl_line_buffer + cmd_start, cmd_len) != 0) continue; + return cmd.complete(rl_line_buffer, start, end); + } + + // else, no completion available + return NULL; +} + int cli_main() { char* input = NULL; - rl_completion_entry_function = rl_completion_entries; + rl_attempted_completion_function = rl_attempted_completion; while (1) { if (input != NULL) free(input); diff --git a/client/rl.h b/client/rl.h index 503225f..5e80d1a 100644 --- a/client/rl.h +++ b/client/rl.h @@ -6,5 +6,5 @@ #define CLI_PROMPT "(" COLOR_BOLD "pbc" COLOR_OFF ") " int cli_main(); -void rl_printf(const char *fmt, ...); +void rl_printf(const char * fmt, ...); -- cgit v1.2.3 From 439859f1133fb88e64df31acaa1b2845e36ba348 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Wed, 29 May 2024 15:07:30 +0200 Subject: add documentation + string escape --- client/cmd.cpp | 18 +++++------ client/cmd.h | 18 ++++------- client/parse.cpp | 17 ++++++++-- client/pbc.1 | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+), 24 deletions(-) (limited to 'client') diff --git a/client/cmd.cpp b/client/cmd.cpp index 2871daf..a6242af 100644 --- a/client/cmd.cpp +++ b/client/cmd.cpp @@ -67,15 +67,15 @@ 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_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[] = { diff --git a/client/cmd.h b/client/cmd.h index 9c58fb6..7fefe98 100644 --- a/client/cmd.h +++ b/client/cmd.h @@ -17,7 +17,6 @@ cmd_handle_t cmd_exit; cmd_handle_t cmd_test; cmd_handle_t cmd_help; cmd_complete_t cmd_help_complete; -cmd_handle_t cmd_status; cmd_handle_t cmd_reset; cmd_handle_t cmd_ls; cmd_handle_t cmd_send; @@ -29,21 +28,11 @@ static const cmd_t cmds[] = { .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_status, - .name = "status", - .info = "show global puzzle box state (main controller state)", - }, { .handle = cmd_reset, .name = "reset", @@ -57,7 +46,7 @@ static const cmd_t cmds[] = { { .handle = cmd_ls, .name = "ls", - .info = "list connected puzzle modules", + .info = "list connected puzzle modules and their state", }, #ifdef DEBUG { @@ -65,6 +54,11 @@ static const cmd_t cmds[] = { .name = "send", .info = "[debug] send raw message", }, + { + .handle = cmd_test, + .name = "test", + .info = "[debug] send a test puzbus message", + }, #endif }; static const size_t cmds_length = sizeof(cmds) / sizeof(cmds[0]); diff --git a/client/parse.cpp b/client/parse.cpp index 56d1137..16f0781 100644 --- a/client/parse.cpp +++ b/client/parse.cpp @@ -6,7 +6,6 @@ #include "parse.h" static int parse_string(const char * str, char * data, size_t * offset) { - char closing = str[0]; char escape = false; int i = 0; size_t len = strlen(str); @@ -21,15 +20,27 @@ static int parse_string(const char * str, char * data, size_t * offset) { default: return -i; } + char closing = str[i]; for (i = 1; i < len && str[i] != '\0'; i++, *offset += 1) { char c = str[i]; - // TODO: handle escaped characters - if (c == closing) return i + 1; // +1 for closing quote + if (escape && c == '\\') { + char x = str[i + 1]; + if (x == '0') c = '\0'; + else if (x == 't') c = '\t'; + else if (x == 'n') c = '\n'; + else if (x == 'r') c = '\r'; + else if (x == '\\') c = '\\'; + else if (x == '\"') c = '\"'; + else if (x == '\'') c = '\''; + else break; + i++; + } + if (data != NULL) data[*offset] = c; } diff --git a/client/pbc.1 b/client/pbc.1 index e69de29..f5a2198 100644 --- a/client/pbc.1 +++ b/client/pbc.1 @@ -0,0 +1,94 @@ +\# vim: ft=groff +.de I2C +I\*{2\*}C +.. +.TH pbc 1 +.SH NAME +pbc \- puzzle box client +.SH SYNPOSIS +pbc [port] +.SH DESCRIPTION +Connect to a puzzle box at the IPv4 address specified by \fIaddr\fP and +optionally port specified by \fIport\fP. The default port is 9191. Once +connected, a +.MR readline 3 -based +CLI is started, and commands can be sent. +.SH COMMANDS +.TP +exit +Disconnect from the puzzle box and exit pbc. This command takes no arguments. +.TP +help +Print a list of available commands with descriptions. This command takes no +arguments. +.TP +ls +List all puzzle modules, their state, and the combined state of all puzzle +modules (global state of the main controller). +.TP +reset [mod ...] +Set the main controller or specific puzzle module's global state to \fIidle\fP. +If no modules are specified, the main controller's state is updated. One or +more modules can be specified to update them at once. +.TP +skip [mod ...] +Set the main controller or specific puzzle module's global state to +\fIsolved\fP. If no modules are specified, the main controller's state is +updated. One or more modules can be specified to update them at once. +.SH DEBUG COMMANDS +The commands detailed under this section are only available in version of pbc +compiled with debug support. +.TP +send +Send arbitrary data specified by \fIdata\fP to the +.I2C +address specified by \fIaddr\fP. \fIdata\fP may consist of multiple arguments +separated by IFS, in which case the arguments are concatenated. +.TP +test +Send a test command containing the ASCII string "Hello world!" to +.I2C +address 0x39. This command takes no arguments. +.SH DATA FORMATS +.TP +number +Numbers can be specified as decimal or hexadecimal using a "0x" prefix. All +numbers are unsigned. Decimal literals are always cast to 8-bit integers, while +hexadecimal literals are cast to the smallest type that will fit the specified +number. Numbers are always sent as little endian. + +Examples: 0 123 255 0x10 0x1245 0xdeadBEEF +.TP +hexstr +Hexadecimal string literals are specified by hexadecimal bytes separated by +colons. Each byte must be exactly 2 hexadecimal characters long and followed by +a colon (except for the last byte). The minimum length of a hexstr is 2 bytes, +as it must include at least a single colon. + +Examples: de:ad:be:ef 00:00 +.TP +string +A string literal starts and ends with a single quote. All characters within +this literal are sent as-is, and no escaping is possible. + +Examples: 'Hello world!' 'string' ' hello ' + +When double quotes are used instead of single quotes, the following escape +sequences are recognised and replaced with special characters: + +\\0 -> 0x00 (null) +.br +\\t -> 0x09 (tab) +.br +\\n -> 0x0a (newline) +.br +\\r -> 0x0d (carriage return) +.br +\\\\ -> 0x5c (backslash) +.br +\\" -> 0x22 (double quote) +.br +\\' -> 0x27 (single quote) + +Examples: "Hello world!\\0" "foo\\nbar" + -- cgit v1.2.3 From a9a38d387f98b29b68f97b8a9c6858d2b399caf0 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Wed, 29 May 2024 17:41:41 +0200 Subject: WIP puzzle bus driver rework --- client/CMakeLists.txt | 2 ++ client/cmd.cpp | 3 ++- shared/include.cmake | 5 +++++ shared/pb/bus.h | 18 ++++++++++++++++++ shared/pb/driver.c | 31 ++++++++++++++++++++++++++++++ shared/pb/driver.h | 24 ++++++++++++++++++++++++ shared/pb/types.h | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++ shared/puzbus.h | 39 -------------------------------------- 8 files changed, 134 insertions(+), 40 deletions(-) create mode 100644 shared/include.cmake create mode 100644 shared/pb/bus.h create mode 100644 shared/pb/driver.c create mode 100644 shared/pb/driver.h create mode 100644 shared/pb/types.h delete mode 100644 shared/puzbus.h (limited to 'client') diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 57a2447..50d3cd7 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -11,6 +11,7 @@ add_compile_definitions(DEBUG) project(puzzlebox_client C CXX) include(../i2ctcp/include.cmake) +include(../shared/include.cmake) add_executable(pbc main.cpp @@ -23,6 +24,7 @@ add_executable(pbc target_link_libraries(pbc i2ctcp + 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 index a6242af..b7adfae 100644 --- a/client/cmd.cpp +++ b/client/cmd.cpp @@ -5,11 +5,12 @@ #include "cmd.h" #include "i2ctcpv1.h" +#include "pb/types.h" #include "rl.h" #include "sock.h" #include "parse.h" -#include "../shared/puzbus.h" +#include "pb/bus.h" char* consume_token(char* input, const char* ifs) { strtok(input, ifs); diff --git a/shared/include.cmake b/shared/include.cmake new file mode 100644 index 0000000..f07a78b --- /dev/null +++ b/shared/include.cmake @@ -0,0 +1,5 @@ +include_directories(${CMAKE_CURRENT_LIST_DIR}) +add_library(puzbus STATIC + ${CMAKE_CURRENT_LIST_DIR}/pb/driver.c + ) + diff --git a/shared/pb/bus.h b/shared/pb/bus.h new file mode 100644 index 0000000..6f464c3 --- /dev/null +++ b/shared/pb/bus.h @@ -0,0 +1,18 @@ +#pragma once + +// 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/pb/driver.c b/shared/pb/driver.c new file mode 100644 index 0000000..2552b61 --- /dev/null +++ b/shared/pb/driver.c @@ -0,0 +1,31 @@ +#include "types.h" +#include "driver.h" + +__weak bool pbdrv_hook_cmd() { + return false; +} + +__weak void pbdrv_i2c_recv(uint16_t addr, const char * buf, size_t sz) { + if (sz == 0) return; + enum pb_cmd cmd = (enum pb_cmd) buf[0]; + + // shift buffer pointer to only contain the puzzle bus message buf + buf++; + sz--; + + // allow user to override command handler while still using this weak + // function + if (pbdrv_hook_cmd(cmd, buf, sz)) return; + + switch (cmd) { + case PB_CMD_READ: return pbdrv_handle_read(buf, sz); + // case PB_CMD_WRITE: return pbdrv_handle_write(buf, sz); + // case PB_CMD_MAGIC: return pbdrv_handle_magic(buf, sz); + default: return; + } +} + +__weak void pbdrv_i2c_send(uint16_t addr, const char * buf, size_t sz) { + return; +} + diff --git a/shared/pb/driver.h b/shared/pb/driver.h new file mode 100644 index 0000000..1c9c3a3 --- /dev/null +++ b/shared/pb/driver.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include + +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void pbdrv_i2c_recv(uint16_t addr, const char * buf, size_t sz); +void pbdrv_i2c_send(uint16_t addr, const char * buf, size_t sz); + +void pbdrv_hook_state(enum pb_global_state * state, bool rw); +bool pbdrv_hook_cmd(enum pb_cmd cmd, const char * buf, size_t sz); + +void pbdrv_handle_read(const char * buf, size_t sz); + +#ifdef __cplusplus +} +#endif + diff --git a/shared/pb/types.h b/shared/pb/types.h new file mode 100644 index 0000000..8150194 --- /dev/null +++ b/shared/pb/types.h @@ -0,0 +1,52 @@ +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __GNUC__ +#define __packed __attribute__((packed)) +#define __weak __attribute__((weak)) +#endif +#ifndef __packed +#error Could not determine packed attribute for current compiler +#define __packed +#endif +#ifndef __weak +#error Could not determine weak attribute for current compiler +#define __weak +#endif + +/** + * \brief puzzle bus command types + * + * The first byte of a puzzle bus message's data indicates the command type. + */ +enum __packed 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 __packed 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 +}; + +typedef struct __packed { + uint8_t address; +} pb_cmd_read_t; + +typedef struct __packed { + uint8_t address; + uint8_t data[]; +} pb_cmd_write_t; + +#ifdef __cplusplus +} +#endif + diff --git a/shared/puzbus.h b/shared/puzbus.h deleted file mode 100644 index 59a8867..0000000 --- a/shared/puzbus.h +++ /dev/null @@ -1,39 +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 - -/** - * \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 d1f5291f82f5e13db756adc919883e310cc537de Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Thu, 30 May 2024 14:02:51 +0200 Subject: implement message dumping --- client/cmd.cpp | 53 ++++++++++++++++++++++++++++++++++++++++++----------- client/cmd.h | 9 ++++++++- client/rl.cpp | 14 +++++++++++++- client/rl.h | 1 + client/sock.cpp | 13 ++++++++++++- 5 files changed, 76 insertions(+), 14 deletions(-) (limited to 'client') diff --git a/client/cmd.cpp b/client/cmd.cpp index b7adfae..e3c9eb9 100644 --- a/client/cmd.cpp +++ b/client/cmd.cpp @@ -43,7 +43,7 @@ void cmd_help(char*) { ); } -void cmd_send(char* addr_str) { +void cmd_send(char * addr_str) { char* data_str = consume_token(addr_str, IFS); char* end; @@ -68,16 +68,6 @@ 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, @@ -105,3 +95,44 @@ void cmd_ls(char*) { i2c_send(BUSADDR_MAIN, msg, sizeof(msg)); } +extern bool i2c_dump_send; +extern bool i2c_dump_recv; +const char * dump_modes[] = { + "none", + "send", + "recv", + "both", + NULL, +}; +void cmd_dump(char * mode) { + consume_token(mode, IFS); + mode += strspn(mode, IFS); + + for (int i = 0; dump_modes[i] != NULL; i++) { + if (strcmp(mode, dump_modes[i]) == 0) { + i2c_dump_send = (i >> 0) & 1; + i2c_dump_recv = (i >> 1) & 1; + return; + } + } + + printf("mode \"%s\" unknown\n", mode); +} +char** cmd_dump_complete(const char * text, int begin, int end) { + int word = rl_word(rl_line_buffer, begin); + if (word != 1) return NULL; + + return rl_completion_matches(text, [](const char * text, int state) -> char * { + static size_t i = 0; + if (state == 0) i = 0; + + while (dump_modes[i] != NULL) { + const char * mode = dump_modes[i++]; + if (strncmp(text, mode, strlen(text)) == 0) + return strdup(mode); + } + return NULL; + }); + + return NULL; +} diff --git a/client/cmd.h b/client/cmd.h index 7fefe98..961ef89 100644 --- a/client/cmd.h +++ b/client/cmd.h @@ -16,11 +16,12 @@ typedef struct cmd cmd_t; cmd_handle_t cmd_exit; cmd_handle_t cmd_test; cmd_handle_t cmd_help; -cmd_complete_t cmd_help_complete; cmd_handle_t cmd_reset; cmd_handle_t cmd_ls; cmd_handle_t cmd_send; cmd_handle_t cmd_skip; +cmd_handle_t cmd_dump; +cmd_complete_t cmd_dump_complete; static const cmd_t cmds[] = { { @@ -59,6 +60,12 @@ static const cmd_t cmds[] = { .name = "test", .info = "[debug] send a test puzbus message", }, + { + .handle = cmd_dump, + .name = "dump", + .info = "[debug] dump sent or received messages", + .complete = cmd_dump_complete, + }, #endif }; static const size_t cmds_length = sizeof(cmds) / sizeof(cmds[0]); diff --git a/client/rl.cpp b/client/rl.cpp index 2fdd356..b8113aa 100644 --- a/client/rl.cpp +++ b/client/rl.cpp @@ -82,7 +82,7 @@ static char** rl_attempted_completion(const char * text, int start, int end) { cmd_t cmd = cmds[i]; if (cmd.complete == NULL) continue; if (strncmp(cmd.name, rl_line_buffer + cmd_start, cmd_len) != 0) continue; - return cmd.complete(rl_line_buffer, start, end); + return cmd.complete(text, start, end); } // else, no completion available @@ -107,3 +107,15 @@ int cli_main() { return EXIT_SUCCESS; } +int rl_word(const char * line, int cursor) { + int word = -1; + for (int i = 0; line[i] != '\0';) { + i += strspn(line + i, IFS); + int len = strcspn(line + i, IFS); + word++; + i += len; + if (i > cursor) break; + } + return word; +} + diff --git a/client/rl.h b/client/rl.h index 5e80d1a..c3bf2c7 100644 --- a/client/rl.h +++ b/client/rl.h @@ -7,4 +7,5 @@ int cli_main(); void rl_printf(const char * fmt, ...); +int rl_word(const char * line, int cursor); diff --git a/client/sock.cpp b/client/sock.cpp index 2d5787d..f5bd564 100644 --- a/client/sock.cpp +++ b/client/sock.cpp @@ -13,6 +13,7 @@ #include "i2ctcpv1.h" #include "sock.h" #include "rl.h" +#include "xxd.h" using std::logic_error; using std::thread; @@ -105,6 +106,9 @@ void PBSocket::sock_task() { sock_close(); } +bool i2c_dump_send = false; +bool i2c_dump_recv = true; + void i2c_send(uint16_t addr, const char * data, size_t data_size) { i2ctcp_msg_t msg = { .addr = addr, @@ -117,9 +121,16 @@ void i2c_send(uint16_t addr, const char * data, size_t data_size) { if (!i2ctcp_write(&msg, &packed, &size)) return; sock->send(packed, size); + if (i2c_dump_send) { + printf("[i2c send] data(0x%02lx):\n", data_size); + xxd(data, 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); + if (i2c_dump_recv) { + printf("[i2c recv] data(0x%02lx):\n", data_size); + xxd(data, data_size); + } } -- cgit v1.2.3 From b231e9d808f40aef0895787ea09624787b10addd Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Thu, 30 May 2024 15:51:08 +0200 Subject: more client i2c shuffling --- client/CMakeLists.txt | 2 +- client/cmd.cpp | 3 +- client/i2c.cpp | 37 ++++++++++++++++ client/i2c.h | 8 ++++ client/sock.cpp | 31 +------------ client/sock.h | 3 -- shared/include.cmake | 2 +- shared/pb/driver.c | 113 ----------------------------------------------- shared/pb/driver.h | 62 -------------------------- shared/pb/moddrv.c | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++ shared/pb/moddrv.h | 62 ++++++++++++++++++++++++++ 11 files changed, 229 insertions(+), 212 deletions(-) create mode 100644 client/i2c.cpp create mode 100644 client/i2c.h delete mode 100644 shared/pb/driver.c delete mode 100644 shared/pb/driver.h create mode 100644 shared/pb/moddrv.c create mode 100644 shared/pb/moddrv.h (limited to 'client') diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 50d3cd7..cb891a6 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -20,11 +20,11 @@ add_executable(pbc cmd.cpp parse.cpp xxd.c + i2c.cpp ) target_link_libraries(pbc i2ctcp - 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 index e3c9eb9..5ac2ff3 100644 --- a/client/cmd.cpp +++ b/client/cmd.cpp @@ -4,10 +4,9 @@ #include #include "cmd.h" -#include "i2ctcpv1.h" #include "pb/types.h" #include "rl.h" -#include "sock.h" +#include "i2c.h" #include "parse.h" #include "pb/bus.h" diff --git a/client/i2c.cpp b/client/i2c.cpp new file mode 100644 index 0000000..fd29e1e --- /dev/null +++ b/client/i2c.cpp @@ -0,0 +1,37 @@ +#include +#include + +#include "i2ctcpv1.h" +#include "sock.h" +#include "xxd.h" + +bool i2c_dump_send = false; +bool i2c_dump_recv = true; + +void i2c_send(uint16_t addr, const char * data, size_t data_size) { + i2ctcp_msg_t msg = { + .addr = addr, + .data = (char *) data, + .length = data_size, + }; + + char* packed; + size_t size; + if (!i2ctcp_write(&msg, &packed, &size)) return; + + sock->send(packed, size); + if (i2c_dump_send) { + printf("[%s] addr(0x%02x) data(0x%02lx):\n", __FUNCTION__, addr, data_size); + xxd(data, data_size); + } + + free(packed); +} + +void i2c_recv(uint16_t addr, const char * data, size_t data_size) { + if (i2c_dump_recv) { + printf("[%s] addr(0x%02x) data(0x%02lx):\n", __FUNCTION__, addr, data_size); + xxd(data, data_size); + } +} + diff --git a/client/i2c.h b/client/i2c.h new file mode 100644 index 0000000..f9f58f9 --- /dev/null +++ b/client/i2c.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +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/client/sock.cpp b/client/sock.cpp index f5bd564..638bf9d 100644 --- a/client/sock.cpp +++ b/client/sock.cpp @@ -7,13 +7,12 @@ #include #include #include - #include #include "i2ctcpv1.h" #include "sock.h" #include "rl.h" -#include "xxd.h" +#include "i2c.h" using std::logic_error; using std::thread; @@ -106,31 +105,3 @@ void PBSocket::sock_task() { sock_close(); } -bool i2c_dump_send = false; -bool i2c_dump_recv = true; - -void i2c_send(uint16_t addr, const char * data, size_t data_size) { - i2ctcp_msg_t msg = { - .addr = addr, - .data = (char *) data, - .length = data_size, - }; - - char* packed; - size_t size; - if (!i2ctcp_write(&msg, &packed, &size)) return; - - sock->send(packed, size); - if (i2c_dump_send) { - printf("[i2c send] data(0x%02lx):\n", data_size); - xxd(data, data_size); - } -} - -void i2c_recv(uint16_t addr, const char * data, size_t data_size) { - if (i2c_dump_recv) { - printf("[i2c recv] data(0x%02lx):\n", data_size); - xxd(data, data_size); - } -} - diff --git a/client/sock.h b/client/sock.h index 42eba3b..0dee09e 100644 --- a/client/sock.h +++ b/client/sock.h @@ -29,6 +29,3 @@ private: extern PBSocket* sock; -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/shared/include.cmake b/shared/include.cmake index f07a78b..c4b01c2 100644 --- a/shared/include.cmake +++ b/shared/include.cmake @@ -1,5 +1,5 @@ include_directories(${CMAKE_CURRENT_LIST_DIR}) add_library(puzbus STATIC - ${CMAKE_CURRENT_LIST_DIR}/pb/driver.c + ${CMAKE_CURRENT_LIST_DIR}/pb/moddrv.c ) diff --git a/shared/pb/driver.c b/shared/pb/driver.c deleted file mode 100644 index f43d5c1..0000000 --- a/shared/pb/driver.c +++ /dev/null @@ -1,113 +0,0 @@ -#include - -#include "types.h" -#include "driver.h" - -/** \brief [private] placeholder global state variable */ -static pb_state_t _global_state = PB_GS_NOINIT; - -/** \brief [private] main controller global state */ -static pb_state_t _main_state = PB_GS_NOINIT; - -__weak pb_state_t pbdrv_hook_mod_state_read() { - return _global_state; -} - -__weak void pbdrv_hook_mod_state_write(pb_state_t state) { - _global_state = state; -} - -__weak void pbdrv_hook_main_state_update(pb_state_t state) { } - -__weak bool pbdrv_hook_cmd(uint16_t i2c_addr, pb_cmd_t cmd, const char * buf, size_t sz) { - return false; -} - -__weak void pbdrv_i2c_recv(uint16_t i2c_addr, const char * buf, size_t sz) { - if (sz == 0) return; - pb_cmd_t cmd = (enum pb_cmd) buf[0]; - - // shift buffer pointer to only contain the puzzle bus message buf - buf++; - sz--; - - // allow user to implement custom commands - if (pbdrv_hook_cmd(i2c_addr, cmd, buf, sz)) - return; - - switch (cmd) { - case PB_CMD_READ: return pbdrv_handle_read(i2c_addr, buf, sz); - case PB_CMD_WRITE: return pbdrv_handle_write(i2c_addr, buf, sz); - case PB_CMD_MAGIC: return pbdrv_handle_magic(i2c_addr, buf, sz); - case PB_CMD_SEX: return pbdrv_handle_sex(i2c_addr, buf, sz); - default: return; - } -} - -__weak void pbdrv_handle_read(uint16_t i2c_addr, const char * buf, size_t sz) { - if (sz == 0) return; - pb_cmd_read_t * cmd = (pb_cmd_read_t *) buf; - - // allow user to addrimplement custom read handlers - if (pbdrv_hook_read(i2c_addr, cmd->address)) - return; - - switch (cmd->address) { - case PB_ADDR_GS: { - char res[] = { - PB_CMD_READ, - PB_ADDR_GS, - pbdrv_hook_mod_state_read(), - }; - return pbdrv_i2c_send(i2c_addr, res, sizeof(res)); - } - default: return; - } -} - -__weak void pbdrv_handle_write(uint16_t i2c_addr, const char * buf, size_t sz) { - if (sz < 2) return; // must have address and at least 1 byte data - pb_cmd_write_t * cmd = (pb_cmd_write_t *) buf; - - // allow user to implement custom read handlers - if (pbdrv_hook_write(i2c_addr, cmd->address, (char *) cmd->data, sz - 1)) - return; - - switch (cmd->address) { - case PB_ADDR_GS: - pbdrv_hook_mod_state_write(cmd->data[0]); - break; - default: return; - } -} - -__weak void pbdrv_handle_magic(uint16_t i2c_addr, const char * buf, size_t sz) { - if (sz != sizeof(pb_magic_msg)) return; - if (memcmp(buf, pb_magic_msg, sizeof(pb_magic_msg)) != 0) return; - - size_t res_size = sizeof(pb_cmd_t) + sizeof(pb_magic_res); - char res[res_size]; - res[0] = PB_CMD_MAGIC; - memcpy(res, pb_magic_res, sizeof(pb_magic_res)); - - pbdrv_i2c_send(i2c_addr, res, res_size); -} - -__weak void pbdrv_handle_sex(uint16_t i2c_addr, const char * buf, size_t sz) { - if (sz == 0) return; - pb_cmd_sex_t * cmd = (pb_cmd_sex_t *) buf; - - // send own state - char res[] = { - PB_CMD_SEX, - pbdrv_hook_mod_state_read(), - }; - pbdrv_i2c_send(i2c_addr, res, sizeof(res)); - - if (cmd->main_state == _main_state) return; - // keep main controller state - _main_state = cmd->main_state; - // call update if main state changed - pbdrv_hook_main_state_update(_main_state); -} - diff --git a/shared/pb/driver.h b/shared/pb/driver.h deleted file mode 100644 index c4e1167..0000000 --- a/shared/pb/driver.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -/** - * \file puzzle bus driver implementation - * - * Most \c pbdrv_* functions have a weak implementation, which may be - * overwritten by a custom implementation. This allows you to use the default - * implementation where possible, and only implement extensions required for - * your puzzle module. Please see spec.adoc for more information about how to - * use the puzzle bus driver library. - */ - -#include -#include -#include - -#include "types.h" - -#ifndef PBDRV_MOD_NAME -#define PBDRV_MOD_NAME "???" -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -void pbdrv_i2c_recv(uint16_t i2c_addr, const char * buf, size_t sz); -void pbdrv_i2c_send(uint16_t i2c_addr, const char * buf, size_t sz); - -pb_state_t pbdrv_hook_mod_state_read(); -void pbdrv_hook_mod_state_write(pb_state_t state); -void pbdrv_hook_main_state_update(pb_state_t state); - -/** - * \name hooks - * - * Implementing this function allows you to use the weak implementation of \c - * pbdrv_i2c_recv() while being able to implement custom command handlers. - * - * \return true if the cmd was recognized, or false to forward the command to - * the default handlers - * - * \{ - */ - -/** \brief cmd receive hook */ -bool pbdrv_hook_cmd(uint16_t i2c_addr, pb_cmd_t cmd, const char * buf, size_t sz); -/** \brief read cmd hook */ -bool pbdrv_hook_read(uint16_t i2c_addr, uint8_t addr); -/** \brief write cmd hook */ -bool pbdrv_hook_write(uint16_t i2c_addr, uint8_t addr, const char * buf, size_t sz); -//! \} - -void pbdrv_handle_read(uint16_t i2c_addr, const char * buf, size_t sz); -void pbdrv_handle_write(uint16_t i2c_addr, const char * buf, size_t sz); -void pbdrv_handle_magic(uint16_t i2c_addr, const char * buf, size_t sz); -void pbdrv_handle_sex(uint16_t i2c_addr, const char * buf, size_t sz); - -#ifdef __cplusplus -} -#endif - diff --git a/shared/pb/moddrv.c b/shared/pb/moddrv.c new file mode 100644 index 0000000..4c897e0 --- /dev/null +++ b/shared/pb/moddrv.c @@ -0,0 +1,118 @@ +#include + +#include "types.h" +#include "moddrv.h" + +/** \brief [private] placeholder global state variable */ +static pb_state_t _global_state = PB_GS_NOINIT; + +/** \brief [private] main controller global state */ +static pb_state_t _main_state = PB_GS_NOINIT; + +__weak pb_state_t pbdrv_hook_mod_state_read() { + return _global_state; +} + +__weak void pbdrv_hook_mod_state_write(pb_state_t state) { + _global_state = state; +} + +__weak void pbdrv_i2c_recv(uint16_t i2c_addr, const char * buf, size_t sz) { + if (sz == 0) return; + pb_cmd_t cmd = (enum pb_cmd) buf[0]; + + // shift buffer pointer to only contain the puzzle bus message buf + buf++; + sz--; + + // allow user to implement custom commands + if (pbdrv_hook_cmd(i2c_addr, cmd, buf, sz)) + return; + + switch (cmd) { + case PB_CMD_READ: return pbdrv_handle_read(i2c_addr, buf, sz); + case PB_CMD_WRITE: return pbdrv_handle_write(i2c_addr, buf, sz); + case PB_CMD_MAGIC: return pbdrv_handle_magic(i2c_addr, buf, sz); + case PB_CMD_SEX: return pbdrv_handle_sex(i2c_addr, buf, sz); + default: return; + } +} + +__weak void pbdrv_handle_read(uint16_t i2c_addr, const char * buf, size_t sz) { + if (sz == 0) return; + pb_cmd_read_t * cmd = (pb_cmd_read_t *) buf; + + // allow user to addrimplement custom read handlers + if (pbdrv_hook_read(i2c_addr, cmd->address)) + return; + + switch (cmd->address) { + case PB_ADDR_GS: { + char res[] = { + PB_CMD_READ, + PB_ADDR_GS, + pbdrv_hook_mod_state_read(), + }; + return pbdrv_i2c_send(i2c_addr, res, sizeof(res)); + } + default: return; + } +} + +__weak void pbdrv_handle_write(uint16_t i2c_addr, const char * buf, size_t sz) { + if (sz < 2) return; // must have address and at least 1 byte data + pb_cmd_write_t * cmd = (pb_cmd_write_t *) buf; + + // allow user to implement custom read handlers + if (pbdrv_hook_write(i2c_addr, cmd->address, (char *) cmd->data, sz - 1)) + return; + + switch (cmd->address) { + case PB_ADDR_GS: + pbdrv_hook_mod_state_write(cmd->data[0]); + break; + default: return; + } +} + +__weak void pbdrv_handle_magic(uint16_t i2c_addr, const char * buf, size_t sz) { + if (sz != sizeof(pb_magic_msg)) return; + if (memcmp(buf, pb_magic_msg, sizeof(pb_magic_msg)) != 0) return; + + size_t res_size = sizeof(pb_cmd_t) + sizeof(pb_magic_res); + char res[res_size]; + res[0] = PB_CMD_MAGIC; + memcpy(res, pb_magic_res, sizeof(pb_magic_res)); + + pbdrv_i2c_send(i2c_addr, res, res_size); +} + +__weak void pbdrv_handle_sex(uint16_t i2c_addr, const char * buf, size_t sz) { + if (sz == 0) return; + pb_cmd_sex_t * cmd = (pb_cmd_sex_t *) buf; + + // send own state + char res[] = { + PB_CMD_SEX, + pbdrv_hook_mod_state_read(), + }; + pbdrv_i2c_send(i2c_addr, res, sizeof(res)); + + if (cmd->main_state == _main_state) return; + // keep main controller state + _main_state = cmd->main_state; + // call update if main state changed + pbdrv_hook_main_state_update(_main_state); +} + +__weak void pbdrv_hook_main_state_update(pb_state_t state) { } +__weak bool pbdrv_hook_cmd(uint16_t i2c_addr, pb_cmd_t cmd, const char * buf, size_t sz) { + return false; +} +__weak bool pbdrv_hook_read(uint16_t i2c_addr, uint8_t addr) { + return false; +} +__weak bool pbdrv_hook_write(uint16_t i2c_addr, uint8_t addr, const char * buf, size_t sz) { + return false; +} + diff --git a/shared/pb/moddrv.h b/shared/pb/moddrv.h new file mode 100644 index 0000000..c4e1167 --- /dev/null +++ b/shared/pb/moddrv.h @@ -0,0 +1,62 @@ +#pragma once + +/** + * \file puzzle bus driver implementation + * + * Most \c pbdrv_* functions have a weak implementation, which may be + * overwritten by a custom implementation. This allows you to use the default + * implementation where possible, and only implement extensions required for + * your puzzle module. Please see spec.adoc for more information about how to + * use the puzzle bus driver library. + */ + +#include +#include +#include + +#include "types.h" + +#ifndef PBDRV_MOD_NAME +#define PBDRV_MOD_NAME "???" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +void pbdrv_i2c_recv(uint16_t i2c_addr, const char * buf, size_t sz); +void pbdrv_i2c_send(uint16_t i2c_addr, const char * buf, size_t sz); + +pb_state_t pbdrv_hook_mod_state_read(); +void pbdrv_hook_mod_state_write(pb_state_t state); +void pbdrv_hook_main_state_update(pb_state_t state); + +/** + * \name hooks + * + * Implementing this function allows you to use the weak implementation of \c + * pbdrv_i2c_recv() while being able to implement custom command handlers. + * + * \return true if the cmd was recognized, or false to forward the command to + * the default handlers + * + * \{ + */ + +/** \brief cmd receive hook */ +bool pbdrv_hook_cmd(uint16_t i2c_addr, pb_cmd_t cmd, const char * buf, size_t sz); +/** \brief read cmd hook */ +bool pbdrv_hook_read(uint16_t i2c_addr, uint8_t addr); +/** \brief write cmd hook */ +bool pbdrv_hook_write(uint16_t i2c_addr, uint8_t addr, const char * buf, size_t sz); +//! \} + +void pbdrv_handle_read(uint16_t i2c_addr, const char * buf, size_t sz); +void pbdrv_handle_write(uint16_t i2c_addr, const char * buf, size_t sz); +void pbdrv_handle_magic(uint16_t i2c_addr, const char * buf, size_t sz); +void pbdrv_handle_sex(uint16_t i2c_addr, const char * buf, size_t sz); + +#ifdef __cplusplus +} +#endif + -- cgit v1.2.3 From 47bbee8b8a5d7eae452b76f58f30d6a70d265ebe Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Thu, 30 May 2024 17:48:27 +0200 Subject: fix wild bug --- client/CMakeLists.txt | 1 - client/sock.cpp | 1 + i2ctcp/i2ctcpv1.c | 28 +++++++++++++++++----------- i2ctcp/include.cmake | 3 +++ 4 files changed, 21 insertions(+), 12 deletions(-) (limited to 'client') diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index cb891a6..d838266 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -29,4 +29,3 @@ target_link_libraries(pbc readline # this is such a common library that I did not bother adding it as a submodule ) - diff --git a/client/sock.cpp b/client/sock.cpp index 638bf9d..95a3685 100644 --- a/client/sock.cpp +++ b/client/sock.cpp @@ -73,6 +73,7 @@ void PBSocket::send(const char * buf, size_t buf_sz) { void PBSocket::sock_task() { i2ctcp_msg_t input; + i2ctcp_read_reset(&input); while(1) { char buf[80]; diff --git a/i2ctcp/i2ctcpv1.c b/i2ctcp/i2ctcpv1.c index 36a5dbd..b406d8d 100644 --- a/i2ctcp/i2ctcpv1.c +++ b/i2ctcp/i2ctcpv1.c @@ -7,10 +7,6 @@ #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 @@ -21,17 +17,27 @@ int i2ctcp_read(i2ctcp_msg_t * target, const char * buf, size_t buf_sz) { // message. The check here is kind of optional. if (buf_sz < 4) return -1; + // mpack reader is used for the first buffer block, as it contains the data + // size info + mpack_reader_t reader; + mpack_reader_init_data(&reader, buf, buf_sz); + 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; + // read remaining data in (header) packet + size_t to_read = mpack_reader_remaining(&reader, NULL); + mpack_read_bytes(&reader, target->data, to_read); + target->_rdata -= to_read; + } else { + // continue reading chunks of target->data until the amount of bytes + // specified in target->length + size_t to_read = MIN(buf_sz, target->_rdata); + char * data = target->data + target->length - target->_rdata; + memcpy(data, buf, to_read); + target->_rdata -= to_read; + } // if rdata = 0, the message was completely read return target->_rdata; diff --git a/i2ctcp/include.cmake b/i2ctcp/include.cmake index d755b57..b61b2a4 100644 --- a/i2ctcp/include.cmake +++ b/i2ctcp/include.cmake @@ -14,3 +14,6 @@ add_library(mpack STATIC ${CMAKE_CURRENT_LIST_DIR}/lib/mpack/src/mpack/mpack-writer.c ) +# causes some wild crashes, please leave off +add_compile_definitions(MPACK_READ_TRACKING=0) + -- cgit v1.2.3 From 18d06c79b9f6a625eb218a15c8216556fb99dc02 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Thu, 30 May 2024 18:58:13 +0200 Subject: WIP parsing the connected puzzle module list --- client/i2c.cpp | 30 ++++++++++++++++++++++++++++++ client/readme.md | 10 ++++++++++ shared/pb/mod/main.h | 13 +++++++++++++ shared/pb/moddrv.c | 14 +++++++------- shared/pb/moddrv.h | 8 ++++---- shared/pb/spec.adoc | 20 +++++++++++++------- shared/pb/types.h | 12 +++++++++--- 7 files changed, 86 insertions(+), 21 deletions(-) create mode 100644 shared/pb/mod/main.h (limited to 'client') diff --git a/client/i2c.cpp b/client/i2c.cpp index fd29e1e..ee57e20 100644 --- a/client/i2c.cpp +++ b/client/i2c.cpp @@ -5,6 +5,11 @@ #include "sock.h" #include "xxd.h" +#include "pb/bus.h" +#include "pb/types.h" + +#include "pb/mod/main.h" + bool i2c_dump_send = false; bool i2c_dump_recv = true; @@ -28,10 +33,35 @@ void i2c_send(uint16_t addr, const char * data, size_t data_size) { free(packed); } +static void i2c_handle_cmd_read(uint16_t, const char *, size_t); + void i2c_recv(uint16_t addr, const char * data, size_t data_size) { if (i2c_dump_recv) { printf("[%s] addr(0x%02x) data(0x%02lx):\n", __FUNCTION__, addr, data_size); xxd(data, data_size); } + + if (data_size == 0) return; + enum pb_cmd cmd = (enum pb_cmd) data[0]; + data++; data_size--; + + switch (cmd) { + case PB_CMD_READ: return i2c_handle_cmd_read(addr, data, data_size); + default: return; + } +} + +static void i2c_handle_cmd_read(uint16_t i2c_addr, const char * buf, size_t sz) { + if (sz < 2) return; // require data address + 1 byte of data + pb_cmd_read_t * cmd = (pb_cmd_read_t *) buf; + sz--; // sz now represents size of cmd->data + + if (i2c_addr == BUSADDR_MAIN && cmd->address == 0x01) { + if (sz % 2 != 0) return; // invalid data + for (size_t offset = 0; offset < sz; offset += sizeof(pb_mod_main_mod_t)) { + pb_mod_main_mod_t * mod = (pb_mod_main_mod_t *) (cmd->data + offset); + printf("module at addr 0x%02x with state %d\n", mod->addr, mod->state); + } + } } diff --git a/client/readme.md b/client/readme.md index ea3e034..da48cf1 100644 --- a/client/readme.md +++ b/client/readme.md @@ -7,6 +7,16 @@ game operator to control and monitor the state of a puzzle box, but is also a useful debugging tool when developing puzzle modules, as it allows you to send arbitrary data over the puzzle bus. +## WIP TODO + +- cleanup + - separate ../shared/pb/moddrv.c into a puzzle module specific and 'common' bit + - use the common bit in i2c.cpp instead + - cast to structs in ../shared/pb/moddrv.c +- functionality + - print pretty tree of connected puzzle modules + - add enum to string functions in CLIENT ONLY + ## Features - List detected puzzle modules diff --git a/shared/pb/mod/main.h b/shared/pb/mod/main.h new file mode 100644 index 0000000..56ccd3d --- /dev/null +++ b/shared/pb/mod/main.h @@ -0,0 +1,13 @@ +#pragma once + +#include "../types.h" + +typedef struct __packed { + const uint8_t addr; + const enum pb_state state; +} pb_mod_main_mod_t; + +enum __packed { + PB_MOD_MAIN_ADDR_MODS = 0x01, //!< connected puzzle modules +}; + diff --git a/shared/pb/moddrv.c b/shared/pb/moddrv.c index 4c897e0..1f7fab8 100644 --- a/shared/pb/moddrv.c +++ b/shared/pb/moddrv.c @@ -4,22 +4,22 @@ #include "moddrv.h" /** \brief [private] placeholder global state variable */ -static pb_state_t _global_state = PB_GS_NOINIT; +static enum pb_state _global_state = PB_GS_NOINIT; /** \brief [private] main controller global state */ -static pb_state_t _main_state = PB_GS_NOINIT; +static enum pb_state _main_state = PB_GS_NOINIT; -__weak pb_state_t pbdrv_hook_mod_state_read() { +__weak enum pb_state pbdrv_hook_mod_state_read() { return _global_state; } -__weak void pbdrv_hook_mod_state_write(pb_state_t state) { +__weak void pbdrv_hook_mod_state_write(enum pb_state state) { _global_state = state; } __weak void pbdrv_i2c_recv(uint16_t i2c_addr, const char * buf, size_t sz) { if (sz == 0) return; - pb_cmd_t cmd = (enum pb_cmd) buf[0]; + enum pb_cmd cmd = (enum pb_cmd) buf[0]; // shift buffer pointer to only contain the puzzle bus message buf buf++; @@ -105,8 +105,8 @@ __weak void pbdrv_handle_sex(uint16_t i2c_addr, const char * buf, size_t sz) { pbdrv_hook_main_state_update(_main_state); } -__weak void pbdrv_hook_main_state_update(pb_state_t state) { } -__weak bool pbdrv_hook_cmd(uint16_t i2c_addr, pb_cmd_t cmd, const char * buf, size_t sz) { +__weak void pbdrv_hook_main_state_update(enum pb_state state) { } +__weak bool pbdrv_hook_cmd(uint16_t i2c_addr, enum pb_state cmd, const char * buf, size_t sz) { return false; } __weak bool pbdrv_hook_read(uint16_t i2c_addr, uint8_t addr) { diff --git a/shared/pb/moddrv.h b/shared/pb/moddrv.h index c4e1167..ecfc13a 100644 --- a/shared/pb/moddrv.h +++ b/shared/pb/moddrv.h @@ -27,9 +27,9 @@ extern "C" { void pbdrv_i2c_recv(uint16_t i2c_addr, const char * buf, size_t sz); void pbdrv_i2c_send(uint16_t i2c_addr, const char * buf, size_t sz); -pb_state_t pbdrv_hook_mod_state_read(); -void pbdrv_hook_mod_state_write(pb_state_t state); -void pbdrv_hook_main_state_update(pb_state_t state); +enum pb_state pbdrv_hook_mod_state_read(); +void pbdrv_hook_mod_state_write(enum pb_state state); +void pbdrv_hook_main_state_update(enum pb_state state); /** * \name hooks @@ -44,7 +44,7 @@ void pbdrv_hook_main_state_update(pb_state_t state); */ /** \brief cmd receive hook */ -bool pbdrv_hook_cmd(uint16_t i2c_addr, pb_cmd_t cmd, const char * buf, size_t sz); +bool pbdrv_hook_cmd(uint16_t i2c_addr, enum pb_state cmd, const char * buf, size_t sz); /** \brief read cmd hook */ bool pbdrv_hook_read(uint16_t i2c_addr, uint8_t addr); /** \brief write cmd hook */ diff --git a/shared/pb/spec.adoc b/shared/pb/spec.adoc index a99497b..3172e84 100644 --- a/shared/pb/spec.adoc +++ b/shared/pb/spec.adoc @@ -10,10 +10,16 @@ puzzle module framework. The puzzle bus carries data over a standard I^2^C bus. Additional details about this bus can be found in the link:../../docs/design.adoc[Design document]. -NOTE: Addresses influence the puzzle box's behavior, as the order of puzzles is -determined by the puzzle module address. Two puzzle modules may use the same -address, but this will mean that they cannot be used simultaniously in the same -puzzle box. Known addresses are documented in link:bus.h[]. +The following details are important to puzzle module developers, as they may +cause unexpected behavior: + +- *Addresses influence the puzzle box's behavior*. The order of puzzles is + determined by the puzzle module address. Two puzzle modules may use the same + address, but this will mean that they cannot be used simultaniously in the + same puzzle box. Known addresses are documented in link:bus.h[]. +- *The read/write bit of an I^2^C frame determines how it's handled*. I^2^C + *read* frames are treated as requests, while *write* frames are treated as + responses. == Puzzle bus driver (pbdrv) @@ -46,8 +52,8 @@ default implementations, but this is discouraged. [[sec:state-global]] == Global state -If your puzzle module defines its own ``pb_state_t``, you can tell the driver -to use it by implementing the ``pbdrv_hook_state_read`` and +If your puzzle module defines its own global ``enum pb_state``, you can tell +the driver to use it by implementing the ``pbdrv_hook_state_read`` and ``pbdrv_hook_state_write`` functions. These functions are also used by the default implementation of the read/write commands to address 0 (global state). @@ -115,7 +121,7 @@ command(s) that you want to overwrite. Example: ```c -bool pbdrv_hook_cmd(uint16_t i2c_addr, pb_cmd_t cmd, const char * buf, size_t sz) { +bool pbdrv_hook_cmd(uint16_t i2c_addr, enum pb_cmd cmd, const char * buf, size_t sz) { if (cmd == 0x54) { printf("custom command received!\n"); return true; diff --git a/shared/pb/types.h b/shared/pb/types.h index 7996a19..f2e2078 100644 --- a/shared/pb/types.h +++ b/shared/pb/types.h @@ -29,7 +29,7 @@ enum __packed pb_cmd { PB_CMD_SEX, //!< state exchange PB_CMD_MAGIC, //!< magic message }; -typedef enum pb_cmd pb_cmd_t; +// typedef enum pb_cmd pb_cmd_t; /** \brief magic sent from main controller to puzzle module */ static const char pb_magic_msg[] = { 0x70, 0x75, 0x7a, 0x62, 0x75, 0x73 }; @@ -43,10 +43,16 @@ enum __packed pb_state { PB_GS_PLAYING, //!< puzzle actively being solved PB_GS_SOLVED, //!< puzzle completed }; -typedef enum pb_state pb_state_t; +// typedef enum pb_state pb_state_t; + +typedef struct __packed { + const enum pb_cmd cmd; + const uint8_t data[]; +} pb_cmd_t; typedef struct __packed { const uint8_t address; + const uint8_t data[]; } pb_cmd_read_t; typedef struct __packed { @@ -55,7 +61,7 @@ typedef struct __packed { } pb_cmd_write_t; typedef struct __packed { - const pb_state_t main_state; + const enum pb_state main_state; } pb_cmd_sex_t; enum __packed { -- cgit v1.2.3