happy with the blog portion. Recipes needs to be re-integrated

This commit is contained in:
len0rd 2023-01-20 10:32:51 -05:00
parent 932c17d274
commit 67fd3d91af
29 changed files with 412 additions and 2194 deletions

6
.gitignore vendored
View file

@ -1,5 +1,5 @@
*.DS_STORE *.DS_STORE
node_modules _website/
views/partials/md/
views/partials/generated/
*.mp4 *.mp4
.doctrees/
__pycache__/

1
_templates/sitename.html Normal file
View file

@ -0,0 +1 @@
<a class="navbar-brand text-wrap" href="{{ pathto(master_doc) }}"><h1 class="site-logo" id="site-title">{{ html_title }}</h1></a>

View file

@ -1,37 +0,0 @@
img {
display: block;
margin-left: auto;
margin-right: auto;
padding-top: 2vh;
padding-bottom: 2vh;
width: 50%;
}
iframe {
display: block;
margin-left: auto;
margin-right: auto;
width: 50%;
margin-top: 2vh;
margin-bottom: 2vh;
}
p {
padding-left: 5%;
}
ul {
margin-left: 5%;
}
ol {
margin-left: 5%;
}
li p {
padding-left: 0;
}
.sticky-offset {
top: 56px !important;
}

View file

