Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: doc: Add an interactive board catalog and generate "Supported features" using DTS #79160

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
325 changes: 325 additions & 0 deletions boards/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,334 @@ available under :zephyr_file:`doc/templates/board.tmpl`.
.. toctree::
:maxdepth: 2
:glob:
:hidden:

*/index

.. raw:: html

<style>
.filter-form {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 20px;
padding: 10px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}

.filter-form input[type="text"],
.filter-form input[type="number"],
.filter-form select {
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
flex: 1 1 200px;
background-color: var(--input-background-color);
color: var(--body-color);
transition: all 0.3s ease;
}

.filter-form input[type="text"]:focus,
.filter-form input[type="number"]:focus,
.filter-form select:focus {
border-color: var(--input-focus-border-color);
}

.catalog {
display: flex;
flex-wrap: wrap;
gap: 20px;
justify-content: center;
/* Center the cards horizontally */
margin-top: 20px;
}

.board-card {
flex: 1 1 calc(33.3% - 20px);
/* Three cards per row */
max-width: calc(33.3% - 20px);
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
background-color: var(--admonition-note-background-color);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
align-items: center;
/* Center card content */
transition: all 0.3s ease;
}

.board-card:hover {
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
/* Add hover effect for card */
transform: translateY(-5px);
/* Slight lift on hover */
}

.board-card img {
width: auto;
height: auto;
max-height: 180px;
margin-bottom: 15px;
border-radius: 4px;
display: block;
margin-left: auto;
margin-right: auto;
}


@media (max-width: 1024px) {
.board-card {
flex: 1 1 calc(50% - 20px);
max-width: calc(50% - 20px);
}
}

@media (max-width: 768px) {
.board-card {
flex: 1 1 calc(100% - 20px);
max-width: calc(100% - 20px);
}

.board-card img {
max-height: 120px;
}
}

.board-card h3 {
margin: 10px 0 5px;
text-align: center;
font-size: 18px;
font-weight: 500;
color: var(--body-color);
}

.board-card p {
margin: 5px 0;
text-align: center;
font-size: 14px;
color: var(--body-color);
}
</style>

<form class="filter-form">
<input type="text" id="name" placeholder="Name" style="flex-basis:600px">
<select id="arch">
<option value="">Select Architecture</option>
<!-- Architecture options will be populated dynamically -->
</select>
<select id="vendor">
<option value="">Select Vendor</option>
<!-- Vendor options will be populated dynamically -->
</select>
<input type="number" id="minRam" placeholder="Min RAM (KB)" />
<input type="number" id="minFlash" placeholder="Min Flash (KB)" />
</form>

<div id="number-of-matches" style="text-align: center;"></div>

<div class="catalog" id="catalog">
<!-- Board cards will be populated dynamically -->
</div>

<script>
function formatSize(sizeInKB) {
function smartRound(num) {
return num.toLocaleString('en-US', { minimumFractionDigits: 0, maximumFractionDigits: 2 });
}

if (sizeInKB >= 1024 * 1024) {
return smartRound(sizeInKB / (1024 * 1024)) + " GB";
} else if (sizeInKB >= 1024) {
return smartRound(sizeInKB / 1024) + " MB";
} else {
return smartRound(sizeInKB) + " KB";
}
}

