diff options
-rw-r--r-- | puzzle/spec.adoc | 158 | ||||
-rw-r--r-- | shared/pb/driver.c | 2 | ||||
-rw-r--r-- | shared/pb/driver.h | 4 | ||||
-rw-r--r-- | shared/pb/types.h | 12 |
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; |