@ -1,65 +0,0 @@
.icon-footer {
padding: 3%;
padding: 2vh;
}
.bgimage {
width: 100%;
height: 100vh;
background: url('../img/logo.svg');
background-repeat: no-repeat;
background-position: center;
background-size: cover;
background-attachment: fixed;
}
.bgimage h1 {
color: white;
text-shadow: 2px 2px #333;
}
.center {
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
}
.cenMain {
padding-top: 40%;
padding-bottom: 40%;
padding-top: 40vh;
padding-bottom: 40vh;
width: 60%;
margin: auto;
}
.container-cenMain {
height: 100%;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
.topMargin {
padding-top: 5%;
padding-top: 5vh;
width: 60%;
margin: auto;
}
.code {
background-color: #09003d;
}
.card-soft-link {
color: #5AB2DA;
padding-left: 2%;
}
.card-homepage {
background-color: rgba(0, 0, 0, 0.1) !important;
padding: 2%;
height: 100%;
}

View file

@ -0,0 +1,75 @@
<mxfile host="65bd71144e">
<diagram id="SKV8PHPIui5c4Fc6kAOh" name="Page-1">
<mxGraphModel dx="889" dy="665" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="11" style="edgeStyle=orthogonalEdgeStyle;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;endArrow=diamond;endFill=0;" parent="1" source="2" target="8" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="14" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=diamond;endFill=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" parent="1" source="2" target="12" edge="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="460" y="100"/>
<mxPoint x="425" y="100"/>
</Array>
</mxGeometry>
</mxCell>
<mxCell id="2" value="Hardware Abstraction Layer (Interfaces of core hardware drivers)" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="250" y="260" width="360" height="70" as="geometry"/>
</mxCell>
<mxCell id="3" value="ISPI" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="250" y="290" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="4" value="IGPIO" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="320" y="290" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="5" value="IUART" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="390" y="290" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="6" value="II2C" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="460" y="290" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="7" value="..." style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="530" y="290" width="80" height="40" as="geometry"/>
</mxCell>
<mxCell id="13" style="edgeStyle=orthogonalEdgeStyle;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;endArrow=diamond;endFill=0;" parent="1" source="8" target="12" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="8" value="Device Drivers &lt;br&gt;(decoupled from underlying&lt;br&gt;&amp;nbsp;hardware driver implementation)" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="250" y="120" width="180" height="100" as="geometry"/>
</mxCell>
<mxCell id="9" value="IMU" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="250" y="180" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="10" value="etc etc" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="320" y="180" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="12" value="Libraries&lt;br&gt;(Business logic that uses a combination of device and&lt;br&gt;hardware driver interfaces)" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="250" y="10" width="350" height="60" as="geometry"/>
</mxCell>
<mxCell id="16" style="edgeStyle=orthogonalEdgeStyle;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;endArrow=classicThin;endFill=0;" parent="1" source="15" target="5" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="15" value="Hardware-specific implementations of interfaces" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="245" y="370" width="360" height="70" as="geometry"/>
</mxCell>
<mxCell id="17" value="MCU X&lt;br&gt;implementation" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="262.5" y="410" width="117.5" height="70" as="geometry"/>
</mxCell>
<mxCell id="18" value="MCU Y&lt;br&gt;implementation" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="410" y="410" width="120" height="70" as="geometry"/>
</mxCell>
<mxCell id="19" value="" style="shape=curlyBracket;whiteSpace=wrap;html=1;rounded=1;" parent="1" vertex="1">
<mxGeometry x="210" y="400" width="20" height="90" as="geometry"/>
</mxCell>
<mxCell id="20" value="" style="shape=curlyBracket;whiteSpace=wrap;html=1;rounded=1;flipH=1;" parent="1" vertex="1">
<mxGeometry x="620" y="400" width="20" height="90" as="geometry"/>
</mxCell>
<mxCell id="22" value="&lt;h1&gt;&lt;br&gt;&lt;/h1&gt;&lt;div&gt;Implementation is build-time selectable depending on your target environment&lt;/div&gt;" style="text;html=1;strokeColor=none;fillColor=none;spacing=5;spacingTop=-20;whiteSpace=wrap;overflow=hidden;rounded=0;align=right;" parent="1" vertex="1">
<mxGeometry x="20" y="370" width="190" height="110" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View file

@ -0,0 +1,112 @@
<mxfile host="65bd71144e">
<diagram id="SKV8PHPIui5c4Fc6kAOh" name="Page-1">
<mxGraphModel dx="1739" dy="1765" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="30" value="Application ABC&lt;br&gt;- top-level execution (main())&lt;br&gt;- initializes libraries required for app using&lt;br&gt;interface references from Board Data ABC" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" vertex="1" parent="1">
<mxGeometry x="155" y="-200" width="270" height="160" as="geometry"/>
</mxCell>
<mxCell id="11" style="edgeStyle=orthogonalEdgeStyle;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;endArrow=diamond;endFill=0;" parent="1" source="2" target="8" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="14" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=diamond;endFill=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" parent="1" source="2" target="12" edge="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="460" y="90"/>
<mxPoint x="470" y="90"/>
</Array>
</mxGeometry>
</mxCell>
<mxCell id="2" value="Hardware Abstraction Layer (Interfaces of core hardware drivers)" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="250" y="260" width="360" height="70" as="geometry"/>
</mxCell>
<mxCell id="3" value="ISPI" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="250" y="290" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="4" value="IGPIO" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="320" y="290" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="5" value="IUART" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="390" y="290" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="6" value="II2C" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="460" y="290" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="7" value="..." style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="530" y="290" width="80" height="40" as="geometry"/>
</mxCell>
<mxCell id="13" style="edgeStyle=orthogonalEdgeStyle;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;endArrow=diamond;endFill=0;" parent="1" source="8" target="12" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="28" style="edgeStyle=orthogonalEdgeStyle;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;endArrow=diamond;endFill=0;" edge="1" parent="1" source="8" target="27">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="340" y="90"/>
<mxPoint x="290" y="90"/>
</Array>
</mxGeometry>
</mxCell>
<mxCell id="8" value="Device Drivers &lt;br&gt;(decoupled from underlying&lt;br&gt;&amp;nbsp;hardware driver implementation)" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="250" y="120" width="180" height="100" as="geometry"/>
</mxCell>
<mxCell id="9" value="IMU" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="250" y="180" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="10" value="etc etc" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="320" y="180" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="31" style="edgeStyle=orthogonalEdgeStyle;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;endArrow=diamond;endFill=0;" edge="1" parent="1" source="12" target="27">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="12" value="Libraries&lt;br&gt;(Business logic that uses a combination of device and&lt;br&gt;hardware driver interfaces)" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="325" y="10" width="290" height="60" as="geometry"/>
</mxCell>
<mxCell id="16" style="edgeStyle=orthogonalEdgeStyle;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;endArrow=classicThin;endFill=0;" parent="1" source="15" target="5" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="26" style="edgeStyle=orthogonalEdgeStyle;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;endArrow=diamond;endFill=0;" edge="1" parent="1" source="15" target="23">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="15" value="Hardware-specific implementations of interfaces" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="245" y="370" width="360" height="70" as="geometry"/>
</mxCell>
<mxCell id="17" value="MCU X&lt;br&gt;implementation" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="262.5" y="410" width="117.5" height="70" as="geometry"/>
</mxCell>
<mxCell id="18" value="MCU Y&lt;br&gt;implementation" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" parent="1" vertex="1">
<mxGeometry x="410" y="410" width="120" height="70" as="geometry"/>
</mxCell>
<mxCell id="19" value="" style="shape=curlyBracket;whiteSpace=wrap;html=1;rounded=1;" parent="1" vertex="1">
<mxGeometry x="210" y="400" width="20" height="90" as="geometry"/>
</mxCell>
<mxCell id="20" value="" style="shape=curlyBracket;whiteSpace=wrap;html=1;rounded=1;flipH=1;" parent="1" vertex="1">
<mxGeometry x="620" y="400" width="20" height="90" as="geometry"/>
</mxCell>
<mxCell id="22" value="&lt;h1&gt;&lt;br&gt;&lt;/h1&gt;&lt;div&gt;Implementation is build-time selectable depending on your target environment&lt;/div&gt;" style="text;html=1;strokeColor=none;fillColor=none;spacing=5;spacingTop=-20;whiteSpace=wrap;overflow=hidden;rounded=0;align=right;" parent="1" vertex="1">
<mxGeometry x="20" y="370" width="190" height="110" as="geometry"/>
</mxCell>
<mxCell id="29" style="edgeStyle=orthogonalEdgeStyle;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;endArrow=diamond;endFill=0;" edge="1" parent="1" source="23" target="27">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="105" y="90"/>
<mxPoint x="290" y="90"/>
</Array>
</mxGeometry>
</mxCell>
<mxCell id="23" value="Board Configuration info:&lt;br&gt;- gpio configuration&lt;br&gt;- clock configuration&lt;br&gt;- any special bootup specifics" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" vertex="1" parent="1">
<mxGeometry x="-30" y="120" width="270" height="105" as="geometry"/>
</mxCell>
<mxCell id="24" value="Board A&lt;br&gt;Has &quot;MCU X&quot;" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" vertex="1" parent="1">
<mxGeometry x="-20" y="200" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="25" value="Board B&lt;br&gt;Has &quot;MCU Y&quot;" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" vertex="1" parent="1">
<mxGeometry x="110" y="200" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="27" value="Board Data ABC&lt;br&gt;- Selects board&lt;br&gt;- initializes board-specific drivers that are&lt;br&gt;required by the application" style="html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;" vertex="1" parent="1">
<mxGeometry x="155" y="-120" width="270" height="80" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

59
conf.py Normal file
View file

@ -0,0 +1,59 @@
# Sphinx docs configuration for building project documentation
from datetime import datetime
project = "lenordsNet"
author = "lenord"
copyright = f"{datetime.now().year}, lenordsNet"
extensions = [
"sphinxcontrib.youtube",
"ablog",
"sphinx.ext.intersphinx",
"sphinx_design",
]
html_static_path = ["_static"]
templates_path = ["_templates"]
blog_baseurl = "https://lenord.me/"
# A path relative to the configuration directory for posts archive pages.
blog_path = "posts"
# The "title" for the posts, used in active pages. Default is ``'Blog'``.
blog_title = "lenordsNet"
fontawesome_included = True
html_baseurl = blog_baseurl
html_title = blog_title
html_theme = "sphinx_book_theme"
html_theme_options = {
"repository_url": "https://github.com/len0rd/",
"search_bar_text": "search ...",
"show_prev_next": False,
"navbar_center": [],
"use_fullscreen_button": False,
"use_repository_button": True,
# "footer_items": ["copyright", "sphinx-version", x"last-updated"],
}
html_favicon = "assets/img/favicon.ico"
html_sidebars = {
"*": [
"sitename.html",
"search-field.html",
"recentposts.html",
"archives.html",
],
"posts/**": [
"sitename.html",
"search-field.html",
"postcard.html",
"recentposts.html",
"archives.html",
],
}
html_context = {"html_title": html_title}
pygments_style = "sas"

15
index.rst Normal file
View file

@ -0,0 +1,15 @@
Recent Posts
============
.. toctree::
:name: mastertoc
:caption: Contents
:titlesonly:
:hidden:
.. postlist:: 10
:author: len0rd
:date: %Y-%m-%d
:format: {date} - {title}
:list-style: none
:excerpts:

1387
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,27 +0,0 @@
{
"name": "len0rd_net",
"version": "0.0.1",
"description": "src for lenords.net",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"prestart": "./server_startup.sh"
},
"repository": {
"type": "git",
"url": "git+https://github.com/len0rd/personal-website.git"
},
"author": "len0rd",
"license": "MIT",
"bugs": {
"url": "https://github.com/len0rd/personal-website/issues"
},
"homepage": "https://github.com/len0rd/personal-website",
"dependencies": {
"ejs": "^3.1.8",
"express": "^4.17.1",
"markdown-it": "^13.0.1",
"markdown-it-hashtag": "^0.4.0",
"mkdirp": "^1.0.4"
}
}

