diff options
author | Loek Le Blansch <loek@pipeframe.xyz> | 2024-05-30 12:38:28 +0200 |
---|---|---|
committer | Loek Le Blansch <loek@pipeframe.xyz> | 2024-05-30 12:38:28 +0200 |
commit | 2f443639c159f510b121fb8b3d7ce6aae61b06fb (patch) | |
tree | 5700d28460740ea340b26f26dc62800dc93614b9 /shared | |
parent | e5a0e65c43934a803c430e53bfa4421a2861a080 (diff) |
implement pbdrv
Diffstat (limited to 'shared')
-rw-r--r-- | shared/pb/driver.c | 102 | ||||
-rw-r--r-- | shared/pb/driver.h | 52 | ||||
-rw-r--r-- | shared/pb/spec.adoc | 127 | ||||
-rw-r--r-- | shared/pb/types.h | 20 |
4 files changed, 279 insertions, 22 deletions
diff --git a/shared/pb/driver.c b/shared/pb/driver.c index 6b675ca..f43d5c1 100644 --- a/shared/pb/driver.c +++ b/shared/pb/driver.c @@ -1,11 +1,29 @@ +#include <memory.h> + #include "types.h" #include "driver.h" -__weak bool pbdrv_hook_cmd() { +/** \brief [private] placeholder global state variable */ +static pb_state_t _global_state = PB_GS_NOINIT; + +/** \brief [private] main controller global state */ +static pb_state_t _main_state = PB_GS_NOINIT; + +__weak pb_state_t pbdrv_hook_mod_state_read() { + return _global_state; +} + +__weak void pbdrv_hook_mod_state_write(pb_state_t state) { + _global_state = state; +} + +__weak void pbdrv_hook_main_state_update(pb_state_t state) { } + +__weak bool pbdrv_hook_cmd(uint16_t i2c_addr, pb_cmd_t cmd, const char * buf, size_t sz) { return false; } -__weak void pbdrv_i2c_recv(uint16_t addr, const char * buf, size_t sz) { +__weak void pbdrv_i2c_recv(uint16_t i2c_addr, const char * buf, size_t sz) { if (sz == 0) return; pb_cmd_t cmd = (enum pb_cmd) buf[0]; @@ -13,19 +31,83 @@ __weak void pbdrv_i2c_recv(uint16_t addr, const char * buf, size_t sz) { buf++; sz--; - // allow user to override command handler while still using this weak - // function - if (pbdrv_hook_cmd(cmd, buf, sz)) return; + // allow user to implement custom commands + if (pbdrv_hook_cmd(i2c_addr, cmd, buf, sz)) + return; switch (cmd) { - case PB_CMD_READ: return pbdrv_handle_read(buf, sz); - // case PB_CMD_WRITE: return pbdrv_handle_write(buf, sz); - // case PB_CMD_MAGIC: return pbdrv_handle_magic(buf, sz); + case PB_CMD_READ: return pbdrv_handle_read(i2c_addr, buf, sz); + case PB_CMD_WRITE: return pbdrv_handle_write(i2c_addr, buf, sz); + case PB_CMD_MAGIC: return pbdrv_handle_magic(i2c_addr, buf, sz); + case PB_CMD_SEX: return pbdrv_handle_sex(i2c_addr, buf, sz); + default: return; + } +} + +__weak void pbdrv_handle_read(uint16_t i2c_addr, const char * buf, size_t sz) { + if (sz == 0) return; + pb_cmd_read_t * cmd = (pb_cmd_read_t *) buf; + + // allow user to addrimplement custom read handlers + if (pbdrv_hook_read(i2c_addr, cmd->address)) + return; + + switch (cmd->address) { + case PB_ADDR_GS: { + char res[] = { + PB_CMD_READ, + PB_ADDR_GS, + pbdrv_hook_mod_state_read(), + }; + return pbdrv_i2c_send(i2c_addr, res, sizeof(res)); + } + default: return; + } +} + +__weak void pbdrv_handle_write(uint16_t i2c_addr, const char * buf, size_t sz) { + if (sz < 2) return; // must have address and at least 1 byte data + pb_cmd_write_t * cmd = (pb_cmd_write_t *) buf; + + // allow user to implement custom read handlers + if (pbdrv_hook_write(i2c_addr, cmd->address, (char *) cmd->data, sz - 1)) + return; + + switch (cmd->address) { + case PB_ADDR_GS: + pbdrv_hook_mod_state_write(cmd->data[0]); + break; default: return; } } -__weak void pbdrv_i2c_send(uint16_t addr, const char * buf, size_t sz) { - return; +__weak void pbdrv_handle_magic(uint16_t i2c_addr, const char * buf, size_t sz) { + if (sz != sizeof(pb_magic_msg)) return; + if (memcmp(buf, pb_magic_msg, sizeof(pb_magic_msg)) != 0) return; + + size_t res_size = sizeof(pb_cmd_t) + sizeof(pb_magic_res); + char res[res_size]; + res[0] = PB_CMD_MAGIC; + memcpy(res, pb_magic_res, sizeof(pb_magic_res)); + + pbdrv_i2c_send(i2c_addr, res, res_size); +} + +__weak void pbdrv_handle_sex(uint16_t i2c_addr, const char * buf, size_t sz) { + if (sz == 0) return; + pb_cmd_sex_t * cmd = (pb_cmd_sex_t *) buf; + + // send own state + char res[] = { + PB_CMD_SEX, + pbdrv_hook_mod_state_read(), + }; + pbdrv_i2c_send(i2c_addr, res, sizeof(res)); + + if (cmd->main_state == _main_state) return; + // keep main controller state + _main_state = cmd->main_state; + // call update if main state changed + pbdrv_hook_main_state_update(_main_state); } diff --git a/shared/pb/driver.h b/shared/pb/driver.h index b5b2784..c4e1167 100644 --- a/shared/pb/driver.h +++ b/shared/pb/driver.h @@ -1,22 +1,60 @@ #pragma once +/** + * \file puzzle bus driver implementation + * + * Most \c pbdrv_* functions have a weak implementation, which may be + * overwritten by a custom implementation. This allows you to use the default + * implementation where possible, and only implement extensions required for + * your puzzle module. Please see spec.adoc for more information about how to + * use the puzzle bus driver library. + */ + #include <stdint.h> #include <stddef.h> #include <stdbool.h> #include "types.h" +#ifndef PBDRV_MOD_NAME +#define PBDRV_MOD_NAME "???" +#endif + #ifdef __cplusplus extern "C" { #endif -void pbdrv_i2c_recv(uint16_t addr, const char * buf, size_t sz); -void pbdrv_i2c_send(uint16_t addr, 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); +void pbdrv_i2c_recv(uint16_t i2c_addr, const char * buf, size_t sz); +void pbdrv_i2c_send(uint16_t i2c_addr, const char * buf, size_t sz); + +pb_state_t pbdrv_hook_mod_state_read(); +void pbdrv_hook_mod_state_write(pb_state_t state); +void pbdrv_hook_main_state_update(pb_state_t state); + +/** + * \name hooks + * + * Implementing this function allows you to use the weak implementation of \c + * pbdrv_i2c_recv() while being able to implement custom command handlers. + * + * \return true if the cmd was recognized, or false to forward the command to + * the default handlers + * + * \{ + */ + +/** \brief cmd receive hook */ +bool pbdrv_hook_cmd(uint16_t i2c_addr, pb_cmd_t cmd, const char * buf, size_t sz); +/** \brief read cmd hook */ +bool pbdrv_hook_read(uint16_t i2c_addr, uint8_t addr); +/** \brief write cmd hook */ +bool pbdrv_hook_write(uint16_t i2c_addr, uint8_t addr, const char * buf, size_t sz); +//! \} + +void pbdrv_handle_read(uint16_t i2c_addr, const char * buf, size_t sz); +void pbdrv_handle_write(uint16_t i2c_addr, const char * buf, size_t sz); +void pbdrv_handle_magic(uint16_t i2c_addr, const char * buf, size_t sz); +void pbdrv_handle_sex(uint16_t i2c_addr, const char * buf, size_t sz); #ifdef __cplusplus } diff --git a/shared/pb/spec.adoc b/shared/pb/spec.adoc new file mode 100644 index 0000000..a99497b --- /dev/null +++ b/shared/pb/spec.adoc @@ -0,0 +1,127 @@ += 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]. + +NOTE: Addresses influence the puzzle box's behavior, as 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[]. + +== 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 ``pb_state_t``, 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, pb_cmd_t cmd, const char * buf, size_t sz) { + if (cmd == 0x54) { + printf("custom command received!\n"); + return true; + } + + return false; +} +``` + diff --git a/shared/pb/types.h b/shared/pb/types.h index 93e2f0c..7996a19 100644 --- a/shared/pb/types.h +++ b/shared/pb/types.h @@ -26,12 +26,14 @@ extern "C" { enum __packed pb_cmd { 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 + PB_CMD_SEX, //!< state exchange + PB_CMD_MAGIC, //!< magic message }; typedef enum pb_cmd pb_cmd_t; +/** \brief magic sent from main controller to puzzle module */ static const char pb_magic_msg[] = { 0x70, 0x75, 0x7a, 0x62, 0x75, 0x73 }; +/** \brief magic reply from puzzle module back to main controller */ static const char pb_magic_res[] = { 0x67, 0x61, 0x6d, 0x69, 0x6e, 0x67 }; /** \brief Puzzle bus global states */ @@ -44,14 +46,22 @@ enum __packed pb_state { typedef enum pb_state pb_state_t; typedef struct __packed { - uint8_t address; + const uint8_t address; } pb_cmd_read_t; typedef struct __packed { - uint8_t address; - uint8_t data[]; + const uint8_t address; + const uint8_t data[]; } pb_cmd_write_t; +typedef struct __packed { + const pb_state_t main_state; +} pb_cmd_sex_t; + +enum __packed { + PB_ADDR_GS = 0x00, //!< global state address +}; + #ifdef __cplusplus } #endif |