mirror of
https://github.com/m3y54m/Embedded-Engineering-Roadmap.git
synced 2026-10-11 00:38:24 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7977b7a62b | ||
|
|
81f7e6f7d8 | ||
|
|
44bd12bc7e | ||
|
|
cc34538ad2 | ||
|
|
c9bc31d3fc | ||
|
|
7f1c6e37fe | ||
|
|
263527e9f7 | ||
|
|
0b60717b39 | ||
|
|
fdb9eaba1c | ||
|
|
234f89bb38 | ||
|
|
adca1e955b | ||
|
|
f436b82539 | ||
|
|
52aa4a7efa | ||
|
|
8ba3a7f235 | ||
|
|
0d29726169 | ||
|
|
770423adea | ||
|
|
420a3632fe | ||
|
|
03fba9cb09 | ||
|
|
c1aa63c1aa | ||
|
|
811bf6f779 | ||
|
|
fb6d9c892b | ||
|
|
a6e614bd7b | ||
|
|
718dc4c1a2 | ||
|
|
95798c6c33 | ||
|
|
8d0f027bce | ||
|
|
79949a4123 | ||
|
|
b37dd7ab0c | ||
|
|
0738fcbd8f | ||
|
|
efa2b58a71 | ||
|
|
a9f4eac5c9 | ||
|
|
fbc2bbe074 | ||
|
|
ede541e217 | ||
|
|
3f84cb4946 | ||
|
|
0bc41bf6ac | ||
|
|
37334a10d8 | ||
|
|
c43065728c | ||
|
|
399e9072d8 | ||
|
|
b43ee3b3c9 | ||
|
|
9b0589de9a | ||
|
|
6d2434c093 | ||
|
|
ce19924102 | ||
|
|
e42e802909 | ||
|
|
c6fb798231 | ||
|
|
4d72f70ac1 | ||
|
|
287aba4c37 | ||
|
|
363e2182dc | ||
|
|
a51558de95 | ||
|
|
3243a38153 | ||
|
|
8e983933b6 | ||
|
|
d1265280e0 | ||
|
|
1698faccbf | ||
|
|
10a1359485 | ||
|
|
c341c849cd | ||
|
|
4f4058ca04 | ||
|
|
982dc5e439 | ||
|
|
407857803c | ||
|
|
bfbf6715da | ||
|
|
655c4393f5 | ||
|
|
3ec8d86998 | ||
|
|
09b6b687cc | ||
|
|
8cdcebd74f | ||
|
|
4db7a2607d | ||
|
|
0cbb009e31 | ||
|
|
f9ef67a2f2 | ||
|
|
40cd50535d | ||
|
|
c36274440b | ||
|
|
dd63c2f1f0 | ||
|
|
d8573c8602 | ||
|
|
e733e52cc0 | ||
|
|
2b3afb9077 | ||
|
|
4d43e82a8e | ||
|
|
92344eff24 | ||
|
|
80a11187b7 | ||
|
|
a9689ce9cc | ||
|
|
08fdaa683f | ||
|
|
0c11d5077f | ||
|
|
d2fba71fdb | ||
|
|
4866358427 | ||
|
|
ef2003e1c2 | ||
|
|
f6036f707d | ||
|
|
7a9a4349f0 | ||
|
|
6396c68a9a | ||
|
|
7886954208 | ||
|
|
ccebf9e07e | ||
|
|
bb2bdbf59a | ||
|
|
65ae1f8a4c | ||
|
|
06e65e9636 | ||
|
|
79436daa42 | ||
|
|
f9bda303ab | ||
|
|
7acecc07ea | ||
|
|
3948044dbf | ||
|
|
ce429616af | ||
|
|
bb299c97b8 | ||
|
|
4f57e7322a | ||
|
|
887acbf64d | ||
|
|
dc2738a8cd | ||
|
|
db7f1edbf8 | ||
|
|
73a7e40067 | ||
|
|
9529ffefe4 | ||
|
|
f2bd5403f5 | ||
|
|
faf2d121be | ||
|
|
17022df5e7 | ||
|
|
1bf34b4646 | ||
|
|
1ccad2d532 | ||
|
|
da209028b1 | ||
|
|
9dd36557a5 | ||
|
|
e4afd0f6e9 | ||
|
|
468a3e48e9 | ||
|
|
bb8188ed79 | ||
|
|
144e1b0fa8 | ||
|
|
260fe82159 | ||
|
|
73f33549d5 | ||
|
|
a1d90150e2 | ||
|
|
cb9502e460 | ||
|
|
7c4b025264 | ||
|
|
cf6060c9f5 | ||
|
|
a71d798b6f | ||
|
|
0876545bfa | ||
|
|
bf47fa9812 | ||
|
|
fcc0e6f4da | ||
|
|
675d7ba4a0 | ||
|
|
55f5f21e0a | ||
|
|
8a5db76a73 | ||
|
|
1e4123b58a | ||
|
|
74f9bc837b | ||
|
|
d4e968e341 | ||
|
|
b9648c1a9e | ||
|
|
6b4b93fb95 | ||
|
|
39baabe911 | ||
|
|
f2c0b113a1 | ||
|
|
83d70447a6 | ||
|
|
fb30c35787 | ||
|
|
1f7274bd3b | ||
|
|
16cace6831 | ||
|
|
88556f05dd | ||
|
|
2adfc55547 | ||
|
|
21a28a5628 | ||
|
|
04a6ddc56f | ||
|
|
04666a1778 | ||
|
|
bd4d1fabd1 | ||
|
|
522ac5cd6b | ||
|
|
4b23d44b5b | ||
|
|
6b1b5e37a3 | ||
|
|
b64be6582a | ||
|
|
13e2b8f0e3 | ||
|
|
d534a7981a | ||
|
|
302ad1cf7b | ||
|
|
57ea594a3a | ||
|
|
a046909c32 | ||
|
|
4b90facad3 | ||
|
|
853cad223d | ||
|
|
0bc4ce88bb | ||
|
|
4a9566d917 | ||
|
|
71fdc4b068 | ||
|
|
8fa251fddf | ||
|
|
4bb7b1908f | ||
|
|
63d3632f9d | ||
|
|
b68f6e5902 | ||
|
|
fb4d604bdc | ||
|
|
02b7d51af0 | ||
|
|
f919ec4963 | ||
|
|
42be5a3cff | ||
|
|
7e812ea832 | ||
|
|
8eb6b979d8 | ||
|
|
374bcc818e | ||
|
|
420d375700 | ||
|
|
70d9a7da14 | ||
|
|
2254f0845c | ||
|
|
086066b83a | ||
|
|
52c087ccbd | ||
|
|
42aea2bcf1 | ||
|
|
5f2112a51f | ||
|
|
c0c61ee0ee | ||
|
|
74d1409f73 | ||
|
|
670eaaa6c2 | ||
|
|
18772b293c | ||
|
|
269bcb7595 | ||
|
|
79911f668b | ||
|
|
7bb76db5fa | ||
|
|
33b34ab8c2 | ||
|
|
4fe59cd3a1 | ||
|
|
7415c0d4f3 | ||
|
|
c29d43b8c5 | ||
|
|
36bead8d9c | ||
|
|
13d85a2f45 | ||
|
|
4895841f30 | ||
|
|
f64f741c49 | ||
|
|
e8fbc9cf79 | ||
|
|
05d948f6de |
@@ -1,13 +1 @@
|
|||||||
# These are supported funding model platforms
|
|
||||||
|
|
||||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
|
||||||
patreon: # Replace with a single Patreon username
|
|
||||||
open_collective: # Replace with a single Open Collective username
|
|
||||||
ko_fi: # Replace with a single Ko-fi username
|
|
||||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
|
||||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
|
||||||
liberapay: # Replace with a single Liberapay username
|
|
||||||
issuehunt: # Replace with a single IssueHunt username
|
|
||||||
otechie: # Replace with a single Otechie username
|
|
||||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
|
||||||
custom: ['https://www.buymeacoffee.com/m3y54m']
|
custom: ['https://www.buymeacoffee.com/m3y54m']
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: true
|
||||||
|
contact_links:
|
||||||
|
- name: Contributing guidelines
|
||||||
|
url: https://github.com/m3y54m/Embedded-Engineering-Roadmap/blob/master/CONTRIBUTING.md
|
||||||
|
about: Read these before suggesting a resource or opening a pull request.
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
name: Report a link problem
|
||||||
|
description: A link is broken, outdated, mislabeled or in the wrong section
|
||||||
|
title: "[Link] "
|
||||||
|
labels: ["bug"]
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
id: entry
|
||||||
|
attributes:
|
||||||
|
label: Which link?
|
||||||
|
description: The title or URL of the entry.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: What is wrong, and what should happen to it?
|
||||||
|
description: For example "returns 404, replace with <link>" or "belongs under Debugging".
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
name: Suggest a resource
|
||||||
|
description: Propose a link to add to the roadmap
|
||||||
|
title: "[Resource] "
|
||||||
|
labels: ["enhancement"]
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
id: url
|
||||||
|
attributes:
|
||||||
|
label: Link
|
||||||
|
placeholder: https://
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: section
|
||||||
|
attributes:
|
||||||
|
label: Where does it belong?
|
||||||
|
description: The README section, for example "Microcontrollers > Interrupts".
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: why
|
||||||
|
attributes:
|
||||||
|
label: Why add it?
|
||||||
|
description: One or two sentences. Mention if it is paid or aimed at beginners.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# What changed
|
||||||
|
|
||||||
|
<!-- Added, moved or removed entries, and the section -->
|
||||||
|
|
||||||
|
- [ ] Entries follow the format and markers described in the README
|
||||||
|
- [ ] Each link works and matches its section
|
||||||
|
- [ ] Each addition has a clear reason (free resources preferred; see [CONTRIBUTING.md](../CONTRIBUTING.md))
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: /
|
||||||
|
schedule:
|
||||||
|
interval: monthly
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Many sites in the README (Udemy, Medium, Microchip, ...) answer bots with 403/429 while
|
||||||
|
# working fine in a browser, and some redirect to consent pages, so these count as alive.
|
||||||
|
accept = ["200..=399", "403", "429"]
|
||||||
|
|
||||||
|
exclude = [
|
||||||
|
# Bot challenges or non-standard codes for every request.
|
||||||
|
'^https?://(www\.)?amazon\.',
|
||||||
|
'^https?://a\.co/',
|
||||||
|
'^https?://(www\.)?linkedin\.com/',
|
||||||
|
# Return 404 to non-browser clients although the pages exist.
|
||||||
|
'^https?://(www\.)?nxp\.com/',
|
||||||
|
# Fail HTTP/2 negotiation with the checker's client.
|
||||||
|
'^https?://(www\.|wiki\.)?st\.com/',
|
||||||
|
'^https?://(www\.)?analog\.com/',
|
||||||
|
'^https?://(www\.)?embedded\.com/',
|
||||||
|
'^https?://(www\.)?electronics-tutorials\.ws',
|
||||||
|
'^https?://gemini\.google\.com/',
|
||||||
|
# Broken certificate chain on the server side.
|
||||||
|
'^https?://(www\.)?autosar\.org/',
|
||||||
|
# Repository badge links; GitHub hides these pages from anonymous clients.
|
||||||
|
'^https://github\.com/m3y54m/Embedded-Engineering-Roadmap/(stargazers|watchers|forks|graphs|releases)',
|
||||||
|
]
|
||||||
|
|
||||||
|
max_retries = 3
|
||||||
|
retry_wait_time = 5
|
||||||
|
timeout = 60
|
||||||
|
max_concurrency = 8
|
||||||
|
user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36"
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Check README.md resource entries against the conventions in CONTRIBUTING.md."""
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
README = Path(__file__).resolve().parents[2] / "README.md"
|
||||||
|
|
||||||
|
# Type marker, then optional beginner (👶), then optional invaluable (💎).
|
||||||
|
ENTRY = re.compile(r"^- \[(?:📘|🎞️?|📝|🔗|🎧)👶?💎? .+\]\(https?://\S+\)")
|
||||||
|
LIST_LINK = re.compile(r"^- \[")
|
||||||
|
URL = re.compile(r"\]\((https?://[^)\s]+(?:\([^)\s]*\))?[^)\s]*)\)")
|
||||||
|
BAD_CHARS = {"\ufffd": "U+FFFD replacement character", "\u200b": "zero-width space",
|
||||||
|
"\u200c": "zero-width non-joiner"}
|
||||||
|
HEADING = re.compile(r"^#{2,6} ")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
errors = []
|
||||||
|
section = ""
|
||||||
|
seen = {}
|
||||||
|
for number, line in enumerate(README.read_text(encoding="utf-8").splitlines(), 1):
|
||||||
|
for char, name in BAD_CHARS.items():
|
||||||
|
if char in line:
|
||||||
|
errors.append(f"{number}: {name}")
|
||||||
|
if HEADING.match(line):
|
||||||
|
section = line
|
||||||
|
seen = {}
|
||||||
|
continue
|
||||||
|
if not LIST_LINK.match(line):
|
||||||
|
continue
|
||||||
|
if not ENTRY.match(line):
|
||||||
|
errors.append(f"{number}: entry does not match '- [<type>[👶][💎] Title](url)'")
|
||||||
|
continue
|
||||||
|
match = URL.search(line)
|
||||||
|
if match:
|
||||||
|
url = match.group(1)
|
||||||
|
if url in seen:
|
||||||
|
errors.append(f"{number}: duplicate URL in section, first used on line {seen[url]}")
|
||||||
|
seen[url] = number
|
||||||
|
for error in errors:
|
||||||
|
print(f"README.md:{error}")
|
||||||
|
return 1 if errors else 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Executable
+47
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Decides the roadmap version for a build.
|
||||||
|
# - Patch releases are automatic: a new one is due when the rendered map differs from the latest release's.
|
||||||
|
# - Minor and major releases only happen when a maintainer asks for them (BUMP=minor|major).
|
||||||
|
# Inputs: FINGERPRINT (sha256 of the map PNG rendered without the revision line), BUMP (auto|minor|major),
|
||||||
|
# GH_REPO and GH_TOKEN for the GitHub API. Outputs (to $GITHUB_OUTPUT): release, version, previous, date.
|
||||||
|
set -euo pipefail
|
||||||
|
: "${FINGERPRINT:?}" "${GH_REPO:?}"
|
||||||
|
BUMP="${BUMP:-auto}"
|
||||||
|
|
||||||
|
tag=v0.0.0 published="" body=""
|
||||||
|
if info="$(gh api "repos/$GH_REPO/releases/latest" --jq '[.tag_name, .published_at[0:10]] | @tsv' 2>/dev/null)"; then
|
||||||
|
IFS=$'\t' read -r tag published <<<"$info"
|
||||||
|
body="$(gh api "repos/$GH_REPO/releases/latest" --jq '.body // ""')"
|
||||||
|
fi
|
||||||
|
previous_fingerprint="$(grep -oE 'map-fingerprint: [0-9a-f]{64}' <<<"$body" | cut -d' ' -f2 || true)"
|
||||||
|
|
||||||
|
if [[ ! $tag =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
||||||
|
echo "::error::The latest release tag '$tag' is not in vMAJOR.MINOR.PATCH form."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
major=${BASH_REMATCH[1]} minor=${BASH_REMATCH[2]} patch=${BASH_REMATCH[3]}
|
||||||
|
|
||||||
|
release=true
|
||||||
|
if (( major < 2 )); then
|
||||||
|
# Versions restart at 2.0.0 with the roadmap rendered from the site.
|
||||||
|
version=v2.0.0
|
||||||
|
elif [[ $BUMP == major ]]; then
|
||||||
|
version="v$((major + 1)).0.0"
|
||||||
|
elif [[ $BUMP == minor ]]; then
|
||||||
|
version="v$major.$((minor + 1)).0"
|
||||||
|
elif [[ $FINGERPRINT != "$previous_fingerprint" ]]; then
|
||||||
|
version="v$major.$minor.$((patch + 1))"
|
||||||
|
else
|
||||||
|
release=false
|
||||||
|
version=$tag
|
||||||
|
fi
|
||||||
|
date="$([[ $release == true ]] && date -u +%F || echo "$published")"
|
||||||
|
|
||||||
|
echo "Latest release $tag (map ${previous_fingerprint:-unknown}); this map $FINGERPRINT; bump $BUMP"
|
||||||
|
echo "=> release=$release version=$version date=$date"
|
||||||
|
{
|
||||||
|
echo "release=$release"
|
||||||
|
echo "version=$version"
|
||||||
|
echo "previous=$tag"
|
||||||
|
echo "date=$date"
|
||||||
|
} >> "${GITHUB_OUTPUT:-/dev/stdout}"
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
name: Link check
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 6 * * 1"
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "**/*.md"
|
||||||
|
- ".github/lychee.toml"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lychee:
|
||||||
|
name: Check links
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- name: Check links
|
||||||
|
id: lychee
|
||||||
|
uses: lycheeverse/lychee-action@v2
|
||||||
|
with:
|
||||||
|
args: --config .github/lychee.toml --no-progress "**/*.md"
|
||||||
|
output: lychee/out.md
|
||||||
|
# Pull requests report broken links without blocking; scheduled runs fail and open an issue.
|
||||||
|
fail: ${{ github.event_name != 'pull_request' }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Report broken links
|
||||||
|
if: always() && github.event_name != 'pull_request' && steps.lychee.outputs.exit_code != 0
|
||||||
|
uses: peter-evans/create-issue-from-file@v6
|
||||||
|
with:
|
||||||
|
title: Link check report
|
||||||
|
content-filepath: lychee/out.md
|
||||||
|
labels: broken-links
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
name: Markdown lint
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
markdownlint:
|
||||||
|
name: Markdown lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- uses: DavidAnson/markdownlint-cli2-action@v24
|
||||||
|
with:
|
||||||
|
globs: "**/*.md"
|
||||||
|
|
||||||
|
readme-conventions:
|
||||||
|
name: README conventions
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- name: Check entry format and duplicates
|
||||||
|
run: python3 .github/scripts/check_readme.py
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
name: Roadmap explorer
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
paths:
|
||||||
|
- README.md
|
||||||
|
- "site/**"
|
||||||
|
- .github/workflows/pages.yml
|
||||||
|
- .github/scripts/next-version.sh
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- README.md
|
||||||
|
- "site/**"
|
||||||
|
- .github/workflows/pages.yml
|
||||||
|
- .github/scripts/next-version.sh
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
bump:
|
||||||
|
description: "Version bump: auto releases a patch only if the map changed; minor/major always release"
|
||||||
|
type: choice
|
||||||
|
options: [auto, minor, major]
|
||||||
|
default: auto
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
# Builds on master queue instead of cancelling, so two releases never pick the same version.
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Test and build
|
||||||
|
# Pinned runner and Playwright keep the rendering stable, so the map fingerprint only changes with the map.
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
outputs:
|
||||||
|
release: ${{ steps.version.outputs.release }}
|
||||||
|
version: ${{ steps.version.outputs.version }}
|
||||||
|
previous: ${{ steps.version.outputs.previous }}
|
||||||
|
fingerprint: ${{ steps.fingerprint.outputs.sha256 }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
|
||||||
|
- name: Test the README parser and map layout
|
||||||
|
run: node --test site/parser.test.mjs
|
||||||
|
|
||||||
|
- name: Assemble the site
|
||||||
|
run: |
|
||||||
|
mkdir _site
|
||||||
|
cp site/*.html site/*.css site/*.js site/map.json README.md _site/
|
||||||
|
cp -r site/fonts site/assets _site/
|
||||||
|
|
||||||
|
- name: Install Chromium
|
||||||
|
run: |
|
||||||
|
npm install --no-save --no-package-lock playwright@1.64.0
|
||||||
|
npx playwright install --with-deps chromium
|
||||||
|
|
||||||
|
- name: Fingerprint the map
|
||||||
|
id: fingerprint
|
||||||
|
# Rendered before build.json exists, so the revision line is not part of the fingerprint.
|
||||||
|
run: |
|
||||||
|
node site/tools/render.mjs _site / fingerprint
|
||||||
|
echo "sha256=$(sha256sum fingerprint/Embedded-Engineering-Roadmap.png | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Decide the version
|
||||||
|
id: version
|
||||||
|
env:
|
||||||
|
FINGERPRINT: ${{ steps.fingerprint.outputs.sha256 }}
|
||||||
|
BUMP: ${{ inputs.bump || 'auto' }}
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
GH_REPO: ${{ github.repository }}
|
||||||
|
run: .github/scripts/next-version.sh
|
||||||
|
|
||||||
|
- name: Render the roadmap to PDF and PNG
|
||||||
|
env:
|
||||||
|
VERSION: ${{ steps.version.outputs.version }}
|
||||||
|
DATE: ${{ steps.version.outputs.date }}
|
||||||
|
run: |
|
||||||
|
node -e 'console.log(JSON.stringify({ version: process.env.VERSION, date: process.env.DATE }))' > _site/build.json
|
||||||
|
cat _site/build.json
|
||||||
|
node site/tools/render.mjs _site / _site
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: roadmap-render
|
||||||
|
path: |
|
||||||
|
_site/Embedded-Engineering-Roadmap.pdf
|
||||||
|
_site/Embedded-Engineering-Roadmap.png
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- uses: actions/upload-pages-artifact@v3
|
||||||
|
with:
|
||||||
|
path: _site
|
||||||
|
|
||||||
|
release:
|
||||||
|
name: Release ${{ needs.build.outputs.version }}
|
||||||
|
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master' && needs.build.outputs.release == 'true'
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: roadmap-render
|
||||||
|
|
||||||
|
- name: Create the release with the PDF and PNG
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
GH_REPO: ${{ github.repository }}
|
||||||
|
VERSION: ${{ needs.build.outputs.version }}
|
||||||
|
PREVIOUS: ${{ needs.build.outputs.previous }}
|
||||||
|
FINGERPRINT: ${{ needs.build.outputs.fingerprint }}
|
||||||
|
run: |
|
||||||
|
gh api "repos/$GH_REPO/releases/generate-notes" -f tag_name="$VERSION" -f target_commitish="$GITHUB_SHA" \
|
||||||
|
-f previous_tag_name="$PREVIOUS" --jq .body > notes.md
|
||||||
|
# The next build compares its map with this fingerprint to decide whether a new patch release is due.
|
||||||
|
printf '\n<!-- map-fingerprint: %s -->\n' "$FINGERPRINT" >> notes.md
|
||||||
|
gh release create "$VERSION" Embedded-Engineering-Roadmap.pdf Embedded-Engineering-Roadmap.png \
|
||||||
|
--target "$GITHUB_SHA" --title "$VERSION" --notes-file notes.md
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
name: Deploy to GitHub Pages
|
||||||
|
if: >-
|
||||||
|
!cancelled() && github.event_name != 'pull_request' && github.ref == 'refs/heads/master'
|
||||||
|
&& needs.build.result == 'success' && needs.release.result != 'failure'
|
||||||
|
needs: [build, release]
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
permissions:
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
steps:
|
||||||
|
- id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
# Link checker cache
|
||||||
|
.lycheecache
|
||||||
|
|
||||||
|
# Editors and OS
|
||||||
|
.vscode/
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Local tooling
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"MD026": false,
|
||||||
|
"default": true,
|
||||||
|
"line-length": false
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socioeconomic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our community include:
|
||||||
|
|
||||||
|
- Using welcoming and inclusive language
|
||||||
|
- Being respectful of differing viewpoints and experiences
|
||||||
|
- Gracefully accepting constructive criticism
|
||||||
|
- Focusing on what is best for the community
|
||||||
|
- Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
- The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||||
|
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
- Public or private harassment
|
||||||
|
- Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||||
|
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [insert contact method here]. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at [https://www.contributor-covenant.org/version/1/4/](https://www.contributor-covenant.org/version/1/4/).
|
||||||
|
|
||||||
|
For more details, visit the [Open Source Initiative](https://opensource.org/).
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
Contributions from everyone are welcomed. To keep the roadmap practical, accessible, and high quality, the following guidelines should be followed:
|
||||||
|
|
||||||
|
## 1. Prioritize Beginner-Friendliness
|
||||||
|
|
||||||
|
- Resources and explanations understandable for beginners should be added.
|
||||||
|
- Advanced or specialized topics are welcomed and their inclusion can enrich the roadmap, but they should be explicitly labeled as advanced so beginners are not overwhelmed when choosing resources.
|
||||||
|
|
||||||
|
## 2. Resource Selection Policy
|
||||||
|
|
||||||
|
- Free and open resources are preferred to maximize accessibility.
|
||||||
|
- Paid resources may be included only if they clearly offer more value than existing free options; low-quality paid/free content should not be promoted.
|
||||||
|
- The list should not be spammed with every publication from the same author or creator solely due to their reputation or personal preference. Each resource should be added for a clear reason, with usefulness especially for beginners prioritized.
|
||||||
|
- Resources should be up-to-date, reliable, and organized under relevant headings.
|
||||||
|
|
||||||
|
## 3. Clarity & Structure
|
||||||
|
|
||||||
|
- Clear and direct language should be used. Unnecessary jargon should be avoided. If technical terms are used, simple explanations should be added.
|
||||||
|
- Bullet points and lists should be used for readability and structure.
|
||||||
|
|
||||||
|
## 4. Technical Accuracy
|
||||||
|
|
||||||
|
- The correctness and relevance of all information and links should be verified.
|
||||||
|
- If uncertainty exists, feedback should be requested in the pull request.
|
||||||
|
|
||||||
|
## 5. Roadmap Alignment
|
||||||
|
|
||||||
|
- Contributions should match the topics and structure of the roadmap.
|
||||||
|
- Areas where contributors have experience or genuine interest should be focused on.
|
||||||
|
- If a new topic is thought to make the roadmap more complete, it may be suggested. New topics should be proposed thoughtfully, considering their usefulness and relevance for other learners.
|
||||||
|
|
||||||
|
## 6. Versioning and Releases
|
||||||
|
|
||||||
|
The roadmap image is versioned as `vMAJOR.MINOR.PATCH`, starting from `v2.0.0`.
|
||||||
|
|
||||||
|
- **No new version** for changes that do not change the roadmap map, such as new learning resources, descriptions or documentation.
|
||||||
|
- **Patch** (`v2.0.0` → `v2.0.1`) is automatic: when a change merged into `master` changes the rendered map, CI publishes the next patch release with the PDF and PNG attached.
|
||||||
|
- **Minor** (`v2.0.234` → `v2.1.0`) and **major** (`v2.43.57` → `v3.0.0`) are decided by the maintainer: run the *Roadmap explorer* workflow from the Actions tab on `master` and choose `minor` or `major`.
|
||||||
|
|
||||||
|
Releases should not be created by hand; the workflow renders the files and records the map fingerprint that later builds compare against.
|
||||||
Binary file not shown.
@@ -5,12 +5,11 @@
|
|||||||
[](https://github.com/m3y54m/Embedded-Engineering-Roadmap/watchers)
|
[](https://github.com/m3y54m/Embedded-Engineering-Roadmap/watchers)
|
||||||
[](https://github.com/m3y54m/Embedded-Engineering-Roadmap/graphs/contributors)
|
[](https://github.com/m3y54m/Embedded-Engineering-Roadmap/graphs/contributors)
|
||||||
[](https://github.com/m3y54m/Embedded-Engineering-Roadmap/releases/latest)
|
[](https://github.com/m3y54m/Embedded-Engineering-Roadmap/releases/latest)
|
||||||
[](https://github.com/m3y54m/Embedded-Engineering-Roadmap/releases/latest)
|
|
||||||
[](https://github.com/m3y54m/Embedded-Engineering-Roadmap/releases)
|
[](https://github.com/m3y54m/Embedded-Engineering-Roadmap/releases)
|
||||||
|
|
||||||
This roadmap is designed to help beginners aspiring to build a career as an **Embedded Engineer/Developer**, as well as assist current practitioners in expanding their skills.
|
This roadmap is designed to help beginners aspiring to build a career as an **Embedded Engineer/Developer**, as well as assist current practitioners in expanding their skills.
|
||||||
|
|
||||||
You should know that becoming an embedded engineer requires a broad understanding of both software and hardware. If you really want to pursue this career you must be highly motivated and passionate about it. As well as programming and software development skills, embedded systems demand a solid understanding of hardware functionality. However, as the well-known saying goes, "Hardware is hard!". So if you still know nothing about embedded systems, a hard yet exciting journey awaits you. But don't panic and be patient for the challenges you may encounter along this journey. By dedicating enough time and effort practicing and doing projects you will soon find yourself as a real embedded engineer! 😀
|
Embedded engineering demands a solid understanding of hardware functionality as well as software development and programming skills. If you really want to pursue this career you must be highly motivated and passionate about it. As the well-known saying goes, "Hardware is hard!". But don't panic and be patient for the challenges you may encounter along this exciting journey. By dedicating enough time and effort practicing and doing projects you will soon find yourself as a real embedded engineer! 😀
|
||||||
|
|
||||||
## ❓ What is an Embedded System?
|
## ❓ What is an Embedded System?
|
||||||
|
|
||||||
@@ -53,40 +52,55 @@ It's crucial to note that each company in the embedded industry may have unique
|
|||||||
|
|
||||||
The roadmap provides a comprehensive guide to the essential topics for a typical "Embedded Systems Engineer" role. By delving into both software and hardware aspects, you can develop the necessary skills to thrive in this dynamic field. However, if you have a clear preference for software or hardware, you can tailor your learning path accordingly.
|
The roadmap provides a comprehensive guide to the essential topics for a typical "Embedded Systems Engineer" role. By delving into both software and hardware aspects, you can develop the necessary skills to thrive in this dynamic field. However, if you have a clear preference for software or hardware, you can tailor your learning path accordingly.
|
||||||
|
|
||||||
|
🔗 For more information about the history behind this roadmap, read this article: [Embedded Systems Roadmap: Bridging the Gap](https://interrupt.memfault.com/blog/embedded-systems-roadmap-bridging-the-gap)
|
||||||
|
|
||||||
|
<!-- markdownlint-disable MD028 -->
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> **Remember that the importance of individual software and hardware skills can differ depending on the specific requirements of the company and the job role.**
|
> **Remember that the importance of individual software and hardware skills can differ depending on the specific requirements of the company and the job role.**
|
||||||
|
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> To differentiate between the types of learning resources and the quality of their content, specific symbols are used before each item.
|
> To differentiate between the types of learning resources and the quality of their content, specific symbols are used before each item.
|
||||||
>
|
>
|
||||||
> **Resource types:**
|
> **Resource types:**
|
||||||
|
>
|
||||||
> - 📘 : Books
|
> - 📘 : Books
|
||||||
> - 🎞️ : Videos
|
> - 🎞️ : Videos
|
||||||
> - 📝 : Write-ups, articles, and blog posts
|
> - 📝 : Write-ups, articles, and blog posts
|
||||||
> - 🔗 : Other links that do not fit into any of the above categories
|
> - 🔗 : Other links that do not fit into any of the above categories
|
||||||
>
|
>
|
||||||
> **Content quality symbols:**
|
> **Content quality symbols:**
|
||||||
|
>
|
||||||
> - 👶 : Easy-to-understand and beginner-friendly resources. Refer to them if you do not have prior knowledge in a topic.
|
> - 👶 : Easy-to-understand and beginner-friendly resources. Refer to them if you do not have prior knowledge in a topic.
|
||||||
> - 💎 : Well-known references that have truly invaluable and comprehensive content. Refer to them if you want to deepen your understanding of a topic.
|
> - 💎 : Well-known references that have truly invaluable and comprehensive content. Refer to them if you want to deepen your understanding of a topic.
|
||||||
|
|
||||||
|
<!-- markdownlint-enable MD028 -->
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> **This repository is not affiliated with or financially supported by any content creator, publisher, or organization. We do not endorse or recommend any specific paid resources. Both free and paid resources are included to provide a range of options, allowing you to choose what best suits your needs and preferences.**
|
||||||
|
|
||||||
## 😕 Don't Know Where to Start!
|
## 😕 Don't Know Where to Start!
|
||||||
|
|
||||||
If you feel overwhelmed by the extensive list of topics in the roadmap, you're not alone. Before delving too deeply, let's ease into it with some simple starter projects. Learning embedded systems engineering takes time and effort. Don't get discouraged if you don't understand something right away. Keep practicing and you will eventually get there.
|
**If you feel overwhelmed by the extensive list of topics in the roadmap, you're not alone. Before delving too deeply, let's ease into it with some simple starter projects. Learning embedded systems engineering takes time and effort. Don't get discouraged if you don't understand something right away. Keep practicing and you will eventually get there.**
|
||||||
|
|
||||||
### ⚒️ Projects
|
### ⚒️ Projects
|
||||||
|
|
||||||
Engaging in hands-on projects is the most effective approach to learning. Rather than solely relying on theoretical knowledge from books or articles, undertaking projects allows for practical learning experiences. Even a seemingly basic project has the potential to teach you more than aimless reading. You can refer to books, articles, and courses when faced with difficulties in understanding the problems in a real-world project.
|
Engaging in hands-on projects is the most effective approach to learning. Rather than solely relying on theoretical knowledge from books or articles. Undertaking projects allows for practical learning experiences. Even a seemingly basic project has the potential to teach you more than hours of aimless reading. You can refer to books, articles, and courses when faced with difficulties in understanding the problems in real-world projects.
|
||||||
|
|
||||||
Don't try to build a complex project right away. Start with small, manageable projects to get your feet wet.
|
Don't try to build a complex project right away. Start with small, manageable projects to get your feet wet.
|
||||||
|
|
||||||
- [🔗 Random Nerd Tutorials | Learn ESP32, ESP8266, Arduino, and Raspberry Pi](https://randomnerdtutorials.com/)
|
- [🔗 Random Nerd Tutorials | Learn ESP32, ESP8266, Arduino, and Raspberry Pi](https://randomnerdtutorials.com/)
|
||||||
|
- [🔗 Last Minute Engineers](https://lastminuteengineers.com/)
|
||||||
- [🔗 51 STM32 Projects & Tutorials for Beginners and Up - Hackster.io](https://www.hackster.io/stm32/projects)
|
- [🔗 51 STM32 Projects & Tutorials for Beginners and Up - Hackster.io](https://www.hackster.io/stm32/projects)
|
||||||
- [🔗 STM32 (STM32F103C8) Projects & Tutorials](https://circuitdigest.com/stm32-projects-and-tutorials)
|
- [🔗 STM32 (STM32F103C8) Projects & Tutorials](https://circuitdigest.com/stm32-projects-and-tutorials)
|
||||||
- [🔗 ElectronicWings Projects](https://www.electronicwings.com/projects)
|
- [🔗 ElectronicWings Projects](https://www.electronicwings.com/projects)
|
||||||
- [🔗 STM32 Firmware - Phil’s Lab (YouTube Playlist)](https://youtube.com/playlist?list=PLXSyc11qLa1a4Tqbz228dPZfMrs-KRpzA)
|
- [🔗 STM32 Firmware - Phil’s Lab (YouTube Playlist)](https://youtube.com/playlist?list=PLXSyc11qLa1a4Tqbz228dPZfMrs-KRpzA)
|
||||||
- [🔗 Raspberry Pi Based Embedded Project Ideas](https://www.rs-online.com/designspark/raspberry-pi-based-embedded-project-ideas)
|
- [🔗 Raspberry Pi Based Embedded Project Ideas](https://www.rs-online.com/designspark/raspberry-pi-based-embedded-project-ideas)
|
||||||
- [🔗 Embedded Linux On ARM | Projects](https://www.emertxe.com/embedded-systems/embedded-linux-on-arm/elarm-projects/)
|
- [🔗 Embedded Linux On ARM | Projects](https://www.emertxe.com/embedded-systems/embedded-linux-on-arm/elarm-projects/)
|
||||||
|
- [🔗 Embedded System Project Series - Artful Bytes (YouTube Playlist)](https://www.youtube.com/playlist?list=PLS_iNJJVTtiRV0DZRDcTHnvAuDrKGPN40)
|
||||||
|
- [🔗 ESP32 ESP-IDF Tutorials & IoT Protocols](https://esp32tutorials.com/)
|
||||||
|
- [🔗 flip-card - RP2350 fluid-simulation business card (Rust firmware and PCB design)](https://github.com/Nicholas-L-Johnson/flip-card)
|
||||||
|
- [🔗 picoZ80 - RP2350 + ESP32 drop-in Z80 CPU replacement for retro computers](https://eaw.app/picoz80/)
|
||||||
|
|
||||||
### 🔎 Search and Ask!
|
### 🔎 Search and Ask!
|
||||||
|
|
||||||
@@ -98,23 +112,31 @@ Use search engines to find the answers to your questions.
|
|||||||
|
|
||||||
If you can't find what you're looking for using search engines, AI chatbots can also provide assistance. Keep in mind that AI may provide incorrect answers in some cases. It's best to confirm any answers with more reliable references.
|
If you can't find what you're looking for using search engines, AI chatbots can also provide assistance. Keep in mind that AI may provide incorrect answers in some cases. It's best to confirm any answers with more reliable references.
|
||||||
|
|
||||||
- [🔗 Bing Chat](https://www.bing.com/chat)
|
- [🔗 DuckDuckGo AI Chat](https://duck.ai/)
|
||||||
- [🔗 Bard](https://bard.google.com)
|
- [🔗 Google Gemini](https://gemini.google.com)
|
||||||
- [🔗 ChatGPT](https://chat.openai.com)
|
- [🔗 ChatGPT](https://chatgpt.com)
|
||||||
- [🔗 Poe - Fast, Helpful AI Chat](https://poe.com)
|
- [🔗 Microsoft Copilot](https://copilot.microsoft.com)
|
||||||
|
- [🔗 Claude AI](https://claude.ai/chats)
|
||||||
|
|
||||||
If you have reservations about relying on AI advice alone, you can also ask real people your questions:
|
If you have reservations about relying on AI advice alone, you can also ask your questions from real people:
|
||||||
|
|
||||||
- [🔗 Electrical Engineering Stack Exchange](https://electronics.stackexchange.com/)
|
- [🔗 Electrical Engineering Stack Exchange](https://electronics.stackexchange.com/)
|
||||||
- [🔗 Reddit · r/embedded](https://www.reddit.com/r/embedded/)
|
- [🔗 Reddit · r/embedded](https://www.reddit.com/r/embedded/)
|
||||||
|
|
||||||
|
### ✳️ Career Development
|
||||||
|
|
||||||
|
As an embedded systems engineer, it's essential to stay up-to-date with industry trends, best practices, and new technologies to advance your career. Here are some resources to help you achieve your career goals:
|
||||||
|
|
||||||
|
- [🔗 EmbeddedJobs](https://embedded.jobs/jobs) - A job board focused exclusively on Embedded Systems job market.
|
||||||
|
|
||||||
### 🎥 Courses and YouTube Contents
|
### 🎥 Courses and YouTube Contents
|
||||||
|
|
||||||
You may have heard that YouTube is a university. And it's true - there is an immense amount of invaluable free content on embedded systems available on YouTube. You'll also find some excellent free courses through Coursera and EdX. Additionally, Udemy offers some high-quality paid course options.
|
You may have heard that YouTube is a university. And it's true - there is an extensive amount of invaluable free content on embedded systems available on YouTube. You'll also find some excellent free courses through Coursera and EdX. Additionally, Udemy offers some high-quality paid course options.
|
||||||
|
|
||||||
- [🎞️ DigiKey (YouTube Channel)](https://www.youtube.com/@digikey)
|
- [🎞️ DigiKey (YouTube Channel)](https://www.youtube.com/@digikey)
|
||||||
- [🎞️ Coursera - Introduction to Embedded Systems Software and Development Environments](https://www.coursera.org/learn/introduction-embedded-systems)
|
- [🎞️ Coursera - Introduction to Embedded Systems Software and Development Environments](https://www.coursera.org/learn/introduction-embedded-systems)
|
||||||
- [🎞️ Coursera - Embedded Software and Hardware Architecture](https://www.coursera.org/learn/embedded-software-hardware)
|
- [🎞️ Coursera - Embedded Software and Hardware Architecture](https://www.coursera.org/learn/embedded-software-hardware)
|
||||||
|
- [🎞️ Coursera - ARM Cortex STM32 Deep Dive: Mastering Embedded Systems](https://www.coursera.org/learn/arm-cortex-stm32-deep-dive-mastering-embedded-systems)
|
||||||
- [🎞️ Fastbit Embedded Brain Academy](https://fastbitlab.com/)
|
- [🎞️ Fastbit Embedded Brain Academy](https://fastbitlab.com/)
|
||||||
- [🎞️ Modern Embedded Systems Programming Course (YouTube Playlist)](https://www.youtube.com/playlist?list=PLPW8O6W-1chwyTzI3BHwBLbGQoPFxPAPM)
|
- [🎞️ Modern Embedded Systems Programming Course (YouTube Playlist)](https://www.youtube.com/playlist?list=PLPW8O6W-1chwyTzI3BHwBLbGQoPFxPAPM)
|
||||||
- [🎞️ element14 presents (YouTube Channel)](https://www.youtube.com/@element14presents)
|
- [🎞️ element14 presents (YouTube Channel)](https://www.youtube.com/@element14presents)
|
||||||
@@ -122,15 +144,20 @@ You may have heard that YouTube is a university. And it's true - there is an imm
|
|||||||
- [🎞️ Phil’s Lab (YouTube Channel)](https://www.youtube.com/@PhilsLab)
|
- [🎞️ Phil’s Lab (YouTube Channel)](https://www.youtube.com/@PhilsLab)
|
||||||
- [🎞️ Embedded Systems - Jacob Sorber (YouTube Playlist)](https://www.youtube.com/playlist?list=PL9IEJIKnBJjEcPAz6fss-Hx0TLytCOMVC)
|
- [🎞️ Embedded Systems - Jacob Sorber (YouTube Playlist)](https://www.youtube.com/playlist?list=PL9IEJIKnBJjEcPAz6fss-Hx0TLytCOMVC)
|
||||||
- [🎞️ edX - Embedded Systems - Shape The World: Microcontroller Input/Output](https://learning.edx.org/course/course-v1:UTAustinX+UT.6.10x+3T2022/home)
|
- [🎞️ edX - Embedded Systems - Shape The World: Microcontroller Input/Output](https://learning.edx.org/course/course-v1:UTAustinX+UT.6.10x+3T2022/home)
|
||||||
- [🎞️ edX - Embedded Systems - Shape The World: Multi-Threaded Interfacing](https://www.edx.org/learn/computer-programming/the-university-of-texas-at-austin-embedded-systems-shape-the-world-multi-threaded-in)
|
- [🎞️ edX - Embedded Systems - Shape The World: Multi-Threaded Interfacing](https://www.edx.org/learn/embedded-systems/the-university-of-texas-at-austin-embedded-systems-shape-the-world-multi-threaded-interfacing)
|
||||||
|
- [🎞️ edX - Embedded Systems Essentials with Arm: Getting Started](https://learning.edx.org/course/course-v1:ArmEducation+EDARMXES1.6x+3T2020/home)
|
||||||
|
- [🎞️ Low Byte Productions](https://www.youtube.com/@lowbyteproductions)
|
||||||
|
|
||||||
### 🕹️ Arduino
|
### 🕹️ Arduino
|
||||||
|
|
||||||
If you do not have any background in programming the embedded systems, Arduino boards and libraries are the best choice for you to start and learn the basics. Just keep in mind that most of the Arduino libraries are developed for learning purposes and are not optimized to be used in industry.
|
If you do not have any background in programming embedded systems, Arduino boards and libraries are the best choice for you to start and learn the basics. Just keep in mind that most of the Arduino libraries are developed for learning purposes and are not optimized to be used in industry.
|
||||||
|
|
||||||
Additionally, the [Arduino Core](https://github.com/arduino/ArduinoCore-API) takes care of most of the low-level operations and manipulation of registers that you, as an embedded engineer, should be able to handle yourself. If you want to become a professional embedded developer, you should be able to effectively use industry-standard APIs and SDKs provided and approved by microcontroller vendors. For example, CMSIS for ARM Cortex, STM32Cube for STM32, ESP-IDF for Espressif microcontrollers, etc.
|
Additionally, the [Arduino Core](https://github.com/arduino/ArduinoCore-avr) (the platform-specific implementation of the hardware-independent [ArduinoCore-API](https://github.com/arduino/ArduinoCore-API)) takes care of most of the low-level hardware-associated operations that you, as an embedded engineer, should be able to handle yourself or at least have a clear understanding of. If you want to become a professional embedded developer, you should be able to effectively use industry-standard APIs and SDKs provided and approved by microcontroller vendors, such as CMSIS for ARM Cortex-M microcontrollers, STM32Cube for STM32, ESP-IDF for Espressif microcontrollers, etc.
|
||||||
|
|
||||||
- [🔗 Getting Started with Arduino](https://docs.arduino.cc/learn/starting-guide/getting-started-arduino)
|
- [🔗 Getting Started with Arduino](https://docs.arduino.cc/learn/starting-guide/getting-started-arduino)
|
||||||
|
- [🔗👶 Arduino Built-in Examples](https://docs.arduino.cc/built-in-examples/)
|
||||||
|
- [🔗 Arduino Language Reference](https://docs.arduino.cc/language-reference/)
|
||||||
|
- [🎞️ All New Arduino R4 WiFi LESSONS for Absolute Beginners (YouTube Playlist)](https://www.youtube.com/playlist?list=PLGs0VKk2DiYyn0wN335MXpbi3PRJTMmex)
|
||||||
- [🎞️ New Arduino Tutorials (YouTube Playlist)](https://youtube.com/playlist?list=PLGs0VKk2DiYw-L-RibttcvK-WBZm8WLEP)
|
- [🎞️ New Arduino Tutorials (YouTube Playlist)](https://youtube.com/playlist?list=PLGs0VKk2DiYw-L-RibttcvK-WBZm8WLEP)
|
||||||
- [🎞️ Arduino in a commercial product?](https://www.youtube.com/watch?v=c5LzsqeSCAc)
|
- [🎞️ Arduino in a commercial product?](https://www.youtube.com/watch?v=c5LzsqeSCAc)
|
||||||
- [🎞️ Arduino Project to Product (YouTube Playlist)](https://www.youtube.com/playlist?list=PLEBQazB0HUyQd6Fsf5NQ75M9llbi1_j_8)
|
- [🎞️ Arduino Project to Product (YouTube Playlist)](https://www.youtube.com/playlist?list=PLEBQazB0HUyQd6Fsf5NQ75M9llbi1_j_8)
|
||||||
@@ -138,8 +165,11 @@ Additionally, the [Arduino Core](https://github.com/arduino/ArduinoCore-API) tak
|
|||||||
### 👨🏫 Educational Websites
|
### 👨🏫 Educational Websites
|
||||||
|
|
||||||
- [🔗 DeepBlueMbedded](https://deepbluembedded.com/)
|
- [🔗 DeepBlueMbedded](https://deepbluembedded.com/)
|
||||||
|
- [🔗 Digital Electronics Deeds](https://www.digitalelectronicsdeeds.com/index.html)
|
||||||
- [🔗💎 Interrupt Blog by Memfault](https://interrupt.memfault.com/blog/)
|
- [🔗💎 Interrupt Blog by Memfault](https://interrupt.memfault.com/blog/)
|
||||||
|
- [🔗💎 EmbeddedRelated.com](https://www.embeddedrelated.com/)
|
||||||
- [🔗 ElectronicWings - Hardware Developers Community](https://www.electronicwings.com/)
|
- [🔗 ElectronicWings - Hardware Developers Community](https://www.electronicwings.com/)
|
||||||
|
- [🔗 EduTecnica (Italian)](https://www.edutecnica.it/)
|
||||||
- [🔗 Microchip University](https://mu.microchip.com/)
|
- [🔗 Microchip University](https://mu.microchip.com/)
|
||||||
- [🔗 Nordic Developer Academy](https://academy.nordicsemi.com/)
|
- [🔗 Nordic Developer Academy](https://academy.nordicsemi.com/)
|
||||||
- [🔗 Electronics Tutorials](https://www.electronics-tutorials.ws)
|
- [🔗 Electronics Tutorials](https://www.electronics-tutorials.ws)
|
||||||
@@ -147,11 +177,13 @@ Additionally, the [Arduino Core](https://github.com/arduino/ArduinoCore-API) tak
|
|||||||
- [🔗 Adafruit Learning System](https://learn.adafruit.com/)
|
- [🔗 Adafruit Learning System](https://learn.adafruit.com/)
|
||||||
- [🔗 STM32 World](https://stm32world.com)
|
- [🔗 STM32 World](https://stm32world.com)
|
||||||
- [🔗 ControllersTech](https://controllerstech.com/)
|
- [🔗 ControllersTech](https://controllerstech.com/)
|
||||||
|
- [🔗 Embedded Systems News - CNX Software](https://www.cnx-software.com/)
|
||||||
|
|
||||||
### 🗺️ Other Helpful Roadmaps
|
### 🗺️ Other Helpful Roadmaps
|
||||||
|
|
||||||
- [🔗 Embedded Artistry Beginners Roadmap](https://embeddedartistry.com/beginners/)
|
- [🔗 Embedded Artistry Beginners Roadmap](https://embeddedartistry.com/beginners/)
|
||||||
- [🔗 Embedded Systems Skill Tree](https://github.com/sjpiper145/MakerSkillTree/tree/main/Embedded%20Systems%20Skill%20Tree)
|
- [🔗 Embedded Systems Skill Tree](https://github.com/sjpiper145/MakerSkillTree/tree/main/Embedded%20Systems%20Skill%20Tree)
|
||||||
|
- [🔗 Interactive Embedded Systems Skill Tree (with progress tracking & sync)](https://colonelblacc.github.io/Embedded-Systems-SkillTree)
|
||||||
- [🔗 PCB Design Skill Tree](https://github.com/sjpiper145/MakerSkillTree/tree/main/PCB%20Design%20Skill%20Tree)
|
- [🔗 PCB Design Skill Tree](https://github.com/sjpiper145/MakerSkillTree/tree/main/PCB%20Design%20Skill%20Tree)
|
||||||
- [🔗 FPGA / ASIC Engineering Roadmap](https://github.com/m3y54m/FPGA-ASIC-Roadmap)
|
- [🔗 FPGA / ASIC Engineering Roadmap](https://github.com/m3y54m/FPGA-ASIC-Roadmap)
|
||||||
|
|
||||||
@@ -198,7 +230,7 @@ _________________
|
|||||||
|
|
||||||
### ✳️ Electronics
|
### ✳️ Electronics
|
||||||
|
|
||||||
#### 🔵 Basic Math & Calculus
|
#### 🔵 Basic Calculus
|
||||||
|
|
||||||
- [🎞️ Khan Academy - Calculus 1](https://www.khanacademy.org/math/calculus-1)
|
- [🎞️ Khan Academy - Calculus 1](https://www.khanacademy.org/math/calculus-1)
|
||||||
- [📘👶 Calculus For Dummies - Mark Ryan](https://a.co/d/cyvZqE1)
|
- [📘👶 Calculus For Dummies - Mark Ryan](https://a.co/d/cyvZqE1)
|
||||||
@@ -214,6 +246,7 @@ _________________
|
|||||||
|
|
||||||
#### 🔵 Electronics Fundamentals
|
#### 🔵 Electronics Fundamentals
|
||||||
|
|
||||||
|
- [🔗👶💎 Build Electronic Circuits - Øyvind Nydal Dahl](https://www.build-electronic-circuits.com/)
|
||||||
- [🎞️👶 Electronic Basics - GreatScott! (YouTube Playlist)](https://youtube.com/playlist?list=PLAROrg3NQn7cyu01HpOv5BWo217XWBZu0)
|
- [🎞️👶 Electronic Basics - GreatScott! (YouTube Playlist)](https://youtube.com/playlist?list=PLAROrg3NQn7cyu01HpOv5BWo217XWBZu0)
|
||||||
- [📘👶 Make: Electronics: Learning by Discovery - Charles Platt](https://a.co/d/fbRoM1R)
|
- [📘👶 Make: Electronics: Learning by Discovery - Charles Platt](https://a.co/d/fbRoM1R)
|
||||||
- [📘👶💎 Electronic Devices: Conventional Current Version - Thomas Floyd, David Buchla, Steven Wetterling](https://a.co/d/1dsS0vx)
|
- [📘👶💎 Electronic Devices: Conventional Current Version - Thomas Floyd, David Buchla, Steven Wetterling](https://a.co/d/1dsS0vx)
|
||||||
@@ -222,10 +255,12 @@ _________________
|
|||||||
- [📘💎 The Art of Electronics - Paul Horowitz, Winfield Hill](https://artofelectronics.net/)
|
- [📘💎 The Art of Electronics - Paul Horowitz, Winfield Hill](https://artofelectronics.net/)
|
||||||
- [📝 Here’s a Quick Way to Know about Major Electronic Components](https://www.elprocus.com/major-electronic-components/)
|
- [📝 Here’s a Quick Way to Know about Major Electronic Components](https://www.elprocus.com/major-electronic-components/)
|
||||||
- [📘 Encyclopedia of Electronic Components - Charles Platt](https://a.co/d/83cSiU7)
|
- [📘 Encyclopedia of Electronic Components - Charles Platt](https://a.co/d/83cSiU7)
|
||||||
|
- [🔗 Awesome Electronics: A curated list of electronics resources](https://github.com/kitspace/awesome-electronics)
|
||||||
|
|
||||||
#### 🔵 Digital Design
|
#### 🔵 Digital Design
|
||||||
|
|
||||||
- [🔗👶 Tiny Tapeout > Digital Design Guide](https://tinytapeout.com/digital_design/)
|
- [🔗👶 Tiny Tapeout > Digital Design Guide](https://tinytapeout.com/digital_design/)
|
||||||
|
- [🔗👶 Mvidia - From Transistors to Teraflops (interactive course)](https://jaso1024.com/mvidia/)
|
||||||
- [📘💎 Digital Design - Morris Mano, Michael Ciletti](https://a.co/d/c3tBaoc)
|
- [📘💎 Digital Design - Morris Mano, Michael Ciletti](https://a.co/d/c3tBaoc)
|
||||||
- [📘👶💎 Digital Design and Computer Architecture: ARM Edition - Sarah Harris, David Harris](https://a.co/d/4otoVvI)
|
- [📘👶💎 Digital Design and Computer Architecture: ARM Edition - Sarah Harris, David Harris](https://a.co/d/4otoVvI)
|
||||||
- [📘👶💎 Digital Design and Computer Architecture: RISC-V Edition - Sarah Harris, David Harris](https://a.co/d/61l7Jtb)
|
- [📘👶💎 Digital Design and Computer Architecture: RISC-V Edition - Sarah Harris, David Harris](https://a.co/d/61l7Jtb)
|
||||||
@@ -238,6 +273,7 @@ Computer architecture is the backbone of embedded systems, governing the hardwar
|
|||||||
- [📘👶💎 Computer Organization and Design: ARM Edition - David A. Patterson, John L. Hennessy](https://a.co/d/8YPUXG7)
|
- [📘👶💎 Computer Organization and Design: ARM Edition - David A. Patterson, John L. Hennessy](https://a.co/d/8YPUXG7)
|
||||||
- [📘👶💎 Digital Design and Computer Architecture: ARM Edition - Sarah Harris, David Harris](https://a.co/d/4otoVvI)
|
- [📘👶💎 Digital Design and Computer Architecture: ARM Edition - Sarah Harris, David Harris](https://a.co/d/4otoVvI)
|
||||||
- [📘👶💎 Digital Design and Computer Architecture: RISC-V Edition - Sarah Harris, David Harris](https://a.co/d/61l7Jtb)
|
- [📘👶💎 Digital Design and Computer Architecture: RISC-V Edition - Sarah Harris, David Harris](https://a.co/d/61l7Jtb)
|
||||||
|
- [📘👶 The Elements of Computing Systems - Noam Nisan, Shimon Schocken](https://a.co/d/cX6jkLl)
|
||||||
- [📘 Computer Organization and Embedded Systems - Carl Hamacher, Zvonko Vranesic, Safwat Zaky, Naraig Manjikian](https://a.co/d/2lt4Svf)
|
- [📘 Computer Organization and Embedded Systems - Carl Hamacher, Zvonko Vranesic, Safwat Zaky, Naraig Manjikian](https://a.co/d/2lt4Svf)
|
||||||
- [📘 Embedded Systems Architecture - Tammy Noergaard](https://www.oreilly.com/library/view/embedded-systems-architecture/9780123821966/)
|
- [📘 Embedded Systems Architecture - Tammy Noergaard](https://www.oreilly.com/library/view/embedded-systems-architecture/9780123821966/)
|
||||||
- [📘 Embedded Systems Architecture - Daniele Lacamera](https://www.packtpub.com/product/embedded-systems-architecture-second-edition/9781803239545)
|
- [📘 Embedded Systems Architecture - Daniele Lacamera](https://www.packtpub.com/product/embedded-systems-architecture-second-edition/9781803239545)
|
||||||
@@ -254,7 +290,7 @@ Embedded systems often require specialized test equipment to verify their functi
|
|||||||
#### 🔵 Multimeter
|
#### 🔵 Multimeter
|
||||||
|
|
||||||
- [🎞️👶 Learn How to Use a Multimeter!](https://youtu.be/4lAyzRxsbDc)
|
- [🎞️👶 Learn How to Use a Multimeter!](https://youtu.be/4lAyzRxsbDc)
|
||||||
- [📝👶 How to Use a Multimeter ](https://learn.sparkfun.com/tutorials/how-to-use-a-multimeter/all)
|
- [📝👶 How to Use a Multimeter](https://learn.sparkfun.com/tutorials/how-to-use-a-multimeter/all)
|
||||||
|
|
||||||
#### 🔵 Logic / Protocol Analyzer
|
#### 🔵 Logic / Protocol Analyzer
|
||||||
|
|
||||||
@@ -288,19 +324,28 @@ Breadboarding is a hands-on approach to prototyping circuits, providing a versat
|
|||||||
|
|
||||||
#### 🔵 PCB Design / EMC
|
#### 🔵 PCB Design / EMC
|
||||||
|
|
||||||
- [🎞️ Altium Academy YouTube Channel](https://www.youtube.com/@AltiumAcademy)
|
- [🎞️💎 Altium Academy YouTube Channel](https://www.youtube.com/@AltiumAcademy)
|
||||||
- [🎞️ Robert Feranec YouTube Channel](https://www.youtube.com/@RobertFeranec)
|
- [🎞️💎 Robert Feranec YouTube Channel](https://www.youtube.com/@RobertFeranec)
|
||||||
- [🎞️👶 Altium Tutorials for Beginners - Robert Feranec (YouTube Playlist)](https://www.youtube.com/playlist?list=PLXvLToQzgzdduBaD4horowdWgcG5uGUW4)
|
- [🎞️👶 Altium Tutorials for Beginners - Robert Feranec (YouTube Playlist)](https://www.youtube.com/playlist?list=PLXvLToQzgzdduBaD4horowdWgcG5uGUW4)
|
||||||
- [🎞️👶 Create PCBs for Rapid Prototyping - DigiKey (YouTube Playlist)](https://www.youtube.com/playlist?list=PLEBQazB0HUyRVK1RpHIjyjuTxn5srz6XM)
|
- [🎞️👶 Create PCBs for Rapid Prototyping - DigiKey (YouTube Playlist)](https://www.youtube.com/playlist?list=PLEBQazB0HUyRVK1RpHIjyjuTxn5srz6XM)
|
||||||
|
- [🎞️👶💎 PCB Design for Beginners - Altium Academy (YouTube Playlist)](https://www.youtube.com/playlist?list=PL3aaAq2OJU5EsYtNwTPHNO3RHNJN34FbO)
|
||||||
- [🎞️ How to Make a Raspberry Pi Compute Module 4 Carrier Board - DigiKey (YouTube Playlist)](https://www.youtube.com/playlist?list=PLEBQazB0HUyRwjr761b-MLNEu8FA8WKSG)
|
- [🎞️ How to Make a Raspberry Pi Compute Module 4 Carrier Board - DigiKey (YouTube Playlist)](https://www.youtube.com/playlist?list=PLEBQazB0HUyRwjr761b-MLNEu8FA8WKSG)
|
||||||
- [🎞️ How To Learn PCB Design (My Thoughts, Journey, and Resources) - Phil's Lab #87](https://youtu.be/aODkA2mrimQ)
|
- [🎞️💎 How To Learn PCB Design (My Thoughts, Journey, and Resources) - Phil's Lab #87](https://youtu.be/aODkA2mrimQ)
|
||||||
- [🎞️👶 KiCad 6 STM32 PCB Design Full Tutorial - Phil's Lab #65](https://youtu.be/aVUqaB0IMh4)
|
- [🎞️👶 KiCad 6 STM32 PCB Design Full Tutorial - Phil's Lab #65](https://youtu.be/aVUqaB0IMh4)
|
||||||
- [🎞️👶 Intro to KiCad - DigiKey (YouTube Playlist)](https://www.youtube.com/watch?v=vaCVh2SAZY4&list=PLEBQazB0HUyR24ckSZ5u05TZHV9khgA1O)
|
- [🎞️👶 Intro to KiCad - DigiKey (YouTube Playlist)](https://www.youtube.com/watch?v=vaCVh2SAZY4&list=PLEBQazB0HUyR24ckSZ5u05TZHV9khgA1O)
|
||||||
- [🎞️ PCB Design for EMI & SI - Phil's Lab #64](https://www.youtube.com/watch?v=VtzPL8wQ8-E)
|
- [🎞️💎 PCB Design for EMI & SI - Phil's Lab #64](https://www.youtube.com/watch?v=VtzPL8wQ8-E)
|
||||||
- [🎞️ PCB Design for Advanced Users (YouTube Playlist)](https://youtube.com/playlist?list=PL3aaAq2OJU5EWfYT_Z93tc9pBF_Sidba_)
|
- [🎞️💎 PCB Design for Advanced Users (YouTube Playlist)](https://youtube.com/playlist?list=PL3aaAq2OJU5EWfYT_Z93tc9pBF_Sidba_)
|
||||||
- [🎞️ 9 Simple Tricks to Improve EMC / EMI on Your Boards - Practical examples (with Min Zhang)](https://www.youtube.com/watch?v=Lf51sx6sC0I)
|
- [🎞️💎 PCB Knowledge [PCB Production Tips By PCBWay] (YouTube Playlist)](https://www.youtube.com/playlist?list=PLzxdr3n97eVtHuh7gnDzMubuO836kvfu7)
|
||||||
- [🎞️ Many EMC Tips to Help You Design Better PCB Boards (with Keith Armstrong)](https://www.youtube.com/watch?v=gHF5JyJF-N4)
|
- [🎞️💎 Udemy – High-Speed Board Design Course System On Module - EsteemPCB](https://www.udemy.com/course/highspeed_board_design_course)
|
||||||
|
- [🎞️💎 Udemy – Advanced Hardware and PCB Design Masterclass 2022](https://www.udemy.com/course/advanced-hardware-design-course)
|
||||||
|
- [🎞️💎 9 Simple Tricks to Improve EMC / EMI on Your Boards - Practical examples (with Min Zhang)](https://www.youtube.com/watch?v=Lf51sx6sC0I)
|
||||||
|
- [🎞️💎 Many EMC Tips to Help You Design Better PCB Boards (with Keith Armstrong)](https://www.youtube.com/watch?v=gHF5JyJF-N4)
|
||||||
- [🔗 LearnEMC - EMC Resources](https://learnemc.com/emc-resources)
|
- [🔗 LearnEMC - EMC Resources](https://learnemc.com/emc-resources)
|
||||||
|
- [🔗 A Better Way to Build PCBs - Flux AI](https://www.flux.ai/p/)
|
||||||
|
- [🔗 EasyEDA](https://easyeda.com/)
|
||||||
|
- [🔗 KiCad - Free and open-source schematic capture and PCB design software](https://www.kicad.org/)
|
||||||
|
- [🔗 Fritzing](https://fritzing.org/)
|
||||||
|
- [🔗 Cirkit Designer](https://www.cirkitdesigner.com/)
|
||||||
|
|
||||||
#### 🔵 Soldering / Rework
|
#### 🔵 Soldering / Rework
|
||||||
|
|
||||||
@@ -319,7 +364,7 @@ FPGAs (Field-Programmable Gate Arrays) are specialized integrated circuits that
|
|||||||
|
|
||||||
- [🔗 FPGA / ASIC Engineering Roadmap](https://github.com/m3y54m/FPGA-ASIC-Roadmap)
|
- [🔗 FPGA / ASIC Engineering Roadmap](https://github.com/m3y54m/FPGA-ASIC-Roadmap)
|
||||||
- [📝 What are FPGAs?](https://hackaday.io/project/27550-the-hobbyists-guide-to-fpgas/log/68114-what-are-fpgas)
|
- [📝 What are FPGAs?](https://hackaday.io/project/27550-the-hobbyists-guide-to-fpgas/log/68114-what-are-fpgas)
|
||||||
- [📝 FPGA vs. Microcontroller: How to choose the right one for your project](https://www.youtube.com/watch?v=jblpHUCPQYs)
|
- [🎞️ FPGA vs. Microcontroller: How to choose the right one for your project](https://www.youtube.com/watch?v=jblpHUCPQYs)
|
||||||
- [🎞️👶 Introduction to FPGA (YouTube Playlist)](https://youtube.com/playlist?list=PLEBQazB0HUyT1WmMONxRZn9NmQ_9CIKhb)
|
- [🎞️👶 Introduction to FPGA (YouTube Playlist)](https://youtube.com/playlist?list=PLEBQazB0HUyT1WmMONxRZn9NmQ_9CIKhb)
|
||||||
- [🔗👶 FPGA Fundamentals - Nandland](https://nandland.com/fpga-101/)
|
- [🔗👶 FPGA Fundamentals - Nandland](https://nandland.com/fpga-101/)
|
||||||
- [🎞️💎 Coursera – FPGA Design for Embedded Systems Specialization](https://www.coursera.org/specializations/fpga-design)
|
- [🎞️💎 Coursera – FPGA Design for Embedded Systems Specialization](https://www.coursera.org/specializations/fpga-design)
|
||||||
@@ -331,6 +376,14 @@ _________________
|
|||||||
|
|
||||||
Mastering programming fundamentals and software development principles is essential for a successful embedded design. Embedded systems demand efficient code, optimized data management, reusable design patterns, and effective memory management to function effectively in resource-constrained environments. Embedded developers must possess a strong foundation in these core concepts to create reliable and performant embedded systems.
|
Mastering programming fundamentals and software development principles is essential for a successful embedded design. Embedded systems demand efficient code, optimized data management, reusable design patterns, and effective memory management to function effectively in resource-constrained environments. Embedded developers must possess a strong foundation in these core concepts to create reliable and performant embedded systems.
|
||||||
|
|
||||||
|
#### 🔵 Basic Discrete Mathematics
|
||||||
|
|
||||||
|
- [🎞️ Coursera - Introduction to Discrete Mathematics for Computer Science Specialization](https://www.coursera.org/specializations/discrete-mathematics)
|
||||||
|
- [📘👶 Open Access Discrete Mathematics Books by LibreTexts](https://math.libretexts.org/Bookshelves/Combinatorics_and_Discrete_Mathematics)
|
||||||
|
- [📘👶 Essential Discrete Mathematics for Computer Science - Harry Lewis, Rachel Zax](https://dl.acm.org/doi/book/10.5555/3388468)
|
||||||
|
- [📘👶 Guide to Discrete Mathematics - Gerard O'Regan, Springer](https://link.springer.com/book/10.1007/978-3-030-81588-2)
|
||||||
|
- [📘💎 Bridge to Abstract Mathematics, AMS](https://bookstore.ams.org/view?ProductCode=TEXT/22)
|
||||||
|
|
||||||
#### 🔵 Algorithms & Data Structures
|
#### 🔵 Algorithms & Data Structures
|
||||||
|
|
||||||
- [📘👶💎 Grokking Algorithms - Aditya Y. Bhargava](https://www.manning.com/books/grokking-algorithms)
|
- [📘👶💎 Grokking Algorithms - Aditya Y. Bhargava](https://www.manning.com/books/grokking-algorithms)
|
||||||
@@ -339,21 +392,25 @@ Mastering programming fundamentals and software development principles is essent
|
|||||||
- [🎞️ Udemy – Embedded Systems State Machines & Data Structures](https://www.udemy.com/course/embedded-systems-state-machines-data-structures-like-a-pro/)
|
- [🎞️ Udemy – Embedded Systems State Machines & Data Structures](https://www.udemy.com/course/embedded-systems-state-machines-data-structures-like-a-pro/)
|
||||||
- [🔗 Data Structures in C](https://www.sanfoundry.com/c-programming-examples-data-structures/)
|
- [🔗 Data Structures in C](https://www.sanfoundry.com/c-programming-examples-data-structures/)
|
||||||
- [🎞️ Data Structures - Full Course Using C and C++](https://www.youtube.com/watch?v=B31LgI4Y4DQ)
|
- [🎞️ Data Structures - Full Course Using C and C++](https://www.youtube.com/watch?v=B31LgI4Y4DQ)
|
||||||
|
- [🔗 Hello Algo](https://www.hello-algo.com/en/)
|
||||||
|
|
||||||
#### 🔵 Design Patterns
|
#### 🔵 Design Patterns
|
||||||
|
|
||||||
- [📝 Practical Design Patterns: Opaque Pointers and Objects in C](https://interrupt.memfault.com/blog/opaque-pointers)
|
- [📝 Practical Design Patterns: Opaque Pointers and Objects in C](https://interrupt.memfault.com/blog/opaque-pointers)
|
||||||
- [📘💎 Design Patterns for Embedded Systems in C - Bruce Powel Douglass](https://www.oreilly.com/library/view/design-patterns-for/9781856177078/)
|
- [📘💎 Design Patterns for Embedded Systems in C - Bruce Powel Douglass](https://www.oreilly.com/library/view/design-patterns-for/9781856177078/)
|
||||||
- [📘💎 Making Embedded Systems: Design Patterns for Great Software - Elecia White](https://www.oreilly.com/library/view/making-embedded-systems/9781449308889/)
|
- [📘💎 Making Embedded Systems: Design Patterns for Great Software - Elecia White](https://www.oreilly.com/library/view/making-embedded-systems/9781449308889/)
|
||||||
|
- [🎧💎 Making Embedded Systems: Design Patterns for Great Software - Elecia White (Audio Book)](https://www.audiobooks.com/audiobook/making-embedded-systems-design-patterns-for-great-software/814297)
|
||||||
|
- [🔗 Design Patterns - Refactoring Guru](https://refactoring.guru/design-patterns)
|
||||||
|
|
||||||
#### 🔵 State Machines
|
#### 🔵 State Machines
|
||||||
|
|
||||||
- [📝 Programming embedded systems the easy way – with state machines](https://www.embedded.com/programming-embedded-systems-the-easy-way-with-state-machines/)
|
- [📝 Programming embedded systems the easy way – with state machines](https://www.embedded.com/programming-embedded-systems-the-easy-way-with-state-machines/)
|
||||||
- [🎞️ State Machines (YouTube Playlist)](https://www.youtube.com/playlist?list=PLPW8O6W-1chxym7TgIPV9k5E8YJtSBToI)
|
- [🎞️💎 State Machines (YouTube Playlist)](https://www.youtube.com/playlist?list=PLPW8O6W-1chxym7TgIPV9k5E8YJtSBToI)
|
||||||
- [🎞️ Event-Driven Programming (YouTube Playlist)](https://www.youtube.com/playlist?list=PLPW8O6W-1chx8Y7Oq2gOE0NUPXmQxu2Wr)
|
- [🎞️💎 Event-Driven Programming (YouTube Playlist)](https://www.youtube.com/playlist?list=PLPW8O6W-1chx8Y7Oq2gOE0NUPXmQxu2Wr)
|
||||||
- [🎞️ Udemy - Embedded System Design using UML State Machines](https://www.udemy.com/course/embedded-system-design-using-uml-state-machines/)
|
- [🎞️💎 Udemy - Embedded System Design using UML State Machines](https://www.udemy.com/course/embedded-system-design-using-uml-state-machines/)
|
||||||
- [📝 “Input-Driven” vs. Event-Driven State Machines](https://www.state-machine.com/input-driven-vs-event-driven-state-machines)
|
- [📝 “Input-Driven” vs. Event-Driven State Machines](https://www.state-machine.com/input-driven-vs-event-driven-state-machines)
|
||||||
- [📝 State Machines for Event-Driven Systems](https://barrgroup.com/embedded-systems/how-to/state-machines-event-driven-systems)
|
- [📝 State Machines for Event-Driven Systems](https://barrgroup.com/embedded-systems/how-to/state-machines-event-driven-systems)
|
||||||
|
- [📘 Practical UML Statecharts in C/C++ by Miro Samek](https://www.amazon.sg/Practical-UML-Statecharts-Event-Driven-Programming/dp/0750687061)
|
||||||
|
|
||||||
#### 🔵 Memory Management
|
#### 🔵 Memory Management
|
||||||
|
|
||||||
@@ -380,30 +437,51 @@ Low-level languages like C and assembly provide direct hardware access, enabling
|
|||||||
- [🎞️💎 Microchip University - C Programming Callbacks](https://mu.microchip.com/c-programming-callbacks)
|
- [🎞️💎 Microchip University - C Programming Callbacks](https://mu.microchip.com/c-programming-callbacks)
|
||||||
- [🎞️👶 C Programming for Beginners | Full Course](https://www.youtube.com/watch?v=ssJY5MDLjlo)
|
- [🎞️👶 C Programming for Beginners | Full Course](https://www.youtube.com/watch?v=ssJY5MDLjlo)
|
||||||
- [🎞️ C Programming Tutorials (YouTube Playlist)](https://www.youtube.com/playlist?list=PLA1FTfKBAEX4hblYoH6mnq0zsie2w6Wif)
|
- [🎞️ C Programming Tutorials (YouTube Playlist)](https://www.youtube.com/playlist?list=PLA1FTfKBAEX4hblYoH6mnq0zsie2w6Wif)
|
||||||
- [📘💎 The C Programming Language - Brian W. Kernighan, Dennis M. Ritchie ](https://www.amazon.com/dp/0131103628?ref_=cm_sw_r_cp_ud_dp_KRZ8M1WTXWVG0HMSRBXA)
|
- [📘💎 The C Programming Language - Brian W. Kernighan, Dennis M. Ritchie](https://www.amazon.com/dp/0131103628?ref_=cm_sw_r_cp_ud_dp_KRZ8M1WTXWVG0HMSRBXA)
|
||||||
- [📘 C How to Program - Paul Deitel, Harvey Deitel ](https://deitel.com/c-how-to-program-9-e/)
|
- [🔗👶 C by Example](https://www.cbyexample.com/)
|
||||||
|
- [📘👶 The Little Book of C](https://little-book-of.github.io/c/books/en-US/book.html)
|
||||||
|
- [📘👶 Beej's Guide to C Programming](https://beej.us/guide/bgc/)
|
||||||
|
- [📘 C How to Program - Paul Deitel, Harvey Deitel](https://deitel.com/c-how-to-program-9-e/)
|
||||||
- [📘 Effective C - An Introduction to Professional C Programming - Robert C. Seacord](https://nostarch.com/Effective_C)
|
- [📘 Effective C - An Introduction to Professional C Programming - Robert C. Seacord](https://nostarch.com/Effective_C)
|
||||||
- [🔗 Embedded C Coding Standard](https://barrgroup.com/embedded-systems/books/embedded-c-coding-standard)
|
- [📘 Modern C - Jens Gustedt](https://gustedt.gitlabpages.inria.fr/modern-c/)
|
||||||
|
- [🔗 Embedded C Coding Standard](https://barrgroup.com/free-downloads)
|
||||||
- [🔗 newlib C Library Documentation](https://sourceware.org/newlib/docs.html)
|
- [🔗 newlib C Library Documentation](https://sourceware.org/newlib/docs.html)
|
||||||
- [🔗 The GNU C Library (glibc)](https://www.gnu.org/software/libc/documentation.html)
|
- [🔗 The GNU C Library (glibc)](https://www.gnu.org/software/libc/documentation.html)
|
||||||
- [📝 From Zero to main(): Bare metal C](https://interrupt.memfault.com/blog/zero-to-main-1)
|
- [📝 From Zero to main(): Bare metal C](https://interrupt.memfault.com/blog/zero-to-main-1)
|
||||||
- [📝 From Zero to main(): Bootstrapping libc with Newlib](https://interrupt.memfault.com/blog/boostrapping-libc-with-newlib)
|
- [📝 From Zero to main(): Bootstrapping libc with Newlib](https://interrupt.memfault.com/blog/bootstrapping-libc-with-newlib)
|
||||||
|
- [🎞️👶 Learn C Programming and OOP with Dr. Chuck](https://www.youtube.com/watch?v=PaPN51Mm5qQ)
|
||||||
|
- [📘 Extreme C - Kamran Amini](https://www.packtpub.com/free-ebook/extreme-c/9781789343625)
|
||||||
|
- [🎞️👶 Coursera - C Programming with Linux Specialization](https://www.coursera.org/specializations/c-programming-linux)
|
||||||
|
|
||||||
#### 🔵 C++
|
#### 🔵 C++
|
||||||
|
|
||||||
- [📝 Modern C++ in Embedded Development: (Don't Fear) The ++](https://www.embeddedrelated.com/showarticle/1532.php)
|
- [📝 Modern C++ in Embedded Development: (Don't Fear) The ++](https://www.embeddedrelated.com/showarticle/1532.php)
|
||||||
- [📝 C++ On Embedded Systems](https://blog.mbedded.ninja/programming/languages/c-plus-plus/cpp-on-embedded-systems/)
|
- [📝 C++ On Embedded Systems](https://blog.mbedded.ninja/programming/languages/c-plus-plus/cpp-on-embedded-systems/)
|
||||||
|
- [📝💎 Bare Metal C++ - Alexey Rybachuk](https://arobenko.github.io/bare_metal_cpp/)
|
||||||
|
- [🎞️💎 C++ in Constrained Environments - Bjarne Stroustrup - CppCon 2022](https://www.youtube.com/watch?v=2BuJjaGuInI)
|
||||||
- [🎞️👶 C++ Tutorial for Beginners - Full Course](https://www.youtube.com/watch?v=vLnPwxZdW4Y)
|
- [🎞️👶 C++ Tutorial for Beginners - Full Course](https://www.youtube.com/watch?v=vLnPwxZdW4Y)
|
||||||
- [🎞️ C++ by The Cherno (YouTube Playlist)](https://youtube.com/playlist?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb)
|
- [🎞️ C++ by The Cherno (YouTube Playlist)](https://youtube.com/playlist?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb)
|
||||||
|
- [🎞️👶💎 Udemy - Beginning C++ Programming - From Beginner to Beyond](https://www.udemy.com/course/beginning-c-plus-plus-programming/)
|
||||||
|
- [🎞️💎 CppCon Conferences - YouTube](https://www.youtube.com/@CppCon/playlists)
|
||||||
|
- [📘💎 A Tour of C++ (3rd Edition) - Bjarne Stroustrup](https://www.stroustrup.com/tour3.html)
|
||||||
|
- [📘💎 The C++ programming Language (C++11) - Bjarne Stroustrup](https://www.stroustrup.com/4th.html)
|
||||||
|
- [📘💎 Programming -- Principles and Practice Using C++ (3rd Edition) - Bjarne Stroustrup](https://www.stroustrup.com/programming.html)
|
||||||
- [📘💎 Real-Time C++: Efficient Object-Oriented and Template Microcontroller Programming - Christopher Kormanyos](https://link.springer.com/book/10.1007/978-3-662-62996-3)
|
- [📘💎 Real-Time C++: Efficient Object-Oriented and Template Microcontroller Programming - Christopher Kormanyos](https://link.springer.com/book/10.1007/978-3-662-62996-3)
|
||||||
- [📘 Effective Modern C++ - Scott Meyers](https://www.oreilly.com/library/view/effective-modern-c/9781491908419/)
|
- [📘 Effective Modern C++ - Scott Meyers](https://www.oreilly.com/library/view/effective-modern-c/9781491908419/)
|
||||||
|
|
||||||
|
##### Additional resources from Bjarne Stroustrup, creator of C++
|
||||||
|
|
||||||
|
- [🎞️👶 The Essence of C++ by Bjarne Stroustrup - YouTube](https://www.youtube.com/watch?v=86xWVb4XIyE)
|
||||||
|
- [🎞️👶💎 Delivering Safe C++ by Bjarne Stroustrup - CppCon 2023](https://www.youtube.com/watch?v=I8UvQKvOSSw)
|
||||||
|
|
||||||
#### 🔵 Assembly
|
#### 🔵 Assembly
|
||||||
|
|
||||||
- [📝👶 Introduction to ARM Assembly Basics](https://azeria-labs.com/writing-arm-assembly-part-1/)
|
- [📝👶 Introduction to ARM Assembly Basics](https://azeria-labs.com/writing-arm-assembly-part-1/)
|
||||||
- [🎞️ Udemy – ARM GNU Assembly Programming From Ground Up](https://www.udemy.com/course/arm-gnu-assembly-programming-from-ground-uptm/)
|
- [🎞️ Udemy – ARM GNU Assembly Programming From Ground Up](https://www.udemy.com/course/arm-gnu-assembly-programming-from-ground-uptm/)
|
||||||
- [🎞️ Assembly Language Programming with ARM – Full Tutorial for Beginners](https://www.youtube.com/watch?v=gfmRrPjnEw4)
|
- [🎞️ Assembly Language Programming with ARM – Full Tutorial for Beginners](https://www.youtube.com/watch?v=gfmRrPjnEw4)
|
||||||
- [📝 How to Use Inline Assembly Language in C Code](https://gcc.gnu.org/onlinedocs/gcc/extensions-to-the-c-language-family/how-to-use-inline-assembly-language-in-c-code.html)
|
- [📝 How to Use Inline Assembly Language in C Code](https://gcc.gnu.org/onlinedocs/gcc/Using-Assembly-Language-with-C.html)
|
||||||
|
- [📘 Raspberry Pi Assembly Language Programming : ARM Processor Coding](https://www.amazon.com/Raspberry-Assembly-Language-Programming-Processor/dp/1484252861)
|
||||||
|
- [🔗 Battery info program for x86-64 Linux laptops in 298 bytes of machine code](https://github.com/meribold/btry)
|
||||||
|
|
||||||
#### 🔵 Python
|
#### 🔵 Python
|
||||||
|
|
||||||
@@ -415,9 +493,10 @@ Low-level languages like C and assembly provide direct hardware access, enabling
|
|||||||
##### 🔶 Python for Microcontrollers
|
##### 🔶 Python for Microcontrollers
|
||||||
|
|
||||||
- [🔗 MicroPython - Python for microcontrollers](https://micropython.org/)
|
- [🔗 MicroPython - Python for microcontrollers](https://micropython.org/)
|
||||||
- [🔗 MicroPython 101 | Arduino Documentation](https://docs.arduino.cc/micropython-course/)
|
- [🔗 MicroPython 101 | Arduino Documentation](https://docs.arduino.cc/micropython/)
|
||||||
- [🔗 CircuitPython](https://circuitpython.org/)
|
- [🔗 CircuitPython](https://circuitpython.org/)
|
||||||
- [📝 The Pros and Cons of Designing Embedded Systems with MicroPython](https://www.designnews.com/electronics-test/pros-and-cons-designing-embedded-systems-micropython)
|
- [📝 The Pros and Cons of Designing Embedded Systems with MicroPython](https://www.designnews.com/electronics-test/pros-and-cons-designing-embedded-systems-micropython)
|
||||||
|
- [📝 Programming the ESP32 with MicroPython](https://wolles-elektronikkiste.de/en/programming-the-esp32-with-micropython)
|
||||||
|
|
||||||
#### 🔵 Rust
|
#### 🔵 Rust
|
||||||
|
|
||||||
@@ -426,10 +505,13 @@ Low-level languages like C and assembly provide direct hardware access, enabling
|
|||||||
- [🎞️ The Future of Programming: Rust (YouTube Playlist)](https://www.youtube.com/playlist?list=PLc7W4b0WHTAUAEAguiqpNa5H0QqXJIJI6)
|
- [🎞️ The Future of Programming: Rust (YouTube Playlist)](https://www.youtube.com/playlist?list=PLc7W4b0WHTAUAEAguiqpNa5H0QqXJIJI6)
|
||||||
- [🔗 Community Rust support projects for STM32 microcontrollers](https://github.com/stm32-rs)
|
- [🔗 Community Rust support projects for STM32 microcontrollers](https://github.com/stm32-rs)
|
||||||
- [🔗 Rust on ESP Community](https://github.com/esp-rs)
|
- [🔗 Rust on ESP Community](https://github.com/esp-rs)
|
||||||
|
- [🎞️ Embedded Rust su ESP32 (Italian) - YouTube Playlist](https://www.youtube.com/playlist?list=PLT--ndZEB54ek5_Zv6x6WCioh5m35zh5i)
|
||||||
- [📝 Rust on STM32: Getting started](https://jonathanklimt.de/electronics/programming/embedded-rust/rust-on-stm32-2/)
|
- [📝 Rust on STM32: Getting started](https://jonathanklimt.de/electronics/programming/embedded-rust/rust-on-stm32-2/)
|
||||||
- [📝 From Zero to main(): Bare metal Rust](https://interrupt.memfault.com/blog/zero-to-main-rust-1)
|
- [📝 From Zero to main(): Bare metal Rust](https://interrupt.memfault.com/blog/zero-to-main-rust-1)
|
||||||
- [📝 Writing an OS in Rust - Philipp Oppermann's blog](https://os.phil-opp.com/)
|
- [📝 Writing an OS in Rust - Philipp Oppermann's blog](https://os.phil-opp.com/)
|
||||||
- [📝 Kernel Driver with Rust in 2022](https://not-matthias.github.io/posts/kernel-driver-with-rust/)
|
- [📝 Kernel Driver with Rust in 2022](https://not-matthias.github.io/posts/kernel-driver-with-rust/)
|
||||||
|
- [🔗 Rustlings - Small exercises to get you used to reading and writing Rust code!](https://rustlings.rust-lang.org/)
|
||||||
|
- [🔗 Learn Rust the Effective Way](https://www.rustfinity.com/)
|
||||||
|
|
||||||
#### 🔵 Zig
|
#### 🔵 Zig
|
||||||
|
|
||||||
@@ -447,12 +529,13 @@ Microcontrollers are integrated circuits (ICs) that combine a microprocessor, me
|
|||||||
Some popular microcontroller families include AVR, PIC, STM32, MSP430, nRF, and ESP32. Choosing the right microcontroller involves assessing application requirements, processing power, memory needs, and input/output capabilities. Consider ease of use, cost, reliability, availability, and future expansion.
|
Some popular microcontroller families include AVR, PIC, STM32, MSP430, nRF, and ESP32. Choosing the right microcontroller involves assessing application requirements, processing power, memory needs, and input/output capabilities. Consider ease of use, cost, reliability, availability, and future expansion.
|
||||||
|
|
||||||
- [📝 What Is a Microcontroller? The Defining Characteristics and Architecture of a Common Component](https://www.allaboutcircuits.com/technical-articles/what-is-a-microcontroller-introduction-component-characteristics-component/)
|
- [📝 What Is a Microcontroller? The Defining Characteristics and Architecture of a Common Component](https://www.allaboutcircuits.com/technical-articles/what-is-a-microcontroller-introduction-component-characteristics-component/)
|
||||||
- [📝 How to Choose the Right Microcontroller for Your Application ](https://www.allaboutcircuits.com/technical-articles/how-to-choose-the-right-microcontroller-for-your-application/)
|
- [📝 How to Choose the Right Microcontroller for Your Application](https://www.allaboutcircuits.com/technical-articles/how-to-choose-the-right-microcontroller-for-your-application/)
|
||||||
- [📝 How to Read a Microcontroller Datasheet: Introduction and First Steps](https://www.allaboutcircuits.com/technical-articles/how-to-read-a-microcontroller-datasheet-introduction-and-first-steps2/)
|
- [📝 How to Read a Microcontroller Datasheet: Introduction and First Steps](https://www.allaboutcircuits.com/technical-articles/how-to-read-a-microcontroller-datasheet-introduction-and-first-steps2/)
|
||||||
- [📘👶 Make: AVR Programming - Elliot Williams](https://www.oreilly.com/library/view/make-avr-programming/9781449356484/)
|
- [📘👶 Make: AVR Programming - Elliot Williams](https://www.oreilly.com/library/view/make-avr-programming/9781449356484/)
|
||||||
- [🎞️👶💎 NewbieHack - Microcontroller Tutorial - A Beginners Guide (AVR)](https://www.youtube.com/playlist?list=PLE72E4CFE73BD1DE1)
|
- [🎞️👶💎 NewbieHack - Microcontroller Tutorial - A Beginners Guide (AVR)](https://www.youtube.com/playlist?list=PLE72E4CFE73BD1DE1)
|
||||||
- [🎞️👶 Getting Started with STM32 and Nucleo (YouTube Playlist)](https://youtube.com/playlist?list=PLEBQazB0HUyRYuzfi4clXsKUSgorErmBv)
|
- [🎞️👶 Getting Started with STM32 and Nucleo (YouTube Playlist)](https://youtube.com/playlist?list=PLEBQazB0HUyRYuzfi4clXsKUSgorErmBv)
|
||||||
- [📘💎 Mastering STM32 - Carmine Noviello](https://leanpub.com/mastering-stm32-2nd)
|
- [📘💎 Mastering STM32 - Carmine Noviello](https://leanpub.com/mastering-stm32-2nd)
|
||||||
|
- [📘💎 Developing IoT Projects with ESP32 - Vedat Ozan Oner](https://www.packtpub.com/product/developing-iot-projects-with-esp32-second-edition/9781803237688)
|
||||||
- [🎞️👶 Intro to Raspberry Pi Pico and RP2040 (YouTube Playlist)](https://www.youtube.com/playlist?list=PLEBQazB0HUyQO6rJxKr2umPCgmfAU-cqR)
|
- [🎞️👶 Intro to Raspberry Pi Pico and RP2040 (YouTube Playlist)](https://www.youtube.com/playlist?list=PLEBQazB0HUyQO6rJxKr2umPCgmfAU-cqR)
|
||||||
- [🔗 Getting started with STM32: STM32 step-by-step](https://wiki.st.com/stm32mcu/wiki/Category:Getting_started_with_STM32_:_STM32_step_by_step)
|
- [🔗 Getting started with STM32: STM32 step-by-step](https://wiki.st.com/stm32mcu/wiki/Category:Getting_started_with_STM32_:_STM32_step_by_step)
|
||||||
- [🎞️ Getting Started With AVR (YouTube Playlist)](https://www.youtube.com/playlist?list=PLtQdQmNK_0DRhBWYZ32BEILOykXLpJ8tP)
|
- [🎞️ Getting Started With AVR (YouTube Playlist)](https://www.youtube.com/playlist?list=PLtQdQmNK_0DRhBWYZ32BEILOykXLpJ8tP)
|
||||||
@@ -484,16 +567,24 @@ Some popular microcontroller families include AVR, PIC, STM32, MSP430, nRF, and
|
|||||||
|
|
||||||
#### 🔵 Timers / Counters
|
#### 🔵 Timers / Counters
|
||||||
|
|
||||||
|
- [📝 Introduction to Microcontroller Timers: Periodic Timers](https://www.allaboutcircuits.com/technical-articles/introduction-to-microcontroller-timers-periodic-timers/)
|
||||||
|
- [📝 Timers and Counters (AVR) - Nick Gammon](https://www.gammon.com.au/timers)
|
||||||
- [🎞️ STM32 TIMERS (YouTube Playlist)](https://www.youtube.com/playlist?list=PLfIJKC1ud8gjLZBzjE3kKBMDEH_lUc428)
|
- [🎞️ STM32 TIMERS (YouTube Playlist)](https://www.youtube.com/playlist?list=PLfIJKC1ud8gjLZBzjE3kKBMDEH_lUc428)
|
||||||
|
|
||||||
#### 🔵 PWM
|
#### 🔵 PWM (Pulse Width Modulation)
|
||||||
|
|
||||||
- [🎞️ What is PWM?](https://www.youtube.com/watch?v=B_Ysdv1xRbA)
|
- [🎞️ What is PWM?](https://www.youtube.com/watch?v=B_Ysdv1xRbA)
|
||||||
|
- [📝 Pulse-width Modulation (PWM) Timers in Microcontrollers](https://www.allaboutcircuits.com/technical-articles/introduction-to-microcontroller-timers-pwm-timers/)
|
||||||
- [🎞️ STM32 Guide #3: PWM + Timers](https://www.youtube.com/watch?v=AjN58ceQaF4)
|
- [🎞️ STM32 Guide #3: PWM + Timers](https://www.youtube.com/watch?v=AjN58ceQaF4)
|
||||||
|
|
||||||
|
#### 🔵 RTC (Real Time Clock)
|
||||||
|
|
||||||
|
- [📝 Real Time Clocks (RTCs) in Microcontroller Timers](https://www.allaboutcircuits.com/technical-articles/introduction-to-microcontroller-timers-real-time-clocks/)
|
||||||
|
|
||||||
#### 🔵 Watchdog
|
#### 🔵 Watchdog
|
||||||
|
|
||||||
- [📝 A Guide to Watchdog Timers for Embedded Systems](https://interrupt.memfault.com/blog/firmware-watchdog-best-practices)
|
- [📝 A Guide to Watchdog Timers for Embedded Systems](https://interrupt.memfault.com/blog/firmware-watchdog-best-practices)
|
||||||
|
- [📝 Watchdog Timers in Microcontrollers](https://www.allaboutcircuits.com/technical-articles/watchdog-timers-microcontroller-timers/)
|
||||||
- [🎞️ The Watchdog Timer on Arduino](https://www.youtube.com/watch?v=AzZBgH67mgE)
|
- [🎞️ The Watchdog Timer on Arduino](https://www.youtube.com/watch?v=AzZBgH67mgE)
|
||||||
- [🎞️ WATCHDOGS in STM32 || IWDG and WWDG || CubeIDE](https://www.youtube.com/watch?v=AelNsnpfbcM)
|
- [🎞️ WATCHDOGS in STM32 || IWDG and WWDG || CubeIDE](https://www.youtube.com/watch?v=AelNsnpfbcM)
|
||||||
|
|
||||||
@@ -542,9 +633,12 @@ _________________
|
|||||||
|
|
||||||
Embedded systems often communicate with other devices or external systems via interfaces, protocols. Interfaces provide the physical connections, protocols define data exchange rules. The choice depends on application-specific needs, including bandwidth, distance, security, and power consumption.
|
Embedded systems often communicate with other devices or external systems via interfaces, protocols. Interfaces provide the physical connections, protocols define data exchange rules. The choice depends on application-specific needs, including bandwidth, distance, security, and power consumption.
|
||||||
|
|
||||||
#### 🔵 Basic Protocols
|
#### 🔵 Basic Protocols
|
||||||
|
|
||||||
- [🎞️👶 Understanding Serial Protocols](https://www.youtube.com/watch?v=LEz5UCN3aHA)
|
- [🎞️👶 Understanding Serial Protocols](https://www.youtube.com/watch?v=LEz5UCN3aHA)
|
||||||
|
- [🎞️👶 Serial Protocol Fundamentals](https://www.youtube.com/watch?v=yz7h5xd18OE)
|
||||||
|
- [📝👶 Understanding and Selecting in 2024: I2C, SPI, UART Explained](https://www.parlezvoustech.com/en/comparaison-protocoles-communication-i2c-spi-uart/)
|
||||||
|
- [📝👶 Basics of Wired Embedded Protocols](https://piolabs.com/blog/engineering/wired-embedded-protocols-basics.html)
|
||||||
- [🎞️ PROTOCOLS: UART - I2C - SPI - Serial communications #001](https://www.youtube.com/watch?v=IyGwvGzrqp8)
|
- [🎞️ PROTOCOLS: UART - I2C - SPI - Serial communications #001](https://www.youtube.com/watch?v=IyGwvGzrqp8)
|
||||||
|
|
||||||
##### 🔶 UART
|
##### 🔶 UART
|
||||||
@@ -569,22 +663,21 @@ Embedded systems often communicate with other devices or external systems via in
|
|||||||
|
|
||||||
##### 🔶 SDIO
|
##### 🔶 SDIO
|
||||||
|
|
||||||
- [📝 SDIO Protocol](https://prodigytechno.com/sdio-protocol/)
|
- [📝 SDIO Protocol](https://prodigytechno.com/blog/device/sdio-protocol/)
|
||||||
- [📝 Interface SD CARD with SDIO in STM32](https://controllerstech.com/interface-sd-card-with-sdio-in-stm32/)
|
- [📝 Interface SD CARD with SDIO in STM32](https://controllerstech.com/interface-sd-card-with-stm32-via-sdio-4bit-mode/)
|
||||||
- [🔗 SDIO Card Slave Driver - ESP32 - Technical Documents](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/sdio_slave.html)
|
- [🔗 SDIO Card Slave Driver - ESP32 - Technical Documents](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/sdio_slave.html)
|
||||||
|
|
||||||
##### 🔶 I3C
|
##### 🔶 I3C
|
||||||
|
|
||||||
- [🎞️ What is I3C®?](https://www.youtube.com/watch?v=g3TBNHec5Ec)
|
- [🎞️ What is I3C®?](https://www.youtube.com/watch?v=g3TBNHec5Ec)
|
||||||
- [🔗 MIPI I3C & MIPI I3C Basic](https://www.mipi.org/specifications/i3c-sensor-specification)
|
- [🔗 MIPI I3C Sensor Specification](https://www.mipi.org/specifications/i3c-sensor-specification)
|
||||||
- [📝 I3C Protocol: Understanding and Debug](https://prodigytechno.com/mipi-i3c-protocol-debug/)
|
- [📝 I3C Protocol: Understanding and Debug](https://prodigytechno.com/blog/device/mipi-i3c-protocol-debug/)
|
||||||
- [🎞️ MIPI I3C Basic - The next generation sensor interface enabling low-power IoT applications](https://www.youtube.com/watch?v=xWKxZp_9RFQ)
|
- [🎞️ MIPI I3C Basic - The next generation sensor interface enabling low-power IoT applications](https://www.youtube.com/watch?v=xWKxZp_9RFQ)
|
||||||
|
|
||||||
##### 🔶 1-Wire
|
##### 🔶 1-Wire
|
||||||
|
|
||||||
- [🎞️ 1-Wire® Technology Overview](https://www.youtube.com/watch?v=CjH-OztKe00)
|
- [🎞️ 1-Wire® Technology Overview](https://www.youtube.com/watch?v=CjH-OztKe00)
|
||||||
|
|
||||||
|
|
||||||
#### 🔵 Digital Audio Protocols
|
#### 🔵 Digital Audio Protocols
|
||||||
|
|
||||||
##### 🔶 I2S
|
##### 🔶 I2S
|
||||||
@@ -598,16 +691,23 @@ Embedded systems often communicate with other devices or external systems via in
|
|||||||
- [🔗 HUAWEI LGA Module - PCM Audio Design Guide](https://www.paoli.cz/out/media/HUAWEI%20LGA%20Module%20PCM%20Audio%20Design%20Guide-V100R001_04(1).pdf)
|
- [🔗 HUAWEI LGA Module - PCM Audio Design Guide](https://www.paoli.cz/out/media/HUAWEI%20LGA%20Module%20PCM%20Audio%20Design%20Guide-V100R001_04(1).pdf)
|
||||||
- [📝 PCM Terminology and Concepts](https://larsimmisch.github.io/pyalsaaudio/terminology.html)
|
- [📝 PCM Terminology and Concepts](https://larsimmisch.github.io/pyalsaaudio/terminology.html)
|
||||||
|
|
||||||
#### 🔵 Image & Video Protocols
|
#### 🔵 Display & Camera Protocols
|
||||||
|
|
||||||
|
##### 🔶 SCCB
|
||||||
|
|
||||||
|
- [🔗 SCCB Specification (PDF)](https://people.ece.cornell.edu/land/courses/ece4760/FinalProjects/f2021/jfw225_aei23_dsb298/jfw225_aei23_dsb298/SCCBSpec_AN.pdf)
|
||||||
|
- [📝 OmniVision OV5640 Software Application Notes (PDF)](https://dlscorp.com/wp-content/uploads/2018/04/OmniVision-OV5640-Software-Application-Notes.pdf)
|
||||||
|
|
||||||
##### 🔶 MIPI CSI-2
|
##### 🔶 MIPI CSI-2
|
||||||
|
|
||||||
- [🔗 MIPI Camera Serial Interface 2](https://www.mipi.org/specifications/csi-2)
|
- [🔗 MIPI Camera Serial Interface 2](https://www.mipi.org/specifications/csi-2)
|
||||||
- [🎞️ Tutorial: MIPI CSI-2](https://www.youtube.com/watch?v=8REu_h7bzHM)
|
- [🎞️ Tutorial: MIPI CSI-2](https://www.youtube.com/watch?v=8REu_h7bzHM)
|
||||||
|
- [🔗 AN13573 - i.MX 8/RT MIPI DSI/CSI-2](https://www.nxp.com/docs/en/application-note/AN13573.pdf)
|
||||||
|
|
||||||
##### 🔶 MIPI DSI
|
##### 🔶 MIPI DSI
|
||||||
|
|
||||||
- [🔗 MIPI Display Serial Interface](https://www.mipi.org/specifications/dsi)
|
- [🔗 MIPI Display Serial Interface](https://www.mipi.org/specifications/dsi)
|
||||||
|
- [🔗 AN4860 - Introduction to DSI host on STM32 MCUs and MPUs](https://www.st.com/resource/en/application_note/an4860-introduction-to-dsi-host-on-stm32-mcus-and-mpus-stmicroelectronics.pdf)
|
||||||
|
|
||||||
##### 🔶 HDMI
|
##### 🔶 HDMI
|
||||||
|
|
||||||
@@ -624,7 +724,7 @@ Embedded systems often communicate with other devices or external systems via in
|
|||||||
- [📝 A Practical Guide to BLE Throughput](https://interrupt.memfault.com/blog/ble-throughput-primer)
|
- [📝 A Practical Guide to BLE Throughput](https://interrupt.memfault.com/blog/ble-throughput-primer)
|
||||||
- [🎞️ SparkFun According to Pete #49 - How Bluetooth Works](https://www.youtube.com/watch?v=zJqGLWQGyvk)
|
- [🎞️ SparkFun According to Pete #49 - How Bluetooth Works](https://www.youtube.com/watch?v=zJqGLWQGyvk)
|
||||||
- [🔗 Bluetooth Low Energy Fundamentals - Nordic Semiconductor](https://academy.nordicsemi.com/courses/bluetooth-low-energy-fundamentals/)
|
- [🔗 Bluetooth Low Energy Fundamentals - Nordic Semiconductor](https://academy.nordicsemi.com/courses/bluetooth-low-energy-fundamentals/)
|
||||||
- [🔗 Bluetooth Overview - ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/bluetooth.html)
|
- [🔗 Bluetooth API - ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/bluetooth/index.html)
|
||||||
|
|
||||||
##### 🔶 Wi-Fi
|
##### 🔶 Wi-Fi
|
||||||
|
|
||||||
@@ -638,7 +738,7 @@ Embedded systems often communicate with other devices or external systems via in
|
|||||||
|
|
||||||
- [📝 The Arduino Guide to LoRa® and LoRaWAN®](https://docs.arduino.cc/learn/communication/lorawan-101)
|
- [📝 The Arduino Guide to LoRa® and LoRaWAN®](https://docs.arduino.cc/learn/communication/lorawan-101)
|
||||||
- [🔗💎 The Things Fundamentals on LoRaWAN!](https://www.thethingsnetwork.org/docs/lorawan/)
|
- [🔗💎 The Things Fundamentals on LoRaWAN!](https://www.thethingsnetwork.org/docs/lorawan/)
|
||||||
- [📝 What are LoRa® and LoRaWAN®?](https://lora-developers.semtech.com/documentation/tech-papers-and-guides/lora-and-lorawan/)
|
- [📝 What are LoRa® and LoRaWAN®?](https://www.semtech.com/lora/what-is-lora)
|
||||||
- [🎞️ #112 LoRa / LoRaWAN De-Mystified / Tutorial](https://www.youtube.com/watch?v=hMOwbNUpDQA)
|
- [🎞️ #112 LoRa / LoRaWAN De-Mystified / Tutorial](https://www.youtube.com/watch?v=hMOwbNUpDQA)
|
||||||
- [🔗 ESP32 with LoRa using Arduino IDE – Getting Started](https://randomnerdtutorials.com/esp32-lora-rfm95-transceiver-arduino-ide/)
|
- [🔗 ESP32 with LoRa using Arduino IDE – Getting Started](https://randomnerdtutorials.com/esp32-lora-rfm95-transceiver-arduino-ide/)
|
||||||
|
|
||||||
@@ -679,7 +779,6 @@ Embedded systems often communicate with other devices or external systems via in
|
|||||||
- [🎞️ #144 Internet Protocols: CoAP vs MQTT, Network Sniffing, and preparation for IKEA Tradfri Hacking](https://www.youtube.com/watch?v=pfG8uEDZj5g)
|
- [🎞️ #144 Internet Protocols: CoAP vs MQTT, Network Sniffing, and preparation for IKEA Tradfri Hacking](https://www.youtube.com/watch?v=pfG8uEDZj5g)
|
||||||
- [🎞️ MQTT vs. CoAP | Comparison of IoT Protocols](https://www.youtube.com/watch?v=0CORpVSUQe0)
|
- [🎞️ MQTT vs. CoAP | Comparison of IoT Protocols](https://www.youtube.com/watch?v=0CORpVSUQe0)
|
||||||
- [🎞️ Simple ESP32 IoT Sensor Node Tutorial: WiFi Enabled MQTT Sensor Data Node](https://www.youtube.com/watch?v=x5A5S0hoyJ0)
|
- [🎞️ Simple ESP32 IoT Sensor Node Tutorial: WiFi Enabled MQTT Sensor Data Node](https://www.youtube.com/watch?v=x5A5S0hoyJ0)
|
||||||
- [🔗 Cellular IoT Fundamentals - Nordic Semiconductor](https://academy.nordicsemi.com/courses/cellular-iot-fundamentals/)
|
|
||||||
|
|
||||||
#### 🔵 High-Speed Protocols
|
#### 🔵 High-Speed Protocols
|
||||||
|
|
||||||
@@ -688,7 +787,7 @@ Embedded systems often communicate with other devices or external systems via in
|
|||||||
- [🎞️👶 Microchip University - Ethernet Fundamentals](https://mu.microchip.com/ethernet-fundamentals)
|
- [🎞️👶 Microchip University - Ethernet Fundamentals](https://mu.microchip.com/ethernet-fundamentals)
|
||||||
- [📝 How the Ethernet Protocol Works – A Complete Guide](https://www.freecodecamp.org/news/the-complete-guide-to-the-ethernet-protocol/)
|
- [📝 How the Ethernet Protocol Works – A Complete Guide](https://www.freecodecamp.org/news/the-complete-guide-to-the-ethernet-protocol/)
|
||||||
- [🎞️ What is an Ethernet PHY?](https://www.youtube.com/watch?v=JH3cMYErmKI)
|
- [🎞️ What is an Ethernet PHY?](https://www.youtube.com/watch?v=JH3cMYErmKI)
|
||||||
- [🎞️ The Data Link Layer, MAC Addressing, and the Ethernet Frame](https://youtu.be/_b4dXKB8Pt8)
|
- [🎞️ The Data Link Layer, MAC Addressing, and the Ethernet Frame](https://www.youtube.com/watch?v=_b4dXKB8Pt8)
|
||||||
- [🎞️ Microchip University - Serializer/Deserializer (SerDes) Basics for Your Next Microchip Ethernet PHY Design](https://mu.microchip.com/serializerdeserializer-serdes-basics-for-your-next-microchip-ethernet-phy-design)
|
- [🎞️ Microchip University - Serializer/Deserializer (SerDes) Basics for Your Next Microchip Ethernet PHY Design](https://mu.microchip.com/serializerdeserializer-serdes-basics-for-your-next-microchip-ethernet-phy-design)
|
||||||
- [🎞️ Microchip University - Ethernet Switch Fundamentals](https://mu.microchip.com/ethernet-switch-fundamentals)
|
- [🎞️ Microchip University - Ethernet Switch Fundamentals](https://mu.microchip.com/ethernet-switch-fundamentals)
|
||||||
|
|
||||||
@@ -697,11 +796,13 @@ Embedded systems often communicate with other devices or external systems via in
|
|||||||
- [🎞️👶 Microchip University - Introduction to USB 2.0](https://mu.microchip.com/introduction-to-usb-20)
|
- [🎞️👶 Microchip University - Introduction to USB 2.0](https://mu.microchip.com/introduction-to-usb-20)
|
||||||
- [🎞️👶 Microchip University - USB 3 Fundamentals](https://mu.microchip.com/usb-3-fundamentals)
|
- [🎞️👶 Microchip University - USB 3 Fundamentals](https://mu.microchip.com/usb-3-fundamentals)
|
||||||
- [🎞️ Training - USB 101 - Introduction to USB](https://www.youtube.com/watch?v=5S6ZPmtPzRA)
|
- [🎞️ Training - USB 101 - Introduction to USB](https://www.youtube.com/watch?v=5S6ZPmtPzRA)
|
||||||
- [🔗 USB 101: An Introduction to Universal Serial Bus 2.0](https://www.infineon.com/dgdl/Infineon-AN57294_USB_101_An_Introduction_to_Universal_Serial_Bus_2.0-ApplicationNotes-v09_00-EN.pdf?fileId=8ac78c8c7cdc391c017d072d8e8e5256)
|
- [🔗 USB 101: An Introduction to Universal Serial Bus 2.0](https://www.infineon.com/assets/row/public/documents/cross-divisions/42/infineon-an57294-usb-101-an-introduction-to-universal-serial-bus-2.0-applicationnotes-en.pdf)
|
||||||
- [🎞️ How does a USB keyboard work?](https://youtu.be/wdgULBpRoXk)
|
- [🎞️ How does a USB keyboard work?](https://youtu.be/wdgULBpRoXk)
|
||||||
- [🎞️ How does USB device discovery work?](https://www.youtube.com/watch?v=N0O5Uwc3C0o)
|
- [🎞️ How does USB device discovery work?](https://www.youtube.com/watch?v=N0O5Uwc3C0o)
|
||||||
- [🎞️ MOOC - STM32 USB training (YouTube Playlist)](https://www.youtube.com/playlist?list=PLnMKNibPkDnFFRBVD206EfnnHhQZI4Hxa)
|
- [🎞️ MOOC - STM32 USB training (YouTube Playlist)](https://www.youtube.com/playlist?list=PLnMKNibPkDnFFRBVD206EfnnHhQZI4Hxa)
|
||||||
- [🎞️ Microchip University - USB2 Hub Fundamentals](https://mu.microchip.com/usb2-hub-fundamentals)
|
- [🎞️ Microchip University - USB2 Hub Fundamentals](https://mu.microchip.com/usb2-hub-fundamentals)
|
||||||
|
- [🔗 Beyond Logic - USB in a NutShell](https://www.beyondlogic.org/usbnutshell/usb1.shtml)
|
||||||
|
- [📝 USB for Software Developers: An introduction to writing userspace USB drivers](https://werwolv.net/posts/usb_for_sw_devs/)
|
||||||
|
|
||||||
##### 🔶 PCIe
|
##### 🔶 PCIe
|
||||||
|
|
||||||
@@ -714,6 +815,7 @@ Embedded systems often communicate with other devices or external systems via in
|
|||||||
##### 🔶 CAN
|
##### 🔶 CAN
|
||||||
|
|
||||||
- [🎞️👶 Microchip University - CAN and CAN FD Protocol and Physical Layer Basics](https://mu.microchip.com/understanding-the-can-fd-protocol)
|
- [🎞️👶 Microchip University - CAN and CAN FD Protocol and Physical Layer Basics](https://mu.microchip.com/understanding-the-can-fd-protocol)
|
||||||
|
- [📝👶 CAN bus in 2024: Operation, Advantages and Recent Developments](https://www.parlezvoustech.com/en/bus-can-2024-technologie-avantages-evolutions/)
|
||||||
- [🎞️👶 CAN Bus: Serial Communication - How It Works?](https://www.youtube.com/watch?v=JZSCzRT9TTo&t=21s)
|
- [🎞️👶 CAN Bus: Serial Communication - How It Works?](https://www.youtube.com/watch?v=JZSCzRT9TTo&t=21s)
|
||||||
- [🎞️👶 CAN Bus: A Beginners Guide Part 1](https://www.youtube.com/watch?v=YBrU_eZM110)
|
- [🎞️👶 CAN Bus: A Beginners Guide Part 1](https://www.youtube.com/watch?v=YBrU_eZM110)
|
||||||
- [🎞️👶 CAN Bus: A Beginners Guide Part 2](https://www.youtube.com/watch?v=z5CVljiLhvc)
|
- [🎞️👶 CAN Bus: A Beginners Guide Part 2](https://www.youtube.com/watch?v=z5CVljiLhvc)
|
||||||
@@ -730,12 +832,13 @@ Embedded systems often communicate with other devices or external systems via in
|
|||||||
- [🎞️ TCP - 12 simple ideas to explain the Transmission Control Protocol](https://www.youtube.com/watch?v=JFch3ctY6nE)
|
- [🎞️ TCP - 12 simple ideas to explain the Transmission Control Protocol](https://www.youtube.com/watch?v=JFch3ctY6nE)
|
||||||
- [🎞️ UDP doesn't suck! It's the BEST L4 protocol for THESE types of applications...](https://www.youtube.com/watch?v=LaDRWycC7Iw)
|
- [🎞️ UDP doesn't suck! It's the BEST L4 protocol for THESE types of applications...](https://www.youtube.com/watch?v=LaDRWycC7Iw)
|
||||||
- [🎞️ Networking tutorial - Ben Eater (YouTube Playlist)](https://www.youtube.com/playlist?list=PLowKtXNTBypH19whXTVoG3oKSuOcw_XeW)
|
- [🎞️ Networking tutorial - Ben Eater (YouTube Playlist)](https://www.youtube.com/playlist?list=PLowKtXNTBypH19whXTVoG3oKSuOcw_XeW)
|
||||||
- [📘💎 Hands-On Network Programming with C - Lewis Van Winkle](https://www.amazon.com/exec/obidos/ASIN/1789349869/aoeu-20)
|
- [📘💎 Hands-On Network Programming with C - Lewis Van Winkle](https://www.amazon.com/dp/1789349869)
|
||||||
- [📘💎 Network Algorithmics - George Varghese, Jun Xu](https://www.oreilly.com/library/view/network-algorithmics-2nd/9780128099865/)
|
- [📘💎 Network Algorithmics - George Varghese, Jun Xu](https://www.oreilly.com/library/view/network-algorithmics-2nd/9780128099865/)
|
||||||
- [🔗 tcpdump & libpcap libraries](https://www.tcpdump.org/)
|
- [🔗 tcpdump & libpcap libraries](https://www.tcpdump.org/)
|
||||||
- [🔗 lwIP - A Lightweight TCP/IP stack](https://savannah.nongnu.org/projects/lwip/)
|
- [🔗 lwIP - A Lightweight TCP/IP stack](https://savannah.nongnu.org/projects/lwip/)
|
||||||
- [🔗 lwIP (ESP-IDF)](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/lwip.html)
|
- [🔗 lwIP (ESP-IDF)](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/lwip.html)
|
||||||
- [🔗 Developing applications on STM32Cube with LwIP TCP/IP stack](https://www.st.com/resource/en/user_manual/um1713-developing-applications-on-stm32cube-with-lwip-tcpip-stack-stmicroelectronics.pdf)
|
- [🔗 Developing applications on STM32Cube with LwIP TCP/IP stack](https://www.st.com/resource/en/user_manual/um1713-developing-applications-on-stm32cube-with-lwip-tcpip-stack-stmicroelectronics.pdf)
|
||||||
|
- [📝 Understanding Traceroute](https://tech.stonecharioteer.com/posts/2026/traceroute/)
|
||||||
|
|
||||||
#### 🔵 Cellular Communication
|
#### 🔵 Cellular Communication
|
||||||
|
|
||||||
@@ -753,7 +856,7 @@ _________________
|
|||||||
|
|
||||||
Embedded systems employ a combination of volatile (SRAM, DRAM, PSRAM) and non-volatile (flash, EEPROM, FRAM) memory to store and access data, based on factors like performance, cost, power consumption, and durability.
|
Embedded systems employ a combination of volatile (SRAM, DRAM, PSRAM) and non-volatile (flash, EEPROM, FRAM) memory to store and access data, based on factors like performance, cost, power consumption, and durability.
|
||||||
|
|
||||||
- [📝 Memory Options for Embedded Systems: How to Select the Right Memory Configuration](https://www.qt.io/embedded-development-talk/memory-options-for-embedded-systems-how-to-select-the-right-memory-configuration)
|
- [📝 Memory Options for Embedded Systems: How to Select the Right Memory Configuration](https://www.qt.io/software-insights/how-to-select-a-memory-configuration-for-embedded-systems)
|
||||||
|
|
||||||
#### 🔵 Flash Memory
|
#### 🔵 Flash Memory
|
||||||
|
|
||||||
@@ -794,9 +897,9 @@ A file system is a way of organizing data on a storage device, such as a hard dr
|
|||||||
- [📝 UBI File System](https://docs.kernel.org/filesystems/ubifs.html)
|
- [📝 UBI File System](https://docs.kernel.org/filesystems/ubifs.html)
|
||||||
- [📝 JFFS : The Journalling Flash File System](https://sourceware.org/jffs2/jffs2-html/)
|
- [📝 JFFS : The Journalling Flash File System](https://sourceware.org/jffs2/jffs2-html/)
|
||||||
- [📝 Preventing Filesystem Corruption In Embedded Linux](https://www.embeddedts.com/assets/preventing-filesystem-corruption-in-embedded-linux)
|
- [📝 Preventing Filesystem Corruption In Embedded Linux](https://www.embeddedts.com/assets/preventing-filesystem-corruption-in-embedded-linux)
|
||||||
- [🔗 LittleFS - A high-integrity embedded file system](https://os.mbed.com/blog/entry/littlefs-high-integrity-embedded-fs/)
|
- [🔗 LittleFS - A high-integrity embedded file system](https://github.com/littlefs-project/littlefs)
|
||||||
- [🔗 SPIFS - Wear-leveled SPI flash file system for embedded devices](https://github.com/pellepl/spiffs)
|
- [🔗 SPIFFS - Wear-leveled SPI flash file system for embedded devices](https://github.com/pellepl/spiffs)
|
||||||
- [🔗 EEPROM File System (EEFS)](opensource.gsfc.nasa.gov/projects/eefs/index.php)
|
- [🔗 EEPROM File System (EEFS)](https://github.com/nasa/EEFS)
|
||||||
|
|
||||||
_________________
|
_________________
|
||||||
|
|
||||||
@@ -806,6 +909,12 @@ Embedded system development often involves simulating or emulating hardware envi
|
|||||||
|
|
||||||
- [🔗👶 Wokwi - Online ESP32, STM32, Arduino Simulator](https://wokwi.com/)
|
- [🔗👶 Wokwi - Online ESP32, STM32, Arduino Simulator](https://wokwi.com/)
|
||||||
- [🔗👶 SimulIDE Circuit Simulator](https://simulide.com/)
|
- [🔗👶 SimulIDE Circuit Simulator](https://simulide.com/)
|
||||||
|
- [🔗👶 Digital logic designer and circuit simulator designed for educational purposes](https://github.com/hneemann/Digital)
|
||||||
|
- [🔗👶 EveryCircuit](https://everycircuit.com/)
|
||||||
|
- [🔗👶 Circuit Simulator Applet](https://www.falstad.com/circuit/)
|
||||||
|
- [🔗👶 Tinkercad](https://www.tinkercad.com/)
|
||||||
|
- [🔗👶 Voltsim](https://www.voltsimulator.com/)
|
||||||
|
- [🔗 Velxio - Arduino & Embedded Board Emulator](https://github.com/davidmonterocrespo24/velxio)
|
||||||
|
|
||||||
#### 🔵 QEMU
|
#### 🔵 QEMU
|
||||||
|
|
||||||
@@ -813,12 +922,13 @@ Embedded system development often involves simulating or emulating hardware envi
|
|||||||
- [📝 Running AVR code in QEMU - A quick-start guide to accelerate AVR firmware development](https://yeah.nah.nz/embedded/qemu-avr/)
|
- [📝 Running AVR code in QEMU - A quick-start guide to accelerate AVR firmware development](https://yeah.nah.nz/embedded/qemu-avr/)
|
||||||
- [📝 QEMU Simulation - Blinky - STM32F767ZI Full Stack](https://longer-vision-robot.gitbook.io/stm32f767zi-full-stack/chapter-2.-programming-for-stm32/2.4-qemu-simulation-blinky)
|
- [📝 QEMU Simulation - Blinky - STM32F767ZI Full Stack](https://longer-vision-robot.gitbook.io/stm32f767zi-full-stack/chapter-2.-programming-for-stm32/2.4-qemu-simulation-blinky)
|
||||||
- [📝 Emulating a Raspberry Pi in QEMU](https://interrupt.memfault.com/blog/emulating-raspberry-pi-in-qemu)
|
- [📝 Emulating a Raspberry Pi in QEMU](https://interrupt.memfault.com/blog/emulating-raspberry-pi-in-qemu)
|
||||||
|
- [📝👶 Building an ARM Embedded Linux System in QEMU](https://github.com/dchithinh/mastering-embeded-linux-programming)
|
||||||
|
|
||||||
#### 🔵 Renode
|
#### 🔵 Renode
|
||||||
|
|
||||||
- [🔗 Renode - GitHub Repo](https://github.com/renode/renode)
|
- [🔗 Renode - GitHub Repo](https://github.com/renode/renode)
|
||||||
- [🔗 Renode - Documentation](https://renode.readthedocs.io/en/latest/)
|
- [🔗 Renode - Documentation](https://renode.readthedocs.io/en/latest/)
|
||||||
- [🔗 Renode - Official Tutorials](https://renode.io/tutorials/)
|
- [🔗 Renode - Running your first demo](https://renode.readthedocs.io/en/latest/introduction/demo.html)
|
||||||
- [📝 Cortex-M MCU Emulation with Renode](https://interrupt.memfault.com/blog/intro-to-renode)
|
- [📝 Cortex-M MCU Emulation with Renode](https://interrupt.memfault.com/blog/intro-to-renode)
|
||||||
- [📝 A simple guide to get started on renode](https://github.com/tarciszera/renode_guide)
|
- [📝 A simple guide to get started on renode](https://github.com/tarciszera/renode_guide)
|
||||||
- [🎞️ Using CI-based workflow with Renode in bringing TensorFlow Lite to Zephyr](https://www.youtube.com/watch?v=jF94cXPoZZg)
|
- [🎞️ Using CI-based workflow with Renode in bringing TensorFlow Lite to Zephyr](https://www.youtube.com/watch?v=jF94cXPoZZg)
|
||||||
@@ -867,8 +977,9 @@ Digital signal processing (DSP) is a branch of engineering that deals with the p
|
|||||||
- [🎞️ Udemy – Digital Signal Processing (DSP) From Ground Up in C](https://www.udemy.com/course/digital-signal-processing-dsp-from-ground-uptm-in-c/)
|
- [🎞️ Udemy – Digital Signal Processing (DSP) From Ground Up in C](https://www.udemy.com/course/digital-signal-processing-dsp-from-ground-uptm-in-c/)
|
||||||
- [📘 Real-Time Digital Signal Processing: Fundamentals, Implementations and Applications - Sen M. Kuo, Bob H. Lee, Wenshun Tian](https://www.wiley.com/en-us/Real+Time+Digital+Signal+Processing%3A+Fundamentals%2C+Implementations+and+Applications%2C+3rd+Edition-p-9781118414323)
|
- [📘 Real-Time Digital Signal Processing: Fundamentals, Implementations and Applications - Sen M. Kuo, Bob H. Lee, Wenshun Tian](https://www.wiley.com/en-us/Real+Time+Digital+Signal+Processing%3A+Fundamentals%2C+Implementations+and+Applications%2C+3rd+Edition-p-9781118414323)
|
||||||
- [📘 Real-Time Digital Signal Processing from MATLAB to C with the TMS320C6x DSPs - Thad B. Welch, Cameron H.G. Wright, Michael G. Morrow](https://www.routledge.com/Real-Time-Digital-Signal-Processing-from-MATLAB-to-C-with-the-TMS320C6x/Welch-Wright-Morrow/p/book/9780367736453)
|
- [📘 Real-Time Digital Signal Processing from MATLAB to C with the TMS320C6x DSPs - Thad B. Welch, Cameron H.G. Wright, Michael G. Morrow](https://www.routledge.com/Real-Time-Digital-Signal-Processing-from-MATLAB-to-C-with-the-TMS320C6x/Welch-Wright-Morrow/p/book/9780367736453)
|
||||||
- [📘 Schaum's Outline of Signals and Systems - Hwei P. Hsu](https://www.mhprofessional.com/schaum-s-outline-of-signals-and-systems-fourth-edition-9781260454246-usa)
|
- [📘 Schaum's Outline of Signals and Systems - Hwei P. Hsu](https://www.amazon.com/dp/126045424X)
|
||||||
- [📘💎 Digital Signal Processing - John G. Proakis, Dimitris K. Manolakis](https://a.co/d/75P5PiE)
|
- [📘💎 Digital Signal Processing - John G. Proakis, Dimitris K. Manolakis](https://a.co/d/75P5PiE)
|
||||||
|
- [📘 Introduction to Computer Music](https://composerprogrammer.com/introductiontocomputermusic.pdf)
|
||||||
|
|
||||||
#### 🔵 Discrete Fourier Transform / FFT
|
#### 🔵 Discrete Fourier Transform / FFT
|
||||||
|
|
||||||
@@ -877,16 +988,21 @@ Digital signal processing (DSP) is a branch of engineering that deals with the p
|
|||||||
- [🎞️ The FFT Algorithm - Simple Step by Step](https://www.youtube.com/watch?v=htCj9exbGo0)
|
- [🎞️ The FFT Algorithm - Simple Step by Step](https://www.youtube.com/watch?v=htCj9exbGo0)
|
||||||
- [🎞️ STM32 Fast Fourier Transform (CMSIS DSP FFT) - Phil's Lab #111](https://youtu.be/d1KvgOwWvkM)
|
- [🎞️ STM32 Fast Fourier Transform (CMSIS DSP FFT) - Phil's Lab #111](https://youtu.be/d1KvgOwWvkM)
|
||||||
|
|
||||||
|
_________________
|
||||||
|
|
||||||
### ✳️ Control Theory
|
### ✳️ Control Theory
|
||||||
|
|
||||||
- [🎞️ Understanding Control Systems (YouTube Playlist)](https://youtube.com/playlist?list=PLn8PRpmsu08q8CE0pbZ-cSrMm_WYJfVGd)
|
- [🎞️ Understanding Control Systems (YouTube Playlist)](https://youtube.com/playlist?list=PLn8PRpmsu08q8CE0pbZ-cSrMm_WYJfVGd)
|
||||||
- [🎞️ Control Systems - CircuitBread (YouTube Playlist)](https://www.youtube.com/playlist?list=PLfYdTiQCV_p4YZNJWExM-5_g0fk9bHDL8)
|
- [🎞️ Control Systems - CircuitBread (YouTube Playlist)](https://www.youtube.com/playlist?list=PLfYdTiQCV_p4YZNJWExM-5_g0fk9bHDL8)
|
||||||
|
- [🎞️💎 Brian Douglas' Control Systems Lectures (YouTube Channel)](https://www.youtube.com/@BrianBDouglas)
|
||||||
- [📘💎 Control Systems Engineering - Norman S. Nise](https://www.wiley.com/en-us/Control+Systems+Engineering%2C+8th+Edition-p-9781119474227)
|
- [📘💎 Control Systems Engineering - Norman S. Nise](https://www.wiley.com/en-us/Control+Systems+Engineering%2C+8th+Edition-p-9781119474227)
|
||||||
- [📘💎 Modern Control Systems - Richard C. Dorf, Robert H. Bishop](https://www.pearson.com/en-us/subject-catalog/p/modern-control-systems/P200000003484/9780137307098)
|
- [📘💎 Modern Control Systems - Richard C. Dorf, Robert H. Bishop](https://www.pearson.com/en-us/subject-catalog/p/modern-control-systems/P200000003484/9780137307098)
|
||||||
|
- [🔗 Kalman Filter](https://kalmanfilter.net/)
|
||||||
|
|
||||||
#### 🔵 PID Controller
|
#### 🔵 PID Controller
|
||||||
|
|
||||||
- [🎞️👶 What is a PID Controller? | DigiKey](https://www.youtube.com/watch?v=tFVAaUcOm4I)
|
- [🎞️👶 What is a PID Controller? | DigiKey](https://www.youtube.com/watch?v=tFVAaUcOm4I)
|
||||||
|
- [🎞️👶 How to Tune a PID Controller for an Inverted Pendulum | DigiKey](https://www.youtube.com/watch?v=hRnofMxEf3Q)
|
||||||
- [🎞️ PID Controller Explained](https://www.youtube.com/watch?v=fv6dLTEvl74)
|
- [🎞️ PID Controller Explained](https://www.youtube.com/watch?v=fv6dLTEvl74)
|
||||||
- [🎞️ Understanding PID Control (YouTube Playlist)](https://youtube.com/playlist?list=PLn8PRpmsu08pQBgjxYFXSsODEF3Jqmm-y)
|
- [🎞️ Understanding PID Control (YouTube Playlist)](https://youtube.com/playlist?list=PLn8PRpmsu08pQBgjxYFXSsODEF3Jqmm-y)
|
||||||
- [🎞️ PID Controller Implementation in Software - Phil's Lab #6](https://www.youtube.com/watch?v=zOByx3Izf5U)
|
- [🎞️ PID Controller Implementation in Software - Phil's Lab #6](https://www.youtube.com/watch?v=zOByx3Izf5U)
|
||||||
@@ -906,11 +1022,15 @@ Embedded systems can be programmed with either an operating system (OS) or direc
|
|||||||
#### 🔵 Operating System Fundamentals
|
#### 🔵 Operating System Fundamentals
|
||||||
|
|
||||||
- [📝👶 Putting the “You” in CPU](https://cpu.land/)
|
- [📝👶 Putting the “You” in CPU](https://cpu.land/)
|
||||||
|
- [📝👶 Operating System in 1,000 Lines](https://operating-system-in-1000-lines.vercel.app/en/)
|
||||||
- [📘👶 The little book about OS development](https://littleosbook.github.io/)
|
- [📘👶 The little book about OS development](https://littleosbook.github.io/)
|
||||||
- [📘👶 Operating Systems: From 0 to 1](https://github.com/tuhdo/os01/blob/master/Operating_Systems_From_0_to_1.pdf)
|
- [📘👶 Operating Systems: From 0 to 1](https://github.com/tuhdo/os01/blob/master/Operating_Systems_From_0_to_1.pdf)
|
||||||
|
- [🔗 An operating system](https://github.com/roscopeco/anos)
|
||||||
|
- [📝 Porting Mac OS X to the Nintendo Wii](https://bryankeller.github.io/2026/04/08/porting-mac-os-x-nintendo-wii.html)
|
||||||
- [📘💎 Operating Systems: Three Easy Pieces - Remzi H Arpaci-Dusseau, Andrea C Arpaci-Dusseau](https://pages.cs.wisc.edu/~remzi/OSTEP/)
|
- [📘💎 Operating Systems: Three Easy Pieces - Remzi H Arpaci-Dusseau, Andrea C Arpaci-Dusseau](https://pages.cs.wisc.edu/~remzi/OSTEP/)
|
||||||
- [📘💎 Modern Operating Systems - Andrew S. Tanenbaum, Herbert Bos](https://www.pearson.com/en-us/subject-catalog/p/modern-operating-systems/P200000003295)
|
- [📘💎 Modern Operating Systems - Andrew S. Tanenbaum, Herbert Bos](https://www.pearson.com/en-us/subject-catalog/p/modern-operating-systems/P200000003295)
|
||||||
- [📝 Writing an OS in Rust - Philipp Oppermann's blog](https://os.phil-opp.com/)
|
- [📝 Writing an OS in Rust - Philipp Oppermann's blog](https://os.phil-opp.com/)
|
||||||
|
- [🔗 Operating System development tutorials in Rust on the Raspberry Pi](https://github.com/rust-embedded/rust-raspberrypi-OS-tutorials)
|
||||||
|
|
||||||
#### 🔵 Real-Time Operating Systems
|
#### 🔵 Real-Time Operating Systems
|
||||||
|
|
||||||
@@ -922,8 +1042,9 @@ Real-time operating systems (RTOS) are specialized operating systems designed to
|
|||||||
- [📝 RTOS vs. Bare Metal: Navigating Performance, Complexity, and Efficiency](https://weston-embedded.com/support/media-articles/119-rtos-vs-bare-metal-navigating-performance-complexity-and-efficiency)
|
- [📝 RTOS vs. Bare Metal: Navigating Performance, Complexity, and Efficiency](https://weston-embedded.com/support/media-articles/119-rtos-vs-bare-metal-navigating-performance-complexity-and-efficiency)
|
||||||
- [📝 The Pros and Cons of RTOS vs Bare Metal: Which Will You Choose?](https://medium.com/@lanceharvieruntime/the-pros-and-cons-of-rtos-vs-bare-metal-which-will-you-choose-756e33ba6df7)
|
- [📝 The Pros and Cons of RTOS vs Bare Metal: Which Will You Choose?](https://medium.com/@lanceharvieruntime/the-pros-and-cons-of-rtos-vs-bare-metal-which-will-you-choose-756e33ba6df7)
|
||||||
- [📝 FreeRTOS vs Linux for Embedded Systems](https://www.bytesnap.com/news-blog/freertos-vs-linux-embedded-systems/)
|
- [📝 FreeRTOS vs Linux for Embedded Systems](https://www.bytesnap.com/news-blog/freertos-vs-linux-embedded-systems/)
|
||||||
|
- [🔗 tinyOS RTOS](https://github.com/cmc-labo/tinyos-rtos)
|
||||||
- [🔗 Real-Time Systems Concepts](https://micrium.atlassian.net/wiki/spaces/osiidoc/pages/163855/Real-Time+Systems+Concepts)
|
- [🔗 Real-Time Systems Concepts](https://micrium.atlassian.net/wiki/spaces/osiidoc/pages/163855/Real-Time+Systems+Concepts)
|
||||||
- [🔗 RTOS Fundamentals](https://www.freertos.org/implementation/a00002.html)
|
- [🔗 RTOS Fundamentals](https://www.freertos.org/Documentation/01-FreeRTOS-quick-start/01-Beginners-guide/01-RTOS-fundamentals)
|
||||||
- [📝 A Simple Scheduler via an Interrupt-driven Actor Model](https://interrupt.memfault.com/blog/hardware-actor-scheduler)
|
- [📝 A Simple Scheduler via an Interrupt-driven Actor Model](https://interrupt.memfault.com/blog/hardware-actor-scheduler)
|
||||||
- [📝 ARM Cortex-M RTOS Context Switching](https://interrupt.memfault.com/blog/cortex-m-rtos-context-switching)
|
- [📝 ARM Cortex-M RTOS Context Switching](https://interrupt.memfault.com/blog/cortex-m-rtos-context-switching)
|
||||||
- [🎞️ RTOS (YouTube Playlist)](https://www.youtube.com/playlist?list=PLPW8O6W-1chyrd_Msnn4LD6LBs2slJITs)
|
- [🎞️ RTOS (YouTube Playlist)](https://www.youtube.com/playlist?list=PLPW8O6W-1chyrd_Msnn4LD6LBs2slJITs)
|
||||||
@@ -933,20 +1054,22 @@ Real-time operating systems (RTOS) are specialized operating systems designed to
|
|||||||
|
|
||||||
- [🔗 FreeRTOS - Market leading RTOS](https://www.freertos.org)
|
- [🔗 FreeRTOS - Market leading RTOS](https://www.freertos.org)
|
||||||
- [🎞️👶 Introduction to RTOS (YouTube Playlist)](https://www.youtube.com/playlist?list=PLEBQazB0HUyQ4hAPU1cJED6t3DU0h34bz)
|
- [🎞️👶 Introduction to RTOS (YouTube Playlist)](https://www.youtube.com/playlist?list=PLEBQazB0HUyQ4hAPU1cJED6t3DU0h34bz)
|
||||||
- [📘💎 Mastering the FreeRTOS Real Time Kernel - a Hands On Tutorial Guide](https://www.freertos.org/Documentation/RTOS_book.html)
|
- [📘💎 Mastering the FreeRTOS Real Time Kernel - a Hands On Tutorial Guide](https://www.freertos.org/Documentation/02-Kernel/07-Books-and-manual/01-RTOS_book)
|
||||||
- [🎞️👶 Microchip University - FreeRTOS Simplified: A Beginner's Guide to Develop and Debug FreeRTOS Applications](https://mu.microchip.com/freertos-simplified)
|
- [🎞️👶 Microchip University - FreeRTOS Simplified: A Beginner's Guide to Develop and Debug FreeRTOS Applications](https://mu.microchip.com/freertos-simplified)
|
||||||
- [🎞️ Getting Started With STM32 and Nucleo Part 3: FreeRTOS - How To Run Multiple Threads w/ CMSIS-RTOS](https://www.youtube.com/watch?v=CdpgqpuPSyQ)
|
- [🎞️ Getting Started With STM32 and Nucleo Part 3: FreeRTOS - How To Run Multiple Threads w/ CMSIS-RTOS](https://www.youtube.com/watch?v=CdpgqpuPSyQ)
|
||||||
- [📘 Hands-On RTOS with Microcontrollers: Building real-time embedded systems using FreeRTOS, STM32 MCUs, and SEGGER debug tools - Brian Amos](https://a.co/d/11MSPlo)
|
- [📘 Hands-On RTOS with Microcontrollers: Building real-time embedded systems using FreeRTOS, STM32 MCUs, and SEGGER debug tools - Brian Amos](https://a.co/d/11MSPlo)
|
||||||
- [📘 Beginning STM32: Developing with FreeRTOS, libopencm3 and GCC - Warren Gay](https://link.springer.com/book/10.1007/978-1-4842-3624-6)
|
- [📘 Beginning STM32: Developing with FreeRTOS, libopencm3 and GCC - Warren Gay](https://link.springer.com/book/10.1007/978-1-4842-3624-6)
|
||||||
- [🔗 SafeRTOS - Safety Critical Real-Time OS](https://www.freertos.org/FreeRTOS-Plus/Safety_Critical_Certified/SafeRTOS.html)
|
- [🔗 SafeRTOS - Safety Critical Real-Time OS](https://www.freertos.org/Partners/Software/SafeRTOS)
|
||||||
|
|
||||||
##### 🔶 Zephyr
|
##### 🔶 Zephyr
|
||||||
|
|
||||||
- [🔗 Zephyr® Project](https://www.zephyrproject.org/)
|
- [🔗 Zephyr® Project](https://www.zephyrproject.org/)
|
||||||
|
- [📝💎 Getting Started With Zephyr](https://www.embeddedrelated.com/showarticle/1505.php)
|
||||||
- [🔗 Zephyr: Tutorial for Beginners](https://maksimdrachov.github.io/zephyr-rtos-tutorial/)
|
- [🔗 Zephyr: Tutorial for Beginners](https://maksimdrachov.github.io/zephyr-rtos-tutorial/)
|
||||||
- [📝 Why We Moved from FreeRTOS to Zephyr RTOS](https://www.zephyrproject.org/why-we-moved-from-freertos-to-zephyr-rtos/)
|
- [📝 Why We Moved from FreeRTOS to Zephyr RTOS](https://www.zephyrproject.org/why-we-moved-from-freertos-to-zephyr-rtos/)
|
||||||
- [🔗 nRF Connect SDK](https://academy.nordicsemi.com/courses/nrf-connect-sdk-fundamentals/)
|
- [🔗 nRF Connect SDK](https://academy.nordicsemi.com/courses/nrf-connect-sdk-fundamentals/)
|
||||||
- [🎞️ ESP32 on Zephyr OS (YouTube Playlist)](https://www.youtube.com/playlist?list=PLEQVp_6G_y4iFfemAbFsKw6tsGABarTwp)
|
- [🎞️ ESP32 on Zephyr OS (YouTube Playlist)](https://www.youtube.com/playlist?list=PLEQVp_6G_y4iFfemAbFsKw6tsGABarTwp)
|
||||||
|
- [🎞️💎 Introduction to Zephyr - Digikey (YouTube Playlist)](https://www.youtube.com/playlist?list=PLEBQazB0HUyTmK2zdwhaf8bLwuEaDH-52)
|
||||||
|
|
||||||
##### 🔶 μC/OS / Micriμm OS
|
##### 🔶 μC/OS / Micriμm OS
|
||||||
|
|
||||||
@@ -969,8 +1092,8 @@ Real-time operating systems (RTOS) are specialized operating systems designed to
|
|||||||
|
|
||||||
##### 🔶 Mbed OS
|
##### 🔶 Mbed OS
|
||||||
|
|
||||||
- [🔗 Mbed OS](https://os.mbed.com/mbed-os/)
|
- [🔗 Mbed OS](https://github.com/ARMmbed/mbed-os)
|
||||||
- [🔗 Mbed OS Documentation](https://os.mbed.com/docs/mbed-os/)
|
- [🔗 Mbed CE - Community-maintained continuation of Mbed OS](https://mbed-ce.dev/)
|
||||||
|
|
||||||
##### 🔶 QNX
|
##### 🔶 QNX
|
||||||
|
|
||||||
@@ -979,16 +1102,15 @@ Real-time operating systems (RTOS) are specialized operating systems designed to
|
|||||||
|
|
||||||
##### 🔶 VxWorks
|
##### 🔶 VxWorks
|
||||||
|
|
||||||
- [🔗 VxWorks | Industry Leading RTOS for Embedded Systems](https://www.windriver.com/products/vxworks)
|
- [🔗 VxWorks | Industry Leading RTOS for Embedded Systems](https://www.windriver.com/products/embedded/vxworks)
|
||||||
- [🔗 VxWorks Documentation](https://docs.windriver.com/category/os_vxworks?labelkey=os_vxworks)
|
- [🔗 VxWorks Documentation](https://docs.windriver.com/category/os_vxworks?labelkey=os_vxworks)
|
||||||
- [🎞️ VxWORKS-RTOS - Kumar M (YouTube Playlist)](https://www.youtube.com/playlist?list=PLyp1I7W35-q34sYWBlBg8xgoqsizZ57P7)
|
- [🎞️ VxWORKS-RTOS - Kumar M (YouTube Playlist)](https://www.youtube.com/playlist?list=PLyp1I7W35-q34sYWBlBg8xgoqsizZ57P7)
|
||||||
|
|
||||||
##### 🔶 Azure RTOS (ThreadX)
|
##### 🔶 Eclipse ThreadX (formerly Azure RTOS)
|
||||||
|
|
||||||
- [🔗 Azure RTOS - Making embedded IoT development and connectivity easy](https://azure.microsoft.com/en-us/services/rtos/)
|
|
||||||
- [🔗 Microsoft Azure RTOS documentation](https://learn.microsoft.com/en-us/azure/rtos/)
|
|
||||||
- [🔗 Azure RTOS ThreadX](https://github.com/azure-rtos/threadx)
|
|
||||||
|
|
||||||
|
- [🔗 Eclipse ThreadX - Advanced Embedded RTOS](https://threadx.io/)
|
||||||
|
- [🔗 Eclipse ThreadX documentation (formerly Azure RTOS docs)](https://github.com/eclipse-threadx/rtos-docs)
|
||||||
|
- [🔗 Eclipse ThreadX source code](https://github.com/eclipse-threadx/threadx)
|
||||||
|
|
||||||
#### 🔵 Embedded Linux
|
#### 🔵 Embedded Linux
|
||||||
|
|
||||||
@@ -997,7 +1119,7 @@ Embedded Linux is a specialized version of the Linux operating system tailored f
|
|||||||
- [📝 What Is Embedded Linux?](https://www.windriver.com/solutions/learning/embedded-linux)
|
- [📝 What Is Embedded Linux?](https://www.windriver.com/solutions/learning/embedded-linux)
|
||||||
- [📝 FreeRTOS vs Linux for Embedded Systems](https://www.bytesnap.com/news-blog/freertos-vs-linux-embedded-systems/)
|
- [📝 FreeRTOS vs Linux for Embedded Systems](https://www.bytesnap.com/news-blog/freertos-vs-linux-embedded-systems/)
|
||||||
- [🎞️👶 Introduction to Embedded Linux (YouTube Playlist)](https://youtube.com/playlist?list=PLEBQazB0HUyTpoJoZecRK6PpDG31Y7RPB)
|
- [🎞️👶 Introduction to Embedded Linux (YouTube Playlist)](https://youtube.com/playlist?list=PLEBQazB0HUyTpoJoZecRK6PpDG31Y7RPB)
|
||||||
- [📝 Mastering Embedded Linux Series - George Hilliard's blog](https://www.thirtythreeforty.net/series/mastering-embedded-linux/)
|
- [📝 Mastering Embedded Linux Series (Part 1) - George Hilliard's blog](https://www.thirtythreeforty.net/posts/mastering-embedded-linux-part-1-concepts/)
|
||||||
- [📘💎 Mastering Embedded Linux Programming - Chris Simmonds](https://www.packtpub.com/product/mastering-embedded-linux-programming-third-edition/9781789530384)
|
- [📘💎 Mastering Embedded Linux Programming - Chris Simmonds](https://www.packtpub.com/product/mastering-embedded-linux-programming-third-edition/9781789530384)
|
||||||
- [🎞️💎 Coursera - Advanced Embedded Linux Development Specialization](https://www.coursera.org/specializations/advanced-embedded-linux-development)
|
- [🎞️💎 Coursera - Advanced Embedded Linux Development Specialization](https://www.coursera.org/specializations/advanced-embedded-linux-development)
|
||||||
- [🔗 Linux From Scratch - step-by-step instructions for building your own custom Linux system](https://www.linuxfromscratch.org/)
|
- [🔗 Linux From Scratch - step-by-step instructions for building your own custom Linux system](https://www.linuxfromscratch.org/)
|
||||||
@@ -1006,7 +1128,6 @@ Embedded Linux is a specialized version of the Linux operating system tailored f
|
|||||||
- [🔗 Android Open Source Project](https://source.android.com)
|
- [🔗 Android Open Source Project](https://source.android.com)
|
||||||
- [🔗 Android Automotive](https://source.android.com/docs/automotive)
|
- [🔗 Android Automotive](https://source.android.com/docs/automotive)
|
||||||
|
|
||||||
|
|
||||||
##### 🔶 Linux Kernel
|
##### 🔶 Linux Kernel
|
||||||
|
|
||||||
- [📘👶 Linux Kernel Development - Robert Love](https://www.oreilly.com/library/view/linux-kernel-development/9780768696974/)
|
- [📘👶 Linux Kernel Development - Robert Love](https://www.oreilly.com/library/view/linux-kernel-development/9780768696974/)
|
||||||
@@ -1015,15 +1136,16 @@ Embedded Linux is a specialized version of the Linux operating system tailored f
|
|||||||
|
|
||||||
##### 🔶 Linux Device Drivers
|
##### 🔶 Linux Device Drivers
|
||||||
|
|
||||||
- [📝👶 Writing a Simple Linux Kernel Module](https://blog.sourcerer.io/writing-a-simple-linux-kernel-module-d9dc3762c234)
|
|
||||||
- [📘💎 Linux Device Drivers - Jonathan Corbet, Alessandro Rubini, Greg Kroah-Hartman](https://lwn.net/Kernel/LDD3/)
|
- [📘💎 Linux Device Drivers - Jonathan Corbet, Alessandro Rubini, Greg Kroah-Hartman](https://lwn.net/Kernel/LDD3/)
|
||||||
- [📘 The Linux Kernel Module Programming Guide](https://sysprog21.github.io/lkmpg/)
|
- [📘 The Linux Kernel Module Programming Guide](https://sysprog21.github.io/lkmpg/)
|
||||||
|
- [🔗👶 Kernel modules - Linux Kernel Teaching Labs](https://linux-kernel-labs.github.io/refs/heads/master/labs/kernel_modules.html)
|
||||||
- [📘 Mastering Linux Device Driver Development - John Madieu](https://www.packtpub.com/product/mastering-linux-device-driver-development/9781789342048)
|
- [📘 Mastering Linux Device Driver Development - John Madieu](https://www.packtpub.com/product/mastering-linux-device-driver-development/9781789342048)
|
||||||
- [📝 Kernel Driver with Rust in 2022](https://not-matthias.github.io/posts/kernel-driver-with-rust/)
|
- [📝 Kernel Driver with Rust in 2022](https://not-matthias.github.io/posts/kernel-driver-with-rust/)
|
||||||
|
|
||||||
##### 🔶 U-Boot
|
##### 🔶 U-Boot
|
||||||
|
|
||||||
- [🔗 The U-Boot Documentation](https://u-boot.readthedocs.io/en/latest/)
|
- [🔗 The U-Boot Documentation](https://docs.u-boot.org/en/latest/)
|
||||||
|
- [🔗 Barebox](https://www.barebox.org/doc/latest/index.html)
|
||||||
|
|
||||||
##### 🔶 Buildroot
|
##### 🔶 Buildroot
|
||||||
|
|
||||||
@@ -1036,33 +1158,40 @@ Embedded Linux is a specialized version of the Linux operating system tailored f
|
|||||||
- [🔗 Yocto Project Quick Build](https://docs.yoctoproject.org/brief-yoctoprojectqs/index.html)
|
- [🔗 Yocto Project Quick Build](https://docs.yoctoproject.org/brief-yoctoprojectqs/index.html)
|
||||||
- [🔗💎 Yocto Project Documentation](https://docs.yoctoproject.org/index.html)
|
- [🔗💎 Yocto Project Documentation](https://docs.yoctoproject.org/index.html)
|
||||||
- [🎞️ Introduction to Embedded Linux Part 2 - Yocto Project | Digi-Key Electronics](https://www.youtube.com/watch?v=2-PwskQrZac&t=7s)
|
- [🎞️ Introduction to Embedded Linux Part 2 - Yocto Project | Digi-Key Electronics](https://www.youtube.com/watch?v=2-PwskQrZac&t=7s)
|
||||||
- [📝 Build Linux Image for Raspberry Pi board using Yocto Project](https://tutorialadda.com/yocto/create-your-own-linux-image-for-the-raspberry-pi-board-using-yocto-project)
|
|
||||||
- [🎞️ Yocto Project Tutorial Series (Basic to Advance) (YouTube Playlist)](https://www.youtube.com/playlist?list=PLwqS94HTEwpQmgL1UsSwNk_2tQdzq3eVJ)
|
- [🎞️ Yocto Project Tutorial Series (Basic to Advance) (YouTube Playlist)](https://www.youtube.com/playlist?list=PLwqS94HTEwpQmgL1UsSwNk_2tQdzq3eVJ)
|
||||||
- [🎞️ Udemy – Embedded Linux using Yocto](https://www.udemy.com/course/embedded-linux-using-yocto/)
|
- [🎞️ Udemy – Embedded Linux using Yocto](https://www.udemy.com/course/embedded-linux-using-yocto/)
|
||||||
- [📘💎 Embedded Linux Systems with the Yocto Project - Rudolf J.Streif](https://www.oreilly.com/library/view/embedded-linux-systems/9780133443301/)
|
- [📘💎 Embedded Linux Systems with the Yocto Project - Rudolf J.Streif](https://www.oreilly.com/library/view/embedded-linux-systems/9780133443301/)
|
||||||
- [📘 Embedded Linux Development using Yocto Project Cookbook - Alex Gonzalez](https://www.oreilly.com/library/view/embedded-linux-development/9781788399210/)
|
- [📘 Embedded Linux Development using Yocto Project Cookbook - Alex Gonzalez](https://www.oreilly.com/library/view/embedded-linux-development/9781788399210/)
|
||||||
|
- [📘 Bootlin Embedded Linux, Kernel, drivers, Yocto, Buildroot and Graphics Training](https://bootlin.com/training/)
|
||||||
|
- [🔗 meta-raspberrypi - Yocto/OpenEmbedded layer for Raspberry Pi boards](https://meta-raspberrypi.readthedocs.io/en/latest/)
|
||||||
|
|
||||||
##### 🔶 Multithreading & Parallel Processing
|
#### 🔵 Multithreading & Parallel Processing
|
||||||
|
|
||||||
- [📝 Threading/Concurrency vs Parallelism](http://www.danielmoth.com/Blog/threadingconcurrency-vs-parallelism.aspx)
|
- [📝 Threading/Concurrency vs Parallelism](http://www.danielmoth.com/Blog/threadingconcurrency-vs-parallelism.aspx)
|
||||||
- [📝 Multi-threading and Parallel Programming](https://dev.to/kwereutosu/multi-threading-and-parallel-programming-1l9m)
|
- [📝 Multi-threading and Parallel Programming](https://dev.to/kwereutosu/multi-threading-and-parallel-programming-1l9m)
|
||||||
- [📝 Multitasking vs Multithreading vs Multiprocessing](https://medium.com/codex/multitasking-vs-multithreading-vs-multiprocessing-2b0087c861ae)
|
- [📝 Multitasking vs Multithreading vs Multiprocessing](https://medium.com/codex/multitasking-vs-multithreading-vs-multiprocessing-2b0087c861ae)
|
||||||
- [📘💎 Programming with POSIX Threads - David Butenhof](https://a.co/d/9R6DswF)
|
- [📘💎 Programming with POSIX Threads - David Butenhof](https://a.co/d/9R6DswF)
|
||||||
- [🔗 Parallel Programming and Performance Optimization With OpenMP](https://passlab.github.io/OpenMPProgrammingBook/index.html)
|
|
||||||
- [🎞️ Introduction to OpenMP - Tim Mattson (Intel) (YouTube Playlist)](https://youtube.com/playlist?list=PLLX-Q6B8xqZ8n8bwjGdzBJ25X2utwnoEG)
|
|
||||||
- [🔗 OpenCL Tutorials](https://www.intel.com/content/www/us/en/developer/articles/training/opencl-tutorials.html)
|
|
||||||
- [🔗 CUDA C++ Programming Guide](https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html)
|
|
||||||
- [📘 C++ Concurrency in Action - Anthony Williams](https://www.manning.com/books/c-plus-plus-concurrency-in-action)
|
- [📘 C++ Concurrency in Action - Anthony Williams](https://www.manning.com/books/c-plus-plus-concurrency-in-action)
|
||||||
|
|
||||||
##### 🔶 Inter-Process Communication (IPC)
|
##### 🔶 High-Performance Computing (HPC)
|
||||||
|
|
||||||
|
- [🔗 Parallel Programming and Performance Optimization With OpenMP](https://passlab.github.io/InteractiveOpenMPProgramming/)
|
||||||
|
- [🎞️ Introduction to OpenMP - Tim Mattson (Intel) (YouTube Playlist)](https://youtube.com/playlist?list=PLLX-Q6B8xqZ8n8bwjGdzBJ25X2utwnoEG)
|
||||||
|
- [🔗 OpenCL Tutorials](https://www.intel.com/content/www/us/en/developer/articles/training/opencl-tutorials.html)
|
||||||
|
- [🔗 CUDA Programming Guide](https://docs.nvidia.com/cuda/cuda-programming-guide/index.html)
|
||||||
|
- [📝👶 An Even Easier Introduction to CUDA - NVIDIA Technical Blog](https://developer.nvidia.com/blog/even-easier-introduction-cuda/)
|
||||||
|
- [🔗👶 GPU Puzzles - Solve puzzles, learn CUDA](https://github.com/srush/GPU-Puzzles)
|
||||||
|
|
||||||
|
#### 🔵 Inter-Process Communication (IPC)
|
||||||
|
|
||||||
- [📝 Inter Process Communication (IPC)](https://www.scaler.com/topics/operating-system/inter-process-communication-in-os/)
|
- [📝 Inter Process Communication (IPC)](https://www.scaler.com/topics/operating-system/inter-process-communication-in-os/)
|
||||||
- [📝 What Is Inter-Process Communication In Linux?](https://www.scaler.com/topics/ipc-in-linux/)
|
- [📝 What Is Inter-Process Communication In Linux?](https://www.scaler.com/topics/ipc-in-linux/)
|
||||||
- [🎞️ Udemy – Linux Inter Process Communication (IPC) from Scratch in C](https://www.udemy.com/course/linuxipc/)
|
- [🎞️ Udemy – Linux Inter Process Communication (IPC) from Scratch in C](https://www.udemy.com/course/linuxipc/)
|
||||||
|
- [📘 Extreme C - Kamran Amini | Chapter 19: Single-Host IPC and Sockets](https://www.packtpub.com/free-ebook/extreme-c/9781789343625)
|
||||||
- [🎞️ Udemy – Multi-Threading and IPC with Qt 5 C++](https://www.udemy.com/course/multi-threading-and-ipc-with-qt-c-plus-plus/)
|
- [🎞️ Udemy – Multi-Threading and IPC with Qt 5 C++](https://www.udemy.com/course/multi-threading-and-ipc-with-qt-c-plus-plus/)
|
||||||
- [📝 D-Bus Tutorial](https://dbus.freedesktop.org/doc/dbus-tutorial.html)
|
- [📝 D-Bus Tutorial](https://dbus.freedesktop.org/doc/dbus-tutorial.html)
|
||||||
|
|
||||||
##### 🔶 Qt Framework
|
#### 🔵 Qt Framework
|
||||||
|
|
||||||
- [🔗 Qt Documentation](https://doc.qt.io/)
|
- [🔗 Qt Documentation](https://doc.qt.io/)
|
||||||
|
|
||||||
@@ -1092,7 +1221,7 @@ GDB (GNU Debugger) is a powerful and versatile debugger for source-level and mac
|
|||||||
- [📝 Advanced GDB Usage](https://interrupt.memfault.com/blog/advanced-gdb)
|
- [📝 Advanced GDB Usage](https://interrupt.memfault.com/blog/advanced-gdb)
|
||||||
- [📝 How do breakpoints even work?](https://interrupt.memfault.com/blog/cortex-m-breakpoints)
|
- [📝 How do breakpoints even work?](https://interrupt.memfault.com/blog/cortex-m-breakpoints)
|
||||||
- [🔗 GNU GDB Debugger Command Cheat Sheet](http://www.yolinux.com/TUTORIALS/GDB-Commands.html)
|
- [🔗 GNU GDB Debugger Command Cheat Sheet](http://www.yolinux.com/TUTORIALS/GDB-Commands.html)
|
||||||
- [🔗 gdbgui - A browser-based frontend to gdb (gnu debugger) ](https://www.gdbgui.com/)
|
- [🔗 gdbgui - A browser-based frontend to gdb (gnu debugger)](https://www.gdbgui.com/)
|
||||||
- [🎞️ everyone needs to stop using print debugging (do THIS instead)](https://www.youtube.com/watch?v=3T3ZDquDDVg)
|
- [🎞️ everyone needs to stop using print debugging (do THIS instead)](https://www.youtube.com/watch?v=3T3ZDquDDVg)
|
||||||
- [🎞️ GDB is REALLY easy! Find Bugs in Your Code with Only A Few Commands](https://www.youtube.com/watch?v=Dq8l1_-QgAc)
|
- [🎞️ GDB is REALLY easy! Find Bugs in Your Code with Only A Few Commands](https://www.youtube.com/watch?v=Dq8l1_-QgAc)
|
||||||
- [📝 Introduction to ARM Semihosting](https://interrupt.memfault.com/blog/arm-semihosting)
|
- [📝 Introduction to ARM Semihosting](https://interrupt.memfault.com/blog/arm-semihosting)
|
||||||
@@ -1121,7 +1250,7 @@ GCC (the GNU Compiler Collection) is a free and open-source compiler system that
|
|||||||
- [🔗 Keil MDK & µVision](https://www.keil.com/)
|
- [🔗 Keil MDK & µVision](https://www.keil.com/)
|
||||||
- [🔗 IAR Embedded Workbench](https://www.iar.com)
|
- [🔗 IAR Embedded Workbench](https://www.iar.com)
|
||||||
|
|
||||||
#### 🔵 Make & CMake
|
#### 🔵 Make / CMake
|
||||||
|
|
||||||
CMake and Make are both tools for building software applications. CMake is a meta-build system that generates Makefiles, which are then used by Make to build the software. CMake is more versatile and cross-platform than Make, and it is becoming the more popular choice for modern software development.
|
CMake and Make are both tools for building software applications. CMake is a meta-build system that generates Makefiles, which are then used by Make to build the software. CMake is more versatile and cross-platform than Make, and it is becoming the more popular choice for modern software development.
|
||||||
|
|
||||||
@@ -1130,6 +1259,7 @@ CMake and Make are both tools for building software applications. CMake is a met
|
|||||||
- [🔗 CMake Tutorial](https://cmake.org/cmake/help/latest/guide/tutorial/index.html)
|
- [🔗 CMake Tutorial](https://cmake.org/cmake/help/latest/guide/tutorial/index.html)
|
||||||
- [🎞️ How to CMake Good (YouTube Playlist)](https://youtube.com/playlist?list=PLK6MXr8gasrGmIiSuVQXpfFuE1uPT615s)
|
- [🎞️ How to CMake Good (YouTube Playlist)](https://youtube.com/playlist?list=PLK6MXr8gasrGmIiSuVQXpfFuE1uPT615s)
|
||||||
- [📝👶 The most thoroughly commented embedded CMakeLists file](https://dnedic.github.io/blog/the-most-thoroughly-commented-embedded-cmakelists/)
|
- [📝👶 The most thoroughly commented embedded CMakeLists file](https://dnedic.github.io/blog/the-most-thoroughly-commented-embedded-cmakelists/)
|
||||||
|
- [🔗 A lightweight build and workflow tool for C/C++](https://github.com/randerson112/craft)
|
||||||
|
|
||||||
#### 🔵 Bash Scripting
|
#### 🔵 Bash Scripting
|
||||||
|
|
||||||
@@ -1143,11 +1273,12 @@ Bash scripting serves as a powerful tool in embedded systems development, enabli
|
|||||||
Docker containers provide a consistent and isolated environment for building software applications. This can help to improve the reproducibility of builds and reduce the risk of errors. Docker also makes it easier to share build environments, which can save time and effort for developers.
|
Docker containers provide a consistent and isolated environment for building software applications. This can help to improve the reproducibility of builds and reduce the risk of errors. Docker also makes it easier to share build environments, which can save time and effort for developers.
|
||||||
|
|
||||||
- [🔗 Docker Docs](https://docs.docker.com/get-started/)
|
- [🔗 Docker Docs](https://docs.docker.com/get-started/)
|
||||||
- [🔗 Docker Cheat Sheet](https://dockerlabs.collabnix.com/docker/cheatsheet/)
|
- [🔗 Docker Cheat Sheet](https://docs.docker.com/get-started/docker_cheatsheet.pdf)
|
||||||
- [📝👶 Docker for Dummies](https://dev.to/stevenmcgown/docker-for-dummies-2bff)
|
- [📝👶 Docker for Dummies](https://dev.to/stevenmcgown/docker-for-dummies-2bff)
|
||||||
- [🎞️ Introduction to Docker for the Embedded Developer](https://www.youtube.com/watch?v=Fz7ou-VBk-w)
|
- [🎞️ Introduction to Docker for the Embedded Developer](https://www.youtube.com/watch?v=Fz7ou-VBk-w)
|
||||||
- [🎞️ Intro to CI/CD Part 1: Getting Started with Docker | Digi-Key Electronics](https://youtu.be/1nxGcfIm-TU)
|
- [🎞️ Intro to CI/CD Part 1: Getting Started with Docker | Digi-Key Electronics](https://youtu.be/1nxGcfIm-TU)
|
||||||
- [📝 A Modern C Development Environment](https://interrupt.memfault.com/blog/a-modern-c-dev-env)
|
- [📝 A Modern C Development Environment](https://interrupt.memfault.com/blog/a-modern-c-dev-env)
|
||||||
|
|
||||||
_________________
|
_________________
|
||||||
|
|
||||||
### ✳️ Software Development Life Cycle (SDLC) Models
|
### ✳️ Software Development Life Cycle (SDLC) Models
|
||||||
@@ -1160,8 +1291,9 @@ Software Development Life Cycle (SDLC) models provide a structured approach to s
|
|||||||
#### 🔵 Agile / SCRUM
|
#### 🔵 Agile / SCRUM
|
||||||
|
|
||||||
- [📝 Does agile work with embedded software?](https://www.embedded.com/does-agile-work-with-embedded-software/)
|
- [📝 Does agile work with embedded software?](https://www.embedded.com/does-agile-work-with-embedded-software/)
|
||||||
- [📝 Scrum for embedded software: Good – but for reasons other than what your manager thinks](https://www.elektrobit.com/trends/scrum-for-embedded-software/)
|
|
||||||
- [📝 What Is Scrum: A Guide to the Most Popular Agile Framework](https://www.scrumalliance.org/about-scrum)
|
- [📝 What Is Scrum: A Guide to the Most Popular Agile Framework](https://www.scrumalliance.org/about-scrum)
|
||||||
|
- [🔗 The Scrum Guide](https://scrumguides.org/scrum-guide.html)
|
||||||
|
- [🔗 Wingman Software - Agility for Embedded Software Development (James Grenning)](https://wingman-sw.com/)
|
||||||
- [📝 An agile guide to scrum meetings](https://www.atlassian.com/agile/scrum/ceremonies)
|
- [📝 An agile guide to scrum meetings](https://www.atlassian.com/agile/scrum/ceremonies)
|
||||||
- [📝 What is scaled agile framework? (SAFe)](https://www.atlassian.com/agile/agile-at-scale/what-is-safe)
|
- [📝 What is scaled agile framework? (SAFe)](https://www.atlassian.com/agile/agile-at-scale/what-is-safe)
|
||||||
- [🔗 Jira - Issue & Project Tracking Software](https://confluence.atlassian.com/jira)
|
- [🔗 Jira - Issue & Project Tracking Software](https://confluence.atlassian.com/jira)
|
||||||
@@ -1208,13 +1340,22 @@ Testing is an integral part of the embedded systems development process, ensurin
|
|||||||
- [📝 Embedded C/C++ Unit Testing Basics](https://interrupt.memfault.com/blog/unit-testing-basics)
|
- [📝 Embedded C/C++ Unit Testing Basics](https://interrupt.memfault.com/blog/unit-testing-basics)
|
||||||
- [📝 Embedded C/C++ Unit Testing with Mocks](https://interrupt.memfault.com/blog/unit-test-mocking)
|
- [📝 Embedded C/C++ Unit Testing with Mocks](https://interrupt.memfault.com/blog/unit-test-mocking)
|
||||||
- [🔗 Unit Testing for C (especially Embedded Software)](http://www.throwtheswitch.org/unity)
|
- [🔗 Unit Testing for C (especially Embedded Software)](http://www.throwtheswitch.org/unity)
|
||||||
- [🔗 GoogleTest - Google Testing and Mocking Framework](https://github.com/google/googletest)
|
- [📝 How to Use Ceedling for Embedded Test-Driven Development](https://www.electronvector.com/how-to-use-ceedling-for-embedded-test-driven-development)
|
||||||
- [📘 Unit Testing Principles, Practices, and Patterns - Vladimir Khorikov](https://www.manning.com/books/unit-testing)
|
- [📘 Unit Testing Principles, Practices, and Patterns - Vladimir Khorikov](https://www.manning.com/books/unit-testing)
|
||||||
|
- [🔗 Catch2 - A modern, C++-native, test framework for unit-tests](https://github.com/catchorg/Catch2)
|
||||||
|
- [🔗 pytest-embedded](https://docs.espressif.com/projects/pytest-embedded/en/latest/)
|
||||||
|
- [📝 Introduction to testing ESP32 code with Pytest](https://blog.gistre.epita.fr/posts/brice.parent-2023-09-11-introduction_to_testing_esp32_code_with_pytest/)
|
||||||
|
- [🔗 Pigweed](https://pigweed.dev/)
|
||||||
|
|
||||||
|
###### [🔸 GoogleTest - Google Testing and Mocking Framework](https://github.com/google/googletest)
|
||||||
|
|
||||||
|
- [🔗 GoogleTest User’s Guide](https://google.github.io/googletest/)
|
||||||
|
- [🎞️ GoogleTest And GoogleMock (GTest and GMock)](https://www.youtube.com/playlist?list=PLHn7_PzMqzs5JE58kw4nWiFELEkQek5G0)
|
||||||
|
|
||||||
##### 🔶 Integration Testing
|
##### 🔶 Integration Testing
|
||||||
|
|
||||||
- [📝 Hardware CI Arena](https://electricui.com/blog/hardware-testing)
|
- [📝 Hardware CI Arena](https://electricui.com/blog/hardware-testing)
|
||||||
- [📝 Exclave: Hardware Testing in Mass Production, Made Easier](https://www.bunniestudios.com/blog/?p=5450)
|
- [📝 Exclave: Hardware Testing in Mass Production, Made Easier](https://www.bunniestudios.com/blog/2018/exclave-hardware-testing-in-mass-production-made-easier)
|
||||||
- [📝 Regression Testing of Embedded Systems](https://www.parasoft.com/blog/regression-testing-of-embedded-systems/)
|
- [📝 Regression Testing of Embedded Systems](https://www.parasoft.com/blog/regression-testing-of-embedded-systems/)
|
||||||
|
|
||||||
#### 🔵 CI/CD Pipelines
|
#### 🔵 CI/CD Pipelines
|
||||||
@@ -1234,7 +1375,8 @@ Continuous Integration (CI) and Continuous Delivery (CD) are software developmen
|
|||||||
|
|
||||||
Software-in-the-loop (SIL) and hardware-in-the-loop (HIL) testing are two crucial techniques used in embedded systems development to validate the functionality and performance of software before it is deployed on real hardware. These testing methods simulate the real-world environment of the embedded system using software models or hardware emulators.
|
Software-in-the-loop (SIL) and hardware-in-the-loop (HIL) testing are two crucial techniques used in embedded systems development to validate the functionality and performance of software before it is deployed on real hardware. These testing methods simulate the real-world environment of the embedded system using software models or hardware emulators.
|
||||||
|
|
||||||
- [📝 Hardware-in-Loop and Software-in-Loop Testing](https://roboticsknowledgebase.com/wiki/system-design-development/In-Loop-Testing/)
|
- [📝 What Is Hardware-in-the-Loop (HIL)? Differences Between MIL, SIL, PIL, and HIL - MathWorks](https://www.mathworks.com/discovery/hardware-in-the-loop-hil.html)
|
||||||
|
- [📝 Hardware-in-the-loop simulation - Wikipedia](https://en.wikipedia.org/wiki/Hardware-in-the-loop_simulation)
|
||||||
- [🎞️ Embedded CI/CD with HIL Testing (YouTube Playlist)](https://youtube.com/playlist?list=PL4cGeWgaBTe1uwiqIAc6fwPzPpvgPZI2J)
|
- [🎞️ Embedded CI/CD with HIL Testing (YouTube Playlist)](https://youtube.com/playlist?list=PL4cGeWgaBTe1uwiqIAc6fwPzPpvgPZI2J)
|
||||||
|
|
||||||
#### 🔵 Standards & Certifications
|
#### 🔵 Standards & Certifications
|
||||||
@@ -1281,7 +1423,7 @@ Learning hardware hacking helps security professionals understand how embedded s
|
|||||||
- [🎞️ Understanding AES Encryption Mechanics: BMPS](https://youtu.be/OnhtzFJW_4I)
|
- [🎞️ Understanding AES Encryption Mechanics: BMPS](https://youtu.be/OnhtzFJW_4I)
|
||||||
- [📘💎 Understanding Cryptography: A Textbook for Students and Practitioners - Christof Paar, Jan Pelzl](https://link.springer.com/book/10.1007/978-3-642-04101-3)
|
- [📘💎 Understanding Cryptography: A Textbook for Students and Practitioners - Christof Paar, Jan Pelzl](https://link.springer.com/book/10.1007/978-3-642-04101-3)
|
||||||
- [📘💎 Handbook of Applied Cryptography - Alfred J. Menezes, Paul C. van Oorschot, Scott A. Vanstone](https://cacr.uwaterloo.ca/hac/)
|
- [📘💎 Handbook of Applied Cryptography - Alfred J. Menezes, Paul C. van Oorschot, Scott A. Vanstone](https://cacr.uwaterloo.ca/hac/)
|
||||||
- [📘 Serious Cryptography: A Practical Introduction to Modern Encryption - Jean-Philippe Aumasson](https://nostarch.com/seriouscrypto)
|
- [📘 Serious Cryptography, 2nd Edition: A Practical Introduction to Modern Encryption - Jean-Philippe Aumasson](https://nostarch.com/serious-cryptography-2nd-edition)
|
||||||
- [🎞️ MOOC - Security Part2: Basics of cryptography (YouTube Playlist)](https://youtube.com/playlist?list=PLnMKNibPkDnFSFh57UFTZLpy-7lZiwTHh)
|
- [🎞️ MOOC - Security Part2: Basics of cryptography (YouTube Playlist)](https://youtube.com/playlist?list=PLnMKNibPkDnFSFh57UFTZLpy-7lZiwTHh)
|
||||||
- [🎞️ MOOC - Security Part3 : STM32 security features (YouTube Playlist)](https://youtube.com/playlist?list=PLnMKNibPkDnFzux3PHKUEi14ftDn9Cbm7)
|
- [🎞️ MOOC - Security Part3 : STM32 security features (YouTube Playlist)](https://youtube.com/playlist?list=PLnMKNibPkDnFzux3PHKUEi14ftDn9Cbm7)
|
||||||
- [📝 Introduction to encryption for embedded Linux developers](https://sergioprado.blog/introduction-to-encryption-for-embedded-linux-developers/)
|
- [📝 Introduction to encryption for embedded Linux developers](https://sergioprado.blog/introduction-to-encryption-for-embedded-linux-developers/)
|
||||||
@@ -1325,14 +1467,15 @@ The Internet of Things (IoT) refers to a network of interconnected devices, whic
|
|||||||
|
|
||||||
- [📝 Saving bandwidth with delta firmware updates](https://interrupt.memfault.com/blog/ota-delta-updates)
|
- [📝 Saving bandwidth with delta firmware updates](https://interrupt.memfault.com/blog/ota-delta-updates)
|
||||||
- [📝 Delta Over-The-Air Device Firmware Update](https://thinkty.net/projects/2023/05/07/delta_ota.html)
|
- [📝 Delta Over-The-Air Device Firmware Update](https://thinkty.net/projects/2023/05/07/delta_ota.html)
|
||||||
- [📝 FreeRTOS Delta Over-the-Air Updates](https://www.freertos.org/2022/01/delta-over-the-air-updates.html)
|
- [📝 FreeRTOS Delta Over-the-Air Updates](https://www.freertos.org/Community/Blogs/2022/delta-over-the-air-updates)
|
||||||
- [🔗 ESP Delta OTA](https://components.espressif.com/components/espressif/esp_delta_ota)
|
- [🔗 ESP Delta OTA](https://components.espressif.com/components/espressif/esp_delta_ota)
|
||||||
|
|
||||||
|
_________________
|
||||||
|
|
||||||
### ✳️ Edge AI
|
### ✳️ Edge AI
|
||||||
|
|
||||||
Edge AI refers to the ability of devices to perform machine learning tasks on their own, without the need to send data to a central server. This can be done by using small, specialized AI models that are embedded directly into the device. TinyML is a subset of Edge AI that focuses on developing these models for devices with very limited computing power, such as microcontrollers and sensors.
|
Edge AI refers to the ability of devices to perform machine learning tasks on their own, without the need to send data to a central server. This can be done by using small, specialized AI models that are embedded directly into the device. TinyML is a subset of Edge AI that focuses on developing these models for devices with very limited computing power, such as microcontrollers and sensors.
|
||||||
|
|
||||||
- [🎞️ Udemy – Getting Started with Embedded AI | Edge AI](https://www.udemy.com/course/getting-started-with-embedded-ai-hands-on-experience/)
|
|
||||||
- [🎞️ Intro to Edge AI: Machine Learning + IoT – Maker.io Tutorial | Digi-Key Electronics](https://www.youtube.com/watch?v=Ejld8XZmvwE)
|
- [🎞️ Intro to Edge AI: Machine Learning + IoT – Maker.io Tutorial | Digi-Key Electronics](https://www.youtube.com/watch?v=Ejld8XZmvwE)
|
||||||
|
|
||||||
#### 🔵 AI & ML Basics
|
#### 🔵 AI & ML Basics
|
||||||
@@ -1357,7 +1500,7 @@ Edge AI refers to the ability of devices to perform machine learning tasks on th
|
|||||||
- [🎞️ edX - Fundamentals of TinyML](https://www.edx.org/learn/machine-learning/harvard-university-fundamentals-of-tinyml)
|
- [🎞️ edX - Fundamentals of TinyML](https://www.edx.org/learn/machine-learning/harvard-university-fundamentals-of-tinyml)
|
||||||
- [🎞️ edX - Applications of TinyML](https://www.edx.org/learn/tinyml/harvard-university-applications-of-tinyml)
|
- [🎞️ edX - Applications of TinyML](https://www.edx.org/learn/tinyml/harvard-university-applications-of-tinyml)
|
||||||
- [🎞️ edX - Deploying TinyML](https://www.edx.org/learn/tinyml/harvard-university-deploying-tinyml)
|
- [🎞️ edX - Deploying TinyML](https://www.edx.org/learn/tinyml/harvard-university-deploying-tinyml)
|
||||||
- [🎞️ edX - MLOps for Scaling TinyML](https://www.edx.org/learn/computer-programming/harvard-university-mlops-for-scaling-tinyml)
|
- [🎞️ edX - MLOps for Scaling TinyML](https://www.edx.org/learn/tinyml/harvard-university-mlops-for-scaling-tinyml)
|
||||||
- [📘 TinyML: Machine Learning with TensorFlow Lite on Arduino and Ultra-Low-Power Microcontrollers - Pete Warden, Daniel Situnayake](https://tinymlbook.com/)
|
- [📘 TinyML: Machine Learning with TensorFlow Lite on Arduino and Ultra-Low-Power Microcontrollers - Pete Warden, Daniel Situnayake](https://tinymlbook.com/)
|
||||||
|
|
||||||
_________________
|
_________________
|
||||||
@@ -1370,12 +1513,44 @@ AUTOSAR, or AUTomotive Open System ARchitecture, is a global industry standard f
|
|||||||
- [🎞️ AUTOSAR Basics | AUTOSAR Tutorial | Architecture | Automotive](https://www.youtube.com/watch?v=7b5BY1IAfwY)
|
- [🎞️ AUTOSAR Basics | AUTOSAR Tutorial | Architecture | Automotive](https://www.youtube.com/watch?v=7b5BY1IAfwY)
|
||||||
- [🎞️ Introduction to AUTOSAR](https://www.youtube.com/watch?v=NfZI8wvgZPo)
|
- [🎞️ Introduction to AUTOSAR](https://www.youtube.com/watch?v=NfZI8wvgZPo)
|
||||||
- [🎞️ Udemy - Autosar Architecture (Learn from Scratch with Demo)](https://www.udemy.com/course/autosar-architecture/)
|
- [🎞️ Udemy - Autosar Architecture (Learn from Scratch with Demo)](https://www.udemy.com/course/autosar-architecture/)
|
||||||
|
- [📝 OSEK OS Overview](https://www.autosartoday.com/posts/osek_os_overview)
|
||||||
|
- [🔗💎 OSEK/VDX Operating System Specification 2.2.3](https://www.osek-vdx.org/mirror/os223.pdf)
|
||||||
|
- [🔗💎 OSEK/VDX OIL (OSEK Implementation Language) Specification 2.4.1](https://www.osek-vdx.org/mirror/oil241.pdf)
|
||||||
|
|
||||||
|
_________________
|
||||||
|
|
||||||
|
### ✳️ Appendix-A: Advanced Topics
|
||||||
|
|
||||||
|
#### 🔵 Discrete Mathematics
|
||||||
|
|
||||||
|
A common subject that is essential to Computer Science students and some Math/Electrical Engineering Majors based on the [Association For Computing Machinery Guidelines for CS/CE education](https://www.acm.org/education/curricula-recommendations). It houses the most essential subjects for _abstract mathematical modelling_ and _theory of computation_. Furthermore, it helps students to establish the essential engineering mindset that represents the common basis among all computer science and computer engineering domains. However, Logic, Theory of combinatorics, Coding Theory, and Theory of Computation are among the most essential subjects for Embedded Systems Engineers.
|
||||||
|
|
||||||
|
_**Goal of learning Discrete Mathematics:**_ Embedded Systems Engineers should be able to grasp most of these information sooner or later based on their subspeciality; as it explains the scientific and theoretical basis of computer engineering (e.g., Coding Theory for Audio/Video Encoding/Decoding and Data Transmission - Automata Theory for event control and data transmission - Logic for digital circuit design and switching algebra - $$\Pi$$ Calculus and Automata Theory for Concurrency Modelling).
|
||||||
|
|
||||||
|
_Discrete Mathematics_ is a multi-semester course that involves subjects about the theoretical mathematical basis of computer science, including but not limited to:
|
||||||
|
|
||||||
|
- Logic (Propositional, Quantificational and Normal Forms).
|
||||||
|
- Axiomatic Set Theory (Sets, Operations on sets, and Power Sets).
|
||||||
|
- Relation Sets and Function Theory (i.e., $$\lambda$$ Calculus).
|
||||||
|
- Theory of combinatorics.
|
||||||
|
- Matrix and Graph Theory.
|
||||||
|
- Theory of computation (i.e., Computational Automata Theory, Computational Complexity, and Computational Computability).
|
||||||
|
- Automata Theory (e.g., Finite-state Machines, Pushdown Automata, Turing Machines, and Cellular Automata).
|
||||||
|
- Process Modelling/Calculi (i.e., $$\pi$$ Calculus).
|
||||||
|
- Coding Theory.
|
||||||
|
- Division Theorem Modular Arithmetics (Parts of Number Theory).
|
||||||
|
- [📘💎 Discrete Mathematics and its applications by Rosen](https://dl.acm.org/doi/book/10.5555/579402)
|
||||||
|
- [📘💎 Discrete Mathematics with Applications by Susanna S. Epp](https://dl.acm.org/doi/book/10.5555/1941983)
|
||||||
|
- [📘💎 Switching and Finite Automata Theory, Cambridge Press](https://dl.acm.org/doi/10.5555/1708070)
|
||||||
|
- [📘💎 Discrete Mathematical Structures by Bernard Kolman](https://www.pearson.com/en-us/subject-catalog/p/discrete-mathematical-structures-classic-version/P200000006227/9780137538782?srsltid=AfmBOooBdohmNtq7Wu42tHwSt7lUma46hBmKjqk8XJ8NNI6sf2-4-1kg)
|
||||||
|
- [📘💎 Invitation to Discrete Mathematics, Oxford University Press](https://global.oup.com/academic/product/invitation-to-discrete-mathematics-9780198570431?cc=eg&lang=en&)
|
||||||
|
- [📘💎 The Mathematics of Cellular Automata by Jane Hawkins, AMS](https://bookstore.ams.org/stml-108)
|
||||||
|
|
||||||
## History
|
## History
|
||||||
|
|
||||||
At first this was meant to be my personal reading list but as the list gradually became bigger and bigger, I wondered why not share it with others. So I did research on current job postings for embedded engineering roles, selecting the most frequent skills and including them on a roadmap. In order to make the roadmap more comprehensive I also added some basic skills and finally came up with what you see here.
|
At first this was meant to be my personal reading list but as the list gradually became bigger and bigger, I wondered why not share it with others. So I did research on current job postings for embedded engineering roles, selecting the most frequent skills and including them on a roadmap. In order to make the roadmap more comprehensive I also added some basic skills and finally came up with what you see here.
|
||||||
|
|
||||||
The idea of creating this roadmap came from [vazeri / Embedded-Engineering-RoadMap-2018](https://github.com/vazeri/Embedded-Engineering-RoadMap-2018) which was well designed but had some flaws and not updated for years. I took that idea, changed the structure of the roadmap and tried to improve it. The initial results can be accessed in [m3y54m / Embedded-Engineering-Roadmap-Archived](https://github.com/m3y54m/Embedded-Engineering-Roadmap-Archived) which is now archived. Early versions of the roadmap were created using [Balsamiq Wireframes](https://balsamiq.com) which is not suitable for these types of diagrams. So I decided to use Microsoft Visio instead and redrew the whole diagram.
|
The idea of creating this roadmap came from [vazeri / Embedded-Engineering-RoadMap-2018](https://github.com/vazeri/Embedded-Engineering-RoadMap-2018) which was well designed but had some flaws and not updated for years. I took that idea, changed the structure of the roadmap and tried to improve it. The initial results can be accessed in [m3y54m / Embedded-Engineering-Roadmap-Archived](https://github.com/m3y54m/Embedded-Engineering-Roadmap-Archived) which is now archived. Early versions of the roadmap were created using [Balsamiq Wireframes](https://balsamiq.com) which is not suitable for these types of diagrams. Today the roadmap is drawn by the interactive explorer in this repository, directly from this README and the map definition.
|
||||||
|
|
||||||
## Copyright
|
## Copyright
|
||||||
|
|
||||||
@@ -1393,4 +1568,4 @@ Special thanks to my friends in the community of Iranian Embedded Engineers in T
|
|||||||
|
|
||||||
If you think that this roadmap can be improved in anyway or you know about some good learning resources that can be added here, please start an issue or a pull request. I’ll be maintaining and updating this repository frequently.
|
If you think that this roadmap can be improved in anyway or you know about some good learning resources that can be added here, please start an issue or a pull request. I’ll be maintaining and updating this repository frequently.
|
||||||
|
|
||||||
The source file is created using [Microsoft Visio](https://office.com/visio) in `.vsdx` format and included in this repository for your contributions. If you do not have Microsoft Visio or you want to use free software, you can use [draw.io](https://draw.io) which can import and export `.vsdx` files.
|
The roadmap image and PDF are generated from this repository. Topics, descriptions and learning resources come from this README; where each topic sits on the map and how important it is come from [`site/map.json`](site/map.json). Edit those files and the interactive explorer in [`site/`](site) renders the PNG and PDF automatically.
|
||||||
|
|||||||
+668
@@ -0,0 +1,668 @@
|
|||||||
|
import { parseRoadmap, renderInline, TYPES } from './parser.js';
|
||||||
|
import { linkDiagram } from './diagram.js';
|
||||||
|
import { createMapView } from './mapview.js';
|
||||||
|
import { CREDITS, IMPORTANCE, areaDots, areaLabel, h, plural } from './dom.js';
|
||||||
|
|
||||||
|
const SOURCES = [
|
||||||
|
'README.md',
|
||||||
|
'../README.md',
|
||||||
|
'https://raw.githubusercontent.com/m3y54m/Embedded-Engineering-Roadmap/master/README.md',
|
||||||
|
];
|
||||||
|
const REPO_URL = 'https://github.com/m3y54m/Embedded-Engineering-Roadmap';
|
||||||
|
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
|
||||||
|
const narrowScreen = window.matchMedia('(max-width: 960px)');
|
||||||
|
|
||||||
|
const els = {
|
||||||
|
diagram: document.getElementById('diagram'),
|
||||||
|
outline: document.getElementById('outline'),
|
||||||
|
panel: document.getElementById('panel'),
|
||||||
|
status: document.getElementById('status'),
|
||||||
|
search: document.getElementById('search'),
|
||||||
|
results: document.getElementById('search-results'),
|
||||||
|
theme: document.getElementById('theme'),
|
||||||
|
tabs: [...document.querySelectorAll('[data-view]')],
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
data: null,
|
||||||
|
selected: null,
|
||||||
|
includeSub: null,
|
||||||
|
filters: { types: new Set(), beginner: false, gem: false },
|
||||||
|
results: [],
|
||||||
|
active: -1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------- small helpers ----------
|
||||||
|
|
||||||
|
const normalize = (text) => text.normalize('NFKD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
|
||||||
|
|
||||||
|
function* descendants(node) {
|
||||||
|
yield node;
|
||||||
|
for (const child of node.children) yield* descendants(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
function store(key, value) {
|
||||||
|
try {
|
||||||
|
if (value === undefined) return localStorage.getItem(key);
|
||||||
|
localStorage.setItem(key, value);
|
||||||
|
} catch {
|
||||||
|
// Storage can be unavailable (private mode, blocked cookies); preferences just won't persist.
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- colors ----------
|
||||||
|
|
||||||
|
function assignHues(data) {
|
||||||
|
const [first, ...rest] = data.groups;
|
||||||
|
const start = rest.length ? first.topics : [];
|
||||||
|
const learning = rest.length ? rest.flatMap((g) => g.topics) : data.root.children;
|
||||||
|
start.forEach((t, i) => { t.hue = 168 + (start.length > 1 ? (i * 40) / (start.length - 1) : 0); });
|
||||||
|
learning.forEach((t, i) => { t.hue = (214 + (i * 300) / Math.max(1, learning.length)) % 360; });
|
||||||
|
data.root.children.forEach((top, index) => {
|
||||||
|
top.index = index;
|
||||||
|
for (const n of descendants(top)) n.top = top;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function colorOf(node) {
|
||||||
|
if (!node.top) return 'var(--accent)';
|
||||||
|
const dark = document.documentElement.dataset.theme !== 'light';
|
||||||
|
const step = Math.min(node.depth - 1, 3);
|
||||||
|
const alt = node.top.index % 2;
|
||||||
|
const light = dark ? [54, 44, 37, 32][step] + alt * 7 : [62, 73, 81, 87][step] - alt * 6;
|
||||||
|
const sat = dark ? [70, 56, 46, 40][step] : [72, 64, 56, 50][step];
|
||||||
|
return `hsl(${node.top.hue.toFixed(1)} ${sat}% ${light}%)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function swatch(node) {
|
||||||
|
const el = h('span', { class: 'swatch', 'aria-hidden': 'true' });
|
||||||
|
el.style.background = colorOf(node);
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
function truncate(text, max) {
|
||||||
|
if (text.length <= max) return text;
|
||||||
|
return max < 4 ? '' : `${text.slice(0, max - 1).trimEnd()}…`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- navigation ----------
|
||||||
|
|
||||||
|
function navigate(node, { push = true, resource = null, reveal = false } = {}) {
|
||||||
|
if (state.selected !== node) state.includeSub = null;
|
||||||
|
state.selected = node;
|
||||||
|
if (resource) resetFilters(false);
|
||||||
|
if (state.mapView) state.mapView.select(node === state.data.root ? null : node);
|
||||||
|
renderPanel(node);
|
||||||
|
syncOutline();
|
||||||
|
if (resource) revealResource(resource);
|
||||||
|
const root = state.data.root;
|
||||||
|
document.title = node === root ? 'Embedded Engineering Roadmap · Explorer' : `${node.title} · Embedded Engineering Roadmap`;
|
||||||
|
if (push) {
|
||||||
|
const hash = node === root ? '#/' : `#/${node.slug}`;
|
||||||
|
if (location.hash !== hash) history.pushState(null, '', hash);
|
||||||
|
}
|
||||||
|
if (reveal && narrowScreen.matches) openSheet(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function routeFromHash() {
|
||||||
|
const slug = decodeURIComponent(location.hash.replace(/^#\/?/, ''));
|
||||||
|
navigate((slug && state.data.bySlug.get(slug)) || state.data.root, { push: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- details panel ----------
|
||||||
|
|
||||||
|
function section(title, ...content) {
|
||||||
|
return h('section', { class: 'panel-section' }, title ? h('h3', {}, title) : null, ...content);
|
||||||
|
}
|
||||||
|
|
||||||
|
function crumbs(node) {
|
||||||
|
const chain = [];
|
||||||
|
for (let n = node.parent; n; n = n.parent) chain.unshift(n);
|
||||||
|
if (!chain.length) return null;
|
||||||
|
const root = state.data.root;
|
||||||
|
return h('nav', { class: 'crumbs', 'aria-label': 'Topic path' }, chain.flatMap((n, i) => [
|
||||||
|
i ? h('span', { 'aria-hidden': 'true' }, '›') : null,
|
||||||
|
h('button', { type: 'button', onclick: () => navigate(n) }, n === root ? 'Overview' : n.title),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
function description(blocks) {
|
||||||
|
const wrap = h('div', { class: 'desc' });
|
||||||
|
for (const block of blocks) {
|
||||||
|
if (block.type === 'p') {
|
||||||
|
const p = h('p');
|
||||||
|
p.innerHTML = renderInline(block.text);
|
||||||
|
wrap.append(p);
|
||||||
|
} else {
|
||||||
|
const ul = h('ul');
|
||||||
|
for (const item of block.items) {
|
||||||
|
const li = h('li');
|
||||||
|
li.innerHTML = renderInline(item);
|
||||||
|
ul.append(li);
|
||||||
|
}
|
||||||
|
wrap.append(ul);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
function topicChips(nodes) {
|
||||||
|
return h('div', { class: 'chips' }, nodes.map((n) => h('button', {
|
||||||
|
type: 'button',
|
||||||
|
class: 'chip-btn',
|
||||||
|
onclick: () => navigate(n, { reveal: true }),
|
||||||
|
}, swatch(n), n.title, h('span', { class: 'n' }, n.total))));
|
||||||
|
}
|
||||||
|
|
||||||
|
function related(node) {
|
||||||
|
const items = [...node.links]
|
||||||
|
.sort((a, b) => b.weight - a.weight)
|
||||||
|
.map((link) => {
|
||||||
|
const other = link.source === node ? link.target : link.source;
|
||||||
|
const reasons = [];
|
||||||
|
if (link.shared.length) reasons.push(plural(link.shared.length, 'shared resource'));
|
||||||
|
const outgoing = link.mentions.filter((m) => m.topic === node);
|
||||||
|
const incoming = link.mentions.filter((m) => m.topic !== node);
|
||||||
|
if (outgoing.length) reasons.push(`mentioned here: “${truncate(outgoing[0].text, 60)}”`);
|
||||||
|
else if (incoming.length) reasons.push(`mentions this topic: “${truncate(incoming[0].text, 60)}”`);
|
||||||
|
const title = [...link.shared.map((s) => `Shared: ${s}`), ...link.mentions.map((m) => `${m.topic.title}: ${m.text}`)].join('\n');
|
||||||
|
return h('li', {}, h('button', { type: 'button', title, onclick: () => navigate(other, { reveal: true }) },
|
||||||
|
swatch(other),
|
||||||
|
h('span', {}, other.title),
|
||||||
|
h('span', { class: 'why' }, reasons.join(' · '))));
|
||||||
|
});
|
||||||
|
return h('ul', { class: 'related' }, items);
|
||||||
|
}
|
||||||
|
|
||||||
|
function passes(r) {
|
||||||
|
const f = state.filters;
|
||||||
|
return (!f.types.size || f.types.has(r.type)) && (!f.beginner || r.beginner) && (!f.gem || r.gem);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetFilters(rerender = true) {
|
||||||
|
state.filters = { types: new Set(), beginner: false, gem: false };
|
||||||
|
if (rerender) rerenderPanel();
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterBar(entries) {
|
||||||
|
const f = state.filters;
|
||||||
|
const present = Object.keys(TYPES).filter((type) => entries.some((r) => r.type === type));
|
||||||
|
const button = (label, pressed, onclick) => h('button', { type: 'button', class: 'filter', 'aria-pressed': String(pressed), onclick }, label);
|
||||||
|
const active = f.types.size || f.beginner || f.gem;
|
||||||
|
const hasBeginner = entries.some((r) => r.beginner);
|
||||||
|
const hasGem = entries.some((r) => r.gem);
|
||||||
|
if (present.length < 2 && !hasBeginner && !hasGem && !active) return null;
|
||||||
|
return h('div', { class: 'filters', role: 'group', 'aria-label': 'Filter resources' },
|
||||||
|
present.length > 1 ? present.map((type) => button(`${TYPES[type].icon} ${TYPES[type].label}`, f.types.has(type), () => {
|
||||||
|
if (f.types.has(type)) f.types.delete(type);
|
||||||
|
else f.types.add(type);
|
||||||
|
rerenderPanel();
|
||||||
|
})) : null,
|
||||||
|
hasBeginner ? button('👶 Beginner', f.beginner, () => { f.beginner = !f.beginner; rerenderPanel(); }) : null,
|
||||||
|
hasGem ? button('💎 Essential', f.gem, () => { f.gem = !f.gem; rerenderPanel(); }) : null,
|
||||||
|
active ? h('button', { type: 'button', class: 'filter reset', onclick: () => resetFilters() }, 'Clear filters') : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resourceItem(r) {
|
||||||
|
const type = TYPES[r.type];
|
||||||
|
return h('li', { class: 'res', 'data-url': r.url },
|
||||||
|
h('span', { class: 'res-icon', title: type.label, 'aria-label': type.label, role: 'img' }, type.icon),
|
||||||
|
h('div', { class: 'res-body' },
|
||||||
|
h('a', { class: 'res-title', href: r.url, target: '_blank', rel: 'noopener noreferrer' }, r.title),
|
||||||
|
h('div', { class: 'res-meta' },
|
||||||
|
h('span', {}, r.domain),
|
||||||
|
r.beginner ? h('span', { class: 'badge beginner' }, 'Beginner') : null,
|
||||||
|
r.gem ? h('span', { class: 'badge gem' }, 'Essential') : null),
|
||||||
|
r.note ? h('p', { class: 'res-note' }, r.note) : null,
|
||||||
|
r.sharedWith.length ? h('div', { class: 'res-also' }, 'Also listed in ', r.sharedWith.flatMap((t, i) => [
|
||||||
|
i ? ', ' : null,
|
||||||
|
h('button', { type: 'button', class: 'link-btn', onclick: () => navigate(t, { reveal: true }) }, t.title),
|
||||||
|
])) : null));
|
||||||
|
}
|
||||||
|
|
||||||
|
function resources(node) {
|
||||||
|
const hasSub = node.children.length > 0 && node.total > node.resources.length;
|
||||||
|
const include = hasSub && (state.includeSub ?? node.resources.length === 0);
|
||||||
|
const groups = include
|
||||||
|
? [...descendants(node)].filter((n) => n.resources.length).map((n) => ({ node: n, list: n.resources }))
|
||||||
|
: [{ node, list: node.resources }];
|
||||||
|
const all = groups.flatMap((g) => g.list);
|
||||||
|
const shown = all.filter(passes).length;
|
||||||
|
|
||||||
|
const sec = h('section', { class: 'panel-section' },
|
||||||
|
h('div', { class: 'res-head' },
|
||||||
|
h('h3', {}, 'Resources'),
|
||||||
|
h('span', { class: 'count' }, shown === all.length ? plural(all.length, 'item') : `${shown} of ${all.length}`)));
|
||||||
|
if (hasSub) {
|
||||||
|
const box = h('input', { type: 'checkbox', checked: include });
|
||||||
|
box.addEventListener('change', () => {
|
||||||
|
state.includeSub = box.checked;
|
||||||
|
rerenderPanel();
|
||||||
|
});
|
||||||
|
sec.append(h('label', { class: 'toggle' }, box, `Include subtopics (${plural(node.total, 'resource')})`));
|
||||||
|
}
|
||||||
|
if (!all.length) {
|
||||||
|
sec.append(h('p', { class: 'empty' }, 'This topic only groups its subtopics. Pick one above.'));
|
||||||
|
return sec;
|
||||||
|
}
|
||||||
|
const filters = filterBar(all);
|
||||||
|
if (filters) sec.append(filters);
|
||||||
|
if (!shown) {
|
||||||
|
sec.append(h('div', { class: 'empty' }, 'No resources match these filters. ',
|
||||||
|
h('button', { type: 'button', class: 'link-btn', onclick: () => resetFilters() }, 'Clear filters')));
|
||||||
|
return sec;
|
||||||
|
}
|
||||||
|
for (const group of groups) {
|
||||||
|
const list = group.list.filter(passes);
|
||||||
|
if (!list.length) continue;
|
||||||
|
if (include) {
|
||||||
|
sec.append(h('div', { class: 'res-group' },
|
||||||
|
h('button', { type: 'button', onclick: () => navigate(group.node, { reveal: true }) }, group.node.title)));
|
||||||
|
}
|
||||||
|
sec.append(h('ul', { class: 'res-list' }, list.map(resourceItem)));
|
||||||
|
}
|
||||||
|
return sec;
|
||||||
|
}
|
||||||
|
|
||||||
|
function areaSummary() {
|
||||||
|
const boxes = state.plan ? state.plan.boxes.filter((b) => !b.header) : [];
|
||||||
|
const count = (test) => boxes.filter(test).length;
|
||||||
|
const tile = (areas, n, label) => h('div', { class: 'stat' }, areaDots(areas), h('b', {}, n), h('span', {}, label));
|
||||||
|
return h('div', { class: 'stats areas' },
|
||||||
|
tile(['SOFTWARE'], count((b) => b.areas.length === 1 && b.areas[0] === 'SOFTWARE'), 'Software only'),
|
||||||
|
tile(['SOFTWARE', 'HARDWARE'], count((b) => b.areas.includes('SOFTWARE') && b.areas.includes('HARDWARE') && !b.areas.includes('SOFT SKILLS')), 'Software ∩ Hardware'),
|
||||||
|
tile(['HARDWARE'], count((b) => b.areas.length === 1 && b.areas[0] === 'HARDWARE'), 'Hardware only'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function overview(root) {
|
||||||
|
const { stats, groups } = state.data;
|
||||||
|
return [
|
||||||
|
section(null, description(root.description)),
|
||||||
|
section(null, h('div', { class: 'stats' },
|
||||||
|
h('div', { class: 'stat' }, h('b', {}, stats.topics), h('span', {}, 'topics')),
|
||||||
|
h('div', { class: 'stat' }, h('b', {}, stats.resources), h('span', {}, 'resources')),
|
||||||
|
h('div', { class: 'stat' }, h('b', {}, stats.links), h('span', {}, 'connections')))),
|
||||||
|
state.plan ? section('Diagram topics by area', areaSummary()) : null,
|
||||||
|
...groups.map((g) => section(g.title, topicChips(g.topics))),
|
||||||
|
section('How to explore', h('ul', { class: 'howto' },
|
||||||
|
h('li', {}, 'Map follows the roadmap diagram: topics sit in Software, Hardware or both, and are colored by importance.'),
|
||||||
|
h('li', {}, 'Use the Areas chips to highlight a single area or only the Software ∩ Hardware cross-section.'),
|
||||||
|
h('li', {}, 'Hover or select a topic to see lines to the topics it shares resources with (solid) or is mentioned by (dotted).'),
|
||||||
|
h('li', {}, 'Outline lists every topic of the README; use it to browse topics that are not on the map.'),
|
||||||
|
h('li', {}, 'Press ', h('kbd', {}, '/'), ' to search topics and resources.'))),
|
||||||
|
section(null, h('p', { class: 'res-note' }, 'Built from the ',
|
||||||
|
h('a', { href: `${REPO_URL}#readme`, target: '_blank', rel: 'noopener noreferrer' }, 'roadmap README'),
|
||||||
|
' and the map definition in the repository.')),
|
||||||
|
section('Credits', credits()),
|
||||||
|
].filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
function credits() {
|
||||||
|
const external = (href, ...children) => h('a', { href, target: '_blank', rel: 'noopener noreferrer' }, ...children);
|
||||||
|
const build = state.build;
|
||||||
|
return h('div', { class: 'credits' },
|
||||||
|
h('img', { class: 'credits-logo', src: 'assets/logo.svg', alt: '', width: 48, height: 48 }),
|
||||||
|
h('div', { class: 'credits-body' },
|
||||||
|
h('p', {}, 'Creator & maintainer: ', external(CREDITS.authorUrl, h('strong', {}, CREDITS.author))),
|
||||||
|
build ? h('p', {}, 'Revision ', h('strong', {}, build.version), ` · last update ${build.date}`) : null,
|
||||||
|
h('p', {}, 'Licensed under the ', external(CREDITS.licenseUrl, CREDITS.licenseName), '.'),
|
||||||
|
external(CREDITS.licenseUrl, h('img', { class: 'credits-badge', src: 'assets/cc-by-sa.svg', alt: CREDITS.license, width: 88, height: 31 }))));
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPanel(node) {
|
||||||
|
const root = state.data.root;
|
||||||
|
const panel = els.panel;
|
||||||
|
panel.replaceChildren();
|
||||||
|
panel.append(h('header', { class: 'panel-head' },
|
||||||
|
h('button', { type: 'button', class: 'icon-btn sheet-close', 'aria-label': 'Close details', onclick: () => openSheet(false) }, '✕'),
|
||||||
|
crumbs(node),
|
||||||
|
h('h2', { class: 'panel-title' },
|
||||||
|
node.icon ? h('span', { class: 'title-icon', 'aria-hidden': 'true' }, node.icon) : node === root ? null : swatch(node),
|
||||||
|
node === root ? 'Embedded Engineering Roadmap' : node.title),
|
||||||
|
node === root ? null : h('div', { class: 'meta' },
|
||||||
|
node.areas && node.areas.length ? h('span', { class: 'pill area-pill' }, areaDots(node.areas), areaLabel(node.areas)) : null,
|
||||||
|
node.importance ? h('span', { class: `pill imp-pill ${node.importance}` }, IMPORTANCE[node.importance]) : null,
|
||||||
|
h('span', { class: 'pill' }, plural(node.total, 'resource')),
|
||||||
|
node.children.length ? h('span', { class: 'pill' }, plural(node.children.length, 'subtopic')) : null,
|
||||||
|
node.links.length ? h('span', { class: 'pill' }, `${node.links.length} connected`) : null)));
|
||||||
|
|
||||||
|
if (node === root) {
|
||||||
|
panel.append(...overview(root));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (node.description.length) panel.append(section('About', description(node.description)));
|
||||||
|
if (node.children.length) panel.append(section('Subtopics', topicChips(node.children)));
|
||||||
|
if (node.links.length) panel.append(section('Connected topics', related(node)));
|
||||||
|
panel.append(resources(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
function rerenderPanel() {
|
||||||
|
const top = els.panel.scrollTop;
|
||||||
|
renderPanel(state.selected);
|
||||||
|
els.panel.scrollTop = top;
|
||||||
|
}
|
||||||
|
|
||||||
|
function revealResource(resource) {
|
||||||
|
const item = [...els.panel.querySelectorAll('.res')].find((li) => li.dataset.url === resource.url);
|
||||||
|
if (!item) return;
|
||||||
|
item.scrollIntoView({ block: 'center', behavior: reducedMotion.matches ? 'auto' : 'smooth' });
|
||||||
|
item.classList.remove('flash');
|
||||||
|
void item.offsetWidth;
|
||||||
|
item.classList.add('flash');
|
||||||
|
}
|
||||||
|
|
||||||
|
function openSheet(open) {
|
||||||
|
els.panel.classList.toggle('open', open);
|
||||||
|
if (open) els.panel.focus({ preventScroll: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- outline view ----------
|
||||||
|
|
||||||
|
function buildOutline() {
|
||||||
|
const { groups, stats } = state.data;
|
||||||
|
const item = (node) => {
|
||||||
|
const button = h('button', {
|
||||||
|
type: 'button',
|
||||||
|
class: 'tree-btn',
|
||||||
|
'data-id': node.id,
|
||||||
|
onclick: (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
navigate(node, { reveal: true });
|
||||||
|
},
|
||||||
|
}, swatch(node), h('span', { class: 'tree-title' }, node.title),
|
||||||
|
node.areas && node.areas.length ? areaDots(node.areas) : null,
|
||||||
|
h('span', { class: 'tree-count' }, node.total));
|
||||||
|
if (!node.children.length) return h('li', {}, button);
|
||||||
|
const details = h('details', {}, h('summary', {}, button), h('ul', { class: 'tree' }, node.children.map(item)));
|
||||||
|
return h('li', {}, details);
|
||||||
|
};
|
||||||
|
els.outline.replaceChildren(
|
||||||
|
h('h2', {}, 'All topics'),
|
||||||
|
h('p', { class: 'res-note' }, `${stats.topics} topics and ${stats.resources} resources. Select a topic to see its details; use the arrows to expand.`),
|
||||||
|
...groups.map((g) => h('section', { class: 'outline-group' }, h('h3', {}, g.title), h('ul', { class: 'tree' }, g.topics.map(item)))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncOutline() {
|
||||||
|
for (const btn of els.outline.querySelectorAll('.tree-btn[aria-current]')) btn.removeAttribute('aria-current');
|
||||||
|
const btn = state.selected && els.outline.querySelector(`.tree-btn[data-id="${CSS.escape(state.selected.id)}"]`);
|
||||||
|
if (!btn) return;
|
||||||
|
btn.setAttribute('aria-current', 'true');
|
||||||
|
for (let el = btn.parentElement; el && el !== els.outline; el = el.parentElement) {
|
||||||
|
if (el.tagName === 'DETAILS') el.open = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const VIEWS = ['map', 'list'];
|
||||||
|
|
||||||
|
function setView(view) {
|
||||||
|
if (!VIEWS.includes(view) || (view === 'map' && !state.mapView)) view = state.mapView ? 'map' : 'list';
|
||||||
|
els.diagram.hidden = view !== 'map';
|
||||||
|
els.outline.hidden = view !== 'list';
|
||||||
|
for (const tab of els.tabs) {
|
||||||
|
tab.setAttribute('aria-selected', String(tab.dataset.view === view));
|
||||||
|
if (tab.dataset.view === 'map') tab.hidden = !state.mapView;
|
||||||
|
}
|
||||||
|
store('roadmap-view', view);
|
||||||
|
if (view === 'map') state.mapView.show();
|
||||||
|
if (view === 'list') {
|
||||||
|
syncOutline();
|
||||||
|
const current = els.outline.querySelector('.tree-btn[aria-current]');
|
||||||
|
if (current) current.scrollIntoView({ block: 'center' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- search ----------
|
||||||
|
|
||||||
|
function buildIndex() {
|
||||||
|
state.index = {
|
||||||
|
topics: state.data.topics.map((node) => ({
|
||||||
|
node,
|
||||||
|
title: normalize(node.title),
|
||||||
|
text: normalize([node.title, ...node.path].join(' ')),
|
||||||
|
})),
|
||||||
|
resources: state.data.topics.flatMap((node) => node.resources.map((r) => ({
|
||||||
|
node,
|
||||||
|
r,
|
||||||
|
text: normalize(`${r.title} ${r.domain}`),
|
||||||
|
}))),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function runSearch(query) {
|
||||||
|
const tokens = normalize(query).split(/\s+/).filter(Boolean);
|
||||||
|
if (!tokens.length) return [];
|
||||||
|
const match = (entry) => tokens.every((t) => entry.text.includes(t));
|
||||||
|
const topics = state.index.topics
|
||||||
|
.filter(match)
|
||||||
|
.map((e) => ({ ...e, score: e.title.startsWith(tokens[0]) ? 0 : e.title.includes(tokens[0]) ? 1 : 2 }))
|
||||||
|
.sort((a, b) => a.score - b.score || a.node.depth - b.node.depth)
|
||||||
|
.slice(0, 6)
|
||||||
|
.map((e) => ({ kind: 'topic', node: e.node }));
|
||||||
|
const seen = new Set();
|
||||||
|
const resourceHits = state.index.resources
|
||||||
|
.filter((e) => match(e) && !seen.has(e.r.url) && seen.add(e.r.url))
|
||||||
|
.slice(0, 8)
|
||||||
|
.map((e) => ({ kind: 'resource', node: e.node, r: e.r }));
|
||||||
|
return [...topics, ...resourceHits];
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderResults() {
|
||||||
|
const box = els.results;
|
||||||
|
const query = els.search.value.trim();
|
||||||
|
box.replaceChildren();
|
||||||
|
if (!query) {
|
||||||
|
closeResults();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const results = state.results;
|
||||||
|
if (!results.length) {
|
||||||
|
box.append(h('div', { class: 'result-empty' }, `No topics or resources match “${query}”.`));
|
||||||
|
}
|
||||||
|
let lastKind = '';
|
||||||
|
results.forEach((res, i) => {
|
||||||
|
if (res.kind !== lastKind) {
|
||||||
|
box.append(h('div', { class: 'result-group', role: 'presentation' }, res.kind === 'topic' ? 'Topics' : 'Resources'));
|
||||||
|
lastKind = res.kind;
|
||||||
|
}
|
||||||
|
const isTopic = res.kind === 'topic';
|
||||||
|
const option = h('div', {
|
||||||
|
id: `result-${i}`,
|
||||||
|
class: 'result',
|
||||||
|
role: 'option',
|
||||||
|
'aria-selected': String(i === state.active),
|
||||||
|
},
|
||||||
|
isTopic ? swatch(res.node) : h('span', { 'aria-hidden': 'true' }, TYPES[res.r.type].icon),
|
||||||
|
h('div', { class: 'result-main' },
|
||||||
|
h('div', { class: 'result-title' }, isTopic ? res.node.title : res.r.title, isTopic && res.node.areas && res.node.areas.length ? areaDots(res.node.areas) : null),
|
||||||
|
h('div', { class: 'result-sub' }, isTopic
|
||||||
|
? (res.node.path.length ? res.node.path.join(' › ') : plural(res.node.total, 'resource'))
|
||||||
|
: `${res.r.domain} · ${res.node.title}`)));
|
||||||
|
option.addEventListener('pointerdown', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
choose(i);
|
||||||
|
});
|
||||||
|
box.append(option);
|
||||||
|
});
|
||||||
|
box.hidden = false;
|
||||||
|
els.search.setAttribute('aria-expanded', 'true');
|
||||||
|
els.search.setAttribute('aria-activedescendant', state.active >= 0 ? `result-${state.active}` : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeResults() {
|
||||||
|
els.results.hidden = true;
|
||||||
|
els.search.setAttribute('aria-expanded', 'false');
|
||||||
|
els.search.removeAttribute('aria-activedescendant');
|
||||||
|
state.active = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function choose(index) {
|
||||||
|
const res = state.results[index];
|
||||||
|
if (!res) return;
|
||||||
|
closeResults();
|
||||||
|
els.search.value = '';
|
||||||
|
els.search.blur();
|
||||||
|
if (res.kind === 'topic') navigate(res.node, { reveal: true });
|
||||||
|
else navigate(res.node, { resource: res.r, reveal: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function wireSearch() {
|
||||||
|
els.search.addEventListener('input', () => {
|
||||||
|
state.results = runSearch(els.search.value);
|
||||||
|
state.active = state.results.length ? 0 : -1;
|
||||||
|
renderResults();
|
||||||
|
});
|
||||||
|
els.search.addEventListener('keydown', (e) => {
|
||||||
|
const count = state.results.length;
|
||||||
|
if (e.key === 'ArrowDown' && count) {
|
||||||
|
e.preventDefault();
|
||||||
|
state.active = (state.active + 1) % count;
|
||||||
|
renderResults();
|
||||||
|
document.getElementById(`result-${state.active}`)?.scrollIntoView({ block: 'nearest' });
|
||||||
|
} else if (e.key === 'ArrowUp' && count) {
|
||||||
|
e.preventDefault();
|
||||||
|
state.active = (state.active - 1 + count) % count;
|
||||||
|
renderResults();
|
||||||
|
document.getElementById(`result-${state.active}`)?.scrollIntoView({ block: 'nearest' });
|
||||||
|
} else if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
choose(state.active >= 0 ? state.active : 0);
|
||||||
|
} else if (e.key === 'Escape') {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (els.search.value) {
|
||||||
|
els.search.value = '';
|
||||||
|
closeResults();
|
||||||
|
} else {
|
||||||
|
els.search.blur();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
els.search.addEventListener('focus', () => {
|
||||||
|
if (els.search.value.trim()) renderResults();
|
||||||
|
});
|
||||||
|
els.search.addEventListener('blur', () => window.setTimeout(closeResults, 120));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- theme, loading, startup ----------
|
||||||
|
|
||||||
|
function applyTheme(theme) {
|
||||||
|
document.documentElement.dataset.theme = theme;
|
||||||
|
if (!state.data) return;
|
||||||
|
buildOutline();
|
||||||
|
syncOutline();
|
||||||
|
rerenderPanel();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadMarkdown() {
|
||||||
|
for (const source of SOURCES) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(source, { cache: 'no-cache' });
|
||||||
|
if (!response.ok) continue;
|
||||||
|
const text = await response.text();
|
||||||
|
if (text.includes('# Embedded Systems Engineering Roadmap')) return text;
|
||||||
|
} catch {
|
||||||
|
// Try the next source.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error('README.md could not be loaded');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadMap() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('map.json', { cache: 'no-cache' });
|
||||||
|
return response.ok ? await response.json() : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Revision and date, written by CI next to the deployed site; absent in local previews.
|
||||||
|
async function loadBuild() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('build.json', { cache: 'no-cache' });
|
||||||
|
return response.ok ? await response.json() : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTyping(el) {
|
||||||
|
return el instanceof HTMLElement && (el.isContentEditable || ['INPUT', 'TEXTAREA', 'SELECT'].includes(el.tagName));
|
||||||
|
}
|
||||||
|
|
||||||
|
function wireGlobal() {
|
||||||
|
els.theme.addEventListener('click', () => {
|
||||||
|
const next = document.documentElement.dataset.theme === 'light' ? 'dark' : 'light';
|
||||||
|
store('roadmap-theme', next);
|
||||||
|
applyTheme(next);
|
||||||
|
});
|
||||||
|
for (const tab of els.tabs) tab.addEventListener('click', () => setView(tab.dataset.view));
|
||||||
|
document.getElementById('print').addEventListener('click', () => window.print());
|
||||||
|
// The PDF and PNG are rendered by CI next to the deployed site; local previews don't have them.
|
||||||
|
for (const link of document.querySelectorAll('[data-download]')) {
|
||||||
|
fetch(link.getAttribute('href'), { method: 'HEAD', cache: 'no-cache' })
|
||||||
|
.then((response) => { link.hidden = !response.ok; }, () => {});
|
||||||
|
}
|
||||||
|
// Printing always produces the diagram, whatever view is open.
|
||||||
|
let viewBeforePrint = null;
|
||||||
|
window.addEventListener('beforeprint', () => {
|
||||||
|
if (!state.mapView) return;
|
||||||
|
viewBeforePrint = store('roadmap-view');
|
||||||
|
setView('map');
|
||||||
|
state.mapView.prepareForPrint();
|
||||||
|
});
|
||||||
|
window.addEventListener('afterprint', () => {
|
||||||
|
if (state.mapView) state.mapView.afterPrint();
|
||||||
|
if (viewBeforePrint && viewBeforePrint !== 'map') setView(viewBeforePrint);
|
||||||
|
viewBeforePrint = null;
|
||||||
|
});
|
||||||
|
window.addEventListener('popstate', () => routeFromHash());
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (isTyping(e.target) || e.metaKey || e.ctrlKey || e.altKey) return;
|
||||||
|
if (e.key === '/') {
|
||||||
|
e.preventDefault();
|
||||||
|
els.search.focus();
|
||||||
|
} else if (e.key === 'Escape') {
|
||||||
|
if (els.panel.classList.contains('open')) openSheet(false);
|
||||||
|
else if (state.selected && state.selected.parent) navigate(state.selected.parent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
applyTheme(store('roadmap-theme') || (window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark'));
|
||||||
|
let markdown;
|
||||||
|
const diagramRequest = loadMap();
|
||||||
|
const buildRequest = loadBuild();
|
||||||
|
try {
|
||||||
|
markdown = await loadMarkdown();
|
||||||
|
} catch {
|
||||||
|
els.status.replaceChildren('The roadmap could not be loaded. ',
|
||||||
|
h('a', { href: `${REPO_URL}#readme` }, 'Read it on GitHub instead'), '.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.data = parseRoadmap(markdown);
|
||||||
|
assignHues(state.data);
|
||||||
|
const diagram = await diagramRequest;
|
||||||
|
state.build = await buildRequest;
|
||||||
|
if (diagram) {
|
||||||
|
state.plan = linkDiagram(diagram, state.data);
|
||||||
|
state.mapView = createMapView({
|
||||||
|
root: els.diagram,
|
||||||
|
plan: state.plan,
|
||||||
|
data: state.data,
|
||||||
|
build: state.build,
|
||||||
|
onSelect: (topic) => navigate(topic, { reveal: true }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
buildOutline();
|
||||||
|
buildIndex();
|
||||||
|
wireSearch();
|
||||||
|
wireGlobal();
|
||||||
|
els.status.hidden = true;
|
||||||
|
routeFromHash();
|
||||||
|
setView(store('roadmap-view') || 'map');
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://web.resource.org/cc/"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="120"
|
||||||
|
height="42"
|
||||||
|
id="svg2759"
|
||||||
|
sodipodi:version="0.32"
|
||||||
|
inkscape:version="0.45+devel"
|
||||||
|
version="1.0"
|
||||||
|
sodipodi:docname="by-sa.svg"
|
||||||
|
inkscape:output_extension="org.inkscape.output.svg.inkscape">
|
||||||
|
<defs
|
||||||
|
id="defs2761" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#8b8b8b"
|
||||||
|
borderopacity="1"
|
||||||
|
gridtolerance="10000"
|
||||||
|
guidetolerance="10"
|
||||||
|
objecttolerance="10"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="1"
|
||||||
|
inkscape:cx="179"
|
||||||
|
inkscape:cy="89.569904"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
width="120px"
|
||||||
|
height="42px"
|
||||||
|
inkscape:showpageshadow="false"
|
||||||
|
inkscape:window-width="1198"
|
||||||
|
inkscape:window-height="624"
|
||||||
|
inkscape:window-x="488"
|
||||||
|
inkscape:window-y="401" />
|
||||||
|
<metadata
|
||||||
|
id="metadata2764">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<g
|
||||||
|
transform="matrix(0.9937807,0,0,0.9936694,-177.69409,-74.436409)"
|
||||||
|
id="g287"
|
||||||
|
inkscape:export-filename="/mnt/hgfs/Bov/Documents/Work/2007/cc/identity/srr buttons/big/by-sa.png"
|
||||||
|
inkscape:export-xdpi="300.23013"
|
||||||
|
inkscape:export-ydpi="300.23013">
|
||||||
|
<path
|
||||||
|
id="path3817_2_"
|
||||||
|
nodetypes="ccccccc"
|
||||||
|
d="M 182.23532,75.39014 L 296.29928,75.59326 C 297.89303,75.59326 299.31686,75.35644 299.31686,78.77344 L 299.17721,116.34033 L 179.3569,116.34033 L 179.3569,78.63379 C 179.3569,76.94922 179.51999,75.39014 182.23532,75.39014 z"
|
||||||
|
style="fill:#aab2ab" />
|
||||||
|
|
||||||
|
<g
|
||||||
|
id="g5908_2_"
|
||||||
|
transform="matrix(0.872921,0,0,0.872921,50.12536,143.2144)">
|
||||||
|
|
||||||
|
<path
|
||||||
|
id="path5906_2_"
|
||||||
|
cx="296.35416"
|
||||||
|
ry="22.939548"
|
||||||
|
cy="264.3577"
|
||||||
|
type="arc"
|
||||||
|
rx="22.939548"
|
||||||
|
d="M 187.20944,-55.6792 C 187.21502,-46.99896 180.18158,-39.95825 171.50134,-39.95212 C 162.82113,-39.94708 155.77929,-46.97998 155.77426,-55.66016 C 155.77426,-55.66687 155.77426,-55.67249 155.77426,-55.6792 C 155.76922,-64.36054 162.80209,-71.40125 171.48233,-71.40631 C 180.16367,-71.41193 187.20441,-64.37842 187.20944,-55.69824 C 187.20944,-55.69263 187.20944,-55.68591 187.20944,-55.6792 z"
|
||||||
|
style="fill:#ffffff" />
|
||||||
|
|
||||||
|
<g
|
||||||
|
id="g5706_2_"
|
||||||
|
transform="translate(-289.6157,99.0653)">
|
||||||
|
<path
|
||||||
|
id="path5708_2_"
|
||||||
|
d="M 473.88455,-167.54724 C 477.36996,-164.06128 479.11294,-159.79333 479.11294,-154.74451 C 479.11294,-149.69513 477.40014,-145.47303 473.9746,-142.07715 C 470.33929,-138.50055 466.04281,-136.71283 461.08513,-136.71283 C 456.18736,-136.71283 451.96526,-138.48544 448.42003,-142.03238 C 444.87419,-145.57819 443.10158,-149.81537 443.10158,-154.74451 C 443.10158,-159.6731 444.87419,-163.94049 448.42003,-167.54724 C 451.87523,-171.03375 456.09728,-172.77618 461.08513,-172.77618 C 466.13342,-172.77618 470.39914,-171.03375 473.88455,-167.54724 z M 450.76657,-165.20239 C 447.81982,-162.22601 446.34701,-158.7395 446.34701,-154.74005 C 446.34701,-150.7417 447.80529,-147.28485 450.72125,-144.36938 C 453.63778,-141.45288 457.10974,-139.99462 461.1383,-139.99462 C 465.16683,-139.99462 468.66848,-141.46743 471.64486,-144.41363 C 474.47076,-147.14947 475.88427,-150.59069 475.88427,-154.74005 C 475.88427,-158.85809 474.44781,-162.35297 471.57659,-165.22479 C 468.70595,-168.09546 465.22671,-169.53131 461.1383,-169.53131 C 457.04993,-169.53131 453.59192,-168.08813 450.76657,-165.20239 z M 458.52106,-156.49927 C 458.07074,-157.4809 457.39673,-157.9715 456.49781,-157.9715 C 454.90867,-157.9715 454.11439,-156.90198 454.11439,-154.763 C 454.11439,-152.62341 454.90867,-151.55389 456.49781,-151.55389 C 457.54719,-151.55389 458.29676,-152.07519 458.74647,-153.11901 L 460.94923,-151.94598 C 459.8993,-150.0805 458.32417,-149.14697 456.22374,-149.14697 C 454.60384,-149.14697 453.30611,-149.64367 452.33168,-150.63653 C 451.35561,-151.62994 450.86894,-152.99926 450.86894,-154.7445 C 450.86894,-156.46008 451.37123,-157.82159 452.37642,-158.83013 C 453.38161,-159.83806 454.63347,-160.34264 456.13423,-160.34264 C 458.35435,-160.34264 459.94407,-159.46776 460.90504,-157.71978 L 458.52106,-156.49927 z M 468.8844,-156.49927 C 468.43353,-157.4809 467.77292,-157.9715 466.90201,-157.9715 C 465.28095,-157.9715 464.46988,-156.90198 464.46988,-154.763 C 464.46988,-152.62341 465.28095,-151.55389 466.90201,-151.55389 C 467.95304,-151.55389 468.68918,-152.07519 469.10925,-153.11901 L 471.36126,-151.94598 C 470.31301,-150.0805 468.74007,-149.14697 466.64358,-149.14697 C 465.02587,-149.14697 463.73095,-149.64367 462.75711,-150.63653 C 461.78494,-151.62994 461.29773,-152.99926 461.29773,-154.7445 C 461.29773,-156.46008 461.79221,-157.82159 462.78061,-158.83013 C 463.76843,-159.83806 465.02588,-160.34264 466.55408,-160.34264 C 468.77027,-160.34264 470.35776,-159.46776 471.3154,-157.71978 L 468.8844,-156.49927 z" />
|
||||||
|
|
||||||
|
</g>
|
||||||
|
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<path
|
||||||
|
d="M 297.29639,74.91064 L 181.06688,74.91064 C 179.8203,74.91064 178.80614,75.92529 178.80614,77.17187 L 178.80614,116.66748 C 178.80614,116.94922 179.03466,117.17822 179.31639,117.17822 L 299.04639,117.17822 C 299.32813,117.17822 299.55713,116.94922 299.55713,116.66748 L 299.55713,77.17188 C 299.55713,75.92529 298.54297,74.91064 297.29639,74.91064 z M 181.06688,75.93213 L 297.29639,75.93213 C 297.97998,75.93213 298.53565,76.48828 298.53565,77.17188 C 298.53565,77.17188 298.53565,93.09131 298.53565,104.59034 L 215.4619,104.59034 C 212.41698,110.09571 206.55077,113.83399 199.81835,113.83399 C 193.083,113.83399 187.21825,110.09913 184.1748,104.59034 L 179.82666,104.59034 C 179.82666,93.09132 179.82666,77.17188 179.82666,77.17188 C 179.82664,76.48828 180.38329,75.93213 181.06688,75.93213 z"
|
||||||
|
id="path294" />
|
||||||
|
|
||||||
|
<g
|
||||||
|
enable-background="new "
|
||||||
|
id="g296">
|
||||||
|
<path
|
||||||
|
d="M 265.60986,112.8833 C 265.68994,113.03906 265.79736,113.16504 265.93115,113.26172 C 266.06494,113.35791 266.22119,113.42969 266.40088,113.47608 C 266.58154,113.52296 266.76807,113.54639 266.96045,113.54639 C 267.09033,113.54639 267.22998,113.53565 267.3794,113.51368 C 267.52784,113.4922 267.66749,113.44972 267.79835,113.3877 C 267.92823,113.32569 268.03761,113.23975 268.12355,113.13086 C 268.21144,113.02197 268.25441,112.88379 268.25441,112.71533 C 268.25441,112.53515 268.19679,112.38916 268.08156,112.27685 C 267.9673,112.16455 267.81594,112.07177 267.62941,111.99658 C 267.44386,111.92236 267.23195,111.85693 266.9966,111.80078 C 266.76027,111.74463 266.52101,111.68262 266.27883,111.61377 C 266.02981,111.55176 265.78762,111.47559 265.55129,111.38525 C 265.31594,111.29541 265.10402,111.17822 264.9175,111.03515 C 264.73098,110.89208 264.58059,110.71337 264.46535,110.49853 C 264.35109,110.28369 264.29347,110.02392 264.29347,109.71923 C 264.29347,109.37646 264.36671,109.07958 264.51222,108.82763 C 264.6587,108.57568 264.85011,108.36572 265.08644,108.19726 C 265.32179,108.02929 265.58937,107.90478 265.8882,107.82372 C 266.18605,107.74315 266.48488,107.70263 266.78273,107.70263 C 267.13136,107.70263 267.46535,107.74169 267.78566,107.81982 C 268.105,107.89746 268.39015,108.02392 268.6382,108.19824 C 268.88722,108.37256 269.08449,108.59521 269.23097,108.86621 C 269.37648,109.13721 269.44972,109.46582 269.44972,109.85156 L 268.02784,109.85156 C 268.01514,109.65234 267.97315,109.4873 267.90284,109.35693 C 267.83155,109.22607 267.73682,109.12353 267.61964,109.04834 C 267.50148,108.97412 267.36671,108.9209 267.21534,108.89014 C 267.063,108.85889 266.89796,108.84326 266.71827,108.84326 C 266.60108,108.84326 266.48292,108.85596 266.36573,108.88037 C 266.24757,108.90576 266.14112,108.94922 266.04542,109.01123 C 265.94874,109.07373 265.86964,109.15137 265.80812,109.24463 C 265.7466,109.33838 265.71535,109.45654 265.71535,109.59961 C 265.71535,109.73047 265.73976,109.83643 265.78957,109.91699 C 265.83937,109.99804 265.93801,110.07275 266.08352,110.14111 C 266.22903,110.20947 266.43118,110.27832 266.68899,110.34668 C 266.9468,110.41504 267.28372,110.50244 267.70071,110.60791 C 267.82473,110.63281 267.99661,110.67822 268.21731,110.74365 C 268.43801,110.80908 268.65676,110.91308 268.87454,111.05615 C 269.09231,111.1997 269.27981,111.39111 269.43899,111.63037 C 269.59719,111.87012 269.67629,112.17676 269.67629,112.55029 C 269.67629,112.85547 269.61672,113.13867 269.49856,113.3999 C 269.3804,113.66162 269.20461,113.8872 268.97122,114.07666 C 268.73782,114.26709 268.44876,114.41455 268.10403,114.52051 C 267.75833,114.62647 267.35794,114.6792 266.90481,114.6792 C 266.53762,114.6792 266.18118,114.63379 265.83547,114.54346 C 265.49074,114.45313 265.18508,114.31104 264.92043,114.11768 C 264.65676,113.92432 264.4468,113.67774 264.29055,113.37891 C 264.13528,113.07959 264.06106,112.7251 264.06692,112.31397 L 265.4888,112.31397 C 265.48877,112.53809 265.52881,112.72803 265.60986,112.8833 z"
|
||||||
|
id="path298"
|
||||||
|
style="fill:#ffffff" />
|
||||||
|
|
||||||
|
<path
|
||||||
|
d="M 273.8667,107.8667 L 276.35986,114.53076 L 274.8374,114.53076 L 274.33349,113.04638 L 271.84033,113.04638 L 271.31787,114.53076 L 269.84326,114.53076 L 272.36377,107.8667 L 273.8667,107.8667 z M 273.95068,111.95264 L 273.11084,109.50928 L 273.09229,109.50928 L 272.22315,111.95264 L 273.95068,111.95264 z"
|
||||||
|
id="path300"
|
||||||
|
style="fill:#ffffff" />
|
||||||
|
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g
|
||||||
|
enable-background="new "
|
||||||
|
id="g302">
|
||||||
|
<path
|
||||||
|
d="M 239.17821,107.8667 C 239.49559,107.8667 239.78563,107.89502 240.04735,107.95068 C 240.30907,108.00683 240.53368,108.09863 240.72118,108.22607 C 240.9077,108.35351 241.05321,108.52295 241.15575,108.73437 C 241.25829,108.94579 241.31005,109.20703 241.31005,109.51806 C 241.31005,109.854 241.23388,110.13329 241.08056,110.35742 C 240.92822,110.58154 240.70165,110.76465 240.40283,110.90771 C 240.81494,111.02587 241.12256,111.23291 241.32568,111.5288 C 241.5288,111.82469 241.63037,112.18114 241.63037,112.59814 C 241.63037,112.93408 241.56494,113.22509 241.43408,113.47119 C 241.30322,113.7168 241.12646,113.91748 240.90576,114.07324 C 240.68408,114.229 240.43115,114.34424 240.14795,114.41845 C 239.86377,114.49365 239.57275,114.53075 239.27295,114.53075 L 236.03662,114.53075 L 236.03662,107.86669 L 239.17821,107.86669 L 239.17821,107.8667 z M 238.99071,110.56201 C 239.25243,110.56201 239.46727,110.5 239.63622,110.37597 C 239.80419,110.25146 239.88817,110.05029 239.88817,109.77099 C 239.88817,109.61572 239.85985,109.48828 239.80419,109.38915 C 239.74755,109.28954 239.67333,109.21239 239.57958,109.15624 C 239.48583,109.10058 239.37841,109.06151 239.25731,109.04003 C 239.13524,109.01806 239.00926,109.00732 238.8784,109.00732 L 237.50535,109.00732 L 237.50535,110.56201 L 238.99071,110.56201 z M 239.07664,113.39014 C 239.22019,113.39014 239.35691,113.37647 239.48777,113.34815 C 239.61863,113.32032 239.73484,113.27344 239.83445,113.2085 C 239.93406,113.14307 240.01316,113.0542 240.07273,112.94239 C 240.1323,112.83058 240.1616,112.68751 240.1616,112.51319 C 240.1616,112.17139 240.06492,111.92725 239.87156,111.78126 C 239.6782,111.63527 239.42234,111.56202 239.10496,111.56202 L 237.50535,111.56202 L 237.50535,113.39014 L 239.07664,113.39014 z"
|
||||||
|
id="path304"
|
||||||
|
style="fill:#ffffff" />
|
||||||
|
|
||||||
|
<path
|
||||||
|
d="M 241.88914,107.8667 L 243.53269,107.8667 L 245.09324,110.49854 L 246.64402,107.8667 L 248.27781,107.8667 L 245.80418,111.97315 L 245.80418,114.53077 L 244.33543,114.53077 L 244.33543,111.93604 L 241.88914,107.8667 z"
|
||||||
|
id="path306"
|
||||||
|
style="fill:#ffffff" />
|
||||||
|
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g
|
||||||
|
id="g6316_1_"
|
||||||
|
transform="matrix(0.624995,0,0,0.624995,391.2294,176.9332)">
|
||||||
|
|
||||||
|
<path
|
||||||
|
id="path6318_1_"
|
||||||
|
cx="475.97119"
|
||||||
|
ry="29.209877"
|
||||||
|
cy="252.08646"
|
||||||
|
type="arc"
|
||||||
|
rx="29.209877"
|
||||||
|
d="M -175.0083,-139.1153 C -175.00204,-129.7035 -182.62555,-122.06751 -192.03812,-122.06049 C -201.44913,-122.05341 -209.08512,-129.67774 -209.09293,-139.09028 C -209.09293,-139.09809 -209.09293,-139.10749 -209.09293,-139.1153 C -209.09919,-148.52784 -201.47413,-156.1623 -192.06311,-156.17011 C -182.65054,-156.17713 -175.01456,-148.55207 -175.0083,-139.14026 C -175.0083,-139.13092 -175.0083,-139.1239 -175.0083,-139.1153 z"
|
||||||
|
style="fill:#ffffff" />
|
||||||
|
|
||||||
|
<g
|
||||||
|
id="g6320_1_"
|
||||||
|
transform="translate(-23.9521,-89.72962)">
|
||||||
|
<path
|
||||||
|
id="path6322_1_"
|
||||||
|
d="M -168.2204,-68.05536 C -173.39234,-68.05536 -177.76892,-66.25067 -181.35175,-62.64203 C -185.02836,-58.90759 -186.86588,-54.48883 -186.86588,-49.38568 C -186.86588,-44.28253 -185.02836,-39.89416 -181.35175,-36.22308 C -177.67673,-32.55114 -173.29859,-30.71521 -168.2204,-30.71521 C -163.07974,-30.71521 -158.62503,-32.56677 -154.85312,-36.26996 C -151.30307,-39.78558 -149.52652,-44.15827 -149.52652,-49.38568 C -149.52652,-54.6123 -151.33432,-59.03265 -154.94843,-62.64203 C -158.5625,-66.25067 -162.98599,-68.05536 -168.2204,-68.05536 z M -168.17352,-64.69519 C -163.936,-64.69519 -160.33752,-63.20221 -157.37655,-60.21466 C -154.38748,-57.25836 -152.89214,-53.64899 -152.89214,-49.38568 C -152.89214,-45.09186 -154.35466,-41.52856 -157.28438,-38.69653 C -160.36876,-35.64727 -163.99849,-34.12304 -168.17351,-34.12304 C -172.34856,-34.12304 -175.94701,-35.63244 -178.96892,-38.64965 C -181.9908,-41.66918 -183.50176,-45.24657 -183.50176,-49.38567 C -183.50176,-53.52398 -181.97518,-57.13414 -178.92205,-60.21465 C -175.9939,-63.20221 -172.41107,-64.69519 -168.17352,-64.69519 z" />
|
||||||
|
|
||||||
|
<path
|
||||||
|
id="path6324_1_"
|
||||||
|
d="M -176.49548,-52.02087 C -175.75171,-56.71856 -172.44387,-59.22949 -168.30008,-59.22949 C -162.33911,-59.22949 -158.70783,-54.90448 -158.70783,-49.1372 C -158.70783,-43.50982 -162.57194,-39.13793 -168.39383,-39.13793 C -172.39856,-39.13793 -175.98297,-41.60277 -176.63611,-46.43877 L -171.93292,-46.43877 C -171.7923,-43.92778 -170.1626,-43.04418 -167.83447,-43.04418 C -165.1813,-43.04418 -163.4563,-45.50908 -163.4563,-49.27709 C -163.4563,-53.22942 -164.94693,-55.32244 -167.74228,-55.32244 C -169.79074,-55.32244 -171.55948,-54.57787 -171.93292,-52.02087 L -170.56418,-52.02789 L -174.26734,-48.32629 L -177.96894,-52.02789 L -176.49548,-52.02087 z" />
|
||||||
|
|
||||||
|
</g>
|
||||||
|
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g
|
||||||
|
id="g313">
|
||||||
|
<circle
|
||||||
|
cx="242.56226"
|
||||||
|
cy="90.224609"
|
||||||
|
r="10.8064"
|
||||||
|
id="circle315"
|
||||||
|
sodipodi:cx="242.56226"
|
||||||
|
sodipodi:cy="90.224609"
|
||||||
|
sodipodi:rx="10.8064"
|
||||||
|
sodipodi:ry="10.8064"
|
||||||
|
style="fill:#ffffff" />
|
||||||
|
|
||||||
|
<g
|
||||||
|
id="g317">
|
||||||
|
<path
|
||||||
|
d="M 245.68994,87.09766 C 245.68994,86.68116 245.35205,86.34424 244.93603,86.34424 L 240.16357,86.34424 C 239.74755,86.34424 239.40966,86.68115 239.40966,87.09766 L 239.40966,91.87061 L 240.74071,91.87061 L 240.74071,97.52295 L 244.3579,97.52295 L 244.3579,91.87061 L 245.68993,91.87061 L 245.68993,87.09766 L 245.68994,87.09766 z"
|
||||||
|
id="path319" />
|
||||||
|
|
||||||
|
<circle
|
||||||
|
cx="242.5498"
|
||||||
|
cy="84.083008"
|
||||||
|
r="1.63232"
|
||||||
|
id="circle321"
|
||||||
|
sodipodi:cx="242.5498"
|
||||||
|
sodipodi:cy="84.083008"
|
||||||
|
sodipodi:rx="1.63232"
|
||||||
|
sodipodi:ry="1.63232" />
|
||||||
|
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<path
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M 242.53467,78.31836 C 239.30322,78.31836 236.56641,79.4458 234.32715,81.70215 C 232.0293,84.03516 230.88086,86.79736 230.88086,89.98633 C 230.88086,93.1753 232.0293,95.91846 234.32715,98.21338 C 236.625,100.50781 239.36133,101.65527 242.53467,101.65527 C 245.74756,101.65527 248.53272,100.49853 250.88819,98.18359 C 253.10889,95.98681 254.21827,93.2539 254.21827,89.98632 C 254.21827,86.71874 253.08936,83.95751 250.83057,81.70214 C 248.57178,79.4458 245.80615,78.31836 242.53467,78.31836 z M 242.56396,80.41797 C 245.2124,80.41797 247.46142,81.35156 249.31103,83.21875 C 251.18115,85.06592 252.11572,87.32227 252.11572,89.98633 C 252.11572,92.66992 251.20068,94.89746 249.36963,96.66699 C 247.4419,98.57275 245.17334,99.52539 242.56397,99.52539 C 239.9546,99.52539 237.70557,98.58252 235.81739,96.6958 C 233.92774,94.80957 232.98389,92.57324 232.98389,89.98633 C 232.98389,87.3999 233.93799,85.14404 235.84619,83.21875 C 237.67676,81.35156 239.9165,80.41797 242.56396,80.41797 z"
|
||||||
|
id="path323"
|
||||||
|
style="fill-rule:evenodd" />
|
||||||
|
|
||||||
|
</g>
|
||||||
|
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,10 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300" width="300" height="300">
|
||||||
|
<rect width="300" height="300" fill="#afd074"/>
|
||||||
|
<g fill="#000">
|
||||||
|
<rect x="135" y="65" width="30" height="30"/><rect x="170" y="65" width="30" height="30"/><rect x="205" y="65" width="30" height="30"/>
|
||||||
|
<rect x="135" y="100" width="30" height="30"/><rect x="205" y="100" width="30" height="30"/>
|
||||||
|
<rect x="65" y="135" width="30" height="30"/><rect x="100" y="135" width="30" height="30"/><rect x="135" y="135" width="30" height="30"/><rect x="170" y="135" width="30" height="30"/><rect x="205" y="135" width="30" height="30"/>
|
||||||
|
<rect x="65" y="170" width="30" height="30"/><rect x="135" y="170" width="30" height="30"/>
|
||||||
|
<rect x="65" y="205" width="30" height="30"/><rect x="100" y="205" width="30" height="30"/><rect x="135" y="205" width="30" height="30"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 879 B |
+144
@@ -0,0 +1,144 @@
|
|||||||
|
// Links the topics drawn on the map (map.json) to README topics, so every topic knows
|
||||||
|
// which areas (Software, Hardware, Soft skills) it sits in and how important the map marks it.
|
||||||
|
|
||||||
|
// Map labels whose README heading is worded differently.
|
||||||
|
const ALIASES = {
|
||||||
|
'ADC / DAC': ['ADC', 'DAC'],
|
||||||
|
'Buildroot / Yocto': ['Buildroot', 'Yocto'],
|
||||||
|
'TDD & Unit Testing': ['Test Driven Development (TDD)', 'Unit Testing'],
|
||||||
|
'Threading / Parallelism': ['Multithreading & Parallel Processing'],
|
||||||
|
'Device Drivers': ['Linux Device Drivers'],
|
||||||
|
'Real-Time OS': ['Real-Time Operating Systems'],
|
||||||
|
'Interfaces & Protocols': ['Interfaces, Protocols & Communication Technologies'],
|
||||||
|
Basic: ['Basic Protocols'],
|
||||||
|
'High-Speed': ['High-Speed Protocols'],
|
||||||
|
Wireless: ['Wireless Protocols'],
|
||||||
|
Industrial: ['Industrial Protocols'],
|
||||||
|
Automotive: ['Automotive Protocols'],
|
||||||
|
Network: ['Network Protocols / Socket Programming'],
|
||||||
|
'TCP/IP': ['Network Protocols / Socket Programming'],
|
||||||
|
UDP: ['Network Protocols / Socket Programming'],
|
||||||
|
Cellular: ['Cellular Communication'],
|
||||||
|
MQTT: ['CoAP & MQTT'],
|
||||||
|
CoAP: ['CoAP & MQTT'],
|
||||||
|
'LTE-M / 5G': ['LTE-M & NB-IoT'],
|
||||||
|
'NB-IoT': ['LTE-M & NB-IoT'],
|
||||||
|
'Basic Math & Calculus': ['Basic Calculus'],
|
||||||
|
'SDLC Models': ['Software Development Life Cycle (SDLC) Models'],
|
||||||
|
'Version Control': ['Version Control Systems'],
|
||||||
|
AUTOSAR: ['AUTOSAR Architecture'],
|
||||||
|
// Drawn on the map but without their own README section: open the protocol family instead.
|
||||||
|
Profinet: ['Industrial Protocols'],
|
||||||
|
LIN: ['Automotive Protocols'],
|
||||||
|
MOST: ['Automotive Protocols'],
|
||||||
|
FlexRay: ['Automotive Protocols'],
|
||||||
|
UWB: ['Wireless Protocols'],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AREA_ORDER = ['SOFTWARE', 'HARDWARE', 'SOFT SKILLS'];
|
||||||
|
export const IMPORTANCE_LEVELS = ['required', 'recommended', 'possible'];
|
||||||
|
const RANK = { required: 3, recommended: 2, possible: 1 };
|
||||||
|
|
||||||
|
const key = (text) => text.normalize('NFKC').toLowerCase().replace(/\s+/g, ' ').trim();
|
||||||
|
|
||||||
|
function topicIndex(topics) {
|
||||||
|
const index = new Map();
|
||||||
|
const add = (k, topic) => {
|
||||||
|
if (k && !index.has(k)) index.set(k, topic);
|
||||||
|
};
|
||||||
|
const byDepth = [...topics].sort((a, b) => a.depth - b.depth);
|
||||||
|
for (const t of byDepth) add(key(t.title), t);
|
||||||
|
for (const t of byDepth) {
|
||||||
|
const base = t.title.replace(/\s*\([^)]*\)\s*/g, ' ').trim();
|
||||||
|
add(key(base), t);
|
||||||
|
const inner = t.title.match(/\(([^)]+)\)/);
|
||||||
|
if (inner && /^[A-Z0-9-]{2,6}$/.test(inner[1])) add(key(inner[1]), t);
|
||||||
|
add(key(base.split(' / ')[0]), t);
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function linkDiagram(map, data) {
|
||||||
|
const index = topicIndex(data.topics);
|
||||||
|
const softSkills = data.topics.find((t) => t.depth === 1 && key(t.title) === 'soft skills');
|
||||||
|
const resolve = (text, areas = []) => {
|
||||||
|
if (ALIASES[text]) return ALIASES[text].map((title) => index.get(key(title))).filter(Boolean);
|
||||||
|
const topic = index.get(key(text));
|
||||||
|
if (topic) return [topic];
|
||||||
|
if (softSkills && areas.includes('SOFT SKILLS')) return [softSkills];
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
// A cluster's areas are those of the band it sits in; a group's are the areas all its clusters share.
|
||||||
|
const clusterAreas = new Map();
|
||||||
|
const clusters = {};
|
||||||
|
const boxes = [];
|
||||||
|
for (const band of map.bands) {
|
||||||
|
for (const [cluster, spec] of Object.entries(band.clusters)) {
|
||||||
|
clusterAreas.set(cluster, band.areas);
|
||||||
|
clusters[cluster] = { header: spec.header, caption: spec.caption, per: spec.per };
|
||||||
|
if (spec.header) boxes.push({ text: spec.header, importance: null, areas: band.areas, cluster, header: true });
|
||||||
|
for (const [text, importance] of spec.topics) boxes.push({ text, importance, areas: band.areas, cluster, header: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
boxes.forEach((b, i) => Object.assign(b, { id: `box-${i}`, topics: resolve(b.text, b.areas) }));
|
||||||
|
const regions = Object.entries(map.groups).map(([name, clusters]) => {
|
||||||
|
const areas = AREA_ORDER.filter((a) => clusters.every((c) => (clusterAreas.get(c) || []).includes(a)));
|
||||||
|
return { name, clusters, areas, topics: resolve(name) };
|
||||||
|
});
|
||||||
|
|
||||||
|
const boxOf = new Map();
|
||||||
|
for (const t of data.topics) {
|
||||||
|
t.areas = [];
|
||||||
|
t.importance = null;
|
||||||
|
t.boxes = [];
|
||||||
|
}
|
||||||
|
for (const box of boxes) {
|
||||||
|
for (const t of box.topics) {
|
||||||
|
t.boxes.push(box);
|
||||||
|
if (!boxOf.has(t)) boxOf.set(t, box);
|
||||||
|
for (const area of box.areas) if (!t.areas.includes(area)) t.areas.push(area);
|
||||||
|
if (box.importance && (!t.importance || RANK[box.importance] > RANK[t.importance])) t.importance = box.importance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const region of regions) {
|
||||||
|
for (const t of region.topics) {
|
||||||
|
for (const area of region.areas) if (!t.areas.includes(area)) t.areas.push(area);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Subtopics that are not drawn in the diagram inherit the areas of their closest drawn ancestor.
|
||||||
|
for (const t of data.topics) {
|
||||||
|
if (!t.areas.length) for (let p = t.parent; p && !t.areas.length; p = p.parent) t.areas = [...(p.areas || [])];
|
||||||
|
t.areas.sort((a, b) => AREA_ORDER.indexOf(a) - AREA_ORDER.indexOf(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The box that stands for a topic on the map: its own, or the closest ancestor's.
|
||||||
|
const boxFor = (topic) => {
|
||||||
|
for (let t = topic; t; t = t.parent) if (boxOf.has(t)) return boxOf.get(t);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const pairs = new Map();
|
||||||
|
for (const link of data.links) {
|
||||||
|
const a = boxFor(link.source);
|
||||||
|
const b = boxFor(link.target);
|
||||||
|
if (!a || !b || a === b) continue;
|
||||||
|
const [p, q] = a.id < b.id ? [a, b] : [b, a];
|
||||||
|
const id = `${p.id}|${q.id}`;
|
||||||
|
if (!pairs.has(id)) pairs.set(id, { a: p, b: q, weight: 0, shared: 0, links: [] });
|
||||||
|
const pair = pairs.get(id);
|
||||||
|
pair.weight += link.weight;
|
||||||
|
pair.shared += link.shared.length;
|
||||||
|
pair.links.push(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
regions,
|
||||||
|
clusters,
|
||||||
|
boxes,
|
||||||
|
boxFor,
|
||||||
|
connections: [...pairs.values()],
|
||||||
|
unmatched: boxes.filter((b) => !b.header && !b.topics.length).map((b) => b.text),
|
||||||
|
invalid: boxes.filter((b) => !b.header && !IMPORTANCE_LEVELS.includes(b.importance)).map((b) => `${b.text}: ${b.importance}`),
|
||||||
|
};
|
||||||
|
}
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
// DOM helpers shared by the views.
|
||||||
|
|
||||||
|
export const SVG_NS = 'http://www.w3.org/2000/svg';
|
||||||
|
|
||||||
|
export const plural = (n, word) => `${n} ${word}${n === 1 ? '' : 's'}`;
|
||||||
|
|
||||||
|
export const CREDITS = {
|
||||||
|
author: 'Meysam Parvizi',
|
||||||
|
authorUrl: 'https://github.com/m3y54m',
|
||||||
|
repo: 'github.com/m3y54m/Embedded-Engineering-Roadmap',
|
||||||
|
license: 'CC BY-SA 4.0',
|
||||||
|
licenseName: 'Creative Commons Attribution-ShareAlike 4.0 International License',
|
||||||
|
licenseUrl: 'https://creativecommons.org/licenses/by-sa/4.0/',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GITHUB_MARK = 'M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z';
|
||||||
|
|
||||||
|
export function h(tag, props = {}, ...children) {
|
||||||
|
const el = document.createElement(tag);
|
||||||
|
for (const [key, value] of Object.entries(props)) {
|
||||||
|
if (value == null || value === false) continue;
|
||||||
|
if (key === 'class') el.className = value;
|
||||||
|
else if (key.startsWith('on')) el.addEventListener(key.slice(2), value);
|
||||||
|
else el.setAttribute(key, value === true ? '' : value);
|
||||||
|
}
|
||||||
|
for (const child of children.flat(Infinity)) {
|
||||||
|
if (child == null || child === false || child === '') continue;
|
||||||
|
el.append(child instanceof Node ? child : document.createTextNode(String(child)));
|
||||||
|
}
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function svg(tag, attrs = {}, parent = null) {
|
||||||
|
const el = document.createElementNS(SVG_NS, tag);
|
||||||
|
for (const [key, value] of Object.entries(attrs)) if (value != null) el.setAttribute(key, value);
|
||||||
|
if (parent) parent.append(el);
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AREAS = {
|
||||||
|
SOFTWARE: { label: 'Software', cls: 'sw' },
|
||||||
|
HARDWARE: { label: 'Hardware', cls: 'hw' },
|
||||||
|
'SOFT SKILLS': { label: 'Soft skills', cls: 'ss' },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IMPORTANCE = {
|
||||||
|
required: 'Required',
|
||||||
|
recommended: 'Recommended',
|
||||||
|
possible: 'Possibility',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const areaClass = (area) => (AREAS[area] ? AREAS[area].cls : 'xx');
|
||||||
|
export const areaLabel = (areas) => areas.map((a) => (AREAS[a] ? AREAS[a].label : a)).join(' ∩ ');
|
||||||
|
|
||||||
|
export function areaDots(areas) {
|
||||||
|
return h('span', { class: 'area-dots', 'aria-hidden': 'true' }, areas.map((a) => h('i', { class: `dot ${areaClass(a)}` })));
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
Copyright 2019 The Sofia Sans Project Authors (https://github.com/lettersoup/Sofia-Sans)
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
https://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
Binary file not shown.
@@ -0,0 +1,70 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en" data-theme="dark">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self'; font-src 'self'; img-src 'self' data:; connect-src 'self' https://raw.githubusercontent.com; base-uri 'none'; form-action 'none'; object-src 'none'">
|
||||||
|
<title>Embedded Engineering Roadmap · Explorer</title>
|
||||||
|
<meta name="description" content="Interactive map of the Embedded Systems Engineering Roadmap: explore topics, how they connect, and their learning resources.">
|
||||||
|
<link rel="icon" href="assets/logo.svg" type="image/svg+xml">
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
<link rel="stylesheet" href="map.css">
|
||||||
|
<script type="module" src="app.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="app">
|
||||||
|
<header class="topbar">
|
||||||
|
<a class="brand" href="#/" aria-label="Roadmap overview">
|
||||||
|
<img class="brand-mark" src="assets/logo.svg" alt="" width="36" height="36">
|
||||||
|
<span class="brand-text">
|
||||||
|
<strong>Embedded Engineering Roadmap</strong>
|
||||||
|
<span>Interactive explorer</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="search" role="search">
|
||||||
|
<svg class="search-icon" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true"><circle cx="11" cy="11" r="7" fill="none" stroke="currentColor" stroke-width="2"/><path d="m20 20-4.2-4.2" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
|
||||||
|
<input id="search" type="search" placeholder="Search topics and resources" autocomplete="off" spellcheck="false"
|
||||||
|
role="combobox" aria-label="Search topics and resources" aria-expanded="false" aria-controls="search-results" aria-autocomplete="list">
|
||||||
|
<kbd aria-hidden="true">/</kbd>
|
||||||
|
<div id="search-results" class="search-results" role="listbox" aria-label="Search results" hidden></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<div class="segmented" role="tablist" aria-label="View">
|
||||||
|
<button type="button" role="tab" data-view="map" aria-selected="true" aria-controls="diagram">Map</button>
|
||||||
|
<button type="button" role="tab" data-view="list" aria-selected="false" aria-controls="outline">Outline</button>
|
||||||
|
</div>
|
||||||
|
<button type="button" id="print" class="icon-btn" aria-label="Print or save the roadmap as PDF" title="Print or save as PDF (A4)">
|
||||||
|
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path d="M7 9V3h10v6M7 17H5a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-2M7 14h10v7H7z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<a class="icon-btn text-btn" href="Embedded-Engineering-Roadmap.pdf" download data-download hidden title="Download the roadmap as PDF (A4)">
|
||||||
|
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true"><path d="M12 4v11m0 0-4.5-4.5M12 15l4.5-4.5M5 19h14" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"/></svg>PDF
|
||||||
|
</a>
|
||||||
|
<a class="icon-btn text-btn" href="Embedded-Engineering-Roadmap.png" download data-download hidden title="Download the roadmap as PNG image">
|
||||||
|
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true"><path d="M12 4v11m0 0-4.5-4.5M12 15l4.5-4.5M5 19h14" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"/></svg>PNG
|
||||||
|
</a>
|
||||||
|
<button type="button" id="theme" class="icon-btn" aria-label="Switch color theme" title="Switch color theme">
|
||||||
|
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path d="M12 3a9 9 0 1 0 9 9A7 7 0 0 1 12 3Z" fill="currentColor"/></svg>
|
||||||
|
</button>
|
||||||
|
<a class="icon-btn" href="https://github.com/m3y54m/Embedded-Engineering-Roadmap" target="_blank" rel="noopener noreferrer" aria-label="Repository on GitHub" title="Repository on GitHub">
|
||||||
|
<svg viewBox="0 0 16 16" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="layout">
|
||||||
|
<section class="stage" aria-label="Roadmap">
|
||||||
|
<div id="diagram" class="map-view" role="tabpanel" hidden></div>
|
||||||
|
<div id="outline" class="outline" role="tabpanel" hidden></div>
|
||||||
|
<div id="status" class="status" role="status">
|
||||||
|
<span class="spinner" aria-hidden="true"></span>
|
||||||
|
<span>Loading roadmap…</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<aside id="panel" class="panel" aria-label="Topic details" tabindex="-1"></aside>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+167
@@ -0,0 +1,167 @@
|
|||||||
|
// Print-friendly layout (A4 portrait proportions) for the roadmap map.
|
||||||
|
// Topics come from map.json; this file only decides where each cluster goes. Three horizontal bands
|
||||||
|
// make the areas explicit: Software only (top), Software ∩ Hardware (middle), Hardware only
|
||||||
|
// (bottom). Soft skills are not technical, so they get their own area below the others. The
|
||||||
|
// Real-Time OS cluster sits where the Operating Systems and Microcontrollers groups overlap.
|
||||||
|
|
||||||
|
// Minimum distance between any two boxes, texts or lines.
|
||||||
|
export const SPACE = 10;
|
||||||
|
const COL = 170;
|
||||||
|
const GUTTER = 26;
|
||||||
|
const LEFT = 86;
|
||||||
|
const TOP = 168;
|
||||||
|
const BOX_H = 26;
|
||||||
|
const BOX_H2 = 42;
|
||||||
|
const HEAD_H = 20;
|
||||||
|
const HEAD = HEAD_H + SPACE;
|
||||||
|
const GAP = 2 * SPACE;
|
||||||
|
const FOOTER_H = 56;
|
||||||
|
// Rough width of one character of box text, used to decide when a label wraps to two lines.
|
||||||
|
const CHAR_W = 6.3;
|
||||||
|
const TEXT_PAD = 8;
|
||||||
|
|
||||||
|
export const PAGE_WIDTH = LEFT * 2 + COL * 5 + GUTTER * 4;
|
||||||
|
|
||||||
|
const colX = (i) => LEFT + i * (COL + GUTTER);
|
||||||
|
const spanW = (n) => n * COL + (n - 1) * GUTTER;
|
||||||
|
|
||||||
|
// Cluster ids from map.json that this layout knows where to place.
|
||||||
|
export const PLACED = [
|
||||||
|
'languages', 'fundamentals', 'debugging', 'sdlc', 'vcs', 'build', 'testing', 'specialized', 'osBase', 'linux',
|
||||||
|
'rtos', 'mcu', 'bridge', 'basic', 'wireless', 'highSpeed', 'industrial', 'cellular', 'network', 'automotive',
|
||||||
|
'electronics', 'equipment', 'prototyping', 'fpga', 'soft',
|
||||||
|
];
|
||||||
|
|
||||||
|
function place(cluster, x, y, w) {
|
||||||
|
const items = [];
|
||||||
|
let cy = y;
|
||||||
|
if (cluster.headerBox || cluster.caption) {
|
||||||
|
items.push({
|
||||||
|
kind: cluster.headerBox ? 'header' : 'caption',
|
||||||
|
box: cluster.headerBox || null,
|
||||||
|
text: cluster.headerBox ? cluster.headerBox.text : cluster.caption,
|
||||||
|
x, y: cy, w, h: HEAD_H,
|
||||||
|
});
|
||||||
|
cy += HEAD;
|
||||||
|
}
|
||||||
|
const per = cluster.per || 1;
|
||||||
|
const bw = (w - (per - 1) * SPACE) / per;
|
||||||
|
for (let i = 0; i < cluster.boxes.length; i += per) {
|
||||||
|
const row = cluster.boxes.slice(i, i + per);
|
||||||
|
// Every box in a row gets the same height so the row stays aligned.
|
||||||
|
const h = row.some((b) => b.text.length * CHAR_W + 2 * TEXT_PAD > bw && b.text.includes(' ')) ? BOX_H2 : BOX_H;
|
||||||
|
row.forEach((box, j) => items.push({ kind: 'box', box, text: box.text, x: x + j * (bw + SPACE), y: cy, w: bw, h }));
|
||||||
|
cy += h + SPACE;
|
||||||
|
}
|
||||||
|
return { items, bottom: cy - SPACE };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildLayout(plan) {
|
||||||
|
const clusters = {};
|
||||||
|
for (const id of PLACED) {
|
||||||
|
const spec = plan.clusters[id] || {};
|
||||||
|
const own = plan.boxes.filter((b) => b.cluster === id);
|
||||||
|
clusters[id] = { ...spec, headerBox: own.find((b) => b.header) || null, boxes: own.filter((b) => !b.header) };
|
||||||
|
}
|
||||||
|
// Topics in clusters this layout cannot place yet; the tests report them.
|
||||||
|
const missing = plan.boxes.filter((b) => !PLACED.includes(b.cluster)).map((b) => `${b.cluster}: ${b.text}`);
|
||||||
|
|
||||||
|
const items = [];
|
||||||
|
const add = (result) => {
|
||||||
|
items.push(...result.items);
|
||||||
|
return result.bottom;
|
||||||
|
};
|
||||||
|
const stack = (ids, x, y, w) => ids.reduce((top, id, i) => add(place(clusters[id], x, i ? top + GAP : top, w)), y);
|
||||||
|
const c = [0, 1, 2, 3, 4].map(colX);
|
||||||
|
const wide = spanW(2);
|
||||||
|
// Group tags are 22 high and centered on the group's top border.
|
||||||
|
const TAG = 11;
|
||||||
|
const belowTag = (y) => y + TAG + SPACE;
|
||||||
|
|
||||||
|
// Software only.
|
||||||
|
const languagesBottom = add(place(clusters.languages, c[0], TOP, COL));
|
||||||
|
const fundamentalsBottom = add(place(clusters.fundamentals, c[1], TOP, COL));
|
||||||
|
const col3 = stack(['debugging', 'sdlc', 'vcs'], c[2], TOP, COL);
|
||||||
|
const col4 = stack(['build', 'testing'], c[3], TOP, COL);
|
||||||
|
const col5 = add(place(clusters.specialized, c[4], TOP, COL));
|
||||||
|
|
||||||
|
// OS outline is 2 spaces outside its content, Microcontrollers 1 space, and the Interfaces
|
||||||
|
// outline keeps a full space from both inside the gutter between them.
|
||||||
|
const leftW = wide + GUTTER - 4 * SPACE;
|
||||||
|
const osTop = Math.max(languagesBottom, fundamentalsBottom) + GAP + TAG;
|
||||||
|
let osY = add(place(clusters.osBase, c[0], belowTag(osTop), leftW));
|
||||||
|
osY = add(place(clusters.linux, c[0], osY + GAP, leftW));
|
||||||
|
|
||||||
|
// Software ∩ Hardware.
|
||||||
|
const crossTop = Math.max(osY, col3, col4, col5) + GAP;
|
||||||
|
const groupTop = crossTop + GAP;
|
||||||
|
const rtosBottom = add(place(clusters.rtos, c[0], belowTag(groupTop), leftW));
|
||||||
|
const os = { x: c[0] - 2 * SPACE, y: osTop, w: leftW + 4 * SPACE, h: rtosBottom + SPACE - osTop };
|
||||||
|
const mcuBottom = add(place(clusters.mcu, c[0], os.y + os.h + SPACE, leftW));
|
||||||
|
const mcu = { x: c[0] - SPACE, y: groupTop, w: leftW + 2 * SPACE, h: mcuBottom + SPACE - groupTop };
|
||||||
|
|
||||||
|
const subW = (wide - 2 * SPACE) / 3;
|
||||||
|
const sub = [0, 1, 2].map((i) => c[2] + i * (subW + SPACE));
|
||||||
|
const ifContent = belowTag(groupTop);
|
||||||
|
const ifBottom = Math.max(
|
||||||
|
stack(['basic', 'wireless'], sub[0], ifContent, subW),
|
||||||
|
stack(['highSpeed', 'industrial', 'cellular'], sub[1], ifContent, subW),
|
||||||
|
stack(['network', 'automotive'], sub[2], ifContent, subW),
|
||||||
|
);
|
||||||
|
const interfaces = { x: c[2] - SPACE, y: groupTop, w: wide + 2 * SPACE, h: ifBottom + SPACE - groupTop };
|
||||||
|
const bridgeBottom = add(place(clusters.bridge, c[4], groupTop, COL));
|
||||||
|
const crossBottom = Math.max(mcu.y + mcu.h, interfaces.y + interfaces.h, bridgeBottom) + GAP;
|
||||||
|
|
||||||
|
// Hardware only.
|
||||||
|
const hwTop = crossBottom + GAP;
|
||||||
|
const hwBottom = Math.max(
|
||||||
|
add(place(clusters.electronics, c[0], hwTop, wide)),
|
||||||
|
add(place(clusters.equipment, c[2], hwTop, COL)),
|
||||||
|
add(place(clusters.prototyping, c[3], hwTop, COL)),
|
||||||
|
add(place(clusters.fpga, c[4], hwTop + HEAD, COL)),
|
||||||
|
);
|
||||||
|
|
||||||
|
const areaX = 24;
|
||||||
|
const areaW = PAGE_WIDTH - 2 * areaX;
|
||||||
|
const swTop = TOP - 40;
|
||||||
|
const hwEnd = hwBottom + GAP;
|
||||||
|
|
||||||
|
// Soft skills: a separate area under the technical ones, tall enough for its margin label.
|
||||||
|
const softTop = hwEnd + GAP;
|
||||||
|
const softRows = Math.ceil(clusters.soft.boxes.length / clusters.soft.per);
|
||||||
|
const softH = Math.max(132, softRows * (BOX_H2 + SPACE) - SPACE + 2 * GAP);
|
||||||
|
const soft = { x: areaX, y: softTop, w: areaW, h: softH };
|
||||||
|
const softContentH = place(clusters.soft, 0, 0, spanW(5)).bottom;
|
||||||
|
add(place(clusters.soft, c[0], softTop + (softH - softContentH) / 2, spanW(5)));
|
||||||
|
const softEnd = softTop + softH;
|
||||||
|
|
||||||
|
const regionByName = new Map(plan.regions.map((r) => [r.name, r]));
|
||||||
|
const groupTopics = (name) => (regionByName.get(name) || { topics: [] }).topics;
|
||||||
|
const footer = { x: areaX, y: softEnd + GAP, w: areaW, h: FOOTER_H };
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: PAGE_WIDTH,
|
||||||
|
height: footer.y + footer.h + 16,
|
||||||
|
titleY: 58,
|
||||||
|
footer,
|
||||||
|
areas: {
|
||||||
|
SOFTWARE: { x: areaX, y: swTop, w: areaW, h: crossBottom - swTop },
|
||||||
|
HARDWARE: { x: areaX, y: crossTop, w: areaW, h: hwEnd - crossTop },
|
||||||
|
'SOFT SKILLS': soft,
|
||||||
|
},
|
||||||
|
cross: { x: areaX, y: crossTop, w: areaW, h: crossBottom - crossTop },
|
||||||
|
bands: [
|
||||||
|
{ label: 'SOFTWARE', cls: 'sw', y0: swTop, y1: crossTop },
|
||||||
|
{ label: 'SOFTWARE ∩ HARDWARE', cls: 'both', y0: crossTop, y1: crossBottom },
|
||||||
|
{ label: 'HARDWARE', cls: 'hw', y0: crossBottom, y1: hwEnd },
|
||||||
|
{ label: 'SOFT SKILLS', cls: 'ss', y0: softTop, y1: softEnd },
|
||||||
|
],
|
||||||
|
groups: [
|
||||||
|
{ name: 'Operating Systems', cls: 'g-os', rect: os, topics: groupTopics('Operating Systems') },
|
||||||
|
{ name: 'Microcontrollers', cls: 'g-mcu', rect: mcu, topics: groupTopics('Microcontrollers') },
|
||||||
|
{ name: 'Interfaces & Protocols', cls: 'g-ifc', rect: interfaces, topics: groupTopics('Interfaces & Protocols') },
|
||||||
|
],
|
||||||
|
items,
|
||||||
|
missing,
|
||||||
|
};
|
||||||
|
}
|
||||||
+516
@@ -0,0 +1,516 @@
|
|||||||
|
/* Map view: the roadmap diagram. */
|
||||||
|
|
||||||
|
.map-view {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-top {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px 16px 10px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
background: var(--surface);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-strip,
|
||||||
|
.map-toolbar {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
overflow-x: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-strip > *,
|
||||||
|
.map-toolbar > * {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-toolbar-label {
|
||||||
|
min-width: 74px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-chip,
|
||||||
|
.map-chip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
min-height: 30px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border: 1px solid var(--border-strong);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--surface-2);
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s, border-color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-chip:hover,
|
||||||
|
.map-chip:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: var(--chip-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-chip > span {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-chip[aria-pressed="true"] {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: var(--accent);
|
||||||
|
color: var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-chip.clear {
|
||||||
|
border-style: dashed;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-toolbar-sep {
|
||||||
|
width: 1px;
|
||||||
|
height: 22px;
|
||||||
|
margin: 0 4px;
|
||||||
|
background: var(--border-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-summary {
|
||||||
|
margin-left: 4px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot.both {
|
||||||
|
background: linear-gradient(90deg, var(--sw) 50%, var(--hw) 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.imp-swatch {
|
||||||
|
display: inline-block;
|
||||||
|
width: 16px;
|
||||||
|
height: 11px;
|
||||||
|
border: 1px solid var(--box-edge);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.imp-swatch.required { background: var(--imp-required); }
|
||||||
|
.imp-swatch.recommended { background: var(--imp-recommended); }
|
||||||
|
.imp-swatch.possible { background: var(--imp-possible); border-color: var(--border-strong); }
|
||||||
|
|
||||||
|
.map-canvas {
|
||||||
|
position: relative;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diagram {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
font-family: var(--font-display);
|
||||||
|
touch-action: none;
|
||||||
|
user-select: none;
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diagram.panning {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-bg {
|
||||||
|
fill: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
fill: var(--text);
|
||||||
|
font-size: 34px;
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-text {
|
||||||
|
fill: var(--text);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-note {
|
||||||
|
fill: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-imp { stroke: var(--box-edge); stroke-width: 1; }
|
||||||
|
.legend-imp.required { fill: var(--imp-required); }
|
||||||
|
.legend-imp.recommended { fill: var(--imp-recommended); }
|
||||||
|
.legend-imp.possible { fill: var(--imp-possible); stroke: var(--border-strong); }
|
||||||
|
.legend-area { fill: none; stroke-width: 2.5; }
|
||||||
|
.legend-area.sw { stroke: var(--sw); }
|
||||||
|
.legend-area.hw { stroke: var(--hw); }
|
||||||
|
.legend-area.ss { stroke: var(--ss); }
|
||||||
|
.legend-hatch { fill: url(#cross-hatch); stroke: var(--border-strong); }
|
||||||
|
|
||||||
|
.area-fill {
|
||||||
|
stroke: none;
|
||||||
|
fill-opacity: var(--area-tint);
|
||||||
|
}
|
||||||
|
|
||||||
|
.area-fill.sw { fill: var(--sw); }
|
||||||
|
.area-fill.hw { fill: var(--hw); }
|
||||||
|
.area-fill.ss { fill: var(--ss); fill-opacity: calc(var(--area-tint) * 1.6); }
|
||||||
|
|
||||||
|
.area-line {
|
||||||
|
fill: none;
|
||||||
|
stroke-width: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.area-line.sw { stroke: var(--sw); }
|
||||||
|
.area-line.hw { stroke: var(--hw); }
|
||||||
|
.area-line.ss { stroke: var(--ss); stroke-width: 2.5; }
|
||||||
|
|
||||||
|
.overlap-fill {
|
||||||
|
fill: url(#cross-hatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hatch-a,
|
||||||
|
.hatch-b {
|
||||||
|
stroke-width: 2.4;
|
||||||
|
stroke-opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hatch-a { stroke: var(--sw); }
|
||||||
|
.hatch-b { stroke: var(--hw); }
|
||||||
|
|
||||||
|
.band-label {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.22em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.band-label.sw { fill: var(--sw-text); }
|
||||||
|
.band-label.hw { fill: var(--hw-text); }
|
||||||
|
.band-label.both { fill: var(--text); }
|
||||||
|
.band-label.ss { fill: var(--ss-text); }
|
||||||
|
|
||||||
|
.group-line {
|
||||||
|
fill: none;
|
||||||
|
stroke-width: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-line.g-os { stroke: var(--g-os); }
|
||||||
|
.group-line.g-mcu { stroke: var(--g-mcu); }
|
||||||
|
.group-line.g-ifc { stroke: var(--g-ifc); }
|
||||||
|
|
||||||
|
.tag-bg {
|
||||||
|
fill: var(--surface-solid);
|
||||||
|
stroke-width: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-text {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 800;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-tag.g-os .tag-bg { stroke: var(--g-os); }
|
||||||
|
.group-tag.g-mcu .tag-bg { stroke: var(--g-mcu); }
|
||||||
|
.group-tag.g-ifc .tag-bg { stroke: var(--g-ifc); }
|
||||||
|
.group-tag.g-os .tag-text { fill: var(--g-os); }
|
||||||
|
.group-tag.g-mcu .tag-text { fill: var(--g-mcu); }
|
||||||
|
.group-tag.g-ifc .tag-text { fill: var(--g-ifc); }
|
||||||
|
|
||||||
|
.cluster-caption {
|
||||||
|
fill: var(--muted);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box .box-bg {
|
||||||
|
stroke: var(--box-edge);
|
||||||
|
stroke-width: 1;
|
||||||
|
transition: filter 0.15s, stroke 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box.required .box-bg { fill: var(--imp-required); }
|
||||||
|
.box.recommended .box-bg { fill: var(--imp-recommended); }
|
||||||
|
.box.possible .box-bg { fill: var(--imp-possible); stroke: var(--border-strong); }
|
||||||
|
|
||||||
|
.box-text {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box.required .box-text { fill: var(--imp-required-text); }
|
||||||
|
.box.recommended .box-text { fill: var(--imp-recommended-text); }
|
||||||
|
.box.possible .box-text { fill: var(--imp-possible-text); }
|
||||||
|
|
||||||
|
.box .header-text {
|
||||||
|
fill: var(--text);
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box.target:hover .box-bg,
|
||||||
|
.box.lit .box-bg {
|
||||||
|
filter: brightness(1.06);
|
||||||
|
stroke: var(--accent);
|
||||||
|
stroke-width: 2.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box.header.target:hover .header-text,
|
||||||
|
.box.header.lit .header-text,
|
||||||
|
.group-tag.target:hover .tag-text {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-tag.target:hover .tag-bg {
|
||||||
|
fill: var(--chip-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.box.selected .box-bg {
|
||||||
|
stroke: var(--text);
|
||||||
|
stroke-width: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box.header.selected .header-text {
|
||||||
|
fill: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.box.target:focus-visible .box-bg,
|
||||||
|
.group-tag.target:focus-visible .tag-bg {
|
||||||
|
stroke: var(--accent);
|
||||||
|
stroke-width: 3.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box.header.target:focus-visible .header-text {
|
||||||
|
fill: var(--accent);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-boxes .box {
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diagram.filtering .box:not(.match, .header) {
|
||||||
|
opacity: 0.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diagram.has-focus:not(.filtering) .box:not(.lit, .header) {
|
||||||
|
opacity: 0.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link,
|
||||||
|
.link-all {
|
||||||
|
fill: none;
|
||||||
|
stroke: var(--accent);
|
||||||
|
stroke-linecap: round;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link.mention,
|
||||||
|
.link-all.mention {
|
||||||
|
stroke-dasharray: 5 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-all {
|
||||||
|
stroke-opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-linksAll {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diagram.show-all .layer-linksAll {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diagram.show-all.has-focus .layer-linksAll {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-end {
|
||||||
|
fill: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.credit-main {
|
||||||
|
fill: var(--text);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.credit-text {
|
||||||
|
fill: var(--muted);
|
||||||
|
font-size: 12.5px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.credit-strong {
|
||||||
|
fill: var(--text);
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.credit-icon {
|
||||||
|
fill: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-credits a:hover text {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-controls {
|
||||||
|
position: absolute;
|
||||||
|
right: 16px;
|
||||||
|
bottom: 16px;
|
||||||
|
display: grid;
|
||||||
|
justify-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 14px;
|
||||||
|
background: var(--surface);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-controls .icon-btn {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-level {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 11px;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
.map-top {
|
||||||
|
padding: 8px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-strip,
|
||||||
|
.map-toolbar {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
overflow-x: auto;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-chip,
|
||||||
|
.map-chip,
|
||||||
|
.map-toolbar-label,
|
||||||
|
.map-summary {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-controls {
|
||||||
|
bottom: 12px;
|
||||||
|
right: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Printing: the diagram alone on an A4 page, in print colors. */
|
||||||
|
|
||||||
|
@page {
|
||||||
|
size: A4 portrait;
|
||||||
|
margin: 8mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
html[data-theme] {
|
||||||
|
color-scheme: light;
|
||||||
|
--bg: #ffffff;
|
||||||
|
--text: #0f172a;
|
||||||
|
--muted: #475569;
|
||||||
|
--surface-solid: #ffffff;
|
||||||
|
--border-strong: rgba(15, 23, 42, 0.35);
|
||||||
|
--chip-hover: #ffffff;
|
||||||
|
--accent: #3b4fd8;
|
||||||
|
--sw: #2b9fbf;
|
||||||
|
--hw: #e8a400;
|
||||||
|
--ss: #12b85a;
|
||||||
|
--sw-text: #0b6a85;
|
||||||
|
--hw-text: #8a5b00;
|
||||||
|
--ss-text: #0b7a3b;
|
||||||
|
--g-os: #6d4fb0;
|
||||||
|
--g-mcu: #b2453c;
|
||||||
|
--g-ifc: #4b5567;
|
||||||
|
--area-tint: 0.06;
|
||||||
|
--imp-required: #fff04a;
|
||||||
|
--imp-required-text: #1d1a00;
|
||||||
|
--imp-recommended: #fbd7bb;
|
||||||
|
--imp-recommended-text: #2b1407;
|
||||||
|
--imp-possible: #f1f3f6;
|
||||||
|
--imp-possible-text: #1f2937;
|
||||||
|
--box-edge: rgba(15, 23, 42, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: auto;
|
||||||
|
overflow: visible;
|
||||||
|
background: #ffffff;
|
||||||
|
print-color-adjust: exact;
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar,
|
||||||
|
.panel,
|
||||||
|
.map-top,
|
||||||
|
.zoom-controls,
|
||||||
|
.tooltip,
|
||||||
|
#outline,
|
||||||
|
.status {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app,
|
||||||
|
.layout,
|
||||||
|
.stage,
|
||||||
|
.map-view,
|
||||||
|
.map-canvas {
|
||||||
|
display: block !important;
|
||||||
|
position: static !important;
|
||||||
|
height: auto !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diagram {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-height: 280mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diagram .box,
|
||||||
|
.diagram .box-bg {
|
||||||
|
opacity: 1 !important;
|
||||||
|
filter: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-links,
|
||||||
|
.layer-linksAll {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
+266
@@ -0,0 +1,266 @@
|
|||||||
|
{
|
||||||
|
"bands": [
|
||||||
|
{
|
||||||
|
"label": "SOFTWARE",
|
||||||
|
"areas": ["SOFTWARE"],
|
||||||
|
"clusters": {
|
||||||
|
"languages": {
|
||||||
|
"header": "Programming Languages",
|
||||||
|
"per": 2,
|
||||||
|
"topics": [
|
||||||
|
["C", "required"],
|
||||||
|
["C++", "recommended"],
|
||||||
|
["Python", "recommended"],
|
||||||
|
["Assembly", "possible"],
|
||||||
|
["Rust", "possible"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fundamentals": {
|
||||||
|
"header": "Programming Fundamentals",
|
||||||
|
"topics": [
|
||||||
|
["Algorithms & Data Structures", "required"],
|
||||||
|
["Design Patterns", "required"],
|
||||||
|
["State Machines", "required"],
|
||||||
|
["Memory Management", "required"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"debugging": {
|
||||||
|
"header": "Debugging",
|
||||||
|
"topics": [
|
||||||
|
["JTAG / SWD", "required"],
|
||||||
|
["GDB", "required"],
|
||||||
|
["OpenOCD", "possible"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sdlc": {
|
||||||
|
"header": "SDLC Models",
|
||||||
|
"per": 2,
|
||||||
|
"topics": [
|
||||||
|
["Agile / SCRUM", "recommended"],
|
||||||
|
["V-Model", "recommended"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"vcs": {
|
||||||
|
"header": "Version Control",
|
||||||
|
"per": 2,
|
||||||
|
"topics": [
|
||||||
|
["Git", "required"],
|
||||||
|
["SVN", "possible"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"header": "Build System",
|
||||||
|
"topics": [
|
||||||
|
["Compilers / GCC", "required"],
|
||||||
|
["Make / CMake", "required"],
|
||||||
|
["Bash Scripting", "recommended"],
|
||||||
|
["Docker", "possible"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"testing": {
|
||||||
|
"header": "Testing",
|
||||||
|
"topics": [
|
||||||
|
["TDD & Unit Testing", "required"],
|
||||||
|
["CI/CD Pipelines", "recommended"],
|
||||||
|
["SIL / HIL Testing", "possible"],
|
||||||
|
["Standards & Certifications", "possible"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"specialized": {
|
||||||
|
"caption": "Specializations",
|
||||||
|
"topics": [
|
||||||
|
["Embedded Security", "possible"],
|
||||||
|
["Embedded GUI", "possible"],
|
||||||
|
["IoT", "possible"],
|
||||||
|
["Edge AI", "possible"],
|
||||||
|
["AUTOSAR", "possible"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"osBase": {
|
||||||
|
"topics": [
|
||||||
|
["Operating System Fundamentals", "recommended"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"linux": {
|
||||||
|
"header": "Embedded Linux",
|
||||||
|
"per": 2,
|
||||||
|
"topics": [
|
||||||
|
["Linux Kernel", "recommended"],
|
||||||
|
["Device Drivers", "recommended"],
|
||||||
|
["U-Boot", "possible"],
|
||||||
|
["Buildroot / Yocto", "possible"],
|
||||||
|
["Threading / Parallelism", "possible"],
|
||||||
|
["IPC", "possible"],
|
||||||
|
["Qt Framework", "possible"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "SOFTWARE ∩ HARDWARE",
|
||||||
|
"areas": ["SOFTWARE", "HARDWARE"],
|
||||||
|
"clusters": {
|
||||||
|
"rtos": {
|
||||||
|
"header": "Real-Time OS",
|
||||||
|
"per": 2,
|
||||||
|
"topics": [
|
||||||
|
["RTOS Basics", "required"],
|
||||||
|
["FreeRTOS", "recommended"],
|
||||||
|
["Zephyr", "recommended"],
|
||||||
|
["QNX", "possible"],
|
||||||
|
["µC/OS", "possible"],
|
||||||
|
["RT-Thread", "possible"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mcu": {
|
||||||
|
"per": 2,
|
||||||
|
"topics": [
|
||||||
|
["GPIO", "required"],
|
||||||
|
["ADC / DAC", "required"],
|
||||||
|
["Timers / Counters", "required"],
|
||||||
|
["PWM", "required"],
|
||||||
|
["Watchdog", "required"],
|
||||||
|
["Interrupts", "required"],
|
||||||
|
["DMA", "required"],
|
||||||
|
["Clock Management", "required"],
|
||||||
|
["Power Management", "recommended"],
|
||||||
|
["Bootloader / DFU", "recommended"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"bridge": {
|
||||||
|
"caption": "Hardware-facing software",
|
||||||
|
"topics": [
|
||||||
|
["Memory Technologies & File Systems", "recommended"],
|
||||||
|
["Hardware Simulation / Emulation", "recommended"],
|
||||||
|
["Sensors & Actuators", "possible"],
|
||||||
|
["Digital Signal Processing", "possible"],
|
||||||
|
["Control Theory", "possible"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"basic": {
|
||||||
|
"header": "Basic",
|
||||||
|
"topics": [
|
||||||
|
["UART", "required"],
|
||||||
|
["I2C", "required"],
|
||||||
|
["SPI", "required"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"wireless": {
|
||||||
|
"header": "Wireless",
|
||||||
|
"topics": [
|
||||||
|
["Bluetooth", "recommended"],
|
||||||
|
["Wi-Fi", "recommended"],
|
||||||
|
["LoRa", "possible"],
|
||||||
|
["Zigbee", "possible"],
|
||||||
|
["Thread", "possible"],
|
||||||
|
["Matter", "possible"],
|
||||||
|
["UWB", "possible"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"highSpeed": {
|
||||||
|
"header": "High-Speed",
|
||||||
|
"topics": [
|
||||||
|
["Ethernet", "recommended"],
|
||||||
|
["USB", "recommended"],
|
||||||
|
["PCIe", "possible"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"industrial": {
|
||||||
|
"header": "Industrial",
|
||||||
|
"topics": [
|
||||||
|
["Modbus", "recommended"],
|
||||||
|
["Profinet", "possible"],
|
||||||
|
["EtherCAT", "possible"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cellular": {
|
||||||
|
"header": "Cellular",
|
||||||
|
"topics": [
|
||||||
|
["GSM / LTE", "possible"],
|
||||||
|
["LTE-M / 5G", "possible"],
|
||||||
|
["NB-IoT", "possible"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"network": {
|
||||||
|
"header": "Network",
|
||||||
|
"topics": [
|
||||||
|
["MQTT", "recommended"],
|
||||||
|
["CoAP", "possible"],
|
||||||
|
["TCP/IP", "possible"],
|
||||||
|
["UDP", "possible"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"automotive": {
|
||||||
|
"header": "Automotive",
|
||||||
|
"topics": [
|
||||||
|
["CAN", "recommended"],
|
||||||
|
["LIN", "possible"],
|
||||||
|
["MOST", "possible"],
|
||||||
|
["FlexRay", "possible"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "HARDWARE",
|
||||||
|
"areas": ["HARDWARE"],
|
||||||
|
"clusters": {
|
||||||
|
"electronics": {
|
||||||
|
"header": "Electronics",
|
||||||
|
"per": 2,
|
||||||
|
"topics": [
|
||||||
|
["Basic Math & Calculus", "required"],
|
||||||
|
["Principles of Electric Circuits", "required"],
|
||||||
|
["Electronics Fundamentals", "required"],
|
||||||
|
["Digital Design", "required"],
|
||||||
|
["Computer Architecture", "required"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"equipment": {
|
||||||
|
"header": "Using Test Equipment",
|
||||||
|
"topics": [
|
||||||
|
["Multimeter", "required"],
|
||||||
|
["Logic / Protocol Analyzer", "required"],
|
||||||
|
["Oscilloscope", "recommended"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"prototyping": {
|
||||||
|
"header": "Prototyping Skills",
|
||||||
|
"topics": [
|
||||||
|
["Breadboarding", "recommended"],
|
||||||
|
["Hardware Design Basics", "recommended"],
|
||||||
|
["PCB Design / EMC", "recommended"],
|
||||||
|
["Soldering / Rework", "recommended"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fpga": {
|
||||||
|
"topics": [
|
||||||
|
["FPGA Development", "possible"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "SOFT SKILLS",
|
||||||
|
"areas": ["SOFT SKILLS"],
|
||||||
|
"clusters": {
|
||||||
|
"soft": {
|
||||||
|
"per": 3,
|
||||||
|
"topics": [
|
||||||
|
["Communication Skills", "required"],
|
||||||
|
["Problem-Solving Skills & Critical Thinking", "required"],
|
||||||
|
["Teamwork & Collaborative Abilities", "required"],
|
||||||
|
["Organizational & Time Management Skills", "required"],
|
||||||
|
["Being Self-Driven and Independent", "required"],
|
||||||
|
["Adaptability & Patience", "required"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"groups": {
|
||||||
|
"Operating Systems": ["osBase", "linux", "rtos"],
|
||||||
|
"Microcontrollers": ["rtos", "mcu"],
|
||||||
|
"Interfaces & Protocols": ["basic", "wireless", "highSpeed", "industrial", "cellular", "network", "automotive"]
|
||||||
|
}
|
||||||
|
}
|
||||||
+572
@@ -0,0 +1,572 @@
|
|||||||
|
// Interactive roadmap diagram: Software / Hardware / Soft skills areas with their cross-sections,
|
||||||
|
// importance colors, filters and connections between topics. Laid out by layout.js to fit A4.
|
||||||
|
import { AREAS, CREDITS, GITHUB_MARK, IMPORTANCE, areaDots, areaLabel, h, plural, svg } from './dom.js';
|
||||||
|
import { SPACE, buildLayout } from './layout.js';
|
||||||
|
|
||||||
|
const MIN_ZOOM = 0.6;
|
||||||
|
const MAX_ZOOM = 6;
|
||||||
|
|
||||||
|
function intersect(a, b) {
|
||||||
|
const x = Math.max(a.x, b.x);
|
||||||
|
const y = Math.max(a.y, b.y);
|
||||||
|
const w = Math.min(a.x + a.w, b.x + b.w) - x;
|
||||||
|
const hh = Math.min(a.y + a.h, b.y + b.h) - y;
|
||||||
|
return w > 0 && hh > 0 ? { x, y, w, h: hh } : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const center = (r) => [r.x + r.w / 2, r.y + r.h / 2];
|
||||||
|
const rect = (r, attrs, parent) => svg('rect', { x: r.x, y: r.y, width: r.w, height: r.h, ...attrs }, parent);
|
||||||
|
|
||||||
|
function splitInTwo(text) {
|
||||||
|
const words = text.split(' ');
|
||||||
|
let best = [text];
|
||||||
|
let score = Infinity;
|
||||||
|
for (let i = 1; i < words.length; i += 1) {
|
||||||
|
const a = words.slice(0, i).join(' ');
|
||||||
|
const b = words.slice(i).join(' ');
|
||||||
|
if (Math.max(a.length, b.length) < score) {
|
||||||
|
score = Math.max(a.length, b.length);
|
||||||
|
best = [a, b];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createMapView({ root, plan, data, build, onSelect }) {
|
||||||
|
const layout = buildLayout(plan);
|
||||||
|
const state = { selected: null, hover: null, areas: new Set(), importance: new Set(), showAll: false, view: null, drag: null };
|
||||||
|
const boxEls = new Map();
|
||||||
|
const rectOf = new Map();
|
||||||
|
const legendParts = [];
|
||||||
|
|
||||||
|
// ---------- toolbar ----------
|
||||||
|
const chip = (label, cls, pressed, onclick, title) => h('button', {
|
||||||
|
type: 'button', class: `map-chip ${cls}`, 'aria-pressed': String(pressed), onclick, title,
|
||||||
|
}, label);
|
||||||
|
const toolbar = h('div', { class: 'map-toolbar', role: 'toolbar', 'aria-label': 'Highlight topics' });
|
||||||
|
const summary = h('span', { class: 'map-summary', role: 'status' });
|
||||||
|
|
||||||
|
function renderToolbar() {
|
||||||
|
const cross = state.areas.size === 2 && state.areas.has('SOFTWARE') && state.areas.has('HARDWARE');
|
||||||
|
toolbar.replaceChildren(...[
|
||||||
|
h('span', { class: 'map-toolbar-label' }, 'Highlight'),
|
||||||
|
Object.entries(AREAS).map(([area, info]) => chip(h('span', {}, h('i', { class: `dot ${info.cls}` }), info.label),
|
||||||
|
'', state.areas.has(area) && !cross, () => {
|
||||||
|
state.areas = state.areas.has(area) && !cross ? new Set() : new Set([area]);
|
||||||
|
refresh();
|
||||||
|
}, `Topics in ${info.label}`)),
|
||||||
|
chip(h('span', {}, h('i', { class: 'dot both' }), 'Software ∩ Hardware'), 'cross', cross, () => {
|
||||||
|
state.areas = cross ? new Set() : new Set(['SOFTWARE', 'HARDWARE']);
|
||||||
|
refresh();
|
||||||
|
}, 'Topics that belong to both Software and Hardware'),
|
||||||
|
h('span', { class: 'map-toolbar-sep', 'aria-hidden': 'true' }),
|
||||||
|
Object.entries(IMPORTANCE).map(([level, label]) => chip(h('span', {}, h('i', { class: `imp-swatch ${level}` }), label),
|
||||||
|
'', state.importance.has(level), () => toggle(state.importance, level))),
|
||||||
|
h('span', { class: 'map-toolbar-sep', 'aria-hidden': 'true' }),
|
||||||
|
chip('Show all connections', 'links', state.showAll, () => { state.showAll = !state.showAll; refresh(); },
|
||||||
|
'Lines between topics that share resources or mention each other'),
|
||||||
|
state.areas.size || state.importance.size ? chip('Clear', 'clear', false, () => {
|
||||||
|
state.areas.clear();
|
||||||
|
state.importance.clear();
|
||||||
|
refresh();
|
||||||
|
}) : null,
|
||||||
|
summary,
|
||||||
|
].flat().filter(Boolean));
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle(set, value) {
|
||||||
|
if (set.has(value)) set.delete(value);
|
||||||
|
else set.add(value);
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
// README topics that the diagram does not cover.
|
||||||
|
const extra = data.root.children.filter((t) => !t.areas.length);
|
||||||
|
const strip = h('nav', { class: 'start-strip', 'aria-label': 'Getting started topics' },
|
||||||
|
h('span', { class: 'map-toolbar-label' }, 'Start here'),
|
||||||
|
extra.map((t) => h('button', { type: 'button', class: 'start-chip', onclick: () => onSelect(t) },
|
||||||
|
t.icon ? h('span', { 'aria-hidden': 'true' }, t.icon) : null, t.title)));
|
||||||
|
|
||||||
|
// ---------- svg ----------
|
||||||
|
const chart = svg('svg', {
|
||||||
|
class: 'diagram',
|
||||||
|
role: 'group',
|
||||||
|
'aria-label': 'Roadmap diagram. Tab moves between topics, Enter opens one.',
|
||||||
|
preserveAspectRatio: 'xMidYMid meet',
|
||||||
|
});
|
||||||
|
const defs = svg('defs', {}, chart);
|
||||||
|
const hatch = svg('pattern', { id: 'cross-hatch', width: 10, height: 10, patternUnits: 'userSpaceOnUse', patternTransform: 'rotate(45)' }, defs);
|
||||||
|
svg('line', { x1: 0, y1: 0, x2: 0, y2: 10, class: 'hatch-a' }, hatch);
|
||||||
|
svg('line', { x1: 5, y1: 0, x2: 5, y2: 10, class: 'hatch-b' }, hatch);
|
||||||
|
|
||||||
|
const layers = {};
|
||||||
|
for (const name of ['page', 'areas', 'overlap', 'outlines', 'groups', 'linksAll', 'links', 'boxes', 'labels']) {
|
||||||
|
layers[name] = svg('g', { class: `layer-${name}` }, chart);
|
||||||
|
}
|
||||||
|
|
||||||
|
rect({ x: 0, y: 0, w: layout.width, h: layout.height }, { class: 'page-bg' }, layers.page);
|
||||||
|
|
||||||
|
// Title and legend, so the diagram also stands on its own when printed.
|
||||||
|
const title = svg('text', { x: 24, y: layout.titleY, class: 'page-title' }, layers.labels);
|
||||||
|
title.textContent = 'Embedded Systems Engineering Roadmap';
|
||||||
|
const legend = svg('g', { class: 'page-legend' }, layers.labels);
|
||||||
|
const legendItems = [
|
||||||
|
...Object.entries(IMPORTANCE).map(([level, label]) => ({ kind: 'imp', cls: level, label })),
|
||||||
|
{ kind: 'area', cls: 'sw', label: 'Software' },
|
||||||
|
{ kind: 'area', cls: 'hw', label: 'Hardware' },
|
||||||
|
{ kind: 'hatch', cls: '', label: 'Software ∩ Hardware' },
|
||||||
|
{ kind: 'area', cls: 'ss', label: 'Soft skills' },
|
||||||
|
];
|
||||||
|
for (const item of legendItems) {
|
||||||
|
const y = layout.titleY + 22;
|
||||||
|
const cls = item.kind === 'imp' ? `legend-imp ${item.cls}` : item.kind === 'area' ? `legend-area ${item.cls}` : 'legend-hatch';
|
||||||
|
const key = rect({ x: 0, y, w: 22, h: 13 }, { rx: item.kind === 'imp' ? 3 : 4, class: cls }, legend);
|
||||||
|
const t = svg('text', { x: 0, y: y + 11, class: 'legend-text' }, legend);
|
||||||
|
t.textContent = item.label;
|
||||||
|
legendParts.push({ key, t, label: item.label });
|
||||||
|
}
|
||||||
|
|
||||||
|
function layoutLegend() {
|
||||||
|
let x = 26;
|
||||||
|
for (const part of legendParts) {
|
||||||
|
part.key.setAttribute('x', x);
|
||||||
|
part.t.setAttribute('x', x + 28);
|
||||||
|
x += 28 + ((part.t.getComputedTextLength && part.t.getComputedTextLength()) || part.label.length * 6.5) + 20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const note = svg('text', { x: 24, y: layout.titleY + 54, class: 'legend-note' }, layers.labels);
|
||||||
|
note.textContent = 'Colors show the average importance of each skill for an embedded systems engineer.';
|
||||||
|
|
||||||
|
// Areas and their cross-sections.
|
||||||
|
const sw = layout.areas.SOFTWARE;
|
||||||
|
const hw = layout.areas.HARDWARE;
|
||||||
|
const ss = layout.areas['SOFT SKILLS'];
|
||||||
|
rect(sw, { rx: 28, class: 'area-fill sw' }, layers.areas);
|
||||||
|
rect(hw, { rx: 28, class: 'area-fill hw' }, layers.areas);
|
||||||
|
rect(ss, { rx: 28, class: 'area-fill ss' }, layers.areas);
|
||||||
|
rect(layout.cross, { class: 'overlap-fill' }, layers.overlap);
|
||||||
|
rect(sw, { rx: 28, class: 'area-line sw' }, layers.outlines);
|
||||||
|
rect(hw, { rx: 28, class: 'area-line hw' }, layers.outlines);
|
||||||
|
rect(ss, { rx: 28, class: 'area-line ss' }, layers.outlines);
|
||||||
|
|
||||||
|
// Band names run down the left margin; every band reads as one area or a cross-section.
|
||||||
|
for (const band of layout.bands) {
|
||||||
|
const cy = (band.y0 + band.y1) / 2;
|
||||||
|
const t = svg('text', {
|
||||||
|
x: 0, y: 0, class: `band-label ${band.cls}`, 'text-anchor': 'middle', 'dominant-baseline': 'central',
|
||||||
|
transform: `translate(${sw.x + 18} ${cy.toFixed(1)}) rotate(-90)`,
|
||||||
|
}, layers.labels);
|
||||||
|
t.textContent = band.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tags = [];
|
||||||
|
function tag(text, cls, r, topic = null) {
|
||||||
|
const g = svg('g', { class: `tag ${cls}` }, layers.labels);
|
||||||
|
const bg = svg('rect', { class: 'tag-bg', height: 22, rx: 11 }, g);
|
||||||
|
const label = svg('text', { class: 'tag-text', 'dominant-baseline': 'central' }, g);
|
||||||
|
label.textContent = text;
|
||||||
|
const entry = { g, bg, label, text, r };
|
||||||
|
tags.push(entry);
|
||||||
|
if (topic) wireTarget(g, topic, null, `${text} group`);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group labels all sit on the top-left of their own border.
|
||||||
|
for (const group of layout.groups) {
|
||||||
|
rect(group.rect, { rx: 16, class: `group-line ${group.cls}` }, layers.groups);
|
||||||
|
tag(group.name, `group-tag ${group.cls}`, group.rect, group.topics[0] || null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function layoutTags() {
|
||||||
|
for (const t of tags) {
|
||||||
|
const width = (t.label.getComputedTextLength && t.label.getComputedTextLength()) || t.text.length * 7;
|
||||||
|
const x = t.r.x + 22;
|
||||||
|
const y = t.r.y - 11;
|
||||||
|
t.bg.setAttribute('x', x.toFixed(1));
|
||||||
|
t.bg.setAttribute('y', y.toFixed(1));
|
||||||
|
t.bg.setAttribute('width', (width + 22).toFixed(1));
|
||||||
|
t.label.setAttribute('x', (x + 11).toFixed(1));
|
||||||
|
t.label.setAttribute('y', (y + 11).toFixed(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Credits: where the resources are, who maintains the roadmap, its license and revision.
|
||||||
|
const { x: fx, y: fy, w: fw } = layout.footer;
|
||||||
|
const fr = fx + fw;
|
||||||
|
const credits = svg('g', { class: 'page-credits' }, layers.labels);
|
||||||
|
const link = (href) => svg('a', { href, target: '_blank', rel: 'noopener noreferrer' }, credits);
|
||||||
|
const icon = svg('svg', { x: fx, y: fy + 2, width: 16, height: 16, viewBox: '0 0 16 16' }, credits);
|
||||||
|
svg('path', { d: GITHUB_MARK, class: 'credit-icon' }, icon);
|
||||||
|
const repo = svg('text', { x: fx + 24, y: fy + 15, class: 'credit-main' }, link(`https://${CREDITS.repo}`));
|
||||||
|
repo.textContent = `Learning resources for every topic: ${CREDITS.repo}`;
|
||||||
|
const creditLine = (y, label, value) => {
|
||||||
|
const t = svg('text', { x: fx, y, class: 'credit-text' }, credits);
|
||||||
|
t.append(label);
|
||||||
|
svg('tspan', { class: 'credit-strong' }, t).textContent = value;
|
||||||
|
};
|
||||||
|
creditLine(fy + 35, 'Creator & Maintainer: ', CREDITS.author);
|
||||||
|
if (build) creditLine(fy + 53, 'Revision: ', `${build.version} · Last update: ${build.date}`);
|
||||||
|
const logoSize = 52;
|
||||||
|
const badge = { w: 103, h: 36 };
|
||||||
|
svg('image', { href: 'assets/logo.svg', x: fr - logoSize, y: fy, width: logoSize, height: logoSize }, credits);
|
||||||
|
const badgeX = fr - logoSize - SPACE - badge.w;
|
||||||
|
svg('image', { href: 'assets/cc-by-sa.svg', x: badgeX, y: fy + (logoSize - badge.h) / 2, width: badge.w, height: badge.h }, link(CREDITS.licenseUrl));
|
||||||
|
const licenseText = svg('text', { x: badgeX - 14, y: fy + 22, 'text-anchor': 'end', class: 'credit-text' }, link(CREDITS.licenseUrl));
|
||||||
|
licenseText.textContent = 'This work is licensed under the Creative Commons';
|
||||||
|
const licenseText2 = svg('text', { x: badgeX - 14, y: fy + 40, 'text-anchor': 'end', class: 'credit-text' }, link(CREDITS.licenseUrl));
|
||||||
|
licenseText2.textContent = 'Attribution-ShareAlike 4.0 International License.';
|
||||||
|
|
||||||
|
// ---------- topics ----------
|
||||||
|
for (const item of layout.items) {
|
||||||
|
const box = item.box;
|
||||||
|
if (item.kind === 'caption') {
|
||||||
|
const t = svg('text', { x: item.x, y: item.y + 15, class: 'cluster-caption' }, layers.boxes);
|
||||||
|
t.textContent = item.text.toUpperCase();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const topic = box.topics[0] || null;
|
||||||
|
const g = svg('g', { class: `box ${item.kind === 'header' ? 'header' : box.importance}`, 'data-box': box.id }, layers.boxes);
|
||||||
|
rectOf.set(box, item);
|
||||||
|
if (item.kind === 'header') {
|
||||||
|
const t = svg('text', { x: item.x, y: item.y + 16, class: 'box-text header-text' }, g);
|
||||||
|
t.textContent = item.text;
|
||||||
|
boxEls.set(box, { g, text: t, width: item.w, header: true });
|
||||||
|
} else {
|
||||||
|
rect(item, { rx: 5, class: 'box-bg' }, g);
|
||||||
|
const lines = item.h >= 36 ? splitInTwo(item.text) : [item.text];
|
||||||
|
const t = svg('text', { x: item.x + item.w / 2, y: item.y + item.h / 2, 'text-anchor': 'middle', class: 'box-text' }, g);
|
||||||
|
lines.forEach((line, i) => {
|
||||||
|
const span = svg('tspan', { x: item.x + item.w / 2, dy: i === 0 ? `${0.36 - (lines.length - 1) * 0.55}em` : '1.1em' }, t);
|
||||||
|
span.textContent = line;
|
||||||
|
});
|
||||||
|
boxEls.set(box, { g, text: t, width: item.w - 10 });
|
||||||
|
}
|
||||||
|
for (const t of box.topics) {
|
||||||
|
if (!t.layoutBoxes) t.layoutBoxes = [];
|
||||||
|
t.layoutBoxes.push(box);
|
||||||
|
}
|
||||||
|
if (topic) wireTarget(g, topic, box);
|
||||||
|
}
|
||||||
|
|
||||||
|
function describe(topic, box) {
|
||||||
|
const parts = [box ? box.text : topic.title];
|
||||||
|
if (topic.areas.length) parts.push(areaLabel(topic.areas));
|
||||||
|
const importance = (box && box.importance) || topic.importance;
|
||||||
|
if (importance) parts.push(IMPORTANCE[importance]);
|
||||||
|
parts.push(plural(topic.total, 'resource'));
|
||||||
|
return parts.join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
function wireTarget(el, topic, box = null, label = null) {
|
||||||
|
el.setAttribute('tabindex', '0');
|
||||||
|
el.setAttribute('role', 'button');
|
||||||
|
el.setAttribute('aria-label', label ? `${label}, ${plural(topic.total, 'resource')}` : describe(topic, box));
|
||||||
|
el.classList.add('target');
|
||||||
|
el.addEventListener('pointerenter', (e) => hover(topic, box, e));
|
||||||
|
el.addEventListener('pointermove', moveTip);
|
||||||
|
el.addEventListener('pointerleave', () => hover(null));
|
||||||
|
el.addEventListener('focus', () => hover(topic, box));
|
||||||
|
el.addEventListener('blur', () => hover(null));
|
||||||
|
el.addEventListener('click', () => {
|
||||||
|
if (state.drag && state.drag.moved) return;
|
||||||
|
onSelect(topic);
|
||||||
|
});
|
||||||
|
el.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
onSelect(topic);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- tooltip ----------
|
||||||
|
const tip = h('div', { class: 'tooltip', hidden: true });
|
||||||
|
function hover(topic, box = null, e = null) {
|
||||||
|
state.hover = topic;
|
||||||
|
drawLinks();
|
||||||
|
if (!topic || !e) {
|
||||||
|
tip.hidden = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const importance = (box && box.importance) || topic.importance;
|
||||||
|
tip.replaceChildren(...[
|
||||||
|
h('strong', {}, topic.title),
|
||||||
|
topic.areas.length ? h('div', { class: 'tip-areas' }, areaDots(topic.areas), areaLabel(topic.areas)) : null,
|
||||||
|
h('div', { class: 'tip-meta' }, [importance ? IMPORTANCE[importance] : null, plural(topic.total, 'resource'),
|
||||||
|
topic.links && topic.links.length ? `${topic.links.length} connected` : null].filter(Boolean).join(' · ')),
|
||||||
|
h('div', { class: 'tip-hint' }, 'Click for details and resources'),
|
||||||
|
].filter(Boolean));
|
||||||
|
tip.hidden = false;
|
||||||
|
moveTip(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveTip(e) {
|
||||||
|
if (tip.hidden) return;
|
||||||
|
const bounds = root.getBoundingClientRect();
|
||||||
|
let x = e.clientX - bounds.left + 16;
|
||||||
|
let y = e.clientY - bounds.top + 16;
|
||||||
|
if (x + tip.offsetWidth > bounds.width - 8) x = e.clientX - bounds.left - tip.offsetWidth - 16;
|
||||||
|
if (y + tip.offsetHeight > bounds.height - 8) y = e.clientY - bounds.top - tip.offsetHeight - 16;
|
||||||
|
tip.style.transform = `translate(${Math.max(8, x)}px, ${Math.max(8, y)}px)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- connections ----------
|
||||||
|
const connections = plan.connections.filter((c) => rectOf.has(c.a) && rectOf.has(c.b));
|
||||||
|
|
||||||
|
function curve(a, b) {
|
||||||
|
const [x1, y1] = center(rectOf.get(a));
|
||||||
|
const [x2, y2] = center(rectOf.get(b));
|
||||||
|
const mx = (x1 + x2) / 2;
|
||||||
|
const my = (y1 + y2) / 2;
|
||||||
|
const bend = 0.2;
|
||||||
|
return `M${x1.toFixed(1)} ${y1.toFixed(1)}Q${(mx - (y2 - y1) * bend).toFixed(1)} ${(my + (x2 - x1) * bend).toFixed(1)} ${x2.toFixed(1)} ${y2.toFixed(1)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawConnection(connection, layer, cls) {
|
||||||
|
svg('path', {
|
||||||
|
d: curve(connection.a, connection.b),
|
||||||
|
class: `${cls} ${connection.shared ? 'shared' : 'mention'}`,
|
||||||
|
'stroke-width': (1.4 + Math.min(3, Math.sqrt(connection.weight) * 0.7)).toFixed(2),
|
||||||
|
}, layer);
|
||||||
|
if (cls !== 'link') return;
|
||||||
|
for (const box of [connection.a, connection.b]) {
|
||||||
|
const [cx, cy] = center(rectOf.get(box));
|
||||||
|
svg('circle', { cx: cx.toFixed(1), cy: cy.toFixed(1), r: 3, class: 'link-end' }, layer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const connection of connections) drawConnection(connection, layers.linksAll, 'link-all');
|
||||||
|
|
||||||
|
const isWithin = (node, topic) => {
|
||||||
|
for (let n = node; n; n = n.parent) if (n === topic) return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
function drawLinks() {
|
||||||
|
layers.links.replaceChildren();
|
||||||
|
const topic = state.hover || state.selected;
|
||||||
|
const lit = new Set();
|
||||||
|
if (topic) {
|
||||||
|
const own = new Set(plan.boxes.filter((b) => rectOf.has(b) && b.topics.some((t) => isWithin(t, topic))));
|
||||||
|
const fallback = plan.boxFor(topic);
|
||||||
|
if (fallback && rectOf.has(fallback)) own.add(fallback);
|
||||||
|
for (const b of own) lit.add(b);
|
||||||
|
for (const connection of connections) {
|
||||||
|
if (!(own.has(connection.a) || own.has(connection.b))) continue;
|
||||||
|
if (!connection.links.some((l) => isWithin(l.source, topic) || isWithin(l.target, topic))) continue;
|
||||||
|
drawConnection(connection, layers.links, 'link');
|
||||||
|
lit.add(connection.a);
|
||||||
|
lit.add(connection.b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chart.classList.toggle('has-focus', Boolean(state.hover));
|
||||||
|
for (const [box, el] of boxEls) {
|
||||||
|
el.g.classList.toggle('lit', lit.has(box));
|
||||||
|
el.g.classList.toggle('selected', Boolean(state.selected) && box.topics.includes(state.selected));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- filters ----------
|
||||||
|
function matches(box) {
|
||||||
|
if (!rectOf.has(box) || boxEls.get(box).header) return false;
|
||||||
|
for (const area of state.areas) if (!box.areas.includes(area)) return false;
|
||||||
|
if (state.areas.size === 1 && box.areas.filter((a) => a !== 'SOFT SKILLS').length > 1 && !state.areas.has('SOFT SKILLS')) {
|
||||||
|
// A single area means "only this area", so the cross-section stands apart.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !state.importance.size || state.importance.has(box.importance);
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh() {
|
||||||
|
const filtering = state.areas.size > 0 || state.importance.size > 0;
|
||||||
|
chart.classList.toggle('filtering', filtering);
|
||||||
|
chart.classList.toggle('show-all', state.showAll);
|
||||||
|
let count = 0;
|
||||||
|
for (const [box, el] of boxEls) {
|
||||||
|
const match = filtering && matches(box);
|
||||||
|
if (match) count += 1;
|
||||||
|
el.g.classList.toggle('match', match);
|
||||||
|
}
|
||||||
|
summary.textContent = filtering ? `${plural(count, 'topic')}` : '';
|
||||||
|
renderToolbar();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- pan and zoom ----------
|
||||||
|
const page = { w: layout.width, h: layout.height };
|
||||||
|
const MAX_SCALE = 1.5;
|
||||||
|
let fitMode = 'width';
|
||||||
|
|
||||||
|
function canvasSize() {
|
||||||
|
const box = chart.getBoundingClientRect();
|
||||||
|
return box.width > 0 && box.height > 0 ? { w: box.width, h: box.height } : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The view always has the canvas' aspect ratio, so the drawing fills it without letterboxing.
|
||||||
|
function setView(view) {
|
||||||
|
const size = canvasSize();
|
||||||
|
const v = { ...view };
|
||||||
|
v.h = size ? v.w * (size.h / size.w) : v.h || v.w * (page.h / page.w);
|
||||||
|
const slackX = Math.max(0, page.w - v.w);
|
||||||
|
const slackY = Math.max(0, page.h - v.h);
|
||||||
|
v.x = slackX ? Math.min(Math.max(v.x, -40), slackX + 40) : (page.w - v.w) / 2;
|
||||||
|
v.y = slackY ? Math.min(Math.max(v.y, -40), slackY + 40) : (page.h - v.h) / 2;
|
||||||
|
state.view = v;
|
||||||
|
chart.setAttribute('viewBox', `${v.x.toFixed(1)} ${v.y.toFixed(1)} ${v.w.toFixed(1)} ${v.h.toFixed(1)}`);
|
||||||
|
if (size) zoomLevel.textContent = `${Math.round((size.w / v.w) * 100)}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fitWidth() {
|
||||||
|
const size = canvasSize();
|
||||||
|
const width = size ? Math.max(page.w + 40, size.w / MAX_SCALE) : page.w;
|
||||||
|
fitMode = 'width';
|
||||||
|
setView({ x: (page.w - width) / 2, y: -20, w: width });
|
||||||
|
fitButton.title = 'Show the whole page';
|
||||||
|
}
|
||||||
|
|
||||||
|
function fitPage() {
|
||||||
|
const size = canvasSize();
|
||||||
|
const aspect = size ? size.h / size.w : page.h / page.w;
|
||||||
|
const width = Math.max(page.w, (page.h + 40) / aspect) + 40;
|
||||||
|
fitMode = 'page';
|
||||||
|
setView({ x: (page.w - width) / 2, y: 0, w: width });
|
||||||
|
fitButton.title = 'Fit the width';
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleFit = () => (fitMode === 'width' ? fitPage() : fitWidth());
|
||||||
|
|
||||||
|
function toChart(clientX, clientY) {
|
||||||
|
const matrix = chart.getScreenCTM();
|
||||||
|
if (!matrix) return [0, 0];
|
||||||
|
const p = new DOMPoint(clientX, clientY).matrixTransform(matrix.inverse());
|
||||||
|
return [p.x, p.y];
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoomAt(factor, clientX, clientY) {
|
||||||
|
const v = state.view;
|
||||||
|
const size = canvasSize();
|
||||||
|
if (!size) return;
|
||||||
|
const scale = (size.w / v.w) * factor;
|
||||||
|
if (scale < (size.w / page.w) * MIN_ZOOM || scale > MAX_ZOOM) return;
|
||||||
|
const [px, py] = clientX == null ? [v.x + v.w / 2, v.y + v.h / 2] : toChart(clientX, clientY);
|
||||||
|
fitMode = 'custom';
|
||||||
|
setView({ x: px - (px - v.x) / factor, y: py - (py - v.y) / factor, w: v.w / factor });
|
||||||
|
}
|
||||||
|
|
||||||
|
chart.addEventListener('wheel', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (e.ctrlKey || e.metaKey) {
|
||||||
|
zoomAt(Math.exp(-e.deltaY * 0.0025), e.clientX, e.clientY);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const size = canvasSize();
|
||||||
|
if (!size) return;
|
||||||
|
const unit = state.view.w / size.w;
|
||||||
|
const dx = e.shiftKey && !e.deltaX ? e.deltaY : e.deltaX;
|
||||||
|
const dy = e.shiftKey && !e.deltaX ? 0 : e.deltaY;
|
||||||
|
setView({ ...state.view, x: state.view.x + dx * unit, y: state.view.y + dy * unit });
|
||||||
|
}, { passive: false });
|
||||||
|
chart.addEventListener('pointerdown', (e) => {
|
||||||
|
if (e.button !== 0) return;
|
||||||
|
state.drag = { x: e.clientX, y: e.clientY, view: { ...state.view }, moved: false, id: e.pointerId };
|
||||||
|
});
|
||||||
|
chart.addEventListener('pointermove', (e) => {
|
||||||
|
const drag = state.drag;
|
||||||
|
if (!drag || drag.id !== e.pointerId) return;
|
||||||
|
const dx = e.clientX - drag.x;
|
||||||
|
const dy = e.clientY - drag.y;
|
||||||
|
if (!drag.moved && Math.hypot(dx, dy) < 5) return;
|
||||||
|
if (!drag.moved) {
|
||||||
|
drag.moved = true;
|
||||||
|
chart.setPointerCapture(e.pointerId);
|
||||||
|
chart.classList.add('panning');
|
||||||
|
}
|
||||||
|
const scale = drag.view.w / chart.getBoundingClientRect().width;
|
||||||
|
fitMode = 'custom';
|
||||||
|
setView({ ...drag.view, x: drag.view.x - dx * scale, y: drag.view.y - dy * scale });
|
||||||
|
});
|
||||||
|
const endDrag = (e) => {
|
||||||
|
if (!state.drag || state.drag.id !== e.pointerId) return;
|
||||||
|
chart.classList.remove('panning');
|
||||||
|
// The click that follows a drag still needs to see `moved`.
|
||||||
|
window.setTimeout(() => { state.drag = null; }, 0);
|
||||||
|
};
|
||||||
|
chart.addEventListener('pointerup', endDrag);
|
||||||
|
chart.addEventListener('pointercancel', endDrag);
|
||||||
|
chart.addEventListener('dblclick', toggleFit);
|
||||||
|
|
||||||
|
const zoomLevel = h('span', { class: 'zoom-level', 'aria-live': 'polite' }, '100%');
|
||||||
|
const fitButton = h('button', { type: 'button', class: 'icon-btn', 'aria-label': 'Toggle between fitting the width and the whole page', title: 'Show the whole page', onclick: toggleFit }, '⤢');
|
||||||
|
const zoomControls = h('div', { class: 'zoom-controls' },
|
||||||
|
h('button', { type: 'button', class: 'icon-btn', 'aria-label': 'Zoom in', title: 'Zoom in (Ctrl + scroll)', onclick: () => zoomAt(1.25) }, '+'),
|
||||||
|
zoomLevel,
|
||||||
|
h('button', { type: 'button', class: 'icon-btn', 'aria-label': 'Zoom out', title: 'Zoom out (Ctrl + scroll)', onclick: () => zoomAt(1 / 1.25) }, '−'),
|
||||||
|
fitButton);
|
||||||
|
|
||||||
|
root.replaceChildren(
|
||||||
|
h('div', { class: 'map-top' }, strip, toolbar),
|
||||||
|
h('div', { class: 'map-canvas' }, chart, zoomControls),
|
||||||
|
tip,
|
||||||
|
);
|
||||||
|
fitWidth();
|
||||||
|
refresh();
|
||||||
|
layoutLegend();
|
||||||
|
layoutTags();
|
||||||
|
// Tag and legend widths depend on the web font, so measure again once it has loaded.
|
||||||
|
document.fonts.load('800 14px "Sofia Sans Semi Condensed"').then(() => requestAnimationFrame(relayout), () => {});
|
||||||
|
|
||||||
|
// Keep the same part of the diagram in view when the window or panel changes size.
|
||||||
|
new ResizeObserver(() => {
|
||||||
|
if (!canvasSize() || state.beforePrint) return;
|
||||||
|
if (fitMode === 'width') fitWidth();
|
||||||
|
else if (fitMode === 'page') fitPage();
|
||||||
|
else setView(state.view);
|
||||||
|
}).observe(chart);
|
||||||
|
|
||||||
|
// Long labels are squeezed to fit their box once real text widths are known.
|
||||||
|
function fitText() {
|
||||||
|
for (const [, el] of boxEls) {
|
||||||
|
const spans = el.header ? [el.text] : [...el.text.querySelectorAll('tspan')];
|
||||||
|
for (const span of spans) {
|
||||||
|
span.removeAttribute('textLength');
|
||||||
|
span.removeAttribute('lengthAdjust');
|
||||||
|
if (span.getComputedTextLength() > el.width) {
|
||||||
|
span.setAttribute('textLength', el.width.toFixed(1));
|
||||||
|
span.setAttribute('lengthAdjust', 'spacingAndGlyphs');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function relayout() {
|
||||||
|
layoutLegend();
|
||||||
|
layoutTags();
|
||||||
|
fitText();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
select(topic) {
|
||||||
|
state.selected = topic;
|
||||||
|
drawLinks();
|
||||||
|
},
|
||||||
|
show() {
|
||||||
|
if (fitMode === 'width') fitWidth();
|
||||||
|
relayout();
|
||||||
|
},
|
||||||
|
prepareForPrint() {
|
||||||
|
state.hover = null;
|
||||||
|
tip.hidden = true;
|
||||||
|
state.beforePrint = { view: { ...state.view }, fitMode };
|
||||||
|
state.view = { x: 0, y: 0, w: page.w, h: page.h };
|
||||||
|
chart.setAttribute('viewBox', `0 0 ${page.w} ${page.h}`);
|
||||||
|
relayout();
|
||||||
|
drawLinks();
|
||||||
|
},
|
||||||
|
afterPrint() {
|
||||||
|
if (!state.beforePrint) return;
|
||||||
|
fitMode = state.beforePrint.fitMode;
|
||||||
|
setView(state.beforePrint.view);
|
||||||
|
state.beforePrint = null;
|
||||||
|
},
|
||||||
|
layout,
|
||||||
|
};
|
||||||
|
}
|
||||||
+299
@@ -0,0 +1,299 @@
|
|||||||
|
// Turns the roadmap README into a topic tree with descriptions, resources and cross-links.
|
||||||
|
// Pure module: no DOM access, so it runs in the browser and under `node --test`.
|
||||||
|
|
||||||
|
export const TYPES = {
|
||||||
|
book: { icon: '📘', label: 'Book' },
|
||||||
|
video: { icon: '🎞️', label: 'Video' },
|
||||||
|
article: { icon: '📝', label: 'Article' },
|
||||||
|
link: { icon: '🔗', label: 'Link' },
|
||||||
|
audio: { icon: '🎧', label: 'Audio book' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const TYPE_BY_SYMBOL = { '📘': 'book', '🎞': 'video', '📝': 'article', '🔗': 'link', '🎧': 'audio' };
|
||||||
|
// Heading prefixes the README uses only to mark the heading level.
|
||||||
|
const LEVEL_MARKERS = new Set(['✳', '🔵', '🔶', '🔸']);
|
||||||
|
const SYMBOLS = /^[\p{Extended_Pictographic}\uFE0F\u200D\u200C\u20E3\s]+/u;
|
||||||
|
const INVISIBLE = /[\uFE0F\u200D\u200C\u20E3\s]/u;
|
||||||
|
const URL_PART = String.raw`https?:\/\/[^\s()]+(?:\([^\s()]*\)[^\s()]*)*`;
|
||||||
|
const HEADING = /^(#{1,6})\s+(.+)$/;
|
||||||
|
const RESOURCE = new RegExp(String.raw`^\s*[-*]\s+\[(.+)\]\((${URL_PART})\)(.*)$`, 'u');
|
||||||
|
const HEADING_LINK = new RegExp(String.raw`^\[(.+)\]\((${URL_PART})\)$`, 'u');
|
||||||
|
const BULLET = /^\s*[-*]\s+(.+)$/;
|
||||||
|
const SKIP = /^(\s*>|<!--|!\[|\[!\[|_{3,}\s*$|\|)/;
|
||||||
|
|
||||||
|
function splitSymbols(text) {
|
||||||
|
const match = text.match(SYMBOLS);
|
||||||
|
const prefix = match ? match[0] : '';
|
||||||
|
return {
|
||||||
|
prefix: prefix.trim(),
|
||||||
|
symbols: [...prefix].filter((c) => !INVISIBLE.test(c)),
|
||||||
|
rest: text.slice(prefix.length).replace(/\s+/g, ' ').trim(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function headingInfo(text) {
|
||||||
|
const link = text.match(HEADING_LINK);
|
||||||
|
const { prefix, symbols, rest } = splitSymbols(link ? link[1] : text);
|
||||||
|
const generic = symbols.every((s) => LEVEL_MARKERS.has(s));
|
||||||
|
return { title: rest, icon: generic ? '' : prefix, link: link ? link[2] : null, label: link ? link[1] : null };
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeResource(label, url, trailing = '') {
|
||||||
|
const { symbols, rest } = splitSymbols(label);
|
||||||
|
let type = 'link';
|
||||||
|
for (const s of symbols) if (TYPE_BY_SYMBOL[s]) type = TYPE_BY_SYMBOL[s];
|
||||||
|
let domain = '';
|
||||||
|
try {
|
||||||
|
domain = new URL(url).hostname.replace(/^www\./, '');
|
||||||
|
} catch {
|
||||||
|
domain = '';
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
title: rest,
|
||||||
|
url,
|
||||||
|
domain,
|
||||||
|
type,
|
||||||
|
beginner: symbols.includes('👶'),
|
||||||
|
gem: symbols.includes('💎'),
|
||||||
|
note: trailing.replace(/^\s*[-–—:]\s*/, '').trim(),
|
||||||
|
topic: null,
|
||||||
|
sharedWith: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function slugify(text) {
|
||||||
|
return text
|
||||||
|
.normalize('NFKD')
|
||||||
|
.replace(/[\u0300-\u036f]/g, '')
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/\+\+/g, 'pp')
|
||||||
|
.replace(/#/g, 'sharp')
|
||||||
|
.replace(/[^a-z0-9]+/g, '-')
|
||||||
|
.replace(/^-+|-+$/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeUrl(url) {
|
||||||
|
try {
|
||||||
|
const u = new URL(url);
|
||||||
|
return `${u.hostname.replace(/^www\./, '').toLowerCase()}${u.pathname.replace(/\/+$/, '')}${u.search}`;
|
||||||
|
} catch {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeNode(fields) {
|
||||||
|
return { title: '', icon: '', depth: 0, group: '', parent: null, children: [], resources: [], description: [], ...fields };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseRoadmap(markdown) {
|
||||||
|
const lines = markdown.replace(/\r\n?/g, '\n').split('\n');
|
||||||
|
const root = makeNode({ id: 'root', slug: '', title: 'Embedded Engineering Roadmap' });
|
||||||
|
const groups = [];
|
||||||
|
const stack = [];
|
||||||
|
let target = root;
|
||||||
|
let topic = null;
|
||||||
|
let paragraph = [];
|
||||||
|
|
||||||
|
const flush = () => {
|
||||||
|
if (paragraph.length && target) target.description.push({ type: 'p', text: paragraph.join(' ') });
|
||||||
|
paragraph = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const heading = line.match(HEADING);
|
||||||
|
if (heading) {
|
||||||
|
flush();
|
||||||
|
const level = heading[1].length;
|
||||||
|
if (level === 1) continue;
|
||||||
|
const info = headingInfo(heading[2].trim());
|
||||||
|
if (level === 2) {
|
||||||
|
const group = makeNode({ title: info.title, icon: info.icon, topics: [] });
|
||||||
|
groups.push(group);
|
||||||
|
stack.length = 0;
|
||||||
|
target = group;
|
||||||
|
topic = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
while (stack.length && stack[stack.length - 1].level >= level) stack.pop();
|
||||||
|
const parent = stack.length ? stack[stack.length - 1].node : root;
|
||||||
|
const group = groups[groups.length - 1];
|
||||||
|
const node = makeNode({ title: info.title, icon: info.icon, depth: parent.depth + 1, group: group ? group.title : '', parent });
|
||||||
|
if (info.link) node.resources.push(makeResource(info.label, info.link));
|
||||||
|
parent.children.push(node);
|
||||||
|
if (group && parent === root) group.topics.push(node);
|
||||||
|
stack.push({ level, node });
|
||||||
|
target = node;
|
||||||
|
topic = node;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resource = line.match(RESOURCE);
|
||||||
|
if (resource) {
|
||||||
|
flush();
|
||||||
|
if (topic) topic.resources.push(makeResource(resource[1], resource[2], resource[3]));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SKIP.test(line)) {
|
||||||
|
flush();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bullet = line.match(BULLET);
|
||||||
|
if (bullet) {
|
||||||
|
flush();
|
||||||
|
if (!target) continue;
|
||||||
|
const last = target.description[target.description.length - 1];
|
||||||
|
if (last && last.type === 'ul') last.items.push(bullet[1].trim());
|
||||||
|
else target.description.push({ type: 'ul', items: [bullet[1].trim()] });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!line.trim()) {
|
||||||
|
flush();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Text before the first "##" heading after the badges is the README introduction.
|
||||||
|
if (target === root && root.description.length >= 2) continue;
|
||||||
|
paragraph.push(line.trim());
|
||||||
|
}
|
||||||
|
flush();
|
||||||
|
|
||||||
|
const topics = [];
|
||||||
|
const used = new Set(['']);
|
||||||
|
const walk = (node, path) => {
|
||||||
|
for (const child of node.children) {
|
||||||
|
let slug = slugify(child.title) || 'topic';
|
||||||
|
if (used.has(slug)) slug = `${slug}-${slugify(node.title)}`;
|
||||||
|
for (let i = 2; used.has(slug); i += 1) slug = `${slugify(child.title)}-${i}`;
|
||||||
|
used.add(slug);
|
||||||
|
child.slug = slug;
|
||||||
|
child.id = slug;
|
||||||
|
child.path = path;
|
||||||
|
for (const r of child.resources) r.topic = child;
|
||||||
|
topics.push(child);
|
||||||
|
walk(child, [...path, child.title]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
walk(root, []);
|
||||||
|
|
||||||
|
const total = (node) => {
|
||||||
|
node.total = node.resources.length + node.children.reduce((sum, c) => sum + total(c), 0);
|
||||||
|
return node.total;
|
||||||
|
};
|
||||||
|
total(root);
|
||||||
|
|
||||||
|
const order = new Map(topics.map((t, i) => [t, i]));
|
||||||
|
const linkMap = new Map();
|
||||||
|
const linkFor = (a, b) => {
|
||||||
|
const [source, target] = order.get(a) <= order.get(b) ? [a, b] : [b, a];
|
||||||
|
const key = `${source.id}|${target.id}`;
|
||||||
|
if (!linkMap.has(key)) linkMap.set(key, { source, target, shared: [], mentions: [] });
|
||||||
|
return linkMap.get(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
const byUrl = new Map();
|
||||||
|
for (const node of topics) {
|
||||||
|
for (const r of node.resources) {
|
||||||
|
const key = normalizeUrl(r.url);
|
||||||
|
if (!byUrl.has(key)) byUrl.set(key, []);
|
||||||
|
byUrl.get(key).push(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const list of byUrl.values()) {
|
||||||
|
const owners = [...new Set(list.map((r) => r.topic))];
|
||||||
|
if (owners.length < 2) continue;
|
||||||
|
for (const r of list) r.sharedWith = owners.filter((o) => o !== r.topic);
|
||||||
|
for (let i = 0; i < owners.length; i += 1) {
|
||||||
|
for (let j = i + 1; j < owners.length; j += 1) linkFor(owners[i], owners[j]).shared.push(list[0].title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchers = topics.map((t) => ({ topic: t, patterns: aliases(t.title).map(aliasPattern) }));
|
||||||
|
for (const from of topics) {
|
||||||
|
const texts = [
|
||||||
|
...from.resources.map((r) => `${r.title} ${r.note}`),
|
||||||
|
...from.description.flatMap((b) => (b.type === 'p' ? [b.text] : b.items)),
|
||||||
|
];
|
||||||
|
for (const { topic: to, patterns } of matchers) {
|
||||||
|
if (to === from || isLineage(from, to)) continue;
|
||||||
|
for (const text of texts) {
|
||||||
|
if (patterns.some((p) => p.test(text))) linkFor(from, to).mentions.push({ topic: from, text: plainText(text) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const links = [...linkMap.values()];
|
||||||
|
for (const link of links) link.weight = link.shared.length * 2 + link.mentions.length;
|
||||||
|
for (const t of topics) t.links = [];
|
||||||
|
for (const link of links) {
|
||||||
|
link.source.links.push(link);
|
||||||
|
link.target.links.push(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
root,
|
||||||
|
topics,
|
||||||
|
groups: groups.filter((g) => g.topics.length),
|
||||||
|
links,
|
||||||
|
bySlug: new Map(topics.map((t) => [t.slug, t])),
|
||||||
|
stats: { topics: topics.length, resources: byUrl.size, links: links.length },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Topic names too generic to count as a reference when they appear in another topic's text.
|
||||||
|
const ALIAS_STOPLIST = new Set(['Electronics', 'Make', 'Projects', 'Testing', 'Logic', 'Sensors', 'Actuators', 'Search and Ask!']);
|
||||||
|
|
||||||
|
function aliases(title) {
|
||||||
|
const found = new Set([title]);
|
||||||
|
const base = title.replace(/\s*\(([^)]*)\)\s*/g, (m, inner) => {
|
||||||
|
found.add(inner.trim());
|
||||||
|
return ' ';
|
||||||
|
}).trim();
|
||||||
|
found.add(base);
|
||||||
|
for (const part of base.split(' / ')) found.add(part.trim());
|
||||||
|
return [...found].filter((a) => !ALIAS_STOPLIST.has(a) && (a.length >= 3 || /^[A-Z0-9]{2,}$/.test(a)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function aliasPattern(alias) {
|
||||||
|
const escaped = alias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
return new RegExp(`(?<![\\w+#-])${escaped}(?![\\w+#-])`, 'u');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLineage(a, b) {
|
||||||
|
for (let n = a; n; n = n.parent) if (n === b) return true;
|
||||||
|
for (let n = b; n; n = n.parent) if (n === a) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function plainText(markdown) {
|
||||||
|
return markdown.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1').replace(/[*_`$]/g, '').replace(/\s+/g, ' ').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const ESCAPES = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
||||||
|
const GREEK = { Pi: 'Π', pi: 'π', lambda: 'λ' };
|
||||||
|
|
||||||
|
export function escapeHtml(text) {
|
||||||
|
return text.replace(/[&<>"']/g, (c) => ESCAPES[c]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatText(escaped) {
|
||||||
|
return escaped
|
||||||
|
.replace(/`([^`]+)`/g, '<code>$1</code>')
|
||||||
|
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
||||||
|
.replace(/(^|[\s(])_(.+?)_(?=[\s.,;:)!?]|$)/g, '$1<em>$2</em>')
|
||||||
|
.replace(/\$\$\\(\w+)\$\$/g, (m, name) => GREEK[name] || name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renders README inline markdown to HTML. Input is escaped first; only http(s) links become anchors.
|
||||||
|
export function renderInline(markdown) {
|
||||||
|
const link = /\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g;
|
||||||
|
let html = '';
|
||||||
|
let last = 0;
|
||||||
|
for (const m of markdown.matchAll(link)) {
|
||||||
|
html += formatText(escapeHtml(markdown.slice(last, m.index)));
|
||||||
|
html += `<a href="${escapeHtml(m[2])}" target="_blank" rel="noopener noreferrer">${formatText(escapeHtml(m[1]))}</a>`;
|
||||||
|
last = m.index + m[0].length;
|
||||||
|
}
|
||||||
|
return html + formatText(escapeHtml(markdown.slice(last)));
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import { test } from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
import { parseRoadmap, renderInline } from './parser.js';
|
||||||
|
import { linkDiagram } from './diagram.js';
|
||||||
|
import { SPACE, buildLayout } from './layout.js';
|
||||||
|
|
||||||
|
const data = parseRoadmap(readFileSync(new URL('../README.md', import.meta.url), 'utf8'));
|
||||||
|
const find = (title) => data.topics.find((t) => t.title === title);
|
||||||
|
const linked = (a, b) => data.links.find((l) => (l.source === a && l.target === b) || (l.source === b && l.target === a));
|
||||||
|
|
||||||
|
test('builds the topic tree from the README headings', () => {
|
||||||
|
assert.ok(data.topics.length > 100);
|
||||||
|
assert.equal(find('Microcontrollers').depth, 1);
|
||||||
|
assert.ok(find('Microcontrollers').children.some((c) => c.title === 'GPIO'));
|
||||||
|
assert.equal(find('1-Wire').parent.title, 'Basic Protocols');
|
||||||
|
assert.equal(find('High-Performance Computing (HPC)').parent.title, 'Multithreading & Parallel Processing');
|
||||||
|
assert.equal(find('History'), undefined);
|
||||||
|
assert.deepEqual(data.groups.map((g) => g.title), ["Don't Know Where to Start!", 'Learning Resources']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('gives every topic a unique slug', () => {
|
||||||
|
assert.equal(new Set(data.topics.map((t) => t.slug)).size, data.topics.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('parses resource markers, bracketed titles and URLs with parentheses', () => {
|
||||||
|
const beej = find('C').resources.find((r) => r.title.startsWith("Beej's Guide"));
|
||||||
|
assert.deepEqual([beej.type, beej.beginner, beej.gem], ['book', true, false]);
|
||||||
|
const j1939 = find('CAN').resources.find((r) => r.title.startsWith('J1939'));
|
||||||
|
assert.equal(j1939.title, 'J1939 Explained - A Simple Intro [v2.0 | 2021]');
|
||||||
|
assert.match(find('PCM').resources[0].url, /_04\(1\)\.pdf$/);
|
||||||
|
assert.equal(find('GoogleTest - Google Testing and Mocking Framework').resources[0].url, 'https://github.com/google/googletest');
|
||||||
|
assert.match(find('Career Development').resources[0].note, /job board/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('keeps descriptions and the README introduction', () => {
|
||||||
|
assert.match(data.root.description[0].text, /^This roadmap is designed/);
|
||||||
|
assert.match(find('Microcontrollers').description[0].text, /^Microcontrollers are integrated circuits/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('links topics that share a resource or mention each other', () => {
|
||||||
|
assert.ok(linked(find('Docker'), find('CI/CD Pipelines')).shared.length > 0);
|
||||||
|
assert.ok(linked(find('I2C'), find('Sensors')).mentions.length > 0);
|
||||||
|
assert.equal(linked(find('Microcontrollers'), find('GPIO')), undefined, 'parent and child are not cross-linked');
|
||||||
|
assert.equal(linked(find('Thread'), find('RT-Thread')), undefined, 'RT-Thread is not a mention of Thread');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renderInline escapes HTML and only links http(s) URLs', () => {
|
||||||
|
assert.equal(
|
||||||
|
renderInline('<b>x</b> **y** [a](https://e.com/?q=1&r=2) [j](javascript:alert(1))'),
|
||||||
|
'<b>x</b> <strong>y</strong> <a href="https://e.com/?q=1&r=2" target="_blank" rel="noopener noreferrer">a</a> [j](javascript:alert(1))',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const map = JSON.parse(readFileSync(new URL('./map.json', import.meta.url), 'utf8'));
|
||||||
|
|
||||||
|
test('places topics in the map areas and their cross-section', () => {
|
||||||
|
const plan = linkDiagram(map, data);
|
||||||
|
assert.deepEqual(plan.unmatched, [], 'every map topic matches a README heading (or an alias in diagram.js)');
|
||||||
|
assert.deepEqual(plan.invalid, [], 'importance is required, recommended or possible');
|
||||||
|
assert.deepEqual(find('GPIO').areas, ['SOFTWARE', 'HARDWARE']);
|
||||||
|
assert.deepEqual(find('I2C').areas, ['SOFTWARE', 'HARDWARE']);
|
||||||
|
assert.deepEqual(find('Sensors & Actuators').areas, ['SOFTWARE', 'HARDWARE']);
|
||||||
|
assert.deepEqual(find('GDB').areas, ['SOFTWARE']);
|
||||||
|
assert.deepEqual(find('Oscilloscope').areas, ['HARDWARE']);
|
||||||
|
assert.deepEqual(find('Soft Skills').areas, ['SOFT SKILLS']);
|
||||||
|
assert.deepEqual(find('Microcontrollers').areas, ['SOFTWARE', 'HARDWARE']);
|
||||||
|
assert.equal(find('RTOS Basics').importance, 'required');
|
||||||
|
assert.equal(find('Rust').importance, 'possible');
|
||||||
|
assert.deepEqual(find('Linux Device Drivers').areas, ['SOFTWARE']);
|
||||||
|
assert.equal(find('Projects').areas.length, 0, 'getting-started topics are not on the map');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('lays the map out on one A4 page with a minimum spacing between boxes', () => {
|
||||||
|
const layout = buildLayout(linkDiagram(map, data));
|
||||||
|
assert.deepEqual(layout.missing, [], 'every cluster in map.json has a place in layout.js');
|
||||||
|
assert.ok(layout.height / layout.width <= Math.SQRT2, 'fits A4 portrait');
|
||||||
|
const boxes = layout.items.filter((i) => i.kind === 'box');
|
||||||
|
const inside = (a, b) => a.x >= b.x && a.y >= b.y && a.x + a.w <= b.x + b.w && a.y + a.h <= b.y + b.h;
|
||||||
|
const gap = (a, b) => Math.max(b.x - a.x - a.w, a.x - b.x - b.w, b.y - a.y - a.h, a.y - b.y - b.h);
|
||||||
|
for (const [i, a] of boxes.entries()) {
|
||||||
|
for (const b of boxes.slice(i + 1)) assert.ok(gap(a, b) >= SPACE - 0.01, `${a.text} / ${b.text}`);
|
||||||
|
for (const g of layout.groups) {
|
||||||
|
if (!inside(a, g.rect)) continue;
|
||||||
|
const r = g.rect;
|
||||||
|
const d = Math.min(a.x - r.x, a.y - r.y, r.x + r.w - a.x - a.w, r.y + r.h - a.y - a.h);
|
||||||
|
assert.ok(d >= SPACE - 0.01, `${a.text} too close to the ${g.name} outline`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
+1148
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,78 @@
|
|||||||
|
// Renders the roadmap map to an A4 PDF and a PNG with headless Chromium, straight from the site.
|
||||||
|
// Usage: node site/tools/render.mjs [rootDir] [pagePath] [outDir]
|
||||||
|
// defaults: repository root, /site/, ./dist (needs: npm install --no-save playwright)
|
||||||
|
import { createServer } from 'node:http';
|
||||||
|
import { mkdir, readFile } from 'node:fs/promises';
|
||||||
|
import { extname, join, resolve, sep } from 'node:path';
|
||||||
|
import { chromium } from 'playwright';
|
||||||
|
|
||||||
|
const root = resolve(process.argv[2] || '.');
|
||||||
|
const pagePath = process.argv[3] || '/site/';
|
||||||
|
const outDir = resolve(process.argv[4] || 'dist');
|
||||||
|
const NAME = 'Embedded-Engineering-Roadmap';
|
||||||
|
const PNG_SCALE = 2;
|
||||||
|
|
||||||
|
const TYPES = {
|
||||||
|
'.html': 'text/html; charset=utf-8',
|
||||||
|
'.js': 'text/javascript; charset=utf-8',
|
||||||
|
'.css': 'text/css; charset=utf-8',
|
||||||
|
'.json': 'application/json',
|
||||||
|
'.md': 'text/markdown; charset=utf-8',
|
||||||
|
'.woff2': 'font/woff2',
|
||||||
|
'.svg': 'image/svg+xml',
|
||||||
|
'.png': 'image/png',
|
||||||
|
};
|
||||||
|
|
||||||
|
const server = createServer(async (req, res) => {
|
||||||
|
const url = new URL(req.url, 'http://localhost');
|
||||||
|
const file = resolve(join(root, decodeURIComponent(url.pathname), url.pathname.endsWith('/') ? 'index.html' : ''));
|
||||||
|
if (file !== root && !file.startsWith(root + sep)) {
|
||||||
|
res.writeHead(403).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const body = await readFile(file);
|
||||||
|
res.writeHead(200, { 'content-type': TYPES[extname(file)] || 'application/octet-stream' }).end(body);
|
||||||
|
} catch {
|
||||||
|
res.writeHead(404).end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await new Promise((ok) => server.listen(0, '127.0.0.1', ok));
|
||||||
|
const url = `http://127.0.0.1:${server.address().port}${pagePath}`;
|
||||||
|
|
||||||
|
const browser = await chromium.launch();
|
||||||
|
try {
|
||||||
|
// bypassCSP lets the PNG step add one style rule; the site itself only allows its own stylesheets.
|
||||||
|
const context = await browser.newContext({ viewport: { width: 1400, height: 1000 }, deviceScaleFactor: PNG_SCALE, bypassCSP: true });
|
||||||
|
const page = await context.newPage();
|
||||||
|
const errors = [];
|
||||||
|
page.on('pageerror', (e) => errors.push(e.message));
|
||||||
|
await page.goto(url, { waitUntil: 'networkidle' });
|
||||||
|
await page.waitForSelector('.diagram .box');
|
||||||
|
await page.evaluate(async () => {
|
||||||
|
await document.fonts.ready;
|
||||||
|
await new Promise((ok) => requestAnimationFrame(() => requestAnimationFrame(ok)));
|
||||||
|
});
|
||||||
|
if (errors.length) throw new Error(`The page reported errors:\n${errors.join('\n')}`);
|
||||||
|
|
||||||
|
await mkdir(outDir, { recursive: true });
|
||||||
|
await page.evaluate(() => window.dispatchEvent(new Event('beforeprint')));
|
||||||
|
await page.emulateMedia({ media: 'print' });
|
||||||
|
await page.pdf({ path: join(outDir, `${NAME}.pdf`), format: 'A4', printBackground: true, preferCSSPageSize: true });
|
||||||
|
|
||||||
|
// PNG: the same print rendering, at the diagram's own size instead of the page's.
|
||||||
|
// page.pdf() fires afterprint, which restores the screen view, so prepare for print again.
|
||||||
|
const { w, h } = await page.evaluate(() => {
|
||||||
|
const bg = document.querySelector('.diagram .page-bg');
|
||||||
|
return { w: Number(bg.getAttribute('width')), h: Number(bg.getAttribute('height')) };
|
||||||
|
});
|
||||||
|
await page.setViewportSize({ width: Math.ceil(w) + 40, height: Math.ceil(h) + 40 });
|
||||||
|
await page.addStyleTag({ content: `.diagram { width: ${w}px !important; height: ${h}px !important; max-height: none !important; }` });
|
||||||
|
await page.evaluate(() => window.dispatchEvent(new Event('beforeprint')));
|
||||||
|
await page.locator('.diagram').screenshot({ path: join(outDir, `${NAME}.png`) });
|
||||||
|
|
||||||
|
console.log(`Wrote ${NAME}.pdf and ${NAME}.png (${Math.round(w * PNG_SCALE)}x${Math.round(h * PNG_SCALE)}) to ${outDir}`);
|
||||||
|
} finally {
|
||||||
|
await browser.close();
|
||||||
|
server.close();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user