View file

@ -1,3 +1,6 @@
sphinx sphinx
sphinx_bootstrap_theme ablog
sphinx-design
pydata_sphinx_theme
sphinxcontrib-youtube sphinxcontrib-youtube
sphinx-book-theme

View file

@ -3,6 +3,11 @@
Darkstar Quadcopter Darkstar Quadcopter
=================== ===================
.. post:: 31, July 2018
:tags: drone, diy
:category: Projects
:author: len0rd
The Darkstar is a RC quadcopter with the ability to fly autonomously through pre-designated waypoints, using advanced estimation techniques and object avoidance. The Darkstar is a RC quadcopter with the ability to fly autonomously through pre-designated waypoints, using advanced estimation techniques and object avoidance.
At least that is the end-goal. Getting there however requires resources I simply do not have as a college student (read: money). Given such constraints, building an advance copter on the cheap sounded like a good challenge. Perhaps you can learn from my mistakes if you're interested in such a venture. At least that is the end-goal. Getting there however requires resources I simply do not have as a college student (read: money). Given such constraints, building an advance copter on the cheap sounded like a good challenge. Perhaps you can learn from my mistakes if you're interested in such a venture.

View file

@ -0,0 +1,123 @@
.. embedded_dev_primer:
Embedded Development Primer
===========================
.. post:: 05, January 2023
:tags: diy, docker, embedded, development, toolchain, advice
:category: Projects
:author: len0rd
This post is meant to be an overview of some principles and practices I have found helpful for professional baremetal embedded development. This is meant for people who want to have total control over their embedded development environment and codebase.
Tools like `platformio <https://platformio.org/>`_ are excellent and can help us get projects off the ground but are perhaps a little opaque when it comes to toolchain and dependency management. In a professional setting, I often find I need total control over toolchains and dependencies down to the lowest-level. Learning how to intelligently manage these tools in a modern way has been a major effort in my career so far. This post will serve as a collection of my thoughts and lessons-learned from building multiple embedded projects from the ground up.
Lesson 1: Docker for toolchain management
-----------------------------------------
Step in my shoes for a minute: You start a new job. As usual your first task is setting up a development machine for the project. How do you setup the machine? Ah of course, the project has a 10,000 word README with detailed instructions of how to set everything up. It requires you use a very specific linux distro/kernel, install packages in the 'correct' order, and adhere to a specific directory structure. You follow the steps. The project fails to build. You now spend a week combing through the README, comparing your machine to other developers, maybe even reinstalling and starting from scratch.
Does this scenario sound familiar? Its something I have encountered multiple times in my career. Obviously there are a lot of issues with the "installation README" approach. I've also worked projects where a monolithic bash script handles dependency setup. While slightly better, this often encounters the same problems as a README. The root of the issue is that existing developers do not have to consistently use the installation README/script. Therefore these helper utilities are not maintained and eventually fall out of date. They are only touched when a new developer is required to struggle through them. They also become a nightmare when larger upgrades (like a distro bump) become necessary.
These dependency-management issues extend far beyond just setting up a new environment. They also contribute to the core "it works on my machine" problems we so often encounter. In embedded development such problems are amplified since our dependencies (toolchains, debug packages) seemingly cant be neatly container-ized like other languages (python). `Docker <https://docs.docker.com/>`_ is the solution to all of these problems.
What does docker provide?
- **A documented, repeatable dependency installation method**
- **Dependency management that can be version-controlled with the project.** Your dockerfile and the dependencies it captures evolve with the project!
- **A consistent environment between developers and CI servers.** Greatly reducing the "it works on my machine" problem
- **Development environment freedom.** Developers can run any parent distro or IDE they want as long as they can use docker
- **Easy to experiment with and revert dependency changes.** This is huge when looking at bumping toolchains
- **Project environment isolation.** One project doesn't mess things up for another project
Truly I can not sing enough praises to how docker has streamlined embedded development in my career. Today, many IDEs support developing from within a docker container. For instance VSCodes `devcontainer extension <https://code.visualstudio.com/docs/devcontainers/create-dev-container>`_. But since docker is an independent tool, your free to use it with any ide/text-editor you want.
Some common questions:
What about debugging?
^^^^^^^^^^^^^^^^^^^^^
If my toolchain (gcc, gdb) is all within a docker container, how do I flash or debug my target hardware? Easy, just mount your devices into the container. When you run a container in privileged mode (``--privileged``) you can bind mount pretty much anything. So I will bind-mount ``/dev`` which means all USB devices attached to my host are also accessible within the container:
.. code-block:: bash
docker run --mount source=/dev,target=/dev,type=bind --privileged --mount<BIND MOUNT PROJECT DIR> <IMAGE_NAME>
As you would expect, your container will also need udev rules installed if you want to access devices as a non-root user. This configuration step can be easily captured in your dockerfile!
Sharing a development dockerfile with a CI server?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Dependencies/requirements for development are not the same as CI/CD requirements. Typically development requirements are a super-set of CI/CD requirements. ie: developers need additional debug/helper tools in addition to the core toolchain dependencies. This is where `multi-stage docker builds <https://docs.docker.com/build/building/multi-stage/>`_ come in. They allow you to define separate stages in a dockerfile. Thus allowing you to avoid unnecessary installations on CI. For example:
.. code-block:: dockerfile
FROM ubuntu:latest AS project_builder_base
RUN apt update \
&& apt install <toolchain>
&& ...
FROM project_builder_base AS project_development
RUN apt update \
&& apt install openocd <debugTool2> ...
Then in CI you only build the ``project_builder_base`` stage with:
.. code-block:: bash
docker build --target project_builder_base -t projectTag:latest
Multi-stage builds like this require using dockers new build backend `"buildkit" <https://docs.docker.com/build/buildkit/>`_
Lesson 2: Abstract ASAP
-----------------------
Making unit-testable code in embedded development can be challenging. There is a good chunk of code that can only be run on your target and would be complex to either mock out or build an automated test-rig for. I'm talking about the low-level drivers that interact directly with a MCU's hardware peripherals. For this reason, I think its critical to create a decoupling abstraction layer as-soon-as-possible in modern embedded development.
What does that look like? Something like this:
.. image:: ../assets/img/writeup/embedded_dev_primer/hal_concept.svg
Notice all SOC/MCU-specific code is encapsulated down in a single library which we abstract out with an interface layer immediately.
Benefits:
- **Easy to build a project for multiple targets** This benefit is huge. Imagine being able to build the same application for both linux (to unit test/simulate) and for your embedded target. You will catch bugs sooner and easier
- **All layers above the Hardware Abstraction Layer can be unit tested.** This includes device drivers (ie: sensor or actuator drivers) which in old-style development are often coupled to a specific MCU peripheral.
- **Component modularity.** At a system level, it is now very simple to move drivers and libraries from one MCU to another. As long as you have a Hardware Abstraction Layer (HAL) implementation for the new MCU you can mix and match things around. This can be critical in the prototyping stage of product development
I plan to dive into these concepts further in a future post.
Lesson 3: Separate the concepts of MCU and board
------------------------------------------------
This concept is going to add yet-another piece to the Hardware Abstraction Layer puzzle. Separating your HAL implementation from board-specifics is an important step to having a fully modular embedded development project.
What are "board-specifics"? This is the term I use to describe the configuration information of a MCU that is specific to a real piece of hardware. For instance: MCU's will have a set of GPIOs. These GPIOs can be used as basic GPIOs or they can be mapped to SPI clocks, UART data lines, etc etc. The functional mapping of these pins is a detail that is specific to the overall hardware (aka 'board') you run your application on. It makes sense that information like this that is specific to a PCB board layout of a particular MCU should be encapsulated in its own 'library'. This library is separate from your HAL implementation
Here's an extension of the earlier diagram with these concepts added:
.. image:: ../assets/img/writeup/embedded_dev_primer/hal_concept_with_board.svg
Benefits:
- **Easy to track hardware revisions.** Structuring your project this way allows you to simultaneously support multiple revisions of a single board, which can be valuable if hardware availability is limited
- **Hardware Abstraction Layer doesnt change over board revisions.** With MCU peripheral drivers decoupled from board hardware - they dont need to change with each board change
- **Applications only instantiate drivers/resources they need.** Having a abstracted board data at the application-level allows you to only instantiate the drivers/peripherals that are required by the application. This will save memory and compute.
Conclusion
----------
This was a very brief overview of some of my most valuable baremetal development lessons-learned. Following these lessons allows truly agile hardware and software development in the embedded space, which I dont think was possible with older development paradigms. I plan to expand on each of these topics in future posts.

