aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomasintAnker <thomasintanker1@gmail.com>2024-06-18 16:23:51 +0200
committerThomasintAnker <thomasintanker1@gmail.com>2024-06-18 16:23:51 +0200
commita55d0bed6240c54f6173b1e38e80212c02c302de (patch)
tree07c15eebc8cd84e1071a3f72d3c74475017372f3
parentb45b5d04daa29fcdd456233a931dcbb5b287769f (diff)
parent245fde65808ce902064ab438296f04f691d007e7 (diff)
Merge branch 'master' into wip/handover
-rw-r--r--.clang-format19
-rw-r--r--.clang-tidy25
-rw-r--r--.gitmodules10
-rw-r--r--client/CMakeLists.txt8
-rw-r--r--client/cmd.cpp37
l---------client/compile_commands.json1
-rw-r--r--client/i2c.cpp41
-rw-r--r--client/makefile2
-rw-r--r--client/rl.cpp23
-rw-r--r--client/rl.h1
-rw-r--r--docs/handover.adoc110
-rw-r--r--docs/share/meta.adoc2
-rw-r--r--i2ctcp/i2ctcpv1.c60
-rw-r--r--i2ctcp/include.cmake19
l---------i2ctcp/lib1
-rw-r--r--lazy.mk21
m---------lib/Arduino-CMake-Toolchain0
-rw-r--r--lib/i2ctcp/CMakeLists.txt14
-rw-r--r--lib/i2ctcp/i2ctcpv1.c43
-rw-r--r--lib/i2ctcp/i2ctcpv1.h (renamed from i2ctcp/i2ctcpv1.h)0
l---------lib/i2ctcp/lib1
-rw-r--r--lib/i2ctcp/makefile4
-rw-r--r--lib/i2ctcp/readme.md (renamed from i2ctcp/readme.md)0
-rw-r--r--lib/mpack/CMakeLists.txt28
-rw-r--r--lib/mpack/makefile4
-rw-r--r--lib/mpack/mpack.h19
-rw-r--r--lib/mpack/read-remaining.c10
m---------lib/mpack/src (renamed from lib/mpack)0
-rw-r--r--lib/pbdrv/CMakeLists.txt28
-rw-r--r--lib/pbdrv/drv/arduino/cfg.cmake7
-rw-r--r--lib/pbdrv/drv/arduino/mod.cpp37
-rw-r--r--lib/pbdrv/drv/arduino/mod.h17
-rw-r--r--lib/pbdrv/drv/rp2040/cfg.cmake7
-rw-r--r--lib/pbdrv/drv/rp2040/mod.c73
-rw-r--r--lib/pbdrv/drv/rp2040/mod.h13
l---------lib/pbdrv/lib1
-rw-r--r--lib/pbdrv/makefile4
-rw-r--r--lib/pbdrv/mod/main.h (renamed from shared/pb/mod/main.h)8
-rw-r--r--lib/pbdrv/pb-mod.c26
-rw-r--r--lib/pbdrv/pb-mod.h34
-rw-r--r--lib/pbdrv/pb-read.c3
-rw-r--r--lib/pbdrv/pb-read.h10
-rw-r--r--lib/pbdrv/pb-types.h98
-rw-r--r--lib/pbdrv/pb-write.c35
-rw-r--r--lib/pbdrv/pb-write.h26
-rw-r--r--lib/pbdrv/pb.h22
-rw-r--r--lib/pbdrv/spec.adoc (renamed from shared/pb/spec.adoc)0
-rw-r--r--main/CMakeLists.txt11
-rw-r--r--main/blink.c16
-rw-r--r--main/blink.h4
l---------main/compile_commands.json1
-rw-r--r--main/config.def.h75
-rw-r--r--main/i2c.c179
-rw-r--r--main/i2c.h47
-rw-r--r--main/init.c36
-rw-r--r--main/init.h29
-rw-r--r--main/main.c22
-rw-r--r--main/mod.c5
-rw-r--r--main/readme.md24
-rw-r--r--main/sock.c6
-rw-r--r--main/sock.h2
-rw-r--r--main/tasks.c17
-rw-r--r--main/tasks.h4
-rw-r--r--puzzle/dummy/CMakeLists.txt37
l---------puzzle/dummy/lib1
-rw-r--r--puzzle/dummy/main.cpp36
-rw-r--r--puzzle/dummy/makefile20
-rw-r--r--puzzle/readme.md31
-rw-r--r--readme.md49
-rw-r--r--shared/include.cmake5
-rw-r--r--shared/pb/bus.h18
-rw-r--r--shared/pb/moddrv.c118
-rw-r--r--shared/pb/moddrv.h62
-rw-r--r--shared/pb/types.h78
-rw-r--r--test/CMakeLists.txt25
-rw-r--r--test/ExampleTest.cpp7
-rw-r--r--test/i2ctcp/main.cpp46
-rw-r--r--test/makefile7
-rw-r--r--test/pbdrv/main.cpp18
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
diff --git a/lazy.mk b/lazy.mk
index 2620961..236aef7 100644
--- a/lazy.mk
+++ b/lazy.mk
@@ -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
diff --git a/main/i2c.c b/main/i2c.c
index 5ce0507..b0a0d11 100644
--- a/main/i2c.c
+++ b/main/i2c.c
@@ -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();
+ // }
+ // }
}
+
diff --git a/main/i2c.h b/main/i2c.h
index fcfa23b..6625756 100644
--- a/main/i2c.h
+++ b/main/i2c.h
@@ -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)
+
diff --git a/readme.md b/readme.md
index ac703b7..457ffaa 100644
--- a/readme.md
+++ b/readme.md
@@ -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);
+}
+