aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
m---------lib/Arduino-CMake-Toolchain0
-rw-r--r--lib/i2ctcp/i2ctcpv1.h66
-rw-r--r--lib/i2ctcp/readme.md25
-rw-r--r--lib/mpack/mpack.h31
-rw-r--r--lib/pbdrv/CMakeLists.txt6
-rw-r--r--lib/pbdrv/drv/arduino/include.cmake2
-rw-r--r--lib/pbdrv/drv/arduino/index.dox25
-rw-r--r--lib/pbdrv/drv/arduino/mod.cpp37
-rw-r--r--lib/pbdrv/drv/index.dox19
-rw-r--r--lib/pbdrv/drv/rp2040/include.cmake11
-rw-r--r--lib/pbdrv/drv/rp2040/index.dox31
-rw-r--r--lib/pbdrv/drv/rp2040/mod.c49
-rw-r--r--lib/pbdrv/drv/rp2040/pb-mod.h21
-rw-r--r--lib/pbdrv/ext/freertos/include.cmake4
-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.c9
-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.dox73
-rw-r--r--lib/pbdrv/pb-buf.h20
-rw-r--r--lib/pbdrv/pb-mem.h55
-rw-r--r--lib/pbdrv/pb-mod.h96
-rw-r--r--lib/pbdrv/pb-msg.h43
-rw-r--r--lib/pbdrv/pb-route.c23
-rw-r--r--lib/pbdrv/pb-route.h175
-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
33 files changed, 1053 insertions, 300 deletions
diff --git a/lib/Arduino-CMake-Toolchain b/lib/Arduino-CMake-Toolchain
-Subproject e745a9bed3c3fb83442d55bf05630f31574674f
+Subproject 953b2e63ddf434868bfba60244fb714262fce5f
diff --git a/lib/i2ctcp/i2ctcpv1.h b/lib/i2ctcp/i2ctcpv1.h
index 799b668..e9bc0d9 100644
--- a/lib/i2ctcp/i2ctcpv1.h
+++ b/lib/i2ctcp/i2ctcpv1.h
@@ -2,43 +2,69 @@
#include <stddef.h>
#include <stdint.h>
+#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
-/** \brief I2C over TCP message (v1) */
-struct i2ctcp_msg {
- uint16_t addr; //!< I^2^C address
+/**
+ * \defgroup i2ctcp i2ctcp
+ * \brief I2C over TCP
+ *
+ * This library includes protocol (de)serialization functions for sending and
+ * receiving I2C messages over TCP. These functions are used by the \ref main
+ * "main controller" and the \ref pbc "puzzle box client (pbc)". This library
+ * does not include any puzzle bus specific code.
+ *
+ * mpack is used for the actual (de)serialization, and the functions in this
+ * library act as helpers for parsing from chunked data streams.
+ *
+ * To use these functions, include the following statement in your
+ * CMakeLists.txt:
+ *
+ * ```cmake
+ * # include pbdrv
+ * add_subdirectory(lib/i2ctcp)
+ *
+ * # link with executable
+ * target_link_libraries(main i2ctcp)
+ * ```
+ *
+ * \{
+ */
+
+//! I2C over TCP message (v1)
+typedef struct {
+ uint16_t addr; //!< I2C address
char * data; //!< message content
size_t length; //!< message size
size_t _rdata; //!< \private remaining bytes to read until message is complete
-};
-typedef struct i2ctcp_msg i2ctcp_msg_t;
+} i2ctcp_msg_t;
/**
* \brief Read chunk of input stream, and store resulting message in \p target
*
* This function is called for each chunk of data from an input stream, and
* will parse the next puzzle bus message into \p target. The input stream is
- * assumed to only contain messages encoded by \p i2ctcp_write()
+ * assumed to only contain messages encoded by \c i2ctcp_write()
*
- * \param target pointer to struct that will contain the finished message data
- * \param buf pointer to input stream data chunk
- * \param buf_sz size of \p buf
+ * \param target Pointer to struct that will contain the finished message data
+ * \param buf Pointer to input stream data chunk
+ * \param buf_sz Size of \p buf
*
* \returns Integer representing amount of bytes required to finish message, or
* -1 if the message header could not be read. If this function returns 0, the
* message in \p target is complete.
*
- * \note target->data will automatically be allocated by this function, even if
- * the message is not fully parsed. This variable must be `free()`d by the
+ * \note \p target->data will automatically be allocated by this function, even
+ * if the message is not fully parsed. This variable must be free'd by the
* caller after each complete message to prevent memory leaks.
*/
int i2ctcp_read(i2ctcp_msg_t * target, const char * buf, size_t buf_sz);
/**
- * \brief reset the remaining message data counter
+ * \brief Reset the remaining message data counter
*
* Calling this function has the effect of forcing \c i2ctcp_read() to parse
* the next buffer chunk as the start of a new message. This function may be
@@ -52,19 +78,21 @@ void i2ctcp_read_reset(i2ctcp_msg_t * target);
*
* This function allocates a buffer large enough to fit the message specified
* in \p target, and encodes the data in \p target in a format that can be
- * decoded later using \p i2ctcp_read()
+ * decoded later using \c i2ctcp_read().
*
- * \param target pointer to struct that contains the message data
- * \param buf pointer to \c char* that will contain the formatted message
- * \param buf_sz pointer to \c size_t that will represent the final size of \p buf
+ * \param target Pointer to struct that contains the message data
+ * \param buf Pointer to \c char* that will contain the formatted message
+ * \param buf_sz Pointer to \c size_t that will represent the final size of \p buf
*
- * \returns boolean true if a the message could be encoded successfully, false
- * if there was some kind of error
+ * \returns Boolean \c true if a the message could be encoded successfully, \c
+ * false if there was some kind of error
*
- * \note the pointer stored in \p buf must be `free()`d by the caller afterwards
+ * \note The pointer stored in \p buf must be free'd by the caller afterwards
*/
bool i2ctcp_write(const i2ctcp_msg_t * target, char ** buf, size_t * buf_sz);
+/// \}
+
#ifdef __cplusplus
}
#endif
diff --git a/lib/i2ctcp/readme.md b/lib/i2ctcp/readme.md
deleted file mode 100644
index d5bfe6d..0000000
--- a/lib/i2ctcp/readme.md
+++ /dev/null
@@ -1,25 +0,0 @@
-# i2ctcp (I<sup>2</sup>C over TCP)
-
-This folder includes protocol (de)serialization functions for sending and
-receiving I<sup>2</sup>C messages over TCP. These functions are used by the
-[main controller](../main) and the [puzzle box client (pbc)](../client). This
-folder does not include any puzzle bus specific code, and the headers for
-puzbus are in the [shared](../shared) folder instead.
-
-[MessagePack][msgpack] (specifically the [mpack][mpack] implementation) is used
-for the actual serialization/deserializtion, and the functions in this folder
-act as helpers for parsing from chunked data streams.
-
-To use these functions, include the following statement in your CMakeLists.txt:
-```cmake
-include(../i2ctcp/include.cmake)
-```
-
-The functions are available by `#include`ing the `i2ctcpv1.h` header, and are
-extensively documented using Doxygen-style comments.
-
-[msgpack]: https://msgpack.org/
-[mpack]: https://github.com/ludocode/mpack/
-
-
-
diff --git a/lib/mpack/mpack.h b/lib/mpack/mpack.h
index 7c0c089..33521c4 100644
--- a/lib/mpack/mpack.h
+++ b/lib/mpack/mpack.h
@@ -2,18 +2,39 @@
#include "src/src/mpack/mpack.h"
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \defgroup mpack mpack
+ * \brief Mpack extensions specific to this project
+ *
+ * The mpack folder under `lib/` contains a CMakeLists.txt for integrating the
+ * mapck library with CMake, and includes an extension in the form of an
+ * additional utility function.
+ *
+ * \{
+ */
+
/**
- * \brief read remaining bytes in reader without opening a tag first
+ * \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
+ * \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
+ * \return Amount of bytes read
*/
size_t mpack_read_remaining_bytes(mpack_reader_t * reader, char * p, size_t count);
+/// \}
+
+#ifdef __cplusplus
+}
+#endif
+
diff --git a/lib/pbdrv/CMakeLists.txt b/lib/pbdrv/CMakeLists.txt
index 998ed4d..903a3b4 100644
--- a/lib/pbdrv/CMakeLists.txt
+++ b/lib/pbdrv/CMakeLists.txt
@@ -23,7 +23,7 @@ add_library(pbdrv STATIC
pb-buf.c
)
target_include_directories(pbdrv SYSTEM INTERFACE .)
-target_link_libraries(pbdrv mpack)
+target_link_libraries(pbdrv PRIVATE mpack)
# puzzle bus *module* specific code
add_library(pbdrv-mod OBJECT
@@ -32,9 +32,9 @@ add_library(pbdrv-mod OBJECT
pb-route.c
)
target_include_directories(pbdrv-mod SYSTEM INTERFACE .)
-target_link_libraries(pbdrv-mod pbdrv)
+target_link_libraries(pbdrv-mod PUBLIC pbdrv)
# puzzle bus drivers
include(drv/arduino/include.cmake)
-# include(drv/rp2040/include.cmake) # please see /main/pbdrv.h
+include(drv/rp2040/include.cmake)
diff --git a/lib/pbdrv/drv/arduino/include.cmake b/lib/pbdrv/drv/arduino/include.cmake
index 1e2ff08..5ec1124 100644
--- a/lib/pbdrv/drv/arduino/include.cmake
+++ b/lib/pbdrv/drv/arduino/include.cmake
@@ -3,7 +3,7 @@ if(NOT DEFINED ARDUINO)
endif()
target_sources(pbdrv-mod PRIVATE "${CMAKE_CURRENT_LIST_DIR}/mod.cpp")
-target_link_arduino_libraries(pbdrv-mod core Wire)
+target_link_arduino_libraries(pbdrv-mod PRIVATE core Wire)
# freertos is used to defer the handling of i2c messages outside the receive
# interrupt service routine
diff --git a/lib/pbdrv/drv/arduino/index.dox b/lib/pbdrv/drv/arduino/index.dox
new file mode 100644
index 0000000..03510dd
--- /dev/null
+++ b/lib/pbdrv/drv/arduino/index.dox
@@ -0,0 +1,25 @@
+// vim:ft=doxygen
+/**
+\ingroup pb_drv
+\defgroup pb_drv_arduino Arduino
+\brief Arduino ATmega (w/ Arduino-CMake-Toolchain) driver
+
+This driver is known to work with the following MCUs:
+- ATmega328P (Arduino Uno)
+- ATmega2560 (Arduino Mega)
+
+\par Usage
+- Link the \c pbdrv-mod library with your main executable
+
+\note 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.
+
+A complete example for this driver is available in the \ref puzzle/dummy
+folder.
+
+*/
+
diff --git a/lib/pbdrv/drv/arduino/mod.cpp b/lib/pbdrv/drv/arduino/mod.cpp
index 9130334..c381077 100644
--- a/lib/pbdrv/drv/arduino/mod.cpp
+++ b/lib/pbdrv/drv/arduino/mod.cpp
@@ -1,7 +1,3 @@
-#ifndef ARDUINO
-#error This driver only works on the Arduino platform!
-#endif
-
#include <Arduino.h>
#include <Wire.h>
#include <avr/delay.h>
@@ -42,7 +38,17 @@ static void pb_setup() {
Wire.onReceive(recv_event);
}
+/**
+ * \ingroup pb_drv_arduino
+ * \warning This function includes a hard-coded 10ms delay before sending. This
+ * is to work around a weird issue where the Arduino pulls both SDA and SCL low
+ * while attempting to initiate an I2C transmission. We were able to verify
+ * that the Arduino correctly handles bus arbitration under a test scenario
+ * with 2 Uno's, but ran into issues while integrating the Arduino's with the
+ * RP2040.
+ */
__weak void pb_i2c_send(i2c_addr_t addr, const uint8_t * buf, size_t sz) {
+ vTaskDelay(10 / portTICK_PERIOD_MS); // prevent bus collisions
Wire.beginTransmission((int) addr);
Wire.write(buf, sz);
Wire.endTransmission(true);
@@ -64,7 +70,20 @@ void loop_task() {
}
}
-//! Application entrypoint
+/**
+ * \ingroup pb_drv_arduino
+ * \brief Application entrypoint
+ *
+ * This function overrides the default (weak) implementation of the \c main()
+ * function in the Arduino framework. No additional setup is required to use
+ * this driver.
+ *
+ * \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 +93,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..89b9247
--- /dev/null
+++ b/lib/pbdrv/drv/index.dox
@@ -0,0 +1,19 @@
+// 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.
+
+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/drv/rp2040/include.cmake b/lib/pbdrv/drv/rp2040/include.cmake
new file mode 100644
index 0000000..6d25e96
--- /dev/null
+++ b/lib/pbdrv/drv/rp2040/include.cmake
@@ -0,0 +1,11 @@
+if(NOT PICO_PLATFORM STREQUAL "rp2040")
+ return()
+endif()
+
+target_sources(pbdrv-mod PRIVATE "${CMAKE_CURRENT_LIST_DIR}/mod.c")
+target_link_libraries(pbdrv-mod PRIVATE hardware_i2c_headers pico_i2c_slave_headers)
+target_include_directories(pbdrv-mod INTERFACE "${CMAKE_CURRENT_LIST_DIR}")
+
+# pico-stdlib is compatible with C stdlib
+include("${CMAKE_CURRENT_LIST_DIR}/../../ext/stdlib/include.cmake")
+
diff --git a/lib/pbdrv/drv/rp2040/index.dox b/lib/pbdrv/drv/rp2040/index.dox
new file mode 100644
index 0000000..eb01d15
--- /dev/null
+++ b/lib/pbdrv/drv/rp2040/index.dox
@@ -0,0 +1,31 @@
+// vim:ft=doxygen
+/**
+\ingroup pb_drv
+\defgroup pb_drv_rp2040 RP2040
+\brief Raspberry Pi Pico and Pico W driver
+
+This driver is known to work with the following MCUs:
+- Raspberry Pi Pico W
+
+\note 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.
+
+\note This driver includes a workaround that uses both I2C peripherals on the
+RP2040. To use this driver, make sure you initialize both I2C peripherals, and
+connect their respective SDA and SCL pins to the same I2C bus.
+
+\par Usage
+- Link the \c pbdrv-mod library with your main executable
+- Call the \c pb_setup() function (from \c <pb-mod.h>) during setup
+
+\note This driver is automatically enabled if the variable \c PICO_PLATFORM is
+equal to \c "rp2040" in your CMakeLists.txt (it is by default when using the
+pico-sdk CMake library).
+
+\note This driver automatically includes the \ref pb_ext_stdlib
+"stdlib extension" as the pico-sdk implements the C standard library.
+
+*/
+
diff --git a/lib/pbdrv/drv/rp2040/mod.c b/lib/pbdrv/drv/rp2040/mod.c
new file mode 100644
index 0000000..1535da9
--- /dev/null
+++ b/lib/pbdrv/drv/rp2040/mod.c
@@ -0,0 +1,49 @@
+#include "pb.h"
+
+#include "pb.h"
+#include "pb-types.h"
+#include "pb-mod.h"
+#include "pb-send.h"
+#include "pb-buf.h"
+
+#include <hardware/i2c.h>
+#include <hardware/gpio.h>
+#include <pico/i2c_slave.h>
+
+#define PB_I2C_S i2c0
+#define PB_I2C_M i2c1
+
+#define BUF_SIZE 256
+
+uint8_t i2c_msg_buf[BUF_SIZE];
+size_t i2c_msg_buf_sz = 0;
+
+// This function is called from the I2C ISR
+static void recv_event(i2c_inst_t *i2c, i2c_slave_event_t event) {
+ switch (event) {
+ case I2C_SLAVE_RECEIVE: {
+ if (i2c_msg_buf_sz == BUF_SIZE) return;
+ i2c_msg_buf[i2c_msg_buf_sz++] = i2c_read_byte_raw(PB_I2C_S);
+ break;
+ }
+ case I2C_SLAVE_FINISH: {
+ pb_i2c_recv(i2c_msg_buf, i2c_msg_buf_sz);
+ i2c_msg_buf_sz = 0;
+ break;
+ }
+ default: break;
+ }
+}
+
+void pb_setup() {
+ i2c_init(PB_I2C_S, PB_CLOCK_SPEED_HZ);
+ i2c_init(PB_I2C_M, PB_CLOCK_SPEED_HZ);
+
+ i2c_slave_init(PB_I2C_S, PB_MOD_ADDR, &recv_event);
+}
+
+__weak void pb_i2c_send(i2c_addr_t addr, const uint8_t * buf, size_t sz) {
+ // false to write stop condition to i2c bus
+ i2c_write_timeout_us(PB_I2C_M, addr, buf, sz, false, PB_TIMEOUT_US);
+}
+
diff --git a/lib/pbdrv/drv/rp2040/pb-mod.h b/lib/pbdrv/drv/rp2040/pb-mod.h
new file mode 100644
index 0000000..96edc70
--- /dev/null
+++ b/lib/pbdrv/drv/rp2040/pb-mod.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \ingroup pb_drv_rp2040
+ * \brief Puzzle bus driver setup
+ *
+ * This function must be called from a setup/init function to initialize the
+ * I2C peripherals used by this driver.
+ */
+void pb_setup();
+
+#ifdef __cplusplus
+}
+#endif
+
+#include "../../pb-mod.h"
+
diff --git a/lib/pbdrv/ext/freertos/include.cmake b/lib/pbdrv/ext/freertos/include.cmake
index e7ab7fd..e205c8f 100644
--- a/lib/pbdrv/ext/freertos/include.cmake
+++ b/lib/pbdrv/ext/freertos/include.cmake
@@ -1,9 +1,7 @@
target_sources(pbdrv PRIVATE "${CMAKE_CURRENT_LIST_DIR}/pb-mem.c")
-target_link_libraries(pbdrv
+target_link_libraries(pbdrv PUBLIC
freertos_kernel
freertos_kernel_include
freertos_config
)
-target_sources(pbdrv-mod PRIVATE "${CMAKE_CURRENT_LIST_DIR}/pb-mod.c")
-
diff --git a/lib/pbdrv/ext/freertos/index.dox b/lib/pbdrv/ext/freertos/index.dox
new file mode 100644
index 0000000..7d72918
--- /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
+*/
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
deleted file mode 100644
index 75495be..0000000
--- a/lib/pbdrv/ext/freertos/pb-mod.c
+++ /dev/null
@@ -1,9 +0,0 @@
-#include <FreeRTOS.h>
-#include <task.h>
-
-#include "../../pb-types.h"
-
-__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..8ddcb6a
--- /dev/null
+++ b/lib/pbdrv/index.dox
@@ -0,0 +1,73 @@
+// 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.
+
+\par Example
+```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. Please see
+the \ref pb_drv "drivers" page for target-specific usage instructions.
+
+\par 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..91e1d1f 100644
--- a/lib/pbdrv/pb-mod.h
+++ b/lib/pbdrv/pb-mod.h
@@ -1,38 +1,100 @@
#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;
+/// \}
+
+/**
+ * \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
+/// \{
/**
- * \brief platform-specific blocking delay function
+ * \defgroup pb_hook_mod_state State
+ * \brief Provide your own global state variable
*
- * FIXME: this should be removed (see handover: RP2040 I2C limitations)
+ * 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.
+ *
+ * \{
*/
-void pb_mod_blocking_delay_ms(unsigned long ms);
+
+/**
+ * \brief global state read hook
+ * \return current value of global state enum
+ *
+ * The default implementation of this function uses an internal global state
+ * variable in \ref pbdrv.
+ */
+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..5a7bd67 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) {}
@@ -85,9 +93,6 @@ __weak void pb_route_cmd_magic_req(pb_msg_t * msg) {
// // return early if magic doesn't match
if (pb_memcmp(cmd->magic, pb_cmd_magic_req, sizeof(pb_cmd_magic_req)) != 0) return;
- // FIXME: this should be removed (see handover: RP2040 I2C limitations)
- pb_mod_blocking_delay_ms(2000);
-
pb_buf_t buf = pb_send_magic_res();
pb_send_reply(msg, &buf);
pb_buf_free(&buf);
diff --git a/lib/pbdrv/pb-route.h b/lib/pbdrv/pb-route.h
index 41c009a..2a28c0b 100644
--- a/lib/pbdrv/pb-route.h
+++ b/lib/pbdrv/pb-route.h
@@ -6,31 +6,190 @@
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);
+// TODO: add link to pb_route_cmd_state_res handler in main/i2c.c
+/**
+ * \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. (\ref main_route_cmd_magic_res
+ * "link")
+ */
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 861fa85..44b590b 100644
--- a/lib/pbdrv/pb-types.h
+++ b/lib/pbdrv/pb-types.h
@@ -8,63 +8,125 @@
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;
@@ -75,12 +137,14 @@ typedef struct {
pb_global_state_t state; //!< global state
} pb_puzzle_module_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;
-}
-```
-