View file

@ -3,6 +3,11 @@
LS-1: Modular Synth LS-1: Modular Synth
=================== ===================
.. post:: 04, September 2018
:tags: audio, synthesizer, diy, breadboard
:category: Projects
:author: len0rd
The LS-1 is a modular oscillator and sequencer, and includes 2 LFOs (low-frequency oscillators), one external oscillator, and the oscillator attached to the sequencer. The sequencer itself is made up of a counter which acts as a LFO/clock divider, and dual muxes to select the feedback resistance and led to display. The counter outputs and mux select inputs have ports on the front-panel allowing the user to mix and match LFO divisions with mux selects, thus creating custom sequences. The LS-1 is a modular oscillator and sequencer, and includes 2 LFOs (low-frequency oscillators), one external oscillator, and the oscillator attached to the sequencer. The sequencer itself is made up of a counter which acts as a LFO/clock divider, and dual muxes to select the feedback resistance and led to display. The counter outputs and mux select inputs have ports on the front-panel allowing the user to mix and match LFO divisions with mux selects, thus creating custom sequences.
This build included a lot of firsts for me. This is the first time Ive used Eagle to create a PCB/schematic (which should honestly be considered an atrocity given I am a Computer Engineering student), my first in creating a metal case using a water jet, and in general this is my first large-scale hobby project. This build included a lot of firsts for me. This is the first time Ive used Eagle to create a PCB/schematic (which should honestly be considered an atrocity given I am a Computer Engineering student), my first in creating a metal case using a water jet, and in general this is my first large-scale hobby project.

View file

