From 2f443639c159f510b121fb8b3d7ce6aae61b06fb Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Thu, 30 May 2024 12:38:28 +0200 Subject: implement pbdrv --- shared/pb/spec.adoc | 127 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 shared/pb/spec.adoc (limited to 'shared/pb/spec.adoc') 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 + <> 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 <>. +- If you want to implement custom puzzle bus commands, you can implement the + <>. + +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; +} +``` + -- cgit v1.2.3 From 18d06c79b9f6a625eb218a15c8216556fb99dc02 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Thu, 30 May 2024 18:58:13 +0200 Subject: WIP parsing the connected puzzle module list --- client/i2c.cpp | 30 ++++++++++++++++++++++++++++++ client/readme.md | 10 ++++++++++ shared/pb/mod/main.h | 13 +++++++++++++ shared/pb/moddrv.c | 14 +++++++------- shared/pb/moddrv.h | 8 ++++---- shared/pb/spec.adoc | 20 +++++++++++++------- shared/pb/types.h | 12 +++++++++--- 7 files changed, 86 insertions(+), 21 deletions(-) create mode 100644 shared/pb/mod/main.h (limited to 'shared/pb/spec.adoc') diff --git a/client/i2c.cpp b/client/i2c.cpp index fd29e1e..ee57e20 100644 --- a/client/i2c.cpp +++ b/client/i2c.cpp @@ -5,6 +5,11 @@ #include "sock.h" #include "xxd.h" +#include "pb/bus.h" +#include "pb/types.h" + +#include "pb/mod/main.h" + bool i2c_dump_send = false; bool i2c_dump_recv = true; @@ -28,10 +33,35 @@ void i2c_send(uint16_t addr, const char * data, size_t data_size) { free(packed); } +static void i2c_handle_cmd_read(uint16_t, const char *, size_t); + void i2c_recv(uint16_t addr, const char * data, size_t data_size) { if (i2c_dump_recv) { printf("[%s] addr(0x%02x) data(0x%02lx):\n", __FUNCTION__, addr, data_size); xxd(data, data_size); } + + if (data_size == 0) return; + enum pb_cmd cmd = (enum pb_cmd) data[0]; + data++; data_size--; + + switch (cmd) { + case PB_CMD_READ: return i2c_handle_cmd_read(addr, data, data_size); + default: return; + } +} + +static void i2c_handle_cmd_read(uint16_t i2c_addr, const char * buf, size_t sz) { + if (sz < 2) return; // require data address + 1 byte of data + pb_cmd_read_t * cmd = (pb_cmd_read_t *) buf; + sz--; // sz now represents size of cmd->data + + if (i2c_addr == BUSADDR_MAIN && cmd->address == 0x01) { + if (sz % 2 != 0) return; // invalid data + for (size_t offset = 0; offset < sz; offset += sizeof(pb_mod_main_mod_t)) { + pb_mod_main_mod_t * mod = (pb_mod_main_mod_t *) (cmd->data + offset); + printf("module at addr 0x%02x with state %d\n", mod->addr, mod->state); + } + } } diff --git a/client/readme.md b/client/readme.md index ea3e034..da48cf1 100644 --- a/client/readme.md +++ b/client/readme.md @@ -7,6 +7,16 @@ game operator to control and monitor the state of a puzzle box, but is also a useful debugging tool when developing puzzle modules, as it allows you to send arbitrary data over the puzzle bus. +## WIP TODO + +- cleanup + - separate ../shared/pb/moddrv.c into a puzzle module specific and 'common' bit + - use the common bit in i2c.cpp instead + - cast to structs in ../shared/pb/moddrv.c +- functionality + - print pretty tree of connected puzzle modules + - add enum to string functions in CLIENT ONLY + ## Features - List detected puzzle modules diff --git a/shared/pb/mod/main.h b/shared/pb/mod/main.h new file mode 100644 index 0000000..56ccd3d --- /dev/null +++ b/shared/pb/mod/main.h @@ -0,0 +1,13 @@ +#pragma once + +#include "../types.h" + +typedef struct __packed { + const uint8_t addr; + const enum pb_state state; +} pb_mod_main_mod_t; + +enum __packed { + PB_MOD_MAIN_ADDR_MODS = 0x01, //!< connected puzzle modules +}; + diff --git a/shared/pb/moddrv.c b/shared/pb/moddrv.c index 4c897e0..1f7fab8 100644 --- a/shared/pb/moddrv.c +++ b/shared/pb/moddrv.c @@ -4,22 +4,22 @@ #include "moddrv.h" /** \brief [private] placeholder global state variable */ -static pb_state_t _global_state = PB_GS_NOINIT; +static enum pb_state _global_state = PB_GS_NOINIT; /** \brief [private] main controller global state */ -static pb_state_t _main_state = PB_GS_NOINIT; +static enum pb_state _main_state = PB_GS_NOINIT; -__weak pb_state_t pbdrv_hook_mod_state_read() { +__weak enum pb_state pbdrv_hook_mod_state_read() { return _global_state; } -__weak void pbdrv_hook_mod_state_write(pb_state_t state) { +__weak void pbdrv_hook_mod_state_write(enum pb_state state) { _global_state = state; } __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]; + enum pb_cmd cmd = (enum pb_cmd) buf[0]; // shift buffer pointer to only contain the puzzle bus message buf buf++; @@ -105,8 +105,8 @@ __weak void pbdrv_handle_sex(uint16_t i2c_addr, const char * buf, size_t sz) { pbdrv_hook_main_state_update(_main_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) { +__weak void pbdrv_hook_main_state_update(enum pb_state state) { } +__weak bool pbdrv_hook_cmd(uint16_t i2c_addr, enum pb_state cmd, const char * buf, size_t sz) { return false; } __weak bool pbdrv_hook_read(uint16_t i2c_addr, uint8_t addr) { diff --git a/shared/pb/moddrv.h b/shared/pb/moddrv.h index c4e1167..ecfc13a 100644 --- a/shared/pb/moddrv.h +++ b/shared/pb/moddrv.h @@ -27,9 +27,9 @@ extern "C" { 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); +enum pb_state pbdrv_hook_mod_state_read(); +void pbdrv_hook_mod_state_write(enum pb_state state); +void pbdrv_hook_main_state_update(enum pb_state state); /** * \name hooks @@ -44,7 +44,7 @@ void pbdrv_hook_main_state_update(pb_state_t state); */ /** \brief cmd receive hook */ -bool pbdrv_hook_cmd(uint16_t i2c_addr, pb_cmd_t cmd, const char * buf, size_t sz); +bool pbdrv_hook_cmd(uint16_t i2c_addr, enum pb_state 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 */ diff --git a/shared/pb/spec.adoc b/shared/pb/spec.adoc index a99497b..3172e84 100644 --- a/shared/pb/spec.adoc +++ b/shared/pb/spec.adoc @@ -10,10 +10,16 @@ puzzle module framework. 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[]. +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) @@ -46,8 +52,8 @@ 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 +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). @@ -115,7 +121,7 @@ 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) { +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; diff --git a/shared/pb/types.h b/shared/pb/types.h index 7996a19..f2e2078 100644 --- a/shared/pb/types.h +++ b/shared/pb/types.h @@ -29,7 +29,7 @@ enum __packed pb_cmd { PB_CMD_SEX, //!< state exchange PB_CMD_MAGIC, //!< magic message }; -typedef enum pb_cmd pb_cmd_t; +// 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 }; @@ -43,10 +43,16 @@ enum __packed pb_state { PB_GS_PLAYING, //!< puzzle actively being solved PB_GS_SOLVED, //!< puzzle completed }; -typedef enum pb_state pb_state_t; +// typedef enum pb_state pb_state_t; + +typedef struct __packed { + const enum pb_cmd cmd; + const uint8_t data[]; +} pb_cmd_t; typedef struct __packed { const uint8_t address; + const uint8_t data[]; } pb_cmd_read_t; typedef struct __packed { @@ -55,7 +61,7 @@ typedef struct __packed { } pb_cmd_write_t; typedef struct __packed { - const pb_state_t main_state; + const enum pb_state main_state; } pb_cmd_sex_t; enum __packed { -- cgit v1.2.3