From 2f443639c159f510b121fb8b3d7ce6aae61b06fb Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
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
+	<<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;
+}
+```
+
-- 
cgit v1.2.3


From 18d06c79b9f6a625eb218a15c8216556fb99dc02 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
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