document.addEventListener("DOMContentLoaded", function () {
let data;

// Guess path to the board catalog JSON file based on the download link on this page
const boardCatalogLink = document.querySelector("a.reference.download.internal[href$='.json']");

// Fetch the JSON data
fetch(boardCatalogLink.href)
.then((response) => response.json())
.then((jsonData) => {
data = jsonData;

const archs = new Set();
const vendors = {};

Object.values(data).forEach((board) => {
if (board.arch) {
archs.add(board.arch);
}
if (board.vendor_prefix && !vendors[board.vendor_prefix]) {
vendors[board.vendor_prefix] = board.vendor;
}
});

archToHumanReadable = {
"arc": "Synopsys DesignWare ARC",
"arm": "ARM",
"arm64": "ARM 64",
"mips": "MIPS",
"nios2": "Altera Nios II",
"posix": "POSIX",
"riscv": "RISC-V",
"sparc": "SPARC",
"x86": "x86",
"xtensa": "Xtensa"
};

// Populate architecture select, sorted alphabetically by human-readable name
const archSelect = document.getElementById("arch");
Array.from(archs)
.sort((a, b) => archToHumanReadable[a].localeCompare(archToHumanReadable[b], undefined, { sensitivity: "base" }))
.forEach((arch) => {
const option = document.createElement("option");
option.value = arch;
option.textContent = archToHumanReadable[arch];
archSelect.appendChild(option);
});

// Populate vendor select, sorted alphabetically
const vendorSelect = document.getElementById("vendor");
Array.from(Object.entries(vendors))
.sort((a, b) => a[1].localeCompare(b[1], undefined, { sensitivity: "base" }))
.forEach(([prefix, vendor]) => {
const option = document.createElement("option");
option.value = prefix;
option.textContent = vendor;
vendorSelect.appendChild(option);
});

generateBoardCards(data);
updateBoardCount();

const form = document.querySelector(".filter-form");
form.addEventListener("input", function () {
filterBoards(data);
});
});
});

function generateBoardCards(data) {
const catalog = document.getElementById("catalog");
catalog.innerHTML = ""; // Clear existing content

// sort boards alphabetically
Object.values(data)
.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: "base" }))
.forEach((board) => {
const boardCard = document.createElement("div");
boardCard.className = "board-card";

// Set data attributes for filtering
boardCard.setAttribute("data-arch", (board.arch || "").toLowerCase());
boardCard.setAttribute("data-vendor", (board.vendor_prefix || "").toLowerCase());
boardCard.setAttribute("data-ram", board.ram || "0");
boardCard.setAttribute("data-flash", board.flash || "0");

// Create image
const img = document.createElement("img");
if (board.image) {
img.src = board.image.replace("/Users/kartben/zephyrproject/zephyr/", "/");
} else {
img.src = "https://via.placeholder.com/200";
}
img.alt = "Board Image";
boardCard.appendChild(img);

// Board name
const h3 = document.createElement("h3");
h3.textContent = board.name || "Unknown Board";
boardCard.appendChild(h3);

basicInfoPara = document.createElement("p");

// Architecture
const pArch = document.createTextNode("Architecture: ");
pArch.textContent += (board.arch || "N/A");
basicInfoPara.appendChild(pArch);
basicInfoPara.appendChild(document.createElement("br"));

// RAM
const pRAM = document.createTextNode("RAM: ");
pRAM.textContent += board.ram ? formatSize(board.ram) : "N/A";
basicInfoPara.appendChild(pRAM);
basicInfoPara.appendChild(document.createElement("br"));

// Flash
const pFlash = document.createTextNode("Flash: ");
pFlash.textContent += board.flash ? formatSize(board.flash) : "N/A";
basicInfoPara.appendChild(pFlash);

boardCard.appendChild(basicInfoPara);

// clicking on the boards card should open the board's README
boardCard.addEventListener("click", function () {
window.open(`../${board.dir}/doc/index.html`, "_self");
});

document.getElementById("catalog").appendChild(boardCard);
});
}

function updateBoardCount() {
const boards = document.getElementsByClassName("board-card");
const visibleBoards = Array.from(boards).filter((board) => board.style.display !== "none").length;
const totalBoards = boards.length;
document.getElementById("number-of-matches").textContent = `Showing ${visibleBoards} of ${totalBoards} boards`;
}