@ -3,6 +3,11 @@
My Website My Website
========== ==========
.. post:: 31, July 2018
:tags: coding, diy, old
:category: Projects
:author: len0rd
Starting out with this website, I had essentially no knowledge of modern web technologies. I knew that I wanted something modern but also easy to maintain that I could use well into the future. Starting out with this website, I had essentially no knowledge of modern web technologies. I knew that I wanted something modern but also easy to maintain that I could use well into the future.
The end result is the site you see here. By no means perfect or beautiful, but functional and a place where I can store guides mainly for my benefit. But maybe for your benefit too? I certainly dont know who's reading this ¯\\\_(ツ)_/¯ The end result is the site you see here. By no means perfect or beautiful, but functional and a place where I can store guides mainly for my benefit. But maybe for your benefit too? I certainly dont know who's reading this ¯\\\_(ツ)_/¯

View file

@ -3,6 +3,11 @@
Pallet Desk Pallet Desk
=========== ===========
.. post:: 02, September 2018
:tags: furniture, diy, amateur, pallets
:category: Projects
:author: len0rd
As I finished up my sophomore year at university, I realized that I needed my own desk. At that point I had lived in three different apartments. All 3 of them had desks provided, and all of those desks were mediocre at best, downright broken at worst. What's worse is at the time I worked from home, which meant I *needed* a reliable desk. With the summer about to hit, and plans to work full-time from home, I knew I needed something fast. Here's the end result: As I finished up my sophomore year at university, I realized that I needed my own desk. At that point I had lived in three different apartments. All 3 of them had desks provided, and all of those desks were mediocre at best, downright broken at worst. What's worse is at the time I worked from home, which meant I *needed* a reliable desk. With the summer about to hit, and plans to work full-time from home, I knew I needed something fast. Here's the end result:
.. image:: ../assets/img/writeup/palletDesk/finished-1-sm.jpg .. image:: ../assets/img/writeup/palletDesk/finished-1-sm.jpg

View file

