aboutsummaryrefslogtreecommitdiff
path: root/lib/pbdrv
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pbdrv')
-rw-r--r--lib/pbdrv/drv/arduino/index.dox19
-rw-r--r--lib/pbdrv/drv/arduino/mod.cpp20
-rw-r--r--lib/pbdrv/drv/index.dox32
-rw-r--r--lib/pbdrv/ext/freertos/index.dox6
-rw-r--r--lib/pbdrv/ext/freertos/pb-mem.c5
-rw-r--r--lib/pbdrv/ext/freertos/pb-mod.c1
-rw-r--r--lib/pbdrv/ext/index.dox24
-rw-r--r--lib/pbdrv/ext/stdlib/index.dox6
-rw-r--r--lib/pbdrv/ext/stdlib/pb-mem.c5
-rw-r--r--lib/pbdrv/index.dox76
-rw-r--r--lib/pbdrv/pb-buf.h20
-rw-r--r--lib/pbdrv/pb-mem.h55
-rw-r--r--lib/pbdrv/pb-mod.h104
-rw-r--r--lib/pbdrv/pb-msg.h43
-rw-r--r--lib/pbdrv/pb-route.c20
-rw-r--r--lib/pbdrv/pb-route.h173
-rw-r--r--lib/pbdrv/pb-send.c14
-rw-r--r--lib/pbdrv/pb-send.h117
-rw-r--r--lib/pbdrv/pb-serial.h76
-rw-r--r--lib/pbdrv/pb-types.h118
-rw-r--r--lib/pbdrv/pb.h28
-rw-r--r--lib/pbdrv/spec.adoc133
22 files changed, 867 insertions, 228 deletions
diff --git a/lib/pbdrv/drv/arduino/index.dox b/lib/pbdrv/drv/arduino/index.dox
new file mode 100644
index 0000000..4c74222
--- /dev/null
+++ b/lib/pbdrv/drv/arduino/index.dox
@@ -0,0 +1,19 @@
+// vim:ft=doxygen
+/**
+\ingroup pb_drv
+\defgroup pb_drv_arduino Arduino
+\brief Arduino (Arduino-CMake-Toolchain) driver
+
+This driver is automatically enabled if the variable \c ARDUINO is defined in
+your CMakeLists.txt (it is by default when using Arduino-CMake-Toolchain).
+
+\note This driver automatically includes the
+\ref pb_ext_freertos "FreeRTOS extension" for deferring calls to \c
+pb_i2c_recv() from the I2C ISR.
+
+This driver is known to work with the following MCUs:
+- ATmega328P (Arduino Uno)
+- ATmega2560 (Arduino Mega)
+
+*/
+
diff --git a/lib/pbdrv/drv/arduino/mod.cpp b/lib/pbdrv/drv/arduino/mod.cpp
index 9130334..2eef8d5 100644
--- a/lib/pbdrv/drv/arduino/mod.cpp
+++ b/lib/pbdrv/drv/arduino/mod.cpp
@@ -42,6 +42,7 @@ static void pb_setup() {
Wire.onReceive(recv_event);
}
+/// \ingroup pb_drv_arduino
__weak void pb_i2c_send(i2c_addr_t addr, const uint8_t * buf, size_t sz) {
Wire.beginTransmission((int) addr);
Wire.write(buf, sz);
@@ -64,7 +65,16 @@ void loop_task() {
}
}
-//! Application entrypoint
+/**
+ * \ingroup pb_drv_arduino
+ * \brief Application entrypoint
+ *
+ * \note I should really be able to use Arduino's initVariant function for
+ * this, but I can't seem to get it to link properly using the CMake setup in
+ * this repository. Overriding the main() function seems to work, and the
+ * USBCON thing in the default Arduino main() function isn't needed because
+ * puzzle modules are likely not using USB.
+ */
int main(void) {
init(); // call arduino internal setup
setup(); // call regular arduino setup
@@ -74,11 +84,3 @@ int main(void) {
return 0;
}
-/**
- * \note I should really be able to use Arduino's initVariant function for
- * this, but I can't seem to get it to link properly using the CMake setup in
- * this repository. Overriding the main() function seems to work, and the
- * USBCON thing in the default Arduino main() function isn't needed because
- * puzzle modules are likely not using USB.
- */
-
diff --git a/lib/pbdrv/drv/index.dox b/lib/pbdrv/drv/index.dox
new file mode 100644
index 0000000..1fe09e2
--- /dev/null
+++ b/lib/pbdrv/drv/index.dox
@@ -0,0 +1,32 @@
+// vim:ft=doxygen
+/**
+\ingroup pbdrv-mod
+\defgroup pb_drv Drivers
+\brief Platform-specific \ref pbdrv-mod implementations
+
+Like \ref pb_ext "extensions", drivers provide platform-specific
+implementations for various functions used in \ref pbdrv-mod.
+
+Drivers are automatically included based on your build configuration, and you
+only need to ensure \c pbdrv-mod is linked with your final executable in order
+to use one of the available drivers:
+
+```cmake
+# include pbdrv
+add_subdirectory(lib/pbdrv)
+
+# link pbdrv-mod
+target_link_libraries(main pbdrv-mod)
+
+```
+
+If there is no existing driver for your target, you may implement the following
+in order to use \ref pbdrv-mod:
+
+- The \c pb_i2c_recv() function must be **called** for every received I2C
+ message
+- The \c pb_i2c_send() function must be **implemented** using the
+ platform/device-specific I2C write function
+
+*/
+
diff --git a/lib/pbdrv/ext/freertos/index.dox b/lib/pbdrv/ext/freertos/index.dox
new file mode 100644
index 0000000..dfa45ff
--- /dev/null
+++ b/lib/pbdrv/ext/freertos/index.dox
@@ -0,0 +1,6 @@
+// vim:ft=doxygen
+/**
+\ingroup pb_ext
+\defgroup pb_ext_freertos FreeRTOS
+\brief FreeRTOS memory management and scheduler-based delay
+*/
diff --git a/lib/pbdrv/ext/freertos/pb-mem.c b/lib/pbdrv/ext/freertos/pb-mem.c
index b18d79f..6647f05 100644
--- a/lib/pbdrv/ext/freertos/pb-mem.c
+++ b/lib/pbdrv/ext/freertos/pb-mem.c
@@ -3,24 +3,29 @@
#include "../../pb-mem.h"
#include "../../pb-types.h"
+/// \ingroup pb_ext_freertos
inline void * pb_malloc(size_t sz) {
return pvPortMalloc(sz);
}
+/// \ingroup pb_ext_freertos
inline void pb_free(void * ptr) {
vPortFree(ptr);
}
+/// \ingroup pb_ext_freertos
__weak inline void * pb_realloc(void * ptr, size_t sz) {
return NULL; // shit out of luck (don't use mpack_writer_init_growable)
}
+/// \ingroup pb_ext_freertos
__weak void * pb_memcpy(void * dest, const void * src, size_t sz) {
for (size_t offset = 0; offset < sz; offset++)
*((char*) dest + offset) = *((char*) src + offset);
return dest;
}
+/// \ingroup pb_ext_freertos
__weak int pb_memcmp(const void * a, const void * b, size_t sz) {
for (size_t offset = 0; offset < sz; offset++) {
int diff = *((char*) a + offset) - *((char*) b + offset);
diff --git a/lib/pbdrv/ext/freertos/pb-mod.c b/lib/pbdrv/ext/freertos/pb-mod.c
index 75495be..5c0aa36 100644
--- a/lib/pbdrv/ext/freertos/pb-mod.c
+++ b/lib/pbdrv/ext/freertos/pb-mod.c
@@ -3,6 +3,7 @@
#include "../../pb-types.h"
+/// \ingroup pb_ext_freertos
__weak void pb_mod_blocking_delay_ms(unsigned long ms) {
vTaskDelay(ms / portTICK_PERIOD_MS);
}
diff --git a/lib/pbdrv/ext/index.dox b/lib/pbdrv/ext/index.dox
new file mode 100644
index 0000000..f7d2bc6
--- /dev/null
+++ b/lib/pbdrv/ext/index.dox
@@ -0,0 +1,24 @@
+// vim:ft=doxygen
+/**
+\ingroup pbdrv
+\ingroup pbdrv-mod
+\defgroup pb_ext Extensions
+\brief Platform-specific \ref pbdrv implementations
+
+Extensions provide platform-specific implementations for various functions used
+in \ref pbdrv, and allows \ref pbdrv to remain completely portable. Extensions
+are used in both \ref pbdrv and \ref pbdrv-mod.
+
+In order to use an extension, include the appropriate CMake lists file for your
+target platform after the \ref pbdrv include:
+
+```cmake
+# include pbdrv
+add_subdirectory(lib/pbdrv)
+
+# use stdlib extension (for use with C standard library)
+include(lib/pbdrv/ext/stdlib/include.cmake)
+
+```
+
+*/
diff --git a/lib/pbdrv/ext/stdlib/index.dox b/lib/pbdrv/ext/stdlib/index.dox
new file mode 100644
index 0000000..756af1e
--- /dev/null
+++ b/lib/pbdrv/ext/stdlib/index.dox
@@ -0,0 +1,6 @@
+// vim:ft=doxygen
+/**
+\ingroup pb_ext
+\defgroup pb_ext_stdlib stdlib
+\brief C stdlib memory management
+*/
diff --git a/lib/pbdrv/ext/stdlib/pb-mem.c b/lib/pbdrv/ext/stdlib/pb-mem.c
index b260c2c..328efbb 100644
--- a/lib/pbdrv/ext/stdlib/pb-mem.c
+++ b/lib/pbdrv/ext/stdlib/pb-mem.c
@@ -3,22 +3,27 @@
#include "../../pb-mem.h"
+/// \ingroup pb_ext_stdlib
inline void * pb_malloc(size_t sz) {
return malloc(sz);
}
+/// \ingroup pb_ext_stdlib
inline void pb_free(void * ptr) {
free(ptr);
}
+/// \ingroup pb_ext_stdlib
inline void * pb_realloc(void * ptr, size_t sz) {
return realloc(ptr, sz);
}
+/// \ingroup pb_ext_stdlib
void * pb_memcpy(void * dest, const void * src, size_t sz) {
return memcpy(dest, src, sz);
}
+/// \ingroup pb_ext_stdlib
int pb_memcmp(const void * a, const void * b, size_t sz) {
return memcmp(a, b, sz);
}
diff --git a/lib/pbdrv/index.dox b/lib/pbdrv/index.dox
new file mode 100644
index 0000000..eb0fd63
--- /dev/null
+++ b/lib/pbdrv/index.dox
@@ -0,0 +1,76 @@
+// vim:ft=doxygen
+/**
+
+\defgroup pbdrv pbdrv
+\brief Standalone puzzle bus driver
+
+\ref pbdrv is a standalone portable static library for handling (i.e.
+(de)serialization) of puzzle bus messages. \ref pbdrv is meant for use within
+applications that handle puzzle bus messages, but are not puzzle modules
+themselves. For a complete puzzle module driver, please see \ref pbdrv-mod.
+
+If you order to use \ref pbdrv, you need to include this folder in your
+CMakeLists.txt file, include the \ref pb_ext "extension" for your target
+platform, and link the \c pbdrv library with your executable:
+
+```cmake
+# include pbdrv
+add_subdirectory(lib/pbdrv)
+
+# <use extension>
+
+# link with executable
+target_link_libraries(main pbdrv)
+```
+
+
+\defgroup pbdrv-mod pbdrv-mod
+\brief Puzzle module driver (superset of \ref pbdrv)
+
+pbdrv-mod is a superset of \ref pbdrv, and includes functions specific to
+puzzle bus modules. \ref pbdrv-mod compiles to an object file instead of a
+static library because it may depend on functions that rely on external
+libraries. \ref pbdrv-mod is still considered standalone, but requires either
+using an existing \ref pb_drv "driver", or (partially) implementing the driver
+functions.
+
+Like \ref pbdrv, \ref pbdrv-mod can be used by including this folder in your
+CMakeLists.txt file and linking the library with your executable. A notable
+difference with \ref pbdrv-mod is that you do not need to include an extension.
+\ref pb_ext "Extensions" are still used by \ref pbdrv-mod, but they are
+included automatically by the target platform's \ref pb_drv "driver". The
+appropriate \ref pb_drv "driver" to load is also automatically detected.
+
+Example:
+
+```cmake
+# include pbdrv
+add_subdirectory(lib/pbdrv)
+
+# link with executable
+target_link_libraries(main pbdrv-mod)
+```
+
+\note In most cases, the \ref pb_hook "hooks" should be sufficient to realize
+extensions or custom behavior not provided by \ref pbdrv-mod.
+
+\note Most \c pb_* 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.
+
+\see pbdrv
+
+\{
+
+\defgroup pb_hook Hook
+\brief Functions for (partially) overriding default behavior
+
+Hooks are functions that allow the user to implement custom behavior (i.e.
+extend or conditionally replace the default handlers), without needing to
+completely overwrite the built-in handlers or understand the internals of \ref
+pbdrv-mod.
+
+\}
+
+*/
diff --git a/lib/pbdrv/pb-buf.h b/lib/pbdrv/pb-buf.h
index 78ee380..8b4bb10 100644
--- a/lib/pbdrv/pb-buf.h
+++ b/lib/pbdrv/pb-buf.h
@@ -6,14 +6,30 @@
extern "C" {
#endif
+/**
+ * \ingroup pbdrv
+ * \ingroup pbdrv-mod
+ * \defgroup pb_buf Buffer
+ * \brief Binary data buffer type used in \ref pbdrv
+ * \{
+ */
+
//! binary buffer struct
typedef struct {
- char * data; //! pointer to data
- size_t size; //! size of data
+ char * data; //!< pointer to data
+ size_t size; //!< size of data
} pb_buf_t;
+/**
+ * \brief free a \c pb_buf_t
+ *
+ * This function calls \c pb_free() on the \c data member of a \c pb_buf_t
+ * struct if it is not equal to \c NULL.
+ */
void pb_buf_free(pb_buf_t * buf);
+/// \}
+
#ifdef __cplusplus
}
#endif
diff --git a/lib/pbdrv/pb-mem.h b/lib/pbdrv/pb-mem.h
index 72be214..4d0f995 100644
--- a/lib/pbdrv/pb-mem.h
+++ b/lib/pbdrv/pb-mem.h
@@ -6,13 +6,68 @@
extern "C" {
#endif
+/**
+ * \ingroup pbdrv
+ * \ingroup pbdrv-mod
+ * \defgroup pb_mem Memory
+ * \brief Platform-specific memory management functions
+ *
+ * \note This header only declares the memory management functions, and it is
+ * up to \ref pb_ext "extensions" to implement the underlying memory management
+ * functions.
+ *
+ * TODO: ref to extensions
+ *
+ * \{
+ */
+
+/**
+ * \brief Allocate a contiguous chunk of memory
+ * \param sz Requested size of memory area
+ * \return Pointer to memory area, or \c NULL on failure
+ * \note The allocated memory must be free'd again using \c pb_free()
+ */
void * pb_malloc(size_t sz);
+/**
+ * \brief Free a chunk of memory previously allocated with \c pb_malloc()
+ * \param ptr Pointer to memory area
+ */
void pb_free(void * ptr);
+/**
+ * \brief Resize the memory area \c ptr to size \c sz
+ * \param ptr Pointer to allocated memory area
+ * \param sz Requested new size of memory area
+ * \return Pointer to memory area, or \c NULL on failure
+ * \warning This function is not available on FreeRTOS, and should be avoided
+ * if possible. MPack's \c mpack_writer_init_growable() is known to use \c
+ * realloc(), and should not be used for this reason.
+ */
void * pb_realloc(void * ptr, size_t sz);
+/**
+ * \brief copy a memory region
+ * \param dest Pointer to destination memory
+ * \param src Pointer to source memory
+ * \param sz Number of bytes to copy
+ * \return Pointer to \c dest
+ *
+ * This function has a portable implementation, and is always available.
+ */
void * pb_memcpy(void * dest, const void * src, size_t sz);
+/**
+ * \brief compare two memory regions
+ * \param a Pointer to first memory region
+ * \param b Pointer to second memory region
+ * \param sz Number of bytes to compare
+ * \return 0 if the memory regions are identical, or the difference between the
+ * first non-matching byte
+ *
+ * This function has a portable implementation, and is always available.
+ */
int pb_memcmp(const void * a, const void * b, size_t sz);
+/// \}
+
#ifdef __cplusplus
}
#endif
diff --git a/lib/pbdrv/pb-mod.h b/lib/pbdrv/pb-mod.h
index ae36d22..3869e55 100644
--- a/lib/pbdrv/pb-mod.h
+++ b/lib/pbdrv/pb-mod.h
@@ -1,38 +1,108 @@
#pragma once
-/**
- * \file puzzle bus driver implementation
- *
- * Most \c pb_* 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 "pb-types.h"
#ifdef __cplusplus
extern "C" {
#endif
-//! puzzle module name (optional, default = "???")
+/**
+ * \ingroup pbdrv-mod
+ * \defgroup pb_mod Module
+ * \brief Puzzle module metadata and auxiliary utility functions
+ * \{
+ */
+
+/**
+ * \brief Puzzle module name
+ * \note This constant is optional to define, its default value is "???"
+ */
extern const char * PB_MOD_NAME;
-//! puzzle module bus address (required)
+/**
+ * \brief Puzzle module bus address
+ * \warning This variable **must** be defined by the user
+ */
extern const i2c_addr_t PB_MOD_ADDR;
+/**
+ * \brief Platform-specific blocking delay function
+ *
+ * FIXME: this entire function should be removed (see handover: RP2040 I2C
+ * limitations)
+ */
+void pb_mod_blocking_delay_ms(unsigned long ms);
+
+/// \}
+
+/**
+ * \ingroup pbdrv-mod
+ * \defgroup pb_i2c I2C
+ * \brief I2C send/receive handlers
+ *
+ * \{
+ */
+
+/**
+ * \brief Handle a received message from the I2C bus (puzzle bus)
+ *
+ * This function attempts to parse an I2C message as a puzzle bus message, and
+ * calls the appropriate handler for the message if it is considered valid.
+ * Invalid messages are silently ignored.
+ *
+ * \param buf pointer to message content
+ * \param sz size of \p buf
+ *
+ * \note This function should not be directly called from an ISR. Please use
+ * FreeRTOS's \c xTimerPendFunctionCallFromISR() or a similar scheduler-based
+ * deferred function call mechanism instead.
+ */
void pb_i2c_recv(const uint8_t * buf, size_t sz);
+/**
+ * \brief Send a message in master-mode on the I2C bus (puzzle bus)
+ *
+ * This function sends an I2C message to the address specified by \p i2c_addr.
+ *
+ * \param i2c_addr address of slave controller
+ * \param buf pointer to message content
+ * \param sz size of \p buf
+ */
void pb_i2c_send(i2c_addr_t i2c_addr, const uint8_t * buf, size_t sz);
-pb_global_state_t pb_hook_mod_state_read();
-void pb_hook_mod_state_write(pb_global_state_t state);
+/// \}
+
+/// \ingroup pb_hook
+/// \{
+
+/**
+ * \defgroup pb_hook_mod_state State
+ * \brief Provide your own global state variable
+ *
+ * If your puzzle module defines its own global \c pb_global_state_t, you can
+ * tell the driver to use it by implementing these functions. These functions
+ * are also used internally by the driver.
+ *
+ * \{
+ */
/**
- * \brief platform-specific blocking delay function
+ * \brief global state read hook
+ * \return current value of global state enum
*
- * FIXME: this should be removed (see handover: RP2040 I2C limitations)
+ * The default implementation of this function uses an internal global state
+ * variable in \ref pbdrv.
*/
-void pb_mod_blocking_delay_ms(unsigned long ms);
+pb_global_state_t pb_hook_mod_state_read();
+/**
+ * \brief global state write hook
+ * \param state new value of global state enum
+ *
+ * The default implementation of this function uses an internal global state
+ * variable in \ref pbdrv.
+ */
+void pb_hook_mod_state_write(pb_global_state_t state);
+
+/// \}
+/// \}
#ifdef __cplusplus
}
diff --git a/lib/pbdrv/pb-msg.h b/lib/pbdrv/pb-msg.h
index f27d4c4..ff5bcde 100644
--- a/lib/pbdrv/pb-msg.h
+++ b/lib/pbdrv/pb-msg.h
@@ -7,10 +7,53 @@
extern "C" {
#endif
+/**
+ * \ingroup pbdrv
+ * \ingroup pbdrv-mod
+ * \defgroup pb_msg Message
+ * \brief Message (de)serialization
+ * \{
+ */
+
+/**
+ * \brief Serialize a message into a binary buffer
+ *
+ * \note This function allocates a \c pb_buf_t that should be free'd using \c
+ * pb_buf_free()
+ *
+ * \param msg Message to serialize
+ *
+ * \warning The type of \c msg->cmd is inferred from \c msg->type. If the
+ * message is not correctly formatted, this function may cause undefined
+ * behavior. If possible, use functions from \ref pb_send instead.
+ *
+ * \return \c pb_buf_t containing the serialized message, or an empty struct if
+ * serialization failed
+ */
pb_buf_t pb_msg_write(const pb_msg_t * msg);
+/**
+ * \brief Read a binary buffer and attempt to deserialize it as a puzzle bus
+ * message
+ *
+ * \note This function allocates a \c pb_msg_t pointer that should be free'd
+ * using \c pb_msg_free()
+ *
+ * \param buf Binary data to interpret as puzzle bus message
+ *
+ * \return \c pb_msg_t pointer containing the deserialized message, or NULL if
+ * serialization failed
+ */
pb_msg_t * pb_msg_read(const pb_buf_t * buf);
+/**
+ * \brief Recursively free fields of a \c pb_msg_t
+ *
+ * \note The \p msg pointer itself is also free'd by this function. You should
+ * set it to NULL afterwards to avoid confusion.
+ */
void pb_msg_free(pb_msg_t * msg);
+/// \}
+
#ifdef __cplusplus
}
#endif
diff --git a/lib/pbdrv/pb-route.c b/lib/pbdrv/pb-route.c
index ee47700..f5c32d6 100644
--- a/lib/pbdrv/pb-route.c
+++ b/lib/pbdrv/pb-route.c
@@ -56,19 +56,27 @@ __weak void pb_route_cmd_prop_req(pb_msg_t * msg) {}
__weak void pb_route_cmd_prop_res(pb_msg_t * msg) {}
__weak void pb_route_cmd_prop_set(pb_msg_t * msg) {}
+//! last known global state of last STATE REQ sender (i.e. main controller)
static pb_global_state_t _main_state = PB_GS_NOINIT;
-__weak void pb_hook_main_state_update(pb_global_state_t state) {}
+__weak void pb_hook_ev_main_state_update(pb_global_state_t state) {}
+__weak void pb_hook_ev_module_init() {
+ pb_hook_mod_state_write(PB_GS_IDLE);
+}
__weak void pb_route_cmd_state_req(pb_msg_t * msg) {
pb_global_state_t own_state = pb_hook_mod_state_read();
- pb_buf_t buf = pb_send_state_res(own_state);
- pb_send_reply(msg, &buf);
- pb_buf_free(&buf);
// notify of new global state variable
pb_cmd_state_t * cmd = msg->cmd;
- if (cmd->state != _main_state)
- pb_hook_main_state_update(cmd->state);
+ if (cmd->state != _main_state) {
+ // first STATE REQ = module init OK
+ if (_main_state == PB_GS_NOINIT) pb_hook_ev_module_init();
+ pb_hook_ev_main_state_update(cmd->state);
+ }
_main_state = cmd->state;
+
+ pb_buf_t buf = pb_send_state_res();
+ pb_send_reply(msg, &buf);
+ pb_buf_free(&buf);
}
__weak void pb_route_cmd_state_res(pb_msg_t * msg) {}
diff --git a/lib/pbdrv/pb-route.h b/lib/pbdrv/pb-route.h
index 41c009a..b80e4ec 100644
--- a/lib/pbdrv/pb-route.h
+++ b/lib/pbdrv/pb-route.h
@@ -6,31 +6,188 @@
extern "C" {
#endif
-void pb_route_msg(pb_msg_t * msg);
+/**
+ * \ingroup pbdrv-mod
+ * \defgroup pb_route Routing
+ * \internal
+ * \brief Parsed message handler routing
+ *
+ * These functions form a tree-shaped call graph, and are used to handle
+ * specific commands received from \c pb_i2c_recv().
+ *
+ * \{
+ */
-bool pb_hook_route_msg(pb_msg_t * msg);
+/**
+ * \brief Handle a message with type {\ref PB_CMD_PROP "PROP", \ref
+ * PB_CMD_STATE "STATE", \ref PB_CMD_MAGIC "MAGIC"}
+ *
+ * Calls the next handler depending on \c msg->type.
+ */
+void pb_route_msg(pb_msg_t * msg);
+/**
+ * \brief Handle a \ref PB_CMD_PROP "PROP" message with action {\ref
+ * pb_route_cmd_prop_req "REQ", \ref pb_route_cmd_prop_res "RES", \ref
+ * pb_route_cmd_prop_set "SET"}
+ *
+ * Calls the next handler depending on \c msg->action.
+ */
void pb_route_cmd_prop(pb_msg_t * msg);
+/**
+ * \brief Handle a \ref PB_CMD_STATE "STATE" message with action {\ref
+ * pb_route_cmd_state_req "REQ", \ref pb_route_cmd_state_res "RES", \ref
+ * pb_route_cmd_state_set "SET"}
+ *
+ * Calls the next handler depending on \c msg->action.
+ */
void pb_route_cmd_state(pb_msg_t * msg);
+/**
+ * \brief Handle a \ref PB_CMD_MAGIC "MAGIC" message with action {\ref
+ * pb_route_cmd_magic_req "REQ", \ref pb_route_cmd_magic_res "RES"}
+ *
+ * Calls the next handler depending on \c msg->action.
+ *
+ * \note Messages with type \c MAGIC and action \c SET will be silently
+ * ignored, as there is no such command.
+ */
void pb_route_cmd_magic(pb_msg_t * msg);
-bool pb_hook_route_cmd_prop(pb_msg_t * msg);
-bool pb_hook_route_cmd_state(pb_msg_t * msg);
-bool pb_hook_route_cmd_magic(pb_msg_t * msg);
-
+/**
+ * \brief Handle a \ref PB_CMD_PROP "PROP" message with action \ref
+ * PB_ACTION_REQ "REQ"
+ *
+ * The default implementation of this function is empty, as puzzle module
+ * properties are user-defined.
+ */
void pb_route_cmd_prop_req(pb_msg_t * msg);
+/**
+ * \brief Handle a \ref PB_CMD_PROP "PROP" message with action \ref
+ * PB_ACTION_RES "RES"
+ *
+ * The default implementation of this function is empty, as puzzle module
+ * properties are user-defined.
+ */
void pb_route_cmd_prop_res(pb_msg_t * msg);
+/**
+ * \brief Handle a \ref PB_CMD_PROP "PROP" message with action \ref
+ * PB_ACTION_SET "SET"
+ *
+ * The default implementation of this function is empty, as puzzle module
+ * properties are user-defined.
+ */
void pb_route_cmd_prop_set(pb_msg_t * msg);
+/**
+ * \brief Handle a \ref PB_CMD_STATE "STATE" message with action \ref
+ * PB_ACTION_REQ "REQ"
+ *
+ * The default implementation of this function does the following:
+ * - Call \c pb_hook_ev_module_init() if this is the first received \c STATE \c
+ * REQ command.
+ * - Call \c pb_hook_ev_main_state_update() if the main controller state has
+ * changed.
+ * - Reply with a \c STATE \c RES message.
+ */
void pb_route_cmd_state_req(pb_msg_t * msg);
+/**
+ * \brief Handle a \ref PB_CMD_STATE "STATE" message with action \ref
+ * PB_ACTION_RES "RES"
+ *
+ * The default implementation of this function is empty, as only the main
+ * controller handles this type of command.
+ */
void pb_route_cmd_state_res(pb_msg_t * msg);
+/**
+ * \brief Handle a \ref PB_CMD_STATE "STATE" message with action \ref
+ * PB_ACTION_SET "SET"
+ *
+ * The default implementation of this function does the following:
+ * - Write the global state variable using \c pb_hook_mod_state_write().
+ */
void pb_route_cmd_state_set(pb_msg_t * msg);
-void pb_hook_main_state_update(pb_global_state_t state);
-
+/**
+ * \brief Handle a \ref PB_CMD_MAGIC "MAGIC" message with action \ref
+ * PB_ACTION_REQ "REQ"
+ *
+ * The default implementation of this function does the following:
+ * - Verify the size of the magic string
+ * - Verify the content of the magic string
+ * - Reply with a \c MAGIC \c RES message.
+ */
void pb_route_cmd_magic_req(pb_msg_t * msg);
+/**
+ * \brief Handle a \ref PB_CMD_MAGIC "MAGIC" message with action \ref
+ * PB_ACTION_RES "RES"
+ *
+ * The default implementation of this function is empty, as only the main
+ * controller handles this type of command.
+ */
void pb_route_cmd_magic_res(pb_msg_t * msg);
+/// \}
+
+/**
+ * \ingroup pb_hook
+ * \defgroup pb_hook_route Routing
+ * \brief Conditionally use substitute or extend the built-in message handlers
+ * \{
+ */
+
+/**
+ * \brief \c pb_route_msg() hook
+ *
+ * The default implementation of this function immediately returns false.
+ *
+ * \return \c true if execution should continue to the default handler, or \c
+ * false if it should stop (i.e. the message was handled).
+ */
+bool pb_hook_route_msg(pb_msg_t * msg);
+
+//! \c pb_route_cmd_prop() hook \copydetails pb_hook_route_msg
+bool pb_hook_route_cmd_prop(pb_msg_t * msg);
+//! \c pb_route_cmd_state() hook \copydetails pb_hook_route_msg
+bool pb_hook_route_cmd_state(pb_msg_t * msg);
+//! \c pb_route_cmd_magic() hook \copydetails pb_hook_route_msg
+bool pb_hook_route_cmd_magic(pb_msg_t * msg);
+
+/// \}
+
+/**
+ * \ingroup pb_hook
+ * \defgroup pb_hook_ev Events
+ * \brief Functions called on puzzle bus-related events
+ * \{
+ */
+
+/**
+ * \brief Main controller state update hook
+ *
+ * The default implementation of this function is empty.
+ *
+ * \param state New state of main controller
+ *
+ * \note This function is also called when the first \c STATE \c REQ command is
+ * received, as the main controller state variable used to check if the state
+ * actually changed is initialized to \ref PB_GS_NOINIT. In this case, this
+ * function is called *after* \c pb_hook_ev_module_init().
+ */
+void pb_hook_ev_main_state_update(pb_global_state_t state);
+/**
+ * \brief Module initialized hook
+ *
+ * The default implementation of this function calls \c
+ * pb_hook_mod_state_write() with \ref PB_GS_IDLE.
+ *
+ * This function is called when the first \c STATE \c REQ command is received,
+ * indicating this puzzle module has been registered successfully by the main
+ * controller, and is now part of an active play session.
+ */
+void pb_hook_ev_module_init();
+
+/// \}
+
#ifdef __cplusplus
}
#endif
diff --git a/lib/pbdrv/pb-send.c b/lib/pbdrv/pb-send.c
index 66c43c1..dc34c44 100644
--- a/lib/pbdrv/pb-send.c
+++ b/lib/pbdrv/pb-send.c
@@ -2,7 +2,7 @@
#include "pb-mod.h"
#include "pb-msg.h"
-__weak void pb_send_reply(pb_msg_t * msg, pb_buf_t * reply) {
+__weak void pb_send_reply(const pb_msg_t * msg, const pb_buf_t * reply) {
return pb_i2c_send(msg->sender, (uint8_t *) reply->data, reply->size);
}
@@ -21,10 +21,10 @@ pb_buf_t pb_send_read_req(uint8_t propid) {
return pb_msg_write(&msg);
}
-pb_buf_t pb_send_read_res(uint8_t propid, uint8_t * value, size_t size) {
+pb_buf_t pb_send_read_res(uint8_t propid, const uint8_t * value, size_t size) {
pb_cmd_prop_t cmd = {
.propid = propid,
- .value = value,
+ .value = (uint8_t *) value,
._value_size = size,
};
pb_msg_t msg = {
@@ -36,10 +36,10 @@ pb_buf_t pb_send_read_res(uint8_t propid, uint8_t * value, size_t size) {
return pb_msg_write(&msg);
}
-pb_buf_t pb_send_write_req(uint8_t propid, uint8_t * value, size_t size) {
+pb_buf_t pb_send_write_req(uint8_t propid, const uint8_t * value, size_t size) {
pb_cmd_prop_t cmd = {
.propid = propid,
- .value = value,
+ .value = (uint8_t *) value,
._value_size = size,
};
pb_msg_t msg = {
@@ -64,9 +64,9 @@ pb_buf_t pb_send_state_req() {
return pb_msg_write(&msg);
}
-pb_buf_t pb_send_state_res(pb_global_state_t state) {
+pb_buf_t pb_send_state_res() {
pb_cmd_state_t cmd = {
- .state = state,
+ .state = pb_hook_mod_state_read(),
};
pb_msg_t msg = {
.type = PB_CMD_STATE,
diff --git a/lib/pbdrv/pb-send.h b/lib/pbdrv/pb-send.h
index 2f8be1e..7e21eda 100644
--- a/lib/pbdrv/pb-send.h
+++ b/lib/pbdrv/pb-send.h
@@ -7,17 +7,126 @@
extern "C" {
#endif
-void pb_send_reply(pb_msg_t * msg, pb_buf_t * reply);
+/**
+ * \ingroup pbdrv-mod
+ * \defgroup pb_send Send
+ * \brief Functions for directly creating serialized message buffers
+ * \{
+ */
+/**
+ * \brief Utility function for replying to a message
+ *
+ * \param msg Message to reply to
+ * \param reply Data to send as reply
+ *
+ * This function uses \c pb_i2c_send() to send \p reply to \p msg->sender.
+ */
+void pb_send_reply(const pb_msg_t * msg, const pb_buf_t * reply);
+
+/**
+ * \brief Create a serialized \ref PB_CMD_PROP "PROP" \ref PB_ACTION_REQ "REQ"
+ * message
+ *
+ * \param propid Property ID to request
+ *
+ * \return Message buffer
+ *
+ * \note The buffer returned by this function must be free'd with \c
+ * pb_buf_free().
+ */
pb_buf_t pb_send_read_req(uint8_t propid);
-pb_buf_t pb_send_read_res(uint8_t propid, uint8_t * value, size_t size);
-pb_buf_t pb_send_write_req(uint8_t propid, uint8_t * value, size_t size);
+/**
+ * \brief Create a serialized \ref PB_CMD_PROP "PROP" \ref PB_ACTION_RES "RES"
+ * message
+ *
+ * \param propid Requested property ID
+ * \param value Pointer to structured data in property
+ * \param size Size of \p value
+ *
+ * \return Message buffer
+ *
+ * \note The buffer returned by this function must be free'd with \c
+ * pb_buf_free().
+ */
+pb_buf_t pb_send_read_res(uint8_t propid, const uint8_t * value, size_t size);
+/**
+ * \brief Create a serialized \ref PB_CMD_PROP "PROP" \ref PB_ACTION_SET "SET"
+ * message
+ *
+ * \param propid Property ID to write
+ * \param value Pointer to data to write to property
+ * \param size Size of \p value
+ *
+ * \return Message buffer
+ *
+ * \note The buffer returned by this function must be free'd with \c
+ * pb_buf_free().
+ */
+pb_buf_t pb_send_write_req(uint8_t propid, const uint8_t * value, size_t size);
+/**
+ * \brief Create a serialized \ref PB_CMD_STATE "STATE" \ref PB_ACTION_REQ
+ * "REQ" message
+ *
+ * The current module's state is obtained using \c pb_hook_mod_state_read().
+ *
+ * \return Message buffer
+ *
+ * \note The buffer returned by this function must be free'd with \c
+ * pb_buf_free().
+ */
pb_buf_t pb_send_state_req();
-pb_buf_t pb_send_state_res(pb_global_state_t state);
+/**
+ * \brief Create a serialized \ref PB_CMD_STATE "STATE" \ref PB_ACTION_RES
+ * "RES" message
+ *
+ * The current module's state is obtained using \c pb_hook_mod_state_read().
+ *
+ * \return Message buffer
+ *
+ * \note The buffer returned by this function must be free'd with \c
+ * pb_buf_free().
+ */
+pb_buf_t pb_send_state_res();
+/**
+ * \brief Create a serialized \ref PB_CMD_STATE "STATE" \ref PB_ACTION_SET
+ * "SET" message
+ *
+ * \param state Requested new state
+ *
+ * \return Message buffer
+ *
+ * \note The buffer returned by this function must be free'd with \c
+ * pb_buf_free().
+ */
pb_buf_t pb_send_state_set(pb_global_state_t state);
+/**
+ * \brief Create a serialized \ref PB_CMD_MAGIC "MAGIC" \ref PB_ACTION_REQ
+ * "REQ" message
+ *
+ * The magic string is equal to \ref pb_cmd_magic_req.
+ *
+ * \return Message buffer
+ *
+ * \note The buffer returned by this function must be free'd with \c
+ * pb_buf_free().
+ */
pb_buf_t pb_send_magic_req();
+/**
+ * \brief Create a serialized \ref PB_CMD_MAGIC "MAGIC" \ref PB_ACTION_RES
+ * "RES" message
+ *
+ * The magic string is equal to \ref pb_cmd_magic_res.
+ *
+ * \return Message buffer
+ *
+ * \note The buffer returned by this function must be free'd with \c
+ * pb_buf_free().
+ */
pb_buf_t pb_send_magic_res();
+/// \}
+
#ifdef __cplusplus
}
#endif
diff --git a/lib/pbdrv/pb-serial.h b/lib/pbdrv/pb-serial.h
index d3d0007..79f08d7 100644
--- a/lib/pbdrv/pb-serial.h
+++ b/lib/pbdrv/pb-serial.h
@@ -8,23 +8,77 @@
extern "C" {
#endif
-#define __pb_cmd(name) \
- pb_ser_r_t pb_ser_r_##name; \
- pb_ser_w_t pb_ser_w_##name; \
- pb_ser_free_t pb_ser_free_##name;
+/**
+ * \ingroup pbdrv
+ * \ingroup pbdrv-mod
+ * \defgroup pb_ser Serial
+ * \internal
+ * \brief Internal (de)serialization functions using mpack
+ *
+ * \{
+ */
+/**
+ * \brief Write (serialize) message fields using mpack
+ *
+ * \param writer Pointer to \c mpack_writer_t instance
+ * \param msg Pointer to message struct to read from
+ */
typedef void pb_ser_w_t(mpack_writer_t * writer, const pb_msg_t * msg);
-pb_ser_w_t pb_ser_w;
-
+/**
+ * \brief Read (deserialize) message fields using mpack
+ *
+ * \param reader Pointer to \c mpack_reader_t instance
+ * \param msg Pointer to message struct to write to
+ */
typedef void pb_ser_r_t(mpack_reader_t * reader, pb_msg_t * msg);
-pb_ser_r_t pb_ser_r;
-
+/**
+ * \brief Recursively free message struct fields
+ *
+ * \param msg Pointer to message struct to free
+ */
typedef void pb_ser_free_t(pb_msg_t * msg);
+
+/**
+ * \brief Write the \ref pb_msg_t header fields and call another function for
+ * \p msg->cmd.
+ * \see pb_ser_w_t
+ */
+pb_ser_w_t pb_ser_w;
+/**
+ * \brief Read the \ref pb_msg_t header fields and call another function for \p
+ * msg->cmd.
+ * \see pb_ser_r_t
+ */
+pb_ser_r_t pb_ser_r;
+/**
+ * \brief Call another function for \p msg->cmd.
+ * \see pb_ser_free_t
+ */
pb_ser_free_t pb_ser_free;
-__pb_cmd(cmd_prop)
-__pb_cmd(cmd_state)
-__pb_cmd(cmd_magic)
+//! Write the \ref pb_cmd_prop_t fields \see pb_ser_w_t
+pb_ser_w_t pb_ser_w_cmd_prop;
+//! Read the \ref pb_cmd_prop_t fields \see pb_ser_r_t
+pb_ser_r_t pb_ser_r_cmd_prop;
+//! Free the \ref pb_cmd_prop_t fields \see pb_ser_free_t
+pb_ser_free_t pb_ser_free_cmd_prop;
+
+//! Write the \ref pb_cmd_state_t fields \see pb_ser_w_t
+pb_ser_w_t pb_ser_w_cmd_state;
+//! Read the \ref pb_cmd_state_t fields \see pb_ser_r_t
+pb_ser_r_t pb_ser_r_cmd_state;
+//! Free the \ref pb_cmd_state_t fields \see pb_ser_free_t
+pb_ser_free_t pb_ser_free_cmd_state;
+
+//! Write the \ref pb_cmd_magic_t fields \see pb_ser_w_t
+pb_ser_w_t pb_ser_w_cmd_magic;
+//! Read the \ref pb_cmd_magic_t fields \see pb_ser_r_t
+pb_ser_r_t pb_ser_r_cmd_magic;
+//! Free the \ref pb_cmd_magic_t fields \see pb_ser_free_t
+pb_ser_free_t pb_ser_free_cmd_magic;
+
+/// \}
#ifdef __cplusplus
}
diff --git a/lib/pbdrv/pb-types.h b/lib/pbdrv/pb-types.h
index 4d085f9..ef3df54 100644
--- a/lib/pbdrv/pb-types.h
+++ b/lib/pbdrv/pb-types.h
@@ -8,73 +8,137 @@
extern "C" {
#endif
+/**
+ * \ingroup pbdrv
+ * \ingroup pbdrv-mod
+ * \defgroup pb_types Types
+ * \brief Datatypes used within \ref pbdrv
+ *
+ * \{
+ */
+
#ifdef __GNUC__
#define __weak __attribute__((weak))
#endif
#ifndef __weak
#error Could not determine weak attribute for current compiler
+//! Mark function as weak (allow user to override implementation)
#define __weak
#endif
//! I2C address (10 or 7 bit)
typedef uint16_t i2c_addr_t;
-//! puzzle bus command types
-enum pb_cmd_id {
- PB_CMD_PROP, //!< puzzle module property
- PB_CMD_STATE, //!< global state
- PB_CMD_MAGIC, //!< magic (handshake)
-};
-typedef enum pb_cmd_id pb_cmd_id_t;
+//! Puzzle bus command types
+typedef enum {
+ /**
+ * \brief puzzle module property (\ref pb_route_cmd_prop_req "REQ", \ref
+ * pb_route_cmd_prop_res "RES", \ref pb_route_cmd_prop_set "SET")
+ *
+ * The \c PROP command type is used for exchanging arbitrary data between
+ * puzzle modules and/or the puzzle box client (pbc) over the TCP bridge.
+ * These properties are not used by the puzzle framework.
+ */
+ PB_CMD_PROP,
+ /**
+ * \brief puzzle module global state variable (\ref pb_route_cmd_state_req
+ * "REQ", \ref pb_route_cmd_state_res "RES", \ref pb_route_cmd_state_set
+ * "SET")
+ *
+ * The \c STATE command is used by puzzle modules to inform the main
+ * controller about their global state. The main controller aggregates the
+ * states of all connected puzzle modules and exchanges this aggregated state
+ * with the puzzle modules to indicate when the entire puzzle box is solved.
+ */
+ PB_CMD_STATE,
+ /**
+ * \brief magic (handshake) (\ref pb_route_cmd_magic_req "REQ", \ref
+ * pb_route_cmd_magic_res "RES")
+ *
+ * The \c MAGIC command effectively serves as a 'secret handshake' (using a
+ * _magic_ value) which is used to distinguish between puzzle modules and
+ * unrelated I2C devices.
+ */
+ PB_CMD_MAGIC,
+} pb_cmd_id_t;
-//! puzzle bus command action types
-enum pb_action {
+//! Puzzle bus command action types
+typedef enum {
PB_ACTION_REQ, //!< request
PB_ACTION_RES, //!< response
PB_ACTION_SET, //!< (over)write
-};
-typedef enum pb_action pb_action_t;
+} pb_action_t;
-//! puzzle bus global states
-enum pb_global_state {
+//! Puzzle module global states
+typedef enum {
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;
+} pb_global_state_t;
-//! magic sent from main controller to puzzle module
+/**
+ * \brief Magic sent from main controller to puzzle module (="puzbus")
+ *
+ * The size of this array can be obtained by \c sizeof(pb_cmd_magic_req).
+ */
static const char pb_cmd_magic_req[] = { 0x70, 0x75, 0x7a, 0x62, 0x75, 0x73 };
-//! magic reply from puzzle module back to main controller
+/**
+ * \brief Magic reply from puzzle module back to main controller (="gaming")
+ *
+ * The size of this array can be obtained by \c sizeof(pb_cmd_magic_res).
+ */
static const char pb_cmd_magic_res[] = { 0x67, 0x61, 0x6d, 0x69, 0x6e, 0x67 };
-//! puzzle bus message header (shared by all commands)
+//! puzzle bus message header / container (shared by all commands)
typedef struct {
- pb_cmd_id_t type; //!< command type
- pb_action_t action; //!< command action
- i2c_addr_t sender; //!< i2c address of sender
- void * cmd; //!< command data (type dependant)
+ /**
+ * \brief Command type (see \ref pb_cmd_id_t)
+ *
+ * This is used to identify what the message is about.
+ */
+ pb_cmd_id_t type;
+ /**
+ * \brief Command action (see \ref pb_action_t)
+ *
+ * This is used to specify what should happen as a result of this message.
+ */
+ pb_action_t action;
+ /**
+ * \brief I2C address of sender
+ *
+ * This is used to facilitate the 'network' features, as the sender of an I2C
+ * write is unknown.
+ */
+ i2c_addr_t sender;
+ /**
+ * \brief Command data (dependent on \p type)
+ *
+ * Struct containing command type-specific data.
+ */
+ void * cmd;
} pb_msg_t;
-//! PB_CMD_PROP data
+//! \ref PB_CMD_PROP data
typedef struct {
uint8_t propid; //!< id of state property
uint8_t * value; //!< new or current value
- size_t _value_size; //!< [META] size of \p value
+ size_t _value_size; //!< size of \p value
} pb_cmd_prop_t;
-//! PB_CMD_STATE data
+//! \ref PB_CMD_STATE data
typedef struct {
pb_global_state_t state; //!< global state
} pb_cmd_state_t;
-//! PB_CMD_MAGIC data
+//! \ref PB_CMD_MAGIC data
typedef struct {
char * magic; //!< magic value
- size_t _magic_size; //!< [META] size of \p magic
+ size_t _magic_size; //!< size of \p magic
} pb_cmd_magic_t;
+/// \}
+
#ifdef __cplusplus
}
#endif
diff --git a/lib/pbdrv/pb.h b/lib/pbdrv/pb.h
index 0f2e9d1..cef04d8 100644
--- a/lib/pbdrv/pb.h
+++ b/lib/pbdrv/pb.h
@@ -1,23 +1,43 @@
#pragma once
+/**
+ * \ingroup pbdrv
+ * \ingroup pbdrv-mod
+ * \defgroup pb Bus
+ * \brief Constants for the puzzle bus hardware
+ * \{
+ */
+
+//! I2C bus speed in hertz (100 KHz)
#define PB_CLOCK_SPEED_HZ 100000
+//! I2C bus timeout delay in milliseconds
#define PB_TIMEOUT_MS 10
+//! I2C bus timeout delay in microseconds
#define PB_TIMEOUT_US (1e3 * PB_TIMEOUT_MS)
-// Adafruit NeoTrellis modules
+//! Adafruit NeoTrellis module 1 I2C address
#define PB_ADDR_ADA_NEO_1 0x2E
+//! Adafruit NeoTrellis module 2 I2C address
#define PB_ADDR_ADA_NEO_2 0x2F
+//! Adafruit NeoTrellis module 3 I2C address
#define PB_ADDR_ADA_NEO_3 0x30
+//! Adafruit NeoTrellis module 4 I2C address
#define PB_ADDR_ADA_NEO_4 0x32
-// Main controller
+//! Main controller I2C address
#define PB_ADDR_MOD_MAIN 0x08
-// Puzzle modules
+//! NeoTrellis puzzle module I2C address
#define PB_ADDR_MOD_NEOTRELLIS 0x21
+//! Software puzzle module I2C address
#define PB_ADDR_MOD_SOFTWARE 0x22
+//! Hardware puzzle module I2C address
#define PB_ADDR_MOD_HARDWARE 0x23
+//! Vault puzzle module I2C address
#define PB_ADDR_MOD_VAULT 0x24
-// #define BUSADDR_MOD_AUTOMATION 0x25
+//! Automation puzzle module I2C address
+#define BUSADDR_MOD_AUTOMATION 0x25
+//! Dummy puzzle module I2C address
#define PB_ADDR_MOD_DUMMY 0x69
+/// \}
diff --git a/lib/pbdrv/spec.adoc b/lib/pbdrv/spec.adoc
deleted file mode 100644
index 3172e84..0000000
--- a/lib/pbdrv/spec.adoc
+++ /dev/null
@@ -1,133 +0,0 @@
-= Puzzle module specification
-
-This folder contains an implementation of the puzzle bus protocol
-specification, and is targeted at puzzle module developers. This document
-describes the required implementation steps for integrating a new game into the
-puzzle module framework.
-
-== The bus
-
-The puzzle bus carries data over a standard I^2^C bus. Additional details about
-this bus can be found in the link:../../docs/design.adoc[Design document].
-
-The following details are important to puzzle module developers, as they may
-cause unexpected behavior:
-
-- *Addresses influence the puzzle box's behavior*. The order of puzzles is
- determined by the puzzle module address. Two puzzle modules may use the same
- address, but this will mean that they cannot be used simultaniously in the
- same puzzle box. Known addresses are documented in link:bus.h[].
-- *The read/write bit of an I^2^C frame determines how it's handled*. I^2^C
- *read* frames are treated as requests, while *write* frames are treated as
- responses.
-
-== Puzzle bus driver (pbdrv)
-
-The library in this folder is a partial implementation of the puzzle bus
-specification *for puzzle modules*. Most functions in the driver are marked
-with the 'weak' attribute, which allows you to override them by providing an
-implementation.
-
-In order to utilize this driver, the following must be done:
-
-- The ``pbdrv_i2c_recv`` function must be *called* for every received *I^2^C
- read* frame
-- The ``pbdrv_i2c_send`` function must be *implemented* with the
- platform-specific *I^2^C write* function
-
-This is enough to get the puzzle module registered. You may also want to
-implement some of the following integrations:
-
-- If your game uses the global state variable, you should implement the
- <<sec:state-global,global state hooks>> to point the driver to your own
- global state variable, and be notified of reads/writes to it.
-- If you want to expose additional game state variables over the puzzle bus,
- you should implement the <<sec:state-aux,auxiliary state hooks>>.
-- If you want to implement custom puzzle bus commands, you can implement the
- <<sec:cmd,command hook>>.
-
-All other kinds of integrations/hooks can likely be realized by overriding the
-default implementations, but this is discouraged.
-
-[[sec:state-global]]
-== Global state
-
-If your puzzle module defines its own global ``enum pb_state``, you can tell
-the driver to use it by implementing the ``pbdrv_hook_state_read`` and
-``pbdrv_hook_state_write`` functions. These functions are also used by the
-default implementation of the read/write commands to address 0 (global state).
-
-Example:
-
-```c
-pb_state_t global_state = PB_GS_NOINIT;
-
-pb_state_t pbdrv_hook_mod_state_read() {
- return global_state;
-}
-
-void pbdrv_hook_mod_state_write(pb_state_t state) {
- global_state = state;
-}
-```
-
-[[sec:state-aux]]
-== Auxiliary state
-
-You can expose additional state variables by implementing the
-``pbdrv_hook_read`` and ``pbdrv_hook_write`` functions. These functions should
-return ``true`` for state addresses you want to override.
-
-Example:
-
-```c
-#define CUSTOM_VAR_ADDR 0x01
-uint8_t my_custom_variable = 10;
-
-bool pbdrv_hook_read(uint16_t i2c_addr, uint8_t addr) {
- switch (addr) {
- case CUSTOM_VAR_ADDR: {
- char res[] = { PB_CMD_READ, addr, my_custom_variable };
- pbdrv_i2c_send(i2c_addr, res, sizeof(res));
- break;
- }
- default: return false;
- }
-
- return true;
-}
-
-bool pbdrv_hook_write(uint16_t i2c_addr, uint8_t addr, const char * buf, size_t sz) {
- switch (addr) {
- case CUSTOM_VAR_ADDR: {
- if (sz != 1) return false;
- my_custom_variable = buf[0];
- break;
- }
- default: return false;
- }
-
- return true;
-}
-```
-
-[[sec:cmd]]
-== Custom commands
-
-Similar to the auxiliary state, custom commands can be added by implementing
-the ``pbdrv_hook_cmd`` function, which should return ``true`` for the
-command(s) that you want to overwrite.
-
-Example:
-
-```c
-bool pbdrv_hook_cmd(uint16_t i2c_addr, enum pb_cmd cmd, const char * buf, size_t sz) {
- if (cmd == 0x54) {
- printf("custom command received!\n");
- return true;
- }
-
- return false;
-}
-```
-