1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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.
|