@ -1,226 +0,0 @@
// convert our markdown documentation files
// to 'static' html/ejs partials:
// while this is a bit inconvenient (you need to restart
// the server everytime you want to see
// md changes), it is more efficient in
// that we aren't converting MD -> ejs
// on EVERY request
const fs = require('fs'),
mkdirp = require('mkdirp'),
recipeInputDir = './recipes/',
recipeOutputDir = './views/partials/md/recipes/',
recipeListGeneratedOutputDir = './views/partials/generated/',
projectClassMap = {
h1: 'display-1' //tag type : class to add to all tags of that type (class="display-1" added to all <h1>)
};
const { assert } = require('console');
function addClassToTag(text, classMap) {
var modifiedText = text;
Object.keys(classMap).forEach(function (key) {
var regex = new RegExp(`<(${key})(.*?)>`, 'g');
matcher = regex.exec(modifiedText);
// only proceed if we found a match, and the class we add isn't already on the tag somehow
while (matcher != null && !matcher[2].includes(classMap[key])) {
// add the class content WHILE preserving any other properties already in the tag!
console.log("adding class content in: " + matcher[0]);
var restOfTag = matcher[2];
modifiedText = modifiedText.replace(matcher[0], `<${key} class="${classMap[key]}" ${restOfTag}>`);
matcher = regex.exec(modifiedText);
}
});
return modifiedText;
}
// handles adding classes to specific
// tag types automatically in project writeups
const projectsAddHeaderClass = {
type: 'output', // when it's triggered -> output is at the very end when text is html
filter: text => { return addClassToTag(text, projectClassMap); }
};
function convertRecipeMarkdown(inputDir, outputDir) {
var md = require('markdown-it')()
.use(require('markdown-it-hashtag'));
md.renderer.rules.hashtag_open = function (tokens, idx) {
var tagName = tokens[idx].content.toLowerCase();
return '<a href="/recipe_navigator?tag=' + tagName + '"><span class="badge bg-secondary">';
}
md.renderer.rules.hashtag_close = function () { return '</span></a>'; }
// This is a hardcoded markdown header section number to html file name
//
// Example.md:
// """
// ... maybe some other header info here -| - exported as filename-title.ejs
// # Delicious Recipe Name -|
// Catch phrase or yield -|
// | - exported as filename-subtitle
// image of the food |
// -|
// ## Ingredients -|
// ... ingredients table, etc | - exported as filename-ingredients.ejs
// -|
// ## Instructions
// """
//
// NOTE: these titles are HARDCODED in recipe_template.ejs!
const mdSectionHtmlTitles = [
'title',
// 'subtitle',
'ingredients',
'instructions',
]
mkdirp.sync(outputDir);
fs.readdir(inputDir, (err, files) => {
files.forEach(file => {
if (!file.endsWith('.md')) {
return;
}
let fileNameNoExtension = file.slice(0, -3);
console.log('converting: ' + fileNameNoExtension);
fs.readFile(inputDir + file, 'utf8', (err, data) => {
if (err) {
console.error(err);
return;
}
var ingredientTableRegex = new RegExp(`^(\\|?.*?)\\|(.*?)(\\|.*?\\|.*?)\n`, `gm`);
var ingredientDashCheck = new RegExp("^\-+$");
ingredientTableMatcher = ingredientTableRegex.exec(data);
while (ingredientTableMatcher != null) {
meas = ingredientTableMatcher[1];
let unit = ingredientTableMatcher[2].toLowerCase().trim();
if (unit != "unit" && unit && !ingredientDashCheck.test(unit)) {
meas += unit + " ";
}
data = data.replace(ingredientTableMatcher[0], `${meas}${ingredientTableMatcher[3]}\n`);
ingredientTableMatcher = ingredientTableRegex.exec(data);
}
ingredientTableMatcher = ingredientTableRegex.exec(data);
while (ingredientTableMatcher != null) {
meas = ingredientTableMatcher[1];
let unit = ingredientTableMatcher[2].toLowerCase().trim();
if (unit != "unit" && unit && !ingredientDashCheck.test(unit)) {
meas += unit + " ";
}
data = data.replace(ingredientTableMatcher[0], `${meas}${ingredientTableMatcher[3]}\n`);
ingredientTableMatcher = ingredientTableRegex.exec(data);
}
let tokens = md.parse(data)
let sections = []
sections.push([]); // start off the array and put everything before and including the first header in title
let numSections = 0;
for (const token of tokens) {
if (token.type === 'heading_open') {
if (numSections == 0) {
numSections++;
}
else if (numSections < mdSectionHtmlTitles.length) {
numSections++;
sections.push([]);
}
}
sections[sections.length - 1].push(token)
}
assert(sections.length <= mdSectionHtmlTitles.length);
// hardcode bootstrap class attribute to add to <table> tag in ingredients
for (let ii = 0; ii < sections[1].length; ii++) {
if (sections[1][ii].type == 'table_open') {
sections[1][ii].attrs = [["class", "table table-striped table-sm table-hover"]];
break;
}
}
for (let ii = 0; ii < sections.length; ii++) {
let html = md.renderer.render(sections[ii], md.options);
// hardcode making images in the title section larger
if (ii == 0) {
var regex = new RegExp(`<img (.*?)>`, `g`);
matcher = regex.exec(html);
while (matcher != null && !matcher[1].includes("w-100")) {
var restOfTag = matcher[1];
html = html.replace(matcher[0], `<img class="w-100" ${restOfTag}>`);
matcher = regex.exec(html);
}
}
fs.writeFileSync(outputDir + fileNameNoExtension + '-' + mdSectionHtmlTitles[ii] + '.ejs', html, 'utf8');
}
});
});
});
}
function generateRecipeNavigatorList(recipeSrcDir, generatedOutputDir) {
// generate a list of recipe links. While doing so generate an array
// of unique hashtags found in all recipes
mkdirp.sync(generatedOutputDir);
let recipeListPartialOut = "";
let allRecipeHashtags = [];
fs.readdir(recipeSrcDir, (err, files) => {
files.sort().forEach(file => {
if (!file.endsWith('.md')) {
return;
}
let fileNameNoExtension = file.slice(0, -3);
const data = fs.readFileSync(recipeSrcDir + file, { encoding: 'utf8', flag: 'r' });
// find all hashtags in the file
var hashtagRegex = new RegExp(`#(\\w+)`, `g`);
hashtagMatcher = hashtagRegex.exec(data);
var recipeTags = []; // hashtags of the current recipe only
while (hashtagMatcher != null) {
var hashtag = hashtagMatcher[1].toLowerCase();
if (!allRecipeHashtags.includes(hashtag)) {
allRecipeHashtags.push(hashtag);
}
if (!recipeTags.includes(hashtag)) {
recipeTags.push(hashtag);
}
hashtagMatcher = hashtagRegex.exec(data);
}
let combinedRecipeTags = "";
if (recipeTags.length > 0) {
combinedRecipeTags = recipeTags.join(",");
}
// get first recipe title from document
var titleRegex = new RegExp(`#\\s+(.+)\\n`, `g`);
titleMatcher = titleRegex.exec(data);
var recipeTitle = fileNameNoExtension;
if (titleMatcher != null) {
recipeTitle = titleMatcher[1];
}
recipeListPartialOut += `<a href="recipes/${fileNameNoExtension}" class="list-group-item list-group-item-action" tags="${combinedRecipeTags}">${recipeTitle}</a>\n`;
});
// writeout the link list partial
fs.writeFileSync(generatedOutputDir + "recipe-links.ejs", recipeListPartialOut, "utf-8");
// now generate the hashtag button list partial
// TODO: in the future sort the list by number of hashtag hits (most -> least common)
// instead of alphabetically
let tagListPartialOut = "";
allRecipeHashtags.sort().forEach(hashtag => {
tagListPartialOut += `<button type="button" class="btn btn-light btn-sm">${hashtag}</button>\n`;
});
fs.writeFileSync(generatedOutputDir + "recipe-tags.ejs", tagListPartialOut, "utf-8");
});
}
convertRecipeMarkdown(recipeInputDir, recipeOutputDir);
generateRecipeNavigatorList(recipeInputDir, recipeListGeneratedOutputDir);

View file

@ -1,28 +0,0 @@
# Sphinx docs configuration for building project documentation
from datetime import datetime
import sphinx_bootstrap_theme
import os
project = "lenordsNet Projects"
author = "lenord"
copyright = f"{datetime.now().year}, lenordsNet"
extensions = [
"sphinxcontrib.youtube",
]
root_doc = "contents"
html_theme = "bootstrap"
html_theme_path = sphinx_bootstrap_theme.get_html_theme_path()
html_baseurl = "/"
html_use_index = False
html_theme_options = {
"navbar_title": "lenordsNet",
"navbar_sidebarrel": False,
"navbar_class": "navbar navbar-inverse navbar-dark",
"source_link_position": "footer",
"bootswatch_theme": "cyborg",
}
pygments_style = "sas"

View file

@ -1,15 +0,0 @@
lenordsNet project documentation
================================
`Home <../../>`_
----------------
.. toctree::
:maxdepth: 2
:caption: Contents:
Home <../../#http://>
ls1synth
myWebsite
palletDesk
darkstar

View file

@ -1,42 +0,0 @@
const PORT = 8090;
var express = require('express');
const path = require("path");
var app = express();
console.log('Starting express server on port ' + PORT);
// set the view engine to ejs
app.set('view engine', 'ejs');
app.set("views", path.join(__dirname, "views"));
// add folder for static content:
app.use(express.static(path.join(__dirname, 'assets')));
function getRootPage(req, res) {
let pageName = req.params["pageName"];
if (pageName === null || pageName === undefined) {
pageName = "index";
}
// hardcoded check for favicon as it always comes in on /favicon.ico :/
if (req.path.includes("favicon")) {
res.sendFile(pageName, { root: path.join(__dirname, "assets", "img") });
}
else {
res.render(path.join("pages", pageName));
}
}
app.get("/:pageName", getRootPage);
app.get("/", getRootPage);
app.get("/projects/:projectName", (req, res) => {
res.sendFile(req.params["projectName"], { root: path.join(__dirname, "assets", "projects") })
});
app.get("/recipes/:recipeName", (req, res) => {
let pathname = path.join("pages", "recipes", "recipe_template");
let page = path.join("partials", "md", "recipes", req.params["recipeName"])
res.render(pathname, { "page": page });
});
app.listen(PORT);

