diff options
| author | Kleidi Bujari <mail@4kb.net> | 2025-04-02 21:59:20 -0400 |
|---|---|---|
| committer | Kleidi Bujari <mail@4kb.net> | 2025-04-02 21:59:20 -0400 |
| commit | 09c2b6de63a6ff35348fcb2c2eb57b974f0a8a52 (patch) | |
| tree | bd8f1d3a51f12cd98764ae572b00f0b32de62644 /packages | |
| parent | b1738c105de3bc0ba7cd27f68c9eb146439a0964 (diff) | |
| download | depot-09c2b6de63a6ff35348fcb2c2eb57b974f0a8a52.tar.gz depot-09c2b6de63a6ff35348fcb2c2eb57b974f0a8a52.tar.bz2 depot-09c2b6de63a6ff35348fcb2c2eb57b974f0a8a52.zip | |
automate project structure
Diffstat (limited to 'packages')
25 files changed, 1205 insertions, 0 deletions
diff --git a/packages/blog/.gitignore b/packages/blog/.gitignore new file mode 100644 index 0000000..decc3f8 --- /dev/null +++ b/packages/blog/.gitignore @@ -0,0 +1,2 @@ +public/ +nohup.out diff --git a/packages/blog/config.toml b/packages/blog/config.toml new file mode 100644 index 0000000..69c389b --- /dev/null +++ b/packages/blog/config.toml @@ -0,0 +1,8 @@ +base_url = "https://4kb.net" +build_search_index = false +compile_sass = false +generate_feeds = true +minify_html = true + +[markdown] +highlight_code = false diff --git a/packages/blog/content/posts/_index.md b/packages/blog/content/posts/_index.md new file mode 100644 index 0000000..9efc62e --- /dev/null +++ b/packages/blog/content/posts/_index.md @@ -0,0 +1,6 @@ ++++ +title = "Posts" +sort_by = "date" +page_template = "post.html" +redirect_to="/" ++++ diff --git a/packages/blog/content/posts/deterministic-hostnames.md b/packages/blog/content/posts/deterministic-hostnames.md new file mode 100644 index 0000000..43dc7d8 --- /dev/null +++ b/packages/blog/content/posts/deterministic-hostnames.md @@ -0,0 +1,80 @@ +--- +title: "Deterministic and unique network hostnames" +date: "2024-11-10" +--- + +As part of building out a Kubernetes cluster, I wanted to build and distribute a +single OS image to create stateless worker nodes. Using network booting, and +some clever tricks to differentiate nodes, we can create a scaleable and +efficient farm of workers for a cluster that don't even need disks. + +The idea came from a plan to build a cluster using the +[compute blade](https://computeblade.com/), and a few Raspberry Pi SBCs I +already own. Running the cluster from an SD card is not recommended due to the +not-so-great reliability of the flash used by most manufacturers, so I wanted to +try PXE booting each Pi to save money rather than purchasing an SSD for each +one. The compute blades do support an NVMe disk, but I plan to use those for a +storage cluster later, so they need to remain empty. + +## Base image + +Alpine Linux has been my preferred server OS for a long time. It provides a very +lightweight base system, and bundles an excellent bootstrapping system, +[apkovl](https://wiki.alpinelinux.org/wiki/Alpine_local_backup), that allows the +user to save a set of customisations to an system as an overlay to a stock +Alpine live image. In other words, we can create our image once, save the +changes as an `apkovl.tar.gz` file, and apply the same changes to a base system +on boot. This file can even be provided as a +[kernel parameter](https://wiki.alpinelinux.org/wiki/PXE_boot#Guide_to_options) +and will be fetched from a remote webserver automatically! + +Since the image and configuration will be shipped to the node via the network, +an added benefit of using Alpine is its tiny space consumption. I'm not using +enough nodes for this to really matter, but it's a cool optimization regardless. + +## Differentiating the nodes + +One of the main goals of this project is that there should be no persistent +storage required outside the boot image itself. Since every node will download +and generate the same root file-system on startup, the first problem that arises +is how the nodes will identify themselves both on the network and the cluster, +given that it's not possible to name them ahead of time. In other words, any +given node has to generate a unique hostname that won't collide with other +workers, and that will be the same each time that node boots. + +Since these nodes will not have a predefined name, we have to rely on +characteristics of the hardware to differentiate each one. The hardware MAC +address is perfect for this, since it's unique to to each node and will not be +wiped away after the node reboots. On a system like Linux that exposes its +hardware through a _sysfs_, we can find a file containing the address at +`/sys/class/net/eth0/address`. I don't really like the idea of attaching the +literal MAC address of the node to its network hostname, since it's a security +risk, and a bit too verbose. Instead, we can transform it into something safer +using a `sha1sum`, which is already present on our Alpine base system: + +```console +sha1sum /sys/class/net/eth0/address | head -c 6 | awk '{print "worker-" $0}' +``` + +### Applying the new name + +Ideally, the node should apply its generated hostname before reaching out for an +address over DHCP or joining the cluster. We can make sure it happens before any +traffic is sent out by adding a `pre-up` command to the right interface in +`/etc/network/interfaces`: + +``` + +... + +auto eth0 +iface eth0 inet dhcp + pre-up sha1sum /sys/class/net/eth0/address | head -c 6 | awk '{print "worker-" $0}' > /etc/hostname + +... +``` + +The VM I tested with looks outputs `worker-e2fae8`. Pretty clean result, and if +you want to know the physical node that maps to each hostname, you can take note +of the MAC address beforehand and generate the same hash on another computer to +match them up. diff --git a/packages/blog/content/posts/nohup.md b/packages/blog/content/posts/nohup.md new file mode 100644 index 0000000..64f7983 --- /dev/null +++ b/packages/blog/content/posts/nohup.md @@ -0,0 +1,26 @@ +--- +title: "Spawning background processes" +date: "2024-11-17" +--- + +Working in a terminal, +I often pair my editor with a background process watching files. +Before reaching for terminal multiplexers, +see if you can get away with simple tty job control. +Spawn the background process, +still attached to the terminal instance: + +``` +program args & +``` + +Also redirect its output to a file, +for when the process writes to the tty from the background: + +``` +nohup program args & +``` + +Extra reading: + +- <https://jvns.ca/blog/2024/07/03/reasons-to-use-job-control/> diff --git a/packages/blog/content/posts/stateless-compute-networks.md b/packages/blog/content/posts/stateless-compute-networks.md new file mode 100644 index 0000000..bac9e5d --- /dev/null +++ b/packages/blog/content/posts/stateless-compute-networks.md @@ -0,0 +1,41 @@ +--- +title: "On stateless compute networks" +date: "2024-11-10" +draft: true +--- + +On the topic of distributed systems and clustering, +I am quite invested in the idea of compute nodes that rely entirely on the network for configuration. +Arbitrary nodes can join a pre-existing cluster, +offering their CPU time and memory for computation without relying on any pre-existing configuration on the node itself. +In other words, any computer could pick up work, +only needing power and a network connection to the cluster. + +Perhaps this eventually leads into a "self-healing" cluster where only one node is manually bootstrapped, +which then serves a _configuration endpoint_ for other stateless nodes to reach out to for their instructions, +which they will then also serve once they are themselves ready. + +Early revisions of these notes mention Kubernetes, +but I am also trying to achieve similar results with NixOS on a custom project. +In any case, these are my ever-updating notes towards a general implementation of a stateless distributed systems architecture. + +## Self healing cluster + +Assuming control of an external DHCP server, +a self healing Kubernetes cluster would be feasible, +with the PXE boot artifacts supplied by the cluster itself. +That is, as long as one node is running the pod hosting the artifacts on a given endpoint, +other nodes can boot those artifacts and join the cluster, +thereby being able to host the artifacts as well. + +## Configuration endpoint + +The nodes shouldn't require a disk installed to be able to join the network. +Rather, the lofty goal of zero-configuration compute nodes passes the job of node initialization to the supporting network. +This is accomplished with PXE boot instructions supplied over DHCP. + +I delegate the following tasks to a single node in the subnet: + +- Gateway: Optional outbound connections if required +- DHCP server: Cluster IPAM +- TFTP and HTTP server: Serves iPXE firmware and kernel/initrd artifacts diff --git a/packages/blog/content/posts/vim-compilers.md b/packages/blog/content/posts/vim-compilers.md new file mode 100644 index 0000000..f735228 --- /dev/null +++ b/packages/blog/content/posts/vim-compilers.md @@ -0,0 +1,20 @@ +--- +title: "Neovim's built-in compilers" +date: "2025-01-17" +--- + +Today I learned that neovim's `:make` comes with many +[backends](https://neovim.io/doc/user/quickfix.html#_6.-selecting-a-compiler) +already configured. +This works for checking c/cpp and python files, among other, but I was +most interested to see [Typst] and [Pandoc] listed as well. + +I was looking to add this functionality with a plugin or implementing it +manually, +where a document can be compiled on the fly from the editor. +After setting `:compiler pandoc`, +generating a pdf is done with `:make pdf`, +with other pandoc options just appended afterwards if needed. + +[typst]: https://typst.app +[pandoc]: https://github.com/jgm/pandoc diff --git a/packages/blog/default.nix b/packages/blog/default.nix new file mode 100644 index 0000000..204388f --- /dev/null +++ b/packages/blog/default.nix @@ -0,0 +1,21 @@ +{ pkgs, perSystem, ... }: +let + inherit (perSystem.self) + cv + ; +in +pkgs.stdenvNoCC.mkDerivation { + name = "4kb.net"; + src = ./.; + + nativeBuildInputs = with pkgs; [ + zola + ]; + + buildPhase = "zola build"; + installPhase = '' + mkdir -p $out/share + cp -r public/* $out/ + cp ${cv} $out/share/cv.pdf + ''; +} diff --git a/packages/blog/templates/404.html b/packages/blog/templates/404.html new file mode 100644 index 0000000..a4669df --- /dev/null +++ b/packages/blog/templates/404.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} + +{% block content %} +<head> + <title>Error 404!</title> +</head> + +404! +{% endblock content %} diff --git a/packages/blog/templates/base.html b/packages/blog/templates/base.html new file mode 100644 index 0000000..5a91df5 --- /dev/null +++ b/packages/blog/templates/base.html @@ -0,0 +1,88 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>{% block title %}4kb.net{% endblock title %}</title> + </head> + <style> + :root { + --bg: #181616; + --fg: #c5c9c5; + --link: #76946a; + --linkhover: #98bb6c; + } + + html { + scroll-behavior: smooth; + color-scheme: dark; + } + + body { + font-family: serif; + padding: 0 0.75em; + max-width: 42em; + margin: auto; + background: var(--bg); + color: var(--fg); + } + + p { + line-height: 1.3em; + } + + th, + td { + padding: 0.2em 0.4em; + border: thin solid; + } + + table { + border: thin solid; + border-collapse: collapse; + } + + a, + a:link { + color: var(--link); + } + + a:hover { + color: var(--linkhover); + } + + article img { + display: block; + margin: 0 auto; + max-width: 80%; + height: auto; + object-fit: contain; + } + + pre { + padding: 1em; + overflow-x: scroll; + } + </style> + <body> + <header + style=" + margin: 3em 0 3em 0; + display: flex; + gap: 8px; + flex-direction: column; + font-family: monospace; + " + > + <span + >Kleidi Bujari <<a href="mailto:mail@4kb.net">mail@4kb.net</a>></span + > + <nav style="display: flex; gap: 8px"> + <span><a href="/">4kb.net</a> |</span> + <a target="_blank" href="https://github.com/kbujari">git</a> + <a href="/atom.xml">rss</a> + </nav> + </header> + <main>{% block content %} {% endblock %}</main> + </body> +</html> diff --git a/packages/blog/templates/index.html b/packages/blog/templates/index.html new file mode 100644 index 0000000..6e8a2af --- /dev/null +++ b/packages/blog/templates/index.html @@ -0,0 +1,16 @@ +{% extends "base.html" %} {% block content %} + +<div> + {% set section = get_section(path="posts/_index.md") %} + + <ul style="list-style: none; padding-left: 0"> + {% for page in section.pages %} + <li style="display: flex; gap: 1em; margin-bottom: 0.3em"> + <span sty>{{ page.date }}</span> + <a href="{{ page.permalink | safe }}">{{ page.title }}</a> + </li> + {% endfor %} + </ul> + + {% endblock content %} +</div> diff --git a/packages/blog/templates/post.html b/packages/blog/templates/post.html new file mode 100644 index 0000000..b819520 --- /dev/null +++ b/packages/blog/templates/post.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} + +{% block title%} + {{ page.title }} - {{ super() }} +{% endblock title %} + +{% block content %} + +<h1 style="text-align: center">{{ page.title }}</h1> +<article> + {{ page.content | safe }} +</article> + +{% endblock content %} diff --git a/packages/cses/default.nix b/packages/cses/default.nix new file mode 100644 index 0000000..6538eb8 --- /dev/null +++ b/packages/cses/default.nix @@ -0,0 +1,13 @@ +{ pkgs ? import <nixpkgs> { }, ... }: + +pkgs.mkShell { + packages = with pkgs; [ + clang-tools + (writeShellScriptBin "cpprun" '' + TEMP=".tmp.cpp" + g++ -std=c++20 -O3 ./problems/"$1.cpp" -o $TEMP + ./$TEMP + rm $TEMP + '') + ]; +} diff --git a/packages/cses/problems/1068.cpp b/packages/cses/problems/1068.cpp new file mode 100644 index 0000000..8d05936 --- /dev/null +++ b/packages/cses/problems/1068.cpp @@ -0,0 +1,17 @@ +#include <iostream> + +int main(int argc, char *argv[]) { + unsigned long long n; + std::cin >> n; + + while (n != 1) { + std::cout << n << " "; + + if (n % 2 == 0) + n /= 2; + else + n = (n * 3) + 1; + } + + std::cout << 1; +} diff --git a/packages/cses/problems/1069.cpp b/packages/cses/problems/1069.cpp new file mode 100644 index 0000000..232d2f7 --- /dev/null +++ b/packages/cses/problems/1069.cpp @@ -0,0 +1,23 @@ +#include <iostream> + +int main() { + std::string input; + std::cin >> input; + + auto max = 0; + auto curr = '-'; + auto count = 0; + + for (auto const ch : input) { + if (curr != ch) { + curr = ch; + count = 0; + } + + count += 1; + max = std::max(max, count); + } + + std::cout << max; + return 0; +} diff --git a/packages/cses/problems/1070.cpp b/packages/cses/problems/1070.cpp new file mode 100644 index 0000000..131a644 --- /dev/null +++ b/packages/cses/problems/1070.cpp @@ -0,0 +1,15 @@ +#include <iostream> + +int main() { + int n; + std::cin >> n; + + if (n == 1) + std::cout << 1; + else if (n < 4) + std::cout << "NO SOLUTION"; + else { + for (auto i = 2; i <= n; i += 2) std::cout << i << " "; + for (auto i = 1; i <= n; i += 2) std::cout << i << " "; + } +} diff --git a/packages/cses/problems/1071.cpp b/packages/cses/problems/1071.cpp new file mode 100644 index 0000000..778cc86 --- /dev/null +++ b/packages/cses/problems/1071.cpp @@ -0,0 +1,19 @@ +#include <iostream> +#include <tuple> +#include <vector> + +using ull = unsigned long long; + +int main() { + int n; + std::cin >> n; + + while (n--) { + ull x, y; + std::cin >> y >> x; + + auto area = y * y; + auto perimeter = y + y + 1; + auto max = area + perimeter; + } +} diff --git a/packages/cses/problems/1083.cpp b/packages/cses/problems/1083.cpp new file mode 100644 index 0000000..a3cd253 --- /dev/null +++ b/packages/cses/problems/1083.cpp @@ -0,0 +1,20 @@ +#include <iostream> +#include <set> + +using ull = unsigned long long; + +int main(int argc, char *argv[]) { + ull n; + std::cin >> n; + + auto set = std::set<ull>{}; + + std::string numstr; + while (std::getline(std::cin, numstr, ' ')) set.insert(std::stoi(numstr)); + + for (auto i = 1; i <= n; i++) { + if (set.contains(i)) continue; + std::cout << i; + break; + } +} diff --git a/packages/cses/problems/1094.cpp b/packages/cses/problems/1094.cpp new file mode 100644 index 0000000..56f3886 --- /dev/null +++ b/packages/cses/problems/1094.cpp @@ -0,0 +1,27 @@ +#include <iostream> +#include <vector> + +using ull = unsigned long long; + +int main() { + ull n; + auto arr = std::vector<ull>{}; + + std::cin >> n; + + std::string type; + while (std::cin >> type) arr.push_back(std::stoi(type)); + + ull count = 0; + auto last = arr[0]; + + for (auto const num : arr) { + if (num > last) { + last = num; + continue; + } + count += last - num; + } + + std::cout << count; +} diff --git a/packages/cv/cv.typ b/packages/cv/cv.typ new file mode 100644 index 0000000..1120e39 --- /dev/null +++ b/packages/cv/cv.typ @@ -0,0 +1,250 @@ +#let resume( + author: "", + email: "", + github: "", + personal-site: "", + accent-color: "#000000", + font: "New Computer Modern", + body, +) = { + set document(author: author, title: author) + set text( + // LaTeX style font + font: font, + size: 10pt, + lang: "en", + ligatures: false + ) + + set page( + margin: (0.5in), + paper: "us-letter", + ) + + show link: underline + show link: set text( + fill: rgb(accent-color), + ) + + show heading.where(level: 2): it => [ + #pad(top: 0pt, bottom: -10pt, [#smallcaps(it.body)]) + #line(length: 100%, stroke: 1pt) + ] + + // Accent Color Styling + show heading: set text( + fill: rgb(accent-color), + ) + + show heading.where(level: 1): it => [ + #set align(left) + #set text( + weight: 700, + size: 20pt, + ) + #pad(it.body) + ] + + [= #(author)] + + let contact-item(value, prefix: "", link-type: "") = { + if value != "" { + if link-type != "" { + link(link-type + value)[#(prefix + value)] + } else { + value + } + } + } + + // Personal Info + pad( + top: 0.25em, + align(left)[ + #{ + let items = ( + contact-item(email, link-type: "mailto:"), + contact-item(github, link-type: "https://"), + contact-item(personal-site, link-type: "https://"), + ) + items.filter(x => x != none).join(" | ") + } + ], + ) + + set par(justify: true) + + body +} + +#let generic-two-by-two( + top-left: "", + top-right: "", + bottom-left: "", + bottom-right: "", +) = { + [ + #top-left #h(1fr) #top-right \ + #bottom-left #h(1fr) #bottom-right + ] +} + +#let dates-helper( + start-date: "", + end-date: "", +) = { + start-date + " " + $dash.em$ + " " + end-date +} + +#let edu( + institution: "", + dates: "", + degree: "", + location: "", +) = { + generic-two-by-two( + top-left: strong(institution), + top-right: location, + bottom-left: emph(degree), + bottom-right: emph(dates), + ) +} + +#let work( + title: "", + dates: "", + company: "", + location: "", +) = { + generic-two-by-two( + top-left: strong(title), + top-right: dates, + bottom-left: company, + bottom-right: emph(location), + ) +} + +#show: resume.with( + author: "Kleidi Bujari", + email: "mail@4kb.net", + github: "github.com/kbujari", + personal-site: "4kb.net", +) + +== Education + +#edu( + institution: "Toronto Metropolitan University", + dates: dates-helper(start-date: "Sep 2020", end-date: "Apr 2025"), + location: "Ontario, Canada", + degree: "Bachelor's of Engineering, Computer Engineering", +) + +- *Relevant Coursework*: + Data Structures, Embedded Programming, Compilers, Digital Systems, Computer Networks +- *Extracurriculars*: + Member of student robotics design team, + teaching assistant for micro-processor courses. + +== Experience + +#work( + company: "Toronto Metropolitan University", + title: "Graduate Research Assistant", + dates: dates-helper(start-date: "May 2024", end-date: "Sep 2024"), + location: "Toronto, Canada", +) + +- Implemented transformations for hundreds of media files, + using ffmpeg and unix primitives to parallelize workload. +- Designed frontend with AstroJS to generate only static HTML, + ensuring compatibility with many hosting providers. + +#work( + company: "Canadian Broadcasting Corporation", + title: "Network Engineering Intern", + dates: dates-helper(start-date: "May 2023", end-date: "Apr 2024"), + location: "Toronto, Canada", +) + +- Designed custom PXE-boot implementation for hundreds of devices using NetBox, + eliminating manual configuration. +- Configured Hyper-Converged Proxmox cluster for 2024 Olympics, + saving \$250k+ with reused hardware and open software. +- Deployed vendor-agnostic routing observability from scratch, + with Prometheus metrics and Grafana dashboards. +- Mentored junior application developers in modern C++23 programming, + aiding in performance design and memory safety. + +#work( + company: "WSP Canada", + title: "Student Engineer", + dates: dates-helper(start-date: "May", end-date: "Aug") + ", 2021, 2022", + location: "Toronto, Canada", +) + +- Contributed to subway car control systems with modern C++20, + replacing legacy code with newer STL functions. +- Extended internal distributed filesystem with support for deduplication and compression, + reclaiming 30% of storage. +- Validated new electrical designs for power consumption, + cost efficiency, and viability with existing systems. +- Participated in reviewing and adjusting large scale electrical and structural engineering designs. + +== Projects + +*Custom Linux Distribution* --- +Designed entire Linux distribution used for hosting production servers, +daily desktop use, and embedded programming on a Raspberry Pi. +Using NixOS, it supports enabling only required functionality at build time. +Features systemd, +an in-memory root filesystem, +ZFS persistent storage with backups, +and extremely hardened networking. + +*ICER Compressor* --- +Image compression library written in Rust, +designed for deep-space communication. +Hand tuned for speed and portability by using only integer arithmetic, +no heap allocations, and no standard library by default. +Achieves practically instant compressions, even on microprocessors. + +*Kubernetes Cluster* --- +Bare-metal compute cluster managed with GitOps to be completely reproducible. +Uses Cilium CNI for fast eBPF networking and BGP load balancing, +FluxCD for cluster management, +and CEPH distributed storage for stateful workloads. +Running Prometheus, Loki and AlertManager for complete observability. + +*Mirrorlist Generator* --- +Fetches Arch Linux package mirrors, +filtering them based on user parameters. +Sorts and outputs formatted data compliant with the pacman package manager. +Heavily outperforms default Python implementation. + +*Toronto Metropolitan Robotics* --- +Member of university design team working on space focused automated robotics. +Designed custom STM32 hardware with various interfaces (SPI, I2C, etc.) for controlling motor functions on robot. +Worked alongside various subteams to deliver a competition ready autonomous system. + +== Skills + +- *Languages*: #( + "Rust", + "C++", + "Nix", + "Haskell", + "TypeScript", + "Lua", + "Python", + ).join(", ") + +- *Technologies*: #( + "Linux", + "Compilers", + "Virtualisation", + "Terraform", + "Ansible", + "Computer Networks", + "Frontend (Svelte, Astro)" + ).join(", ") diff --git a/packages/cv/default.nix b/packages/cv/default.nix new file mode 100644 index 0000000..9de6903 --- /dev/null +++ b/packages/cv/default.nix @@ -0,0 +1,5 @@ +{ pkgs, ... }: + +pkgs.runCommand "cv" { } '' + ${pkgs.typst}/bin/typst compile --format pdf ${./cv.typ} $out +'' diff --git a/packages/perf-flamegraph.nix b/packages/perf-flamegraph.nix new file mode 100644 index 0000000..b472b74 --- /dev/null +++ b/packages/perf-flamegraph.nix @@ -0,0 +1,12 @@ +# Script that collects perf timing for the execution of a command and writes a +# flamegraph to stdout +{ pkgs, ... }: + +pkgs.writeShellScriptBin "perf-flamegraph" '' + set -euo pipefail + + ${pkgs.linuxPackages.perf}/bin/perf record -g --call-graph dwarf -F max "$@" + ${pkgs.linuxPackages.perf}/bin/perf script \ + | ${pkgs.flamegraph}/bin/stackcollapse-perf.pl \ + | ${pkgs.flamegraph}/bin/flamegraph.pl +'' diff --git a/packages/resistors/default.nix b/packages/resistors/default.nix new file mode 100644 index 0000000..6a038ea --- /dev/null +++ b/packages/resistors/default.nix @@ -0,0 +1,7 @@ +{ pkgs, ... }: + +pkgs.runCommandNoCC "resisto-rs" { } '' + mkdir -p $out + cp ${./index.html} $out/index.html + cp ${./styles.css} $out/styles.css +'' diff --git a/packages/resistors/index.html b/packages/resistors/index.html new file mode 100644 index 0000000..0683f49 --- /dev/null +++ b/packages/resistors/index.html @@ -0,0 +1,339 @@ +<!doctype html> +<head> + <meta charset="utf-8" /> + <title>rs-calc</title> + <link rel="stylesheet" href="styles.css" /> + <meta name="viewport" content="initial-scale=1.0" /> +</head> + +<body> + <h1>rs-calc</h1> + + <input id="resistor-query" placeholder="e.g. 3.3K" /> + <label class="resistor-query-label" for="resistor-query">Ω</label> + + <div id="resistor-result" style="display: none"> + <h2>four bands</h2> + <div class="resistor-diagram resistor-diagram-4"> + <div id="resistor-stripe-4-1" class="resistor-stripe"></div> + <div id="resistor-stripe-4-2" class="resistor-stripe"></div> + <div id="resistor-stripe-4-3" class="resistor-stripe"></div> + <div id="resistor-stripe-4-4" class="resistor-stripe"> + <br /> + </div> + <div + class="resistor-stripe-tolerance resistor-stripe" + style="color: #000; background-color: #cfb53b" + > + gold<br />±5% + </div> + </div> + <h2>five bands</h2> + <div class="resistor-diagram resistor-diagram-5"> + <div id="resistor-stripe-5-1" class="resistor-stripe"></div> + <div id="resistor-stripe-5-2" class="resistor-stripe"></div> + <div id="resistor-stripe-5-3" class="resistor-stripe"></div> + <div id="resistor-stripe-5-4" class="resistor-stripe"></div> + <div + class="resistor-stripe-tolerance resistor-stripe" + style="color: #fff; background-color: #964b00" + > + brown<br />±1% + </div> + </div> + <h2>surface mount</h2> + <div id="resistor-smt-3" class="resistor-smt"></div> + </div> + + <footer> + <p> + forked from + <a href="https://github.com/joewalnes/resisto.rs/" target="_blank">this</a + >, with removed analytics, cleaned up code. + <a href="https://github.com/kleidib" target="_blank">my github</a> + </p> + </footer> +</body> + +<script> + // Resistor calculator. + // -Joe Walnes + // See resistors-test.html + + var resistors = {}; + + // These hex codes came from + // http://en.wikipedia.org/wiki/Electronic_color_code + resistors.digitsToColors = { + "-2": { hex: "#c0c0c0", label: "#000", name: "silver", multiplier: "0.01" }, + "-1": { hex: "#cfb53b", label: "#000", name: "gold", multiplier: "0.1" }, + 0: { hex: "#000000", label: "#fff", name: "black", figure: "0" }, + 1: { + hex: "#964b00", + label: "#fff", + name: "brown", + figure: "1", + multiplier: "10", + }, + 2: { + hex: "#ff0000", + label: "#fff", + name: "red", + figure: "2", + multiplier: "100", + }, + 3: { + hex: "#ffa500", + label: "#000", + name: "orange", + figure: "3", + multiplier: "1K", + }, + 4: { + hex: "#ffff00", + label: "#000", + name: "yellow", + figure: "4", + multiplier: "10K", + }, + 5: { + hex: "#9acd32", + label: "#000", + name: "green", + figure: "5", + multiplier: "100K", + }, + 6: { + hex: "#6495ed", + label: "#000", + name: "blue", + figure: "6", + multiplier: "1M", + }, + 7: { + hex: "#ee82ee", + label: "#000", + name: "purple", + figure: "7", + multiplier: "10M", + }, + 8: { + hex: "#a0a0a0", + label: "#000", + name: "gray", + figure: "8", + multiplier: "100M", + }, + 9: { + hex: "#ffffff", + label: "#000", + name: "white", + figure: "9", + multiplier: "1000M", + }, + }; + + resistors.query = function (input) { + input = input + .replace(/ +?/g, "") + .replace(/ohm[s]?/, "") + .replace(/\u2126/, "") + .replace(/\.$/, ""); + + var value = this.parseValue(input); + + if (value !== null) { + value = this.roundToSignificantPlaces(value, 3); + + if ((value <= 99900000000 && value >= 1) || value === 0) { + var colors5 = this.numberTo5ColorDigits(value); + var colors4 = this.numberTo4ColorDigits(value); + var smt3 = value < 10 ? colors4[0].toString() : colors4.join(""); + var self = this; + + return { + value: value, + smt3: smt3, + //smt4: smt4, + //smtEia96: smtEia96, + formatted: this.formatValue(value), + colors5: colors5.map(function (d) { + return self.digitsToColors[d]; + }), + colors4: colors4.map(function (d) { + return self.digitsToColors[d]; + }), + }; + } + } + + return null; + }; + + /** + * Given ohm rating as string (e.g. 3.2m or 3M2), return + * integer value (e.g. 3200000). + */ + resistors.parseValue = function (input) { + var multiplier = 1; + var match; + + if ((match = input.match(/^(\d+)(\.(\d+))?([km])?$/i))) { + // e.g. 123, 1.23, 1M, 1.23M + var unit = match[4]; + if (unit) { + if (unit == "k" || unit == "K") { + multiplier = 1000; + } + if (unit == "m" || unit == "M") { + multiplier = 1000000; + } + } + + return (match[1] + "." + (match[3] || 0)) * multiplier; + } else if ((match = input.match(/^(\d+)([km])(\d+)$/i))) { + // e.g. 12K3 + var unit = match[2]; + if (unit) { + if (unit == "k" || unit == "K") { + multiplier = 1000; + } + if (unit == "m" || unit == "M") { + multiplier = 1000000; + } + } + return (match[1] + "." + (match[3] || 0)) * multiplier; + } else { + return null; + } + }; + + /** + * Round a value to significant places. + * e.g. (123456789, 3) -> 123000000) + * (0.0045678, 3) -> 0.00457) + */ + resistors.roundToSignificantPlaces = function (value, significant) { + if (!value) { + return 0; + } + var nearest = Math.pow( + 10, + Math.floor(Math.log(Math.abs(value)) / Math.log(10)) - (significant - 1), + ); + return Math.round(value / nearest) * nearest; + }; + + /** + * Given ohm rating as integer (e.g. 470000), return + * array of color digits (e.g. 4, 7, 0, 3). See digitsTo_Colors. + */ + resistors.numberTo5ColorDigits = function (value) { + if (!value) { + return [0, 0, 0, 0]; // Special case + } + + var precision = 5; + var digits = ( + Math.floor(value * 100 * Math.pow(10, precision)) / + Math.pow(10, precision) + ).toString(); + function getDigit(digits, i) { + var d = parseInt(digits[i]); + return isNaN(d) ? 0 : d; + } + return [ + getDigit(digits, 0), + getDigit(digits, 1), + getDigit(digits, 2), + digits.length - 5, + ]; + }; + + /** + * Given ohm rating as integer (e.g. 470000), return + * array of color digits (e.g. 4, 7, 0, 3). See digitsTo_Colors. + */ + resistors.numberTo4ColorDigits = function (value) { + if (!value) { + return [0, 0, 0]; // Special case + } + + var precision = 5; + var digits = ( + Math.floor(value * 100 * Math.pow(10, precision)) / + Math.pow(10, precision) + ).toString(); + function getDigit(digits, i) { + var d = parseInt(digits[i]); + return isNaN(d) ? 0 : d; + } + return [getDigit(digits, 0), getDigit(digits, 1), digits.length - 4]; + }; + + /** + * Given a numeric value, format it like '3.2M' etc. + */ + resistors.formatValue = function (value) { + if (value >= 1000000) { + return value / 1000000 + "M"; + } else if (value >= 1000) { + return value / 1000 + "K"; + } else { + return value.toString(); + } + }; +</script> +<script> + var queryEl = document.getElementById("resistor-query"), + resultEl = document.getElementById("resistor-result"); + + function showStripes(colors, prefix) { + for (var i = 0; i < colors.length; i++) { + var color = colors[i]; + var stripeEl = document.getElementById( + "resistor-stripe-" + prefix + "-" + (i + 1), + ); + stripeEl.style.backgroundColor = color.hex; + stripeEl.style.color = color.label; + var text = color.name + "<br>"; + + if (i == colors.length - 1) { + if (color.multiplier !== undefined) { + text += "×" + color.multiplier; + } else { + text += " "; + } + } else { + text += color.figure; + } + stripeEl.innerHTML = text; + } + } + + queryEl.onchange = queryEl.onkeyup = function () { + location.hash = encodeURIComponent(queryEl.value); + var result = resistors.query(queryEl.value); + + if (result) { + resultEl.style.display = "block"; + showStripes(result.colors4, "4"); + showStripes(result.colors5, "5"); + document.getElementById("resistor-smt-3").innerHTML = result.smt3; + } else { + resultEl.style.display = "none"; + } + }; + + if (location.hash) { + queryEl.value = decodeURIComponent(location.hash.substring(1)); + queryEl.onchange(); + } + + window.onhashchange = function () { + queryEl.value = decodeURIComponent(location.hash.substring(1)); + queryEl.onchange(); + }; + + queryEl.focus(); +</script> diff --git a/packages/resistors/styles.css b/packages/resistors/styles.css new file mode 100644 index 0000000..e2823e4 --- /dev/null +++ b/packages/resistors/styles.css @@ -0,0 +1,127 @@ +* { + /*font-family: 'firacode';*/ + text-rendering: optimizeLegibility; +} + +body { + margin: 10px 20px; + color: #cccccc; + background: #252525; + background-attachment: fixed !important; +} + +h1 { + font-weight: normal; + font-size: 50px; + margin: -10px 0 10px 0; +} + +h2 { + font-weight: normal; + font-size: 15px; +} + +strong { + font-weight: normal; +} + +footer { + position: absolute; + font-size: 12px; + color: #555; + bottom: 0; + left: 0; + right: 0; + padding: 20px; +} + +a { + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +footer a { + color: #775; +} + +#resistor-query { + font-size: 20px; + border: 1px solid #999; + color: #fff; + background-color: #444; + outline: none; + width: 180px; +} + +.resistor-stripe { + width: 80px; + height: 150px; + display: inline-block; + padding: 4px 4px; +} + +.resistor-smt { + display: inline-block; + padding: 0px 10px; + font-size: 35px; + background-color: #000; + border-left: 15px solid silver; + border-right: 15px solid silver; + border-top: 1px solid silver; + border-bottom: 1px solid silver; + margin-right: 10px; +} + +@media print, screen and (max-width: 520px) { + .resistor-stripe { + width: 55px; + font-size: 80%; + height: 100px; + } + .resistor-smt { + font-size: 25px; + border-left: 12px solid silver; + border-right: 12px solid silver; + } +} + +@media print, screen and (max-width: 410px) { + .resistor-stripe { + width: 35px; + font-size: 55%; + height: 70px; + } + .resistor-smt { + font-size: 18px; + border-left: 8px solid silver; + border-right: 8px solid silver; + } +} + +@media print, screen and (max-width: 300px) { + .resistor-stripe { + width: 25px; + font-size: 50%; + height: 60px; + padding: 2px; + } + .resistor-smt { + font-size: 13px; + border-left: 6px solid silver; + border-right: 6px solid silver; + padding: 2px 4px; + } +} + +@media print, screen and (max-height: 660px) { + .resistor-stripe { + height: 55px; + } + footer { + position: inherit; + padding: 10px 0; + } +} |