function filterBoards(data) {
const nameInput = document.getElementById("name").value.toLowerCase();
const archSelect = document.getElementById("arch").value.toLowerCase();
const vendorSelect = document.getElementById("vendor").value.toLowerCase();
const minRam = parseInt(document.getElementById("minRam").value, 10) || 0;
const minFlash = parseInt(document.getElementById("minFlash").value, 10) || 0;

console.log(archSelect);

const boards = document.getElementsByClassName("board-card");

Array.from(boards).forEach(function (board) {
const boardName = board.querySelector("h3").textContent.toLowerCase();
const boardArch = board.getAttribute("data-arch").toLowerCase();
const boardVendor = board.getAttribute("data-vendor").toLowerCase();
const boardRam = parseInt(board.getAttribute("data-ram"), 10);
const boardFlash = parseInt(board.getAttribute("data-flash"), 10);

let matches = true;

if (nameInput && !boardName.includes(nameInput)) matches = false;
if (archSelect && !boardArch.includes(archSelect)) matches = false;
if (vendorSelect && boardVendor !== vendorSelect) matches = false;
if (boardRam < minRam) matches = false;
if (boardFlash < minFlash) matches = false;

if (matches) {
board.style.display = "block";
} else {
board.style.display = "none";
}

});

updateBoardCount();

}
</script>

:download:`Raw board catalog <_catalog/board_catalog.json>`.

.. _boards-shields:

Shields
Expand Down
29 changes: 27 additions & 2 deletions doc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ set(SPHINXOPTS "-j auto -W --keep-going -T" CACHE STRING "Default Sphinx Options
set(SPHINXOPTS_EXTRA "" CACHE STRING "Extra Sphinx Options (added to defaults)")
set(LATEXMKOPTS "-halt-on-error -no-shell-escape" CACHE STRING "Default latexmk options")
set(DT_TURBO_MODE OFF CACHE BOOL "Enable DT turbo mode")
set(BOARDS_TURBO_MODE OFF CACHE BOOL "Enable Boards turbo mode")
set(DOC_TAG "development" CACHE STRING "Documentation tag")
set(DTS_ROOTS "${ZEPHYR_BASE}" CACHE STRING "DT bindings root folders")

Expand Down Expand Up @@ -146,6 +147,30 @@ add_custom_target(

set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${GEN_DEVICETREE_REST_SCRIPT})

#-------------------------------------------------------------------------------
# boards catalog

set(GEN_BOARDS_CATALOG_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/_scripts/gen_boards_catalog.py)

set(GEN_BOARDS_ARGS)
if(BOARDS_TURBO_MODE)
list(APPEND GEN_BOARDS_ARGS --turbo-mode)
endif()

add_custom_target(
boards-catalog
COMMAND ${CMAKE_COMMAND} -E env
PYTHONPATH=${ZEPHYR_BASE}/scripts/dts/python-devicetree/src${SEP}$ENV{PYTHONPATH}:${ZEPHYR_BASE}/scripts
ZEPHYR_BASE=${ZEPHYR_BASE}
${PYTHON_EXECUTABLE} ${GEN_BOARDS_CATALOG_SCRIPT}
${GEN_BOARDS_ARGS}
${DOCS_SRC_DIR}/boards/_catalog
VERBATIM
USES_TERMINAL
COMMENT "Generating Boards catalog documentation..."
)


#-------------------------------------------------------------------------------
# html

Expand All @@ -172,7 +197,7 @@ set_target_properties(
ADDITIONAL_CLEAN_FILES "${DOCS_SRC_DIR};${DOCS_HTML_DIR};${DOCS_DOCTREE_DIR}"
)

add_dependencies(html devicetree)
add_dependencies(html devicetree boards-catalog)

#-------------------------------------------------------------------------------
# html-live
Expand Down Expand Up @@ -202,7 +227,7 @@ set_target_properties(
ADDITIONAL_CLEAN_FILES "${DOCS_SRC_DIR};${DOCS_HTML_DIR};${DOCS_DOCTREE_DIR}"
)

add_dependencies(html-live devicetree)
add_dependencies(html-live devicetree boards-catalog)
#-------------------------------------------------------------------------------
# pdf

Expand Down
Loading
Loading