From 9425826203e1c954491a6a29ae84daf1a5edda2c Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Thu, 20 Jun 2024 17:53:58 +0200 Subject: more documentation (WIP design.adoc) --- docs/makefile | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'docs/makefile') diff --git a/docs/makefile b/docs/makefile index eb54a80..15ea3a4 100644 --- a/docs/makefile +++ b/docs/makefile @@ -20,3 +20,8 @@ ASCIIDOCTOR_ARGS += --backend pdf %.pdf: %.adoc $(PDFDEPS) bundle exec asciidoctor $(ASCIIDOCTOR_ARGS) $< +%.svg: %.puml + plantuml -tsvg $< + +design.pdf: img/software-components.svg + -- cgit v1.2.3 From 67f076b151d35a36800734cb31a403fac92ffb6d Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Fri, 21 Jun 2024 13:22:18 +0200 Subject: WIP more design document + add TODOs --- docs/design.adoc | 332 +++++++++++++++++------------- docs/handover.adoc | 3 +- docs/img/.gitignore | 2 + docs/img/sequence-puzzle-module-init.puml | 17 ++ docs/img/sequence-puzzle-module-init.svg | 3 - docs/img/style.ipuml | 1 + docs/makefile | 1 + lib/pbdrv/pb.h | 19 +- 8 files changed, 222 insertions(+), 156 deletions(-) create mode 100644 docs/img/sequence-puzzle-module-init.puml delete mode 100644 docs/img/sequence-puzzle-module-init.svg (limited to 'docs/makefile') diff --git a/docs/design.adoc b/docs/design.adoc index e54c970..7d9759a 100644 --- a/docs/design.adoc +++ b/docs/design.adoc @@ -219,12 +219,36 @@ image::img/system-bus.svg[] This section elaborates on the top-level (hardware) specifications from <> with software design decisions. -=== Puzzle Module Framework +=== Software module separation -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 modules on the puzzle bus. +[[fig:software-component]] +.Software library components +image::img/software-components.svg[] + +<> 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: + +* 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. + +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 (<>). + +=== 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: @@ -240,18 +264,18 @@ designed to facilitate the following: ==== State All puzzle modules implement the same state machine shown in -<>. Note that solid arrows indicate 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 the current state as one of the -states on the right half of <>, which can be -used to skip a puzzle if a player is stuck (<>) -or reset a game if it is malfunctioning (<>). +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 +(<>) or reset a game if it is malfunctioning +(<>). Puzzle modules start in the 'uninitialized' state, where they wait until the main controller sends a SET 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 'reset' state. +and that it may transition from the 'uninitialized' state to the 'idle' state. [[fig:puzzle-module-common-state]] .Global puzzle module state machine @@ -269,70 +293,124 @@ expansion without modification of the main controller software. ==== Commands -The puzzle module framework describes the following command *types*: +// TODO: cleanup + +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]. + +<> shows an example of how messages are +exchanged for the initialization of a puzzle module. + +<> shows an example exchange where the last puzzle +module (A) is solved while (B) is already solved. + +. 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. + +In this example, module B could be the vault puzzle module, which displays a +code when the entire puzzle box is solved. + +[[fig:sequence-puzzle-finish]] +.Puzzle box finish sequence diagram +image::img/sequence-puzzle-finish.svg[] + +The puzzle module framework describes the following command _types_: * ``PROP``: property * ``MAGIC``: handshake * ``STATE``: global state -Each command also has a specific *action*: +Each command also has a specific _action_: * ``REQ``: request * ``RES``: response * ``SET``: (over)write -Not all commands define behavior for all actions (e.g. there is no MAGIC SET -command). +- Not all commands define behavior for all actions (e.g. there is no MAGIC SET + command). +- A REQ command is always answered by a RES command. +- A SET command does not have a reply. +- All commands are sent as I^2^C writes. The Doxygen-generated pages for these command types explain their usage, and -will not be repeated here. +will not be restated in this document. === 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. 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. Puzzle modules start in the 'uninitialized' state (see +<>), in 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]] .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 -<>). 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 @@ -343,99 +421,83 @@ 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 -(<>). +(<>) as it is a simpler solution. + +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). + +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. -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 <>. -==== Operating System +==== Operating system + +TODO -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. +- main controller does tcp and i2c at the same time +- simple scheduler is needed +- curriculum only has FreeRTOS and Zephyr +- Zephyr is overkill +- FreeRTOS it is + +- due to RP2040 limitations, delays are used +- most SDKs I2C drivers directly call I2C message handlers from ISR +- puzzle bus driver functions can no longer be called directly from ISR handlers due to the delay +- FreeRTOS is also used in puzzle modules, though this can likely be removed in the future [[sec:lv2-bus]] -=== Puzzle 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 <>) are sent 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 -|=== - -==== 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 +==== Guidelines -.Puzzle bus message format -[%autowidth] -|=== -| Field | Content +The following assumptions are made about puzzle modules: -| Command | Enum: read, write, update -| Subject | I^2^C address (7-bit) -| Property | Address (8-bit) -| Value | Byte string (variable length) -|=== +* Puzzle modules do not take initiative to send REQ or SET commands. They only + respond to requests from the main controller. +* Puzzle modules can be distinguished from unrelated I^2^C peripherals using + the MAGIC command. +* 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. -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]. +These guidelines allow the following simplifications: -<> shows an example of how messages are -exchanged for the initialization of a puzzle module. +* 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. -<> shows an example exchange where the last puzzle -module (A) is solved while (B) is already solved. +==== Addresses -. 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. +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``). -In this example, module B could be the vault puzzle module, which displays a -code when the entire puzzle box is solved. +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. -[[fig:sequence-puzzle-finish]] -.Puzzle box finish sequence diagram -image::img/sequence-puzzle-finish.svg[] +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). -=== 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 @@ -462,7 +524,7 @@ diagram has been shown in <>. .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 @@ -502,9 +564,9 @@ code. This is shown in <>. .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 @@ -522,7 +584,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 @@ -539,35 +601,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 diff --git a/docs/handover.adoc b/docs/handover.adoc index 0e8af5a..6918256 100644 --- a/docs/handover.adoc +++ b/docs/handover.adoc @@ -109,7 +109,8 @@ Our project documentation was originally written in Microsoft Word, but we later transferred all documentation to AsciiDoc because of issues where OneDrive would roll back changes. If possible, use a documentation system or format that allows using an external version control system like Git to avoid -losing content. +losing content. This is also the reason why our documents may contain +formatting/style errors. === Misconceptions diff --git a/docs/img/.gitignore b/docs/img/.gitignore index afafe8d..529fbaa 100644 --- a/docs/img/.gitignore +++ b/docs/img/.gitignore @@ -1 +1,3 @@ software-components.svg +sequence-puzzle-module-init.svg +sequence-puzzle-finish.svg diff --git a/docs/img/sequence-puzzle-module-init.puml b/docs/img/sequence-puzzle-module-init.puml new file mode 100644 index 0000000..3d2fa56 --- /dev/null +++ b/docs/img/sequence-puzzle-module-init.puml @@ -0,0 +1,17 @@ +@startuml +!include style.ipuml + +participant "main controller" as main +participant "puzzle module" as mod + +activate main + +main -> mod : MAGIC REQ +mod -> main : MAGIC RES + +||| + +main -> mod ++: STATE REQ +mod -> main : STATE RES + +@enduml diff --git a/docs/img/sequence-puzzle-module-init.svg b/docs/img/sequence-puzzle-module-init.svg deleted file mode 100644 index 2e8db4f..0000000 --- a/docs/img/sequence-puzzle-module-init.svg +++ /dev/null @@ -1,3 +0,0 @@ - - -
Main
update
A
state := reset
state := reset
power on
update
\ No newline at end of file diff --git a/docs/img/style.ipuml b/docs/img/style.ipuml index 25bb6d7..9ea5170 100644 --- a/docs/img/style.ipuml +++ b/docs/img/style.ipuml @@ -1,3 +1,4 @@ !theme plain skinparam RoundCorner 0 +hide footbox diff --git a/docs/makefile b/docs/makefile index 15ea3a4..d180398 100644 --- a/docs/makefile +++ b/docs/makefile @@ -24,4 +24,5 @@ ASCIIDOCTOR_ARGS += --backend pdf plantuml -tsvg $< design.pdf: img/software-components.svg +design.pdf: img/sequence-puzzle-module-init.svg diff --git a/lib/pbdrv/pb.h b/lib/pbdrv/pb.h index 0f2e9d1..e8037ae 100644 --- a/lib/pbdrv/pb.h +++ b/lib/pbdrv/pb.h @@ -1,23 +1,34 @@ #pragma once +//! I2C bus speed in hertz (100 KHz) #define PB_CLOCK_SPEED_HZ 100000 +//! I2C bus timeout delay in milliseconds #define PB_TIMEOUT_MS 10 +//! I2C bus timeout delay in microseconds #define PB_TIMEOUT_US (1e3 * PB_TIMEOUT_MS) -// Adafruit NeoTrellis modules +//! Adafruit NeoTrellis module 1 I2C address #define PB_ADDR_ADA_NEO_1 0x2E +//! Adafruit NeoTrellis module 2 I2C address #define PB_ADDR_ADA_NEO_2 0x2F +//! Adafruit NeoTrellis module 3 I2C address #define PB_ADDR_ADA_NEO_3 0x30 +//! Adafruit NeoTrellis module 4 I2C address #define PB_ADDR_ADA_NEO_4 0x32 -// Main controller +//! Main controller I2C address #define PB_ADDR_MOD_MAIN 0x08 -// Puzzle modules +//! NeoTrellis puzzle module I2C address #define PB_ADDR_MOD_NEOTRELLIS 0x21 +//! Software puzzle module I2C address #define PB_ADDR_MOD_SOFTWARE 0x22 +//! Hardware puzzle module I2C address #define PB_ADDR_MOD_HARDWARE 0x23 +//! Vault puzzle module I2C address #define PB_ADDR_MOD_VAULT 0x24 -// #define BUSADDR_MOD_AUTOMATION 0x25 +//! Automation puzzle module I2C address +#define BUSADDR_MOD_AUTOMATION 0x25 +//! Dummy puzzle module I2C address #define PB_ADDR_MOD_DUMMY 0x69 -- cgit v1.2.3