aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--puzzle/spec.adoc158
-rw-r--r--shared/pb/driver.c2
-rw-r--r--shared/pb/driver.h4
-rw-r--r--shared/pb/types.h12
4 files changed, 170 insertions, 6 deletions
diff --git a/puzzle/spec.adoc b/puzzle/spec.adoc
new file mode 100644
index 0000000..fbd1033
--- /dev/null
+++ b/puzzle/spec.adoc
@@ -0,0 +1,158 @@
+= Puzzle module specification
+
+This document contains a subset of the puzzle bus protocol specification, and
+is targeted at puzzle module developers. It describes the required
+implementation steps for integrating a new game into the puzzle module
+framework. All constants and types mentioned in this document are defined in a
+C header: link:../shared/puzbus.h[].
+
+== 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]. This
+document only describes the *data* that is sent over the I^2^C bus. Addresses
+also influence the puzzle box's behavior, and are also further explained in the
+Design document.
+
+== Messages
+
+All puzzle bus messages follow the following format:
+
+[%autowidth]
+|===
+| ``pb_cmd_t command`` | ``uint8_t data[]``
+|===
+
+The format of ``data[]`` is determined by the message type. These messages are
+the core of the puzzle bus framework, and follow these rules:
+
+- Only the main controller takes initiative to send messages (i.e. puzzle
+ modules wait until they are asked to give information, and do not immediately
+ send new information when internal updates occur, etc.)
+- Puzzle modules can only reply to messages sent from the main controller
+- Messages from the main controller to puzzle modules are sent as *I^2^C read*
+ commands (even ``PB_CMD_WRITE`` commands)
+- Reponses from the puzzle modules back to the main contorller are sent as
+ *I^2^C write* commands
+
+In order to properly integrate with the puzzle module framework, a puzzle
+module must do the following:
+
+- <<sec:cmd-magic,Respond to the 'magic message'>>
+- Keep a global state variable of type ``pb_state_t``
+- <<sec:state-global,Implement the exchange command>>
+- <<sec:state-aux,Implement address 0 for the read and write commands>>
+
+[[sec:cmd-magic]]
+=== Magic
+
+The puzzle module will receive a message of type ``PB_CMD_MAGIC`` with data
+being equal to ``pb_magic_msg``. Upon verifying that the message data indeed
+matches ``pb_magic_msg``, the puzzle module sends back a message of type
+``PB_CMD_MAGIC`` with data ``pb_magic_res``. This concludes the puzzle module
+registration process.
+
+Example C code:
+
+```c
+#include "puzbus.h"
+
+void pb_cmd_magic_handler(const char * data, size_t sz) {
+ if (strncmp(buf, pb_magic_msg, sizeof(pb_magic_msg)) != 0) return;
+
+ const char res[] = {
+ PB_CMD_MAGIC,
+ pb_magic_res,
+ };
+
+ i2c_write(BUSADDR_MAIN, res, sizeof(res));
+}
+
+void i2c_read_handle(uint16_t addr, const char * buf, size_t sz) {
+ if (sz < 1) return;
+ pb_cmd_t cmd = (pb_cmd_t) buf[0];
+
+ // shift buffer pointer to only contain the puzzle bus message data
+ buf++; sz--;
+
+ if (cmd == PB_CMD_MAGIC) pb_cmd_magic_handler(buf, sz);
+}
+```
+
+[[sec:state-global]]
+=== Global state
+
+Each puzzle module keeps a single 'global state' variable of type
+``pb_state_t`` that represents the abstracted state of the implemented game.
+Games may implement additional state variables, but the global state is
+required for integration with the puzzle framework.
+
+IMPORTANT: Keep in mind that the global state may be overwritten by a game
+operator, which the puzzle module must be able to handle and react to
+accordingly. Writing to state variables is explained further in the section
+about <<sec:state-aux,auxiliary state variables>>.
+
+The main controller periodically informs each puzzle module of the combined
+puzzle box state, and simultaniously requests each module's state back. This
+message has type ``PB_CMD_EXCHANGE``, and the ``data[]`` of this message
+includes a single ``pb_state_t`` representing the combined box state. The
+puzzle module responds to this message with a message of type
+``PB_CMD_EXCHANGE``, where ``data[]`` is the puzzle module's own global state.
+
+Example C code:
+
+```c
+#include "puzbus.h"
+pb_state_t module_state = PB_STATE_NOINIT;
+
+void pb_cmd_exchange_handler(const char * data, size_t sz) {
+ if (sz < 1) return;
+ pb_state_t main_state = (pb_state_t) data[0];
+
+ // printf("main state is now %d\n", main_state);
+
+ const char res[] = {
+ PB_CMD_EXCHANGE,
+ (char) module_state,
+ };
+
+ i2c_write(BUSADDR_MAIN, res, sizeof(res));
+}
+
+// if (cmd == PB_CMD_EXCHANGE) pb_cmd_exchange_handler(buf, sz);
+// (see above listing)
+```
+
+[[sec:state-aux]]
+=== Auxiliary state
+
+The ``PB_CMD_READ`` command may be used to read game state variables exposed to
+the puzzle bus interface directly. This command has the following format:
+
+[%autowidth]
+|===
+| ``PB_CMD_READ`` | ``uint8_t address``
+|===
+
+Similarly, a ``PB_CMD_WRITE`` command exists for writing to these variables:
+
+[%autowidth]
+|===
+| ``PB_CMD_WRITE`` | ``uint8_t address`` | ``char data[]``
+|===
+
+The format of ``data[]`` is determined by the data type at ``address``. Puzzle
+modules are required to have a <<sec:state-global,global state>> variable, and
+the global state *must* be readable and writeable on address 0. This results in
+``address[0]`` always having a data type of ``pb_state_t``. All other addresses
+are unused by the puzzle module framework, and may be used for implementing
+puzzle-specific functionality.
+
+When a puzzle module receives a ``PB_CMD_READ`` command, it will reply with
+``PB_CMD_WRITE`` to the main controller with the same address, but with
+``data[]`` now filled with the corresponding data at ``address``.
+
+NOTE: The ``PB_CMD_WRITE`` reply must be an *I^2^C write* command to avoid
+being interpreted as a write requests from the puzzle module to the main
+controller.
+
diff --git a/shared/pb/driver.c b/shared/pb/driver.c
index 2552b61..6b675ca 100644
--- a/shared/pb/driver.c
+++ b/shared/pb/driver.c
@@ -7,7 +7,7 @@ __weak bool pbdrv_hook_cmd() {
__weak void pbdrv_i2c_recv(uint16_t addr, const char * buf, size_t sz) {
if (sz == 0) return;
- enum pb_cmd cmd = (enum pb_cmd) buf[0];
+ pb_cmd_t cmd = (enum pb_cmd) buf[0];
// shift buffer pointer to only contain the puzzle bus message buf
buf++;
diff --git a/shared/pb/driver.h b/shared/pb/driver.h
index 1c9c3a3..b5b2784 100644
--- a/shared/pb/driver.h
+++ b/shared/pb/driver.h
@@ -13,8 +13,8 @@ extern "C" {
void pbdrv_i2c_recv(uint16_t addr, const char * buf, size_t sz);
void pbdrv_i2c_send(uint16_t addr, const char * buf, size_t sz);
-void pbdrv_hook_state(enum pb_global_state * state, bool rw);
-bool pbdrv_hook_cmd(enum pb_cmd cmd, const char * buf, size_t sz);
+void pbdrv_hook_state(pb_state_t * state, bool rw);
+bool pbdrv_hook_cmd(pb_cmd_t cmd, const char * buf, size_t sz);
void pbdrv_handle_read(const char * buf, size_t sz);
diff --git a/shared/pb/types.h b/shared/pb/types.h
index 8150194..93e2f0c 100644
--- a/shared/pb/types.h
+++ b/shared/pb/types.h
@@ -24,18 +24,24 @@ extern "C" {
* The first byte of a puzzle bus message's data indicates the command type.
*/
enum __packed pb_cmd {
- PB_CMD_READ, //!< read a puzzle module property
- PB_CMD_WRITE, //!< write to a puzzle module property
+ PB_CMD_READ, //!< read a puzzle module property
+ PB_CMD_WRITE, //!< write to a puzzle module property
// PB_CMD_UPDATE, //!< request an update
+ PB_CMD_MAGIC = 0x69, //!< magic message
};
+typedef enum pb_cmd pb_cmd_t;
+
+static const char pb_magic_msg[] = { 0x70, 0x75, 0x7a, 0x62, 0x75, 0x73 };
+static const char pb_magic_res[] = { 0x67, 0x61, 0x6d, 0x69, 0x6e, 0x67 };
/** \brief Puzzle bus global states */
-enum __packed pb_global_state {
+enum __packed pb_state {
PB_GS_NOINIT, //!< uninitialized (only used by puzzle modules)
PB_GS_IDLE, //!< puzzle not started yet
PB_GS_PLAYING, //!< puzzle actively being solved
PB_GS_SOLVED, //!< puzzle completed
};
+typedef enum pb_state pb_state_t;
typedef struct __packed {
uint8_t address;