diff options
79 files changed, 1237 insertions, 751 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..3266955 --- /dev/null +++ b/.clang-format @@ -0,0 +1,19 @@ +--- +AccessModifierOffset: -4 +AlignConsecutiveAssignments: true +AllowShortIfStatementsOnASingleLine: AllIfsAndElse +AllowShortLoopsOnASingleLine: true +BasedOnStyle: LLVM +BreakBeforeBraces: Attach +ColumnLimit: 180 +EmptyLineBeforeAccessModifier: Always +IndentAccessModifiers: false +IndentCaseLabels: true +IndentWidth: 4 +Language: Cpp +Standard: Cpp11 +TabWidth: 4 +UseTab: Always +... + +# vim: ft=yaml
\ No newline at end of file diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..7a8470a --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,25 @@ +Checks: '-*,readability-identifier-naming' +CheckOptions: + - { key: readability-identifier-naming.EnumCase, value: lower_case } + - { key: readability-identifier-naming.EnumPrefix, value: hh_e_ } + - { key: readability-identifier-naming.GlobalFunctionCase, value: lower_case } + - { key: readability-identifier-naming.GlobalFunctionPrefix, value: hh_ } + - { key: readability-identifier-naming.ClassCase, value: CamelCase } + - { key: readability-identifier-naming.ClassPrefix, value: hh } + - { key: readability-identifier-naming.ClassMethodCase, value: lower_case } + - { key: readability-identifier-naming.ClassMethodPrefix, value: '' } + - { key: readability-identifier-naming.ClassMemberCase, value: lower_case } + - { key: readability-identifier-naming.ClassMemberPrefix, value: '' } + - { key: readability-identifier-naming.GlobalConstantCase, value: UPPER_CASE } + - { key: readability-identifier-naming.GlobalConstantIgnoredRegexp, value: _.* } + - { key: readability-identifier-naming.GlobalConstantPrefix, value: HH_ } + - { key: readability-identifier-naming.GlobalVariableCase, value: lower_case } + - { key: readability-identifier-naming.GlobalVariableIgnoredRegexp, value: _.* } + - { key: readability-identifier-naming.GlobalVariablePrefix, value: g_hh_ } + - { key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE } + - { key: readability-identifier-naming.MacroDefinitionIgnoredRegexp, value: _.* } + - { key: readability-identifier-naming.MacroDefinitionPrefix, value: HH_ } + - { key: readability-identifier-naming.StructCase, value: lower_case } + - { key: readability-identifier-naming.StructPrefix, value: hh_s_ } + +# vim: ft=yaml diff --git a/.gitmodules b/.gitmodules index 1a813e0..9ff7e96 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,8 +13,14 @@ url = https://github.com/FreeRTOS/FreeRTOS-Kernel branch = V11.1.0 shallow = true -[submodule "lib/mpack"] - path = lib/mpack +[submodule "lib/mpack/src"] + path = lib/mpack/src url = https://github.com/ludocode/mpack branch = v1.1.1 shallow = true +[submodule "lib/Arduino-CMake-Toolchain"] + path = lib/Arduino-CMake-Toolchain + url = https://github.com/a9183756-gh/Arduino-CMake-Toolchain + branch = e745a9bed3c3fb83442d55bf05630f31574674f2 + shallow = true + diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index d838266..7d492b0 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -8,10 +8,11 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS 1) set(CMAKE_BUILD_TYPE Debug) add_compile_definitions(DEBUG) -project(puzzlebox_client C CXX) +project(pbc C CXX) -include(../i2ctcp/include.cmake) -include(../shared/include.cmake) +add_subdirectory(lib/mpack) +add_subdirectory(lib/i2ctcp) +add_subdirectory(lib/pbdrv) add_executable(pbc main.cpp @@ -27,5 +28,6 @@ target_link_libraries(pbc i2ctcp mpack readline # this is such a common library that I did not bother adding it as a submodule + pbdrv ) diff --git a/client/cmd.cpp b/client/cmd.cpp index 5ac2ff3..10d53e3 100644 --- a/client/cmd.cpp +++ b/client/cmd.cpp @@ -4,12 +4,12 @@ #include <string.h> #include "cmd.h" -#include "pb/types.h" +// #include "pb/types.h" #include "rl.h" #include "i2c.h" #include "parse.h" -#include "pb/bus.h" +// #include "pb/bus.h" char* consume_token(char* input, const char* ifs) { strtok(input, ifs); @@ -68,30 +68,12 @@ void cmd_send(char * addr_str) { } void cmd_reset(char*) { - const char msg[] = { - PB_CMD_WRITE, - 0x00, - PB_GS_IDLE, - }; - 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[] = { - PB_CMD_READ, - // TODO: which address is this? - }; - i2c_send(BUSADDR_MAIN, msg, sizeof(msg)); } extern bool i2c_dump_send; @@ -119,19 +101,6 @@ void cmd_dump(char * 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; - }); - + if (word == 1) return rl_complete_list(text, dump_modes); return NULL; } diff --git a/client/compile_commands.json b/client/compile_commands.json deleted file mode 120000 index 25eb4b2..0000000 --- a/client/compile_commands.json +++ /dev/null @@ -1 +0,0 @@ -build/compile_commands.json
\ No newline at end of file diff --git a/client/i2c.cpp b/client/i2c.cpp index ee57e20..4dbc724 100644 --- a/client/i2c.cpp +++ b/client/i2c.cpp @@ -5,10 +5,10 @@ #include "sock.h" #include "xxd.h" -#include "pb/bus.h" -#include "pb/types.h" +#include "pb.h" +#include "pb-types.h" -#include "pb/mod/main.h" +// #include "pb/mod/main.h" bool i2c_dump_send = false; bool i2c_dump_recv = true; @@ -40,28 +40,19 @@ void i2c_recv(uint16_t addr, const char * data, size_t data_size) { 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); - } - } -} +// 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/makefile b/client/makefile index 8352615..5cbf045 100644 --- a/client/makefile +++ b/client/makefile @@ -1,2 +1,4 @@ +TARGET = $(BUILD_DIR)/pbc + include ../lazy.mk diff --git a/client/rl.cpp b/client/rl.cpp index b8113aa..fa44bf4 100644 --- a/client/rl.cpp +++ b/client/rl.cpp @@ -119,3 +119,26 @@ int rl_word(const char * line, int cursor) { return word; } +typedef struct { + const char * word; + const char ** suggestions; +} __rl_complete_list_data_t; +char** rl_complete_list(const char * word, const char ** suggestions) { + __rl_complete_list_data_t data = { + .word = word, + .suggestions = suggestions, + }; + return rl_completion_matches((char *) &data, [](const char * text, int state) -> char * { + __rl_complete_list_data_t data = *(__rl_complete_list_data_t *) text; + static size_t i = 0; + if (state == 0) i = 0; + + while (data.suggestions[i] != NULL) { + const char * suggestion = data.suggestions[i++]; + if (strncmp(data.word, suggestion, strlen(data.word)) == 0) + return strdup(suggestion); + } + return NULL; + }); +} + diff --git a/client/rl.h b/client/rl.h index c3bf2c7..ab31ddb 100644 --- a/client/rl.h +++ b/client/rl.h @@ -8,4 +8,5 @@ int cli_main(); void rl_printf(const char * fmt, ...); int rl_word(const char * line, int cursor); +char ** rl_complete_list(const char * word, const char * suggestions[]); diff --git a/docs/handover.adoc b/docs/handover.adoc index e25a864..a3fd880 100644 --- a/docs/handover.adoc +++ b/docs/handover.adoc @@ -1,3 +1,113 @@ +:document: Handover Report +include::share/meta.adoc[] + +== Introduction + +This is an informal document that summarizes how the 23-24 run of this project +went. We found the previous handover documents to be unhelpful when determining +the 'actual' state of the project in the first few weeks, and felt they did not +address the pitfalls of this project. + +== Incidents + +== Project History + +=== 19-20 + +.19-20 group composition +[%autowidth] +|=== +| Name | Study path + +| Daniël Janssen | Software +| Dion Legierse | Software +| Jop van Laanen | Hardware +| Max van den Heijkant | Software +|=== + +=== 20-21 + +.20-21 group composition +[%autowidth] +|=== +| Name | Study path + +| Joost van Wiechen | Hardware +| Justin Maas | Software +| [[pn:creemers,Merel Creemers]]Merel Creemers | Hardware{empty}footnote:[The +handover report from 20-21 mentions: _"Het frame zelf is niet gelukt om te +realiseren, omdat er communicatie tussen het projectgroep en de CMD-student uit +het niets is verdwenen"_. <<pn:creemers>> was introduced as a hardware-student +in the project plan, but is no longer mentioned in the handover report, which +may indicate that they were removed from the project group. I am unsure if they +were a hardware student that worked on the PCBs or a CMD student working on the +puzzle box chassis.] +| Vincent Lengowski | Hardware +|=== + +=== 21-22 + +.21-22 group composition +[%autowidth] +|=== +| Name | Study path + +| Alex van Kuijk | Hardware +| Jef Baars | Software +| Julian de Bruin | Software +| Lucas van Gastel | Software +| Toon Rockx | Hardware +|=== + +=== 22-23 + +.22-23 group composition +[%autowidth] +|=== +| Name | Study path + +| Frank Bekema | Hardware +| Jasper Gense | Hardware +|=== + +=== 23-24 (current) + +.23-24 group composition +[%autowidth] +|=== +| Name | Study path + +| Elwin Hammer | Software +| [[pn:faase,Lars Faase]]Lars Faase{empty}footnote:[<<pn:faase>> was removed +from the project group on 2024-06-03 following complaints about the lack of +communication.] | Software +| [[pn:blansch,Loek Le Blansch]]Loek Le Blansch | Software +| Thomas in 't Anker | Software +|=== + +== Resources + +Previous years' groups have put their predecessor's documents inside their own +project folder, which has resulted in what we called the 'Russian doll folder +structure'. <<pn:blansch>> has separated out each year's project folder +('master file'), and is hosting these on +<https://media.pipeframe.xyz/puzzlebox>. This directory is also mountable as a +read-only WebDAV share on Windows, MacOS and Linux (using davfs2), and does not +require credentials to log in. Please note that this is very much unofficial, +and is not managed or endorsed by Avans. <<pn:blansch>> is the contact for +removal or transfer of these files. + +== Recommendations + +- The 22-23 design document already mentions that the application of the I^2^C + bus is in a multi-master configuration, but does not mention that this only + works when pull-up resistors are used on the SCL and SDA lines. The pull-up + resistors are required, as omitting them makes the bus arbitration process + very inconsistent which causes frames to be dropped entirely. + +include::share/footer.adoc[] + + :document: Handover include::share/meta.adoc[] diff --git a/docs/share/meta.adoc b/docs/share/meta.adoc index 2e15b79..2b96f25 100644 --- a/docs/share/meta.adoc +++ b/docs/share/meta.adoc @@ -11,7 +11,7 @@ endif::[] :revnumber: 0.0 :revdate: 2024-04-01 :revremark: draft -:author_1: Thomas in ‘t Anker +:author_1: Thomas in 't Anker :author_2: Loek Le Blansch :author_3: Lars Faase :author_4: Elwin Hammer diff --git a/i2ctcp/i2ctcpv1.c b/i2ctcp/i2ctcpv1.c deleted file mode 100644 index b406d8d..0000000 --- a/i2ctcp/i2ctcpv1.c +++ /dev/null @@ -1,60 +0,0 @@ -#include <mpack.h> -#include <stdio.h> - -// MIN() macro -#include <sys/param.h> - -#include "i2ctcpv1.h" - -int i2ctcp_read(i2ctcp_msg_t * target, const char * buf, size_t 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; - - // 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); - - // 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; -} - -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/include.cmake b/i2ctcp/include.cmake deleted file mode 100644 index b61b2a4..0000000 --- a/i2ctcp/include.cmake +++ /dev/null @@ -1,19 +0,0 @@ -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 - ) - -# causes some wild crashes, please leave off -add_compile_definitions(MPACK_READ_TRACKING=0) - diff --git a/i2ctcp/lib b/i2ctcp/lib deleted file mode 120000 index dc598c5..0000000 --- a/i2ctcp/lib +++ /dev/null @@ -1 +0,0 @@ -../lib
\ No newline at end of file @@ -1,20 +1,31 @@ -# this file is for lazy people (loek) +# NOTE: CMAKE IS THE PRIMARY BUILD SYSTEM FOR SUBFOLDERS/LIBRARIES IN THIS +# REPOSITORY. THIS FILE IS PROVIDED PURELY FOR CONVENIENCE, AND SHOULD NOT +# BECOME AN ESSENTIAL PART OF THE BUILD SYSTEM! BUILD_DIR ?= build TARGET ?= $(BUILD_DIR)/main +CMFLAGS += --fresh +CMFLAGS += --log-level WARNING +CMFLAGS += -Wno-deprecated + .PHONY: FORCE all: FORCE $(TARGET) $(BUILD_DIR)/build.ninja: CMakeLists.txt - mkdir -p $(BUILD_DIR) - cmake -B $(BUILD_DIR) -G Ninja --fresh --log-level WARNING + @mkdir -p $(BUILD_DIR) + @cmake -B $(BUILD_DIR) -G Ninja $(CMFLAGS) $(TARGET): $(BUILD_DIR)/build.ninja FORCE - ninja -C $(BUILD_DIR) -# ninja automatically builds in parallel, so is preferred + @ninja -C $(BUILD_DIR) clean: FORCE $(RM) -r $(BUILD_DIR) +# Forward any unknown targets to Ninja +ifneq ($(MAKECMDGOALS),) +%:: + @ninja -C $(BUILD_DIR) $@ +endif + diff --git a/lib/Arduino-CMake-Toolchain b/lib/Arduino-CMake-Toolchain new file mode 160000 +Subproject e745a9bed3c3fb83442d55bf05630f31574674f diff --git a/lib/i2ctcp/CMakeLists.txt b/lib/i2ctcp/CMakeLists.txt new file mode 100644 index 0000000..956dcf7 --- /dev/null +++ b/lib/i2ctcp/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.29) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS 1) + +project(i2ctcp C CXX) + +add_subdirectory(lib/mpack) + +add_library(i2ctcp STATIC i2ctcpv1.c) +target_link_libraries(i2ctcp mpack) +target_include_directories(i2ctcp SYSTEM INTERFACE .) + diff --git a/lib/i2ctcp/i2ctcpv1.c b/lib/i2ctcp/i2ctcpv1.c new file mode 100644 index 0000000..f70cbb1 --- /dev/null +++ b/lib/i2ctcp/i2ctcpv1.c @@ -0,0 +1,43 @@ +#include <mpack.h> +#ifndef MIN +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#include "i2ctcpv1.h" + +int i2ctcp_read(i2ctcp_msg_t * target, const char * buf, size_t buf_sz) { + mpack_reader_t reader; + mpack_reader_init_data(&reader, buf, buf_sz); + + // at start of message + if (target->_rdata == 0) { + target->addr = mpack_expect_u16(&reader); + target->length = target->_rdata = mpack_expect_bin(&reader); + if (mpack_reader_error(&reader) != mpack_ok) return -1; + target->data = (char *) malloc(target->length); + } + + // continue reading chunks of target->data until the amount of bytes + // specified in target->length + char * data = target->data + target->length - target->_rdata; + target->_rdata -= mpack_read_remaining_bytes(&reader, data, target->_rdata); + + // 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/lib/i2ctcp/i2ctcpv1.h index 799b668..799b668 100644 --- a/i2ctcp/i2ctcpv1.h +++ b/lib/i2ctcp/i2ctcpv1.h diff --git a/lib/i2ctcp/lib b/lib/i2ctcp/lib new file mode 120000 index 0000000..58677dd --- /dev/null +++ b/lib/i2ctcp/lib @@ -0,0 +1 @@ +../../lib
\ No newline at end of file diff --git a/lib/i2ctcp/makefile b/lib/i2ctcp/makefile new file mode 100644 index 0000000..5ca3fd1 --- /dev/null +++ b/lib/i2ctcp/makefile @@ -0,0 +1,4 @@ +TARGET = $(BUILD_DIR)/libi2ctcp.a + +include ../../lazy.mk + diff --git a/i2ctcp/readme.md b/lib/i2ctcp/readme.md index d5bfe6d..d5bfe6d 100644 --- a/i2ctcp/readme.md +++ b/lib/i2ctcp/readme.md diff --git a/lib/mpack/CMakeLists.txt b/lib/mpack/CMakeLists.txt new file mode 100644 index 0000000..0e4359d --- /dev/null +++ b/lib/mpack/CMakeLists.txt @@ -0,0 +1,28 @@ +if(TARGET mpack) + return() +endif() + +cmake_minimum_required(VERSION 3.29) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_EXPORT_COMPILE_COMMANDS 1) + +project(mpack C) + +add_library(mpack STATIC + src/src/mpack/mpack-common.c + src/src/mpack/mpack-expect.c + src/src/mpack/mpack-node.c + src/src/mpack/mpack-platform.c + src/src/mpack/mpack-reader.c + src/src/mpack/mpack-writer.c + read-remaining.c + ) +target_include_directories(mpack SYSTEM INTERFACE + . + src/src/mpack + ) + +# causes some wild crashes, please leave off +add_compile_definitions(MPACK_READ_TRACKING=0) + diff --git a/lib/mpack/makefile b/lib/mpack/makefile new file mode 100644 index 0000000..e96794a --- /dev/null +++ b/lib/mpack/makefile @@ -0,0 +1,4 @@ +TARGET = $(BUILD_DIR)/libmpack.a + +include ../../lazy.mk + diff --git a/lib/mpack/mpack.h b/lib/mpack/mpack.h new file mode 100644 index 0000000..7c0c089 --- /dev/null +++ b/lib/mpack/mpack.h @@ -0,0 +1,19 @@ +#pragma once + +#include "src/src/mpack/mpack.h" + +/** + * \brief read remaining bytes in reader without opening a tag first + * + * \param reader pointer to mpack reader object + * \param p pointer to write data to + * \param count maximum number of bytes to read + * + * This function reads *up to* the amount of bytes specified in \p count, or + * less if there is less remaining data in the buffer. If \p count is equal to + * 0, all remaining data in the buffer is read. + * + * \return amount of bytes read + */ +size_t mpack_read_remaining_bytes(mpack_reader_t * reader, char * p, size_t count); + diff --git a/lib/mpack/read-remaining.c b/lib/mpack/read-remaining.c new file mode 100644 index 0000000..ebc9b56 --- /dev/null +++ b/lib/mpack/read-remaining.c @@ -0,0 +1,10 @@ +#include "mpack.h" + +size_t mpack_read_remaining_bytes(mpack_reader_t * reader, char * p, size_t count) { + size_t limit =mpack_reader_remaining(reader, NULL); + if (0 < count && count < limit) + limit = count; + memcpy(p, reader->data, limit); + return limit; +} + diff --git a/lib/mpack b/lib/mpack/src -Subproject 79d3fcd3e04338b06e82d01a62f4aa98c7bad5f +Subproject 79d3fcd3e04338b06e82d01a62f4aa98c7bad5f diff --git a/lib/pbdrv/CMakeLists.txt b/lib/pbdrv/CMakeLists.txt new file mode 100644 index 0000000..ca85b2b --- /dev/null +++ b/lib/pbdrv/CMakeLists.txt @@ -0,0 +1,28 @@ +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(pbdrv C CXX) + +add_subdirectory(lib/mpack) + +# generic puzzle bus (de)serializer library +add_library(pbdrv STATIC pb-read.c pb-write.c) +target_link_libraries(pbdrv mpack) +target_include_directories(pbdrv SYSTEM INTERFACE .) + +# puzzle bus module specific code (superset of pbdrv) +add_library(pbdrv-mod STATIC pb-mod.c) +target_link_libraries(pbdrv-mod pbdrv) +target_include_directories(pbdrv-mod SYSTEM INTERFACE .) + +# supported puzzle bus drivers +include(drv/arduino/cfg.cmake) +include(drv/rp2040/cfg.cmake) + diff --git a/lib/pbdrv/drv/arduino/cfg.cmake b/lib/pbdrv/drv/arduino/cfg.cmake new file mode 100644 index 0000000..36716e3 --- /dev/null +++ b/lib/pbdrv/drv/arduino/cfg.cmake @@ -0,0 +1,7 @@ +if(NOT DEFINED ARDUINO) + return() +endif() + +target_sources(pbdrv-mod PRIVATE "${CMAKE_CURRENT_LIST_DIR}/mod.cpp") +target_link_arduino_libraries(pbdrv-mod core Wire) + diff --git a/lib/pbdrv/drv/arduino/mod.cpp b/lib/pbdrv/drv/arduino/mod.cpp new file mode 100644 index 0000000..8a38a5b --- /dev/null +++ b/lib/pbdrv/drv/arduino/mod.cpp @@ -0,0 +1,37 @@ +#ifndef ARDUINO +#error This driver only works on the Arduino platform! +#endif + +#include <Arduino.h> +#include <Wire.h> + +#include <stdlib.h> +#include <stdint.h> + +#include "../../pb.h" +#include "../../pb-mod.h" +#include "mod.h" + +static void recv_event(int bytes) { + uint8_t * data = (uint8_t *) malloc(bytes); + size_t size = 0; + while (Wire.available()) + data[size++] = Wire.read(); + + pbdrv_i2c_recv(data, size); +} + +void pbdrv_setup() { + Wire.begin((int) PBDRV_MOD_ADDR); + Wire.setWireTimeout(PB_TIMEOUT_US, true); + Wire.setClock(PB_CLOCK_SPEED_HZ); + Wire.onReceive(recv_event); +} + +__weak void pbdrv_i2c_send(i2c_addr_t addr, const uint8_t * buf, size_t sz) { + Wire.beginTransmission((int) addr); + Wire.write(buf, sz); + Wire.endTransmission(true); + Wire.setWireTimeout(PB_TIMEOUT_US, true); +} + diff --git a/lib/pbdrv/drv/arduino/mod.h b/lib/pbdrv/drv/arduino/mod.h new file mode 100644 index 0000000..079941a --- /dev/null +++ b/lib/pbdrv/drv/arduino/mod.h @@ -0,0 +1,17 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \brief puzzle bus driver setup + * + * This function should be called from the Arduino \c setup() function. + */ +void pbdrv_setup(); + +#ifdef __cplusplus +} +#endif + diff --git a/lib/pbdrv/drv/rp2040/cfg.cmake b/lib/pbdrv/drv/rp2040/cfg.cmake new file mode 100644 index 0000000..0fbad18 --- /dev/null +++ b/lib/pbdrv/drv/rp2040/cfg.cmake @@ -0,0 +1,7 @@ +if(NOT PICO_PLATFORM STREQUAL "rp2040") + return() +endif() + +target_sources(pbdrv-mod PRIVATE "${CMAKE_CURRENT_LIST_DIR}/mod.c") +target_link_libraries(pbdrv-mod hardware_i2c pico_i2c_slave) + diff --git a/lib/pbdrv/drv/rp2040/mod.c b/lib/pbdrv/drv/rp2040/mod.c new file mode 100644 index 0000000..6becdee --- /dev/null +++ b/lib/pbdrv/drv/rp2040/mod.c @@ -0,0 +1,73 @@ +#include "mod.h" + +#include "../../pb.h" +#include "../../pb-types.h" +#include "../../pb-mod.h" + +#include <hardware/i2c.h> +#include <hardware/gpio.h> +#include <pico/i2c_slave.h> + +#define PBDRV_I2C i2c0 +#define BUF_SIZE 256 + +static volatile bool pbdrv_i2c_msg_avail = false; +static uint8_t data[BUF_SIZE]; +static size_t size = 0; + +// TODO: create event group instead of pbdrv_i2c_msg_avail +// TODO: create freertos task that monitors the pbdrv_i2c_msg_avail flag and +// calls pbdrv_i2c_recv when a message is received + + +/** + * \note this function is called from the I2C ISR, and should return as quickly + * as possible. + */ +static void recv_event(i2c_inst_t *i2c, i2c_slave_event_t event) { + // message needs to be handled first + if (pbdrv_i2c_msg_avail) return; + + switch (event) { + case I2C_SLAVE_RECEIVE: { + if (size == BUF_SIZE) return; + data[size++] = i2c_read_byte_raw(PBDRV_I2C); + break; + } + case I2C_SLAVE_FINISH: { + // TODO: handle this w/ queue mechanism instead? + // pbdrv_i2c_recv(data, size); + pbdrv_i2c_msg_avail = true; + size = 0; + break; + } + default: break; + } +} + +void pbdrv_setup() { + i2c_init(PBDRV_I2C, PB_CLOCK_SPEED_HZ); + i2c_slave_init(PBDRV_I2C, PBDRV_MOD_ADDR, &recv_event); +} + +/** + * While the RP2040's datasheet claims it supports multi-master configurations + * by implementing bus arbitration, it does not natively support a mode where + * it is configured as a (multi-)master with a slave address, such that it can + * be addressed by other multi-masters. This function includes a hacky + * workaround that teporarily sets the RP2040 to I2C master mode to send a + * message, and then restores it back to slave mode. + * + * This approach results in some received frames being (partially) dropped in + * the time period between the invocation of this function and the bus becoming + * idle (and the message is sent). + */ +__weak void pbdrv_i2c_send(i2c_addr_t addr, const uint8_t * buf, size_t sz) { + i2c_set_slave_mode(PBDRV_I2C, false, PBDRV_MOD_ADDR); + + // false to write stop condition to i2c bus + i2c_write_timeout_us(PBDRV_I2C, addr, buf, sz, false, PB_TIMEOUT_US); + + i2c_set_slave_mode(PBDRV_I2C, true, PBDRV_MOD_ADDR); +} + diff --git a/lib/pbdrv/drv/rp2040/mod.h b/lib/pbdrv/drv/rp2040/mod.h new file mode 100644 index 0000000..0cf2e63 --- /dev/null +++ b/lib/pbdrv/drv/rp2040/mod.h @@ -0,0 +1,13 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +//! puzzle bus driver setup +void pbdrv_setup(); + +#ifdef __cplusplus +} +#endif + diff --git a/lib/pbdrv/lib b/lib/pbdrv/lib new file mode 120000 index 0000000..58677dd --- /dev/null +++ b/lib/pbdrv/lib @@ -0,0 +1 @@ +../../lib
\ No newline at end of file diff --git a/lib/pbdrv/makefile b/lib/pbdrv/makefile new file mode 100644 index 0000000..c87d1af --- /dev/null +++ b/lib/pbdrv/makefile @@ -0,0 +1,4 @@ +TARGET = $(BUILD_DIR)/libpbdrv.a + +include ../../lazy.mk + diff --git a/shared/pb/mod/main.h b/lib/pbdrv/mod/main.h index 56ccd3d..535ce06 100644 --- a/shared/pb/mod/main.h +++ b/lib/pbdrv/mod/main.h @@ -2,12 +2,12 @@ #include "../types.h" -typedef struct __packed { - const uint8_t addr; - const enum pb_state state; +typedef struct { + const i2c_addr_t mod_addr; + const pb_global_state_t mod_state; } pb_mod_main_mod_t; -enum __packed { +enum { PB_MOD_MAIN_ADDR_MODS = 0x01, //!< connected puzzle modules }; diff --git a/lib/pbdrv/pb-mod.c b/lib/pbdrv/pb-mod.c new file mode 100644 index 0000000..740f2a5 --- /dev/null +++ b/lib/pbdrv/pb-mod.c @@ -0,0 +1,26 @@ +#include "pb-types.h" +#include "pb.h" + +//! fallback module name +__weak const char * PBDRV_MOD_NAME = "???"; + +//! [private] placeholder global state variable +static pb_global_state_t _global_state = PB_GS_NOINIT; + +//! [private] main controller global state +static pb_global_state_t _main_state = PB_GS_NOINIT; + +// __weak enum pb_state pbdrv_hook_mod_state_read() { +// return _global_state; +// } + +// __weak void pbdrv_hook_mod_state_write(enum pb_state state) { +// _global_state = state; +// } + +__weak void pbdrv_i2c_recv(const uint8_t * buf, size_t sz) { + return; +} + +__weak void pbdrv_hook_main_state_update(pb_global_state_t state) { } + diff --git a/lib/pbdrv/pb-mod.h b/lib/pbdrv/pb-mod.h new file mode 100644 index 0000000..fa290bf --- /dev/null +++ b/lib/pbdrv/pb-mod.h @@ -0,0 +1,34 @@ +#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 <stdint.h> +#include <stddef.h> +#include <stdbool.h> + +#include "pb-types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//! puzzle module name (optional, default = "???") +extern const char * PBDRV_MOD_NAME; +//! puzzle module bus address (required) +extern const i2c_addr_t PBDRV_MOD_ADDR; + +void pbdrv_i2c_recv(const uint8_t * buf, size_t sz); +void pbdrv_i2c_send(i2c_addr_t i2c_addr, const uint8_t * buf, size_t sz); + +#ifdef __cplusplus +} +#endif + diff --git a/lib/pbdrv/pb-read.c b/lib/pbdrv/pb-read.c new file mode 100644 index 0000000..843420d --- /dev/null +++ b/lib/pbdrv/pb-read.c @@ -0,0 +1,3 @@ +#include "pb-read.h" + + diff --git a/lib/pbdrv/pb-read.h b/lib/pbdrv/pb-read.h new file mode 100644 index 0000000..7a6f0df --- /dev/null +++ b/lib/pbdrv/pb-read.h @@ -0,0 +1,10 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + diff --git a/lib/pbdrv/pb-types.h b/lib/pbdrv/pb-types.h new file mode 100644 index 0000000..96ffc37 --- /dev/null +++ b/lib/pbdrv/pb-types.h @@ -0,0 +1,98 @@ +#pragma once +#include <stdint.h> +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __GNUC__ +#define __weak __attribute__((weak)) +#endif +#ifndef __weak +#error Could not determine weak attribute for current compiler +#define __weak +#endif + +typedef uint16_t i2c_addr_t; + +//! puzzle bus command types +enum pb_cmd_id { + PB_CMD_REQ_READ, //!< request a puzzle module property + PB_CMD_RES_READ, //!< respond to a puzzle module property request + PB_CMD_REQ_WRITE, //!< request to write a puzzle module property + PB_CMD_REQ_STATE, //!< request global state + PB_CMD_RES_STATE, //!< respond to a global state request + PB_CMD_REQ_SET_STATE, //!< request to overwrite module global state + PB_CMD_MAGIC, //!< magic message (regular i2c command) +}; +typedef enum pb_cmd_id pb_cmd_id_t; + +//! magic sent from main controller to puzzle module +static const char pb_cmd_magic_msg[] = { 0x70, 0x75, 0x7a, 0x62, 0x75, 0x73 }; +//! magic reply from puzzle module back to main controller +static const char pb_cmd_magic_res[] = { 0x67, 0x61, 0x6d, 0x69, 0x6e, 0x67 }; + +//! 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 +}; +typedef enum pb_global_state pb_global_state_t; + +//! puzzle bus message header (shared by all commands) +typedef struct { + const pb_cmd_id_t type; //!< command type + const i2c_addr_t sender; //!< i2c address of sender +} pb_msg_header_t; + +//! PB_CMD_REQ_READ data +typedef struct { + const pb_msg_header_t header; + const uint8_t propid; //!< state property id to return +} pb_cmd_req_read_t; + +//! PB_CMD_RES_READ data +typedef struct { + const pb_msg_header_t header; + const uint8_t propid; //!< id of returned state property + const uint8_t value[]; +} pb_cmd_res_read_t; + +//! PB_CMD_REQ_WRITE data +typedef struct { + const pb_msg_header_t header; + const uint8_t propid; //!< state property id to write + const uint8_t value[]; //!< new value of property +} pb_cmd_req_write_t; + +//! PB_CMD_REQ_STATE data +typedef struct { + const pb_msg_header_t header; + const pb_global_state_t state; //!< global state of sender +} pb_cmd_req_state_t; + +//! PB_CMD_RES_STATE data +typedef struct { + const pb_msg_header_t header; + const pb_global_state_t state; //!< global state of sender +} pb_cmd_res_state_t; + +//! PB_CMD_REQ_SET_STATE data +typedef struct { + const pb_msg_header_t header; + const pb_global_state_t state; //!< new global state +} pb_cmd_req_set_state_t; + +//! PB_CMD_MAGIC data +typedef struct { + const pb_msg_header_t header; + const char magic[]; //!< magic value +} pb_cmd_magic_t; + +#ifdef __cplusplus +} +#endif + diff --git a/lib/pbdrv/pb-write.c b/lib/pbdrv/pb-write.c new file mode 100644 index 0000000..752a4ac --- /dev/null +++ b/lib/pbdrv/pb-write.c @@ -0,0 +1,35 @@ +#include <mpack.h> + +#include "pb-write.h" + +typedef struct { + mpack_writer_t writer; + pbdrv_buf_t buf; +} pbdrv_writer_t; + +static pbdrv_writer_t pbdrv_write_init() { + pbdrv_writer_t writer; + mpack_writer_init_growable(&writer.writer, &writer.buf.data, &writer.buf.size); + return writer; +} + +static pbdrv_buf_t pbdrv_write_finish(pbdrv_writer_t * writer) { + if (mpack_writer_destroy(&writer->writer) != mpack_ok) { + writer->buf.data = NULL; + writer->buf.size = 0; + } + return writer->buf; +} + +static void pbdrv_write_msg_header(pbdrv_writer_t * writer, pb_msg_header_t header) { + mpack_write_u8(&writer->writer, header.type); + mpack_write_u16(&writer->writer, header.sender); +} + +pbdrv_buf_t pbdrv_write_cmd_req_set_state(pb_cmd_req_set_state_t data) { + pbdrv_writer_t writer = pbdrv_write_init(); + pbdrv_write_msg_header(&writer, data.header); + mpack_write_u8(&writer.writer, data.state); + return pbdrv_write_finish(&writer); +} + diff --git a/lib/pbdrv/pb-write.h b/lib/pbdrv/pb-write.h new file mode 100644 index 0000000..3245898 --- /dev/null +++ b/lib/pbdrv/pb-write.h @@ -0,0 +1,26 @@ +#pragma once + +#include "pb-types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//! binary buffer struct +typedef struct { + char * data; //! pointer to data + size_t size; //! size of data +} pbdrv_buf_t; + +pbdrv_buf_t pbdrv_write_cmd_req_read(pb_cmd_req_read_t data); +pbdrv_buf_t pbdrv_write_cmd_res_read(pb_cmd_res_read_t data); +pbdrv_buf_t pbdrv_write_cmd_req_write(pb_cmd_req_write_t data); +pbdrv_buf_t pbdrv_write_cmd_req_state(pb_cmd_req_state_t data); +pbdrv_buf_t pbdrv_write_cmd_res_state(pb_cmd_res_state_t data); +pbdrv_buf_t pbdrv_write_cmd_req_set_state(pb_cmd_req_set_state_t data); +pbdrv_buf_t pbdrv_write_cmd_magic(pb_cmd_magic_t data); + +#ifdef __cplusplus +} +#endif + diff --git a/lib/pbdrv/pb.h b/lib/pbdrv/pb.h new file mode 100644 index 0000000..b6efed0 --- /dev/null +++ b/lib/pbdrv/pb.h @@ -0,0 +1,22 @@ +#pragma once + +#define PB_CLOCK_SPEED_HZ 100000 +#define PB_TIMEOUT_MS 10 +#define PB_TIMEOUT_US (1e3 * PB_TIMEOUT_MS) + +// Adafruit NeoTrellis modules +#define PB_ADDR_ADA_NEO_1 0x2E +#define PB_ADDR_ADA_NEO_2 0x2F +#define PB_ADDR_ADA_NEO_3 0x30 +#define PB_ADDR_ADA_NEO_4 0x32 + +// TODO: ??? +#define PB_ADDR_MOD_NEOTRELLIS 0 +#define PB_ADDR_MOD_SOFTWARE 0 +#define PB_ADDR_MOD_HARDWARE 0 +#define PB_ADDR_MOD_VAULT 0 +// #define BUSADDR_MOD_AUTOMATION 0 + +// main controller +#define PB_ADDR_MOD_MAIN 0x00 + diff --git a/shared/pb/spec.adoc b/lib/pbdrv/spec.adoc index 3172e84..3172e84 100644 --- a/shared/pb/spec.adoc +++ b/lib/pbdrv/spec.adoc diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index cf23839..7a0b136 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -7,8 +7,9 @@ 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(../i2ctcp/include.cmake) -include(../shared/include.cmake) +add_subdirectory(lib/mpack) +add_subdirectory(lib/i2ctcp) +add_subdirectory(lib/pbdrv) project(puzzlebox_main C CXX ASM) @@ -19,6 +20,9 @@ add_executable(main init.c sock.c i2c.c + mod.c + tasks.c + blink.c ) pico_enable_stdio_usb(main 1) @@ -27,7 +31,7 @@ pico_add_extra_outputs(main) include_directories(lib/pico-sdk/lib/lwip/contrib/ports/freertos/include) -target_include_directories(main PRIVATE ${CMAKE_CURRENT_LIST_DIR}) +target_include_directories(main PRIVATE .) target_link_libraries(main pico_cyw43_arch_lwip_sys_freertos pico_stdlib @@ -36,5 +40,6 @@ target_link_libraries(main FreeRTOS-Kernel-Heap4 i2ctcp mpack + pbdrv-mod ) diff --git a/main/blink.c b/main/blink.c new file mode 100644 index 0000000..956e910 --- /dev/null +++ b/main/blink.c @@ -0,0 +1,16 @@ +#include <FreeRTOS.h> +#include <task.h> +#include <pico/cyw43_arch.h> + +#include "blink.h" +#include "config.h" + +void blink_task() { + while (true) { + cyw43_arch_gpio_put(CFG_LED_PIN, 1); + vTaskDelay(50 / portTICK_PERIOD_MS); + cyw43_arch_gpio_put(CFG_LED_PIN, 0); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } +} + diff --git a/main/blink.h b/main/blink.h new file mode 100644 index 0000000..51c5f32 --- /dev/null +++ b/main/blink.h @@ -0,0 +1,4 @@ +#pragma once + +void blink_task(); + diff --git a/main/compile_commands.json b/main/compile_commands.json deleted file mode 120000 index 25eb4b2..0000000 --- a/main/compile_commands.json +++ /dev/null @@ -1 +0,0 @@ -build/compile_commands.json
\ No newline at end of file diff --git a/main/config.def.h b/main/config.def.h index 7fcaed9..3d325fe 100644 --- a/main/config.def.h +++ b/main/config.def.h @@ -1,17 +1,72 @@ #pragma once #include <pico/cyw43_arch.h> +#include <cyw43_country.h> -// wifi credentials -#define CONF_NET_SSID "network name" -#define CONF_NET_PASS "network password" -#define CONF_NET_AUTH CYW43_AUTH_WPA2_AES_PSK -// max duration (milliseconds) for establishing wifi connection -#define CONF_NET_CONN_TIMEOUT 10e3 +/** + * \name Network (Wi-Fi) configuration + * \{ + */ +#ifndef CFG_NET_SSID +//! network name (SSID) +#define CFG_NET_SSID "" +#ifndef CFG_NET_DISABLE +//! disable network communication +#define CFG_NET_DISABLE +#warning No SSID defined! Disabling network communication! +#endif +#endif -#include <cyw43_country.h> -#define CONF_NET_COUNTRY CYW43_COUNTRY_NETHERLANDS +#ifndef CFG_NET_PASS +//! network password +#define CFG_NET_PASS "" +#endif + +#ifndef CFG_NET_AUTH +//! network security type +#define CFG_NET_AUTH CYW43_AUTH_OPEN +#endif + +#ifndef CFG_NET_CONN_TIMEOUT +//! max duration (milliseconds) for establishing wifi connection +#define CFG_NET_CONN_TIMEOUT 10e3 +#endif + +#ifdef CFG_NET_DISABLE +#undef CFG_NET_COUNTRY +#define CFG_NET_COUNTRY CYW43_COUNTRY_WORLDWIDE +#endif +#ifndef CFG_NET_COUNTRY +//! radio communications country +#define CFG_NET_COUNTRY CYW43_COUNTRY_NETHERLANDS +#endif +/** \} */ + +/** + * \name i2ctcp server configuration + * \{ + */ +#ifndef CFG_SRV_PORT +//! i2ctcp server port +#define CFG_SRV_PORT 9191 +#endif + +#ifdef CFG_NET_DISABLE +//! disable the i2ctcp server +#define CFG_SRV_DISABLE +#endif +/** \} */ -#define CONF_SRV_PORT 9191 +#ifndef CFG_LED_PIN +//! status LED pin +#define CFG_LED_PIN CYW43_WL_GPIO_LED_PIN +#endif -#define LED_PIN CYW43_WL_GPIO_LED_PIN +#ifndef CFG_SDA_PIN +//! I^2^C SDA pin +#define CFG_SDA_PIN 16 +#endif +#ifndef CFG_SCL_PIN +//! I^2^C SCL pin +#define CFG_SCL_PIN 17 +#endif @@ -1,8 +1,5 @@ -#include "i2c.h" -#include "init.h" -#include "sock.h" -#include "pb/types.h" - +#include <FreeRTOS.h> +#include <task.h> #include <stdio.h> #include <stddef.h> #include <stdint.h> @@ -10,129 +7,65 @@ #include <pico/stdlib.h> #include <hardware/i2c.h> -#include <lwip/opt.h> -#include <lwip/sys.h> -#include <lwip/api.h> -#include <string.h> - -uint8_t found[MAX_SLAVES]; -extern struct netconn* current_connection; - -void init_i2c() { - i2c_init(I2C_PORT, 100 * 1000); // currently at 100kHz - - // Initialize I2C pins - sda(16), scl(17) - gpio_set_function(SDA_PIN, GPIO_FUNC_I2C); - gpio_set_function(SCL_PIN, GPIO_FUNC_I2C); - - gpio_pull_up(SDA_PIN); - gpio_pull_up(SCL_PIN); -} - -int read_i2c(uint8_t addr, uint8_t *output, size_t len) { - // false - finished with bus - return i2c_read_blocking (I2C_PORT, addr, output, len, false); -} - -int write_i2c(uint8_t addr, uint8_t *input, size_t len) { - // true to keep master control of bus - return i2c_write_blocking (I2C_PORT, addr, input, len, true); -} - -bool reserved_addr(uint8_t addr) { - return (addr & 0x78) == 0 || (addr & 0x78) == 0x78; -} - -void init_addr_array(uint8_t array[], int size) { - for(int i = 0; i < size; i++){ - array[i] = 0x00; - } -} - -int write_read_i2c(uint8_t addr, uint8_t *input, size_t input_len, uint8_t *output, size_t output_len){ - // herhaalde start conditie voor direct lezen na i2c write (?) - int ret = write_i2c(addr, input, input_len); - if (ret < 0) { - printf("Write failure while writing data to bus.\n"); - return ret; - } - - // wait for response - absolute_time_t start_time = get_absolute_time(); - while ( absolute_time_diff_us(start_time, get_absolute_time()) / 1000 < MAX_TIMEOUT_TIME ){ - ret = read_i2c(addr, output, output_len); - if( ret > 0 ) { - return ret; - } - sleep_ms(1); - } - - printf("Timeout occurred while waiting for slave response.\n"); - return -1; -} - -// Make sure that current addresses are checked (modules), and invalid addresses are ignore (neotrellis slave) -uint8_t* scan_bus(uint8_t *array) { - int ret; - int i = 0; - uint8_t * rxdata; - uint8_t * handshake_data; - init_addr_array(array, MAX_SLAVES); - - for(int addr = 1; addr < (1<<7); addr++) { - // fix handshake - ret = read_i2c(addr, rxdata, 1); - - if ( ret <= 0 ) - continue; - - char buf[80]; - size_t s = snprintf(buf, 80,"found i2c puzzle module at address: 0x%02x\n", addr); - netconn_write(current_connection, buf, s, NETCONN_COPY); - printf("%.*s", s, buf); - - // do handshake - ret = write_read_i2c(addr, (uint8_t*)pb_magic_msg, sizeof(pb_magic_msg), handshake_data, sizeof(pb_magic_res)); // fix data + length + everything - - if ( ret != sizeof(pb_magic_res)) - continue; - - if ( ret > 0 && (memcmp(handshake_data, pb_magic_res, sizeof(pb_magic_res)) == 0)) { - printf("this was an actual device!!!1111!\n"); - char buf[80]; - size_t s = snprintf(buf, 80,"found i2c puzzle module at address: 0x%02x\n"); - netconn_write(current_connection, buf, s, NETCONN_COPY); - - array[i] = addr; - i++; - } - } - - return array; +#include "i2c.h" +#include "pb-mod.h" + +// uint8_t* scan_bus(uint8_t *array) { +// int ret; +// int i = 0; +// uint8_t rxdata; +// +// for(int addr = 0; addr < (1<<7); addr++) { +// // ignore reserved addresses +// // These are any addresses of the form 000 0xxx or 111 1xxx +// // ret = i2c_read_blocking(I2C_PORT, addr, &rxdata, 1, false); +// +// // if acknowledged -> ret == number of bytes sent +// if(ret > 0){ +// printf("found i2c slave on addr: %d\n", addr); +// array[i] = addr; +// i++; +// } +// } +// +// return array; +// } + +void pbdrv_i2c_recv(const uint8_t * a, size_t b) { + printf("%.*s", b, a); } void bus_task() { // scan bus for slaves // send updates at regular intervals - await_init(); + vTaskDelay(1000 / portTICK_PERIOD_MS); - scan_bus(found); - - while(1) { - // add check if bus is in use - - uint8_t data; - for(int i = 0; i < MAX_SLAVES; i++){ - if( found[i] == 0x00 ) - continue; - - read_i2c(found[i], &data, 2); - if(data > 0) { - printf("Data: %d", data); - } - - } - - sleep_ms(1000); // wait for one second before next loop + // int i = 0; + // uint8_t found[MAX_SLAVES]; + // init_addr_array(found, MAX_SLAVES); + + while (true) { + vTaskDelay(10 / portTICK_PERIOD_MS); + pbdrv_i2c_send(0x69, (uint8_t *) "bbbbbbbb", 9); } + + // while(1) { + // // printf("Bus scan!"); + // scan_bus(found); + + // for(int i = 0; i < MAX_SLAVES; i++){ + // if( found[i] == 0x00 ) + // break; + // + // uint8_t data = 0x01; + // // send data to found slave address + // write_i2c(found[i], &data, 1); + + // data = 0x02; + // write_i2c(found[i], &data, 1); + // // request update from slave addr at found[i] + // //write_i2c(); + // } + // } } + @@ -2,52 +2,9 @@ // https://github.com/raspberrypi/pico-examples/tree/master/i2c // https://www.raspberrypi.com/documentation/microcontrollers/raspberry-pi-pico.html -#include <stddef.h> -#include <stdint.h> -#include <hardware/i2c.h> - -#define SDA_PIN 16 -#define SCL_PIN 17 -#define I2C_PORT i2c0 #define MAX_SLAVES 10 #define MAX_TIMEOUT_TIME 50 //ms -/** - * \brief initialize all required gpio for i2c usage on the pico - * - * This functions only initializes the standard gpio required to start i2c - * communications. - * - * \note Tasks shouldn't depend on any other module in the main controller - */ -void init_i2c(); - -/** - * \brief read data from addr with length len from i2c bus. - * - * This functions reads data from a specific address on the i2c bus, - * the output var will hold the data which was read from said address with - * length len. - */ -int read_i2c(uint8_t addr, uint8_t *output, size_t len); - -/** - * \brief write data to addr with length len from i2c bus. - * \param addr - * \param input - * \param len - * This functions writes data to a specific address on the i2c bus, - * the input var holds the data which will be written to the given - * address with length len. - */ -int write_i2c(uint8_t addr, uint8_t *input, size_t len); - -/** - * \brief -*/ -int write_read_i2c(uint8_t addr, uint8_t *input, size_t input_len, uint8_t *output, size_t output_len); - -uint8_t* scan_bus(uint8_t *array); - -/** \brief looking for slave addresses and requesting updates */ +//! looking for slave addresses and requesting updates void bus_task(); + diff --git a/main/init.c b/main/init.c index 4ab373e..bd00c04 100644 --- a/main/init.c +++ b/main/init.c @@ -1,22 +1,20 @@ -#include "config.h" -#include "init.h" -#include "i2c.h" - #include <FreeRTOS.h> #include <task.h> -#include <event_groups.h> #include <pico/stdio.h> #include <pico/cyw43_arch.h> -EventGroupHandle_t init_complete; +#include "config.h" +#include "init.h" +#include "tasks.h" +#include "drv/rp2040/mod.h" static void init_stdio() { stdio_init_all(); } static void init_cyw34() { - if (cyw43_arch_init_with_country(CONF_NET_COUNTRY)) + if (cyw43_arch_init_with_country(CFG_NET_COUNTRY)) panic("cyw43_arch_init_with_country failed\n"); } @@ -24,30 +22,32 @@ static void init_wifi() { // enable 'station' mode (connect to an access point instead of acting like one) cyw43_arch_enable_sta_mode(); - /* WERKT GEWOON NIET MET DEZE LIJNEN CODE */ - if (cyw43_arch_wifi_connect_timeout_ms(CONF_NET_SSID, CONF_NET_PASS, CONF_NET_AUTH, CONF_NET_CONN_TIMEOUT)) + if (cyw43_arch_wifi_connect_timeout_ms(CFG_NET_SSID, CFG_NET_PASS, CFG_NET_AUTH, CFG_NET_CONN_TIMEOUT)) panic("cyw43_arch_wifi_connect failed\n"); - /* WERKT GEWOON NIET MET DEZE LIJNEN CODE */ - - printf("connected to Wi-Fi\n"); // TODO: announce hostname(?) } +static void init_i2c() { + gpio_set_function(CFG_SDA_PIN, GPIO_FUNC_I2C); + gpio_set_function(CFG_SCL_PIN, GPIO_FUNC_I2C); + + pbdrv_setup(); +} + static void async_init() { init_cyw34(); init_i2c(); +#ifndef CFG_NET_DISABLE init_wifi(); - - xEventGroupSetBits(init_complete, 1); +#endif + init_tasks(); // delete self vTaskDelete(NULL); } void init() { - init_complete = xEventGroupCreate(); - // used for debug `printf` and `panic` on errors init_stdio(); @@ -55,7 +55,3 @@ void init() { xTaskCreate((TaskFunction_t) async_init, "init", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 4, NULL); } -void await_init() { - xEventGroupWaitBits(init_complete, 1, pdFALSE, pdFALSE, portMAX_DELAY); -} - diff --git a/main/init.h b/main/init.h index de9023c..73d2773 100644 --- a/main/init.h +++ b/main/init.h @@ -1,38 +1,11 @@ #pragma once -#include <FreeRTOS.h> -#include <event_groups.h> - -/** - * \brief init function complete event group handle - * - * This is required to make sure the main task waits until initialization is - * complete. Due to the combination of FreeRTOS + lwIP, the initialization - * should be done while the task scheduler is running. Specifically the - * cyw43_arch_init functions make the pico hang indefinitely when used while - * the task scheduler is not running. - * - * \note `init_complete` only utilizes LSB, so `uxBitsToWaitFor` should always - * be set to *1* - */ -extern EventGroupHandle_t init_complete; - /** - * \brief initialize all peripherals on the pico + * \brief initialize the main controller * * This function only synchronously initializes the standard input/output (used * for `printf` and `panic`), and queues all other types of initialization in * the `init` task using FreeRTOS. - * - * \note Tasks dependent on the wifi being initialized should use the - * `init_complete` event group to wait for initialization to complete! */ void init(); -/** - * \brief block task until all initialization is complete - * - * utility function, see above comments - */ -void await_init(); - diff --git a/main/main.c b/main/main.c index 7558a0b..7c1bb6a 100644 --- a/main/main.c +++ b/main/main.c @@ -1,32 +1,10 @@ #include <FreeRTOS.h> #include <task.h> -#include <pico/stdlib.h> -#include <pico/time.h> - -#include "config.h" #include "init.h" -#include "sock.h" -#include "i2c.h" - -void blink_task() { - await_init(); // `blink_task` uses GPIO - - while (true) { - cyw43_arch_gpio_put(LED_PIN, 0); - vTaskDelay(250 / portTICK_PERIOD_MS); - cyw43_arch_gpio_put(LED_PIN, 1); - vTaskDelay(250 / portTICK_PERIOD_MS); - } -} int main() { init(); - - xTaskCreate((TaskFunction_t) blink_task, "blink", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, NULL); - xTaskCreate((TaskFunction_t) serve_task, "serve", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, NULL); - xTaskCreate((TaskFunction_t) bus_task, "bus", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, NULL); - vTaskStartScheduler(); while(1) { diff --git a/main/mod.c b/main/mod.c new file mode 100644 index 0000000..b34bbc9 --- /dev/null +++ b/main/mod.c @@ -0,0 +1,5 @@ +#include "pb-mod.h" + +const char * PBDRV_MOD_NAME = "main controller"; +const i2c_addr_t PBDRV_MOD_ADDR = 0x20; + diff --git a/main/readme.md b/main/readme.md index 425a00b..28fcfad 100644 --- a/main/readme.md +++ b/main/readme.md @@ -5,17 +5,23 @@ This directory contains the software for the main controller of the Puzzle Box. ## building 1. make sure the submodules are initialized -2. copy [`config.def.h`](./config.def.h) to `config.h` and edit the defaults -3. `mkdir build` -4. `cmake -B build` -5. `make -C build` or `ninja -C build` (choose your preference) +2. create a `config.h` file and define some options (see `config.def.h` for all + options): + ```c + #pragma once -alternatively, a makefile is provided for convenience + #define CFG_NET_SSID "network name" + #define CFG_NET_PASS "network password" + #define CFG_NET_AUTH CYW43_AUTH_WPA2_AES_PSK -## "flashing" + #include "config.def.h" + ``` +3. use CMake to build -1. [build](#building) -2. (re)connect the raspberry pi pico while holding the BOOTSEL button (this is - the only button) +## flashing + +1. build +2. hold the BOOTSEL button while resetting the pico (by power cycling or + pulling pin 30 (RUN) to GND) 3. `picotool load build/main.uf2` diff --git a/main/sock.c b/main/sock.c index af25d97..5d75e8f 100644 --- a/main/sock.c +++ b/main/sock.c @@ -81,14 +81,12 @@ void accept_handler(struct netconn* conn) { } void serve_task() { - await_init(); - printf("starting server...\n"); struct netconn* conn = netconn_new(NETCONN_TCP); - netconn_bind(conn, IP_ADDR_ANY, CONF_SRV_PORT); + netconn_bind(conn, IP_ADDR_ANY, CFG_SRV_PORT); netconn_listen(conn); - printf("listening on %s:%d\n", ip4addr_ntoa(netif_ip4_addr(netif_list)), CONF_SRV_PORT); + printf("listening on %s:%d\n", ip4addr_ntoa(netif_ip4_addr(netif_list)), CFG_SRV_PORT); while (1) { struct netconn* incoming; if (netconn_accept(conn, &incoming) == ERR_OK) diff --git a/main/sock.h b/main/sock.h index f2db35d..61828fb 100644 --- a/main/sock.h +++ b/main/sock.h @@ -3,7 +3,7 @@ #include <stdint.h> #include <stddef.h> -/** \brief start listening for TCP socket requests */ +//! start listening for TCP socket requests void serve_task(); void i2c_send(uint16_t addr, const char * data, size_t data_size); diff --git a/main/tasks.c b/main/tasks.c new file mode 100644 index 0000000..253c47b --- /dev/null +++ b/main/tasks.c @@ -0,0 +1,17 @@ +#include <FreeRTOS.h> +#include <task.h> + +#include "config.h" +#include "tasks.h" + +#include "blink.h" +#include "i2c.h" +#include "sock.h" + +void init_tasks() { + xTaskCreate((TaskFunction_t) blink_task, "blink", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, NULL); +#ifndef CFG_SRV_DISABLE + xTaskCreate((TaskFunction_t) serve_task, "serve", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, NULL); +#endif + xTaskCreate((TaskFunction_t) bus_task, "bus", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, NULL); +} diff --git a/main/tasks.h b/main/tasks.h new file mode 100644 index 0000000..002f830 --- /dev/null +++ b/main/tasks.h @@ -0,0 +1,4 @@ +#pragma once + +void init_tasks(); + diff --git a/puzzle/dummy/CMakeLists.txt b/puzzle/dummy/CMakeLists.txt new file mode 100644 index 0000000..c485e74 --- /dev/null +++ b/puzzle/dummy/CMakeLists.txt @@ -0,0 +1,37 @@ +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) + +# arduino +set(CMAKE_TOOLCHAIN_FILE ${CMAKE_SOURCE_DIR}/lib/Arduino-CMake-Toolchain/Arduino-toolchain.cmake) +set(ARDUINO_BOARD "Arduino Uno [avr.uno]") + +# used for testing +# set(ARDUINO_BOARD "Raspberry Pi Pico W [rp2040.rpipicow]") +# add_compile_definitions(USE_TINYUSB) +# include_directories(/home/loek/.arduino15/packages/rp2040/hardware/rp2040/3.9.2/libraries/Adafruit_TinyUSB_Arduino/src/arduino) + +project(pb_mod_dummy C CXX) + +add_subdirectory(lib/pbdrv) + +add_executable(main + main.cpp + ) + +target_link_libraries(main + pbdrv-mod + ) +target_link_arduino_libraries(main + core + Wire + ) + +target_enable_arduino_upload(main) + diff --git a/puzzle/dummy/lib b/puzzle/dummy/lib new file mode 120000 index 0000000..58677dd --- /dev/null +++ b/puzzle/dummy/lib @@ -0,0 +1 @@ +../../lib
\ No newline at end of file diff --git a/puzzle/dummy/main.cpp b/puzzle/dummy/main.cpp new file mode 100644 index 0000000..3d84679 --- /dev/null +++ b/puzzle/dummy/main.cpp @@ -0,0 +1,36 @@ +#include <Arduino.h> +#include <Wire.h> + +#include "drv/arduino/mod.h" +#include "pb-mod.h" + +#ifdef TEST_A +#define ADDR_RX 0x69 +#define ADDR_TX 0x20 +#define MSG "aa" +#define MSG_SIZE 3 +#define MSG_DELAY 10 +#endif + +#ifdef TEST_B +#define ADDR_TX 0x69 +#define ADDR_RX 0x20 +#define MSG "bbbbbbbb" +#define MSG_SIZE 9 +#define MSG_DELAY 10 +#endif + +const char * PBDRV_MOD_NAME = "dummy"; +const i2c_addr_t PBDRV_MOD_ADDR = ADDR_RX; + +void setup() { + pbdrv_setup(); +} + +void loop() { + pbdrv_i2c_send(ADDR_TX, (uint8_t *) MSG, MSG_SIZE); + delay(MSG_DELAY); +} + +void pbdrv_i2c_recv(const uint8_t * data, size_t size) { } + diff --git a/puzzle/dummy/makefile b/puzzle/dummy/makefile new file mode 100644 index 0000000..509d8e3 --- /dev/null +++ b/puzzle/dummy/makefile @@ -0,0 +1,20 @@ +TARGET = $(BUILD_DIR)/main.elf + +include ../../lazy.mk + +export SERIAL_PORT ?= /dev/ttyACM0 +flash: upload-main; +upload-main: $(TARGET) + +test: test_a test_b; + +test_a: + $(MAKE) -C . clean + $(MAKE) -E CMFLAGS+=-D\ CMAKE_CXX_FLAGS=-DTEST_A -C . + $(MAKE) -E SERIAL_PORT=/dev/ttyACM0 -C . flash + +test_b: + $(MAKE) -C . clean + $(MAKE) -E CMFLAGS+=-D\ CMAKE_CXX_FLAGS=-DTEST_B -C . + $(MAKE) -E SERIAL_PORT=/dev/ttyACM1 -C . flash + diff --git a/puzzle/readme.md b/puzzle/readme.md new file mode 100644 index 0000000..59c10b1 --- /dev/null +++ b/puzzle/readme.md @@ -0,0 +1,31 @@ +# puzzles + +This folder contains the source code for all puzzle modules. + +## Arduino-based puzzle modules + +Because of the poorly designed hardware (21-22) used during development +(23-24), some puzzle modules ended up being developed using Arduino boards. All +libraries in this repository use CMake for building (for consistency), which +also means the Arduino based puzzle modules use CMake. The CMakeLists.txt of +some puzzles uses the [Arduino-CMake-Toolchain][arduino-cmake]. To build any of +these subfolders, make sure you have done the following: + +- Install the official Arduino IDE +- Open "Tools" > "Board" > "Board manager" +- Install the "Arduino AVR Boards" package (1.8.6 works at the time of writing) + +[arduino-cmake]: https://github.com/a9183756-gh/Arduino-CMake-Toolchain + +## ESP-based puzzle modules + +### ESP-IDF SDK Setup instructions + +1. Install ESP-IDF extension in Visual Studio Code +2. Install using 'express' option +3. Install ESP-IDF v5.2.1 (release version) + + 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) + @@ -17,16 +17,18 @@ Please keep this repository tidy by being aware of the following conventions! ### Folder structure -|folder|contains| -|-|-| -|`/client`|Desktop PC application for controlling the puzzle box -|`/docs`|Project documentation in AsciiDoc(tor) format -|`/i2ctcp`|I<sup>2</sup>C over TCP protocol functions (used by main and client) -|`/lib`|Libraries (tracked as [submodules](#submodules)) -|`/main`|Main controller (RPi pico) software -|`/puzzle/<name>`|Puzzle sources, each puzzle has its own subdirectory -|`/shared`|Shared code -|`/test`|Unit test framework (currently unutilized) +``` +/client desktop PC application for controlling the puzzle box +/docs project documentation in AsciiDoc(tor) format +/lib custom libraries and submodules +├───/i2ctcp I2C over TCP protocol functions (used by main and client) +├───/mpack MsgPack CMake configuration and extension +└───/pbdrv puzzle bus driver (module driver + (de)serializing functions) +/main main controller (RPi pico) software +/puzzle/<name> puzzle sources, each puzzle has its own subdirectory +/shared (unused) shared code +/test unit tests +``` ### Code style @@ -49,25 +51,12 @@ git submodule update --init --recursive --depth 1 until your problems go away. -<!-- -## Tests - -``` -mkdir -p test/build -cd test/build -cmake .. -make -make test -``` ---> - -## ESP SDK setup - -1. Install ESP-IDF extension in Visual Studio Code -2. Install using 'express' option -3. Install ESP-IDF v5.2.1 (release version) +## lazy\.mk - 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) +[`lazy.mk`](./lazy.mk) is a file made by Loek, and includes some rules for +forwarding `make` calls to `cmake` and `ninja`. **This is purely for +convenience, and should not become an essential part of the build system**. +This file should be included at the end of a regular makefile. Any targets +defined in a makefile can be used as-is, while targets that would otherwise be +unknown will be forwarded to Ninja. diff --git a/shared/include.cmake b/shared/include.cmake deleted file mode 100644 index c4b01c2..0000000 --- a/shared/include.cmake +++ /dev/null @@ -1,5 +0,0 @@ -include_directories(${CMAKE_CURRENT_LIST_DIR}) -add_library(puzbus STATIC - ${CMAKE_CURRENT_LIST_DIR}/pb/moddrv.c - ) - diff --git a/shared/pb/bus.h b/shared/pb/bus.h deleted file mode 100644 index 6f464c3..0000000 --- a/shared/pb/bus.h +++ /dev/null @@ -1,18 +0,0 @@ -#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/moddrv.c b/shared/pb/moddrv.c deleted file mode 100644 index 1f7fab8..0000000 --- a/shared/pb/moddrv.c +++ /dev/null @@ -1,118 +0,0 @@ -#include <memory.h> - -#include "types.h" -#include "moddrv.h" - -/** \brief [private] placeholder global state variable */ -static enum pb_state _global_state = PB_GS_NOINIT; - -/** \brief [private] main controller global state */ -static enum pb_state _main_state = PB_GS_NOINIT; - -__weak enum pb_state pbdrv_hook_mod_state_read() { - return _global_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; - 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 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(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) { - 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 deleted file mode 100644 index ecfc13a..0000000 --- a/shared/pb/moddrv.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 <stdint.h> -#include <stddef.h> -#include <stdbool.h> - -#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); - -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 - * - * 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, 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 */ -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/types.h b/shared/pb/types.h deleted file mode 100644 index d4a65ed..0000000 --- a/shared/pb/types.h +++ /dev/null @@ -1,78 +0,0 @@ -#pragma once -#include <stdint.h> - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __GNUC__ -#ifndef __packed -#define __packed __attribute__((packed)) -#endif -#ifndef __weak -#define __weak __attribute__((weak)) -#endif -#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_SEX, //!< state exchange - PB_CMD_MAGIC, //!< magic message -}; -// 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 }; -/** \brief magic reply from puzzle module back to main controller */ -static const char pb_magic_res[] = { 0x67, 0x61, 0x6d, 0x69, 0x6e, 0x67 }; - -/** \brief Puzzle bus global states */ -enum __packed pb_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 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 { - const uint8_t address; - const uint8_t data[]; -} pb_cmd_write_t; - -typedef struct __packed { - const enum pb_state main_state; -} pb_cmd_sex_t; - -enum __packed { - PB_ADDR_GS = 0x00, //!< global state address -}; - -#ifdef __cplusplus -} -#endif - diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a280a86..d5d6e0d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,23 +1,24 @@ cmake_minimum_required(VERSION 3.29) -project(puzzlebox_test C CXX ASM) - set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) set(CMAKE_EXPORT_COMPILE_COMMANDS 1) -add_executable(tests - ExampleTest.cpp -) +project(pbtest C CXX ASM) -enable_testing() +add_executable(test + i2ctcp/main.cpp + pbdrv/main.cpp + ) add_subdirectory(lib/googletest) +add_subdirectory(lib/pbdrv) +add_subdirectory(lib/i2ctcp) -target_include_directories(tests PRIVATE ${CMAKE_CURRENT_LIST_DIR}) -target_link_libraries(tests PRIVATE gtest_main) +target_link_libraries(test + gtest_main + i2ctcp + mpack + pbdrv + ) -add_test( - NAME tests - COMMAND tests -) diff --git a/test/ExampleTest.cpp b/test/ExampleTest.cpp deleted file mode 100644 index a3909f5..0000000 --- a/test/ExampleTest.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include <gtest/gtest.h> - -class ExampleTest : public testing::Test { -protected: -}; - -TEST_F(ExampleTest, Test) { EXPECT_EQ(5, 5); }
\ No newline at end of file diff --git a/test/i2ctcp/main.cpp b/test/i2ctcp/main.cpp new file mode 100644 index 0000000..1f0c3ff --- /dev/null +++ b/test/i2ctcp/main.cpp @@ -0,0 +1,46 @@ +#include <algorithm> +#include <gtest/gtest.h> + +#include "i2ctcpv1.h" + +using std::min; + +const uint8_t data[] = { 0xff, 0x00, 0xde, 0xad, 0xbe, 0xef, }; +const size_t data_len = sizeof(data); +const size_t chunk_size = 6; + +char * send_data = nullptr; +size_t send_size = 0; + +TEST(i2ctcp, send) { + i2ctcp_msg_t send_msg = { + .addr = 0x1122, + .data = (char *) data, + .length = data_len, + }; + + ASSERT_TRUE(i2ctcp_write(&send_msg, &send_data, &send_size)); + ASSERT_NE(send_data, nullptr); + ASSERT_GE(send_size, 0); +} + +TEST(i2ctcp, recv) { + i2ctcp_msg_t recv_msg; + i2ctcp_read_reset(&recv_msg); + for (size_t i = 0; i < send_size; i += chunk_size) { + size_t expected_size = min(send_size, i + chunk_size) - i; + + int parsed = i2ctcp_read(&recv_msg, send_data + i, expected_size); + EXPECT_GE(parsed, 0); + + if (i + expected_size == send_size) + EXPECT_EQ(parsed, 0); + else + EXPECT_GT(parsed, 0); + } + + ASSERT_NE(recv_msg.data, nullptr); + ASSERT_EQ(recv_msg.length, data_len); + ASSERT_EQ(0, memcmp(recv_msg.data, data, data_len)); +} + diff --git a/test/makefile b/test/makefile new file mode 100644 index 0000000..7aeee34 --- /dev/null +++ b/test/makefile @@ -0,0 +1,7 @@ +TARGET = $(BUILD_DIR)/test + +include ../lazy.mk + +test: $(TARGET) FORCE + $(TARGET) + diff --git a/test/pbdrv/main.cpp b/test/pbdrv/main.cpp new file mode 100644 index 0000000..de7e88a --- /dev/null +++ b/test/pbdrv/main.cpp @@ -0,0 +1,18 @@ +#include <gtest/gtest.h> + +#include "pb-write.h" + +TEST(pbdrv, write) { + + pbdrv_buf_t buf = pbdrv_write_cmd_req_set_state({ + .header = { .sender = 0xf0, }, + .state = PB_GS_PLAYING, + }); + for (size_t i = 0; i < buf.size; i++) { + printf("%02x ", buf.data[i] & 0xff); + } + printf("\n"); + + ASSERT_TRUE(true); +} + |