Compare commits

..
14 Commits
Author SHA1 Message Date
Meysam Parvizi 4564daca70 refactor: rename site/ to explorer/ 2026-10-10 05:18:52 +02:00
Meysam Parvizi 8db8117719 docs: add Embedded Android subsection under Embedded Linux (#28) 2026-10-10 05:08:15 +02:00
Meysam Parvizi d395a72a75 docs: give Career Development a real icon 2026-10-10 05:05:27 +02:00
Meysam Parvizi 543a253ab4 docs: merge Appendix-A into a single Discrete Mathematics topic 2026-10-10 05:04:58 +02:00
Meysam Parvizi b6dec3d394 feat(site): add Display & Camera cluster (MIPI CSI-2, MIPI DSI) to the map 2026-10-10 04:57:24 +02:00
Meysam Parvizi da88157952 fix(site): soften cluster header hover and selected highlight 2026-10-10 04:38:48 +02:00
Meysam Parvizi 3150cc358d docs: add free soft-skill resources 2026-10-10 04:36:03 +02:00
Meysam Parvizi b08f3a9699 docs: add resources for UWB, Profinet, LIN, MOST, FlexRay and the six soft skills 2026-10-10 04:16:23 +02:00
Meysam Parvizi 8cf40b4993 fix(site): make cluster headers clickable with pill feedback instead of link-style underline; name the hovered box in the tooltip 2026-10-10 04:05:19 +02:00
Meysam Parvizi 95aff195b4 Move README heading names into map.json, warn about unmapped README sections, document the README-explorer workflow 2026-10-10 03:55:37 +02:00
Meysam Parvizi 2c4faffbd8 feat(site): pinch to zoom and two-finger pan on the map
Two touches zoom around their midpoint and pan with it; lifting them does not open a topic. Safari's trackpad pinch gesture events are handled too.
2026-10-10 03:46:13 +02:00
Meysam Parvizi 558cc859f5 fix(site): render the map without transitions when printing
The box strokes fade from screen to print colors over 0.15 s, and the PNG/PDF render sometimes captured them mid-fade. That made the output differ slightly between runs and the map fingerprint flip, which created an unnecessary release (v2.0.2). Print styles now disable transitions and animations, so 12 of 12 test renders are byte-identical.
2026-10-10 03:10:24 +02:00
Meysam Parvizi 71adde583f fix(site): highlight group labels by their pill on hover instead of underlining 2026-10-10 02:56:48 +02:00
Meysam Parvizi 334e7d0456 docs(readme): introduce the interactive roadmap and link the image to it
Add a short description of the interactive explorer above the roadmap image and make the image open the site.
2026-10-10 02:56:48 +02:00
20 changed files with 289 additions and 108 deletions
+10 -7
View File
@@ -5,13 +5,13 @@ on:
branches: [master] branches: [master]
paths: paths:
- README.md - README.md
- "site/**" - "explorer/**"
- .github/workflows/pages.yml - .github/workflows/pages.yml
- .github/scripts/next-version.sh - .github/scripts/next-version.sh
pull_request: pull_request:
paths: paths:
- README.md - README.md
- "site/**" - "explorer/**"
- .github/workflows/pages.yml - .github/workflows/pages.yml
- .github/scripts/next-version.sh - .github/scripts/next-version.sh
workflow_dispatch: workflow_dispatch:
@@ -48,13 +48,16 @@ jobs:
node-version: 22 node-version: 22
- name: Test the README parser and map layout - name: Test the README parser and map layout
run: node --test site/parser.test.mjs run: node --test explorer/parser.test.mjs
- name: Warn about README sections that are not on the map
run: node explorer/tools/coverage.mjs
- name: Assemble the site - name: Assemble the site
run: | run: |
mkdir _site mkdir _site
cp site/*.html site/*.css site/*.js site/map.json README.md _site/ cp explorer/*.html explorer/*.css explorer/*.js explorer/map.json README.md _site/
cp -r site/fonts site/assets _site/ cp -r explorer/fonts explorer/assets _site/
- name: Install Chromium - name: Install Chromium
run: | run: |
@@ -65,7 +68,7 @@ jobs:
id: fingerprint id: fingerprint
# Rendered before build.json exists, so the revision line is not part of the fingerprint. # Rendered before build.json exists, so the revision line is not part of the fingerprint.
run: | run: |
node site/tools/render.mjs _site / fingerprint node explorer/tools/render.mjs _site / fingerprint
echo "sha256=$(sha256sum fingerprint/Embedded-Engineering-Roadmap.png | cut -d' ' -f1)" >> "$GITHUB_OUTPUT" echo "sha256=$(sha256sum fingerprint/Embedded-Engineering-Roadmap.png | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
- name: Decide the version - name: Decide the version
@@ -84,7 +87,7 @@ jobs:
run: | run: |
node -e 'console.log(JSON.stringify({ version: process.env.VERSION, date: process.env.DATE }))' > _site/build.json node -e 'console.log(JSON.stringify({ version: process.env.VERSION, date: process.env.DATE }))' > _site/build.json
cat _site/build.json cat _site/build.json
node site/tools/render.mjs _site / _site node explorer/tools/render.mjs _site / _site
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
with: with:
+11 -2
View File
@@ -30,9 +30,18 @@ Contributions from everyone are welcomed. To keep the roadmap practical, accessi
- Contributions should match the topics and structure of the roadmap. - Contributions should match the topics and structure of the roadmap.
- Areas where contributors have experience or genuine interest should be focused on. - 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. - 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.
- Connections between topics on the map are listed in `site/map.json` under `links` as `["Topic", "Other topic", "why they are related"]`, using the names shown on the map. Connect topics that a learner should study together or that depend on each other, and keep the reason to one short sentence. Tests fail if a name is misspelled, a pair is listed twice or a topic has no connection. - Connections between topics on the map are listed in `explorer/map.json` under `links` as `["Topic", "Other topic", "why they are related"]`, using the names shown on the map. Connect topics that a learner should study together or that depend on each other, and keep the reason to one short sentence. Tests fail if a name is misspelled, a pair is listed twice or a topic has no connection.
## 6. Versioning and Releases ## 6. How the README Feeds the Interactive Roadmap
The [interactive roadmap](https://m3y54m.github.io/Embedded-Engineering-Roadmap/) reads `README.md` directly; there is no other copy of the content. Most contributions need no change outside the README.
- **Topics** come from heading depth: `##` is a group, `###` a main topic, `####` and deeper are subtopics. The emojis in headings (✳️ 🔵 🔶 🔸) are decoration only. Renaming a heading changes its link (`#/topic-name`).
- **Resources** are list items in the form `- [📘👶💎 Title](https://url)`, optionally followed by a dash and a short note. Symbols: 📘 book, 🎞️ video, 📝 article, 🔗 link, 🎧 audio, 👶 beginner, 💎 essential. `python3 .github/scripts/check_readme.py` checks the format and duplicates.
- **The map** (`explorer/map.json`) draws only the main topics. Each label on it opens the README heading with the same name. If the wording differs, or the label has no section of its own, add `"Label": ["README heading"]` to `readme` in `explorer/map.json`. A new main topic needs a README section and a box in `explorer/map.json` (and at least one entry in `links`), unless it should stay off the map: then list its title or group in `offMap`. CI warns about main topics that are neither.
- **Preview and test locally:** `python3 -m http.server 8765` from the repository root, then open `/explorer/`; `node --test explorer/parser.test.mjs` runs the checks CI runs.
## 7. Versioning and Releases
The roadmap image is versioned as `vMAJOR.MINOR.PATCH`, starting from `v2.0.0`. The roadmap image is versioned as `vMAJOR.MINOR.PATCH`, starting from `v2.0.0`.
+89 -35
View File
@@ -42,7 +42,9 @@ used in an aircraft or rapid transit system.
## 🗺️ The Roadmap ## 🗺️ The Roadmap
![Embedded Systems Engineering Roadmap](https://github.com/m3y54m/Embedded-Engineering-Roadmap/releases/latest/download/Embedded-Engineering-Roadmap.png) 🌐 **[Open the interactive roadmap](https://m3y54m.github.io/Embedded-Engineering-Roadmap/)** to explore it in your browser. Click any topic to read its description and see its learning resources, hover over it to see the topics it is related to, filter topics by area (software, hardware, soft skills) or importance, and search across all topics and resources. The roadmap can also be downloaded there as a PDF or PNG image.
[![Embedded Systems Engineering Roadmap](https://github.com/m3y54m/Embedded-Engineering-Roadmap/releases/latest/download/Embedded-Engineering-Roadmap.png)](https://m3y54m.github.io/Embedded-Engineering-Roadmap/)
The embedded systems engineering roadmap is structured into three fundamental areas: **SOFTWARE**, **HARDWARE**, and **SOFT SKILLS**. The embedded systems engineering roadmap is structured into three fundamental areas: **SOFTWARE**, **HARDWARE**, and **SOFT SKILLS**.
@@ -123,7 +125,7 @@ If you have reservations about relying on AI advice alone, you can also ask your
- [🔗 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 ### 💼 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: 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:
@@ -226,6 +228,42 @@ Similar to other professions, embedded engineers require soft skills that can't
- [📝 Soft Skills For Embedded Systems Software Developers](https://www.embeddedrelated.com/showarticle/1470.php) - [📝 Soft Skills For Embedded Systems Software Developers](https://www.embeddedrelated.com/showarticle/1470.php)
- [📝 10 Skills Every Embedded Engineer Should Have](https://medium.com/@lanceharvieruntime/10-skills-every-embedded-engineer-should-have-dcb867095b91) - [📝 10 Skills Every Embedded Engineer Should Have](https://medium.com/@lanceharvieruntime/10-skills-every-embedded-engineer-should-have-dcb867095b91)
#### 🔵 Communication Skills
- [🔗👶 Technical Writing Courses - Google for Developers](https://developers.google.com/tech-writing)
- [📝💎 How To Ask Questions The Smart Way - Eric S. Raymond](http://www.catb.org/esr/faqs/smart-questions.html)
- [🔗 Software documentation guide - Write the Docs](https://www.writethedocs.org/guide/)
#### 🔵 Problem-Solving Skills & Critical Thinking
- [📘💎 Debugging: The 9 Indispensable Rules for Finding Even the Most Elusive Software and Hardware Problems - David J. Agans](https://debuggingrules.com/)
- [📝 When debugging, your attitude matters - Julia Evans](https://jvns.ca/blog/debugging-attitude-matters/)
#### 🔵 Teamwork & Collaborative Abilities
- [📝 How to Work Well on Teams - Software Engineering at Google](https://abseil.io/resources/swe-book/html/ch02.html)
- [📝 Knowledge Sharing - Software Engineering at Google](https://abseil.io/resources/swe-book/html/ch03.html)
- [🔗💎 Code Review Developer Guide - Google Engineering Practices](https://google.github.io/eng-practices/review/)
- [🔗 Atlassian Team Playbook](https://www.atlassian.com/team-playbook)
#### 🔵 Organizational & Time Management Skills
- [📘💎 Getting Things Done: The Art of Stress-Free Productivity - David Allen](https://gettingthingsdone.com/what-is-gtd/)
- [🎞️👶 Work Smarter, Not Harder: Time Management for Personal & Professional Productivity - Coursera](https://www.coursera.org/learn/work-smarter-not-harder)
- [📝💎 Maker's Schedule, Manager's Schedule - Paul Graham](https://www.paulgraham.com/makersschedule.html)
#### 🔵 Being Self-Driven and Independent
- [🎞️👶 Learning How to Learn - Coursera](https://www.coursera.org/learn/learning-how-to-learn)
- [📝💎 Teach Yourself Programming in Ten Years - Peter Norvig](https://norvig.com/21-days.html)
- [📝💎 How to Do Great Work - Paul Graham](https://paulgraham.com/greatwork.html)
- [📝 You and Your Research - Richard Hamming](https://www.paulgraham.com/hamming.html)
#### 🔵 Adaptability & Patience
- [📘 Mindset: The New Psychology of Success - Carol S. Dweck](https://www.penguinrandomhouse.com/books/44330/mindset-by-carol-s-dweck-phd/)
- [🎞️👶 The power of believing that you can improve - Carol Dweck (TED Talk)](https://www.ted.com/talks/carol_dweck_the_power_of_believing_that_you_can_improve)
_________________ _________________
### ✳️ Electronics ### ✳️ Electronics
@@ -376,13 +414,19 @@ _________________
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 #### 🔵 Discrete Mathematics
Discrete mathematics is the theoretical basis of computer science and engineering. The parts most useful to embedded engineers are logic and switching algebra (digital circuit design), automata theory (state machines and protocols), combinatorics, coding theory (data transmission and audio/video codecs) and modular arithmetic (checksums and cryptography).
- [🎞️ Coursera - Introduction to Discrete Mathematics for Computer Science Specialization](https://www.coursera.org/specializations/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) - [📘👶 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) - [📘👶 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) - [📘👶 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) - [📘💎 Bridge to Abstract Mathematics, AMS](https://bookstore.ams.org/view?ProductCode=TEXT/22)
- [📘💎 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)
- [📘💎 The Mathematics of Cellular Automata by Jane Hawkins, AMS](https://bookstore.ams.org/stml-108)
#### 🔵 Algorithms & Data Structures #### 🔵 Algorithms & Data Structures
@@ -757,6 +801,12 @@ Embedded systems often communicate with other devices or external systems via in
- [🎞️ What is Matter? Unifying IoT Devices for the Smart Home | Digi-Key Electronics](https://youtu.be/vJIEfih9bl0) - [🎞️ What is Matter? Unifying IoT Devices for the Smart Home | Digi-Key Electronics](https://youtu.be/vJIEfih9bl0)
##### 🔶 UWB
- [📘👶 Ultra-Wideband For Dummies - Qorvo](https://www.qorvo.com/design-hub/ebooks/ultra-wideband-for-dummies)
- [🔗 FiRa Consortium - Advancing Ultra-Wideband (UWB) Technology](https://www.firaconsortium.org/)
- [📝 Ultra-wideband - Wikipedia](https://en.wikipedia.org/wiki/Ultra-wideband)
#### 🔵 Industrial Protocols #### 🔵 Industrial Protocols
##### 🔶 Modbus ##### 🔶 Modbus
@@ -774,6 +824,12 @@ Embedded systems often communicate with other devices or external systems via in
- [🎞️ Microchip University - An Introduction to EtherCAT®, EtherCAT P and the Microchip LAN925x family of Slave Controllers](https://mu.microchip.com/an-introduction-to-ethercat-ethercat-p-and-the-microchip-lan925x-family-of-slave-controllers) - [🎞️ Microchip University - An Introduction to EtherCAT®, EtherCAT P and the Microchip LAN925x family of Slave Controllers](https://mu.microchip.com/an-introduction-to-ethercat-ethercat-p-and-the-microchip-lan925x-family-of-slave-controllers)
##### 🔶 Profinet
- [🎞️👶 How PROFINET Works? A Beginner's Guide - PI North America](https://us.profinet.com/webinar/how-profinet-works-a-beginners-guide/)
- [🎞️ PROFINET Video Series](https://www.profinet.com/profinet-explained/explanatory-videos)
- [🔗 PROFINET University](https://profinetuniversity.com/)
##### 🔶 CoAP & MQTT ##### 🔶 CoAP & MQTT
- [🎞️ #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)
@@ -825,6 +881,21 @@ Embedded systems often communicate with other devices or external systems via in
- [🎞️ J1939 Explained - A Simple Intro [v2.0 | 2021]](https://www.youtube.com/watch?v=vlqxu9ojbHg) - [🎞️ J1939 Explained - A Simple Intro [v2.0 | 2021]](https://www.youtube.com/watch?v=vlqxu9ojbHg)
- [🎞️ Unified Diagnostic Services (UDS) Explained - A Simple Intro [2022]](https://www.youtube.com/watch?v=CV_B8tJgI5E) - [🎞️ Unified Diagnostic Services (UDS) Explained - A Simple Intro [2022]](https://www.youtube.com/watch?v=CV_B8tJgI5E)
##### 🔶 LIN
- [📝👶 LIN Bus Explained - A Simple Intro - CSS Electronics](https://www.csselectronics.com/pages/lin-bus-protocol-intro-basics)
- [📝 Local Interconnect Network - Wikipedia](https://en.wikipedia.org/wiki/Local_Interconnect_Network)
##### 🔶 MOST
- [📝 MOST Bus - Wikipedia](https://en.wikipedia.org/wiki/MOST_(bus))
- [🔗 MOST Cooperation - Specifications](https://www.mostcooperation.com/specifications/)
##### 🔶 FlexRay
- [📝 FlexRay Automotive Communication Bus Overview - NI](https://www.ni.com/en/shop/seamlessly-connect-to-third-party-devices-and-supervisory-system/flexray-automotive-communication-bus-overview.html)
- [📝 FlexRay - Wikipedia](https://en.wikipedia.org/wiki/FlexRay)
#### 🔵 Network Protocols / Socket Programming #### 🔵 Network Protocols / Socket Programming
- [🎞️ Networking Fundamentals - Practical Networking (YouTube Playlist)](https://www.youtube.com/playlist?list=PLIFyRwBY_4bRLmKfP1KnZA6rZbRHtxmXi) - [🎞️ Networking Fundamentals - Practical Networking (YouTube Playlist)](https://www.youtube.com/playlist?list=PLIFyRwBY_4bRLmKfP1KnZA6rZbRHtxmXi)
@@ -1125,8 +1196,6 @@ Embedded Linux is a specialized version of the Linux operating system tailored f
- [🔗 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/)
- [🔗 Automotive Grade Linux (AGL)](https://docs.automotivelinux.org/) - [🔗 Automotive Grade Linux (AGL)](https://docs.automotivelinux.org/)
- [🔗 Real Time Linux and `PREEMPT_RT` Patch](https://wiki.linuxfoundation.org/realtime/start) - [🔗 Real Time Linux and `PREEMPT_RT` Patch](https://wiki.linuxfoundation.org/realtime/start)
- [🔗 Android Open Source Project](https://source.android.com)
- [🔗 Android Automotive](https://source.android.com/docs/automotive)
##### 🔶 Linux Kernel ##### 🔶 Linux Kernel
@@ -1165,6 +1234,20 @@ Embedded Linux is a specialized version of the Linux operating system tailored f
- [📘 Bootlin Embedded Linux, Kernel, drivers, Yocto, Buildroot and Graphics Training](https://bootlin.com/training/) - [📘 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/) - [🔗 meta-raspberrypi - Yocto/OpenEmbedded layer for Raspberry Pi boards](https://meta-raspberrypi.readthedocs.io/en/latest/)
##### 🔶 Embedded Android
Embedded Android builds on Embedded Linux: the device runs a Linux kernel with Android-specific additions, and the Android Open Source Project (AOSP) adds its own build system, hardware abstraction layers (HALs) and framework on top. Learn Embedded Linux first, then how AOSP is built, ported to a board and extended with HALs and native (NDK) code.
- [🔗💎 Android Open Source Project](https://source.android.com)
- [🔗👶 AOSP Architecture Overview](https://source.android.com/docs/core/architecture)
- [🔗 Try Android Development - Download, Build and Flash AOSP](https://source.android.com/docs/setup/start)
- [🔗 Android Kernel Overview](https://source.android.com/docs/core/architecture/kernel)
- [🔗 Hardware Abstraction Layer (HAL) Overview](https://source.android.com/docs/core/architecture/hal)
- [🔗 Get Started with the Android NDK](https://developer.android.com/ndk/guides)
- [📘💎 Embedded Android - Karim Yaghmour](https://www.oreilly.com/library/view/embedded-android/9781449327958/)
- [📘 Android System Development Training (Slides and Labs) - Bootlin](https://bootlin.com/doc/legacy/android/)
- [🔗 Android Automotive](https://source.android.com/docs/automotive)
#### 🔵 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)
@@ -1517,35 +1600,6 @@ AUTOSAR, or AUTomotive Open System ARchitecture, is a global industry standard f
- [🔗💎 OSEK/VDX Operating System Specification 2.2.3](https://www.osek-vdx.org/mirror/os223.pdf) - [🔗💎 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) - [🔗💎 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.
@@ -1568,4 +1622,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 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. 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 [`explorer/map.json`](explorer/map.json). Edit those files and the interactive explorer in [`explorer/`](explorer) renders the PNG and PDF automatically.
View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before

Width:  |  Height:  |  Size: 879 B

After

Width:  |  Height:  |  Size: 879 B

+22 -43
View File
@@ -1,40 +1,6 @@
// Links the topics drawn on the map (map.json) to README topics, so every topic knows // 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. // 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 AREA_ORDER = ['SOFTWARE', 'HARDWARE', 'SOFT SKILLS'];
export const IMPORTANCE_LEVELS = ['required', 'recommended', 'possible']; export const IMPORTANCE_LEVELS = ['required', 'recommended', 'possible'];
const RANK = { required: 3, recommended: 2, possible: 1 }; const RANK = { required: 3, recommended: 2, possible: 1 };
@@ -60,13 +26,12 @@ function topicIndex(topics) {
export function linkDiagram(map, data) { export function linkDiagram(map, data) {
const index = topicIndex(data.topics); const index = topicIndex(data.topics);
const softSkills = data.topics.find((t) => t.depth === 1 && key(t.title) === 'soft skills'); // map.json "readme": map labels whose README heading is worded differently (or has no section of its own).
const resolve = (text, areas = []) => { const aliases = map.readme || {};
if (ALIASES[text]) return ALIASES[text].map((title) => index.get(key(title))).filter(Boolean); const resolve = (text) => {
if (Object.hasOwn(aliases, text)) return [aliases[text]].flat().map((title) => index.get(key(title))).filter(Boolean);
const topic = index.get(key(text)); const topic = index.get(key(text));
if (topic) return [topic]; return topic ? [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. // A cluster's areas are those of the band it sits in; a group's are the areas all its clusters share.
@@ -81,7 +46,7 @@ export function linkDiagram(map, data) {
for (const [text, importance] of spec.topics) boxes.push({ text, importance, areas: band.areas, cluster, header: false }); 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) })); boxes.forEach((b, i) => Object.assign(b, { id: `box-${i}`, topics: resolve(b.text) }));
const regions = Object.entries(map.groups).map(([name, clusters]) => { const regions = Object.entries(map.groups).map(([name, clusters]) => {
const areas = AREA_ORDER.filter((a) => clusters.every((c) => (clusterAreas.get(c) || []).includes(a))); const areas = AREA_ORDER.filter((a) => clusters.every((c) => (clusterAreas.get(c) || []).includes(a)));
return { name, clusters, areas, topics: resolve(name) }; return { name, clusters, areas, topics: resolve(name) };
@@ -106,8 +71,18 @@ export function linkDiagram(map, data) {
for (const area of region.areas) if (!t.areas.includes(area)) t.areas.push(area); 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. // Subtopics that are not drawn in the diagram inherit the areas of their closest drawn ancestor;
// a topic that is not drawn itself but has drawn subtopics (Soft Skills) takes theirs.
const fromChildren = new Map();
for (const t of data.topics) { for (const t of data.topics) {
if (!t.areas.length) continue;
for (let p = t.parent; p; p = p.parent) {
if (!fromChildren.has(p)) fromChildren.set(p, new Set());
for (const area of t.areas) fromChildren.get(p).add(area);
}
}
for (const t of data.topics) {
if (!t.areas.length && fromChildren.has(t)) t.areas = [...fromChildren.get(t)];
if (!t.areas.length) for (let p = t.parent; p && !t.areas.length; p = p.parent) t.areas = [...(p.areas || [])]; 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)); t.areas.sort((a, b) => AREA_ORDER.indexOf(a) - AREA_ORDER.indexOf(b));
} }
@@ -166,6 +141,10 @@ export function linkDiagram(map, data) {
connections: [...pairs.values()], connections: [...pairs.values()],
unmatched: boxes.filter((b) => !b.header && !b.topics.length).map((b) => b.text), unmatched: boxes.filter((b) => !b.header && !b.topics.length).map((b) => b.text),
invalidLinks, invalidLinks,
invalid: boxes.filter((b) => !b.header && !IMPORTANCE_LEVELS.includes(b.importance)).map((b) => `${b.text}: ${b.importance}`), invalidReadme: Object.entries(aliases).flatMap(([label, titles]) => {
const onMap = boxes.some((b) => b.text === label) || label in map.groups;
const missing = [titles].flat().filter((title) => !index.has(key(title)));
return onMap && !missing.length ? [] : [`${label} -> ${[titles].flat().join(' + ')}`];
}), invalid: boxes.filter((b) => !b.header && !IMPORTANCE_LEVELS.includes(b.importance)).map((b) => `${b.text}: ${b.importance}`),
}; };
} }
View File
+2 -1
View File
@@ -29,6 +29,7 @@ const spanW = (n) => n * COL + (n - 1) * GUTTER;
export const PLACED = [ export const PLACED = [
'languages', 'fundamentals', 'debugging', 'sdlc', 'vcs', 'build', 'testing', 'specialized', 'osBase', 'linux', 'languages', 'fundamentals', 'debugging', 'sdlc', 'vcs', 'build', 'testing', 'specialized', 'osBase', 'linux',
'rtos', 'mcu', 'bridge', 'basic', 'wireless', 'highSpeed', 'industrial', 'cellular', 'network', 'automotive', 'rtos', 'mcu', 'bridge', 'basic', 'wireless', 'highSpeed', 'industrial', 'cellular', 'network', 'automotive',
'display',
'electronics', 'equipment', 'prototyping', 'fpga', 'soft', 'electronics', 'equipment', 'prototyping', 'fpga', 'soft',
]; ];
@@ -106,7 +107,7 @@ export function buildLayout(plan) {
const ifBottom = Math.max( const ifBottom = Math.max(
stack(['basic', 'wireless'], sub[0], ifContent, subW), stack(['basic', 'wireless'], sub[0], ifContent, subW),
stack(['highSpeed', 'industrial', 'cellular'], sub[1], ifContent, subW), stack(['highSpeed', 'industrial', 'cellular'], sub[1], ifContent, subW),
stack(['network', 'automotive'], sub[2], ifContent, subW), stack(['network', 'automotive', 'display'], sub[2], ifContent, subW),
); );
const interfaces = { x: c[2] - SPACE, y: groupTop, w: wide + 2 * SPACE, h: ifBottom + SPACE - groupTop }; 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 bridgeBottom = add(place(clusters.bridge, c[4], groupTop, COL));
+21 -12
View File
@@ -275,14 +275,19 @@
stroke-width: 2.2; stroke-width: 2.2;
} }
.box.header.target:hover .header-text, .header-bg {
.box.header.lit .header-text, fill: transparent;
.group-tag.target:hover .tag-text { transition: fill 0.15s;
text-decoration: underline; }
.box.header.target:hover .header-bg,
.box.header.selected .header-bg {
fill: color-mix(in srgb, var(--accent) 14%, transparent);
} }
.group-tag.target:hover .tag-bg { .group-tag.target:hover .tag-bg {
fill: var(--chip-hover); fill: color-mix(in srgb, var(--surface-solid) 85%, var(--accent));
stroke-width: 3;
} }
.box.selected .box-bg { .box.selected .box-bg {
@@ -290,19 +295,15 @@
stroke-width: 3; stroke-width: 3;
} }
.box.header.selected .header-text {
fill: var(--accent);
}
.box.target:focus-visible .box-bg, .box.target:focus-visible .box-bg,
.group-tag.target:focus-visible .tag-bg { .group-tag.target:focus-visible .tag-bg {
stroke: var(--accent); stroke: var(--accent);
stroke-width: 3.5; stroke-width: 3.5;
} }
.box.header.target:focus-visible .header-text { .box.header.target:focus-visible .header-bg {
fill: var(--accent); stroke: var(--accent);
text-decoration: underline; stroke-width: 2;
} }
.layer-boxes .box { .layer-boxes .box {
@@ -450,6 +451,14 @@
} }
@media print { @media print {
/* Print the final colors, not a frame of the screen-to-print color transition. */
*,
*::before,
*::after {
transition: none !important;
animation: none !important;
}
html[data-theme] { html[data-theme] {
color-scheme: light; color-scheme: light;
--bg: #ffffff; --bg: #ffffff;
+44 -1
View File
@@ -198,6 +198,14 @@
["MOST", "possible"], ["MOST", "possible"],
["FlexRay", "possible"] ["FlexRay", "possible"]
] ]
},
"display": {
"header": "Display & Camera",
"per": 2,
"topics": [
["MIPI CSI-2", "possible"],
["MIPI DSI", "possible"]
]
} }
} }
}, },
@@ -450,6 +458,13 @@
["CAN", "Oscilloscope", "Check the differential signal and bus termination"], ["CAN", "Oscilloscope", "Check the differential signal and bus termination"],
["MOST", "CAN", "MOST carries media in a vehicle while CAN carries control"], ["MOST", "CAN", "MOST carries media in a vehicle while CAN carries control"],
["MOST", "Ethernet", "Automotive Ethernet is replacing MOST"], ["MOST", "Ethernet", "Automotive Ethernet is replacing MOST"],
["MIPI CSI-2", "MIPI DSI", "Camera and display links share the MIPI D-PHY physical layer"],
["MIPI CSI-2", "I2C", "Camera sensors are configured over a control bus such as I2C"],
["MIPI CSI-2", "Device Drivers", "Linux exposes camera sensors through V4L2 drivers"],
["MIPI CSI-2", "Edge AI", "Camera frames are the main input for on-device vision models"],
["MIPI DSI", "Device Drivers", "Linux drives DSI panels through the DRM/KMS subsystem"],
["MIPI DSI", "Embedded GUI", "The display link carries the interface the GUI renders"],
["MIPI DSI", "DMA", "Frame buffers are streamed to the display with DMA"],
["Basic Math & Calculus", "Principles of Electric Circuits", "Circuit analysis uses algebra and calculus"], ["Basic Math & Calculus", "Principles of Electric Circuits", "Circuit analysis uses algebra and calculus"],
["Principles of Electric Circuits", "Electronics Fundamentals", "Components build on circuit laws"], ["Principles of Electric Circuits", "Electronics Fundamentals", "Components build on circuit laws"],
@@ -471,6 +486,34 @@
"groups": { "groups": {
"Operating Systems": ["osBase", "linux", "rtos"], "Operating Systems": ["osBase", "linux", "rtos"],
"Microcontrollers": ["rtos", "mcu"], "Microcontrollers": ["rtos", "mcu"],
"Interfaces & Protocols": ["basic", "wireless", "highSpeed", "industrial", "cellular", "network", "automotive"] "Interfaces & Protocols": ["basic", "wireless", "highSpeed", "industrial", "cellular", "network", "automotive", "display"]
},
"offMap": ["Don't Know Where to Start!"],
"readme": {
"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"],
"Display & Camera": ["Display & Camera 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"]
} }
} }
+65 -3
View File
@@ -229,9 +229,10 @@ export function createMapView({ root, plan, data, build, onSelect }) {
const g = svg('g', { class: `box ${item.kind === 'header' ? 'header' : box.importance}`, 'data-box': box.id }, layers.boxes); const g = svg('g', { class: `box ${item.kind === 'header' ? 'header' : box.importance}`, 'data-box': box.id }, layers.boxes);
rectOf.set(box, item); rectOf.set(box, item);
if (item.kind === 'header') { if (item.kind === 'header') {
const bg = svg('rect', { class: 'header-bg', rx: 6, x: item.x - 6, y: item.y - 1, height: 24 }, g);
const t = svg('text', { x: item.x, y: item.y + 16, class: 'box-text header-text' }, g); const t = svg('text', { x: item.x, y: item.y + 16, class: 'box-text header-text' }, g);
t.textContent = item.text; t.textContent = item.text;
boxEls.set(box, { g, text: t, width: item.w, header: true }); boxEls.set(box, { g, text: t, bg, x: item.x, width: item.w, header: true });
} else { } else {
rect(item, { rx: 5, class: 'box-bg' }, g); rect(item, { rx: 5, class: 'box-bg' }, g);
const lines = item.h >= 36 ? splitInTwo(item.text) : [item.text]; const lines = item.h >= 36 ? splitInTwo(item.text) : [item.text];
@@ -291,11 +292,11 @@ export function createMapView({ root, plan, data, build, onSelect }) {
} }
const importance = (box && box.importance) || topic.importance; const importance = (box && box.importance) || topic.importance;
tip.replaceChildren(...[ tip.replaceChildren(...[
h('strong', {}, topic.title), h('strong', {}, box ? box.text : topic.title),
topic.areas.length ? h('div', { class: 'tip-areas' }, areaDots(topic.areas), areaLabel(topic.areas)) : null, 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'), h('div', { class: 'tip-meta' }, [importance ? IMPORTANCE[importance] : null, plural(topic.total, 'resource'),
box && box.related.length ? `${box.related.length} related` : null].filter(Boolean).join(' · ')), box && box.related.length ? `${box.related.length} related` : null].filter(Boolean).join(' · ')),
h('div', { class: 'tip-hint' }, 'Click this topic to open its details'), h('div', { class: 'tip-hint' }, box && box.text !== topic.title ? `Click to open “${topic.title}”` : 'Click this topic to open its details'),
].filter(Boolean)); ].filter(Boolean));
tip.hidden = false; tip.hidden = false;
moveTip(e); moveTip(e);
@@ -457,6 +458,41 @@ export function createMapView({ root, plan, data, build, onSelect }) {
setView({ x: px - (px - v.x) / factor, y: py - (py - v.y) / factor, w: v.w / factor }); setView({ x: px - (px - v.x) / factor, y: py - (py - v.y) / factor, w: v.w / factor });
} }
// Two fingers: zoom around their midpoint and pan with it.
const touches = new Map();
let pinch = null;
const spread = () => {
const [a, b] = [...touches.values()];
return { dist: Math.hypot(a.x - b.x, a.y - b.y), x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
};
function beginPinch() {
pinch = spread();
// The click that ends the gesture must not open a topic.
state.drag = { moved: true, id: null };
chart.classList.add('panning');
hover(null);
}
function movePinch() {
if (touches.size < 2) return;
const now = spread();
if (pinch.dist > 0 && now.dist > 0) zoomAt(now.dist / pinch.dist, now.x, now.y);
const unit = state.view.w / chart.getBoundingClientRect().width;
fitMode = 'custom';
setView({ ...state.view, x: state.view.x - (now.x - pinch.x) * unit, y: state.view.y - (now.y - pinch.y) * unit });
pinch = now;
}
function endTouch(e) {
if (!touches.delete(e.pointerId)) return;
if (pinch && touches.size < 2) {
pinch = null;
chart.classList.remove('panning');
}
if (!touches.size && state.drag && state.drag.id === null) window.setTimeout(() => { state.drag = null; }, 0);
}
chart.addEventListener('wheel', (e) => { chart.addEventListener('wheel', (e) => {
e.preventDefault(); e.preventDefault();
if (e.ctrlKey || e.metaKey) { if (e.ctrlKey || e.metaKey) {
@@ -471,10 +507,20 @@ export function createMapView({ root, plan, data, build, onSelect }) {
setView({ ...state.view, x: state.view.x + dx * unit, y: state.view.y + dy * unit }); setView({ ...state.view, x: state.view.x + dx * unit, y: state.view.y + dy * unit });
}, { passive: false }); }, { passive: false });
chart.addEventListener('pointerdown', (e) => { chart.addEventListener('pointerdown', (e) => {
if (e.pointerType === 'touch') touches.set(e.pointerId, { x: e.clientX, y: e.clientY });
if (touches.size === 2) {
beginPinch();
return;
}
if (e.button !== 0) return; if (e.button !== 0) return;
state.drag = { x: e.clientX, y: e.clientY, view: { ...state.view }, moved: false, id: e.pointerId }; state.drag = { x: e.clientX, y: e.clientY, view: { ...state.view }, moved: false, id: e.pointerId };
}); });
chart.addEventListener('pointermove', (e) => { chart.addEventListener('pointermove', (e) => {
if (touches.has(e.pointerId)) touches.set(e.pointerId, { x: e.clientX, y: e.clientY });
if (pinch) {
movePinch();
return;
}
const drag = state.drag; const drag = state.drag;
if (!drag || drag.id !== e.pointerId) return; if (!drag || drag.id !== e.pointerId) return;
const dx = e.clientX - drag.x; const dx = e.clientX - drag.x;
@@ -490,6 +536,7 @@ export function createMapView({ root, plan, data, build, onSelect }) {
setView({ ...drag.view, x: drag.view.x - dx * scale, y: drag.view.y - dy * scale }); setView({ ...drag.view, x: drag.view.x - dx * scale, y: drag.view.y - dy * scale });
}); });
const endDrag = (e) => { const endDrag = (e) => {
endTouch(e);
if (!state.drag || state.drag.id !== e.pointerId) return; if (!state.drag || state.drag.id !== e.pointerId) return;
chart.classList.remove('panning'); chart.classList.remove('panning');
// The click that follows a drag still needs to see `moved`. // The click that follows a drag still needs to see `moved`.
@@ -499,6 +546,20 @@ export function createMapView({ root, plan, data, build, onSelect }) {
chart.addEventListener('pointercancel', endDrag); chart.addEventListener('pointercancel', endDrag);
chart.addEventListener('dblclick', toggleFit); chart.addEventListener('dblclick', toggleFit);
// Safari on a Mac sends its own gesture events for trackpad pinches (other browsers send Ctrl + wheel).
// On iOS the same pinch also arrives as touches, which are handled above, so skip it there.
let gestureScale = 1;
chart.addEventListener('gesturestart', (e) => {
e.preventDefault();
gestureScale = 1;
});
chart.addEventListener('gesturechange', (e) => {
e.preventDefault();
if (touches.size) return;
zoomAt(e.scale / gestureScale, e.clientX, e.clientY);
gestureScale = e.scale;
});
const zoomLevel = h('span', { class: 'zoom-level', 'aria-live': 'polite' }, '100%'); 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 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' }, const zoomControls = h('div', { class: 'zoom-controls' },
@@ -539,6 +600,7 @@ export function createMapView({ root, plan, data, build, onSelect }) {
span.setAttribute('lengthAdjust', 'spacingAndGlyphs'); span.setAttribute('lengthAdjust', 'spacingAndGlyphs');
} }
} }
if (el.bg) el.bg.setAttribute('width', (el.text.getComputedTextLength() + 12).toFixed(1));
} }
} }
@@ -56,7 +56,8 @@ const map = JSON.parse(readFileSync(new URL('./map.json', import.meta.url), 'utf
test('places topics in the map areas and their cross-section', () => { test('places topics in the map areas and their cross-section', () => {
const plan = linkDiagram(map, data); 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.unmatched, [], 'every map topic matches a README heading (or a "readme" entry in map.json)');
assert.deepEqual(plan.invalidReadme, [], 'map.json "readme" entries name a topic on the map and existing README headings');
assert.deepEqual(plan.invalid, [], 'importance is required, recommended or possible'); assert.deepEqual(plan.invalid, [], 'importance is required, recommended or possible');
assert.deepEqual(find('GPIO').areas, ['SOFTWARE', 'HARDWARE']); assert.deepEqual(find('GPIO').areas, ['SOFTWARE', 'HARDWARE']);
assert.deepEqual(find('I2C').areas, ['SOFTWARE', 'HARDWARE']); assert.deepEqual(find('I2C').areas, ['SOFTWARE', 'HARDWARE']);
+20
View File
@@ -0,0 +1,20 @@
// Warns (never fails) about README sections that have no place on the map yet.
// Usage: node explorer/tools/coverage.mjs Sections listed in map.json "offMap" (titles or group names) are skipped.
import { readFileSync } from 'node:fs';
import { parseRoadmap } from '../parser.js';
import { linkDiagram } from '../diagram.js';
const read = (path) => readFileSync(new URL(path, import.meta.url), 'utf8');
const map = JSON.parse(read('../map.json'));
const data = parseRoadmap(read('../../README.md'));
const plan = linkDiagram(map, data);
const drawn = new Set(plan.boxes.flatMap((b) => b.topics));
const onMap = (t) => drawn.has(t) || t.children.some(onMap);
const skipped = new Set(map.offMap || []);
const missing = data.root.children.filter((t) => !skipped.has(t.title) && !skipped.has(t.group) && !onMap(t));
for (const t of missing) {
console.log(`::warning file=README.md,title=Not on the roadmap map::"${t.title}" has no box on the map. Add it to explorer/map.json, or to "offMap" if it should stay off the map (see CONTRIBUTING.md).`);
}
console.log(missing.length ? `${missing.length} README section(s) are not on the map.` : 'Every README section is on the map or listed in "offMap".');
@@ -1,13 +1,13 @@
// Renders the roadmap map to an A4 PDF and a PNG with headless Chromium, straight from the site. // 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] // Usage: node explorer/tools/render.mjs [rootDir] [pagePath] [outDir]
// defaults: repository root, /site/, ./dist (needs: npm install --no-save playwright) // defaults: repository root, /explorer/, ./dist (needs: npm install --no-save playwright)
import { createServer } from 'node:http'; import { createServer } from 'node:http';
import { mkdir, readFile } from 'node:fs/promises'; import { mkdir, readFile } from 'node:fs/promises';
import { extname, join, resolve, sep } from 'node:path'; import { extname, join, resolve, sep } from 'node:path';
import { chromium } from 'playwright'; import { chromium } from 'playwright';
const root = resolve(process.argv[2] || '.'); const root = resolve(process.argv[2] || '.');
const pagePath = process.argv[3] || '/site/'; const pagePath = process.argv[3] || '/explorer/';
const outDir = resolve(process.argv[4] || 'dist'); const outDir = resolve(process.argv[4] || 'dist');
const NAME = 'Embedded-Engineering-Roadmap'; const NAME = 'Embedded-Engineering-Roadmap';
const PNG_SCALE = 2; const PNG_SCALE = 2;