aboutsummaryrefslogtreecommitdiff
path: root/docs/design.adoc
diff options
context:
space:
mode:
Diffstat (limited to 'docs/design.adoc')
-rw-r--r--docs/design.adoc448
1 files changed, 249 insertions, 199 deletions
diff --git a/docs/design.adoc b/docs/design.adoc
index 5ebbb15..9e48fd4 100644
--- a/docs/design.adoc
+++ b/docs/design.adoc
@@ -117,7 +117,7 @@ the following:
** Aggregating game state for all installed puzzle modules.
** Reading and writing game state for all installed puzzle modules.
** Broadcasting updates internally.
-* Serve TCP socket connections for—
+* Serve a TCP socket for—
** Sending state updates
** Manually updating game state
** Integration with the bomb
@@ -147,6 +147,13 @@ The requirements document compares various microcontrollers that fit these
criteria. After this comparison, the decision was made to utilize the Raspberry
Pi Pico W as main controller during development.
+NOTE: This was written while we did not know the puzzle bus specifically
+requires slave-addressible I^2^C multi-master controllers to function properly.
+While the research concludes the RP2040 is a suitable microcontroller for the
+main controller, it is not. The RP2040 was still used, but has required
+implementing workarounds. Please see the handover report for more details on
+how this impacted the project cite:[handover].
+
[[fig:main-controller-top]]
.Main controller top-level block diagram
image::img/main-controller-top.svg[]
@@ -165,12 +172,26 @@ peripherals is handled through a central I^2^C bus referred to as the 'puzzle
bus'. This design was again carried over from the hardware design from the
21-22 group cite:[2122_design].
-The only notable difference made this year was the removal of the
-"HarwareInterrupt" line1{empty}footnote:[This is not a typo], which was
-connected but not utilized cite:[research].
+The previously specified "HarwareInterrupt" line1{empty}footnote:[This is not a
+typo] has been removed this year, as it was connected but not utilized
+cite:[research].
+
+To optimize for flexibility and extensibility, the puzzle bus should ideally
+function as a network (similar to the CAN bus), to allow puzzle modules to send
+responses asynchronously. I^2^C was initially chosen for the puzzle bus due to
+its widespread availability on microcontrollers and in peripherals, but does
+not provide any network-like capabilities.
+
+To archive network-like capabilities, the puzzle bus is specified to be a
+multi-master I^2^C bus, and all puzzle modules are specified to be I^2^C
+multi-master controllers that are slave-addressible. The multi-master part is
+required to prevent I^2^C transmissions from being corrupted in the event of a
+bus collision, and the slave-addressible part is required to both send and
+receive messages on the same controller. This is the only hardware-level
+specification made this year.
-Address definitions and protocol specifications are further detailed in
-<<sec:lv2-bus>>.
+More details on the messages sent over the puzzle bus are described in
+<<sec:lv3-pb-messages>>.
=== Power supply
@@ -202,15 +223,39 @@ image::img/system-bus.svg[]
[[sec:lv2]]
== Modules
-This section elaborates on the top-level specifications from <<sec:lv1>> with
-additional hardware specifications and software design decisions.
+This section elaborates on the top-level (hardware) specifications from
+<<sec:lv1>> with software design decisions.
+
+=== Software module separation
+
+[[fig:software-component]]
+.Software library components
+image::img/software-components.svg[]
+
+<<fig:software-component>> shows a software component diagram with an example
+Arduino-based puzzle module, the main controller and the puzzle box client.
+Notable properties of this architecture include:
-=== Puzzle Module Framework
+* The Arduino SDK, FreeRTOS, mpack, and RPI Pico SDK are external libraries,
+ and with the exception of mpack, these have not been modified.
+* The puzzle bus driver is split into a (portable) standalone library and a
+ module-specific driver.
+* There is a separate library for (de)serializing I^2^C commands for
+ transmission over TCP.
-This subsection defines aspects of the 'puzzle framework' and the interface
-that allows puzzle modules to integrate with this framework. All communication
-described within this subsection refers to 'internal' communication between the
-main controller and puzzle module.
+The specific decision to split the puzzle bus driver and create a separate
+I^2^C over TCP library was made to avoid writing a command set separate from
+internal puzzle bus commands (i.e. one specific to TCP connections). This
+architecture allows the puzzle box client to not only control the main
+controller, but also directly inspect puzzle bus messages for debugging and
+development of future puzzle modules (<<reqs.adoc#req:main-static>>).
+
+=== Puzzle module framework
+
+This subsection defines aspects of the 'puzzle framework': the interface that
+allows puzzle modules to integrate with the puzzle bus main controller. All
+communication described within this subsection refers to 'internal'
+communication between the main controller and puzzle modules on the puzzle bus.
The puzzle framework is the foundation of the puzzle box software, and is
designed to facilitate the following:
@@ -222,107 +267,197 @@ designed to facilitate the following:
* Provide abstracted interfaces to allow for easy integration of the puzzle box
as part of a larger whole (<<reqs.adoc#req:main-interface>>).
+==== Guidelines
+
+The following assumptions are made about puzzle modules:
+
+* Puzzle modules do not take initiative to send REQ or SET commands. They only
+ respond to requests from the main controller.
+* Puzzle modules are I^2^C multi-master controllers that are slave-addressable
+ in master mode.
+* The main controller is a puzzle module, but with the following differences:
+** The main controller is allowed to initiate REQ and SET commands.
+** The main controller's global state is an aggregation of all other puzzle
+ modules, and ignores STATE SET commands.
+
+These guidelines allow the following simplifications:
+
+* Puzzle modules may assume they are always being addressed by the main
+ controller (this simplifies the message architecture).
+* The puzzle bus may be shared with regular I^2^C peripherals without causing
+ issues.
+
+[[sec:lv3-pb-messages]]
+==== Messages
+
+Puzzle bus messages consist of a simple header and variable data. This format
+is shown in <<tab:pb-msg-fmt>>. The messages are (de)serialized using mpack.
+This choice was made after considering various alternative options for sending
+structured messages cite:[research]. Note that all messages are sent as I^2^C
+writes. Due to this, the I^2^C address of a message sender is included in the
+header.
+
+[[tab:pb-msg-fmt]]
+.Puzzle bus message format
+[%autowidth]
+|===
+| Field | Content
+
+| type | Command type (see <<tab:pb-msg-types>>)
+| action | Command action (see <<tab:pb-msg-actions>>)
+| sender | I^2^C address of sender
+| cmd | Command data (dependent on ``type``)
+|===
+
+<<tab:pb-msg-types>> lists the different command types.
+
+[[tab:pb-msg-types]]
+.Puzzle bus command types
+[cols="10,~"]
+|===
+| Type | Description
+
+| ``MAGIC``
+| The MAGIC command effectively serves as a 'secret handshake' (using a _magic_
+value) which is used to distinguish between puzzle modules and unrelated I^2^C
+devices.
+
+| ``STATE``
+| The STATE command is used by puzzle modules to inform the main controller about
+their global state (see <<sec:framework-state>>). The main controller
+aggregates the states of all connected puzzle modules and exchanges this
+aggregated state with the puzzle modules to indicate when the entire puzzle box
+is solved.
+
+| ``PROP``
+| The PROP command type is used for exchanging arbitrary data between puzzle
+modules and/or the puzzle box client (pbc) over the <<sec:main-bridge,TCP
+bridge>>. These properties are not used by the puzzle framework.
+|===
+
+<<tab:pb-msg-actions>> lists the different command actions.
+
+[[tab:pb-msg-actions]]
+.Puzzle bus command actions
+[cols="10,~"]
+|===
+| ``REQ``
+| Mark the command as a request. The receiver of this message is expected to
+return a message of the same type, but with a RES action.
+
+| ``RES``
+| Mark the command as a response to a REQ message. All REQ messages should be
+followed by a RES message.
+
+| ``SET``
+| Request a change / write. The SET command is never followed by a RES.
+|===
+
+Please note that not all commands define behavior for all actions (e.g. there
+is no MAGIC SET command).
+
+Only the MAGIC and STATE commands are required for the puzzle box to function.
+The PROP command was created to allow future expansion without modifying the
+main controller firmware (<<reqs.adoc#req:main-static>>).
+
+The specific format of the 'cmd' field from <<tab:pb-msg-fmt>> is different for
+each command type, and is for this reason only documented in-code using
+Doxygen.
+
[[sec:framework-state]]
==== State
All puzzle modules implement the same state machine shown in
-<<fig:puzzle-module-common-state>>. Note that the arrows indicate state
-transitions that a puzzle module may take on its own. The main controller also
-allows the game operator to manually set the current state as one of the states
-on the right half of <<fig:puzzle-module-common-state>>, which can be used to
-skip a puzzle if a player is stuck (<<reqs.adoc#req:edge-skip-puzzle>>) or
-reset a game if it is malfunctioning (<<reqs.adoc#req:edge-manual-reset>>).
-
-Puzzle modules start in the 'uninitialized' state, where they repeatedly send
-messages to the main controller (see <<sec:main-bridge>>). The state transition
-from 'uninitialized' to 'reset' is forced by the main controller upon
-initialization. States on the right half of <<fig:puzzle-module-common-state>>
-are used during gameplay.
+<<fig:puzzle-module-common-state>>. Note that continuous arrows indicate state
+transitions that a puzzle module may take on its own, while dashed arrows
+indicate state transitions forced by the main controller. The main controller
+also allows the game operator to manually set a module's global state to one of
+these states, which can be used to skip a puzzle if a player is stuck
+(<<reqs.adoc#req:edge-skip-puzzle>>) or reset a game if it is malfunctioning
+(<<reqs.adoc#req:edge-manual-reset>>).
[[fig:puzzle-module-common-state]]
.Global puzzle module state machine
image::img/puzzle-module-common-state.svg[]
-The state machine described in <<fig:puzzle-module-common-state>> is referred
-to as the global state. Puzzle modules may also declare and define custom state
-variables, which is referred to as auxiliary state. These auxiliary state
-variables contain game-specific variables; e.g. the binary state of each button
-on the Neotrellis puzzle module, or the last passcode entered on the vault
-puzzle module.
-
-Separating the auxiliary state from the generic state allows the main
-controller to handle the auxiliary state as an arbitrary blob, which allows for
-future expansion without modification of the main controller software.
+Puzzle modules start in the 'uninitialized' state, where they wait until the
+main controller sends a REQ STATE command. Receiving this command indicates to
+the puzzle module that it was successfully registered by the main controller,
+and that it may transition from the 'uninitialized' state to the 'idle' state.
+This process is also shown in <<fig:sequence-puzzle-module-init>>.
-==== Commands
+The state machine described in <<fig:puzzle-module-common-state>> is referred
+to as the global state. Puzzle modules may also declare and define custom
+variables, which are referred to as properties. These properties may contain
+game-specific variables; e.g. the binary state of each button on the Neotrellis
+puzzle module, or the last passcode entered on the vault puzzle module.
-The puzzle module framework describes the following commands:
+Separating properties from the global state allows the main controller to
+handle these property values as an arbitrary blob, which allows for future
+expansion without modification of the main controller software
+(<<reqs.adoc#req:main-static>>).
-* Read state
-* Write state
-* Update
+==== I^2^C addresses
-The 'read' and 'write' commands are used to communicate both types of state
-defined in <<sec:framework-state>>.
+The RPI Pico SDK prohibits the use of I^2^C addresses reserved by the I^2^C
+specification. This means different addresses from previous years are used.
+These addresses are indexed in the code under a shared header (see
+``lib/pbdrv/pb.h``).
-To avoid issues caused by state synchronization memory consumption on the main
-controller and puzzle modules, auxiliary state is only stored on each
-respective puzzle module's controller. Only global state is cached on the main
-controller to reduce the number of back-and-forth messages required for state
-updates.
+The same I^2^C address may be used by two different puzzle modules, but this
+will make it impossible for them to be used simultaniously.
-These commands are sufficient to realize the puzzle box, but this means that
-the puzzle box would rely heavily on polling-based updates internally. To solve
-this, the 'update' command was created. This command is utilized for various
-kinds of updates, including registering new puzzle modules and updating global
-state.
+The I^2^C addresses are also used to determine the puzzle sequence (i.e. the
+order in which puzzle modules are set to the 'playing' state). The sequence is
+determined by the main controller on startup, and consists of the connected
+puzzle modules' addresses in descending order (i.e. highest address first).
=== Main Controller
This subsection defines the function and state of the main controller.
+==== Initializing puzzle modules
+
+The main controller sends a MAGIC REQ command to every I^2^C address on
+startup. Puzzle modules start in the 'uninitialized' state (see
+<<fig:puzzle-module-common-state>>), during which they do nothing. Puzzle
+modules in this state are still able to reply to requests, including MAGIC REQ
+commands. When the main controller receives a MAGIC RES command, the I^2^C
+address of the sender is added to an internal list for puzzle modules.
+
+After the initial handshake request 'wave' (bus scan), all puzzle modules are
+repeatedly asked for their global state using a STATE REQ command. This request
+also includes the global state of the requesting puzzle module, which is always
+the main controller (under normal circumstances). Upon receiving the first
+STATE REQ command, a puzzle module knows it has been registered successfully by
+the main controller, and may transition into the 'idle' state.
+
+[[fig:sequence-puzzle-module-init]]
+.Puzzle module initialization sequence diagram
+image::img/sequence-puzzle-module-init.svg[]
+
+(Activated lifeline indicates the module is no longer in 'uninitialized' state)
+
==== State
-The global state of the main controller is an aggregated version of all
-installed puzzle modules and is defined by the state machine shown in
+The global state of the main controller is an aggregation of all installed
+puzzle modules and is defined by the state machine shown in
<<fig:main-controller-state>>.
[[fig:main-controller-state]]
.Main controller global state machine
image::img/main-controller-state.svg[]
-The following list describes when each state is active:
+The main controller global state is determined using the following rules:
-* If all puzzle modules are in the 'reset' state, the main controller is also
- in the 'reset' state.
+* If all puzzle modules are in the 'idle' state, the main controller is also in
+ the 'idle' state.
* If all puzzle modules are in the 'solved' state, the main controller is also
- in the 'solved' state.
+ in the 'solved' state.
* Else, the main controller is in the 'playing' state.
-Because the main controller's state is only dependent on the installed puzzle
-modules' state, it is only updated when a puzzle module sends an update
-notification. When the global state of the main module changes, an update
-broadcast is sent to all puzzle modules.
-
-To simplify the commands used to control the puzzle box, the list of installed
-puzzle modules is stored as an auxiliary state variable of the main controller.
-
-==== Initializing Puzzle Modules
-
-Puzzle modules start in the 'uninitialized' state (see
-<<fig:puzzle-module-common-state>>). In this state, the puzzle module
-repeatedly sends an update command to the main controller. The main controller
-responds to this message by sending a 'set state' command with the target state
-as 'reset' as reply. Before this response is sent, the main controller
-internally adds the bus address of the puzzle module requesting to be
-initialized to the list of installed puzzle modules. From the main controller's
-point of view, this reply marks the moment the initialization is complete.
-
-[[fig:sequence-puzzle-module-init]]
-.Puzzle module initialization sequence diagram
-image::img/sequence-puzzle-module-init.svg[]
-
-(Activated lifeline indicates the module is no longer in 'uninitialized' state)
+Due to the repeated STATE REQ commands, this effectively informs the puzzle
+modules when the puzzle box is completely solved.
[[sec:main-bridge]]
==== Bridge
@@ -333,99 +468,40 @@ The Raspberry Pi 3B+ used as main controller during the 21-22 run of the
project set up a Wi-Fi Mesh network cite:[2122_design] to communicate with the
puzzle box. This year's main controller (Raspberry Pi Pico W cite:[research])
uses a standard 802.11b/g/n access point instead
-(<<reqs.adoc#req:main-802-11-ap>>).
+(<<reqs.adoc#req:main-802-11-ap>>) as it is a simpler solution.
-On this network, the main controller hosts a server that serves TCP socket
-connections. These sockets directly forward all internal messages sent to the
-main controller bidirectionally (i.e. on behalf of the main controller).
-Detailed specifications on the TCP socket server are in
-<<sec:lv3-remote-control>>.
-
-==== Operating System
-
-The research document cite:[research] contains a detailed comparison of various
-operating systems that are suitable to realize the functionality described in
-this section. After this comparison, the decision was made to utilize FreeRTOS
-as the operating system on the Rasberry Pi Pico W.
-
-[[sec:lv2-bus]]
-=== Puzzle Bus
-
-This section describes the addresses and communication protocol used on the
-puzzle bus. These specifications only apply to messages sent internally in the
-puzzle box, as messages forwarded by the bridge (see <<sec:main-bridge>>) are
-sent on behalf of the main controller.
+On this network, the main controller hosts a server that serves a TCP socket
+connection. This socket is used to directly forward all internal messages sent
+on the puzzle bus to the puzzle box client bidirectionally (on behalf of the
+main controller).
-==== Addresses
-
-The I^2^C addresses remain mostly unchanged from the 20-21 group's
-implementation cite:[2021_design]. Addresses that were modified since the 20-21
-implementation are marked with an asterisk. Table 1 lists these addresses for
-reference. These addresses are also used to identify specific puzzle modules.
-
-.I^2^C address reference
-[%autowidth]
-|===
-| Peripheral | Address
-
-| Main controller | 0x10*
-| Neotrellis puzzle controller | 0x11*
-| Neotrellis button matrix | 0x12*
-| Software puzzle controller | 0x03
-| Hardware puzzle controller | 0x04
-| Vault puzzle controller | 0x06
-|===
+Due to the separation of the puzzle bus driver code into a standalone library
+for reading/writing puzzle bus commands, and a puzzle module-specific code, the
+puzzle box client is able to read/write raw I^2^C commands directly. A separate
+library was made for serializing I^2^C messages so they can be sent over the
+TCP connection.
-==== Messages
-
-All messages sent over the puzzle bus have a similar format. This format is
-shown in Table 2. Notable details include:
-
-The 'subject' field does not have to match the I^2^C address of the message
-sender or recipient
-
-Property 0x00 stores a module's global state
-
-.Puzzle bus message format
-[%autowidth]
-|===
-| Field | Content
-
-| Command | Enum: read, write, update
-| Subject | I^2^C address (7-bit)
-| Property | Address (8-bit)
-| Value | Byte string (variable length)
-|===
-
-The messages described in Table 2 are (de)serialized using Google's protocol
-buffer library. This choice was made after considering various alternative
-options for sending structured messages cite:[research].
-
-<<fig:sequence-puzzle-module-init>> shows an example of how messages are
-exchanged for the initialization of a puzzle module.
-
-<<fig:sequence-puzzle-finish>> shows an example exchange where the last puzzle
-module (A) is solved while (B) is already solved.
+Detailed specifications on the TCP socket server are in
+<<sec:lv3-remote-control>>.
-. First, module A sets it's own state to 'solved' and subsequently informs the
- main controller of this change.
-. As a result of this update notification, the main controller queries puzzle
- module A for its new global state.
-. Once the main controller has received and confirmed that all puzzle module
- global states are set to 'solved', the main controller sets its own state to
- 'solved', and broadcasts an update message to all puzzle modules.
-. As a result of the update message from the main controller, module B requests
- the main controller's new global state, and is able to verify that all puzzle
- modules have been solved.
+==== Operating system
-In this example, module B could be the vault puzzle module, which displays a
-code when the entire puzzle box is solved.
+Because the main controller needs to asynchronously handle state exchanges with
+puzzle modules while serving a TCP socket connection, the decision to use a
+task scheduler was made. Due to the requirement that most software should be
+covered by the standard curriculum (<<reqs.adoc#req:curriculum-cov>>), this
+choice was between FreeRTOS and Zephyr. FreeRTOS was chosen because it is the
+simplest solution, and because the features Zephyr offers over FreeRTOS are
+already present in the Raspberry Pi Pico SDK.
-[[fig:sequence-puzzle-finish]]
-.Puzzle box finish sequence diagram
-image::img/sequence-puzzle-finish.svg[]
+NOTE: Due to the issues with the RP2040 that were later discovered
+cite:[handover], delays are used within the puzzle bus driver's message
+handling logic. This means that due to the use of the RP2040, *all puzzle
+modules* are required to use a task scheduler or similar mechanism for
+deferring calls to the puzzle bus driver from the I^2^C interrupt service
+routine (ISR).
-=== NeoTrellis Puzzle
+=== NeoTrellis puzzle
This subsection defines aspects of the 'NeoTrellis puzzle' module and gives a
summary of how the puzzle is meant to be solved. This module will be created to
@@ -452,7 +528,7 @@ diagram has been shown in <<fig:neotrellis-io>>.
.NeoTrellis puzzle in-out
image::img/neotrellis-io.png[]
-=== Software Puzzle
+=== Software puzzle
This subsection defines aspects of the 'software puzzle' module and gives a
summary of how the puzzle is meant to be solved. This module will be created to
@@ -492,9 +568,9 @@ code. This is shown in <<fig:software-io>>.
.Software puzzle in-out
image::img/software-io.png[]
-=== Hardware Puzzle
+=== Hardware puzzle
-==== Hardware Puzzle gameplay
+==== Hardware puzzle gameplay
The hardware puzzle has a logic gate puzzle engraved on the front plate of the
puzzle box. To solve the puzzle, the user must set the toggle switches to the
@@ -512,7 +588,7 @@ The inputs and outputs of this puzzle have been taken from the design document
of the previous group which worked on this project (21-22). This input and
output diagram has been shown in Figure ??.
-=== Vault Puzzle
+=== Vault puzzle
==== Vault puzzle gameplay
@@ -529,35 +605,9 @@ clicked the vault resets and they need to start over from the beginning.
.Vault puzzle in-out
image::img/vault-io.png[]
-=== Bomb device
-
-==== Bomb device connection
-
-The bomb connects to a WiFi-network using the 802.11x standard. The hub hosts
-an interface that can be used to identify all the devices including the bomb
-and also pair it to a puzzlebox. After that the game can be set-up and a given
-countdown time and start time will be communicated to the bomb over a TCP
-socket connection. The hub generates a code that will be send to both the
-puzzlebox and bomb so that both devices know what would be or can be expected.
-
-The bomb can also use the WiFi connection to sync. the time.
-
-==== Device inputs & outputs
-
-[[fig:bomb-io]]
-.Bomb device in-out
-image::img/bomb-io.png[]
-
== Components
[[sec:lv3-remote-control]]
-=== Remote Control
-==== Socket Server
-==== Socket Commands
-=== Neotrellis Puzzle
-=== Game state diagrams, activity diagrams (if applicable)
-=== Software Puzzle
-=== Hardware Puzzle
-=== Vault Puzzle
+=== Remote control
[appendix]
== NeoTrellis puzzle example