View file

@ -1,4 +0,0 @@
# build project documentation
sphinx-build project_writeups/ assets/projects -b html
# generate recipe ejs partials
node prestart.js

View file

@ -1,23 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<%- include('../partials/include') %>
</head>
<body class="d-flex flex-column min-vh-100">
<header>
<%- include('../partials/nav') %>
</header>
<div class="container mt-5 topMargin">
<h1>Contact me</h1>
<p>Questions? Comments? Spam? Please email me with the below address:</p>
<h4>len0rd"AT"fastmail.co.uk</h4>
</div>
<%- include('../partials/footer') %>
<%- include('../partials/post_html_include') %>
</body>
</html>

View file

@ -1,142 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<%- include('../partials/include') %>
</head>
<body>
<header>
<%- include('../partials/nav') %>
</header>
<main role="main">
<section class="bgimage">
<div class="container center">
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<h1 class="display-1">welcome to lenordsNet</h1>
<h3 class="text-white">enjoy my ramblings</h3>
</div>
</div>
</div>
</section>
<div class="container-flex code">
<div class="container">
<div class="row text-white pb-5 pt-5">
<div class="col-4 pr-5 d-flex">
<samp class="display-1 ml-auto">></samp>
</div>
<div class="col-8">
<samp>I'm an American embedded software engineer with diverse industry experience. From large
commercial Java projects to low-level C firmware development, I've done a little bit of
everything (except web development as will be evident with this website). Such experience
allows me to understand and solve new problems quickly. My hobbies are just as eclectic as
my professional experience. This website showcases many of my personal projects.</samp>
</div>
</div>
</div>
<div class="row row-cols-1 row-cols-md-3 g-2 p-5">
<div class="col">
<div class="card bg-dark card-homepage border-light text-white">
<div class="card-body">
<h5 class="card-title">Yama Crawler</h5>
<h6 class="card-subtitle mb-2 text-muted">Selenium-Based Web Crawler</h6>
<p class="card-text">Yama is a powerful web crawler/scraper based on Selenium and Java.
Built for and used in a commercial application by yours truly</p>
</div>
<div class="card-footer pb-3">
<a href="https://github.com/len0rd/YamaCrawler" class="card-link card-soft-link">See
Code</a>
</div>
</div>
</div>
<div class="col">
<div class="card bg-dark card-homepage border-light text-white">
<div class="card-body">
<h5 class="card-title">Mavlib Gen</h5>
<h6 class="card-subtitle mb-2 text-muted">Modern Mavlink C generator</h6>
<p class="card-text">This is what happens when I get fed up with a protocols reference
implementation (after using it for over 4 years)</p>
</div>
<div class="card-footer pb-3">
<a href="https://github.com/len0rd/mavlib_gen" class="card-link card-soft-link">See Code</a>
</div>
</div>
</div>
<div class="col">
<div class="card bg-dark card-homepage border-light text-white">
<div class="card-body">
<h5 class="card-title">Darkstar</h5>
<h6 class="card-subtitle mb-2 text-muted">Why buy a quad when you can build it</h6>
<p class="card-text">Darkstar was my first foray into building my own drone. I used a
mixture
of open source software and some of my own stuff to get the hunk of junk into the air
</p>
</div>
<div class="card-footer pb-3">
<a href="projects/darkstar.html" class="btn btn-outline-light">Read More</a>
<a href="https://github.com/len0rd/darkstar_copter" class="card-link card-soft-link">See
Code</a>
</div>
</div>
</div>
<div class="col">
<div class="card bg-dark card-homepage border-light text-white">
<div class="card-body">
<h5 class="card-title">Pallet Desk</h5>
<h6 class="card-subtitle mb-2 text-muted">Reliable and cheap desk</h6>
<p class="card-text">Building my dream desk on a poor-college-student budget. The end
result: a portable and rock solid desk with a couple of flaws</p>
</div>
<div class="card-footer pb-3">
<a href="projects/palletDesk.html" class="btn btn-outline-light">Read More</a>
</div>
</div>
</div>
<div class="col">
<div class="card bg-dark card-homepage border-light text-white">
<div class="card-body">
<h5 class="card-title">My Website</h5>
<h6 class="card-subtitle mb-2 text-muted">Is this meta</h6>
<p class="card-text">A post on my website about how I built my website? Yep. Turns out
things aren't quite plug 'n play when you're a stubborn engineer who wants something to
function in a very specific way</p>
</div>
<div class="card-footer pb-3">
<a href="projects/myWebsite.html" class="btn btn-outline-light">Read More</a>
<a href="https://github.com/len0rd/personal-website" class="card-link card-soft-link">See
Code</a>
</div>
</div>
</div>
<div class="col">
<div class="card bg-dark card-homepage border-light text-white">
<div class="card-body">
<h5 class="card-title">LS-1 Synth</h5>
<h6 class="card-subtitle mb-2 text-muted">Music to my ears</h6>
<p class="card-text">I find that I learn best by doing. So when I starting considering
getting into the <i>extremely</i> expensive hobby of analog modular synths, I figured
the best way to start would be to build my own. The results aren't terribly impressive,
but helped me learn a whole ton along the way</p>
</div>
<div class="card-footer pb-3">
<a href="projects/ls1synth.html" class="btn btn-outline-light">Read More</a>
<a href="https://github.com/len0rd/LS1-sequencer" class="card-link card-soft-link">See
Code</a>
</div>
</div>
</div>
</div>
</div>
</main>
<%- include('../partials/footer') %>
<%- include('../partials/post_html_include') %>
</body>
</html>

