diff --git a/boards/seeed/lora_e5_dev_board/doc/lora_e5_dev_board.rst b/boards/seeed/lora_e5_dev_board/doc/lora_e5_dev_board.rst index ca23421073480..9c2071abd4fb9 100644 --- a/boards/seeed/lora_e5_dev_board/doc/lora_e5_dev_board.rst +++ b/boards/seeed/lora_e5_dev_board/doc/lora_e5_dev_board.rst @@ -1,7 +1,4 @@ -.. _lora_e5_dev_board: - -Seeed Studio LoRa-E5 Dev Board -############################## +.. zephyr:board:: lora_e5_dev_board Overview ******** @@ -13,10 +10,6 @@ The LoRa-E5-HF STM32WLE5JC Module supports multiple LPWAN protocols on the All GPIOs of the LoRa-E5 Module are laid out supporting various data protocols and interfaces including RS-485 and Grove. -.. image:: img/lora_e5_dev_board.jpg - :align: center - :alt: LoRa-E5 Dev board - Hardware ******** diff --git a/boards/seeed/lora_e5_mini/doc/index.rst b/boards/seeed/lora_e5_mini/doc/index.rst index 78227fcb62bfe..05e94cdaf3ae1 100644 --- a/boards/seeed/lora_e5_mini/doc/index.rst +++ b/boards/seeed/lora_e5_mini/doc/index.rst @@ -1,7 +1,4 @@ -.. _lora_e5_mini: - -Seeed Studio LoRa-E5 mini -######################### +.. zephyr:board:: lora_e5_mini Overview ******** @@ -10,10 +7,6 @@ LoRa-E5 mini is a compacted-sized development board suitable for the rapid testing and building of small-sized LoRa device, exposing all capabilities of Seeed Studio LoRa-E5 STM32WLE5JC module. -.. image:: img/lora_e5_mini.jpg - :align: center - :alt: LoRa-E5 mini - Hardware ******** diff --git a/boards/seeed/seeeduino_xiao/doc/index.rst b/boards/seeed/seeeduino_xiao/doc/index.rst index 48003fdb63c4f..7dc905e39b3b7 100644 --- a/boards/seeed/seeeduino_xiao/doc/index.rst +++ b/boards/seeed/seeeduino_xiao/doc/index.rst @@ -1,7 +1,4 @@ -.. _seeeduino_xiao: - -Seeeduino XIAO -############## +.. zephyr:board:: seeeduino_xiao Overview ******** @@ -10,10 +7,6 @@ The Seeeduino XIAO is a tiny (20 mm x 17.5 mm) ARM development board with onboard LEDs, USB port, and range of I/O broken out onto 14 pins. -.. image:: img/seeeduino_xiao.jpg - :align: center - :alt: Seeeduino XIAO - Hardware ******** diff --git a/boards/seeed/wio_terminal/doc/index.rst b/boards/seeed/wio_terminal/doc/index.rst index f640f11b44591..736d3cb6984c9 100644 --- a/boards/seeed/wio_terminal/doc/index.rst +++ b/boards/seeed/wio_terminal/doc/index.rst @@ -1,7 +1,4 @@ -.. _wio_terminal: - -Wio Terminal -############ +.. zephyr:board:: wio_terminal Overview ******** @@ -11,11 +8,6 @@ wireless connectivity (2.4G/5G dual-band Wi-Fi and BLE 5.0), LCD display, USB C port, FPC connector, microSD card slot, Raspberry Pi compatible 40-pins header and 2 Grove connectors. -.. image:: img/wio_terminal.png - :width: 500px - :align: center - :alt: Seeed Studio Wio Terminal - Hardware ******** diff --git a/boards/seeed/xiao_ble/doc/index.rst b/boards/seeed/xiao_ble/doc/index.rst index c33d786e02415..831eb872a9da0 100644 --- a/boards/seeed/xiao_ble/doc/index.rst +++ b/boards/seeed/xiao_ble/doc/index.rst @@ -1,7 +1,4 @@ -.. _xiao_ble: - -XIAO BLE (Sense) -################ +.. zephyr:board:: xiao_ble Overview ******** @@ -10,10 +7,6 @@ The Seeed XIAO BLE (Sense) is a tiny (21 mm x 17.5 mm) Nordic Semiconductor nRF52840 ARM Cortex-M4F development board with onboard LEDs, USB port, QSPI flash, battery charger, and range of I/O broken out into 14 pins. -.. figure:: img/xiao_ble.jpg - :align: center - :alt: XIAO BLE - Hardware ******** diff --git a/boards/seeed/xiao_esp32c3/doc/index.rst b/boards/seeed/xiao_esp32c3/doc/index.rst index 48ce9ddae0d63..fa9811661a4bd 100644 --- a/boards/seeed/xiao_esp32c3/doc/index.rst +++ b/boards/seeed/xiao_esp32c3/doc/index.rst @@ -1,7 +1,4 @@ -.. _xiao_esp32c3: - -XIAO ESP32C3 -############ +.. zephyr:board:: xiao_esp32c3 Overview ******** @@ -11,12 +8,6 @@ Espressif ESP32-C3 WiFi/Bluetooth dual-mode chip. For more details see the `Seeed Studio XIAO ESP32C3`_ wiki page. -.. figure:: img/xiao_esp32c.jpg - :align: center - :alt: XIAO ESP32C3 - - XIAO ESP32C3 - Hardware ******** diff --git a/boards/seeed/xiao_esp32s3/doc/index.rst b/boards/seeed/xiao_esp32s3/doc/index.rst index 4c029bd5d645e..60aef4a2e7d0e 100644 --- a/boards/seeed/xiao_esp32s3/doc/index.rst +++ b/boards/seeed/xiao_esp32s3/doc/index.rst @@ -1,7 +1,4 @@ -.. _xiao_esp32s3: - -XIAO ESP32S3 -############ +.. zephyr:board:: xiao_esp32s3 Overview ******** @@ -11,12 +8,6 @@ Espressif ESP32-S3 WiFi/Bluetooth dual-mode chip. For more details see the `Seeed Studio XIAO ESP32S3`_ wiki page. -.. figure:: img/xiao_esp32s3.jpg - :align: center - :alt: XIAO ESP32S3 - - XIAO ESP32S3 - Hardware ******** diff --git a/boards/seeed/xiao_rp2040/doc/index.rst b/boards/seeed/xiao_rp2040/doc/index.rst index 7206e9be07f58..0cf39c014c7fe 100644 --- a/boards/seeed/xiao_rp2040/doc/index.rst +++ b/boards/seeed/xiao_rp2040/doc/index.rst @@ -1,7 +1,4 @@ -.. _xiao_rp2040: - -XIAO RP2040 -########### +.. zephyr:board:: xiao_rp2040 Overview ******** @@ -13,12 +10,6 @@ to be flashed without any adapter, in a drag-and-drop manner. For more details see the `Seeed Studio XIAO RP2040`_ wiki page. -.. figure:: img/xiao_rp2040.webp - :align: center - :alt: XIAO RP2040 - - XIAO RP2040 - Hardware ******** diff --git a/doc/_extensions/zephyr/domain/__init__.py b/doc/_extensions/zephyr/domain/__init__.py index 0308be48987d1..4814e3cc59173 100644 --- a/doc/_extensions/zephyr/domain/__init__.py +++ b/doc/_extensions/zephyr/domain/__init__.py @@ -15,12 +15,14 @@ - ``zephyr:code-sample-category::`` - Defines a category for grouping code samples. - ``zephyr:code-sample-listing::`` - Shows a listing of code samples found in a given category. - ``zephyr:board-catalog::`` - Shows a listing of boards supported by Zephyr. +- ``zephyr:board::`` - Flags a document as being the documentation page for a board. Roles ----- - ``:zephyr:code-sample:`` - References a code sample. - ``:zephyr:code-sample-category:`` - References a code sample category. +- ``:zephyr:board:`` - References a board. """ @@ -30,7 +32,7 @@ from typing import Any, Dict, Iterator, List, Tuple, Final from docutils import nodes -from docutils.parsers.rst import Directive, directives +from docutils.parsers.rst import directives from docutils.statemachine import StringList from sphinx import addnodes @@ -85,6 +87,10 @@ class CodeSampleListingNode(nodes.Element): pass +class BoardNode(nodes.Element): + pass + + class ConvertCodeSampleNode(SphinxTransform): default_priority = 100 @@ -213,6 +219,65 @@ def convert_node(self, node): node.replace_self(node.children[0]) +class ConvertBoardNode(SphinxTransform): + default_priority = 100 + + def apply(self): + matcher = NodeMatcher(BoardNode) + for node in self.document.traverse(matcher): + self.convert_node(node) + + def convert_node(self, node): + parent = node.parent + siblings_to_move = [] + if parent is not None: + index = parent.index(node) + siblings_to_move = parent.children[index + 1 :] + + new_section = nodes.section(ids=[node["id"]]) + new_section += nodes.title(text=node["full_name"]) + + # create a sidebar with all the board details + sidebar = nodes.sidebar(classes=["board-overview"]) + new_section += sidebar + sidebar += nodes.title(text="Board Overview") + + if node["image"] is not None: + figure = nodes.figure() + # set a scale of 100% to indicate we want a link to the full-size image + figure += nodes.image(uri=f"/{node['image']}", scale=100) + figure += nodes.caption(text=node["full_name"]) + sidebar += figure + + field_list = nodes.field_list() + sidebar += field_list + + details = [ + ("Vendor", node["vendor"]), + ("Architecture", ", ".join(node["archs"])), + ("SoC", ", ".join(node["socs"])), + ] + + for property_name, value in details: + field = nodes.field() + field_name = nodes.field_name(text=property_name) + field_body = nodes.field_body() + field_body += nodes.paragraph(text=value) + field += field_name + field += field_body + field_list += field + + # Move the sibling nodes under the new section + new_section.extend(siblings_to_move) + + # Replace the custom node with the new section + node.replace_self(new_section) + + # Remove the moved siblings from their original parent + for sibling in siblings_to_move: + parent.remove(sibling) + + class CodeSampleCategoriesTocPatching(SphinxPostTransform): default_priority = 5 # needs to run *before* ReferencesResolver @@ -434,7 +499,7 @@ def run(self, **kwargs: Any) -> None: node.replace_self([]) -class CodeSampleDirective(Directive): +class CodeSampleDirective(SphinxDirective): """ A directive for creating a code sample node in the Zephyr documentation. """ @@ -569,6 +634,45 @@ def run(self): return [code_sample_listing_node] +class BoardDirective(SphinxDirective): + has_content = False + required_arguments = 1 + optional_arguments = 0 + + def run(self): + # board_name is passed as the directive argument + board_name = self.arguments[0] + + boards = self.env.domaindata["zephyr"]["boards"] + vendors = self.env.domaindata["zephyr"]["vendors"] + + if board_name not in boards: + logger.warning( + f"Board {board_name} does not seem to be a valid board name.", + location=(self.env.docname, self.lineno), + ) + return [] + elif "docname" in boards[board_name]: + logger.warning( + f"Board {board_name} is already documented in {boards[board_name]['docname']}.", + location=(self.env.docname, self.lineno), + ) + return [] + else: + board = boards[board_name] + # flag board in the domain data as now having a documentation page so that it can be + # cross-referenced etc. + board["docname"] = self.env.docname + + board_node = BoardNode(id=board_name) + board_node["full_name"] = board["full_name"] + board_node["vendor"] = vendors.get(board["vendor"], board["vendor"]) + board_node["archs"] = board["archs"] + board_node["socs"] = board["socs"] + board_node["image"] = board["image"] + return [board_node] + + class BoardCatalogDirective(SphinxDirective): has_content = False required_arguments = 0 @@ -578,13 +682,16 @@ def run(self): if self.env.app.builder.format == "html": self.env.domaindata["zephyr"]["has_board_catalog"][self.env.docname] = True - # As it is not expected that more than one board-catalog directive is used across - # the documentation, and since the generation is only taking a few seconds, we don't - # store the catalog in the domain data. It might change in the future if the generation - # becomes more expensive. - board_catalog = get_catalog() + domain_data = self.env.domaindata["zephyr"] renderer = SphinxRenderer([TEMPLATES_DIR]) - rendered = renderer.render("board-catalog.html", {"catalog": board_catalog}) + rendered = renderer.render( + "board-catalog.html", + { + "boards": domain_data["boards"], + "vendors": domain_data["vendors"], + "socs": domain_data["socs"], + }, + ) return [nodes.raw("", rendered, format="html")] else: return [nodes.paragraph(text="Board catalog is only available in HTML.")] @@ -599,6 +706,7 @@ class ZephyrDomain(Domain): roles = { "code-sample": XRefRole(innernodeclass=nodes.inline, warn_dangling=True), "code-sample-category": XRefRole(innernodeclass=nodes.inline, warn_dangling=True), + "board": XRefRole(innernodeclass=nodes.inline, warn_dangling=True), } directives = { @@ -606,11 +714,13 @@ class ZephyrDomain(Domain): "code-sample-listing": CodeSampleListingDirective, "code-sample-category": CodeSampleCategoryDirective, "board-catalog": BoardCatalogDirective, + "board": BoardDirective, } object_types: Dict[str, ObjType] = { "code-sample": ObjType("code sample", "code-sample"), "code-sample-category": ObjType("code sample category", "code-sample-category"), + "board": ObjType("board", "board"), } initial_data: Dict[str, Any] = { @@ -644,6 +754,12 @@ def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: self.data["code-samples"].update(otherdata["code-samples"]) self.data["code-samples-categories"].update(otherdata["code-samples-categories"]) + # self.data["boards"] contains all the boards right from builder-inited time, but it still # potentially needs merging since a board's docname property is set by BoardDirective to + # indicate the board is documented in a specific document. + for board_name, board in otherdata["boards"].items(): + if "docname" in board: + self.data["boards"][board_name]["docname"] = board["docname"] + # merge category trees by adding all the categories found in the "other" tree that to # self tree other_tree = otherdata["code-samples-categories-tree"] @@ -686,6 +802,18 @@ def get_objects(self): 1, ) + for _, board in self.data["boards"].items(): + # only boards that do have a documentation page are to be considered as valid objects + if "docname" in board: + yield ( + board["name"], + board["full_name"], + "board", + board["docname"], + board["name"], + 1, + ) + # used by Sphinx Immaterial theme def get_object_synopses(self) -> Iterator[Tuple[Tuple[str, str], str]]: for _, code_sample in self.data["code-samples"].items(): @@ -699,18 +827,20 @@ def resolve_xref(self, env, fromdocname, builder, type, target, node, contnode): elem = self.data["code-samples"].get(target) elif type == "code-sample-category": elem = self.data["code-samples-categories"].get(target) + elif type == "board": + elem = self.data["boards"].get(target) else: return if elem: if not node.get("refexplicit"): - contnode = [nodes.Text(elem["name"])] + contnode = [nodes.Text(elem["name"] if type != "board" else elem["full_name"])] return make_refnode( builder, fromdocname, elem["docname"], - elem["id"], + elem["id"] if type != "board" else elem["name"], contnode, elem["description"].astext() if type == "code-sample" else None, ) @@ -804,6 +934,13 @@ def install_static_assets_as_needed( app.add_js_file("js/board-catalog.js") +def load_board_catalog_into_domain(app: Sphinx) -> None: + board_catalog = get_catalog() + app.env.domaindata["zephyr"]["boards"] = board_catalog["boards"] + app.env.domaindata["zephyr"]["vendors"] = board_catalog["vendors"] + app.env.domaindata["zephyr"]["socs"] = board_catalog["socs"] + + def setup(app): app.add_config_value("zephyr_breathe_insert_related_samples", False, "env") @@ -811,6 +948,7 @@ def setup(app): app.add_transform(ConvertCodeSampleNode) app.add_transform(ConvertCodeSampleCategoryNode) + app.add_transform(ConvertBoardNode) app.add_post_transform(ProcessCodeSampleListingNode) app.add_post_transform(CodeSampleCategoriesTocPatching) @@ -820,6 +958,8 @@ def setup(app): "builder-inited", (lambda app: app.config.html_static_path.append(RESOURCES_DIR.as_posix())), ) + app.connect("builder-inited", load_board_catalog_into_domain) + app.connect("html-page-context", install_static_assets_as_needed) app.connect("env-updated", compute_sample_categories_hierarchy) diff --git a/doc/_extensions/zephyr/domain/templates/board-card.html b/doc/_extensions/zephyr/domain/templates/board-card.html index 70a3523e323b6..4c16a3c0cc5ce 100644 --- a/doc/_extensions/zephyr/domain/templates/board-card.html +++ b/doc/_extensions/zephyr/domain/templates/board-card.html @@ -16,9 +16,10 @@ data-vendor="{{ board.vendor }}" data-socs="{{ board.socs | join(" ") }}" tabindex="0"> -