View file

@ -1,103 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<%- include('../partials/include' ) %>
<link rel="stylesheet" type="text/css" href="/css/projects.css">
</head>
<body class="d-flex flex-column min-vh-100">
<header>
<%- include('../partials/nav') %>
</header>
<div class="container mt-5 topMargin">
<div class="row mb-3">
<div class="col">
<h1 class="display-1">Recipes</h1>
</div>
<div class="col-4 align-self-end">
<input class="form-control" id="recipeSearch" type="text" placeholder="search..">
</div>
</div>
<div class="row mb-2">
<div class="col btn-container">
<%- include('../partials/generated/recipe-tags') %>
</div>
</div>
<div class="row">
<div class="list-group list-group-flush" id="recipe-link-container">
<%- include('../partials/generated/recipe-links') %>
</div>
</div>
</div>
<%- include('../partials/footer') %>
<%- include('../partials/post_html_include') %>
<script>
const TAG_ACTIVE_CLASS = "btn-primary";
/// Returns a list of the current recipe hashtags that are enabled (enabled filters)
function getActiveTags() {
var activeTags = [];
// create a list of currently active tags
$(".btn-container button").filter(function() {
if ($(this).hasClass(TAG_ACTIVE_CLASS)){
activeTags.push($(this).text())
}
});
return activeTags;
}
/// check if the given recipe item all the currently selected hashtag filters (an 'AND' search)
function recipeHasAllActiveTags(activeTags, recipe) {
const recipeTags = recipe.attr("tags").toLowerCase().split(",");
return activeTags.every(elem => recipeTags.includes(elem));
}
function recipeHasCurrentSearchPhrase(searchValue, recipe) {
return !searchValue || recipe.text().toLowerCase().indexOf(searchValue) > -1;
}
function setActiveRecipes() {
const activeTags = getActiveTags();
const searchValue = $("#recipeSearch").val().toLowerCase();
$("#recipe-link-container a").filter(function() {
$(this).toggle(recipeHasCurrentSearchPhrase(searchValue, $(this)) && recipeHasAllActiveTags(activeTags, $(this)));
});
}
/// This script is responsible for filtering the recipe list
/// based on enabled/disabled hashtags. I'm certain theres more
/// efficient ways to do this but /shrug
$(".btn-container").on("click", "button", function() {
$(this).toggleClass("btn-light btn-primary");
setActiveRecipes();
});
/// This method is responsible for filtering the recipe list based on the search bar
$(document).ready(function() {
$("#recipeSearch").on("keyup", function() {
setActiveRecipes();
});
});
// apply a tag on page load if specified in the url
$(document).ready(function() {
let searchParams = new URLSearchParams(window.location.search);
if (searchParams.has("tag")) {
const tagParam = searchParams.get("tag").toLowerCase().trim();
$(".btn-container button").filter(function() {
if (tagParam == $(this).text().toLowerCase().trim()) {
$(this).toggleClass("btn-light btn-primary");
setActiveRecipes();
return;
}
});
}
});
</script>
</body>
</html>

View file

@ -1,34 +0,0 @@
<% var rootPath = '../../'; %>
<!DOCTYPE html>
<html lang="en">
<head>
<%- include(rootPath + 'partials/include' ) %>
<link rel="stylesheet" type="text/css" href="/css/projects.css">
</head>
<body>
<header>
<%- include(rootPath + 'partials/nav') %>
</header>
<div class="container mt-5 topMargin">
<div class="row">
<div class="col-md-8">
<%- include(rootPath + page + '-title') %>
</div>
<div class="col">
<div class="card">
<div class="card-body">
<%- include(rootPath + page + '-ingredients') %>
</div>
</div>
</div>
</div>
<%- include(rootPath + page + '-instructions') %>
</div>
<%- include(rootPath + 'partials/post_html_include') %>
</body>
</html>

View file

@ -1,26 +0,0 @@
<!-- you may need to remove 'fixed-bottom' if the footer is persisting on scroll-->
<footer class="footer bg-black">
<div class="container-fluid text-center">
<div class="row">
<div class="col-md-12">
<div class="flex-center py-1">
<a class="li-ic icon-footer" style="color:black" href="https://github.com/len0rd">
<i class="fab fa-github fa-lg" style="color:white"></i>
</a>
<a class="li-ic icon-footer" style="color:black" href="https://www.youtube.com/channel/UCwGDckTCkqqW5W8JcRrxELA">
<i class="fab fa-youtube fa-lg" style="color:white"></i>
</a>
<a class="li-ic icon-footer" href="/contact">
<i class="fas fa-at fa-lg" style="color:white"></i>
</a>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 mb-2">
<div class="footer-copyright text-center text-white">&copy; lenordsNet <%= new Date().getFullYear();%></div>
</div>
</div>
</div>
</footer>

View file

@ -1,9 +0,0 @@
<meta charset="UTF-8">
<link rel="icon" type="image/png" href="/img/favicon.png">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/animate.css@3.5.2/animate.min.css">
<link rel="stylesheet" type="text/css" href="/css/site.css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.2.0/css/all.css" integrity="sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+lsFkW54bOGbiDQ" crossorigin="anonymous">
<link rel="stylesheet" href="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.16.2/build/styles/default.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

View file

@ -1,21 +0,0 @@
<nav class="navbar navbar-expand-sm navbar-dark bg-black fixed-top">
<div class="container-fluid">
<a class="navbar-brand" href="/#">lenordsNet</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarText"
aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarText">
<ul class="navbar-nav me-auto my-2 my-lg-0 navbar-nav-scroll" style="--bs-scroll-height: 100px;">
<li class="nav-item">
<a class="nav-link" href="https://home.lenord.me/">Home Assistant</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/recipe_navigator">Recipes</a>
</li>
</ul>
<a href="/contact" class="btn btn-outline-light">Contact</a>
</div>
</div>
</nav>

View file

@ -1 +0,0 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>