<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/"><channel><title>An Engineer&apos;s Blog</title><description>Tin Nguyen&apos;s - A Development Journal &amp; Personal Blog</description><link>https://tin.ng</link><item><title>Cloudflare DDNS and split-brain DNS with OPNsense and AdGuard Home</title><link>https://tin.ng/blog/2026-09-06--cloudflare-ddns-split-brain-dns-opnsense-adguard</link><guid isPermaLink="true">https://tin.ng/blog/2026-09-06--cloudflare-ddns-split-brain-dns-opnsense-adguard</guid><description>One name for every service, answered differently inside and outside, and working the same from the couch and a hotel Wi-Fi</description><pubDate>Sun, 06 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;Every service in this homelab has one public name, and that name has to work identically from the couch and from a hotel Wi-Fi. This post covers how the names resolve: AdGuard Home as the LAN resolver for ad blocking and caching, OPNsense as the recursive upstream with DDNS updates into Cloudflare, and a split-brain setup where the same endpoint answers with a different IP depending on which side of the firewall the query comes from.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;dns-flow.svg&quot; alt=&quot;DNS flow: LAN clients to AdGuard Home to Unbound to DoT upstream, with DDNS updater and the split-brain answer table&quot;&gt;&lt;/p&gt;
&lt;p&gt;The physical host and guest layout live in &lt;a href=&quot;/blog/2025-08-31--proxmox-9&quot;&gt;the homelab write-up&lt;/a&gt;; this is the DNS layer on top.&lt;/p&gt;
&lt;p&gt;A disclaimer before the details: every hop below has an alternative that is simpler, more modern, or somebody&apos;s favourite. What follows is the shape that survived my ISP changes and my own tinkering.&lt;/p&gt;
&lt;h2&gt;The resolver chain&lt;/h2&gt;
&lt;p&gt;Three resolvers, one job each:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;LAN clients
  -&gt; AdGuard Home       :53     filtering, caching, the only resolver clients see
  -&gt; Unbound (OPNsense) :53     DNSSEC, recursion, DoT upstream
  -&gt; dnsmasq (OPNsense) :53053  authoritative for home.arpa, DHCP leases
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;AdGuard Home runs in an LXC on the core range, and its &lt;strong&gt;sole upstream&lt;/strong&gt; is the OPNsense LAN IP. That ordering is deliberate: filtering cannot be bypassed by pointing a client at a well-known public resolver, because there is no direct path allowed through the firewall for DNS. The Proxmox host itself also resolves through AdGuard (&lt;code&gt;search home.arpa&lt;/code&gt;, &lt;code&gt;nameserver&lt;/code&gt; pointing at AdGuard), so containers and the host get the same filtered answers. Is one strict chain over-engineered for a household? Probably. But DNS is also the first thing to break when everything else looks fine, and walking one known chain beats debugging whatever the router happens to ship with.&lt;/p&gt;
&lt;h2&gt;AdGuard Home: filter and cache&lt;/h2&gt;
&lt;p&gt;The blocking side is four filter lists, query log kept local. The interesting parts are the cache settings, which turned the resolver into a noticeable speed-up for repeated lookups:&lt;/p&gt;
&lt;p&gt;| Setting | Value | Effect |
|---|---|---|
| &lt;code&gt;cache_size&lt;/code&gt; | 10 MB | plenty for a household |
| &lt;code&gt;cache_ttl_min&lt;/code&gt; | 60 s | short-lived records stop hammering upstream |
| &lt;code&gt;cache_ttl_max&lt;/code&gt; | 86400 s | long records stay cached for a day |
| &lt;code&gt;cache_optimistic&lt;/code&gt; | true | expired entries are served stale, then refreshed in the background |
| &lt;code&gt;aaaa_disabled&lt;/code&gt; | true | the LAN is IPv4-only, so AAAA queries get instant empty answers |&lt;/p&gt;
&lt;p&gt;&lt;code&gt;cache_optimistic&lt;/code&gt; matters most when the upstream is slow or down: clients keep getting last-known answers instead of timeouts. None of these numbers are gospel; I tuned them against the query log until repeat lookups felt instant, and a bigger household would outgrow them.&lt;/p&gt;
&lt;p&gt;Bootstrap resolution happens over encrypted DNS (9.9.9.10) so the resolver does not bootstrap itself in plaintext. One rewrite maps &lt;code&gt;agh.home.arpa&lt;/code&gt; to the AdGuard container itself, so its admin UI has a stable local name.&lt;/p&gt;
&lt;h2&gt;DDNS with Cloudflare&lt;/h2&gt;
&lt;p&gt;The ISP cycles the WAN IP, and a handful of services need a direct path that bypasses the Cloudflare Tunnel, raw throughput being the main one. The DDNS updater is not a container: it is the DynDNS plugin on OPNsense itself, using the &lt;code&gt;ddclient&lt;/code&gt; backend in Cloudflare token mode.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;service:   Cloudflare API, zone-scoped token
zone:      the public domain
hostnames: 10 A records, including the firewall&apos;s own pve-ddns name
checkip:   web (icanhazip), 10 s timeout
ttl:       300
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running it on the firewall means no extra container, and the check source (&lt;code&gt;icanhazip&lt;/code&gt;) always sees the WAN IP from the box that owns the WAN interface. TTL is kept at 300 s so a failover or ISP change propagates fast. A dedicated updater container would do the same job; I preferred one less thing to restart, and the plugin has been correct across every address change so far. One cron job also renews the WireGuard resolvers nightly to clear stale DNS state on the tunnels.&lt;/p&gt;
&lt;h2&gt;Split-brain: one name, two answers&lt;/h2&gt;
&lt;p&gt;This is the part that makes bookmarks portable. Every service has one name that works anywhere, but the answer depends on where the query comes from:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;inside:   jellyfin.&amp;#x3C;domain&gt;  -&gt;  Caddy, TLS terminates locally
outside:  jellyfin.&amp;#x3C;domain&gt;  -&gt;  Cloudflare edge -&gt; tunnel or DDNS A record
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Internally this is done with Unbound host overrides. A single host record points &lt;code&gt;caddy.home.arpa&lt;/code&gt; at the reverse proxy, and 40 aliases bind the public subdomains (&lt;code&gt;pve&lt;/code&gt;, &lt;code&gt;jellyfin&lt;/code&gt;, &lt;code&gt;files&lt;/code&gt;, &lt;code&gt;hass&lt;/code&gt;, &lt;code&gt;scrypted&lt;/code&gt;, and the rest) onto that same record. So a LAN client asking for a public name gets the reverse proxy directly, on the LAN, without hairpinning through the ISP path, and Caddy still serves the certificate for the exact name requested.&lt;/p&gt;
&lt;p&gt;Externally the same names live as Cloudflare records. Most are served through the &lt;code&gt;cloudflared&lt;/code&gt; tunnel (no open ports at all), while the ten DDNS hostnames are plain A records on the WAN IP for the services that need raw throughput. Authentication for internal users goes through &lt;code&gt;authelia.&amp;#x3C;domain&gt;&lt;/code&gt;, a CNAME into Cloudflare Access, so the same SSO flow protects both the tunneled and the direct paths.&lt;/p&gt;
&lt;p&gt;The internal-only zone stays fully authoritative on dnsmasq at port 53053, with a hundred-plus static host entries (one per service and device, each with a description). Unbound forwards &lt;code&gt;home.arpa&lt;/code&gt; queries to it over DoT on loopback, so even internal resolution is encrypted hop-by-hop, and the &lt;code&gt;private-domain&lt;/code&gt; option stops rebinding protection from eating the answers.&lt;/p&gt;
&lt;h2&gt;Other DNS tricks on the box&lt;/h2&gt;
&lt;p&gt;A few smaller setups worth noting, all verifiable in the configs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Per-client DNS via DHCP tags.&lt;/strong&gt; The dnsmasq DHCP server groups options into tags: the default range hands out AdGuard, while hosts that route through a VPN tunnel get that tunnel provider&apos;s own resolver instead. A policy-routed host resolving through the wrong resolver is a leak; the tag system closes it at assignment time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The firewall knows its own IP.&lt;/strong&gt; A &lt;code&gt;WAN_IP&lt;/code&gt; alias is a URL table pointing at &lt;code&gt;https://127.0.0.1/dynamic_ip.txt&lt;/code&gt;, a tiny endpoint the firewall serves itself with the current WAN address, refreshed every six minutes. Firewall rules can then reference &quot;my own WAN IP&quot; symbolically instead of a literal address that changes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Recursive hardening on Unbound.&lt;/strong&gt; Aggressive NSEC, prefetch, serve-expired with TTL reset, &lt;code&gt;hide-identity&lt;/code&gt;/&lt;code&gt;hide-version&lt;/code&gt;, and 50 MB/100 MB message and RRSet caches with a 24 h max TTL. Standard list, but each one measurably cuts upstream load.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Everything resolves through AdGuard.&lt;/strong&gt; Host, containers, VMs, the &lt;code&gt;search home.arpa&lt;/code&gt; domain. One query log shows every name the household looked up, which is half of debugging.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Closing&lt;/h2&gt;
&lt;p&gt;The chain looks long on paper, but each hop exists to remove a failure mode: AdGuard so ads die before they load, Unbound so upstream queries are encrypted and validated, dnsmasq so internal names never leave the house, the split-brain aliases so one name works from anywhere, and DDNS so the direct path survives an IP change. Total moving parts a reboot can break: two resolvers and one updater, all running on machines that boot in under a minute and start in the right order. Would I build it this way again? Mostly, yes. There are fewer hops in some guide out there, but each of these has already paid for itself in something that stopped breaking.&lt;/p&gt;</content:encoded><h:img src="/_astro/dns-hero.CLbvAJDH.svg"/><enclosure url="/_astro/dns-hero.CLbvAJDH.svg"/></item><item><title>Reproducible event photo sharing with Immich and Terraform</title><link>https://tin.ng/blog/2026-01-21--immich-event-sharing-with-terraform</link><guid isPermaLink="true">https://tin.ng/blog/2026-01-21--immich-event-sharing-with-terraform</guid><description>A disposable public photo album for one event on Oracle Cloud, seeded from the home instance and gone after the last upload</description><pubDate>Wed, 21 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;The home Immich instance (the one in &lt;a href=&quot;/blog/2025-08-31--proxmox-9&quot;&gt;the homelab write-up&lt;/a&gt;) is for me: private library, private people, private everything. When a community event needs a shared photo collection, that instance is exactly the wrong thing to expose. So instead, for the VietPEI Tet Holiday 2026 event, the guests got their own Immich at &lt;code&gt;example.com/s/sharing&lt;/code&gt; (&quot;Happy Tet 2026!&quot;, 156 shared photos and videos), provisioned by a single &lt;code&gt;terraform apply&lt;/code&gt; and seeded from a locally curated copy.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./guest-flow.svg&quot; alt=&quot;The guest story in four steps: photos at the party, one link in the group chat, everyone adds theirs, photos go home&quot;&gt;&lt;/p&gt;
&lt;p&gt;The Terraform project behind it is written to be re-pointed at the next event: change the domain and the share link in &lt;code&gt;terraform.tfvars&lt;/code&gt;, apply, done.&lt;/p&gt;
&lt;h2&gt;One seed, three artifacts&lt;/h2&gt;
&lt;p&gt;What makes this reproducible is that an Immich share link is just a row in Postgres. Curating the event album locally means users, albums, the public share link, and all the metadata live in the database, and a database dump carries them to a clone. &lt;code&gt;export-seed.sh&lt;/code&gt; builds three artifacts from the local instance:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# full dump: users, albums, the share link
docker exec -e PGPASSWORD=$DB_PASSWORD -t immich_postgres \
    pg_dumpall --clean --if-exists -U $DB_USERNAME | gzip &gt; seed.sql.gz

# library skeleton: folder structure and .immich markers, media excluded
tar --exclude=&apos;**/*.jpg&apos; ... -czf library_skeleton.tar.gz library

# logos and profile pictures
tar -czf assets.tar.gz assets
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The skeleton is the subtle piece: Immich tracks imported files with hidden &lt;code&gt;.immich&lt;/code&gt; marker files next to the media. Shipping the folder structure with markers but without the heavy media lets the clone know what &lt;em&gt;should&lt;/em&gt; exist without carrying gigabytes. The assets bundle covers what guests see before uploading: the site logo and profile pictures.&lt;/p&gt;
&lt;h2&gt;Terraform: the box&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;./event-flow.svg&quot; alt=&quot;Technical flow: the home instance, three seed artifacts, terraform apply, the OCI box and the teardown&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;terraform apply&lt;/code&gt; stands up a self-contained stack on Oracle Cloud:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a VCN with an internet gateway and a security list opening only 22, 80, 443&lt;/li&gt;
&lt;li&gt;an &lt;strong&gt;Ampere A1&lt;/strong&gt; instance (4 OCPUs, 24 GB RAM, ARM), Ubuntu, 200 GB boot volume for photos&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;reserved public IP&lt;/strong&gt;, so the DNS A record never has to chase an ephemeral address&lt;/li&gt;
&lt;li&gt;cloud-init that installs Docker, writes the compose file and Caddyfile, and opens the host iptables for 80/443&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The shape is deliberately boring. ARM keeps it inside the always-free tier, and 4 OCPUs handle upload transcoding for a room full of guests without tuning.&lt;/p&gt;
&lt;p&gt;The provisioning step then SSHes in as the IP-attaching finishes and runs the restore: extract skeleton and assets, &lt;code&gt;docker compose up database redis&lt;/code&gt;, wait for Postgres, then&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;zcat $SEED_FILE | sudo docker exec -i immich_postgres psql -U postgres -d immich
sudo docker compose up -d
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Re-running with a new event is: curate the new album at home, re-run &lt;code&gt;export-seed.sh&lt;/code&gt;, change two tfvars (&lt;code&gt;domain_name&lt;/code&gt;, &lt;code&gt;share_link_id&lt;/code&gt;), apply.&lt;/p&gt;
&lt;h2&gt;The stack at the edge&lt;/h2&gt;
&lt;p&gt;Four containers, no orchestration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;immich-server&lt;/code&gt; (&lt;code&gt;:release&lt;/code&gt;) with the library on the boot volume&lt;/li&gt;
&lt;li&gt;Postgres 14 with the vector extensions, &lt;strong&gt;pinned by image digest&lt;/strong&gt;, because a half-restored ML database is not a debugging experiment you want mid-event&lt;/li&gt;
&lt;li&gt;Redis for jobs and sessions&lt;/li&gt;
&lt;li&gt;Caddy in front with ACME certificates, gzip, and two small jobs: serving &lt;code&gt;/logo.png&lt;/code&gt; from the assets bundle, and the one-line redirect that makes the bare domain open the album&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;#x3C;event-domain&gt; {
  handle /logo.png { root * /srv; file_server }
  encode gzip
  redir / &amp;#x3C;share_link_id&gt; temporary
  reverse_proxy immich-server:2283
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That redirect is why the whole event runs on one short address, &lt;code&gt;example.com/s/sharing&lt;/code&gt;: guests type the short domain and land on the album, no accounts, no instructions. Immich&apos;s server-side rendering even fills the social preview card (&quot;156 shared photos &amp;#x26; videos&quot;), so the link looks right when it is pasted into a group chat.&lt;/p&gt;
&lt;p&gt;The site itself, captured live:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fshare-page-live.D7YPj_DN.png&amp;#x26;w=588&amp;#x26;h=570&amp;#x26;f=webp&quot; alt=&quot;The share page grid: every photo guests and hosts added, on one page&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fshare-page-setup-day.CC_vj6h8.png&amp;#x26;w=1788&amp;#x26;h=577&amp;#x26;f=webp&quot; alt=&quot;The Fri, Feb 6 setup-day group on the same page&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Why not just share the home instance?&lt;/h2&gt;
&lt;p&gt;Three reasons, all learned the hard way:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Privacy&lt;/strong&gt;: the home library holds everything personal; an event instance holds exactly one curated album.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Blast radius&lt;/strong&gt;: a public URL will get crawled, probed and rate-limited; let it hit a disposable box whose &lt;code&gt;terraform destroy&lt;/code&gt; erases VCN, instance, and reserved IP in one command.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bandwidth&lt;/strong&gt;: event week uploads should not share a home uplink with the household&apos;s Jellyfin.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Security and convenience pull against each other hardest here. The room
is mostly parents and children, without the same technological comfort as
the person running the server: most guests will not create an account or
install an app just to see photos, and the existing options, commercial
ones included, nearly all want exactly that. The invitation QR code was
the balance that held: the URL is never advertised online, so the only
people who learn it are the ones scanning the code at the venue. Guests
still forward the link to family afterward, of course, but a link
spreading person to person from the room is a different exposure than one
posted publicly.&lt;/p&gt;
&lt;p&gt;The personal instance stays on the Proxmox host, untouched. The event
instance stays up for a while after the party, then the album is
re-created as a shared album in VietPEI&apos;s private Facebook group, where
the association already operates and where both archival and sharing are
easier than keeping a server alive. Only then does the instance disappear
without a trace, and the next event starts from the same three artifacts.&lt;/p&gt;
&lt;h2&gt;Closing&lt;/h2&gt;
&lt;p&gt;Total setup for the next event: curate the album locally, run &lt;code&gt;./export-seed.sh&lt;/code&gt;, fill in &lt;code&gt;domain_name&lt;/code&gt; and &lt;code&gt;share_link_id&lt;/code&gt;, &lt;code&gt;terraform apply&lt;/code&gt;, point the DNS record at the output IP. The whole path from &quot;we should collect photos for this&quot; to a public share link is an afternoon, most of it upload time.&lt;/p&gt;</content:encoded><h:img src="/_astro/event-hero.BxuKnZJp.svg"/><enclosure url="/_astro/event-hero.BxuKnZJp.svg"/></item><item><title>Tuning Proxmox Backup Server for space and disk writes</title><link>https://tin.ng/blog/2025-11-03--proxmox-backup-server-tuning</link><guid isPermaLink="true">https://tin.ng/blog/2025-11-03--proxmox-backup-server-tuning</guid><description>Deduplication and prune rules squeeze 2.24 TiB of nightly guest backups into 164 GiB, while cache exclusions and journal caps spare the disk</description><pubDate>Mon, 03 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;/blog/2025-08-31--proxmox-9&quot;&gt;single-box homelab&lt;/a&gt; backs up every guest, every night, into Proxmox Backup Server. The datastore sits on the &lt;code&gt;utank&lt;/code&gt; ZFS pool, and the garbage collection run that closed out the tuning reports where all of that lands:&lt;/p&gt;
&lt;p&gt;| Metric | Value |
|---|---|
| Original data indexed | 2.24 TiB |
| Chunks on disk | 164.25 GiB (7.15% of the original) |
| Deduplication factor | 13.99 |
| Chunk count | 86,243 |&lt;/p&gt;
&lt;p&gt;This post is the tuning behind those numbers. The trigger was growth: media and photo libraries that only gain weight, guests that cache and download on the side, and nightly backups that scale with all of it, all fighting for the same 14 TB Exos. One spinning disk wears with every write, and whatever the backups keep has to be stored and pushed offsite too, so this round treats the write budget, the space budget, and the offsite budget as one. What follows: what deduplication was already doing on its own, what prune schedules and garbage collection actually reclaim, which inputs I stopped backing up, and two write-wasters I removed from the backup container itself.&lt;/p&gt;
&lt;h2&gt;What deduplication does for free&lt;/h2&gt;
&lt;p&gt;PBS does not store archive files. Every backup is split into content-addressed chunks, named by their SHA-256 digest and stored once in the chunk store; a snapshot is just an index of digests. At the end of the tuning, 86,243 chunks totalling 164.25 GiB served 420 indexes referencing 2.24 TiB of data.&lt;/p&gt;
&lt;p&gt;The factor is almost 14 because the fleet is thirty-odd LXC guests cloned from a handful of Debian, Ubuntu and Alpine bases. Identical system files produce identical digests across guests and across nights, so each night&apos;s snapshot mostly writes a fresh index plus a thin layer of changed chunks. Nothing had to be configured for this; it falls out of running many similar guests.&lt;/p&gt;
&lt;h2&gt;Prune, then collect the garbage&lt;/h2&gt;
&lt;p&gt;Deleting a snapshot only deletes its index. The chunks stay, because other snapshots still reference them, so an untuned datastore grows forever. The nightly pipeline runs in three steps, in this order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the backup job dumps every guest into PBS (the schedule lives in the homelab post)&lt;/li&gt;
&lt;li&gt;a prune job applies &lt;code&gt;keep-last 2&lt;/code&gt;, &lt;code&gt;keep-daily 3&lt;/code&gt;, &lt;code&gt;keep-weekly 1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;garbage collection sweeps the chunk store&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Garbage collection walks the chunk store, marks every chunk no index points at, and removes the orphans. The subtle part is the 24-hour grace rule: freshly unreferenced chunks are reported as pending removals and survive until the next day&apos;s run. If a backup is uploading while an overlapping snapshot is pruned, both can legitimately reference the same chunks, and the grace period guarantees the running backup can never have a chunk pulled out from under it. The first cycle after the retune left 47.98 GiB in 20,198 chunks pending; a follow-up run removed 2.55 GB in 1,240 chunks, found zero bad chunks, and finished in 255 seconds.&lt;/p&gt;
&lt;p&gt;The before and after of tuning the prune job: 315 snapshots (288 CT, 27 VM) holding 262 GB of chunks, down to 210 snapshots (192 CT, 18 VM) over 35 guests in 164.25 GiB. Restore depth is shallower, which is what I wanted: every kept snapshot is chunks the disk writes, holds, and later cleans up, and payload the offsite leg carries too. Two hot copies, three dailies and a weekly is the window I actually restore from, so the retention stops there.&lt;/p&gt;
&lt;h2&gt;Shrinking the input with .pxarexclude&lt;/h2&gt;
&lt;p&gt;Deduplication keeps one copy of everything, but a copy is still a copy. PBS walks the entire root disk of each LXC, so hidden caches and downloaded files end up in the chunk store no matter how similar they are. I swept the guests with &lt;code&gt;ncdu -x /&lt;/code&gt; (the &lt;code&gt;-x&lt;/code&gt; stays on the root filesystem and ignores bind mounts) and the same things showed up on nearly every guest:&lt;/p&gt;
&lt;p&gt;| Guest | Found |
|---|---|
| a media downloader | 29 GB of downloaded files |
| a download manager | 6 GB of uv package cache |
| the photo library | 6 GB of node_modules and pnpm stores |&lt;/p&gt;
&lt;p&gt;Each rule is one line in &lt;code&gt;/.pxarexclude&lt;/code&gt; at the container root:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;echo &quot;/home/downloader/.cache/uv/*&quot; &gt;&gt; /.pxarexclude
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The files stay live on ZFS; the backup walk just steps over them, so nothing breaks and the chunk store stops growing to track downloads and package caches. The real application state lives in bind mounts under &lt;code&gt;/data&lt;/code&gt; with its own restic coverage via Backrest, which is why skipping these paths costs nothing. One deliberate exception: the git server has no exclude file. Its 45 GB of repositories and SQLite databases is exactly the kind of data the backups exist for.&lt;/p&gt;
&lt;h2&gt;Two write-wasters inside the backup container&lt;/h2&gt;
&lt;p&gt;Space is half the story. The PBS container&apos;s root disk lives on the NVMe ZFS mirror, and ZFS is copy-on-write, so every redundant journal line is amplified into metadata writes on the pool that also hosts every guest root disk.&lt;/p&gt;
&lt;p&gt;The first waster was unbounded &lt;code&gt;systemd-journald&lt;/code&gt;. The PBS container alone had generated 1.0 GB of journal, and a network scanner was past 200 MB, fed by constant ARP broadcast chatter. The fix is a vacuum now and a cap forever, deployed by a host-level loop over every running LXC that actually runs systemd, skipping the OpenRC Alpine guests:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;journalctl --vacuum-size=50M
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[Journal]
SystemMaxUse=50M
MaxRetentionSec=1week
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After the sweep the PBS container&apos;s journal sat at 36.9 MB, and the cap holds it there.&lt;/p&gt;
&lt;p&gt;The second was quieter and nastier. &lt;code&gt;zfs-zed.service&lt;/code&gt; ships enabled with the PBS install, but the ZFS Event Daemon cannot reach block devices from an unprivileged LXC, so it crash-looped forever. The restart counter was at 128,238 when I found it, and every restart wrote more log lines to the same pool. The Proxmox host handles ZFS events natively; the copy inside the container is pure noise:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;systemctl disable --now zfs-zed
systemctl mask zfs-zed
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Checking the disk is still healthy&lt;/h2&gt;
&lt;p&gt;Everything above lands on one physical disk, so it gets checked. SMART on the Exos after 21,893 power-on hours:&lt;/p&gt;
&lt;p&gt;| Attribute | Value |
|---|---|
| Overall health | PASSED |
| Reallocated / pending / uncorrectable sectors | 0 / 0 / 0 |
| Temperature | 33 C |
| Data on the BKP dataset | 243 GB |&lt;/p&gt;
&lt;p&gt;Both pools get monthly scrubs, and the last garbage collection run reported zero bad chunks. The honest limitation from the homelab post stands: the backups share the box with the data they protect. The offsite leg runs on restic as the policy layer and rclone as the backend, two separate providers, everything encrypted before it leaves the box; that wiring is its own post.&lt;/p&gt;
&lt;h2&gt;Closing&lt;/h2&gt;
&lt;p&gt;Deduplication carries most of the load once the guests share base images. The wins that needed actual work were the prune schedule cutting 315 snapshots to 210, exclusions that stop caches and downloads from entering the chunk store, and write hygiene in the containers themselves, a journald cap and a masked &lt;code&gt;zed&lt;/code&gt; whose crash loop had written a small novel into the journal. The result is a backup system protecting 2.24 TiB of guests in 164.25 GiB of disk, with writes only where they earn their place.&lt;/p&gt;</content:encoded><h:img src="/_astro/pbs-dedup-hero.BaKRk2Bd.svg"/><enclosure url="/_astro/pbs-dedup-hero.BaKRk2Bd.svg"/></item><item><title>Family document intake with paperless-ngx</title><link>https://tin.ng/blog/2025-09-19--paperless-ngx-family-document-intake</link><guid isPermaLink="true">https://tin.ng/blog/2025-09-19--paperless-ngx-family-document-intake</guid><description>One shared inbox and a one-button iPhone scanner feed the household archive, where per-sender rules assign ownership and full-text search does the rest</description><pubDate>Fri, 19 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;Every household ends up with the same pile: bills, tax documents, insurance letters, school forms, medical reports, and the receipts that feed expenditure tracking. The &lt;a href=&quot;/blog/2025-08-31--proxmox-9&quot;&gt;single-box homelab&lt;/a&gt; already runs paperless-ngx as its document engine, OCR on everything, correspondents, tags and full-text search. What it did not have was a way for the family to get paper into it. I set this up for my wife and my siblings, who are young enough to be tech savvy but not yet old enough to keep documents of their own; four people, one pile.&lt;/p&gt;
&lt;p&gt;The answer was to stop teaching anyone a new tool. Paperless-ngx sits in the middle and accepts documents through two paths that everyone already knows: a camera, and email. One family member scans straight from an iPhone, everyone else sends mail to a single address, and per-sender rules inside paperless-ngx sort out who owns what.&lt;/p&gt;
&lt;h2&gt;The engine&lt;/h2&gt;
&lt;p&gt;Paperless-ngx runs as its own unprivileged LXC in the homelab stack, in the same tier as the password manager and the git server. It stores nothing clever: a consumption directory, a database, and OCR over everything that enters. Documents get matched into correspondents, tags and document types, and from that point the family searches instead of digging through drawers.&lt;/p&gt;
&lt;p&gt;The web interface is the only public-facing surface this service has, and it goes through the same chain every other public service uses: Cloudflare Tunnel into Caddy for TLS, Authelia in front for single sign-on with two-factor. No inbound ports are opened for it. Family members sign in the same way they sign in to everything else on the box.&lt;/p&gt;
&lt;h2&gt;The scanner already in every pocket&lt;/h2&gt;
&lt;p&gt;For the family members who are least comfortable with technology, the intake tool is &lt;a href=&quot;https://apps.apple.com/us/app/quickscan-pdf-ocr-text-scanner/id1513790291&quot;&gt;QuickScan&lt;/a&gt;, a free iOS scanner app with no ads and no data collection. It matters less which scanner app it is and more what it does well:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;it uses the familiar iOS camera scan flow, with automatic document detection and angle correction, so the phone does the framing&lt;/li&gt;
&lt;li&gt;OCR runs entirely on the device and produces a searchable PDF before anything leaves the phone&lt;/li&gt;
&lt;li&gt;paperless-ngx is a first-class export destination, so the pipeline has no WebDAV workaround in the middle&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So the workflow is: point the camera at the letter, confirm the page borders, tap export. The app talks directly to the paperless-ngx server through its public hostname and authenticates with its own API token, per phone. There is no account to create, no folder to browse, no concept of a server at all, just a camera and a single button that means &quot;file this&quot;.&lt;/p&gt;
&lt;h2&gt;One inbox for everyone&lt;/h2&gt;
&lt;p&gt;The second path meets everyone else where they already are: their mail app. The family shares an iCloud+ subscription, and its custom domain support means the household has real addresses on its own domain. One of them is a dedicated intake address: every family member, on any device, forwards or composes mail to that single address and attaches the document. No subject, no message, nothing typed; the per-sender rules key on the address, not on the words, and the household is lazy enough that this was a hard requirement. Scan it with the built-in mail app camera, forward the school newsletter, photograph the lab result; the sender does not need to think about it at all.&lt;/p&gt;
&lt;p&gt;Inside paperless-ngx, a mail account is configured against that inbox over IMAP. Apple requires an app-specific password for third-party IMAP access, so the integration gets its own credential and nothing else in the Apple ID is exposed. Paperless polls the mailbox on its default schedule of every ten minutes (&lt;code&gt;PAPERLESS_EMAIL_TASK_CRON&lt;/code&gt; if it ever needs tuning), checks each mail against the rules, and consumes the attachments it finds.&lt;/p&gt;
&lt;p&gt;For recurring senders, even the forwarding step is automated away. iCloud Mail runs server-side rules that move known senders straight into the family mailboxes as their mail arrives, which is how the electricity bill files itself without anyone touching a phone. Each rule is named for what it feeds:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Ficloud_rules_single_endpoint.BtNqWXjC.png&amp;#x26;w=1090&amp;#x26;h=552&amp;#x26;f=webp&quot; alt=&quot;iCloud Mail server-side rules moving recurring senders into the family mailboxes&quot;&gt;
&lt;em&gt;Server-side iCloud rules route each recurring sender to the right family mailbox; nobody forwards anything.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;The from address becomes ownership&lt;/h2&gt;
&lt;p&gt;The interesting design decision is what happens after the mail arrives. Nobody is asked to classify anything, so the classification comes from metadata the family cannot get wrong: who sent the mail.&lt;/p&gt;
&lt;p&gt;Each family member has a mail rule keyed on their from address. When mail matching that sender arrives, the attachment is consumed and the rule assigns the document its correspondent and the ownership that goes with it. From there, paperless-ngx&apos;s own matching takes over and fills in tags and document types by content, so a document lands classified, owned and searchable without a single manual step.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fbill_maritime_electric.BruL5dSX.png&amp;#x26;w=163&amp;#x26;h=392&amp;#x26;f=webp&quot; alt=&quot;A Maritime Electric electricity bill in paperless-ngx with correspondent, document type and owner already assigned&quot;&gt;
&lt;em&gt;A consumed electricity bill: correspondent, document type and owner assigned before anyone opened the app.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Paperless-ngx tracks processed messages by IMAP UID, so the same mail is never consumed twice, and every processed message is reviewable under Mail &gt; Processed Mails, which doubles as an audit trail of what arrived and when. Rules can also clean up after themselves, deleting or flagging consumed mail so it does not pile up in the intake inbox.&lt;/p&gt;
&lt;h2&gt;Security posture&lt;/h2&gt;
&lt;p&gt;Nothing about this pipeline requires trusting a third party with the documents. The scan path is device OCR into a token-authenticated API behind Caddy and Authelia. The mail path is paperless-ngx pulling from iCloud over IMAP, an outbound connection, with no listening service anywhere. Inside the perimeter, the usual homelab rules apply: unprivileged container, state on the ZFS pool, everything swept into the nightly backup jobs from the homelab post, and protected by the tuned &lt;a href=&quot;/blog/2025-11-03--proxmox-backup-server-tuning&quot;&gt;Proxmox Backup Server&lt;/a&gt; datastore.&lt;/p&gt;
&lt;p&gt;This is the same balance Vaultwarden had to strike as the family password manager: secure enough to hold the household&apos;s sensitive documents, and friendly enough that nobody gives up at the login screen and leaves the important letters in a drawer. Everything in the perimeter above stays invisible to the family; what they touch is a camera button, an email address, and a sign-in they already use for everything else on the box.&lt;/p&gt;
&lt;h2&gt;The app&apos;s OIDC login&lt;/h2&gt;
&lt;p&gt;Per-phone tokens still need a first login, and that login was the one spot where the phone refused to behave like the browser. The app signs in through Authelia&apos;s OpenID Connect flow, the same provider the web UI uses, but a browser flow can keep a client secret on the server while a phone cannot, and the first token exchange failed with &lt;code&gt;invalid_client&lt;/code&gt; because the client registration did not allow the &lt;code&gt;&apos;none&apos;&lt;/code&gt; token endpoint auth method the app was sending:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Foidc_pkce_auth.uB3u7rVS.png&amp;#x26;w=360&amp;#x26;h=734&amp;#x26;f=webp&quot; alt=&quot;QuickScan in OpenID Connect mode failing its token exchange against Authelia&quot;&gt;
&lt;em&gt;The app&apos;s OpenID Connect mode, mid-debugging: the token exchange rejected because the client registration did not allow the &apos;none&apos; auth method.&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;client_name: &apos;Paperless-ngx&apos;
public: true                               # 1. MUST be true for mobile apps
authorization_policy: &apos;two_factor&apos;
require_pkce: true                         # 2. MUST be true (keeps the flow secure without a secret)
redirect_uris:
  - &apos;x-paperless://oidc-callback&apos;          # 3. The mobile app callback
token_endpoint_auth_method: &apos;none&apos;         # 4. MUST be &apos;none&apos; (Expects no client secret)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Closing&lt;/h2&gt;
&lt;p&gt;QuickScan turns a phone into a one-button scanner for the people who need exactly that, and a single shared inbox on the family domain catches everything else from everyone else. The per-sender rules do the sorting that nobody wanted to do manually, and the whole household ends up in one OCR-indexed library without having learned anything new. That was the requirement, and it holds.&lt;/p&gt;
&lt;p&gt;The loop closes at the printer, of all places. A Canon with AirPrint sits on the home network, so anyone at home can send a document to print, wirelessly, from any device. Away from home, AirPrint cannot reach across the internet, so the move is the tag: mark a document &lt;code&gt;to-print&lt;/code&gt; in the archive and the automation takes over, leaving the page in the tray for whenever we walk back in.&lt;/p&gt;</content:encoded><h:img src="/_astro/intake-hero.8N2XJLdl.svg"/><enclosure url="/_astro/intake-hero.8N2XJLdl.svg"/></item><item><title>A single-box homelab on Proxmox VE 9</title><link>https://tin.ng/blog/2025-08-31--proxmox-9</link><guid isPermaLink="true">https://tin.ng/blog/2025-08-31--proxmox-9</guid><description>Design notes behind 48 isolated containers, split DNS, and per-service VPN routing on one machine the family never has to think about</description><pubDate>Sun, 31 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;The whole homelab runs on one desktop-class box under the stairs: Proxmox VE 9, an OPNsense VM as the network edge, and one unprivileged LXC container per service. It exists for my family first: photos, documents, passwords, media, all of it on this one box, and nobody else in the house ever has to think about it.&lt;/p&gt;
&lt;p&gt;The usage numbers still surprise me, though. The most-used service is not the media stack; it is Vaultwarden, the family password manager. My wife approved of it, and it saw the biggest uptake of anything I have ever deployed: the household&apos;s logins now live in a vault on our own hardware, backed up with everything else. That one service did more for the family&apos;s digital life than every other service on the box combined.&lt;/p&gt;
&lt;p&gt;The design goal was never &quot;datacenter at home&quot;; it was &quot;one machine I can rebuild from notes, where every service is isolated, disposable and backed up&quot;.&lt;/p&gt;
&lt;p&gt;Two conventions carry most of the design:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;One box, one service.&lt;/strong&gt; Each app gets its own container with its own IP, config mount and backup. An upgrade of one service can never break its neighbour.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Everything is a mount point.&lt;/strong&gt; Container root disks stay tiny and disposable on the NVMe pool; the actual data lives in host directories bind-mounted into the guest, so a container can be destroyed and recreated in minutes with zero data loss.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;homelab-architecture-v4.svg&quot; alt=&quot;Homelab architecture: internet, OPNsense edge VM, container groups on the Proxmox host, and the ZFS pools underneath&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Hardware&lt;/h2&gt;
&lt;p&gt;| Part | Choice |
|---|---|
| Board | Gigabyte B760M Gaming Plus WiFi (DDR4) |
| CPU | Intel i5-13400, 10 cores / 16 threads |
| RAM | 64 GB DDR4-2667 |
| Boot / VM disk | 2 x 960 GB Micron 7450 NVMe (ZFS mirror) |
| Bulk disk | 14 TB Seagate Exos (ZFS, single disk) |
| NICs | Intel I226-V 2.5 GbE PCIe card (LAN), Realtek RTL8111 1 GbE (WAN) |
| iGPU | Intel UHD Graphics 730 |&lt;/p&gt;
&lt;p&gt;The 13400 with its UHD 730 handles Jellyfin transcoding and Scrypted camera streams through &lt;code&gt;/dev/dri&lt;/code&gt; passthrough; no discrete GPU needed. The two NICs split the network roles: the Intel I226-V PCIe card bridges to the LAN at 2.5 GbE, the Realtek port bridges to WAN. How the box went together, part by part with photos: &lt;a href=&quot;/blog/2025-05-20--assembling-the-homelab-box&quot;&gt;Assembling the homelab box&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Storage&lt;/h2&gt;
&lt;p&gt;Two ZFS pools, both created with &lt;code&gt;ashift=12&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;zpool create -o ashift=12 rpool mirror nvme0n1p3 nvme1n1p3
zpool create -o ashift=12 utank /dev/sda1
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rpool&lt;/code&gt; (mirror, ~890 GB) holds the OS, every guest root disk, and an &lt;code&gt;appdata&lt;/code&gt; dataset mounted at &lt;code&gt;/data&lt;/code&gt;. Guest disks are cheap here; they are small and snapshotted.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;utank&lt;/code&gt; (~12.7 TB, single disk) holds the bulk: the media library under &lt;code&gt;HTPC/&lt;/code&gt;, the photo library under &lt;code&gt;LIBRARY/&lt;/code&gt;, and backups under &lt;code&gt;BKP/&lt;/code&gt;. The disk itself is a shucked Seagate Expansion Desktop; what turned out to be inside is its own post: &lt;a href=&quot;/blog/2025-06-28--shucking-seagate-expansion-14tb&quot;&gt;Shucking the Seagate Expansion Desktop 14TB&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Scrubs run monthly. Both pools came back clean last scrub, 0 errors.&lt;/p&gt;
&lt;h2&gt;One service, one container&lt;/h2&gt;
&lt;p&gt;All 48 containers are unprivileged LXC guests provisioned from &lt;a href=&quot;https://community-scripts.github.io/ProxmoxVE/&quot;&gt;community-scripts&lt;/a&gt;. VMIDs are allocated in ranges by purpose, and the ranges double as an address plan (more on that below):&lt;/p&gt;
&lt;p&gt;| VMID range | Purpose | Examples |
|---|---|---|
| 10xx | Network infrastructure | AdGuard Home, Caddy, PBS |
| 2001 | Base template | Debian + zsh + pyenv, cloned for new guests |
| 40xx | Media serving | Jellyfin, Audiobookshelf, Komga |
| 50xx | Self-hosted apps | Vaultwarden, Immich, Paperless-ngx, Gitea |
| 60xx | Miscellaneous | Lubelogger, travel planning, file conversion |
| 8xxx | Home automation | Scrypted NVR, go2rtc, plus the HAOS and OPNsense VMs |&lt;/p&gt;
&lt;p&gt;The address plan falls out of the VMID scheme. The LAN is a private /23 split in two ranges: one for core network devices, one for service guests, and each service guest&apos;s last octet comes from its VMID. The Proxmox &lt;code&gt;tags&lt;/code&gt; field carries that octet so the mapping stays visible in the web UI. All guests get their addresses from DHCP; the reservations live in OPNsense&apos;s dnsmasq with a description per host.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;homelab-services-v2.svg&quot; alt=&quot;A few example services per group&quot;&gt;
&lt;em&gt;One container per service. Each group shows only a few examples; the pattern holds for all 48.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Every service container follows the same shape:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a small root disk (1 to 20 GB) on &lt;code&gt;rpool/data&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;its persistent state bind-mounted from &lt;code&gt;/data/lxc/&amp;#x3C;service&gt;/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;read-only media mounts from &lt;code&gt;utank&lt;/code&gt; where it needs them&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because the state is outside the container, rebuilding a guest is: clone the template, re-add the mount, done. That low cost is what makes the one-service rule livable; rebuilding in minutes means I actually dare to touch these guests. Guests that need hardware get it explicitly: the media and camera guests have &lt;code&gt;/dev/dri&lt;/code&gt; bound in for the iGPU, Scrypted also gets &lt;code&gt;/dev/kfd&lt;/code&gt; for compute, and nothing else sees the host devices.&lt;/p&gt;
&lt;h2&gt;The edge: OPNsense in a VM&lt;/h2&gt;
&lt;p&gt;The router runs as a VM in the 8xxx range above, started first on boot (order 1, 30 s boot delay), with 24 GB of RAM and two virtio NICs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;net0&lt;/code&gt; on &lt;code&gt;vmbr0&lt;/code&gt;, the LAN bridge (2.5 GbE port), a static address in the core range&lt;/li&gt;
&lt;li&gt;&lt;code&gt;net1&lt;/code&gt; on &lt;code&gt;vmbr1&lt;/code&gt;, the WAN bridge (1 GbE port), DHCP from the ISP&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Running the firewall as a guest keeps it in the same backup and snapshot pipeline as everything else. The tradeoff is obvious and accepted: no host, no network, so the Proxmox host itself is managed from the LAN side only.&lt;/p&gt;
&lt;h2&gt;DNS and DHCP&lt;/h2&gt;
&lt;p&gt;The resolver chain is split across three components on purpose:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;clients
  -&gt; AdGuard Home        :53   filtering, per-client stats, home.arpa rewrites
  -&gt; Unbound (OPNsense)  :53   DNSSEC, recursion, DoT upstream to 1.1.1.1
  -&gt; dnsmasq (OPNsense)  :53053  authoritative for home.arpa, DHCP lease names
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;AdGuard Home is the only resolver clients see; its sole upstream is Unbound on the OPNsense LAN IP, so nothing on the LAN can bypass filtering by pointing elsewhere. Unbound does the recursive work with DNSSEC on, encrypts upstream queries with DoT (verified against &lt;code&gt;cloudflare-dns.com&lt;/code&gt;), and forwards &lt;code&gt;home.arpa&lt;/code&gt; lookups to dnsmasq on &lt;code&gt;127.0.0.1:53053&lt;/code&gt;, which knows every DHCP lease and static entry. About a hundred hosts have static entries with descriptions; &lt;code&gt;pve.home.arpa&lt;/code&gt;, &lt;code&gt;hass.home.arpa&lt;/code&gt;, &lt;code&gt;scrypted.home.arpa&lt;/code&gt; and friends resolve without touching public DNS.&lt;/p&gt;
&lt;p&gt;DHCP uses tags to hand different resolvers to different hosts. A tag groups one DHCP option with one range; a static host entry then opts a client into it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;default range: option 6 = AdGuard Home (filtering for everyone)&lt;/li&gt;
&lt;li&gt;a tag per tunnel: option 6 = that tunnel provider&apos;s DNS, so a host routed through a tunnel also resolves through it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The DNS tag matters because of the next section: a host policy-routed through a VPN must resolve through that VPN&apos;s DNS too, or queries leak out the wrong path.&lt;/p&gt;
&lt;h2&gt;Selective routing over WireGuard&lt;/h2&gt;
&lt;p&gt;Three always-on WireGuard tunnels terminate on OPNsense as additional WAN interfaces, each to a commercial VPN provider, each with its own peer, keepalive of 25 s, and routes disabled so nothing uses them by default. Each exists so a defined group of hosts can pin its egress to a single provider.&lt;/p&gt;
&lt;p&gt;Policy routing is one repeated pattern. A firewall alias lists the hosts, a LAN rule sends them to the tunnel gateway unless the destination is local, and a hybrid outbound NAT rule masquerades them on that interface:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;action: Pass, quick
interface: LAN, direction: in
source: &amp;#x3C;tunnel host alias&gt;
destination: !( RFC1918 networks )
gateway: &amp;#x3C;tunnel gateway&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Result: LAN traffic between guests never hairpins through a VPN, everything those aliases talk to the internet for exits through the assigned tunnel, and the DHCP DNS tags from the previous section keep name resolution on the same path. The peer configs are regenerable from each provider&apos;s API with a small script, so rotating servers is a re-run of the script, not a rewire.&lt;/p&gt;
&lt;h2&gt;Remote access&lt;/h2&gt;
&lt;p&gt;No inbound ports are forwarded. Remote access runs through a Cloudflare Tunnel from the &lt;code&gt;cloudflared&lt;/code&gt; container to a reverse proxy chain: Caddy terminates TLS per service, Authelia sits in front for SSO and two-factor auth, and the tunnel carries SSH to the Proxmox host the same way. Services are published as &lt;code&gt;service.home.arpa&lt;/code&gt; internally and a personal domain externally, both names pointing at the same Caddy.&lt;/p&gt;
&lt;h2&gt;Media, photos and documents&lt;/h2&gt;
&lt;p&gt;Media serving is where guests lean on the iGPU: Jellyfin transcodes through &lt;code&gt;/dev/dri&lt;/code&gt;, with its library mounted read-only from &lt;code&gt;utank&lt;/code&gt;. Photos run on Immich with the library on &lt;code&gt;utank&lt;/code&gt;, documents on Paperless-ngx, comics on Komga, audiobooks and podcasts on Audiobookshelf.&lt;/p&gt;
&lt;h2&gt;Home automation and cameras&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Home Assistant&lt;/strong&gt; runs as a HAOS VM (the one case where an appliance image beats a container), with Mosquitto and the Matter server as add-ons.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scrypted&lt;/strong&gt; runs in an LXC with &lt;code&gt;/dev/dri&lt;/code&gt; bound in for camera transcoding, mounted volumes on &lt;code&gt;rpool&lt;/code&gt;, plus optional device mounts (&lt;code&gt;/dev/kfd&lt;/code&gt;, Coral-style &lt;code&gt;apex&lt;/code&gt;) wired up for hardware I have not added yet.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Backups&lt;/h2&gt;
&lt;p&gt;Two layered jobs, all snapshot mode where possible:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Nightly&lt;/strong&gt;: vzdump of every guest except PBS itself, into the PBS container&apos;s datastore on &lt;code&gt;utank&lt;/code&gt;, which also moves the guest state off the SSD pool onto the bulk HDD.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nightly&lt;/strong&gt;: the PBS container backs itself up (&lt;code&gt;keep-last 10&lt;/code&gt;) to a plain directory dataset, so the backup server is itself backed up.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Inside the guests, Backrest (restic) covers the app data in &lt;code&gt;/data&lt;/code&gt; separately from the whole-guest vzdumps, which makes single-file restores cheap, and pushes it to a remote repository, which is the offsite leg of the story.&lt;/p&gt;
&lt;h2&gt;Closing&lt;/h2&gt;
&lt;p&gt;What I would keep: the VMID-to-IP convention, config-outside-the-container mounts, and running the firewall as a guest. What is still open: &lt;code&gt;zpool upgrade&lt;/code&gt; for both pools (feature flags are pending since the Proxmox 9 upgrade) and a Coral TPU for Scrypted. The whole stack fits in one desktop box that idles quiet enough to live under the stairs, which was the actual requirement.&lt;/p&gt;</content:encoded><h:img src="/_astro/homelab-stack.Ct11U7-i.svg"/><enclosure url="/_astro/homelab-stack.Ct11U7-i.svg"/></item><item><title>Shucking the Seagate Expansion Desktop 14TB</title><link>https://tin.ng/blog/2025-06-28--shucking-seagate-expansion-14tb</link><guid isPermaLink="true">https://tin.ng/blog/2025-06-28--shucking-seagate-expansion-14tb</guid><description>The enclosure gives up a dual-actuator Exos MACH.2 instead of a consumer drive, and the price math that still makes externals worth opening</description><pubDate>Sat, 28 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;When I was planning the homelab box, the last big piece was bulk storage, 14 TB of it. The cheapest way I found to get there was to buy a drive sealed inside a USB enclosure and keep the drive. Bare enterprise drives hold their price, but the same drive inside an external goes on sale, and that price gap is why shucking is still a thing. So the plan was simple: buy a Seagate Expansion Desktop 14TB on sale, crack the shell, and put whatever I found inside into the NAS case.&lt;/p&gt;
&lt;p&gt;Shucking ends the warranty, and what you get is a lottery: Seagate ships whatever its production lines put in the bin that week. Mine turned out better than I expected. The drive is now the bulk pool of the homelab box; the build itself is in &lt;a href=&quot;/blog/2025-05-20--assembling-the-homelab-box&quot;&gt;Assembling the homelab box&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;An update, a year on. The line in The hunt, that the sale you are waiting for never comes back, turned out to be the whole story: hard drives are extremely expensive now, exactly the direction the datacenter hoard pointed. SSD sticks went past expensive into rude, though, which makes the other call look better every month: the boot pool runs on two refurbished Micron 7450 PRO sticks, and refurb enterprise NVMe plus one big shucked spinner was the right shape for this box.&lt;/p&gt;
&lt;p&gt;As for the drive itself: it spins up and spins down quickly, no long waits either way. It is louder than I expected, with real seek chatter. Standing next to the case, you hear it. But the box lives about 8 feet from the couch, and from the couch it is just background hum.&lt;/p&gt;
&lt;h2&gt;The hunt&lt;/h2&gt;
&lt;p&gt;The timing was bad and I knew it. Datacenters had started buying up every kind of memory, spinning disks included, and drive prices were only headed up. When that wave lands, the sale you are waiting for never comes back.&lt;/p&gt;
&lt;p&gt;I had been watching prices for a while: &lt;a href=&quot;https://shucks.top/&quot;&gt;shucks.top&lt;/a&gt; has been sitting in my NAS planning notes since 2023. This time I used &lt;a href=&quot;https://diskprices.com/?locale=ca&quot;&gt;Disk Prices&lt;/a&gt; as my reference. It ranks every drive by price per terabyte across retailers, and filtered to Canada it gave me a real number that any deal had to beat.&lt;/p&gt;
&lt;p&gt;The cheap enterprise drives people usually recommend come from &lt;a href=&quot;https://serverpartdeals.com/&quot;&gt;ServerPartDeals&lt;/a&gt; and &lt;a href=&quot;https://www.goharddrive.com/&quot;&gt;GoHardDrive&lt;/a&gt;, two US-based sellers of recertified datacenter drives. From Canada, the exchange rate, shipping and customs fees eat a real chunk of what those prices promise. The Western Digital store was the other option I looked at: a 10% sign-up discount that stacks with occasional promotions on the bigger drives, which made new externals genuinely interesting.&lt;/p&gt;
&lt;p&gt;In the end I bought from none of them. I found an eBay.ca listing instead: shuckable Seagate Expansion Desktop 14TB units, priced right, in Canadian dollars, with no customs surprises. And the &lt;a href=&quot;https://forums.redflagdeals.com/best-buy-seagate-expansion-desktop-14tb-usb-3-0-external-hard-drive-239-99-2652335/&quot;&gt;RedFlagDeals thread on that exact drive&lt;/a&gt; gave me the final push: people opening those boxes were finding Exos drives inside. So I ordered one and hoped for the best.&lt;/p&gt;
&lt;p&gt;{/* TODO: record the price actually paid for the eBay listing, to ground &quot;priced right&quot; in a number. */}&lt;/p&gt;
&lt;h2&gt;The enclosure&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fexpansion-desktop-enclosure.uPnVclNw.jpg&amp;#x26;w=1500&amp;#x26;h=1323&amp;#x26;f=webp&quot; alt=&quot;The Seagate Expansion Desktop, a mains-powered 3.5-inch USB 3.0 external&quot;&gt;&lt;/p&gt;
&lt;p&gt;The Expansion Desktop is Seagate&apos;s no-frills external: a plastic shell around a regular 3.5-inch SATA drive, USB 3.0 on the back, a wall adapter for power, and a generic backup software bundle nobody asked for. Seagate closes the shell with plastic tabs and not a single screw, so opening it was never part of the design.&lt;/p&gt;
&lt;h2&gt;Opening it&lt;/h2&gt;
&lt;p&gt;No screws means prying. I ran a spudger along the seam and popped the tabs one by one. The tabs snap rather than release, so there is no opening this thing gently. Inside, the drive sits in its own tray, surrounded by green shock-absorption foam blocks glued to the frame. The foam scrapes off with a thumb, and four screws later the drive was in my hand.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fexos-2x14-shucked.Cr1U-glJ.jpg&amp;#x26;w=1500&amp;#x26;h=2000&amp;#x26;f=webp&quot; alt=&quot;The drive out of the shell, foam blocks still attached&quot;&gt;&lt;/p&gt;
&lt;h2&gt;What was inside&lt;/h2&gt;
&lt;p&gt;This is the part of the shuck where you find out what Seagate put in the box. If you shuck a 14 TB Expansion Desktop, you expect a Barracuda-class consumer drive or a white-label OEM disk. The label on mine read Seagate Exos 2X14, model ST14000NM0121, MACH.2 on it, SATA 6 Gb/s, manufactured August 2023 in Thailand.&lt;/p&gt;
&lt;p&gt;Seagate does not tell you what is inside before you buy, so what I could do was read what other people found: since late 2023, more and more people opening Expansion Desktop 14TB units have been finding the dual-actuator Exos 2X14. My unit was made right in that window, and the label confirmed it. An enterprise drive for external-drive money. I got lucky.&lt;/p&gt;
&lt;h2&gt;What MACH.2 means&lt;/h2&gt;
&lt;p&gt;MACH.2 is Seagate&apos;s dual-actuator design: two independent actuator assemblies in one 3.5-inch chassis, each covering half the platters, with the firmware presenting both as a single 14 TB volume. Two sets of heads means two seek paths, which is where the performance comes from: roughly double the sustained throughput and IOPS of a regular single-actuator drive from the same generation.&lt;/p&gt;
&lt;p&gt;For me, that shows up in a simple way: the photo backup, the media library scan and the nightly vzdump all land on this one disk, and the queue clears instead of stacking up behind a single set of heads.&lt;/p&gt;
&lt;h2&gt;Into the box&lt;/h2&gt;
&lt;p&gt;I dropped the drive into one of the Jonsbo N4&apos;s hot-swap bays, connected through the Fujitsu 9300-8i in IT mode, and the ZFS pool came up as &lt;code&gt;utank&lt;/code&gt;. How the whole box came together is its own post: &lt;a href=&quot;/blog/2025-05-20--assembling-the-homelab-box&quot;&gt;Assembling the homelab box&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;{/* TODO: SMART numbers worth recording: power-on hours, reallocated and pending sectors, temperature range in the bay. */}&lt;/p&gt;
&lt;h2&gt;Closing&lt;/h2&gt;
&lt;p&gt;So, final tally: I gave up the external&apos;s warranty and spent about ten minutes breaking plastic tabs, and in return I got a dual-actuator enterprise drive sitting in a bay where a swap is just opening the front of the case. Would I do it again? Yes, eyes open: the lottery is real, Seagate does not owe anyone an Exos, and the math only works if you would have bought the bare drive anyway.&lt;/p&gt;</content:encoded><h:img src="/_astro/exos-2x14-shucked.Cr1U-glJ.jpg"/><enclosure url="/_astro/exos-2x14-shucked.Cr1U-glJ.jpg"/></item><item><title>Assembling the homelab box</title><link>https://tin.ng/blog/2025-05-20--assembling-the-homelab-box</link><guid isPermaLink="true">https://tin.ng/blog/2025-05-20--assembling-the-homelab-box</guid><description>Part choices for a quiet Proxmox VE 9 server, from a walnut-front NAS case to data-center NVMe, with build photos and the first boot</description><pubDate>Tue, 20 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;This is the hardware half of the homelab story. The software side, the design decisions and the running services, live in &lt;a href=&quot;/blog/2025-08-31--proxmox-9&quot;&gt;A single-box homelab on Proxmox VE 9&lt;/a&gt;; this post is the box itself: what I bought, how it went together, and what the first boot looked like.&lt;/p&gt;
&lt;p&gt;The build came together in spring 2025, and every choice on the list answered to the same constraint: the box lives in the house, under the stairs, a few steps from the living room. That rules out a rack, which is a machine room with fans and a noise floor, and it rules out a NAS appliance, which caps the whole story at whatever its firmware allows. A desktop-class build was the shape that survived: quiet enough to forget, every part replaceable from any computer shop, and nothing in it that only exists to look like a server.&lt;/p&gt;
&lt;h2&gt;The box at a high level&lt;/h2&gt;
&lt;p&gt;Hardware-wise, the requirement was one quiet desktop-class machine that carries the whole household, and every part choice follows from it. Concretely: a micro-ATX build running Proxmox VE 9 with one unprivileged LXC container per service plus a couple of VMs. The i5-13400&apos;s UHD 730 is the workhorse; Jellyfin transcodes and camera streams go through &lt;code&gt;/dev/dri&lt;/code&gt; passthrough, so there is no discrete GPU anywhere adding heat or noise. 64 GB of DDR4 keeps the VMs comfortable, with 24 GB of it assigned to the OPNsense edge VM alone. Storage is two tiers: a mirrored pair of 960 GB NVMe drives as the ZFS &lt;code&gt;rpool&lt;/code&gt;, carrying the OS and every guest root disk, and one 14 TB HDD as the &lt;code&gt;utank&lt;/code&gt; pool for the bulk, media, photos, backups, the data worth keeping. Two NICs split the network roles: the Intel I226-V card bridges to the LAN at 2.5 GbE, the onboard Realtek faces the WAN at 1 GbE, and keeping the roles on separate physical ports is what keeps the firewall bridge simple.&lt;/p&gt;
&lt;p&gt;The parts list below is the concrete version of that summary.&lt;/p&gt;
&lt;h2&gt;The parts&lt;/h2&gt;
&lt;p&gt;The final parts list, matching what the design-notes post runs on today:&lt;/p&gt;
&lt;p&gt;| Part | Choice |
|---|---|
| Case | Jonsbo N4, white, walnut front |
| PSU | Corsair SF750, SFX, 750 W, 80+ Platinum |
| Board | Gigabyte B760M Gaming Plus WiFi (DDR4) |
| CPU | Intel i5-13400, 10 cores / 16 threads |
| Cooler | Thermalright white twin-tower, two 120 mm fans |
| RAM | 64 GB DDR4-2667, Patriot Viper 2 x 32 GB |
| Boot / VM disk | 2 x 960 GB Micron 7450 PRO M.2 (ZFS mirror) |
| Bulk disk | 14 TB Seagate Exos 2X14 (ZFS, single disk) |
| HBA | Fujitsu 9300-8i, LSI SAS3008, IT mode |
| NICs | Intel I226-V 2.5 GbE PCIe card (LAN), onboard Realtek RTL8111 1 GbE (WAN) |
| iGPU | Intel UHD Graphics 730 (built into the 13400) |&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fmotherboard-topview.2IdEvyRh.jpg&amp;#x26;w=1500&amp;#x26;h=2000&amp;#x26;f=webp&quot; alt=&quot;The Gigabyte B760M Gaming Plus WiFi DDR4 on its box before anything touched it&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Why each part&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Case: &lt;a href=&quot;https://www.jonsbo.com/en/products/N4White.html&quot;&gt;Jonsbo N4&lt;/a&gt;.&lt;/strong&gt; A NAS chassis that does not look like a server: walnut front panel, steel body, six 3.5-inch hot-swap bays plus two 2.5-inch mounts, micro-ATX, SFX power supply. The hot-swap bays are the part I would not give back now: a drive change is opening the front and swinging a tray, not a bench session with a screwdriver.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PSU: Corsair SF750.&lt;/strong&gt; Overkill for this box, deliberately so. A 750 W Platinum SFX unit in a build that will never ask for half of it, sized so every future drive and every power spike lands well inside what it can give. Fully modular turns out to matter more in a small case than the wattage does: only the cables the build actually uses ever enter it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Board: Gigabyte B760M Gaming Plus WiFi (DDR4).&lt;/strong&gt; The DDR4 variant, picked at a time when a 2 x 32 GB DDR4 kit cost a fraction of the DDR5 equivalent and 64 GB was exactly where this build needed to land. Four DIMM slots (two still free), one PCIe 4.0 x16, two x1 slots, two M.2 connectors, which is the expansion this build needs and not much more.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPU: i5-13400.&lt;/strong&gt; Six P-cores, four E-cores, sixteen threads, and a 65 W base envelope that only stretches to 148 W when something heavy shows up. I picked it for the performance-per-watt curve: it spends most of its life far below its boost number and still runs every service container and transcode the household throws at it. The UHD 730 on the package is not an afterthought either; it is the transcode engine the whole media stack leans on.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cooler: Thermalright white twin-tower.&lt;/strong&gt; Six heat pipes, one 120 mm fan per tower, chosen because a 65 W-class CPU needs silence more than it needs exotic cooling. White because the case is white.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RAM: Patriot Viper, 2 x 32 GB DDR4-2667.&lt;/strong&gt; Two sticks for the two channels, two slots left open for a future bump. Nothing this box does has ever been memory-speed-bound; capacity was the thing worth buying.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Boot/VM disk: 2 x 960 GB &lt;a href=&quot;https://www.micron.com/products/storage/ssd/data-center-ssd/7450-ssd&quot;&gt;Micron 7450 PRO&lt;/a&gt; M.2.&lt;/strong&gt; Data-center NVMe: PCIe 4.0 x4 and, the part that actually sold me, power-loss protection. On ZFS, sync writes are the whole contract, and a boot pool full of guest disks is the last place I want drives that guess about flushing. M.2 2280 means both drives sit on the board with zero cables.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bulk disk: 14 TB Seagate Exos 2X14.&lt;/strong&gt; Shucked from a Seagate Expansion Desktop external, and the shuck turned out better than the price suggested: the drive inside is a MACH.2 dual-actuator Exos. What was inside, how the shuck went, and what MACH.2 even means get their own post: &lt;a href=&quot;/blog/2025-06-28--shucking-seagate-expansion-14tb&quot;&gt;Shucking the Seagate Expansion Desktop 14TB&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HBA: Fujitsu 9300-8i.&lt;/strong&gt; An OEM LSI SAS3008 card: PCIe 3.0 x8, eight internal 12 Gb/s ports over two SFF-8643 connectors, running IT-mode firmware so ZFS talks to the drives directly with no RAID layer in between. The N4&apos;s backplane hangs off it, and the card is sized for capacities several times what the box holds today.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NICs: Intel I226-V card plus onboard Realtek.&lt;/strong&gt; The I226-V takes the LAN bridge at 2.5 GbE; Intel NICs behave predictably under Linux bridges, which is most of what I ask of them. The onboard Realtek runs the WAN leg, where the ISP gives 1 GbE anyway. Two physical ports, two roles, no VLAN gymnastics on the edge.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Assembly&lt;/h2&gt;
&lt;p&gt;The board got fully populated on its own box first, then the whole assembly dropped into the case in one piece. Working on a bench instead of inside a chassis is the difference between a build afternoon and a build week.&lt;/p&gt;
&lt;h3&gt;CPU&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fcpu-over-open-socket.BOz3vSHa.jpg&amp;#x26;w=1500&amp;#x26;h=2000&amp;#x26;f=webp&quot; alt=&quot;The i5-13400 over the open LGA1700 socket&quot;&gt;&lt;/p&gt;
&lt;p&gt;The socket does all the work: match the triangle on the package to the corner of the socket, rest the chip flat, close the retention bracket, drop the lever. No force anywhere. If it needs force, something is misaligned.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fcpu-seated-in-socket.BZEweac1.jpg&amp;#x26;w=1500&amp;#x26;h=2000&amp;#x26;f=webp&quot; alt=&quot;The 13400 seated and clamped&quot;&gt;&lt;/p&gt;
&lt;h3&gt;RAM&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fram-unboxed.BLUck65f.jpg&amp;#x26;w=1500&amp;#x26;h=2000&amp;#x26;f=webp&quot; alt=&quot;The Viper kit out of the blister&quot;&gt;&lt;/p&gt;
&lt;p&gt;Two 32 GB sticks, paired slots per the board manual, latches closing on both ends with a click that leaves no doubt. That click is the most satisfying part of any build.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fram-installed.DmXMnL7m.jpg&amp;#x26;w=1500&amp;#x26;h=2000&amp;#x26;f=webp&quot; alt=&quot;Both sticks seated next to the cooler towers&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fram-installed-front.35V-O9-g.jpg&amp;#x26;w=2000&amp;#x26;h=1500&amp;#x26;f=webp&quot; alt=&quot;Front view of the Viper spreaders&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Cooler&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fcooler-mounted.CzxaE4zh.jpg&amp;#x26;w=1500&amp;#x26;h=2000&amp;#x26;f=webp&quot; alt=&quot;The white twin-tower mounted on the board&quot;&gt;&lt;/p&gt;
&lt;p&gt;A white Thermalright twin-tower, six heat pipes, one 120 mm fan per tower. Order matters here: RAM goes in before the cooler, because the front tower overhangs the outermost DIMM slot:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fcooler-overhang-over-ram.B6fF8NI4.jpg&amp;#x26;w=2000&amp;#x26;h=1500&amp;#x26;f=webp&quot; alt=&quot;The front tower overhangs the outermost DIMM slot&quot;&gt;&lt;/p&gt;
&lt;p&gt;Tall heat spreaders would not survive that overhang; the Vipers clear it. It is worth deciding slot order and RAM height before buying a cooler this size, not after.&lt;/p&gt;
&lt;h3&gt;On-board storage&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fmicron-m2-pair-installed.UbTidJ1q.jpg&amp;#x26;w=1500&amp;#x26;h=2000&amp;#x26;f=webp&quot; alt=&quot;Both Micron 7450 PRO drives in the board&amp;#x27;s two M.2 slots&quot;&gt;&lt;/p&gt;
&lt;p&gt;Both 960 GB drives went into the board&apos;s two M.2 slots: no cables, no brackets, no drive cages. The board&apos;s plate heatsinks went back over them once the cards were in.&lt;/p&gt;
&lt;h3&gt;Cards&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Flsi-9300-8i-seating.BWRXO_iQ.jpg&amp;#x26;w=2000&amp;#x26;h=1500&amp;#x26;f=webp&quot; alt=&quot;The Fujitsu 9300-8i being seated, with a Micron label visible below it&quot;&gt;&lt;/p&gt;
&lt;p&gt;The SAS card went into the long slot. Eight drive ports over two mini-SAS connectors on a card the size of my palm: the whole storage expansion plan of this build, in one slot.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fi226v-nic-seating.DsEmJNqa.jpg&amp;#x26;w=2000&amp;#x26;h=1500&amp;#x26;f=webp&quot; alt=&quot;The Intel I226-V in an x1 slot, the HBA above it&quot;&gt;&lt;/p&gt;
&lt;p&gt;The I226-V took an x1 slot below the HBA. A 2.5 GbE NIC does not need more than a x1 lane, so the expensive slots stay free for things that do.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fboard-complete-port-view.wBe-tbgf.jpg&amp;#x26;w=2000&amp;#x26;h=1500&amp;#x26;f=webp&quot; alt=&quot;The board fully populated, seen from the port side&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Into the case&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fjonsbo-n4-backplane.uFkC6yIJ.jpg&amp;#x26;w=1500&amp;#x26;h=2000&amp;#x26;f=webp&quot; alt=&quot;The N4&amp;#x27;s drive backplane behind the hot-swap bays&quot;&gt;&lt;/p&gt;
&lt;p&gt;The N4&apos;s backplane sits behind the hot-swap bays; the two SFF-8643 cables from the HBA run straight to it, and every bay becomes a port on the SAS card.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fexos-2x14-shucked.Cr1U-glJ.jpg&amp;#x26;w=1500&amp;#x26;h=2000&amp;#x26;f=webp&quot; alt=&quot;The Exos 2X14 in its Expansion tray right after the shuck&quot;&gt;&lt;/p&gt;
&lt;p&gt;The 14 TB Exos went into a bay and the tray handle took care of the rest; the drive I had shucked myself dropped in exactly like the pictures promised.&lt;/p&gt;
&lt;p&gt;The SF750 went in with only the cables the build uses: motherboard power, CPU power, backplane power, front-panel leads. In a case this size, fully modular is the only way the cabling comes out routed instead of crammed.&lt;/p&gt;
&lt;h3&gt;Cooling&lt;/h3&gt;
&lt;p&gt;The stock case airflow would have been fine on paper. Fine on paper is not what a box under the stairs needs; it needs to be inaudible in a hallway, and drive bays want moving air. So the case got a printed treatment:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Ffront-intake-mod-internal.DBh13GgO.jpg&amp;#x26;w=1500&amp;#x26;h=2000&amp;#x26;f=webp&quot; alt=&quot;Two Arctic 140 mm fans in the printed front frame, with the magnetic mesh beside them&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Ffront-intake-mod-external.BpuWYbXn.jpg&amp;#x26;w=2000&amp;#x26;h=1500&amp;#x26;f=webp&quot; alt=&quot;The finished front intake assembly on the case&quot;&gt;&lt;/p&gt;
&lt;p&gt;A printed frame extension on the front face holds a pair of Arctic 140 mm fans behind a magnetic mesh filter. The frame attaches with magnets and printed clips, so it pops off for cleaning instead of fighting me with screws.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Frear-fan-printed-grille.DYElVxDE.jpg&amp;#x26;w=1500&amp;#x26;h=2000&amp;#x26;f=webp&quot; alt=&quot;The rear fan behind its printed honeycomb grille&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fpsu-side-fan-printed-guard.ChMue8_L.jpg&amp;#x26;w=1500&amp;#x26;h=2000&amp;#x26;f=webp&quot; alt=&quot;A small supplemental fan on the PSU bracket, behind a printed honeycomb guard&quot;&gt;&lt;/p&gt;
&lt;p&gt;The rear fan wears a printed honeycomb grille, and a small supplemental fan on the PSU bracket keeps air moving there. Every fan in this case touches a printed part; the printer earned its corner of the house again. None of it is load-bearing engineering. It just makes this a box I open happily instead of one I avoid.&lt;/p&gt;
&lt;h2&gt;First boot&lt;/h2&gt;
&lt;p&gt;Proxmox VE 9 went on the NVMe pair as a ZFS mirror, and the two pools came up with &lt;code&gt;ashift=12&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;zpool create -o ashift=12 rpool mirror nvme0n1p3 nvme1n1p3
zpool create -o ashift=12 utank /dev/sda1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;rpool&lt;/code&gt; mirror takes the OS and every guest root disk; &lt;code&gt;utank&lt;/code&gt; is the bulk pool for media, photos and backups. From here the story continues in &lt;a href=&quot;/blog/2025-08-31--proxmox-9&quot;&gt;the design-notes post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;{/* TODO: BIOS/UEFI settings worth recording: iGPU enablement for /dev/dri passthrough, boot order, wake-on-power-loss and fan curve settings. */}&lt;/p&gt;
&lt;h2&gt;Closing&lt;/h2&gt;
&lt;p&gt;The list has no exotic parts: a CPU chosen for its power curve, data-center SSDs bought because they tell the truth about flushes, a shucked enterprise drive, and a NAS case that looks like furniture instead of a server. Everything else on top of it, the printed fan frame and the honeycomb grilles, exists because the printer was three steps away and zip ties were not the answer.&lt;/p&gt;
&lt;p&gt;The box went under the stairs and disappeared into the household routine: quiet, hidden, and boring in exactly the way a machine that carries the family&apos;s digital life should be. What the box does all day is the other half of the story: &lt;a href=&quot;/blog/2025-08-31--proxmox-9&quot;&gt;A single-box homelab on Proxmox VE 9&lt;/a&gt;.&lt;/p&gt;</content:encoded><h:img src="/_astro/board-complete-port-view.wBe-tbgf.jpg"/><enclosure url="/_astro/board-complete-port-view.wBe-tbgf.jpg"/></item><item><title>An Obsidian vault on numbered shelves, synced by iCloud and git</title><link>https://tin.ng/blog/2024-11-11--obsidian-vault-icloud-git-sync</link><guid isPermaLink="true">https://tin.ng/blog/2024-11-11--obsidian-vault-icloud-git-sync</guid><description>About a thousand notes across two-digit domains, minimal frontmatter on every file, and two rails that fail differently enough to cover each other</description><pubDate>Mon, 11 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;The vault holds about 1,150 notes and has outlived several systems that
promised to organize them better. This post documents the current shape:
how notes are classified, what metadata every note carries, and the two
sync rails, iCloud and git, that move it between machines. Deliberately no
note contents here; the structure is the interesting part.&lt;/p&gt;
&lt;p&gt;The vault folder name carries a date, which is a habit worth keeping: each
era of life gets a fresh vault, old ones stay frozen and readable, and the
current one never drags a decade of clutter behind it.&lt;/p&gt;
&lt;h2&gt;How I got here&lt;/h2&gt;
&lt;p&gt;OneNote came first, through a bachelor&apos;s degree where the equation editor
was the feature that mattered. I was the only one in the lectures typing
math instead of writing it down, and my hands still know those keystrokes.
What pushed me out was the format: notes locked in a proprietary container
that grep could not read and git could not track.&lt;/p&gt;
&lt;p&gt;The zettelkasten years followed, a run of plain-text editors with Zettlr
as the longest stop. The &lt;code&gt;YYMMDD-&lt;/code&gt; prefix on notes here is a zettelkasten
convention, and it never left. No single editor survived those years:
each had its own ideas about where notes live and how they connect, so
every switch meant another migration.&lt;/p&gt;
&lt;p&gt;Then LaTeX and vim, notes written as &lt;code&gt;.tex&lt;/code&gt; and edited like code. Total
control, and capture slowed to the speed of the toolchain, which is the
wrong direction for notes. None of it was built for a phone.&lt;/p&gt;
&lt;p&gt;Obsidian is where this settled. The notes are plain markdown on disk, so
iCloud and git work on the folder directly and the mobile app reads
exactly what the desktop writes, with no export step anywhere. Migration
ended when the app became replaceable, which is why this post is about
folders and rails rather than features.&lt;/p&gt;
&lt;h2&gt;Numbered shelves&lt;/h2&gt;
&lt;p&gt;Classification is a two-level numbering scheme in the Johnny Decimal
spirit. Top-level domains are two-digit, and every shelf inside them takes
the parent&apos;s number:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;10 Personal      the life admin shelf
20 Journal       daily and period writing
30 Resource      collected knowledge about tools and techniques
40 Project       one shelf per active project
50 Development   long-lived technical topics
60 Academic      courses and research
98 Meta          the vault&apos;s own machinery
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The rule that makes numbering worth it is boring: a note has exactly one
address, search scope shrinks to a shelf, and restructuring means moving
one numbered folder instead of re-sorting a thousand files. When a project
dies, its shelf dies with it. Domains get added above 90 only for the
vault&apos;s own machinery, so the content numbering stays stable.&lt;/p&gt;
&lt;h2&gt;Note identity and metadata&lt;/h2&gt;
&lt;p&gt;Every note starts from a template and carries the same minimal frontmatter:
a creation date, a modification date, and a tag. Templates fill the dates
automatically and prompt once for the tag, because a tag asked for at
creation is the one that actually gets chosen.&lt;/p&gt;
&lt;p&gt;Tags follow a &lt;code&gt;family/scope&lt;/code&gt; pattern, hierarchical where it helps: journal
notes are tagged by family and week, class notes by family and course, so a
semester or a week is one tag click away. Notes also carry an ID prefix
(&lt;code&gt;YYMMDD-&lt;/code&gt;), which keeps note filenames sortable and unique without thinking
about it.&lt;/p&gt;
&lt;p&gt;The template gallery is small on purpose: daily notes, meeting minutes,
class notes, a people note, and capture templates for scans and OCR. Daily
notes are the hub; each one opens with links to yesterday and tomorrow and
an Eisenhower matrix built from task queries, so the day starts with a
filtered view of what is urgent and what matters.&lt;/p&gt;
&lt;h2&gt;Sync rail 1: iCloud&lt;/h2&gt;
&lt;p&gt;The vault lives in iCloud Drive, which means the macOS and iOS Obsidian
apps see the same folder natively. This rail is instant, silent, and needs
no configuration; a note written on the phone is on the laptop by the time
I sit down. Its failure mode is a conflicted copy appearing next to the
original, which is rare and always visible in the file list.&lt;/p&gt;
&lt;h2&gt;Sync rail 2: git&lt;/h2&gt;
&lt;p&gt;The second rail is the obsidian-git plugin, and it is configured to do one
job continuously and another only on command:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Auto-commit every five minutes with a &lt;code&gt;vault backup: &amp;#x3C;date&gt;&lt;/code&gt; message,
changed files listed in the message body.&lt;/li&gt;
&lt;li&gt;Rebase as the sync method, so local history stays linear.&lt;/li&gt;
&lt;li&gt;Push stays manual, to the self-hosted Gitea.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That split is deliberate. Commits are cheap and frequent, capturing a
timeline that no file-level sync can offer; deleting a paragraph yesterday
is recoverable today. Pushing is a decision, because publishing the vault
offsite should never ride an automation. Git never runs on the phones at
all; it protects the vault&apos;s history, not its availability.&lt;/p&gt;
&lt;p&gt;Large files never enter normal git history. The repo routes them through
git LFS instead, and &lt;code&gt;git lfs ls-files&lt;/code&gt; counts more than 2,000 of them,
more than half of them PDFs, with images, Office and iWork documents,
videos, and split archives making up the rest.&lt;/p&gt;
&lt;p&gt;What the Gitea box is, and what protects it, are both later posts:
&lt;a href=&quot;/blog/2025-08-31--proxmox-9&quot;&gt;A single-box homelab on Proxmox VE 9&lt;/a&gt; and
&lt;a href=&quot;/blog/2025-11-03--proxmox-backup-server-tuning&quot;&gt;Tuning Proxmox Backup Server&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Cross-device config: .obsidian and .obsidian.ios&lt;/h2&gt;
&lt;p&gt;The vault carries two config folders. The desktop one holds the automation
stack: obsidian-git, Templater, QuickAdd, and the auto-link-title plugin.
The iOS one, kept alongside as &lt;code&gt;.obsidian.ios&lt;/code&gt; and renamed into place on
the phone and iPad, holds a different set: relative line numbers, an
alternative file tree, the table editor, admonition callouts, a code-block
syntax highlighter, and meld-encrypt for the few notes that are encrypted
at rest.&lt;/p&gt;
&lt;p&gt;The split exists because the platforms are different tools. Git cannot run
inside iOS Obsidian, and the templating automations that make sense at a
keyboard are noise on a phone, where capture speed is the only feature
that matters. Shared machinery stays in the vault itself, in the meta
shelf: templates, the one script, and notes documenting the Shortcuts and
widgets that push content into the vault from the home screen.&lt;/p&gt;
&lt;h2&gt;Closing&lt;/h2&gt;
&lt;p&gt;iCloud gives the vault its speed: every device sees what feels like a local
file. Git gives it history: every past state of every note stays
recoverable. The two fail in different ways, so together they cover each
other&apos;s gaps. Underneath, the numbered shelves and the minimal
frontmatter keep a thousand notes boring, which is exactly what I want from
a note system.&lt;/p&gt;</content:encoded><h:img src="/_astro/obsidian-vault-hero.C8-7ruk3.svg"/><enclosure url="/_astro/obsidian-vault-hero.C8-7ruk3.svg"/></item><item><title>A self-hosted app store for SideStore and F-Droid</title><link>https://tin.ng/blog/2024-08-28--self-hosted-app-store-sidestore-fdroid</link><guid isPermaLink="true">https://tin.ng/blog/2024-08-28--self-hosted-app-store-sidestore-fdroid</guid><description>One Caddy endpoint and static JSON catalogs put sideloaded apps on every device in the house, with signing that never leaves the rack</description><pubDate>Wed, 28 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;Every device in the family runs at least a few apps that never came from the
official stores. My wife and I live on iPhones and iPads; one of my siblings
is on Android, and I want both platforms covered, including the apps I
develop myself. The reasons are ordinary: my wife plays a game that is only
published in the Vietnamese App Store, a few apps stopped shipping versions
for the older iPhone models still in the house, and there is a running list
of open source apps I want to try. On the Apple side that means sideloading
IPAs with a developer certificate; on the Android side it means APKs that
Google Play will never list. Managing that by cable and by messaging random
download links gets old fast, so I treated it as a distribution problem and
built it like every other service in the rack: one endpoint, static files,
boring infrastructure.&lt;/p&gt;
&lt;p&gt;The result is a single host, &lt;code&gt;store.&amp;#x3C;domain&gt;&lt;/code&gt;, that fronts two ecosystems:&lt;/p&gt;
&lt;p&gt;| Platform | Client | Catalog | Binaries |
|---|---|---|---|
| iOS / iPadOS | SideStore | AltStore source JSON | self-hosted IPAs |
| Android | F-Droid client or browser | F-Droid repo layout | self-hosted APKs |&lt;/p&gt;
&lt;p&gt;This post covers the delivery mechanism only.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./appstore-flow.svg&quot; alt=&quot;Delivery flow from Caddy to both platforms&quot;&gt;&lt;/p&gt;
&lt;h2&gt;The catalog is just JSON&lt;/h2&gt;
&lt;p&gt;SideStore and AltStore read a plain JSON document over HTTPS. No server-side
logic is required: the source is a file listing apps, and each app carries its
own metadata, changelog, screenshots, and download URLs. The field names
follow &lt;a href=&quot;https://faq.altstore.io/developers/make-a-source&quot;&gt;AltStore&apos;s published source schema&lt;/a&gt;,
so anything that reads an AltStore source reads this one. The whole schema
fits on one screen:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;name&quot;: &quot;IPA Library&quot;,
  &quot;identifier&quot;: &quot;com.example.altsource&quot;,
  &quot;sourceURL&quot;: &quot;https://store.&amp;#x3C;domain&gt;/source.json&quot;,
  &quot;featuredApps&quot;: [&quot;com.vendor.app&quot;],
  &quot;apps&quot;: [
    {
      &quot;name&quot;: &quot;App name&quot;,
      &quot;bundleIdentifier&quot;: &quot;com.vendor.app&quot;,
      &quot;developerName&quot;: &quot;Vendor&quot;,
      &quot;subtitle&quot;: &quot;Short line&quot;,
      &quot;localizedDescription&quot;: &quot;What the app does&quot;,
      &quot;iconURL&quot;: &quot;https://.../icon.png&quot;,
      &quot;tintColor&quot;: &quot;1fd362&quot;,
      &quot;screenshotURLs&quot;: [&quot;https://.../shot1.png&quot;],
      &quot;version&quot;: &quot;19.49.7&quot;,
      &quot;versionDate&quot;: &quot;2024-12-30&quot;,
      &quot;versionDescription&quot;: &quot;Changelog for this version&quot;,
      &quot;size&quot;: 122046197,
      &quot;downloadURL&quot;: &quot;https://.../app.ipa&quot;,
      &quot;versions&quot;: [
        {
          &quot;version&quot;: &quot;19.49.7&quot;,
          &quot;date&quot;: &quot;2024-12-30&quot;,
          &quot;downloadURL&quot;: &quot;https://.../app.ipa&quot;,
          &quot;size&quot;: 122046197
        }
      ]
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;versions&lt;/code&gt; array is what makes the source feel like a store rather than
a downloads page: the client shows release history and lets a device roll
back to an older IPA without touching the server.&lt;/p&gt;
&lt;p&gt;I run two tiers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The family source, the one catalog every device in the house has added:
the apps people actually install, with a featured row at the top for the
day-one picks.&lt;/li&gt;
&lt;li&gt;The staging source, subscribed only by my own devices: development
builds, A/B tests between two builds of the same app, and experiments
that are not ready for anyone else. When a build settles, it graduates
into the family source.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The local mirror on the NAS&lt;/h2&gt;
&lt;p&gt;The catalogs live inside the LAN on &lt;code&gt;utank&lt;/code&gt;, the 14 TB bulk pool:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/srv/appstore/
├── ipa/                 versioned build output from fastlane
├── apk/
├── catalog/             family + staging sources, local IPAs, screenshots
├── repo/                Android repo root
└── fastlane/            build reports
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;catalog&lt;/code&gt; folder holds both sources, each branded as its own catalog
with a different &lt;code&gt;sourceURL&lt;/code&gt;, pointing &lt;code&gt;downloadURL&lt;/code&gt; entries at local
copies of the binaries. This is where freshly built or resigned IPAs land
first, and it is the only way to distribute my own builds, which have no
reason to exist on a public file host at all. Those builds come out of a
long framework arc: my apps started in React Native, moved through Flutter,
and settled back in the native languages. The framework changed twice; the
output never did: an IPA or an APK that needs a home.&lt;/p&gt;
&lt;h2&gt;Caddy serves both ecosystems&lt;/h2&gt;
&lt;p&gt;The Caddy container gets two read-only bind mounts from &lt;code&gt;utank&lt;/code&gt;, one per
tree. One host block on &lt;code&gt;store.&amp;#x3C;domain&gt;&lt;/code&gt; routes a path prefix to each root
through a reusable snippet. I am leaving the config out of this post:
a published host name plus its paths is all a scraper needs, and the catalog
is not looking for visitors.&lt;/p&gt;
&lt;p&gt;Three details matter here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No directory listing.&lt;/strong&gt; The snippet answers 403 for any bare directory
path; only exact file URLs are served.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No auth.&lt;/strong&gt; Every other host name on this Caddy sits behind Authelia. This
one deliberately does not, because phones need to fetch sources and IPAs
without an interactive login, and the contents are meant to be freely
readable by the people I gave the source URL to anyway.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Correct content types.&lt;/strong&gt; The &lt;code&gt;.apk&lt;/code&gt; files go out as
&lt;code&gt;application/vnd.android.package-archive&lt;/code&gt;, which matters because some
Android browsers refuse installs from a wrong MIME type.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The host name resolves differently depending on where the client sits, which
is split-brain DNS doing its job: on the LAN, Unbound&apos;s host overrides point
&lt;code&gt;store.&amp;#x3C;domain&gt;&lt;/code&gt; straight at the Caddy box, so a 200 MB IPA transfers over
gigabit Ethernet and never leaves the house. Remotely, the same name goes
through Cloudflare to the WAN 443 forward into the same Caddy. One URL works
everywhere; the route changes.&lt;/p&gt;
&lt;h2&gt;iOS: signing and the anisette server&lt;/h2&gt;
&lt;p&gt;SideStore handles the parts Apple makes hard. It is installed once, and
after that it signs and installs IPAs on the device itself, no computer in
the loop. The constraints are Apple&apos;s, not SideStore&apos;s:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;free Apple ID&lt;/strong&gt; signs apps for 7 days and caps three sideloaded apps.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;paid developer account&lt;/strong&gt; ($99 USD/year, roughly 130 CAD) signs for a
full year and lifts the cap. I carry one: the free tier puts the whole
store on a seven-day re-signing schedule, and I would rather pay it than
run that loop for a family.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When SideStore talks to Apple&apos;s signing endpoints it must present anisette
data, the per-device identifiers Apple uses to authenticate requests. I run
that piece myself instead of leaning on a public anisette endpoint: an
&lt;code&gt;omnisette-server&lt;/code&gt; container, one of the smallest guests in the rack at
512 MB, running a single binary:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;omnisette-server --http-port &amp;#x3C;port&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Caddy proxies it over TLS at its own host name, and the anisette URL in
SideStore settings points there. Every device in the family then signs
against my endpoint: no dependence on someone else&apos;s server staying up, and
no third party in the signing path.&lt;/p&gt;
&lt;h2&gt;Android: an F-Droid repo is just a folder&lt;/h2&gt;
&lt;p&gt;F-Droid&apos;s own client can be pointed at any repository, and a repository is
nothing but static files on a web server: an index plus APKs. That makes the
Android half of this setup almost trivially cheap, because the same Caddy
that serves the SideStore source also serves the F-Droid repo from its own
root.&lt;/p&gt;
&lt;p&gt;The layout today holds the client APKs I want on Android devices, fetchable
by URL and installed directly, either from a browser or through an F-Droid
client. The next step when I get to it is generating a proper signed
&lt;code&gt;index-v1.json&lt;/code&gt; with &lt;code&gt;fdroidserver&lt;/code&gt; so the F-Droid client can manage updates
for those apps the way it manages everything else; the folder is already in
the layout that tooling expects, and Caddy does not care what the files are.&lt;/p&gt;
&lt;h2&gt;Why one endpoint matters&lt;/h2&gt;
&lt;p&gt;Both platforms end up behind one host name, one certificate, and one set of
ACLs. Adding an app is dropping a file into a folder and adding a JSON entry;
updating it is adding a version. Devices add the source once and everything
after that is refresh and install from the store UI they already know.&lt;/p&gt;
&lt;p&gt;The recurring cost is the developer account, and that would exist for any
sideloading setup. Everything else rides on hardware and services the lab
already runs, which is exactly how the rest of this homelab is built.&lt;/p&gt;</content:encoded><h:img src="/_astro/appstore-hero.BXBzdKMC.svg"/><enclosure url="/_astro/appstore-hero.BXBzdKMC.svg"/></item><item><title>Semaphore with MediaPipe Pose, retuned for smaller humans</title><link>https://tin.ng/blog/2024-03-09--semaphore-flags-mediapipe-pose</link><guid isPermaLink="true">https://tin.ng/blog/2024-03-09--semaphore-flags-mediapipe-pose</guid><description>A full-body semaphore keyboard carried into a classroom, where the pose math is scale-invariant and the gesture thresholds are not</description><pubDate>Sat, 09 Mar 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;Flag semaphore is a signal alphabet where every letter is a pair of arm
angles. That makes it a perfect party trick for a pose detector: point
MediaPipe at a webcam, bucket each arm direction to the nearest 45 degrees,
and look up the pair in a dictionary.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/everythingishacked&quot;&gt;Everything Is Hacked&lt;/a&gt; (Fletcher Bach)
built exactly that in
&lt;a href=&quot;https://github.com/everythingishacked/Semaphore&quot;&gt;Semaphore&lt;/a&gt;, a full-body
keyboard where you type by waving your arms. The LICENSE file says GPL-3.0,
but the terms that matter are in a commit message: &quot;Do whatever you want
with my code, just don&apos;t make it boring&quot;. His
&lt;a href=&quot;https://youtu.be/h376W93gQq4&quot;&gt;demo video&lt;/a&gt; shows the whole thing in action,
and the project later evolved into a full-body game controller called
&lt;a href=&quot;https://github.com/everythingishacked/Gamebody&quot;&gt;Gamebody&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I took the project to a classroom setting where junior high and high school
students would be the ones signaling. That exposed the one thing the original
did not account for: it was tuned on one adult body, at one distance from one
webcam. This post covers how the original reads a signal, where body size
actually matters, and what I changed so that every size of human in the
room could type.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fsemaphore-alphabet.DaRQGxhq.jpg&amp;#x26;w=5613&amp;#x26;h=3967&amp;#x26;f=webp&quot; alt=&quot;Flag semaphore alphabet&quot;&gt;&lt;/p&gt;
&lt;h2&gt;How the original reads a signal&lt;/h2&gt;
&lt;p&gt;MediaPipe&apos;s Pose solution returns 33 body landmarks per frame in normalized
image coordinates, each with a visibility score. The signaling logic in
&lt;code&gt;semaphore.py&lt;/code&gt; is small:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Each arm is the triplet (shoulder, elbow, wrist). A limb counts as
&quot;pointing&quot; when the elbow angle is within 20 degrees of straight and the
forearm is at least 0.8 times the upper arm, so a bent or relaxed arm
never registers.&lt;/li&gt;
&lt;li&gt;The arm direction is the angle from shoulder to wrist, collapsed to the
nearest 45 degrees. Eight directions per arm.&lt;/li&gt;
&lt;li&gt;The pair of directions is looked up in the &lt;code&gt;SEMAPHORES&lt;/code&gt; dictionary,
which follows the standard alphabet closely. &lt;code&gt;(90, 90)&lt;/code&gt; is space, or
enter when the numerals layer is active.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Everything in that chain is scale-invariant. Angles do not care how tall you
are, and the two sanity checks are ratios: forearm versus upper arm, both
drawn from the same body in the same frame.&lt;/p&gt;
&lt;p&gt;The keyboard&apos;s extra commands ride on other gestures: hands over the mouth
for backspace, a squat for the numerals layer, open hands for shift, a jump
to repeat the last character, leg lifts for command and control, and
crossed-arm-plus-leg-angle combinations for arrow keys.&lt;/p&gt;
&lt;h2&gt;Where body size actually matters&lt;/h2&gt;
&lt;p&gt;I went through the code expecting to rewrite the pose math for kids and did
not need to. The weak point is narrower than that. Three gestures are
thresholded on absolute distances in normalized frame units, and those
constants were clearly tuned on one adult filling one webcam:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;MOUTH_COVER_THRESHOLD = .03   # hands over mouth, frame-fraction distance
SQUAT_THRESHOLD       = .1    # hip-to-knee vertical distance
JUMP_THRESHOLD        = .0001 # hips rise/fall per frame
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A smaller person, or anyone standing farther from the camera, shifts every
one of these. The mouth-to-palm gap shrinks, so backspace fires when the
hands are nowhere near the face. The hip-to-knee gap shrinks, so the squat
layer latches on while standing. The hip rise of a jump compresses below the
threshold, so repeat stops working. Nothing is wrong with the pose math; the
constants are just calibrated for a body that is not twelve years old.&lt;/p&gt;
&lt;h2&gt;The fork&lt;/h2&gt;
&lt;p&gt;My changes are one commit on top of the original, repurposing it from a
keyboard emulator into a self-contained alphabet display. Concretely:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The keyboard injection is gone. The original types through the
&lt;code&gt;keyboard&lt;/code&gt; library, which needs Accessibility permissions and an editor
to type into; the fork keeps an on-screen string instead, where space
and delete edit the string directly and enter commits it and saves the
frame as a PNG. The demo becomes a single window a projector can show,
with no other app involved.&lt;/li&gt;
&lt;li&gt;The squat layer now yields uppercase, and the squat band widened from
0.10 to 0.12. The original mapped the squat layer to digits and symbols;
uppercase letters are the more useful second layer for a classroom.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JUMP_THRESHOLD&lt;/code&gt; dropped from 0.0001 to 0.00008, because a smaller body
produces a smaller apparent hip displacement per frame. Without this the
repeat gesture simply never fires for the youngest participants.&lt;/li&gt;
&lt;li&gt;A two-second cooldown between characters (&lt;code&gt;NEW_CHAR_TIMEOUT&lt;/code&gt;) makes one
pose one letter. Kids hold a signal, it registers, and there is time to
read it before the next one.&lt;/li&gt;
&lt;li&gt;The leg gestures are gone, all commented out. Command, control, and the
arrow-key combinations require balance and coordination that consume
attention better spent on the alphabet itself.&lt;/li&gt;
&lt;li&gt;Selfie flip, landmark drawing, and recording are forced on, and the
running string renders in a white box in the bottom right corner so it
stays readable on a projector.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fpose-skeleton.CGnKjwms.png&amp;#x26;w=540&amp;#x26;h=1080&amp;#x26;f=webp&quot; alt=&quot;Pose landmarks over a live signer&quot;&gt;&lt;/p&gt;
&lt;p&gt;The result runs on an Apple Silicon Mac with &lt;code&gt;mediapipe-silicon&lt;/code&gt; 0.9.2.1 and
OpenCV, one process, one window.&lt;/p&gt;
&lt;h2&gt;The fix I would make next&lt;/h2&gt;
&lt;p&gt;The one thing left unresolved is &lt;code&gt;MOUTH_COVER_THRESHOLD&lt;/code&gt;, still an absolute 0.03 of
frame distance. For students who appear small in frame, hands near the face
can still trip a false delete. The codebase already contains the right
pattern: crossed arms and open hands are measured against the mouth width,
which scales with the person. Backspace wants the same treatment, normalizing
the mouth-to-palm distance by a body segment such as the shoulder width, and
I would fold that in before the next workshop rather than retune the constant
per room.&lt;/p&gt;
&lt;h2&gt;Closing&lt;/h2&gt;
&lt;p&gt;The original Semaphore is a beautifully scoped project: pose landmarks, a
dictionary of angle pairs, and a week of readable code, and none of it needed
touching to work for students. Tuning a gesture UI on one body only gets you
so far; what mattered in the classroom was knowing which three constants to
move and by how much. Credit again to
Everything Is Hacked for the original and for the most generous commit
message in open source.&lt;/p&gt;</content:encoded><h:img src="/_astro/semaphore-hero.BYPW8njI.svg"/><enclosure url="/_astro/semaphore-hero.BYPW8njI.svg"/></item><item><title>STM32F4 USB OTG and Modbus RTU</title><link>https://tin.ng/blog/2023-04-06--stm32f4-usb-otg-modbus-rtu</link><guid isPermaLink="true">https://tin.ng/blog/2023-04-06--stm32f4-usb-otg-modbus-rtu</guid><description>The Discovery board answers as a Modbus slave over a virtual COM port while a PyModBus harness asks the questions from the PC</description><pubDate>Thu, 06 Apr 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;A take-home assignment for an interview asked me to talk to PLC-style machines and a HRI interface inside a high voltage control box. Those boxes run Modbus RTU, so I built a small demonstration of both sides of the conversation: an STM32F4 Discovery board acting as a Modbus RTU slave over its USB OTG port, and a Python harness on the PC doing the asking. The video below is the live demo.&lt;/p&gt;
&lt;p&gt;The nice part of this setup: no USB-to-serial adapter, no RS-485 dongle. The board&apos;s own USB OTG port shows up on the PC as a virtual COM port, and Modbus RTU runs over it directly.&lt;/p&gt;
&lt;h2&gt;The firmware side&lt;/h2&gt;
&lt;p&gt;The board runs a small RTOS with a blinker thread, and that thread doubles as a status light: it blinks with a different period when USB_OTG is active, keeping the duty cycle at 50% either way. So before any protocol traffic happens, you can tell from across the desk whether the connection is up.&lt;/p&gt;
&lt;p&gt;Two things come out of that USB port:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A plain byte stream with board status, the words &lt;code&gt;process: on&lt;/code&gt; and &lt;code&gt;process: off&lt;/code&gt; among them.&lt;/li&gt;
&lt;li&gt;Modbus RTU frames when a master asks. The slave mode supports a 16-bit CRC, and implements function code 1 (Read Coils) and function code 5 (Write Single Coil). Anything else gets a proper Modbus exception instead of silence.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;The PC side with Python&lt;/h2&gt;
&lt;p&gt;Two short scripts drive the demo. The first one, &lt;code&gt;usb_ser.py&lt;/code&gt;, opens the virtual COM port, reads the raw byte stream, decodes it, and appends it to a log file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Decoded Byte: 1947fKM process: off
Decoded Byte: 1947fKM process: on
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second one, &lt;code&gt;mb_tester.py&lt;/code&gt;, uses &lt;a href=&quot;https://pymodbus.readthedocs.io/&quot;&gt;PyModBus&lt;/a&gt; as the Modbus master. Running it with pymodbus debug logging turned on is the best way to see what actually goes over the wire:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;DEBUG:pymodbus:Running Current transaction state: IDLE
DEBUG:pymodbus:Changing transaction state from &apos;SENDING&apos; to &apos;WAITING FOR REPLY&apos;
DEBUG:pymodbus:Changing transaction state from &apos;WAITING FOR REPLY&apos; to &apos;PROCESSING REPLY&apos;
DEBUG:pymodbus:GET: Read Coils Response
DEBUG:pymodbus:Changing transaction state from &apos;PROCESSING REPLY&apos; to &apos;TRANSACTION_COMPLETE&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The demo walks through reads, then writes a single coil on and off, and finally pokes an address that is not implemented. The board answers with exception code 2, illegal data address, which is the correct way for a slave to say &quot;ask me something I have&quot;.&lt;/p&gt;
&lt;h2&gt;Why this simple demo is enough&lt;/h2&gt;
&lt;p&gt;On paper the demo looks almost too small: read some coils, write one coil, log a few bytes. But that is genuinely the whole vocabulary, and industrial machines do not speak a bigger one.&lt;/p&gt;
&lt;p&gt;Every Modbus device, from a cheap power meter to a factory PLC, exposes itself the same way: coils for on/off facts (motor running, fault latched, door closed) and registers for numbers (temperature, vibration, current draw). Read Coils and Write Single Coil are the two verbs this slave implements, and they cover the two things a maintenance system ever does: read the machine&apos;s state and occasionally tell it something. A real PLC just has more addresses and more data behind each one. The transport details this demo already handles, the 16-bit CRC, the request/response states, the exception replies, are identical in the field.&lt;/p&gt;
&lt;p&gt;That matters for automated predictive maintenance, which is a boring loop when you get down to it: poll the machine on a schedule, log what comes back, and watch the trend lines. A bearing does not fail silently; current draw creeps up, vibration climbs, a coil that used to answer starts throwing exceptions. Catching that means having exactly what this demo has: a master that polls without being told, a device that answers with real state, and a script that decodes the stream and appends it to a log. The &lt;code&gt;process: on/off&lt;/code&gt; bytes in the first script are the smallest possible version of that loop. Point the same PyModBus client at a real machine, run it from cron, and feed the log to anything that charts trends, and the loop scales from a desk demo to a control box.&lt;/p&gt;
&lt;p&gt;The exception path is the part that matters for real use. A slave that goes quiet on a bad request stalls the whole polling loop; one that answers with exception code 2 lets the poller log it and move on. In a control box where the poller might be the only thing watching, that reply is what keeps the trend lines continuous.&lt;/p&gt;
&lt;h2&gt;References that made it work&lt;/h2&gt;
&lt;p&gt;The source note behind this post is basically a reading list, and these are the ones that earned their place:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.csimn.com/CSI_pages/Modbus101.html&quot;&gt;Modbus Tutorial from Control Solutions&lt;/a&gt;: the plain-English Modbus 101, read this before touching a register.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.modbusdriver.com/diagslave.html&quot;&gt;diagslave&lt;/a&gt;: a free Modbus slave simulator. Testing the master script against a simulator before the hardware saved me a loop of &quot;is it the code or the board&quot;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/a/71057198&quot;&gt;PyModBus basic example on Stack Overflow&lt;/a&gt; and &lt;a href=&quot;https://stackoverflow.com/a/70730441&quot;&gt;reading registers over RTU&lt;/a&gt;: the two snippets that unblocked the client code.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/techbeast-org/modbus-tcp/blob/master/modbus-read-regsiter.py&quot;&gt;techbeast-org/modbus-tcp&lt;/a&gt;: a minimal read-register script, useful as a sanity check.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/epsilonrt/libmodbuspp&quot;&gt;libmodbuspp&lt;/a&gt;: the C++ wrapper around libmodbus, kept on the list in case the PC side ever needs to be native.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=6MtFLA0NW1c&quot;&gt;Part 2 - Using Python to Read and Write data to Modbus&lt;/a&gt;: the walkthrough I followed for the Python side.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="/_astro/stm32-otg-board.Bz4umGcY.jpg"/><enclosure url="/_astro/stm32-otg-board.Bz4umGcY.jpg"/></item><item><title>Phidget Spatial IMU with ROS</title><link>https://tin.ng/blog/2022-08-09--phidget-spatial-imu-with-ros-1-wrapper</link><guid isPermaLink="true">https://tin.ng/blog/2022-08-09--phidget-spatial-imu-with-ros-1-wrapper</guid><description>Bring up board 1044 over Noetic through the phidgets_drivers wrapper, then calibrate the compass so the heading means something</description><pubDate>Tue, 09 Aug 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;This article describes how to bring up a &lt;a href=&quot;https://www.robotshop.com/ca/en/phidgetspatial-3-3-3-3-axis-compass-gyroscope-accelerometer.html&quot;&gt;PhidgetSpatial 3/3/3&lt;/a&gt; (Phidgets board 1044) and read it over ROS 1: first through the Phidget22 API, where quaternions come from the &lt;code&gt;Spatial&lt;/code&gt; object&apos;s &lt;em&gt;AlgorithmData&lt;/em&gt; event, then through the &lt;a href=&quot;https://wiki.ros.org/phidgets_drivers&quot;&gt;&lt;code&gt;phidgets_drivers&lt;/code&gt;&lt;/a&gt; ROS wrapper. Plus the compass calibration workflow, without which the heading output is garbage. My setup ran on a Jetson Nano with ROS Noetic, and the whole exercise has a destination: a four-wheel differential-drive mobile robot for experiments, whose odometry stack needs a trustworthy heading.&lt;/p&gt;
&lt;h2&gt;The board: PhidgetSpatial 3/3/3&lt;/h2&gt;
&lt;p&gt;The 1044 is a single USB board packing three 3-axis MEMS sensors: accelerometer (±8 g per axis), gyroscope and magnetometer. Hence &quot;3/3/3&quot;. Full specs are in the &lt;a href=&quot;https://www.phidgets.com/docs/1044_User_Guide&quot;&gt;1044 User Guide&lt;/a&gt;; the short version for my purposes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One serial number, four channel types: the three individual sensor channels plus a combined &lt;strong&gt;Spatial&lt;/strong&gt; channel.&lt;/li&gt;
&lt;li&gt;Magnetometer readings in gauss, plus a compass bearing derived from them.&lt;/li&gt;
&lt;li&gt;Data interval and change trigger are configurable per channel; mine sat at 256 ms for everything below.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My unit enumerates as &quot;PhidgetSpatial Precision 3/3/3 High Resolution&quot;, serial 301,849, firmware 403:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fchannels.Cfydaa69.png&amp;#x26;w=522&amp;#x26;h=173&amp;#x26;f=webp&quot; alt=&quot;Phidget Control Panel listing the 1044&amp;#x27;s channels: accelerometer, gyroscope, magnetometer and the combined Spatial channel&quot;&gt;
&lt;em&gt;The 1044 enumerates four channel types: the three individual sensors plus the combined Spatial channel.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Quaternions from the Spatial object&lt;/h2&gt;
&lt;p&gt;In the Phidget22 API, the individual &lt;strong&gt;Accelerometer&lt;/strong&gt;, &lt;strong&gt;Gyroscope&lt;/strong&gt; and &lt;strong&gt;Magnetometer&lt;/strong&gt; channel objects each fire their own data event. To work with quaternions, neither of those is what you want:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use the single &lt;strong&gt;Spatial&lt;/strong&gt; object instead of the three individual channel objects.&lt;/li&gt;
&lt;li&gt;Normally you&apos;d subscribe to the &lt;em&gt;SpatialData&lt;/em&gt; event to have your program respond to the Phidget&apos;s motion.&lt;/li&gt;
&lt;li&gt;To get updates as quaternions, subscribe to the &lt;em&gt;AlgorithmData&lt;/em&gt; event instead; it carries the output of the onboard orientation algorithm (quaternion, plus heading).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The onboard algorithm is an AHRS by default because it fuses the magnetometer. Switch the Algorithm property to IMU to ignore the magnetometer in electromagnetically noisy environments. &lt;em&gt;AlgorithmMagnetometerGain&lt;/em&gt; trades sensor noise against response time.&lt;/p&gt;
&lt;p&gt;A single 1044 exposes all four channel types at once; what the ROS side does with them is next.&lt;/p&gt;
&lt;h2&gt;Bringing it up on ROS 1&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/ros-drivers/phidgets_drivers/tree/noetic/phidgets_spatial&quot;&gt;&lt;code&gt;phidgets_spatial&lt;/code&gt;&lt;/a&gt; node from &lt;a href=&quot;https://wiki.ros.org/phidgets_drivers&quot;&gt;&lt;code&gt;phidgets_drivers&lt;/code&gt;&lt;/a&gt; publishes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/imu/data_raw&lt;/code&gt; (&lt;code&gt;sensor_msgs/Imu&lt;/code&gt;): raw accelerometer and gyroscope data&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/imu/mag&lt;/code&gt; (&lt;code&gt;sensor_msgs/MagneticField&lt;/code&gt;): raw magnetometer data&lt;/li&gt;
&lt;li&gt;&lt;code&gt;imu/is_calibrated&lt;/code&gt; (&lt;code&gt;std_msgs/Bool&lt;/code&gt;): gyroscope calibration status; calibration runs automatically at startup and can be re-triggered any time via the &lt;code&gt;imu/calibrate&lt;/code&gt; service&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Prerequisites&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# IMU tools: imu_filter_madgwick and visualisation tools
sudo apt-get install ros-$ROS_DISTRO-imu-tools

# phidgets_drivers metapackage: libphidget22, phidgets_spatial, ...
sudo apt-get install ros-$ROS_DISTRO-phidgets-drivers # [!code highlight]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both are released as binaries for Noetic, so apt covers everything. Alternatively, build the &lt;a href=&quot;https://github.com/ros-drivers/phidgets_drivers/tree/noetic/phidgets_spatial&quot;&gt;noetic branch&lt;/a&gt; of &lt;code&gt;ros-drivers/phidgets_drivers&lt;/code&gt; from source in a catkin workspace.&lt;/p&gt;
&lt;h3&gt;UDEV&lt;/h3&gt;
&lt;p&gt;Linux needs a udev rule so non-root users can open the USB device. The rules file ships inside the repo at &lt;code&gt;phidgets_drivers/phidgets_api/debian/udev&lt;/code&gt;; it matches Phidgets USB devices (vendor &lt;code&gt;06c2&lt;/code&gt;) and sets &lt;code&gt;MODE=&quot;666&quot;&lt;/code&gt;. The wiki documents the installer script; from your catkin workspace:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sh src/phidgets_drivers/phidgets_api/share/setup-udev.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and enter your sudo password when prompted.&lt;/p&gt;
&lt;h3&gt;Running&lt;/h3&gt;
&lt;p&gt;Start the spatial node; the defaults connect to any Spatial Phidget (&lt;code&gt;serial: -1&lt;/code&gt;, &lt;code&gt;hub_port: 0&lt;/code&gt;) and publish with &lt;code&gt;frame_id: imu_link&lt;/code&gt;. Then run the Madgwick filter from imu-tools to fuse accelerometer, gyroscope and magnetometer into an orientation quaternion on &lt;code&gt;/imu/data&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The screenshot below shows the full chain in tmux over SSH on the Nano: nodelet manager loading &lt;code&gt;ImuFilterNodelet&lt;/code&gt; (imu_filter_madgwick), the Phidgets SPATIAL nodelet connecting, the automatic gyro calibration (it warns to keep the device still for around 2 seconds), and a &lt;code&gt;rostopic echo&lt;/code&gt; of &lt;code&gt;/imu/data&lt;/code&gt; with the quaternion in &lt;code&gt;orientation.x/y/z/w&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fphidget-imu-ros.CGgoaFyJ.png&amp;#x26;w=1277&amp;#x26;h=998&amp;#x26;f=webp&quot; alt=&quot;tmux on the Jetson Nano: the phidgets SPATIAL nodelet connecting, automatic gyro calibration and imu_filter_madgwick running&quot;&gt;
&lt;em&gt;The full chain in tmux on the Nano: gyro calibration, then the Madgwick-filtered quaternion on &lt;code&gt;/imu/data&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Note the &lt;code&gt;No compass correction params found&lt;/code&gt; line in the first screenful. That line ties the two halves of this article together:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The onboard algorithm quaternions arrive through the Phidget API&apos;s &lt;code&gt;AlgorithmData&lt;/code&gt; event, but the Noetic ROS driver publishes raw data by default and only enables onboard orientation estimation for newer hardware (&lt;code&gt;use_orientation&lt;/code&gt;, MOT0109 and up). So with a 1044, quaternions in ROS come from the external Madgwick filter, not from the board&apos;s own algorithm.&lt;/li&gt;
&lt;li&gt;The heading part of that filter depends on the magnetometer, which needs compass calibration before its bearing means anything.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Compass calibration&lt;/h2&gt;
&lt;p&gt;The magnetometer reports the sum of &lt;em&gt;all&lt;/em&gt; magnetic fields, and stationary interference from hard- and soft-iron distortion (up to ±3 gauss of it, per the user guide) skews the raw field vector. Phidgets&apos; &lt;a href=&quot;https://www.phidgets.com/docs/Compass_Calibration&quot;&gt;Compass Calibration&lt;/a&gt; guide covers the procedure, and I have had extra reason to learn it properly: this board was bought in Vietnam and now lives in Canada. The correction parameters describe the magnetic environment around the board, and mine changed continents, so a fit from one country says nothing about the other; every move means recalibrating. My workflow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Sanity-check the raw channels.&lt;/strong&gt; Board flat and at rest: the accelerometer should read ≈ 1 g on Z and ≈ 0 g on X/Y, and the gyroscope ≈ 0 °/s on all axes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Faccelerometer.BIS5qMuy.png&amp;#x26;w=545&amp;#x26;h=802&amp;#x26;f=webp&quot; alt=&quot;Accelerometer channel view with the board flat: Z-axis reads 1.009 g&quot;&gt;
&lt;em&gt;Board flat and at rest: ≈ 1 g on Z.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fgyroscope.Coj-8Cgf.png&amp;#x26;w=543&amp;#x26;h=793&amp;#x26;f=webp&quot; alt=&quot;Gyroscope channel view at rest: angular velocity near 0 deg/s on all axes&quot;&gt;
&lt;em&gt;At rest the gyroscope reads near 0 °/s on all axes.&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Look at the raw magnetic field vector.&lt;/strong&gt; The magnetometer view shows X/Y/Z in gauss plus the field direction, clearly distorted before correction.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fmagnetometer.BpNq4cMp.png&amp;#x26;w=545&amp;#x26;h=627&amp;#x26;f=webp&quot; alt=&quot;Magnetometer channel view showing the raw magnetic field vector before calibration&quot;&gt;
&lt;em&gt;Raw magnetic field vector before calibration: clearly distorted.&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Run Phidgets&apos; Compass Calibrator.&lt;/strong&gt; In the Magnetometer example, click &lt;strong&gt;Calibrate&lt;/strong&gt; to open the Compass Calibration tool. Leave &lt;em&gt;Local Field Strength&lt;/em&gt; at 1.0 (its magnitude doesn&apos;t affect the heading), pick &lt;strong&gt;3-axis&lt;/strong&gt; since the board is free to rotate in all directions, hit &lt;strong&gt;Start&lt;/strong&gt;, and slowly rotate the device away from magnetic interference until the red dots fill out as much of the sphere as possible. &lt;strong&gt;Stop&lt;/strong&gt;, and the tool fits the correction parameters: the red cloud is the raw measurements, the green one the calibrated result. Repeat until the green sphere is centred.&lt;/p&gt;
&lt;p&gt;The calibration values in the tool&apos;s text box are what the Phidget22 API consumes via &lt;code&gt;setMagnetometerCorrectionParameters&lt;/code&gt;; on most Spatials they are also saved to flash, so the calibration survives power cycles. On the ROS side, the same numbers go into the &lt;code&gt;cc_*&lt;/code&gt; parameters of the &lt;code&gt;phidgets_spatial&lt;/code&gt; node: &lt;code&gt;cc_mag_field&lt;/code&gt;, &lt;code&gt;cc_offset0&lt;/code&gt;–&lt;code&gt;cc_offset2&lt;/code&gt;, &lt;code&gt;cc_gain0&lt;/code&gt;–&lt;code&gt;cc_gain2&lt;/code&gt; and &lt;code&gt;cc_t0&lt;/code&gt;–&lt;code&gt;cc_t5&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The end result is a stable calibrated compass bearing on the Spatial channel view: bearing 239.7°, pitch −2.9°, roll 4.4° on my desk:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fall-spatial.B2OTHUL6.png&amp;#x26;w=546&amp;#x26;h=500&amp;#x26;f=webp&quot; alt=&quot;Spatial channel view with the calibrated compass bearing, pitch and roll angles&quot;&gt;
&lt;em&gt;Calibrated compass bearing on the Spatial channel view: 239.7°, pitch −2.9°, roll 4.4°.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;/imu/data&lt;/code&gt; publishing a stable quaternion and the compass correction parameters in place, the IMU is ready to feed the odometry stack.&lt;/p&gt;
&lt;h2&gt;Acknowledgement&lt;/h2&gt;
&lt;p&gt;Driver and parameter details come from the &lt;a href=&quot;https://wiki.ros.org/phidgets_drivers&quot;&gt;phidgets_drivers&lt;/a&gt; wiki and the &lt;a href=&quot;https://github.com/ros-drivers/phidgets_drivers/tree/noetic/phidgets_spatial&quot;&gt;noetic branch README&lt;/a&gt;; the calibration procedure follows Phidgets&apos; &lt;a href=&quot;https://www.phidgets.com/docs/Compass_Calibration&quot;&gt;Compass Calibration&lt;/a&gt; guide and the &lt;a href=&quot;https://www.phidgets.com/docs/1044_User_Guide&quot;&gt;1044 User Guide&lt;/a&gt;.&lt;/p&gt;</content:encoded><h:img src="/_astro/phidget-imu-ros.CGgoaFyJ.png"/><enclosure url="/_astro/phidget-imu-ros.CGgoaFyJ.png"/></item><item><title>GDB Permission Elevation with Catalina</title><link>https://tin.ng/blog/2022-04-10--gdb-permission-elevation-with-catalina</link><guid isPermaLink="true">https://tin.ng/blog/2022-04-10--gdb-permission-elevation-with-catalina</guid><description>Create a self-signed certificate, attach the debugger entitlement, and the Homebrew binary finally signs its way past taskgated</description><pubDate>Sun, 10 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;Out of the box, &lt;code&gt;gdb&lt;/code&gt; on macOS refuses to control another process: since El Capitan&apos;s hardened runtime, attaching a debugger is a privileged operation guarded by &lt;code&gt;taskgated&lt;/code&gt;, and an unsigned &lt;code&gt;gdb&lt;/code&gt; (the default Homebrew install) fails with errors like &lt;code&gt;not allowed to attach to process&lt;/code&gt;. The fix is to give the binary a code signature that carries the &lt;code&gt;com.apple.security.cs.debugger&lt;/code&gt; entitlement, backed by a self-signed certificate created in Keychain Access.&lt;/p&gt;
&lt;p&gt;Steps follow the &lt;a href=&quot;https://sourceware.org/gdb/wiki/PermissionsDarwin#preview&quot;&gt;official sourceware instruction&lt;/a&gt; and were verified on Catalina (10.15) with a Homebrew &lt;code&gt;gdb&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Create the signing certificate&lt;/h2&gt;
&lt;p&gt;In &lt;strong&gt;Keychain Access&lt;/strong&gt;, open the menu bar: &lt;em&gt;Certificate Assistant&lt;/em&gt; → &lt;em&gt;Create a Certificate...&lt;/em&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Name: &lt;code&gt;gdb-cert&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Identity Type: &lt;strong&gt;Self-Signed Root&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Certificate Type: &lt;strong&gt;Code Signing&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If possible, tick &lt;strong&gt;Let me override defaults&lt;/strong&gt; and walk through the wizard:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;Certificate Information&lt;/em&gt;: keep the defaults (Serial Number &lt;code&gt;1&lt;/code&gt;, Validity Period &lt;code&gt;365&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Continue through &lt;em&gt;Personal Information&lt;/em&gt;, &lt;em&gt;Key Pair Information&lt;/em&gt;, &lt;em&gt;Key Usage Extension&lt;/em&gt;, &lt;em&gt;Extended Key Usage Extension&lt;/em&gt;, &lt;em&gt;Basic Constraints Extension&lt;/em&gt; and &lt;em&gt;Subject Alternate Name Extension&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Specify Location for Certificate&lt;/em&gt;: select keychain &lt;strong&gt;System&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then trust the certificate for code signing: double-click &lt;code&gt;gdb-cert&lt;/code&gt; in Keychain Access, expand &lt;strong&gt;Trust&lt;/strong&gt;, and set &lt;strong&gt;Code Signing&lt;/strong&gt; to &lt;strong&gt;Always Trust&lt;/strong&gt; (&lt;code&gt;security dump-trust-settings -d&lt;/code&gt; confirms it).&lt;/p&gt;
&lt;p&gt;On error creating it with the overrides enabled, repeat the wizard and unselect &lt;em&gt;Let me override defaults&lt;/em&gt;. If &lt;code&gt;gdb-cert&lt;/code&gt; still lands in the login keychain instead of System:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Drag &lt;code&gt;gdb-cert&lt;/code&gt; (&lt;strong&gt;not&lt;/strong&gt; the two pub/sec keys next to it) to the &lt;strong&gt;System&lt;/strong&gt; keychains in the sidebar&lt;/li&gt;
&lt;li&gt;If dragging is refused, export &lt;code&gt;gdb-cert&lt;/code&gt; to a local folder, then import it back choosing &lt;strong&gt;System&lt;/strong&gt; as the keychain location&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Create entitlement.xml&lt;/h2&gt;
&lt;p&gt;The entitlement is what actually whitelists debugging; the certificate only vouches for it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;#x3C;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&amp;#x3C;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
&amp;#x3C;plist version=&quot;1.0&quot;&gt;
&amp;#x3C;dict&gt;
  &amp;#x3C;key&gt;com.apple.security.cs.debugger&amp;#x3C;/key&gt;
  &amp;#x3C;true/&gt;
&amp;#x3C;/dict&gt;
&amp;#x3C;/plist&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Sign the gdb binary&lt;/h2&gt;
&lt;p&gt;Homebrew&apos;s &lt;code&gt;gdb&lt;/code&gt; is usually a symlink into the Cellar; &lt;code&gt;$(which gdb)&lt;/code&gt; resolves it either way.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;## Stop taskgated
sudo killall taskgated

## Sign the gdb binary
## The binary might be symlinked to brew at /usr/local/Cellar/gdb/&amp;#x3C;version&gt;/bin/gdb
codesign --entitlements entitlement.xml -fs gdb-cert $(which gdb)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;taskgated&lt;/code&gt; restarts on demand, so killing it just clears the stale state; there is no daemon to re-enable manually. On macOS releases past Big Sur this refresh occasionally refuses to pick up the new signature; a reboot is the reliable fallback.&lt;/p&gt;
&lt;h2&gt;Verify the signature&lt;/h2&gt;
&lt;p&gt;Double-check the signature is intact and the entitlement actually stuck:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;codesign -vv $(which gdb)
codesign -d --entitlements :- $(which gdb)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first command should report &lt;code&gt;valid on disk&lt;/code&gt;, and the second should print the XML above, ending with &lt;code&gt;com.apple.security.cs.debugger=true&lt;/code&gt;. From here, &lt;code&gt;gdb&lt;/code&gt; attaches to processes normally; try &lt;code&gt;gdb &amp;#x3C;pid&gt;&lt;/code&gt; on a running process to confirm.&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://sourceware.org/gdb/wiki/PermissionsDarwin&quot;&gt;PermissionsDarwin&lt;/a&gt; on the official GDB wiki, the source of this procedure.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="/_astro/gdb-codesign-hero.BVQ8G_Eq.svg"/><enclosure url="/_astro/gdb-codesign-hero.BVQ8G_Eq.svg"/></item><item><title>Resize Linux Partition with LVM (via QEMU)</title><link>https://tin.ng/blog/2022-03-25--resize-linux-partition-with-lvm</link><guid isPermaLink="true">https://tin.ng/blog/2022-03-25--resize-linux-partition-with-lvm</guid><description>Grow three layers in order, the virtual disk, the partition under it, and the vgubuntu volume, without reinstalling the Ubuntu 20.04 guest</description><pubDate>Fri, 25 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;A QEMU guest ran out of disk space. The guest existed because I was trying
out &lt;a href=&quot;https://mac.getutm.app&quot;&gt;UTM&lt;/a&gt; as a lighter alternative to Parallels
Desktop and using it to learn QEMU&apos;s tooling suite properly; the full disk
turned into a good excuse to work through that stack end to end. Instead of
reinstalling, grow the disk through all three layers it is built from: the
qcow2 virtual disk, the partition on it, and the LVM logical volume holding
the filesystem. The steps below were done on an Ubuntu 20.04 mini install
using the default LVM layout, following
&lt;a href=&quot;https://www.linuxtechi.com/extend-lvm-partitions/&quot;&gt;linuxtechi&apos;s walkthrough&lt;/a&gt;
as a reference.&lt;/p&gt;
&lt;p&gt;The order matters and each layer gates the next:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Resize the virtual disk (QEMU only, optional)&lt;/li&gt;
&lt;li&gt;Resize the partition to claim the new space (GParted)&lt;/li&gt;
&lt;li&gt;Extend the logical volume and the filesystem (LVM)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;An Ubuntu guest installed on LVM. My guest names its volume group
&lt;code&gt;vgubuntu&lt;/code&gt; with a &lt;code&gt;vgubuntu-root&lt;/code&gt; logical volume; other installs use
different names (&lt;code&gt;ubuntu-vg&lt;/code&gt;/&lt;code&gt;ubuntu-lv&lt;/code&gt; on server installs), so check
yours with &lt;code&gt;vgdisplay&lt;/code&gt; and substitute throughout.&lt;/li&gt;
&lt;li&gt;A &lt;a href=&quot;https://gparted.org/livecd.php&quot;&gt;GParted&lt;/a&gt; live image to move the partition boundaries offline.&lt;/li&gt;
&lt;li&gt;Root access inside the guest for the LVM steps.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Step 1: Resize the virtual disk (QEMU)&lt;/h2&gt;
&lt;p&gt;Skip this step if the disk is physical or already big enough; it only applies to a qcow2 backed by QEMU. From the &lt;strong&gt;host&lt;/strong&gt;, while the guest is shut down:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;# Read the drive filesystem
qemu-img info ubuntu-20.04-mini.qcow2

# Add 15G to the virtual size
# NOTE this is reflected in &quot;virtual size&quot;
qemu-img resize ubuntu-20.04-mini.qcow2 +15G
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 2: Resize the partition (GParted)&lt;/h2&gt;
&lt;p&gt;Boot the guest from the GParted live image so the root filesystem is not mounted while it moves. Booting an entire live OS to drag one partition edge has always felt heavy-handed to me, but a mounted root filesystem cannot move under itself, and this is the only step in the chain with real destructive potential, so it earns the caution.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open GParted and select the top-level physical device containing both root (&lt;code&gt;/&lt;/code&gt;) and boot (&lt;code&gt;/boot&lt;/code&gt;), e.g. &lt;code&gt;/dev/sda&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Right-click the LVM partition, then resize, and drag over the unallocated space left by Step 1 so it joins the physical device.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Apply the operation. The unallocated space is now part of the partition backing the LVM physical volume, but the volume group does not use it yet.&lt;/p&gt;
&lt;h2&gt;Step 3: Extend the logical volume and filesystem&lt;/h2&gt;
&lt;p&gt;Back inside the guest, confirm the current usage and the volume group the installer created:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;# Read the filesystem current size
# Alternatively, lsblk
df -h /home/

# Read the created Volume Group
# The name (here: vgubuntu) was chosen at installation
# NOTE this requires root
vgdisplay vgubuntu
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;vgdisplay&lt;/code&gt; reports the free extent count; that is the budget &lt;code&gt;lvextend&lt;/code&gt; can spend. This is the step where the QEMU side finally pays off: the 15G added back in Step 1 has been sitting there as free extents since the partition claimed it. Grow the logical volume by the intended amount, then write the new size into the ext4 filesystem:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;# Expand 20G for the specific Volume e.g. vgubuntu-root
lvextend -L +20G /dev/mapper/vgubuntu-root

# Apply and write new partitions to LVM
resize2fs /dev/mapper/vgubuntu-root
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;resize2fs&lt;/code&gt; grows the mounted filesystem online, so no reboot or unmount is needed for this last step. Verify with &lt;code&gt;df -h /home/&lt;/code&gt; again; the size column should reflect the grown volume.&lt;/p&gt;
&lt;h2&gt;Closing&lt;/h2&gt;
&lt;p&gt;Three tools for one growing disk, and each layer only knows its own job: &lt;code&gt;qemu-img&lt;/code&gt; grows the image, GParted moves the partition edge, LVM spends the new space. The guest came back with its 15G and no reinstall, and now I know exactly where one layer hands off to the next. That part is hard to learn from a Parallels-style slider that just makes the bar bigger.&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.linuxtechi.com/extend-lvm-partitions/&quot;&gt;Extend LVM partitions&lt;/a&gt; on linuxtechi, the walkthrough these steps follow.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="/_astro/lvm-resize-hero.DIGneeGN.svg"/><enclosure url="/_astro/lvm-resize-hero.DIGneeGN.svg"/></item><item><title>Duck Hunter on a Breadboard</title><link>https://tin.ng/blog/2022-01-05--duck-hunter-on-a-breadboard</link><guid isPermaLink="true">https://tin.ng/blog/2022-01-05--duck-hunter-on-a-breadboard</guid><description>A 60-second score attack on an Arduino Mega and a tiny OLED, five big arcade buttons, and honestly way too much jumper wire</description><pubDate>Wed, 05 Jan 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Video from &apos;../../../components/common/Video.astro&apos;
import prototype from &apos;./duck-hunt-prototype.mp4&apos;&lt;/p&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;This is a little Duck Hunt game running on an Arduino, an OLED screen, and five big arcade buttons. The original is &lt;a href=&quot;https://www.reddit.com/r/arduino/comments/fdhb28/my_new_project_esp_game_on_oled_programed_using/&quot;&gt;Tiny Duck Hunt&lt;/a&gt; by Danko, who built it for a NodeMCU board, and it even got a &lt;a href=&quot;https://hackaday.com/2020/03/08/tiny-duck-hunt-looks-like-big-fun/&quot;&gt;write-up on Hackaday&lt;/a&gt;. Danko shared the code in that thread, so I ported it to a Mega 2560, wired up my own circuit with buttons, and had something playable in an afternoon.&lt;/p&gt;
&lt;p&gt;The whole thing is a 60-second score attack: birds fly across a small screen, you move a crosshair with four buttons, and you press the big red one to shoot.&lt;/p&gt;
&lt;h2&gt;The circuit&lt;/h2&gt;
&lt;p&gt;Everything lives on one breadboard:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A 0.96 inch OLED screen on I2C. It only needs four wires: power, ground, and the two data lines labeled SCK and SDA on the board.&lt;/li&gt;
&lt;li&gt;Four direction buttons on the left, in the same layout as a D-pad: up, down, left, right.&lt;/li&gt;
&lt;li&gt;One big red button on the right for shooting.&lt;/li&gt;
&lt;li&gt;Each button gets a pull-down resistor, so the pin reads low while nobody is touching it and jumps high when the button is pressed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The wiring was honestly the slowest part. Five buttons&apos; worth of jumper wires, all crossing each other, makes the breadboard look messier than it is. But when the screen lit up with &quot;DUCK HUNT, Press fire to start!&quot; it all felt worth it.&lt;/p&gt;
&lt;h2&gt;The gameplay&lt;/h2&gt;
&lt;p&gt;Press fire, wait through the loading bar, and you get 60 seconds. Birds of different sizes fly across the screen, and each size is worth a different amount of points. The score and the clock sit at the top so you can see both fall apart in real time.&lt;/p&gt;
&lt;p&gt;In the clip above I got to about 150, mostly by camping the crosshair in the middle and spamming fire. There is no dog in this version, which means nothing laughs at you when you miss. That alone makes it better than the NES original.&lt;/p&gt;
&lt;h2&gt;Porting it to Arduino&lt;/h2&gt;
&lt;p&gt;The original ran on a NodeMCU, an ESP8266 board with 3.3 V logic and dedicated I2C pins. I moved it to a Mega 2560, and the RAM on that board is the reason it is a Mega and not an Uno: the SSD1306 driver keeps a full 128x64 frame buffer in memory, which is 1 KB before the game stores anything, and the ATmega2560&apos;s 8 KB of SRAM absorbs that along with the sprites and game state without sweating every byte.&lt;/p&gt;
&lt;p&gt;Rendering on this class of hardware was the part I was least ready for. Embedded systems classes teach you to count bytes, but the contrast with the single-board computers I usually reach for made it stick: on a Raspberry Pi, drawing is a solved problem, with a GPU and a whole OS handing you a framebuffer you never think about. On a microcontroller, the frame buffer is a kilobyte of your SRAM.&lt;/p&gt;
&lt;p&gt;The display library became Adafruit&apos;s &lt;a href=&quot;https://github.com/adafruit/Adafruit_SSD1306&quot;&gt;SSD1306 driver&lt;/a&gt; with its &lt;a href=&quot;https://github.com/adafruit/Adafruit-GFX-Library&quot;&gt;GFX&lt;/a&gt; graphics API, and the wiring followed the board. The NodeMCU has fixed I2C pins; on the Mega, SDA and SCL live on pins 20 and 21, so the whole bus moved. The OLED took some persuading after that: the sketch needed the right I2C address, which is 0x3C or 0x3D depending on the module, and the pull-ups on SDA and SCL needed sorting before the bus would talk.&lt;/p&gt;
&lt;p&gt;With the screen up, the port became about holding the frame rate. The ESP8266 pushes frames from an 80 MHz core; the Mega runs at 16 MHz, a fifth of the speed, and the full-buffer redraw showed it. The bird sprites moved into flash with &lt;code&gt;PROGMEM&lt;/code&gt; instead of living in RAM, and the redraw strategy changed so each loop only rewrites what it has to. The game logic carried over as is; once the screen showed something, the buttons were five digital reads, and the whole thing ran without any other changes worth writing home about.&lt;/p&gt;
&lt;p&gt;The animation practice is heading somewhere, too. I want to build an automated water pump system for my uncle, and the tank level should show up on its small display as something friendly and uncluttered, not a debug console. A game loop that fights for frame rate on a 128x64 panel turns out to be good training for that: cheap redraws on a small screen.&lt;/p&gt;
&lt;h2&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.reddit.com/r/arduino/comments/fdhb28/my_new_project_esp_game_on_oled_programed_using/&quot;&gt;Tiny Duck Hunt on r/arduino&lt;/a&gt;, the original by Danko&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hackaday.com/2020/03/08/tiny-duck-hunt-looks-like-big-fun/&quot;&gt;Tiny Duck Hunt Looks Like Big Fun&lt;/a&gt;, the Hackaday write-up&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="/_astro/duck-hunt-title.BglqP4q4.jpg"/><enclosure url="/_astro/duck-hunt-title.BglqP4q4.jpg"/></item><item><title>San Francisco Traffic Density with kepler.gl</title><link>https://tin.ng/blog/2021-11-18--san-francisco-traffic-density-kepler-gl</link><guid isPermaLink="true">https://tin.ng/blog/2021-11-18--san-francisco-traffic-density-kepler-gl</guid><description>Uber Movement data animates the city&apos;s streets hour by hour, then the same toolkit maps my own attempt at New York crash statistics</description><pubDate>Thu, 18 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Video from &apos;../../../components/common/Video.astro&apos;
import traffic from &apos;./traffic-density-animation.mp4&apos;&lt;/p&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;A short one: while living in San Francisco briefly, I set up and ran &lt;a href=&quot;https://kepler.gl&quot;&gt;kepler.gl&lt;/a&gt;, Uber&apos;s open-source geospatial explorer, to plot traffic density across the city by hour of day. The recording and the data below are Uber&apos;s example material, nothing of mine. I later ran the same toolkit myself on New York City; that attempt is at the bottom.&lt;/p&gt;
&lt;h2&gt;The toolkit&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/keplergl/kepler.gl&quot;&gt;kepler.gl&lt;/a&gt; is Uber&apos;s open-source tool for large-scale geospatial visualization, built on WebGL through &lt;a href=&quot;https://deck.gl&quot;&gt;deck.gl&lt;/a&gt;. It runs straight in the browser at &lt;a href=&quot;https://kepler.gl&quot;&gt;kepler.gl&lt;/a&gt;, or self-hosted from the repo.&lt;/p&gt;
&lt;p&gt;The workflow that produces something like the animation above takes three moves, following &lt;a href=&quot;https://www.uber.com/us/en/blog/kepler-data-visualization-traffic-safety/&quot;&gt;Uber&apos;s own walkthrough&lt;/a&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Load a dataset with street segments and an hour-of-day field, no account or install needed&lt;/li&gt;
&lt;li&gt;Color the layer by the density/speed column instead of a single color&lt;/li&gt;
&lt;li&gt;Add a filter on the hour field, which turns into the playback control at the bottom, animating the map across the day&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The dataset behind maps like this one is the speeds release of &lt;a href=&quot;https://movement.uber.com&quot;&gt;Uber Movement&lt;/a&gt;, the program where Uber published aggregated travel-time and speed data for cities including San Francisco. The downloadable files were CSVs named &lt;code&gt;movement-speeds-quarterly-by-hod-san-francisco-&amp;#x3C;year&gt;-Q&amp;#x3C;n&gt;.csv&lt;/code&gt;, speeds per street segment by hour of day (&quot;hod&quot;), with an hourly variant per month. Downloads went through &lt;code&gt;movement.uber.com&lt;/code&gt;&apos;s &lt;code&gt;api/download/url&lt;/code&gt; endpoint, which issued signed S3 links per city and quarter; Uber&apos;s &lt;a href=&quot;https://www.npmjs.com/package/movement-data-toolkit&quot;&gt;movement-data-toolkit&lt;/a&gt; (&lt;code&gt;mdt&lt;/code&gt;) automates exactly that, and can join the speeds back to OpenStreetMap geometries for GeoJSON export. Movement shut down and the endpoint is dead now, so archived copies and mirrors are the only way back to these files.&lt;/p&gt;
&lt;h2&gt;What the playback shows&lt;/h2&gt;
&lt;p&gt;Worth calling out what the animation makes obvious in a way a static map cannot: before dawn only the arterials glow; density builds along the Market Street corridor and the SoMa grid through the morning peak; the whole downtown grid saturates through the afternoon and evening rush; and after midnight it collapses back to a handful of corridors.&lt;/p&gt;
&lt;p&gt;This is anecdotal, but the playback matches the city I walked. I lived in San Francisco briefly, and the map renders the rhythm I remember: the morning crawl along Market and the SoMa grid, the downtown evening rush, the quiet after midnight. Seeing the map agree with my own commute is what sold me on the format.&lt;/p&gt;
&lt;h2&gt;My own attempt: New York City&lt;/h2&gt;
&lt;p&gt;The same toolkit, pointed at New York City. In this 29-second recording, Movement&apos;s street speed gradients color the roads while government crash data sits on top: NHTSA&apos;s FARS fatal crash records and the NYPD Motor Vehicle Collisions dataset, the combination from Uber&apos;s traffic-safety walkthrough, aimed at traffic-related deaths and better urban planning in New York City.&lt;/p&gt;
&lt;p&gt;The reaction when I showed this around was the interesting part. My survey participants were skeptical at first; the map reads as obvious once rendered, yet the same people would never pull crash statistics themselves. That is the value I see in kepler.gl: raw government data with a steep learning curve, made into something anyone can scrub through.&lt;/p&gt;
&lt;h2&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/keplergl/kepler.gl&quot;&gt;kepler.gl&lt;/a&gt;, the toolkit&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.uber.com/us/en/blog/kepler-data-visualization-traffic-safety/&quot;&gt;Visualizing Traffic Safety with Uber Movement Data and Kepler.gl&lt;/a&gt;, Uber&apos;s walkthrough&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/movement-data-toolkit&quot;&gt;movement-data-toolkit&lt;/a&gt;, Uber&apos;s CLI for the Movement data downloads&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://deck.gl&quot;&gt;deck.gl&lt;/a&gt;, the WebGL layers underneath&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="/_astro/traffic-density-hero.Bx97YFwP.jpg"/><enclosure url="/_astro/traffic-density-hero.Bx97YFwP.jpg"/></item><item><title>Alfred, the Powerpack, and a workflow for everything</title><link>https://tin.ng/blog/2021-09-13--alfred-powerpack-workflow</link><guid isPermaLink="true">https://tin.ng/blog/2021-09-13--alfred-powerpack-workflow</guid><description>cmd+space stops being plain Spotlight and starts converting, translating, and expanding text, with nineteen community add-ons riding Dropbox to every Mac</description><pubDate>Mon, 13 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;cmd+space on my Macs opens Alfred, not Spotlight. That swap is small, but it is the entry point to the rest of
this post: Alfred with the
&lt;a href=&quot;https://www.alfredapp.com/shop/&quot;&gt;Powerpack&lt;/a&gt; license, nineteen community
workflows, text expansion snippets, and the entire settings bundle synced
through Dropbox.&lt;/p&gt;
&lt;p&gt;This is the third leg of the keyboard-first setup documented here. The
&lt;a href=&quot;/blog/2021-09-06--karabiner-yabai-tiling-macos&quot;&gt;Karabiner and yabai post&lt;/a&gt;
covers keys and windows; Alfred covers everything that starts with &quot;I want
to&quot;: launch, search, convert, translate, expand, and reach into the homelab
without opening a browser.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./alfred-hero.svg&quot; alt=&quot;Alfred window in the modern dark theme&quot;&gt;&lt;/p&gt;
&lt;h2&gt;The bundle that follows me&lt;/h2&gt;
&lt;p&gt;The whole &lt;code&gt;Alfred.alfredpreferences&lt;/code&gt; bundle lives in
&lt;code&gt;~/Dropbox/Apps/Alfred&lt;/code&gt;, set as the sync folder in Alfred&apos;s preferences.
Every workflow, snippet collection, theme, and setting rides Dropbox, so a
new Mac is one sign-in away from an identical launcher. Bundle edits made on
one machine appear on the others, and the folder doubles as an implicit
backup. Sync itself is a Powerpack feature; without it the settings would be
trapped on one machine.&lt;/p&gt;
&lt;p&gt;Alfred is only the most visible thing in that Dropbox folder. &lt;a href=&quot;https://github.com/lra/mackup&quot;&gt;Mackup&lt;/a&gt;
archives the rest: it moves the config files it knows about into the
Dropbox storage folder and symlinks them back into &lt;code&gt;$HOME&lt;/code&gt;, so the
preferences scattered across &lt;code&gt;~/.config&lt;/code&gt; (and the rest of the home folder)
end up versioned in the same place as the Alfred bundle. The two layers
overlap on purpose; the launcher&apos;s settings survive either path alone.&lt;/p&gt;
&lt;h2&gt;What the Powerpack unlocks&lt;/h2&gt;
&lt;p&gt;Alfred free is already a good Spotlight replacement. The Powerpack is a
one-time license that unlocks the parts this post actually leans on: the
workflow system (nineteen installed, catalogued below), auto-expanding text
snippets with a snippet viewer, custom themes (the launcher wears a
community one called modern dark, 600 px wide, corner roundness 15), and
the Dropbox sync from the previous section.&lt;/p&gt;
&lt;p&gt;Major upgrades ask for a new license; the years between them make that an
easy trade.&lt;/p&gt;
&lt;h2&gt;The launcher surface&lt;/h2&gt;
&lt;p&gt;Three settings shape daily use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Search scope: beyond /Applications, the default results scan
&lt;code&gt;~/Library/CloudStorage&lt;/code&gt; and iCloud Drive, so Dropbox documents and
Obsidian notes show up next to apps without a separate search.&lt;/li&gt;
&lt;li&gt;Terminal: the terminal feature is wired to iTerm through
&lt;a href=&quot;https://github.com/vitorgalvao/custom-alfred-iterm-scripts&quot;&gt;vitorgalvao&apos;s custom-alfred-iterm-scripts&lt;/a&gt;,
so &lt;code&gt;&gt;&lt;/code&gt; runs a command in iTerm, reusing the current tab.&lt;/li&gt;
&lt;li&gt;Clipboard history: text, files, and images all land in the history,
searchable with a shortcut when the thing I copied five minutes ago
mattered more than I thought.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The workflows&lt;/h2&gt;
&lt;p&gt;Nineteen of them, all community builds, grouped by what they do for me:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Into the homelab:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;| Workflow | By | Keyword | What it does |
|---|---|---|---|
| Seerr | notjosh | &lt;code&gt;seerr&lt;/code&gt; | Search and request movies and TV on the Jellyseerr instance from the &lt;a href=&quot;/blog/2025-08-31--proxmox-9&quot;&gt;homelab post&lt;/a&gt;, talking to Sonarr and Radarr on the way |
| atop | Chris Grieser | &lt;code&gt;top&lt;/code&gt; | Process and system monitoring without leaving the launcher, plus &lt;code&gt;net&lt;/code&gt;, &lt;code&gt;dns&lt;/code&gt;, &lt;code&gt;blue&lt;/code&gt;, &lt;code&gt;vol&lt;/code&gt; |
| WHOIS Lookup | FireFingers21 | &lt;code&gt;whois&lt;/code&gt; | Domain lookups before buying one |
| Speedtest | Patrick Sy | &lt;code&gt;speedtest&lt;/code&gt; | Internet speed without a browser tab |
| About Mac | Alan He | &lt;code&gt;about&lt;/code&gt; | Machine details on demand |&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Developer:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;| Workflow | By | Keyword | What it does |
|---|---|---|---|
| String multitool | Yuri Mazursky | &lt;code&gt;str&lt;/code&gt; | Encode, decode, hash, and transform strings |
| AppID | Alan He | &lt;code&gt;appid&lt;/code&gt; | Bundle IDs and versions of installed apps |
| OCR | Alan He | &lt;code&gt;ocr&lt;/code&gt; | Screenshot region to clipboard as text |
| Type Clipboard | Ramiro Araujo | &lt;code&gt;typeclipboard&lt;/code&gt; | Types the clipboard where pasting is impractical, remote desktops and VMs included |
| Menu Bar Search | Benzi Ahamed | &lt;code&gt;menu&lt;/code&gt; | Search menu items of the frontmost app |
| QResolve | Patrick Sy | &lt;code&gt;qrr&lt;/code&gt; | Open the link inside a QR code on screen |&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Security:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;| Workflow | By | Keyword | What it does |
|---|---|---|---|
| Bitwarden Accelerator | Andy Rosen | &lt;code&gt;bw&lt;/code&gt; | Front end for the Bitwarden CLI, which points at the self-hosted vault |&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Everything else:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;| Workflow | By | Keyword | What it does |
|---|---|---|---|
| Currency Converter | Vitor Galvao | &lt;code&gt;curr&lt;/code&gt; | Currencies with live rates |
| World Clock | Fede Calendino | &lt;code&gt;tz&lt;/code&gt; | Time zones for the people I call |
| Color Picker | Patrick Sy | &lt;code&gt;cp&lt;/code&gt; | Hex, RGB, HSL from anywhere |
| Share with AirDrop | Vitor Galvao | | Files, text, and URLs out via AirDrop |
| Words With | Vitor Galvao | &lt;code&gt;wwc&lt;/code&gt; | Words containing a pattern, for crossword emergencies |
| Quick Translate | nkmr-jp | | Translate and paste back |
| DeepL Translate | Yoichiro Hasebe | &lt;code&gt;deepl&lt;/code&gt; | DeepL translation with usage stats |&lt;/p&gt;
&lt;p&gt;The two that get the most use are the ones that collapse a round trip. Seerr
turns &quot;we should watch that&quot; into a request before the thought finishes;
Bitwarden Accelerator means credentials arrive in the clipboard straight
from the self-hosted vault, keyed by a CLI I already trust.&lt;/p&gt;
&lt;h2&gt;Snippets&lt;/h2&gt;
&lt;p&gt;One collection lives in the bundle. &lt;strong&gt;Shorts&lt;/strong&gt; holds auto-expanding
abbreviations for the things I type daily, the github, reddit, and Hacker
News searches among them. One line I deliberately do not cross: secrets
never live in snippets. Plain-text expansion is the wrong vault for
passwords, keys, and one-time codes; anything sensitive goes through the
Bitwarden workflow above, where it is stored encrypted and arrives in the
clipboard only on demand.&lt;/p&gt;
&lt;p&gt;One number ties this post to the
&lt;a href=&quot;/blog/2021-09-06--karabiner-yabai-tiling-macos&quot;&gt;Karabiner setup&lt;/a&gt;: the
snippet viewer opens on command+control+option+shift+V, the same
hyper key + V chord from that post.&lt;/p&gt;
&lt;h2&gt;Closing&lt;/h2&gt;
&lt;p&gt;The division of labor on this Mac: Karabiner handles the keys, yabai handles the
windows, Alfred handles the launching. cmd+space is how every one of the
nineteen things above starts, and the Powerpack is the one-time purchase
behind it.&lt;/p&gt;</content:encoded><h:img src="/_astro/alfred-hero.B5pDJuw1.svg"/><enclosure url="/_astro/alfred-hero.B5pDJuw1.svg"/></item><item><title>A keyboard-first macOS with Karabiner-Elements and yabai</title><link>https://tin.ng/blog/2021-09-06--karabiner-yabai-tiling-macos</link><guid isPermaLink="true">https://tin.ng/blog/2021-09-06--karabiner-yabai-tiling-macos</guid><description>Every window command stays on the home row through a caps lock hyper key, chords, and vi mode, with one config language to debug</description><pubDate>Mon, 06 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;My Mac has no window management muscle memory that involves the mouse. The
tiling window manager is &lt;a href=&quot;https://github.com/koekeishiya/yabai&quot;&gt;yabai&lt;/a&gt;
in BSP mode, and every command reaches it through
&lt;a href=&quot;https://github.com/pqrs-org/Karabiner-Elements&quot;&gt;Karabiner-Elements&lt;/a&gt;.
Most yabai setups pair the WM with skhd for hotkeys; mine does not. Karabiner
is the only hotkey layer, which means one config language, one rule system,
and one place to look when a key stops doing what I expect.&lt;/p&gt;
&lt;p&gt;The setup replaced &lt;a href=&quot;https://folivora.ai&quot;&gt;BetterTouchTool&lt;/a&gt;, which used to own
my window snapping and custom shortcuts, and it retired the scripting layers
from earlier experiments: Hammerspoon&apos;s Lua engine and Übersicht. The rest
of this post is the setup as it stands: the hyper key, the vi and mouse
modes, and the full key map between Karabiner and yabai.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./karabiner-yabai-layers.svg&quot; alt=&quot;From keycaps to window commands&quot;&gt;&lt;/p&gt;
&lt;h2&gt;The complex_modifications layer&lt;/h2&gt;
&lt;p&gt;All the interesting behavior lives in
&lt;code&gt;~/.config/karabiner/assets/complex_modifications/*.json&lt;/code&gt;: twelve
hand-edited files, each a self-contained rule bundle that the Karabiner GUI
imports. Edits here survive every GUI rewrite.&lt;/p&gt;
&lt;p&gt;Per-device settings handle the smaller jobs alongside the rules. Two external
keyboards swap &lt;code&gt;command&lt;/code&gt; and &lt;code&gt;option&lt;/code&gt;, because both are Windows-layout
boards and muscle memory should not care which machine I am typing on. On
the laptop, the built-in keyboard disables itself whenever that external
board is connected. Four fn keys get back their macOS duties (&lt;code&gt;mission control&lt;/code&gt;, &lt;code&gt;launchpad&lt;/code&gt;, and the two illumination keys) for boards that ship
without a media row.&lt;/p&gt;
&lt;h2&gt;The hyper key&lt;/h2&gt;
&lt;p&gt;The anchor of the whole setup is caps lock. When pressed with any other key
it becomes hyper: &lt;code&gt;command+control+option+shift&lt;/code&gt; all at once. That single
decision turns one dead key into a prefix with no competition, since no
macOS app binds all four modifiers.&lt;/p&gt;
&lt;p&gt;The rule comes from the &lt;a href=&quot;https://ke-complex-modifications.pqrs.org/#hyper_key&quot;&gt;Karabiner complex modifications
library&lt;/a&gt;. Real caps
lock did not die; it moved: &lt;code&gt;shift+caps_lock&lt;/code&gt; stays actual caps lock, which
I press roughly never, and
caps lock alone still holds caps lock, which the app launcher relies on for
chording.&lt;/p&gt;
&lt;p&gt;The hyper layer launches apps straight from the keyboard:&lt;/p&gt;
&lt;p&gt;| Key | Opens | Key | Opens |
|---|---|---|---|
| t | iTerm | c | Calendar |
| s | Safari | a | Activity Monitor |
| f | Firefox | d | Disk Utility |
| v | VS Code | i | Music |
| m | Spark | 1 2 3 | OneNote, Word, Excel |&lt;/p&gt;
&lt;p&gt;One subtlety: hyper is exactly the chord macOS uses for its hidden system
diagnostics, so a fumbled launcher key can pop a wifi diagnostic utility in
the middle of a demo. Three rules defuse that by remapping the dangerous
chords to harmless keys:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{ &quot;from&quot;: { &quot;key_code&quot;: &quot;period&quot;,
            &quot;modifiers&quot;: { &quot;mandatory&quot;: [&quot;command&quot;, &quot;shift&quot;, &quot;option&quot;, &quot;control&quot;] } },
  &quot;to&quot;: [ { &quot;key_code&quot;: &quot;f19&quot; } ] }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The comma and &lt;code&gt;w&lt;/code&gt; variants map to F18 and F17 the same way. Function keys
themselves get help too: &lt;code&gt;fn+1&lt;/code&gt; through &lt;code&gt;fn+12&lt;/code&gt; emit F1 through F12 for the
boards that lack a function row, and F3 through F6 remap to mission
control, launchpad, and keyboard illumination.&lt;/p&gt;
&lt;h2&gt;Escape, quit, and punctuation&lt;/h2&gt;
&lt;p&gt;Three small rules earn their keep daily:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;jk escapes.&lt;/strong&gt; Pressing &lt;code&gt;j&lt;/code&gt; and &lt;code&gt;k&lt;/code&gt; within 200 ms of each other, in any
order, emits Escape. The hands almost never leave the home row, in the
editor or anywhere else.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Esc is the backtick key.&lt;/strong&gt; Escape with any modifier passes through as
backtick with that modifier: &lt;code&gt;command+esc&lt;/code&gt; cycles windows (command+backtick),
&lt;code&gt;command+shift+esc&lt;/code&gt; cycles in reverse, and plain &lt;code&gt;shift+esc&lt;/code&gt; types a
tilde. Escape sits in a better spot than the real backtick on most boards.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;command-q needs a hold.&lt;/strong&gt; The quit shortcut requires holding command-q
briefly before the app quits. Between the command/option swap on external
boards and how often I mangle chords, accidental quits were a weekly
event; now they are impossible.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And one for the typists: semicolon and colon swap characters. Unshifted
semicolon types &lt;code&gt;:&lt;/code&gt;, shifted types &lt;code&gt;;&lt;/code&gt;. Code likes colons far more than
prose likes semicolons, so the common character gets the home position.&lt;/p&gt;
&lt;h2&gt;Vi mode and mouse keys&lt;/h2&gt;
&lt;p&gt;Two presets from the &lt;a href=&quot;https://ke-complex-modifications.pqrs.org/#vi_mode&quot;&gt;same
library&lt;/a&gt; handle the
cases where a key cannot just emit another key: Karabiner variables. In the
Vi Mode rule, &lt;code&gt;F+j&lt;/code&gt; pressed together latches vi mode, and until
it clears, &lt;code&gt;hjkl&lt;/code&gt; move the cursor, with variants for word-wise option-arrows
and &lt;code&gt;control+a&lt;/code&gt;/&lt;code&gt;control+e&lt;/code&gt;. A companion chord latches visual mode, where
the same movements carry shift and extend a selection.&lt;/p&gt;
&lt;p&gt;Mouse Keys Mode v4 is the same idea for the pointer, using &lt;code&gt;d&lt;/code&gt; as the mode
key: &lt;code&gt;d+hjkl&lt;/code&gt; moves the cursor, &lt;code&gt;d+u&lt;/code&gt;/&lt;code&gt;d+i&lt;/code&gt;/&lt;code&gt;d+o&lt;/code&gt; click left, middle, and
right, &lt;code&gt;d+s&lt;/code&gt; toggles scroll wheel mode, and &lt;code&gt;d+g&lt;/code&gt;/&lt;code&gt;d+a&lt;/code&gt; drop or raise the
speed to 0.4x and 2x. It rarely replaces the mouse, but for the occasional
trackpad-unreachable click while my hands are on the keyboard, it is
exactly right.&lt;/p&gt;
&lt;h2&gt;Karabiner as the yabai hotkey layer&lt;/h2&gt;
&lt;p&gt;Here is the whole trick in one manipulator: a complex modification rule
whose output is a shell command.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;type&quot;: &quot;basic&quot;,
  &quot;from&quot;: {
    &quot;key_code&quot;: &quot;h&quot;,
    &quot;modifiers&quot;: { &quot;mandatory&quot;: [&quot;option&quot;] }
  },
  &quot;to&quot;: [
    { &quot;shell_command&quot;: &quot;/opt/homebrew/bin/yabai -m window --focus west&quot; }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Option-h focuses the window to the west. Sixteen rules of this shape,
grouped in one &lt;code&gt;yabai-karabiner.json&lt;/code&gt; file, cover every window operation I
use:&lt;/p&gt;
&lt;p&gt;| Keys | yabai command |
|---|---|
| opt + h j k l | focus west / south / north / east |
| opt + p / n | focus previous / next, stack-aware with fallbacks |
| opt + shift + p / n | focus within the stack |
| opt + shift + h j k l | swap with the window in that direction |
| cmd + shift + h j k l | warp the window in that direction |
| opt + shift + w a s d | grow the window |
| cmd + shift + w a s d | shrink the window |
| opt + ctrl + arrows | insert west / south / north / east |
| opt + ctrl + h j k l | stack in that direction |
| opt + r / x / y | rotate 90, mirror y-axis, mirror x-axis |
| opt + d / f | zoom-parent / zoom-fullscreen |
| opt + shift + f | native fullscreen |
| opt + cmd + 0 | balance the tree |
| opt + ctrl + space | toggle float |
| shift + ctrl + z / x / c | send window to previous / recent / next space |
| shift + ctrl + 1..0 | send window to space 1 through 10 |&lt;/p&gt;
&lt;p&gt;The focus rules deserve a note. &lt;code&gt;opt+n&lt;/code&gt; runs
&lt;code&gt;yabai -m window --focus stack.next || yabai -m window --focus next || yabai -m window --focus first&lt;/code&gt;, so the same key walks a stack, then the
flat layout, then wraps around. Stacks stop being a special case.&lt;/p&gt;
&lt;p&gt;On the yabai side, &lt;code&gt;~/.config/yabai/yabairc&lt;/code&gt; is deliberately boring: BSP
layout, a 6 px gap with generous top and bottom padding, &lt;code&gt;window_border on&lt;/code&gt;
at 6 px so the active window is obvious, &lt;code&gt;auto_balance on&lt;/code&gt;, and ctrl-drag
for move, resize, and stack-by-dropping. The bulk of the file is a long
&lt;code&gt;manage=off&lt;/code&gt; list: menu-bar apps, dialogs, utilities, picture-in-picture
windows, anything that tiles badly stays floating, below everything else.
There are commented-out rules that used to pin apps to numbered spaces; the
space assignments now live entirely in my head and the shift+ctrl bindings.&lt;/p&gt;
&lt;h2&gt;Costs and gotchas&lt;/h2&gt;
&lt;p&gt;Every keypress in these rules spawns a shell. At human key rates that is
invisible, but it is worth knowing that each option-h is a process, not a
signal. The hand-edited rule files in &lt;code&gt;assets/&lt;/code&gt; are the safe layer to edit;
the GUI only records that it imported them. Nothing in this stack needs a
scripting engine: Hammerspoon and Übersicht from earlier setups are gone,
because Karabiner already covers both the key remapping and the command
dispatch.&lt;/p&gt;
&lt;p&gt;One thing the move gave up: BetterTouchTool&apos;s customization of the
MacBook&apos;s Touch Bar, the strip above the keyboard. I kept a quick compile
button for Xcode there and used it plenty; Karabiner cannot touch that
hardware, so the bar went back to Apple&apos;s defaults.&lt;/p&gt;
&lt;h2&gt;Closing&lt;/h2&gt;
&lt;p&gt;The practical change is that my hands stay on the home row from login to
shutdown. Window arrangement used to mean trackpad grabs and drag-to-snap;
accidental quits were routine; now both are gone, and window management is
a folder of plain JSON I can actually read. Moving over from
BetterTouchTool took one long evening, and it has paid that back every
workday since.&lt;/p&gt;</content:encoded><h:img src="/_astro/karabiner-yabai-hero.BOxEqQg6.svg"/><enclosure url="/_astro/karabiner-yabai-hero.BOxEqQg6.svg"/></item><item><title>GitHub best practices for collaboration</title><link>https://tin.ng/blog/2021-04-18--github-workflow-best-practices-for-collaboration</link><guid isPermaLink="true">https://tin.ng/blog/2021-04-18--github-workflow-best-practices-for-collaboration</guid><description>A source code control workflow derived from a 6-people team</description><pubDate>Sun, 18 Apr 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;
import { Collapse } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h1&gt;How to follow this guide?&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;#i-first-time&quot;&gt;I. First Time&lt;/a&gt;: When you first setup local development. You only have to &lt;strong&gt;do it once&lt;/strong&gt; and it also contain useful knowledge on why this approach is better than any alternative.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;#ii-development-routine&quot;&gt;II. Development Routine&lt;/a&gt;: What you normally do as a stand-alone developer. This should go in-hands with next section &lt;em&gt;Collaboration&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;#iii-collaboration&quot;&gt;III. Collaboration&lt;/a&gt;: Now it is not just you anymore. This section provides standard solution in resolving conflict of changes.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;#iv-faq&quot;&gt;IV. FAQ&lt;/a&gt;: Frequently Asked Questions pointing to appropriate section and some advanced/detailed answers&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;I. First Time&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;Fork the organisation&apos;s repo &lt;strong&gt;onerch/OneRCH&lt;/strong&gt; via the web interface&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/40393525/95691108-d70b0600-0c46-11eb-9cb2-96908052db61.gif&quot; alt=&quot;First Time Fork&quot; title=&quot;Fork the organisation&amp;#x27;s repo via web interface&quot;&gt;&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Clone your fork to local drive&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://gist.githubusercontent.com/tinng81/8716791851355f88ab77de0fc7fcacc7/raw/dbe4f13d2be640faf4077843c827f95552aff171/first-time-clone.svg&quot; alt=&quot;First Time Clone&quot; title=&quot;Create New Folder and Clone your Fork&quot;&gt;&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Verify your &lt;em&gt;local&lt;/em&gt; clone status and configured remote&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Normally this would resulted in your remote being the source you cloned, i.e. &lt;strong&gt;&amp;#x3C;username&gt; /OneRCH&lt;/strong&gt; or &lt;strong&gt;onerch/OneRCH&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://gist.githubusercontent.com/tinng81/8716791851355f88ab77de0fc7fcacc7/raw/dbe4f13d2be640faf4077843c827f95552aff171/first-time-status.svg&quot; alt=&quot;First Time Status&quot; title=&quot;Check local repo status and its tracked remote&quot;&gt;&lt;/p&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Add &lt;strong&gt;remote&lt;/strong&gt; &lt;code&gt;upstream&lt;/code&gt; from the organisation&apos;s repo&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;git remote add upstream https://&amp;#x3C;username&gt;@github.com/onerch/OneRCH.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://gist.githubusercontent.com/tinng81/8716791851355f88ab77de0fc7fcacc7/raw/dbe4f13d2be640faf4077843c827f95552aff171/first-time-upstream.svg&quot; alt=&quot;First Time Add Upstream&quot; title=&quot;Add the Upstream remote to your config&quot;&gt;&lt;/p&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Fetch latest updates from the organisation&apos;s repo&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At this stage &lt;strong&gt;your history is not the latest&lt;/strong&gt;, some people might pushed their changes to remote &lt;code&gt;upstream&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;git fetch upstream
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://gist.githubusercontent.com/tinng81/8716791851355f88ab77de0fc7fcacc7/raw/dbe4f13d2be640faf4077843c827f95552aff171/first-time-fetch.svg&quot; alt=&quot;First Time Fetch Upstream&quot; title=&quot;Fetch updates from the Upstream remote to your local repo&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;II. Development Routine&lt;/h1&gt;
&lt;p&gt;The very first thing before/after your development is to include any changes from &lt;strong&gt;upstream&lt;/strong&gt; remote.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Apply updates to your local branch&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;git checkout --track origin/developing # For first time checkout
git fetch upstream
git checkout developing
git merge upstream/developing
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://gist.githubusercontent.com/tinng81/8716791851355f88ab77de0fc7fcacc7/raw/dbe4f13d2be640faf4077843c827f95552aff171/dev-routine-apply-changes.svg&quot; alt=&quot;Development Routine apply changes&quot; title=&quot;Apply changes with a linear history with Fast-forward method&quot;&gt;&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Branching for development&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once updated any changes in the &lt;strong&gt;base branch&lt;/strong&gt; to derive from for development, start checkout new &lt;strong&gt;feature branch&lt;/strong&gt; from &lt;strong&gt;base branch&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;git branch --verbose --all
git checkout -b feat-large-feature developing
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://gist.githubusercontent.com/tinng81/8716791851355f88ab77de0fc7fcacc7/raw/dbe4f13d2be640faf4077843c827f95552aff171/dev-routine-branching.svg&quot; alt=&quot;Development Routine branching&quot; title=&quot;Create new branch for new feature based on the latest developing branch&quot;&gt;&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Create Pull Request to merge to organisation&apos;s branch&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/40393525/95691109-d8d4c980-0c46-11eb-90bd-4d5e5c77da65.gif&quot; alt=&quot;Development Routine Pull Request&quot; title=&quot;Create a Pull Request from branches in forked repo to the organisation&amp;#x27;s repo&quot;&gt;&lt;/p&gt;
&lt;p&gt;There are several methods to close a Pull Request:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Create a merge commit&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ All commits included&lt;/li&gt;
&lt;li&gt;✅ Another merge commit&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Squash and Merge&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ All commits &apos;squashed&apos; into one&lt;/li&gt;
&lt;li&gt;❌ Another merge commit&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Rebase and Merge&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ All commits included&lt;/li&gt;
&lt;li&gt;❌ Another merge commit&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h1&gt;III. Collaboration&lt;/h1&gt;
&lt;p&gt;This section focuses solely on &lt;em&gt;resolving conflicts&lt;/em&gt; and &lt;em&gt;best practice&lt;/em&gt; in &lt;strong&gt;cleaning up your commits for linear history&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Update your feature branch with latest changes&lt;/h2&gt;
&lt;p&gt;What is this for?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ Confirm that your commits do not contain breaking changes&lt;/li&gt;
&lt;li&gt;✅ Ensure you&apos;re working on the latest version of development branch&lt;/li&gt;
&lt;li&gt;✅ Help your teammates to keep track easily with a linear history&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;Similar to 1st step in &lt;a href=&quot;#ii-development-routine&quot;&gt;II. Development Routine&lt;/a&gt;, sync &lt;em&gt;base branch&lt;/em&gt; from &lt;code&gt;upstream&lt;/code&gt; remote against your local &lt;em&gt;base branch&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://gist.githubusercontent.com/tinng81/8716791851355f88ab77de0fc7fcacc7/raw/dbe4f13d2be640faf4077843c827f95552aff171/collaboration-merge.svg&quot; alt=&quot;Collaboration Merge changes to local&quot; title=&quot;Merge any changes of developing branch from upstream to local branch&quot;&gt;&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Rebase changes from &lt;strong&gt;base branch&lt;/strong&gt; &lt;em&gt;onto&lt;/em&gt; your &lt;strong&gt;feature branch&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;git checkout feat-large-feature
git rebase developing
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://gist.githubusercontent.com/tinng81/8716791851355f88ab77de0fc7fcacc7/raw/dbe4f13d2be640faf4077843c827f95552aff171/collaboration-rebase.svg&quot; alt=&quot;Collaboration rebase changes to active feature branch&quot; title=&quot;Rebase latest changes of base branch onto current active feature branch&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Cleanup your local commits history&lt;/h2&gt;
&lt;p&gt;What is this for?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ Combine commits: forgot to refactor all variables, accidentally left out&lt;/li&gt;
&lt;li&gt;✅ Keep the entire repository&apos;s history as linear as possible&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;Check the SHA hash of the &lt;strong&gt;last commit you want to retain as-is&lt;/strong&gt;, i.e. any commits made after this will be included in rebase&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pick&lt;/strong&gt;, &lt;strong&gt;squash&lt;/strong&gt; or essentially &lt;strong&gt;drop&lt;/strong&gt; commits&lt;/li&gt;
&lt;li&gt;Re-write the commit message&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;git checkout feat-large-feature
git rebase -i &amp;#x3C;last-commit-retain-as-is-hash&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://gist.githubusercontent.com/tinng81/8716791851355f88ab77de0fc7fcacc7/raw/dbe4f13d2be640faf4077843c827f95552aff171/collaboration-cleanup.svg&quot; alt=&quot;Collaboration clean up rebase&quot; title=&quot;Rebase all commits onto one specific commit, i.e. squash them, on current branch&quot;&gt;&lt;/p&gt;
&lt;p&gt;The rebase command also support relative (exclusive) distance from the commits pointed by &lt;strong&gt;HEAD&lt;/strong&gt;. From previous example, making the third commit &lt;em&gt;35d9556&lt;/em&gt; as the &lt;em&gt;last-commit-to-retain-as-is&lt;/em&gt; with &lt;code&gt;git rebase -i HEAD~3&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When you want to cleanup the source code without affecting the commit history of your &lt;em&gt;feature branch&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;When you simply want to keep a copy of the cleanning commits of your &lt;em&gt;checked-out branch&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;git checkout -b fix-small-typo feat-large-feature
git merge --squash fix-small-typo
git commit -m &quot;Squash Commit Message Title&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://gist.githubusercontent.com/tinng81/8716791851355f88ab77de0fc7fcacc7/raw/dbe4f13d2be640faf4077843c827f95552aff171/collaboration-squash.svg&quot; alt=&quot;Collaboration clean up merge squash&quot; title=&quot;Squash all commits into one squash commit when merging from another branch&quot;&gt;
&lt;/p&gt;
&lt;h2&gt;Conflict &amp;#x26; code breaking changes&lt;/h2&gt;
&lt;p&gt;What is this for?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ Introduce featured implementation onto upstream&lt;/li&gt;
&lt;li&gt;✅ Resolve redundant old version conflicts&lt;/li&gt;
&lt;li&gt;✅ Adapt to the latest implementation from upstream&lt;/li&gt;
&lt;li&gt;❌ Troubleshoot or Debug conflict changes&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;Fetch any updates from &lt;code&gt;upstream&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Try to merge to local &lt;em&gt;base branch&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Check status and open conflicted files to manually resolve it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://gist.githubusercontent.com/tinng81/8716791851355f88ab77de0fc7fcacc7/raw/dbe4f13d2be640faf4077843c827f95552aff171/collaboration-conflict.svg&quot; alt=&quot;Collaboration resolve conflict on merging&quot; title=&quot;Resolve conflicts on merging for latest updates from upstream&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;IV. FAQ&lt;/h1&gt;
&lt;p&gt;to start your changes on feature branch &lt;strong&gt;feat-new-feature&lt;/strong&gt;.&lt;/p&gt;</content:encoded><h:img src="/_astro/rebase.unh5TpPR.png"/><enclosure url="/_astro/rebase.unh5TpPR.png"/></item><item><title>Autonomous micro UAV indoor mapping, no GPS, one camera</title><link>https://tin.ng/blog/2021-01-20--autonomous-uav-indoor-mapping</link><guid isPermaLink="true">https://tin.ng/blog/2021-01-20--autonomous-uav-indoor-mapping</guid><description>Trajectory stress tests and a visual localization shootout end with ORB-SLAM flying a real office and handing back a dense map</description><pubDate>Wed, 20 Jan 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;This is the first portfolio part of my bachelor thesis project: an autonomous micro UAV that maps a building interior without GPS, using nothing but a camera. The &lt;a href=&quot;/blog/2020-06-26--px4-sitl-gazebo-mavros-offboard&quot;&gt;previous post&lt;/a&gt; set up the simulator side, PX4 in Gazebo with MAVROS. This stage points that stack at the actual thesis question: if the drone cannot see satellites, where does its position come from, and how does a map get built from a camera alone?&lt;/p&gt;
&lt;p&gt;The work split into two stages, both recorded the same day. Stage A is the stress test reel: fly the drone hard in simulation, then test visual localization methods against real footage. Stage B is the payoff: the full flight stack with ORB-SLAM, running in both simulation and real life, handing back a finished map.&lt;/p&gt;
&lt;h2&gt;Stage A: stability, agility, and the localization shootout&lt;/h2&gt;
&lt;h3&gt;Offboard communication and stabilising&lt;/h3&gt;
&lt;p&gt;First things first, and the opening caption says it: offboard communication and stabilising. The drone hovers in a simulated room while RViz tracks its pose axes. This is the MAVROS offboard link from the previous post doing its job: without a stable position estimate and a clean command path, nothing later in this post works.&lt;/p&gt;
&lt;h3&gt;Pose agility test: circular trajectory&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fpose-agility-circle.BZ-7dsoi.jpg&amp;#x26;w=640&amp;#x26;h=298&amp;#x26;f=webp&quot; alt=&quot;RViz tracing a circular trajectory with the drone&amp;#x27;s pose axes while the drone flies the circle in Gazebo&quot;&gt;
&lt;em&gt;Pose agility test: a full circle in RViz on the left, the drone following it in Gazebo on the right.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The circle test checks how well the drone tracks a smoothly curving path. Circles are gentle, but they never stop accelerating: the drone has to bank continuously, and that constant banking is what shakes a camera and upsets a visual tracker.&lt;/p&gt;
&lt;h3&gt;Manoeuvring test: lemniscate trajectory&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fmanoeuvring-lemniscate.fnoangF-.jpg&amp;#x26;w=640&amp;#x26;h=298&amp;#x26;f=webp&quot; alt=&quot;RViz tracing a lemniscate, or figure-eight, trajectory with pose axes at each crossing&quot;&gt;
&lt;em&gt;Manoeuvring test: the figure-eight, with the tight turns at the crossing point.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The lemniscate, or figure-eight, is harder on the tracker than the circle. At the crossing point the drone has to flip its turn direction through the tightest curvature on the path. If the controller overshoots there, the error compounds on the way back. Watching the traced line stay clean through the crossing says the whole chain, planner to MAVROS to PX4, is tight.&lt;/p&gt;
&lt;h3&gt;The localization shootout: SDVL vs SVO&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fsdvl-vs-svo.Bl61iXk5.jpg&amp;#x26;w=640&amp;#x26;h=298&amp;#x26;f=webp&quot; alt=&quot;Split screen comparing SDVL and SVO running on the same real office footage&quot;&gt;
&lt;em&gt;The shootout: semi-direct visual localization on the left, SVO on the right, same flight, same office.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;With the drone proven flyable, the video moves to real footage from the office floor and puts two camera-only localization methods head to head. SDVL, &lt;a href=&quot;https://doi.org/10.3390/s19020302&quot;&gt;semi-direct visual localization&lt;/a&gt;, is shown first running on the real feed, building its pink point cloud and camera trail live. Then the split screen runs SDVL and &lt;a href=&quot;https://github.com/uzh-rpg/rpg_svo&quot;&gt;SVO&lt;/a&gt;, semi-direct visual odometry, on the same flight. Both work without GPS; the difference shows up in how they hold onto the world as the drone swings past desks and chairs.&lt;/p&gt;
&lt;h3&gt;Mapping: the dense map&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fdense-map.wSy1ec5p.jpg&amp;#x26;w=640&amp;#x26;h=298&amp;#x26;f=webp&quot; alt=&quot;RViz building a dense white point map slice by slice while the drone sweeps the room in Gazebo&quot;&gt;
&lt;em&gt;Dense mapping: the room accumulating slice by slice from the depth data.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The last leg of Stage A is mapping, dense map, RGB-D only. No loop closures, no landmarks, just depth data piling into a voxel grid until the room is a solid white model in RViz. Localization ran the whole time to keep the drone honest; the dense map is what the flight produces.&lt;/p&gt;
&lt;h2&gt;Stage B: the full stack, simulation and real life&lt;/h2&gt;
&lt;p&gt;Stage B puts ORB-SLAM into the full flight stack and runs it in both worlds at once. The screen is doing four jobs at the same time: the physical drone on the floor, the keyframe trajectory, the camera&apos;s own view with tracked features and the live counter (&lt;code&gt;SLAM MODE | KFs: 24 MPs: 1883 Matches: 250&lt;/code&gt;, keyframes, map points, feature matches), and the dense map growing in RViz.&lt;/p&gt;
&lt;p&gt;The captioned phases repeat the Stage A discipline with the tracker in the loop:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Initialization:&lt;/strong&gt; the drone sits, the camera finds features, and the first keyframes anchor the map. Nothing moves until the tracker is confident.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Diagonal Trajectory:&lt;/strong&gt; climb plus sideways travel at once, so the camera pitches and rolls while the tracker has to keep its place. The Stage A agility test, now with SLAM online.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Back to start:&lt;/strong&gt; fly the loop home and see where &quot;home&quot; actually lands. The gap between where the drone started and where it thinks it started is the drift, and every visual system accumulates some.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Map Completion:&lt;/strong&gt; the dense map fills in to a recognizable copy of the room, and the trajectory loop closes neatly inside it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Finitialization-panels.RoAJkJ-E.jpg&amp;#x26;w=640&amp;#x26;h=360&amp;#x26;f=webp&quot; alt=&quot;The four panels during initialization: drone on the floor, keyframe trajectory, feature-tracked camera view, and the dense map in RViz&quot;&gt;
&lt;em&gt;Initialization: the tracker anchors the first keyframes before anything moves.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The last third of the video returns to SITL, the same PX4-in-Gazebo setup from the previous post, now wearing this thesis work on top. &lt;strong&gt;SITL Mapping, Speed x2.0&lt;/strong&gt; flies a simulated house while the dense pipeline rebuilds it, and &lt;strong&gt;SITL Planner, Speed x2.0&lt;/strong&gt; draws a green path across the finished map from start to goal and flies it. Running the mapping in simulation first is what makes the real flights calm: algorithms get to fail in a house where nobody pays for wall repairs.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fsitl-planner.B94ZGplZ.jpg&amp;#x26;w=640&amp;#x26;h=360&amp;#x26;f=webp&quot; alt=&quot;The SITL planner drawing a green path across the completed house map while the drone follows in Gazebo&quot;&gt;
&lt;em&gt;Planner at work: start to goal over the dense map, at double speed.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;What carried over&lt;/h2&gt;
&lt;p&gt;Stage A was the parts check: a controller that tracks circles and figure-eights, and two visual localization methods that survive real footage. Stage B bolted those parts into one stack, ORB-SLAM included, and ran it in simulation and then on the real floor. It ends with a drone that holds itself together on camera alone and hands back a map of the room it just flew. The next stages push toward longer autonomous runs and the hardware lessons that only exist outside the simulator.&lt;/p&gt;</content:encoded><h:img src="/_astro/map-completion.2Gmz0Bsx.jpg"/><enclosure url="/_astro/map-completion.2Gmz0Bsx.jpg"/></item><item><title>PX4 SITL in Gazebo, flown over MAVROS</title><link>https://tin.ng/blog/2020-06-26--px4-sitl-gazebo-mavros-offboard</link><guid isPermaLink="true">https://tin.ng/blog/2020-06-26--px4-sitl-gazebo-mavros-offboard</guid><description>Four terminal panes, one short flight, the offboard gotcha that blocks arming until setpoints flow, and why the code survives the move to hardware</description><pubDate>Fri, 26 Jun 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;This is step one of a longer project I am working on with my teammates for our bachelor thesis: build a drone, starting in simulation, then moving to human-in-the-loop flying, and finally to the real thing. Before you can crash a drone you built yourself, you should crash it in software first. Software in the loop (SITL) lets you do exactly that, for free, as many times as you want.&lt;/p&gt;
&lt;p&gt;The video below is the whole stack running on my machine: the PX4 flight stack simulated in Gazebo, flown in offboard mode through MAVROS. It is a short flight, and the interesting part is how many moving pieces it takes to get a fake drone to do that.&lt;/p&gt;
&lt;h2&gt;The stack&lt;/h2&gt;
&lt;p&gt;Four pieces are running at once, each with its own terminal pane in the video:&lt;/p&gt;
&lt;p&gt;| Piece | What it is | Why it is here |
| --- | --- | --- |
| Gazebo | 3D physics simulator | Renders the world and simulates physics for the iris quadcopter model that ships with PX4 |
| PX4 (SITL) | The flight stack, compiled for software in the loop | The real autopilot code, but fed by simulated sensors instead of a real IMU and GPS |
| ROS + MAVROS | The ROS middleware and its MAVLink bridge | Lets ROS nodes talk to the autopilot: read state, change modes, send setpoints |
| mavros_controllers | Offboard controller nodes | The setpoint generators that fly the drone |&lt;/p&gt;
&lt;p&gt;I run my own forks of both big pieces: &lt;a href=&quot;https://github.com/tinng81/PX4-Autopilot&quot;&gt;PX4-Autopilot&lt;/a&gt; (upstream is &lt;a href=&quot;https://github.com/PX4/PX4-Autopilot&quot;&gt;PX4/PX4-Autopilot&lt;/a&gt;) and &lt;a href=&quot;https://github.com/tinng81/mavros_controllers&quot;&gt;mavros_controllers&lt;/a&gt;, forked from &lt;a href=&quot;https://github.com/Jaeyoung-Lim/mavros_controllers&quot;&gt;Jaeyoung-Lim&apos;s version&lt;/a&gt;. Same code for now, but the forks are where our own changes will land.&lt;/p&gt;
&lt;h2&gt;MAVLink and MAVROS in drones&lt;/h2&gt;
&lt;p&gt;MAVLink is the little binary protocol that almost every autopilot speaks. It is a stream of numbered messages: heartbeats saying &quot;I am alive&quot;, status messages for armed/disarmed and flight mode, telemetry like position, attitude and battery, command messages like &quot;arm&quot; or &quot;return to launch&quot;, and setpoint messages that say &quot;be at this position, with this velocity&quot;.&lt;/p&gt;
&lt;p&gt;MAVROS is the bridge between that world and ROS. It is a ROS node that turns each MAVLink message into a topic, and each command into a service. So &lt;code&gt;/mavros/state&lt;/code&gt; shows armed and mode, &lt;code&gt;/mavros/local_position/pose&lt;/code&gt; streams the estimated position, &lt;code&gt;/mavros/setpoint_position/local&lt;/code&gt; accepts position targets, and &lt;code&gt;/mavros/cmd/arming&lt;/code&gt; arms the motors. On a real drone, MAVROS runs on a companion computer riding along, wired to the flight controller over serial. In simulation the exact same node is used; the only difference is the link, which is a UDP port instead of a wire:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;roslaunch mavros px4.launch fcu_url:=&quot;udp://:14540@localhost:14557&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MAVROS sends its link to port 14557, where the SITL instance listens for companion connections, and binds port 14540, the standard SDK port PX4 streams its telemetry back to. Because the protocol and the node are identical between simulation and hardware, everything written against MAVROS keeps working when the drone becomes real.&lt;/p&gt;
&lt;h2&gt;The walkthrough&lt;/h2&gt;
&lt;p&gt;With all four terminals open, the demo goes like this.&lt;/p&gt;
&lt;p&gt;First, bring up the simulated autopilot and its world:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;make px4_sitl gazebo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PX4 boots inside the terminal, runs its startup scripts, starts the state estimator and opens Gazebo with the iris quadcopter sitting on the ground. Nothing is flying yet; this is a complete autopilot, just with imaginary sensors.&lt;/p&gt;
&lt;p&gt;Second, connect MAVROS with the &lt;code&gt;fcu_url&lt;/code&gt; above. The console fills with link and parameter traffic, and from here the autopilot is reachable as ordinary ROS topics.&lt;/p&gt;
&lt;p&gt;Third, check the state and take over:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;rostopic echo /mavros/state
rosrun mavros mavsys mode -c OFFBOARD
rosrun mavros mavsafety arm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From that moment the flight stack stops waiting for a pilot. The offboard node publishes position targets, MAVROS translates them into MAVLink setpoints, PX4 turns them into motor commands, and Gazebo moves the model. The video shows the quad climb off the ground, hold and shift position under the controller&apos;s targets, then land again with a land command.&lt;/p&gt;
&lt;p&gt;Finally, &lt;code&gt;rqt_graph&lt;/code&gt; draws the picture of who is talking to whom: the controller node feeding &lt;code&gt;/mavros/setpoint_position/local&lt;/code&gt;, MAVROS in the middle, and everything flowing out through the single UDP link.&lt;/p&gt;
&lt;h2&gt;Team clips: hardware on the bench&lt;/h2&gt;
&lt;p&gt;While the simulation side ran on laptops, the hardware side of the thesis was already waking up on a bench. We kept a couple of short clips, in Vietnamese, as the team&apos;s memory of those first bench days.&lt;/p&gt;
&lt;p&gt;The first one is the motor test: four Emax 2300 kV motors controlled through BLHeli 30A ESCs, powered through a Matek Mini PowerHub power distribution board. Before any of them meet a frame or a flight controller, each one gets spun on the bench to check it spins the right way, responds to throttle, and does not try to eat anything.&lt;/p&gt;
&lt;p&gt;The second is less glamorous and more important: charging the 4S pack straight from a 220V wall socket with a charger, instead of whatever improvised arrangement we would have invented otherwise.&lt;/p&gt;
&lt;h2&gt;What is next&lt;/h2&gt;
&lt;p&gt;This is the first rung of the thesis: prove the brain works before the body exists. Next up in the series is &lt;a href=&quot;/blog/2021-01-20--autonomous-uav-indoor-mapping&quot;&gt;autonomous indoor mapping without GPS&lt;/a&gt;, where this same stack learns to see, and after that the human-in-the-loop flying and the design work for the real airframe.&lt;/p&gt;</content:encoded><h:img src="/_astro/px4-sitl-gazebo-mavros.CixzfBSH.jpg"/><enclosure url="/_astro/px4-sitl-gazebo-mavros.CixzfBSH.jpg"/></item><item><title>Task Scheduler in Real-time OSes (Part 2)</title><link>https://tin.ng/blog/2020-06-05--task-scheduler-in-rtos-part-2</link><guid isPermaLink="true">https://tin.ng/blog/2020-06-05--task-scheduler-in-rtos-part-2</guid><description>Define OS Scheduling based on methodologies such as Timeslice/Priority</description><pubDate>Fri, 05 Jun 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h1&gt;Overview&lt;/h1&gt;
&lt;p&gt;This article is part 2 of an overview series specifically about the &lt;em&gt;Scheduler&lt;/em&gt; in a &lt;strong&gt;Real-time Operating System (RTOS)&lt;/strong&gt;. Examples of such systems can be found in almost anything from automotive application such as airbags, emergency breaks to avionics, and also infotainment, multi-media systems like video playback and QoS in web servers.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2019-09-02--task-scheduler-in-rtos-part-1&quot;&gt;Read Part 1: Task Scheduler in RTOS&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This means that they both have to look at the output of the system and at the time at which the output was generated to determine whether an execution is successful.&lt;/p&gt;
&lt;h1&gt;Static Scheduling&lt;/h1&gt;
&lt;p&gt;As mentioned, &lt;em&gt;predictability&lt;/em&gt; for an RTOS is an important characteristic and Static Scheduling provides just that, the ability to schedule, optimize for the tasks at runtime with very &lt;em&gt;low complexity&lt;/em&gt;. Although, this also means that every modification requires execution of the scheduling algorithm again.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fstatic-taxonomy.OfLcpdk6.png&amp;#x26;w=680&amp;#x26;h=472&amp;#x26;f=webp&quot; alt=&quot;Taxonomy of Static Scheduling&quot;&gt;
&lt;em&gt;Taxonomy of Static Scheduling binded with Priority Scheduling Scheme. Different methodologies include timeslice, periodicity, and priority&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For Static Scheduling, &lt;em&gt;all parameters of all the tasks are assumed knownn&lt;/em&gt; in which the Scheduler built a schedule based on this information. This characterstic makes it simplier to schedule by timeslice based on &lt;em&gt;task arrival pattern&lt;/em&gt; since there will be no change throughout the schedules.&lt;/p&gt;
&lt;p&gt;Another applications can be Process Control System where all sensory and actuator data range of all tasks are known before hand.&lt;/p&gt;
&lt;h2&gt;Clock-Driven Scheduling&lt;/h2&gt;
&lt;p&gt;Clock-Driven Scheduling is a Static Scheduling mechanism that consist of the following characteristics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Schedule is created off-line at compile time&lt;/li&gt;
&lt;li&gt;Precedence is treated explicitly between tasks&lt;/li&gt;
&lt;li&gt;Scheduler operates when timer expires, i.e. Timer interrupt&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Following is example of Clock-Driven Scheduling with a timeslice of Scheduling time $$t=1s$$ and three tasks $$T_1$$ , $$T_2$$ , $$T_3$$ scheduled at compile time.&lt;/p&gt;
&lt;p&gt;| Task ($$T$$) | Period ($$P$$) | Execution Time ($$E_t$$) | Deadline ($$D$$) |
| ------------ | -------------- | --------------------------- | ---------------- |
| $$T_1$$      | 3              | 1                           | 3                |
| $$T_2$$      | 4              | 1                           | 4                |
| $$T_3$$      | 10             | 2                           | 10               |&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Table of Schedule for 3 Tasks with their Execution Time, Period and Deadline&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Before any scheduling, a concept of &lt;strong&gt;CPU Utilization is important&lt;/strong&gt; since tasks with fast period and long execution time will occupy a large amount of CPU resources.&lt;/p&gt;
&lt;p&gt;CPU Utilization is defined as the sum of all tasks&apos; Execution times $$\sum E_i$$ divided by their Periods $$\sum P_i$$. &lt;strong&gt;If the sum is greater than 1, then the system is guaranteed not feasible&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;$$
Utilization = \sum_{i=1}^{n}\frac{E_i}{P_i}
$$&lt;/p&gt;
&lt;p&gt;For this example of 3 Tasks, $$U = \frac{1}{3} + \frac{1}{4} + \frac{1}{5} = 0.78$$, and thus the system &lt;strong&gt;will not be unfeasible&lt;/strong&gt;. It does not mean the system is feasible yet, this point will be discussed in the schedule plot.&lt;/p&gt;
&lt;p&gt;On the following plot, the Scheduler generates and allocates Tasks with &lt;strong&gt;start-of-task&lt;/strong&gt; one direction arrow ($$\uparrow$$) and &lt;strong&gt;end-of-task&lt;/strong&gt; small dot (.) at the end of Execution Time (filled range).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fclock-driven.BnY_lF3J.png&amp;#x26;w=1854&amp;#x26;h=742&amp;#x26;f=webp&quot; alt=&quot;Clock-Driven Scheduling of three tasks at one second timeslice&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Task $T_1$, $T_2$, $T_3$ and Processor $P_0$ plotted on Scheduling time (t) x-axis in 20 seconds&lt;/em&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;On the first line of Task 1, the timer interrupts the system at time 0 and $$T_1$$ is selected. The job of $$T_1$$ is then executed through out its $$E_{t1}$$ and come to a stop at $$t=1$$ then the next task is up.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Side note:&lt;/strong&gt; similarly the second and third lines of Task 2 and 3 respectively.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Since $$T_3$$ start-of-task is $$t=1$$, it takes control next.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Side note:&lt;/strong&gt; there is no concept of priority yet, the only determinants are which Task &lt;em&gt;available&lt;/em&gt; and &lt;em&gt;ready&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;At time $$t=3$$, the Scheduler selects $$T_1$$ eventhough $$T_2$$ also starts at $$t=3$$. That is because Task 3 Period is $$P_3=4$$ and thus not executed until the next timeslice $$t=4$$.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Acknowledgement&lt;/h1&gt;
&lt;p&gt;This series is heavily influenced by a &lt;a href=&quot;https://www.coursera.org/learn/real-time-systems/&quot;&gt;Coursera online course&lt;/a&gt; created by the &lt;strong&gt;EIT-Digital University&lt;/strong&gt; in &lt;em&gt;Turku&lt;/em&gt;, &lt;em&gt;Finland&lt;/em&gt; as part of the &lt;em&gt;on-campus course on Real-Time Systems&lt;/em&gt; given in their Embedded Systems &lt;em&gt;Master&apos;s&lt;/em&gt; curriculum.&lt;/p&gt;</content:encoded><h:img src="/_astro/clock-driven.BnY_lF3J.png"/><enclosure url="/_astro/clock-driven.BnY_lF3J.png"/></item><item><title>Elevate sudo privileges using biometrics</title><link>https://tin.ng/blog/2020-03-16--elevate-privileges-with-sudo-using-your-own-biometrics</link><guid isPermaLink="true">https://tin.ng/blog/2020-03-16--elevate-privileges-with-sudo-using-your-own-biometrics</guid><description>Utilises macOS Touch ID instead of typing your password every single time</description><pubDate>Mon, 16 Mar 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;
import Video from &apos;../../../components/common/Video.astro&apos;
import enabledTouchID from &quot;./enabled-touchid.mp4&quot;;&lt;/p&gt;
&lt;h1&gt;Overview&lt;/h1&gt;
&lt;p&gt;This article describes how to enable Touch ID on your Mac to authenticate you for sudo access instead of a password because chances are you&apos;re not the &quot;owner&quot; of your computer.&lt;/p&gt;
&lt;p&gt;The concept sudo originated on *nix OSes and is quite similar to an admin account on Windows, loosely speaking. In fact, it stands for &lt;strong&gt;SUper user DO&lt;/strong&gt; as in executing as a root user and normally elevated with a password.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Through this tutorial, you can takes a step further and utilises macOS Touch ID to elevate necessary privileges without typing your password every single time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;Enable elevating privileges using Touch ID&lt;/h1&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;$ sudo vim /etc/pam.d/sudo
auth       sufficient     pam_tid.so # [!code highlight]
auth       sufficient     pam_smartcard.so
auth       required       pam_opendirectory.so
...
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Open your favorite terminal emulator, usually &lt;em&gt;Terminal.app&lt;/em&gt; or &lt;em&gt;iTerm.app&lt;/em&gt; and open &lt;code&gt;/etc/pam.d/sudo&lt;/code&gt; with any command line editors such as &lt;em&gt;vim&lt;/em&gt; or &lt;em&gt;nano&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Append the line &lt;code&gt;auth sufficient pam_tid.so&lt;/code&gt; space-separated to the sudo file&lt;/li&gt;
&lt;li&gt;Hit Save (&lt;strong&gt;:wq!&lt;/strong&gt; for &lt;em&gt;vim&lt;/em&gt; or &lt;strong&gt;⌃-x&lt;/strong&gt; then &lt;strong&gt;y&lt;/strong&gt; for &lt;em&gt;nano&lt;/em&gt;) to apply all changes&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;sudo-touchid.svg&quot; alt=&quot;enable elevating privileges&quot;&gt;
&lt;em&gt;Enable elevating privileges using TouchID via Vim editor on terminal. Adding pam_tid.so to sudo configuration to elevate privileges using Touch ID.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Alternatively, navigate through Finder with &lt;strong&gt;⌘-⇧-G&lt;/strong&gt; keystrokes then type in the address &lt;code&gt;/etc/pam.d/sudo&lt;/code&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Right-click and select &lt;em&gt;Get Info&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;In &lt;em&gt;Sharing &amp;#x26; Permissions&lt;/em&gt; section, unlock using your password and grant &lt;strong&gt;Read &amp;#x26; Write access&lt;/strong&gt; so you can open it&lt;/li&gt;
&lt;li&gt;Open the file in any editor and append &lt;code&gt;auth sufficient pam_tid.so&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Save and revert Step 2 to ensure the file is Read-only&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fopen-via-finder.CBOOccXF.png&amp;#x26;w=1204&amp;#x26;h=559&amp;#x26;f=webp&quot; alt=&quot;Open sudo File via Finder&quot; title=&quot;Open sudo file via Finder to edit on a graphical text editor&quot;&gt;&lt;/p&gt;
&lt;h1&gt;Results&lt;/h1&gt;
&lt;p&gt;In a new terminal tab/window, whenever executing sudo, it will automatically prompt for Touch ID if you have successfully configured following the previous steps.&lt;/p&gt;
&lt;h1&gt;(Optional) Tmux Setup&lt;/h1&gt;
&lt;p&gt;The previous result won&apos;t be the same within a Tmux session and require further installation of  &lt;em&gt;fabianishere / pam_reattach&lt;/em&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install the &lt;code&gt;pam_reattach&lt;/code&gt; module with brew&lt;/li&gt;
&lt;li&gt;Similar to previous, append &lt;code&gt;auth optional pam_reattach.so&lt;/code&gt; to the &lt;em&gt;sudo&lt;/em&gt; authentication file&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;brew install fabianishere/personal/pam_reattach # [!code highlight]
sudo vim /etc/pam.d/sudo
auth       optional       pam_reattach.so # [!code highlight]
auth       sufficient     pam_tid.so
auth       sufficient     pam_smartcard.so
...
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Troubleshooting (Extra)&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Issues with iTerm2 not working&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Make sure that you have enabled successfully, also try built-in &lt;em&gt;Terminal.app&lt;/em&gt; to verify it, otherwise open iTerm2 &lt;em&gt;Preferences&lt;/em&gt; (⌘-,) → &lt;em&gt;Advanced&lt;/em&gt; → &lt;em&gt;Allow sessions to survive logging out and back in&lt;/em&gt; and set it to &lt;code&gt;No&lt;/code&gt;.&lt;/p&gt;
&lt;h1&gt;Acknowledgement&lt;/h1&gt;
&lt;p&gt;This tutorial is based on the answers from this &lt;a href=&quot;https://apple.stackexchange.com/a/306324&quot;&gt;StackExchange thread&lt;/a&gt;.&lt;/p&gt;</content:encoded><h:img src="/_astro/enabled-touchid.DCsea4lc.png"/><enclosure url="/_astro/enabled-touchid.DCsea4lc.png"/></item><item><title>Intuitive Understanding of C Declaration</title><link>https://tin.ng/blog/2020-02-06--intuitive-understanding-of-c-declaration</link><guid isPermaLink="true">https://tin.ng/blog/2020-02-06--intuitive-understanding-of-c-declaration</guid><description>Useful &quot;Right-Left&quot; rule for deciphering complex declaration in C programming language</description><pubDate>Thu, 06 Feb 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h1&gt;Overview&lt;/h1&gt;
&lt;p&gt;This article describes several examples of declaration in C programming considering the order of symbols, identifiers and explaining with an intuitive understanding.&lt;/p&gt;
&lt;p&gt;For instance, converting this declaration&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int (*(*func_ptr)(char *,double))[9][20];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to plain text English&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;func_ptr is pointer to function expecting (char *,double) and returning pointer to array (size 9) of array (size 20) of int.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;Prerequisite&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Basic understanding of programming language&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An understanding of C programming language &lt;em&gt;syntax&lt;/em&gt;, &lt;em&gt;data type&lt;/em&gt;, &lt;em&gt;identifier&lt;/em&gt; and the &lt;em&gt;concept of pointers&lt;/em&gt; is advantageous, but &lt;strong&gt;not required&lt;/strong&gt;.&lt;/p&gt;
&lt;h1&gt;The &quot;Right-Left&quot; rule Intuition&lt;/h1&gt;
&lt;p&gt;First and foremost, these symbols in C are important: &lt;em&gt;asterisk&lt;/em&gt; &quot;*&lt;strong&gt;&lt;strong&gt;&quot;, &lt;em&gt;squared bracket&lt;/em&gt; &quot;&lt;/strong&gt;[]&lt;/strong&gt;&quot;, and &lt;em&gt;parenthesis&lt;/em&gt; &quot;&lt;strong&gt;()&lt;/strong&gt;&quot;.&lt;/p&gt;
&lt;p&gt;Later, these can be useful to determine the correct declaration by using their &lt;em&gt;translated English interpretation&lt;/em&gt; and their &lt;em&gt;position in the declaration sentence&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;| Symbol | Interpretation          | Position          |
| ------ | ----------------------- | ----------------- |
| *      | as &quot;pointer to&quot;         | on the left side  |
| []     | as &quot;array of&quot;           | on the right side |
| ()     | as &quot;function returning&quot; | on the right side |&lt;/p&gt;
&lt;p&gt;&lt;em&gt;These symbols can be interpreted in plain English whose position can also be interpreted to order all symbols&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Step 1 – Find the Identifier&lt;/h2&gt;
&lt;p&gt;Take &lt;code&gt;int *p[];&lt;/code&gt; as a basic example which can be interpreted straight forward by looking from the previous &lt;em&gt;Symbol-English&lt;/em&gt; translation table and is &lt;strong&gt;an array of pointers to data type int&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int *p[];
     ^
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here the &lt;strong&gt;Identifier&lt;/strong&gt; is &lt;code&gt;p&lt;/code&gt; and is simply intepreted as &quot;&lt;strong&gt;p is&lt;/strong&gt;&quot;.&lt;/p&gt;
&lt;h2&gt;Step 2 – Read all symbols to the right&lt;/h2&gt;
&lt;p&gt;Next, continue with &lt;em&gt;symbols on the right&lt;/em&gt; of the Identifier &lt;strong&gt;until there is no symbol remaining OR encountering a right parenthesis&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;From the &lt;em&gt;Symbol-English&lt;/em&gt; table, the &quot;&lt;strong&gt;[]&lt;/strong&gt;&quot; appended after the Identifier is interpreted as &quot;Identifier is array of&quot; or in the case of &quot;&lt;strong&gt;()&lt;/strong&gt;&quot; is &quot;Identifier is function returning&quot;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int *p[];
      ^^
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here the &lt;strong&gt;squared bracket&lt;/strong&gt; is &lt;code&gt;[]&lt;/code&gt; encountered and can be intepreted as &quot;p is &lt;strong&gt;array of&lt;/strong&gt;&quot;.&lt;/p&gt;
&lt;p&gt;A special case of multiple left parenthesis with nesting between the initial parentheses only means that the whole procedure is repeated.&lt;/p&gt;
&lt;h2&gt;Step 3 – Read all symbols to the left and then the data type&lt;/h2&gt;
&lt;p&gt;Once there is no symbol on the right, moving &lt;em&gt;towards the data type&lt;/em&gt; starting at the Identifier, i.e. left direction, &lt;strong&gt;until there is no symbol remaining OR encountering a left parenthesis&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int *p[];
    ^
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here the &lt;strong&gt;asterisk&lt;/strong&gt; is &lt;code&gt;*&lt;/code&gt; encountered and can be intepreted as &quot;p is array of &lt;strong&gt;pointer to&lt;/strong&gt;&quot;.&lt;/p&gt;
&lt;p&gt;Lastly, if there is no symbol belong to the &lt;em&gt;Symbol-English&lt;/em&gt; table left, evaluate the data type.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int *p[];
^^^
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here the &lt;strong&gt;data type&lt;/strong&gt; is &lt;code&gt;int&lt;/code&gt; encountered and can be intepreted as &quot;p is array of pointer to &lt;strong&gt;int&lt;/strong&gt;&quot;.&lt;/p&gt;
&lt;h2&gt;Nested Declaration&lt;/h2&gt;
&lt;p&gt;Now using &lt;code&gt;int *(*func())();&lt;/code&gt; for declaration to address the nesting as previously mentioned.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// Step 1 - Find the Identifier
int *(*func())();
       ^^^^
// &quot;func is&quot;

// Step 2 - Read all symbols to the right
int *(*func())();
           ^^
// &quot;func is&quot; 
// found &quot;()&quot;, appending &quot;function returning&quot;

// Step 3 - Encounter right parenthesis, 
// read all symbols to the left
int *(*func())();
      ^
// &quot;func is function returning&quot; 
// found &quot;*&quot;, appending &quot;pointer to&quot;

// Step 4 - Encounter left parenthesis, 
// return to read all symbols to the right
int *(*func())();
              ^^
// &quot;func is function returning pointer to&quot;
// found &quot;()&quot;, appending &quot;function returning&quot;

// Step 5 - No remaining symbols on the right, 
// return to read all symbols to the left
int *(*func())();
    ^
// &quot;func is function returning pointer to function returning&quot;
// found &quot;*&quot;, appending &quot;pointer to&quot;

// Step 6 - Finally, no remaining symbols on either sides, 
// evaluate data type
int *(*func())();
^^^
// &quot;func is function returning pointer to function returning pointer to&quot;
// found data type, appending &quot;int&quot;.
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;The final result is &quot;func is function returning pointer to function returning pointer to int&quot;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;Illegal Declaration&lt;/h1&gt;
&lt;p&gt;Although not required upto this, knowledge of legal C declaration is necessary to avoid making illegal declaration eventhough they made sense in English-like interpretation.&lt;/p&gt;
&lt;p&gt;| Pairs    | Illegal combinations          | Correct intepretation                        |
| -------- | ----------------------------- | -------------------------------------------- |
| &lt;code&gt;[ ]( )&lt;/code&gt; | an array of functions         | an array of &lt;code&gt;pointers to&lt;/code&gt; functions          |
| &lt;code&gt;( )( )&lt;/code&gt; | a function returns a function | a function returns &lt;code&gt;a pointer to&lt;/code&gt; a function |
| &lt;code&gt;( )[ ]&lt;/code&gt; | a function returns an array   | a function returns &lt;code&gt;a pointer to&lt;/code&gt; an array   |&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Interprete English-like declaration also required to make proper and valid declaration based on the C programming syntax&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Take the first example from this article, strip down argument list and array size, and modify it as &lt;code&gt;int *((*func_ptr)())[][];&lt;/code&gt;. Now this is interpreted as&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;func_ptr is pointer to function returning array of array of
                       ^^^^^^^^^^^^^^^^^^^^^^^^
pointer to int
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will cause error thrown despite made sense following the &lt;em&gt;Right-Left&lt;/em&gt; rule, because technically a &lt;strong&gt;function cannot return an array&lt;/strong&gt;, but rather the pointer to that array.&lt;/p&gt;
&lt;h1&gt;Acknowledgement&lt;/h1&gt;
&lt;p&gt;This tutorial is heavily influenced by &lt;a href=&quot;http://cseweb.ucsd.edu/~ricko/CSE30/&quot;&gt;Lecture Notes&lt;/a&gt; created by &lt;em&gt;Mr. Rick Ord&lt;/em&gt; from the &lt;em&gt;Computer Science and Engineering Department&lt;/em&gt;, &lt;strong&gt;UC San Diego&lt;/strong&gt; in &lt;em&gt;California&lt;/em&gt;, &lt;em&gt;USA&lt;/em&gt;.&lt;/p&gt;</content:encoded><h:img src="/_astro/step-by-step.DR62qhOA.png"/><enclosure url="/_astro/step-by-step.DR62qhOA.png"/></item><item><title>MQTT protocol analysis for embedded systems and IoT applications</title><link>https://tin.ng/blog/2019-10-28--mqtt-protocol-analysis-for-iot-application</link><guid isPermaLink="true">https://tin.ng/blog/2019-10-28--mqtt-protocol-analysis-for-iot-application</guid><description>Understand lightweight protocol MQTT and practical implementation with advantages/trade-offs</description><pubDate>Mon, 28 Oct 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h1&gt;Overview&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;MQ Telemetry Transport&lt;/strong&gt; is a &lt;em&gt;Client – Server&lt;/em&gt; Publish/ Subscribe messaging transport protocol and was initially named after the &lt;strong&gt;MQ series&lt;/strong&gt; of IBM where the protocol was first developed for &lt;em&gt;proprietary embedded systems&lt;/em&gt; in the late 90’s.&lt;/p&gt;
&lt;p&gt;Nowadays, MQTT is considered as the &lt;strong&gt;protocol name&lt;/strong&gt;, as opposed to be an &lt;em&gt;acronym&lt;/em&gt; previously, and is the &lt;a href=&quot;https://www.iso.org/standard/69466.html&quot;&gt;de facto standard&lt;/a&gt; by OASIS and later ISO.&lt;/p&gt;
&lt;p&gt;Throughout the development of MQTT, &lt;strong&gt;ease of use&lt;/strong&gt; was always the key concern and thus the protocol are said to be &lt;em&gt;easy to implement&lt;/em&gt; on the &lt;em&gt;client side&lt;/em&gt; and excel at transferring data over the wire since it is &lt;em&gt;lightweight&lt;/em&gt;, &lt;em&gt;simple&lt;/em&gt;, and a widely adapted &lt;em&gt;open standard&lt;/em&gt;.&lt;/p&gt;
&lt;h1&gt;Prerequisite&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;A MQTT Client (for Desktop), e.g. &lt;a href=&quot;https://mqttfx.jensd.de/index.php/download&quot;&gt;MQTT.fx&lt;/a&gt;, a cross-platform client (Windows, macOS, Linux) built under the Eclipse Paho Project. Another option is &lt;a href=&quot;https://mosquitto.org/download/&quot;&gt;Mosquito MQTT&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(Optional) Setup Mathwork free account for &lt;a href=&quot;https://thingspeak.com&quot;&gt;ThingSpeak&lt;/a&gt; &amp;#x26; &lt;a href=&quot;https://www.mathworks.com/products/matlab-online.html&quot;&gt;MATLAB Online&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; From this point onwards, the term MQTT is referring to the &lt;strong&gt;MQTT Version 3.1.1&lt;/strong&gt;.&lt;/p&gt;
&lt;h1&gt;Understand how MQTT works&lt;/h1&gt;
&lt;p&gt;The Pub/ Sub pattern of MQTT provides a &lt;strong&gt;decoupling mechanism&lt;/strong&gt; between the &lt;em&gt;Client&lt;/em&gt; and &lt;em&gt;Server&lt;/em&gt; where the &lt;em&gt;Broker&lt;/em&gt; acting as a &lt;strong&gt;medium&lt;/strong&gt; between them (this can be extrapolated from the preview image).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Space Decoupling: Publisher and Subscriber &lt;strong&gt;do not need to know each other&lt;/strong&gt;, i.e. no exchange of IP address and port.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Time Decoupling: Publisher and Subscriber &lt;strong&gt;do not need to run at the same time&lt;/strong&gt;, e.g. sensor update at specific interval and only retrieved to end-user when needed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Synchronization Decoupling: Operations on both components &lt;strong&gt;do not need to be interrupted&lt;/strong&gt; during sending or receiving.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Fixed Header&lt;/h2&gt;
&lt;p&gt;There are three different aspects of MQTT include &lt;strong&gt;basic concepts&lt;/strong&gt; (Publish/ Subscribe, Client/ Broker), &lt;strong&gt;basic functionalities&lt;/strong&gt; (Connect, Publish, Subscribe) of MQTT and &lt;strong&gt;basic features&lt;/strong&gt; such as &lt;em&gt;Quality of Service&lt;/em&gt;, &lt;em&gt;Retained Messages&lt;/em&gt;, &lt;em&gt;Persistent Session&lt;/em&gt;, &lt;em&gt;Last Will and Testament&lt;/em&gt;, &lt;em&gt;Keep Alive&lt;/em&gt; and more.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Ffixed_header.yrUXrekU.jpg&amp;#x26;w=1188&amp;#x26;h=112&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Fixed Header format&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In general, the protocol has &lt;a href=&quot;http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Figure_2.1_-&quot;&gt;three different headers&lt;/a&gt; vary by the package type.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fstructure_header.Bk5JYnGS.jpg&amp;#x26;w=1188&amp;#x26;h=118&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Structure of an MQTT Control Packet&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;The Variable Header&lt;/h2&gt;
&lt;p&gt;A &lt;em&gt;Variable Header&lt;/em&gt; includes a 2 byte &lt;strong&gt;Packet Identifier&lt;/strong&gt; field  and resides between the &lt;em&gt;Fixed Header&lt;/em&gt; and the &lt;em&gt;Payload&lt;/em&gt;. Although, &lt;a href=&quot;http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349217&quot;&gt;not every Control Package requires the identifier field&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fvariable_header.BH8b3NV2.jpg&amp;#x26;w=406&amp;#x26;h=97&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;An example of Variable Header of the CONNECT packet with Protocol Length, Protocol Name, Protocol Level, Connect Flag, and Keep Alive fields&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For the above example, every bits encoded in hexadecimal format, e.g. Keep Alive field of 60 seconds ($$60_{DEC} = 3C_{HEX}$$).&lt;/p&gt;
&lt;h2&gt;The Payload&lt;/h2&gt;
&lt;p&gt;A Payload is the final part of the packet and also optional depends on the Control Package type. In the case of the &lt;strong&gt;PUBLISH&lt;/strong&gt; packet this is optional and commonly named as the &lt;em&gt;Application Message&lt;/em&gt;, whereas it’s required in the &lt;strong&gt;SUBSCRIBE&lt;/strong&gt; package and contain &lt;em&gt;Topic Filter&lt;/em&gt;, &lt;em&gt;Quality of Service&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fpayload_header.BcHeqNue.jpg&amp;#x26;w=314&amp;#x26;h=106&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;An example of Payload of the CONNECT packet with Client Identifier fields&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Quality of Service&lt;/h2&gt;
&lt;p&gt;MQTT Protocol ensures high delivery guarantees with 3 levels of QoS serve to the constraint of embedded system communication in previous discussion.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;QoS 0&lt;/strong&gt;: Guarantees a &lt;strong&gt;best effort&lt;/strong&gt; delivery but not result.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fmqtt_publish_qos0.D3QWuk84.png&amp;#x26;w=550&amp;#x26;h=223&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;QoS 0 delivers regardless of the result&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;QoS 1&lt;/strong&gt;: Guarantees that a message will be &lt;strong&gt;delivered&lt;/strong&gt; at least once.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fmqtt_publish_qos1.BH2h_aY2.png&amp;#x26;w=598&amp;#x26;h=254&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;QoS 1 delivers with the result accepted at least once&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;QoS 2&lt;/strong&gt;: Guarantees that each message is &lt;strong&gt;received&lt;/strong&gt; only once by the counterpart&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fmqtt_publish_qos2.iseq4i08.png&amp;#x26;w=618&amp;#x26;h=326&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;QoS 2 delivers with the result accepted only once&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Beside that, MQTT also provides other mechanism for quality control.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Last will&lt;/strong&gt; means in case of unexpected disconnection of a client all subscribed clients will get a message from a broker.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Testament and &lt;strong&gt;Retained message&lt;/strong&gt; means that a newly subscribed client will get an immediate status update.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Implement MQTT for practical uses&lt;/h1&gt;
&lt;p&gt;Now that you have an idea of MQTT, let’s examine it in action using &lt;strong&gt;MQTT.fx&lt;/strong&gt; to establish the connection and &lt;strong&gt;Wireshark&lt;/strong&gt; to capture the packet.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Ffx_setting.DqHD5EV-.jpg&amp;#x26;w=1162&amp;#x26;h=268&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;In MQTT.fx Setting tab, select &lt;code&gt;MQTT Broker&lt;/code&gt; under Profile section and fill in ThingSpeak Server info as above&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Under tab &lt;code&gt;Subscribe&lt;/code&gt;, use the below syntax to Subscribe any &lt;a href=&quot;https://thingspeak.com/channels/public&quot;&gt;Public channel&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;channels/&amp;#x3C;channel_ID&gt;/subscribe/json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you receive the channel feed, it means that&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The connection was requested &amp;#x26; Server Acknowledged Request&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Subscription requested &amp;#x26; Server Acknowledged Subscription&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Message Published from Server to MQTT.fx client&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This can be verified in Wireshark by select “Start Capturing Packet” icon in the Top bar and &lt;strong&gt;re-do every steps&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fwireshark_preview.O8yBBSXO.jpg&amp;#x26;w=2128&amp;#x26;h=390&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;MQTT packet captured while using MQTT.fx to the Broker on ThingSpeak server&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Under &lt;strong&gt;network layer&lt;/strong&gt; section, it can seen that MQTT indeed working on top of the &lt;em&gt;TCP/IP layer&lt;/em&gt;. Beside that, Wireshark provides deeper look into the protocol and exactly how the packet  was formed.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fwireshark_connect.nfnJ8sEv.jpg&amp;#x26;w=680&amp;#x26;h=452&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Analysis of MQTT CONNECT packet headers and flags setup&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This can breakdown into several headers as discussed, i.e. Fixed Header  with &lt;em&gt;Control Packet Type&lt;/em&gt;, Variable Header with &lt;em&gt;Protocol Length/ Name&lt;/em&gt;, &lt;em&gt;Connect Flags&lt;/em&gt;, &lt;em&gt;Keep Alive&lt;/em&gt; fields and lastly the Payload with &lt;em&gt;Client ID&lt;/em&gt; and &lt;em&gt;Authentication&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Also, these information can be interpreted in raw hexadecimal format too.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fwireshark_hex.CFOVg9jG.jpg&amp;#x26;w=780&amp;#x26;h=128&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Same MQTT packet in raw HEX code with Headers highlighted and Fields separated&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;By using any DEC-HEX decoder, these code will yield similar result as expected.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fexcel_packet.BvfNVitd.jpg&amp;#x26;w=1886&amp;#x26;h=128&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Analysis of the same CONNECT packet in Microsoft Excel&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Grab it &lt;a href=&quot;./mqtt_tutorial.xlsx&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;ThingSpeak &amp;#x26; MATLAB Application&lt;/h1&gt;
&lt;p&gt;The great thing about ThingSpeak &amp;#x26; MATLAB combination is that it provides an &lt;em&gt;easy-to-implement&lt;/em&gt; approach &lt;em&gt;without any restriction&lt;/em&gt; of further improvement.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fthingspeak_esp.CZK-tZJo.jpg&amp;#x26;w=2184&amp;#x26;h=976&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;ThingSpeak panels provide basic data collection and visualization&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Many tools can be found under &lt;code&gt;Apps&lt;/code&gt; top menu on your ThingSpeak profile with two main categories: &lt;strong&gt;Analytics&lt;/strong&gt; and &lt;strong&gt;Actions&lt;/strong&gt;. Also, MATLAB also provides excellent &lt;a href=&quot;https://www.mathworks.com/matlabcentral/fileexchange/52244-thingspeak-support-toolbox&quot;&gt;MQTT Support Toolbox&lt;/a&gt; for both reading and writing data directly to the ThingSpeak database.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-matlab&quot;&gt;% thingSpeakRead(&amp;#x3C;channelID&gt;, &amp;#x3C;Name&gt;, &amp;#x3C;Value&gt;)
[data,time] = thingSpeakRead(12397)

% Or specific Fields with Output Format flag
thingSpeakRead(12397,&apos;Fields&apos;,[1 4],&apos;NumPoints&apos;,10,&apos;OutputFormat&apos;,&apos;table&apos;)

% Now try to write to your private channel
% NOTE: Example Key Value

% Update multiple fields at once 
thingSpeakWrite(&amp;#x3C;channelID&gt;, &apos;WriteKey&apos;, &amp;#x3C;write-key&gt;,...
		[field1_val, field2_val, field3_val]) 

% Or automated process for batch authentication from multiple channels
thingSpeakAuthenticate(&amp;#x3C;channelID&gt;, &amp;#x3C;Name&gt;, &amp;#x3C;Value&gt;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fthingspeak_bihistogram.CXqVLcm9.jpg&amp;#x26;w=1166&amp;#x26;h=875&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;An Example of Bi-Histogram from ThingSpeak data imported for a Transportation Tracking application&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Troubleshooting (Extra)&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Authorization issues in MQTT.fx&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Make sure that you type in &lt;strong&gt;User-ID&lt;/strong&gt; and &lt;strong&gt;MQTT-API-key&lt;/strong&gt; for fields &amp;#x3C;User Name&gt; and &amp;#x3C;Password&gt; of &lt;em&gt;User Credentials&lt;/em&gt; Tab respectively.&lt;/p&gt;
&lt;p&gt;Both can be found under &lt;code&gt;Account&lt;/code&gt; $$\rightarrow$$ &lt;code&gt;My Profile&lt;/code&gt; on your ThingSpeak profile.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Decimal-Hexadecimal Conversion&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In case you could not access the &lt;a href=&quot;./mqtt_tutorial.xlsx&quot;&gt;Excel&lt;/a&gt; file for reading the content of a MQTT packet, try to use directly in &lt;a href=&quot;https://matlab.mathworks.com&quot;&gt;MATLAB Online&lt;/a&gt; with the &lt;a href=&quot;https://www.mathworks.com/help/matlab/ref/dec2hex.html&quot;&gt;Dec2Hex&lt;/a&gt; built-in function.&lt;/p&gt;</content:encoded><h:img src="/_astro/MQTT.30hPArho.png"/><enclosure url="/_astro/MQTT.30hPArho.png"/></item><item><title>Urban planning application from Ford City Insights</title><link>https://tin.ng/blog/2019-10-10--ford-city-insights-urban-planning</link><guid isPermaLink="true">https://tin.ng/blog/2019-10-10--ford-city-insights-urban-planning</guid><description>Ford is using AI to establish traffic profile on city streets and find safety, travel, and parking solutions</description><pubDate>Thu, 10 Oct 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Video from &apos;../../../components/common/Video.astro&apos;
import cityPreview from &quot;./city-preview.mp4&quot;;
import cityDynamic from &quot;./city-dynamic.mp4&quot;;&lt;/p&gt;
&lt;p&gt;This times, instead of a new car, Ford introduces an AI-powered system promising in urban planning application. The City Insights platform consists of four main sectors: &lt;em&gt;safety&lt;/em&gt;, &lt;em&gt;parking&lt;/em&gt;, &lt;em&gt;transit&lt;/em&gt;, and most importantly a &lt;em&gt;3D model&lt;/em&gt; that extrapolate information from other components.&lt;/p&gt;
&lt;p&gt;Originated from Ann Arbor, Michigan where the authority decided not to build a new parking structure and instead figure out how to direct drivers to open parking spaces with Ford for 18 months.&lt;/p&gt;
&lt;p&gt;Not only that, the platform utilizes AI and data from a variety of source, including &lt;strong&gt;traffic cameras&lt;/strong&gt;, &lt;strong&gt;parking garages status&lt;/strong&gt;, and &lt;strong&gt;police reports&lt;/strong&gt; for an in-depth analysis and report which roads are most likely to happen collisions and thus suggest alternative transportation mode, namely shuttles or scooters.&lt;/p&gt;
&lt;p&gt;As mentioned, the fourth part of City Insights platform is the City Insights Studio made up of 3D-printed buildings and city blocks that projected on the screen showing maps and data. That being said, the generated results are partly from the contribution of &lt;strong&gt;Ford vehicle owner themself who have opted in to data sharing&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Besides that, Ford has worked with  more cities about its &lt;strong&gt;&quot;City:One challenge mobility&quot; program&lt;/strong&gt; and expected to expand the program in the near future.&lt;/p&gt;
&lt;p&gt;Read more about their progress from &lt;a href=&quot;https://medium.com/cityoftomorrow/how-ann-arbor-michigan-became-a-living-lab-for-city-mobility-daf93672b5d0&quot;&gt;Medium&lt;/a&gt;.&lt;/p&gt;</content:encoded><h:img src="/_astro/city-static.Ccd9yjsc.jpg"/><enclosure url="/_astro/city-static.Ccd9yjsc.jpg"/></item><item><title>Public large-scale datasets for autonomous driving research</title><link>https://tin.ng/blog/2019-10-01--public-datasets-for-autonomous-driving-research</link><guid isPermaLink="true">https://tin.ng/blog/2019-10-01--public-datasets-for-autonomous-driving-research</guid><description>A short list of datasets focus on semantic understanding of urban street and integration</description><pubDate>Tue, 01 Oct 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;
import { Timeline } from &apos;astro-pure/user&apos;
import Video from &apos;../../../components/common/Video.astro&apos;
import aevOnroad from &quot;./aev-onroad.mp4&quot;;&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Timeline events={
[
{ date: &apos;Sep 2019&apos;, content: &apos;Audi released AEV Autonomous Driving Dataset (A2D2) an open multi-sensor dataset for autonomous driving research.&apos; },
{ date: &apos;Aug 2019&apos;, content: &apos;Waymo released Open Datasets which comprised of high-resolution sensor data collected by Waymo self-driving cars in a wide variety of conditions.&apos; },
{ date: &apos;Jul 2019&apos;, content: &apos;Lyft released &lt;em&gt;Level 5 Datasets&lt;/em&gt;, a subset of their autonomous driving data collected by Lyft’s Level 5 team with high-quality data from camera and LIDAR sensors.&apos; },
{ date: &apos;Mar 2019&apos;, content: &apos;Aptiv released &lt;em&gt;nuScenes Datasets&lt;/em&gt; which comprised of labelled data of comprehensive autonomous vehicle multi-sensor suite.&apos; },
{ date: &apos;May 2018&apos;, content: &apos;Berkeley released DeepDrive datasets includes Instance segmentation, object detection, drivable areas, lane markings as part of challenges at their hosted CVPR 2018 Workshop on Autonomous Driving.&apos; },
]
} /&gt;&lt;/p&gt;
&lt;p&gt;| Owner    | Dataset               | Feature                                                                                                   | License                                 |
| -------- | --------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------- |
| Audi     | &lt;a href=&quot;https://www.audi-electronics-venture.de/aev/web/en/driving-dataset/dataset.html&quot;&gt;A2D2&lt;/a&gt;             | - 2D semantic segmentation - 3D point cloud labels - 3D bounding boxes - Unlabelled sensor data  | CC BY-ND 4.0[^1]                        |
| Waymo    | &lt;a href=&quot;https://waymo.com/open/about/&quot;&gt;Open Datasets&lt;/a&gt; | - Labelled Camera Data - Labels for LiDAR - 3D bounding boxes - Sensor data                      | Non-Commercial Use                      |
| Lyft     | &lt;a href=&quot;https://level5.lyft.com/dataset/&quot;&gt;Level 5&lt;/a&gt;          | - Labelled Camera Data, LiDAR - 3D bounding boxes - Drivable surface map  - Spatial semantic map | CC BY-NC-SA 4.0[^2]                     |
| Aptiv    | &lt;a href=&quot;https://www.nuscenes.org/overview&quot;&gt;nuScences&lt;/a&gt;        | - Full sensor suite (LiDAR, RADAR, Camera, IMU, GPS) - 3D bounding boxes - Detailed map information | CC BY-NC-SA 4.0[^2]                     |
| Berkeley | &lt;a href=&quot;https://bdd-data.berkeley.edu&quot;&gt;DeepDrive&lt;/a&gt;        | - Labelled Caemera (Data, IMU, GPS) - 2D bounding boxes - Drivable surface map - Lane Markings    | BSD 3-Clause &quot;New&quot;  or &quot;Revised&quot;[^3] |&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Summary Table of Autonomous Driving in Urban Area Datasets&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The common goal is an in-depth understanding of perception for autonomous driving vehicles in complex environment such as the urban area.&lt;/p&gt;
&lt;p&gt;While some datasets focus on imaging technologies, others also offer spatial map, surface map, lane marking and furthermore.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Faev-rgb.CSYK0IoX.jpg&amp;#x26;w=1280&amp;#x26;h=720&amp;#x26;f=webp&quot; alt=&quot;&amp;#x22;Close-up with RGB adaptation at AEV HQ of Autonomous Driving Dataset (A2D2)&amp;#x22;&quot; title=&quot;Audi Electronics Venture GmbH public their Autonomous Driving Dataset (A2D2). Close-up with RGB adaptation at AEV HQ&quot;&gt;&lt;/p&gt;
&lt;p&gt;Another interesting aspects is their licenses where Waymo, Lyft, Aptiv (&lt;em&gt;Waymo Dataset License Agreement for Non-Commercial Use&lt;/em&gt;, &lt;em&gt;CC BY-NC-SA 4.0&lt;/em&gt;[^2]) explicitly state that the dataset is &lt;strong&gt;intended for research&lt;/strong&gt; and require a license for any Commercial Use.&lt;/p&gt;
&lt;p&gt;On the other hand, Berkeley and Audi with their license (&lt;em&gt;BSD 3-Clause&lt;/em&gt;[^3], &lt;em&gt;CC BY-ND 4.0&lt;/em&gt;[^1]) means that it is &lt;strong&gt;permissble for commercialization&lt;/strong&gt; of the technology developed based on such datasets under one modification: the copyrights from the original dataset left intact.&lt;/p&gt;
&lt;p&gt;[^1]: Creative Commons Attribution–NonDerivative 4.0
[^2]: Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License
[^3]: Berkeley Software Distribution&lt;/p&gt;</content:encoded><h:img src="/_astro/aev-rgb.CSYK0IoX.jpg"/><enclosure url="/_astro/aev-rgb.CSYK0IoX.jpg"/></item><item><title>BMW test-drive Personal CoPilot Automated Vehicle in Japan</title><link>https://tin.ng/blog/2019-09-26--bmw-personal-copilot-level-4-av</link><guid isPermaLink="true">https://tin.ng/blog/2019-09-26--bmw-personal-copilot-level-4-av</guid><description>BMW Japan unveiled its SAE L4 test vehicle and publicly offered driving experience</description><pubDate>Thu, 26 Sep 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;p&gt;The test-drive, as showed in the video was offered in the following manner:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Dispatching via a smartphone and the parked vehicle comes and picks the passenger up&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Unlocking, also via a smartphone and the passenger gets seated in the back and starts the car using a panel installed in the middle of the rear seats&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The vehicle automatically drives in between other vehicles and stops when another vehicle gets in the way&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The passenger gets off, and the vehicle parks by itself&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Ftest-drive.DENjmDod.jpg&amp;#x26;w=938&amp;#x26;h=536&amp;#x26;f=webp&quot; alt=&quot;&amp;#x22;BMW SAE L4 Driving Experience&amp;#x22;&quot; title=&quot;Autonomous BMW 7-Series in Japan with panel controller in the passenger seat. Source: Response.jp&quot;&gt;&lt;/p&gt;
&lt;p&gt;The vehicle is BMW 7 Series-based and equipped with&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Two&lt;/strong&gt; GPS antennas on the car roof&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Four&lt;/strong&gt; LiDARs/radars at each vehicle corner&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Five&lt;/strong&gt; cameras: one on each A-pillar; one on each front fender; one on the rear of the car roof&lt;/li&gt;
&lt;li&gt;As well as the conventional trifocal camera, front camera, and rear camera, or sonars and accelerometers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fad-system.xJ5Y-vtY.jpg&amp;#x26;w=938&amp;#x26;h=704&amp;#x26;f=webp&quot; alt=&quot;&amp;#x22;BMW Personal Copilot System in the 7-Series Trunk&amp;#x22;&quot; title=&quot;The trunk is filled with devices such as data logger and I/O unit of sensor channel&quot;&gt;&lt;/p&gt;
&lt;p&gt;Read more about the exhibition from &lt;a href=&quot;https://www.bmwgroup.com/en/NEXTGen/automated_driving.html&quot;&gt;BMW&lt;/a&gt; or &lt;a href=&quot;https://www.bmw.co.jp/ja/topics/brand-and-technology/experience/nextjoy.html&quot;&gt;original report&lt;/a&gt; of BMW JP.&lt;/p&gt;
&lt;hr&gt;</content:encoded><h:img src="/_astro/autonomous-drive.YFZqxY8L.jpg"/><enclosure url="/_astro/autonomous-drive.YFZqxY8L.jpg"/></item><item><title>Mercedes-Benz and Bosch operate Automated Ride-hailing service</title><link>https://tin.ng/blog/2019-09-12--mercedes-benz-bosch-automated-ride-hailing-service</link><guid isPermaLink="true">https://tin.ng/blog/2019-09-12--mercedes-benz-bosch-automated-ride-hailing-service</guid><description>A mobility service went into operation in the Bay Area this Fall 2019</description><pubDate>Thu, 12 Sep 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Video from &apos;../../../components/common/Video.astro&apos;
import sensing from &quot;./sensing.mp4&quot;;&lt;/p&gt;
&lt;p&gt;After Hyundai Motor launched a so-called &quot;BotRide&quot; robotaxi test in Irvine, California, Mercedes-Benz and Bosch pilot their own Ride-hailing program in San José. This is the result of both companies working together for more than two years on solving issues related to operating autonomous vehicles safely on crowded city streets.&lt;/p&gt;
&lt;p&gt;The common goal is an SAE Level 4/5 driving system for fully automated and driverless vehicles, including the software for vehicle management.&lt;/p&gt;
&lt;p&gt;While Mercedes-Benz provides the vehicles, Bosch develops and manufactures components for urban automated driving. Participants hail a ride using an app developed by Daimler Mobility AG which allows potential ride-hailing partners to seamlessly integrate self-driving vehicles into their daily routine.&lt;/p&gt;
&lt;p&gt;Read more about the platform and their pilot service from &lt;a href=&quot;https://www.daimler.com/innovation/case/autonomous/pilot-city-san-jose.html&quot;&gt;Dailmer&lt;/a&gt; and &lt;a href=&quot;https://www.bosch-presse.de/pressportal/de/en/bosch-and-mercedes-benz-start-san-jose-pilot-project-for-automated-ridesharing-service-204032.html&quot;&gt;Bosch&lt;/a&gt; media outlets.&lt;/p&gt;</content:encoded><h:img src="/_astro/freeway.Da9_jkod.jpg"/><enclosure url="/_astro/freeway.Da9_jkod.jpg"/></item><item><title>Task Scheduler in Real-time OSes (Part 1)</title><link>https://tin.ng/blog/2019-09-02--task-scheduler-in-rtos-part-1</link><guid isPermaLink="true">https://tin.ng/blog/2019-09-02--task-scheduler-in-rtos-part-1</guid><description>Define OS Scheduling based on methodologies such as Timeslice/Priority</description><pubDate>Mon, 02 Sep 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h1&gt;Overview&lt;/h1&gt;
&lt;p&gt;This article is part 1 of an overview series specifically about the &lt;em&gt;Scheduler&lt;/em&gt; in a &lt;strong&gt;Real-time Operating System (RTOS)&lt;/strong&gt;. Examples of such systems can be found in almost anything from automotive application such as airbags, emergency breaks to avionics, and also infotainment, multi-media systems like video playback and QoS in web servers.&lt;/p&gt;
&lt;p&gt;This means that they both have to look at the output of the system and at the time at which the output was generated to determine whether an execution is successful.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2020-06-05--task-scheduler-in-rtos-part-2&quot;&gt;Read Part 2: Task Scheduler in RTOS&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Prerequisite&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Assuming you have foundation knowledge about
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Programming&lt;/em&gt;, &lt;em&gt;Big-O Notation&lt;/em&gt;, &lt;em&gt;Algorithm&lt;/em&gt;, &lt;em&gt;Interrupt&lt;/em&gt; and &lt;em&gt;Computer Architecture&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Real-time Operating System (RTOS)&lt;/em&gt; knowledge would make it more comprehensive&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;More about type of Real-time Systems coming later.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(Optional) Schedule Simulation software &lt;a href=&quot;http://projects.laas.fr/simso/&quot;&gt;&lt;strong&gt;SimSo&lt;/strong&gt;&lt;/a&gt; which offer all Platform Compatibility (&lt;em&gt;Python-powered&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Understand the needs for Scheduler&lt;/h1&gt;
&lt;p&gt;That said, in the heart of RTOS is the Scheduler and as the software complexity advances, the scheduling become more complex and thus expect extreme scheduling methods. For instance, there are only two &lt;strong&gt;Electronic Control Units (ECUs)&lt;/strong&gt; in luxury class cars in mid-80s to control most of the Engine, Exhaust, etc. and by the time this article was written, the BMW 7-series models have as many as &lt;em&gt;150 ECUs&lt;/em&gt;&lt;a href=&quot;https://www.embitel.com/blog/embedded-blog/automotive-control-units-development-innovations-mechanical-to-electronics&quot;&gt;^1&lt;/a&gt; and average at 80 ECUs in total.&lt;/p&gt;
&lt;p&gt;There are primarily two distinction of Real-time Systems, a missed deadline&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In &lt;strong&gt;Hard Real-time&lt;/strong&gt;: is &lt;strong&gt;catastrophic&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;In &lt;em&gt;Soft Real-time&lt;/em&gt;: can lead to &lt;em&gt;significant loss&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One important concern in such a system like RTOS is the predictability of system behavior, meaning there should be no space for un-accountability, everything must be &lt;em&gt;accountable&lt;/em&gt; and &lt;em&gt;predictable&lt;/em&gt;. This is often achieved by either &lt;em&gt;static&lt;/em&gt; or &lt;em&gt;dynamic scheduling&lt;/em&gt; of Real-time tasks to meet their deadlines.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Frtos-taxonomy.6xIpVdgu.png&amp;#x26;w=680&amp;#x26;h=588&amp;#x26;f=webp&quot; alt=&quot;Taxonomy of Real-time Systems Scheduling Scheme&quot;&gt;
&lt;em&gt;Taxonomy of Real-time Systems Scheduling Scheme. Classified by system type and scheduling mechanism.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Task Structure&lt;/h2&gt;
&lt;p&gt;The Task Structure in RTOS includes the planning and the scheduling of workload on a processor and  guarantees that the workload timeline is never violated. That workload can be quantified into discrete pieces, i.e. &lt;strong&gt;a Task&lt;/strong&gt;. The functionality of a Task will be executed over and over again and thus differentiate between each iteration by an &lt;em&gt;instance of a task&lt;/em&gt;, a.k.a. &lt;strong&gt;a Job&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A periodic task, which will release a new job every time units, is usually defined by&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Period (P):&lt;/strong&gt; time interval between releases of jobs in a task
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Side note:&lt;/strong&gt; in reality, this interval is not necessarily constant with the presence of &lt;a href=&quot;#task-jitter&quot;&gt;jitter&lt;/a&gt; between task transition.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execution time ($$E_t$$):&lt;/strong&gt; how long the job is&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deadline (D):&lt;/strong&gt; the deadline of a task&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Ftask-structure.Ch-TIo6Z.png&amp;#x26;w=796&amp;#x26;h=186&amp;#x26;f=webp&quot; alt=&quot;Example of Task Structure&quot;&gt;
&lt;em&gt;Example of Task $T_1$ with $P=3$ , $E_t=1s$ on x-axis of timeslice $t=1$.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Since there is a latest time at which a task must be completed, all tasks must never violate its deadline. Although, a deadline can be defined as &lt;em&gt;absolute&lt;/em&gt; or &lt;em&gt;relative&lt;/em&gt; in &lt;em&gt;Hard Real-time&lt;/em&gt; or &lt;em&gt;Soft Real-time&lt;/em&gt; respectively.&lt;/p&gt;
&lt;p&gt;The longer the execution time, the more CPU resources are needed and if $$E_t$$ becomes too large, the CPU gets overloaded and it cannot handle all the tasks within the time limit.&lt;/p&gt;
&lt;h2&gt;Task Jitter&lt;/h2&gt;
&lt;p&gt;From the Task Structure, it is necessary to ensure the consistency between tasks and the amount of time it takes to complete current task and move on to the next task while not overloading the CPU. In reality, the transition is delayed due to various reasons: &lt;em&gt;current task not completely finished&lt;/em&gt;, &lt;em&gt;resource is not released&lt;/em&gt; and furthermore.&lt;/p&gt;
&lt;p&gt;The total amount of time for such delay is called &lt;em&gt;Task Jitter&lt;/em&gt;. Ideally, &lt;strong&gt;no jitter is desirable&lt;/strong&gt;, and Hard real-time scheduling ensures that with extremely precise scheduling, leaving no wasted time between task transition or &lt;a href=&quot;#data-inconsistency&quot;&gt;context switching&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; A &lt;em&gt;Hard Real-time&lt;/em&gt; OS has &lt;strong&gt;less jitter&lt;/strong&gt; than a &lt;em&gt;Soft Real-time OS&lt;/em&gt; in general.&lt;/p&gt;
&lt;p&gt;Example from this &lt;a href=&quot;https://stackoverflow.com/a/34077698&quot;&gt;SO discussion&lt;/a&gt; clearly demonstrate the scenario where Task Jitter is inevitable.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A task should be started after 10ms, but for whatever reason, it is started after 15ms. In our example the jitter is 5ms.&lt;/p&gt;
&lt;p&gt;All tasks would use no more than 100ms per scheduled time, a task that requires and uses 150ms would cause significant scheduling jitter.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Data Inconsistency&lt;/h2&gt;
&lt;p&gt;When the scheduler switches the CPU from executing one task to execute another, the resource from the current running task is released for another task, usually higher priority, to take control and this process is called context switching.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;Context Switch&lt;/strong&gt; enables multiple tasks to share a single CPU by storing/restoring the state and context so that the execution can be preempted and resumed later &lt;em&gt;without interrupting current progress&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The context/shared memory require special attention since switching tasks will inevitably causing data inconsistency where any task could &lt;em&gt;alter the values&lt;/em&gt; or worse &lt;em&gt;completely corrupt it&lt;/em&gt;. This is partly solved by &lt;em&gt;creating a temporary lock on the resources&lt;/em&gt;, &lt;em&gt;buffer snapshot&lt;/em&gt; or other mechanisms such as &lt;strong&gt;Semaphore (Signaling)&lt;/strong&gt; or &lt;strong&gt;Mutual Exclusion (Locking)&lt;/strong&gt; to maintain the correct values between tasks.&lt;/p&gt;
&lt;h2&gt;The O(1) Task Scheduler in Linux&lt;/h2&gt;
&lt;p&gt;Another reason for the needs of a Scheduler is efficiency when it comes to &lt;em&gt;task priority&lt;/em&gt;, what instruction should run first which would require more computing resources to calculate the order. This is what the O(1) Task Scheduler introduced in &lt;strong&gt;Linux 2.6&lt;/strong&gt; trying to solve.&lt;/p&gt;
&lt;p&gt;Essentially, there is a &lt;em&gt;First-In-First-Out&lt;/em&gt; (FIFO) &lt;strong&gt;Active Runqueues&lt;/strong&gt; and another FIFO &lt;strong&gt;Expired Runqueues&lt;/strong&gt;, each with their own tasks and their priorties. All tasks are scheduled based on priorities inside the Active Runqueue and ran based on a pre-defined &lt;strong&gt;timeslice&lt;/strong&gt;, and swap in the Tasks from Expired Runqueue once similar priorities from Active Runqueue is emptied.&lt;/p&gt;
&lt;p&gt;This makes the Linux 2.6 Task Scheduler an O(1) process because the time to schedule is both &lt;em&gt;fixed&lt;/em&gt; and &lt;em&gt;deterministic&lt;/em&gt; regardless of the number of active tasks.&lt;/p&gt;
&lt;h1&gt;Acknowledgement&lt;/h1&gt;
&lt;p&gt;This series is heavily influenced by a &lt;a href=&quot;https://www.coursera.org/learn/real-time-systems/&quot;&gt;Coursera online course&lt;/a&gt; created by the &lt;strong&gt;EIT-Digital University&lt;/strong&gt; in &lt;em&gt;Turku&lt;/em&gt;, &lt;em&gt;Finland&lt;/em&gt; as part of the &lt;em&gt;on-campus course on Real-Time Systems&lt;/em&gt; given in their Embedded Systems &lt;em&gt;Master&apos;s&lt;/em&gt; curriculum.&lt;/p&gt;</content:encoded><h:img src="/_astro/rtos-taxonomy.6xIpVdgu.png"/><enclosure url="/_astro/rtos-taxonomy.6xIpVdgu.png"/></item><item><title>The first fully automated driverless valet parking system</title><link>https://tin.ng/blog/2019-07-26--bosch-mercedes-benz-automated-parking-system</link><guid isPermaLink="true">https://tin.ng/blog/2019-07-26--bosch-mercedes-benz-automated-parking-system</guid><description>Bosch and Daimler collaboration in the Mercedes-Benz Museum in Stuttgart, Germany</description><pubDate>Tue, 16 Jul 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;p&gt;In recent attempt to introduce the world first ever system that categorized at SAE Level 4[^1], two partners with a common objective, Bosch and Daimler, have reached a milestone on the way to automated driving: the joint force have now obtained &lt;strong&gt;approval from the relevant authorities in Baden-Württemberg for their automated parking system&lt;/strong&gt; in the Mercedes-Benz Museum parking garage in Stuttgart.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fautomated-valet-parking.RmI0ECDR.jpg&amp;#x26;w=1024&amp;#x26;h=597&amp;#x26;f=webp&quot; alt=&quot;Automated Valet Parking system without human supervision in Stuttgart, Germany&quot;&gt;
&lt;em&gt;Automated Valet Parking system without human supervision in Stuttgart, Germany&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The developed automated valet parking service is accessible via a &lt;em&gt;smartphone app&lt;/em&gt; and &lt;strong&gt;requires absolutely no safety driver&lt;/strong&gt;. This makes it the world’s first fully automated driverless SAE Level 4¹ parking function to be officially approved for everyday use.&lt;/p&gt;
&lt;p&gt;“This decision by the authorities shows that innovations like automated valet parking are possible in Germany first,” says Dr. Markus Heyn, member of the board of management of Robert #Bosch GmbH. “Driverless driving and parking are important building blocks for tomorrow’s mobility. The automated parking system shows just how far we have already progressed along this development path.&quot;&lt;/p&gt;
&lt;p&gt;“This approval from the Baden-Württemberg authorities sets a precedent for obtaining approval in the future for the parking service in parking garages around the world,” says Dr. Michael Hafner, the head of drive technologies and automated driving at #Daimler AG. “As a pioneer in automated driving, our project paves the way for automated valet parking to go into mass production in the future.”&lt;/p&gt;
&lt;p&gt;Read more about their achivement at Daimler AG official &lt;a href=&quot;https://media.daimler.com/marsMediaSite/en/instance/ko/World-first-Bosch-and-Daimler-obtain-approval-for-driverless-parking-without-human-supervision.xhtml?oid=43968944&quot;&gt;media outlet&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[^1]: About SAE Level 4&lt;/p&gt;</content:encoded><h:img src="/_astro/automated-valet-parking.RmI0ECDR.jpg"/><enclosure url="/_astro/automated-valet-parking.RmI0ECDR.jpg"/></item><item><title>Geolocation accuracy improvement for vehicle tracking</title><link>https://tin.ng/blog/2019-04-21--geolocation-accuracy-improvement-in-urban-area</link><guid isPermaLink="true">https://tin.ng/blog/2019-04-21--geolocation-accuracy-improvement-in-urban-area</guid><description>Basic GPS and Google Map APIs for better render of moving vehicles</description><pubDate>Sun, 21 Apr 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;
import { Collapse } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h1&gt;Overview&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Global Position System&lt;/strong&gt; (GPS) is a satellite-based navigation system &lt;a href=&quot;https://www.gps.gov/technical/ps/2008-SPS-performance-standard.pdf&quot;&gt;operated by&lt;/a&gt; the &lt;strong&gt;United States Air Force&lt;/strong&gt; which provide the &lt;em&gt;geolocation&lt;/em&gt;, &lt;em&gt;time&lt;/em&gt; and &lt;em&gt;derived velocity&lt;/em&gt; where there is an unobstructed line of sight to &lt;a href=&quot;https://www.gps.gov/systems/gps/performance/accuracy/&quot;&gt;four or more&lt;/a&gt; GPS satellites with the accuracy ranging &lt;strong&gt;from 100ft to 15ft&lt;/strong&gt; (30m - 4.5m) radius.&lt;/p&gt;
&lt;p&gt;For instance, mobile phone utilizes &lt;strong&gt;Assisted GPS&lt;/strong&gt; (A-GPS) from telecommunication tower cells to &lt;em&gt;speed up&lt;/em&gt; the triangulation process and provide a better result of the actual location. With Google Maps, the live geolocation can be &lt;em&gt;displayed&lt;/em&gt; and even &lt;em&gt;broadcasted&lt;/em&gt; in &lt;strong&gt;real-time&lt;/strong&gt; with the help of already collected geography data to place the vehicle’s position onto the correct path via &lt;strong&gt;Google Map SnapToRoad API&lt;/strong&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ultimately, the goal for this type of application is to ensure the trajectory of moving vehicles is correct, i.e. moving on roads and provide better user experiences.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;Prerequisite&lt;/h1&gt;
&lt;p&gt;Follow the official documentations to setup your &lt;a href=&quot;https://support.google.com/accounts/answer/27441?hl=en&quot;&gt;Google account&lt;/a&gt; and create a &lt;a href=&quot;https://cloud.google.com/resource-manager/docs/creating-managing-projects&quot;&gt;Google Cloud Platform Project&lt;/a&gt; with &lt;a href=&quot;https://cloud.google.com/billing/docs/how-to/manage-billing-account&quot;&gt;Billing Enabled&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Google Free Tier introduces a 12 months, $300 trial 1 for new user to learn the basics with several &lt;a href=&quot;https://cloud.google.com/free/docs/gcp-free-tier&quot;&gt;limitations&lt;/a&gt; which will not be an issue for our setup.&lt;/p&gt;
&lt;p&gt;Well ! To be perfectly candid, it does depend on how much requests you invoke for your map display, so make sure to verify it and there are options to limit user request or malicious attempt to work up your bill.&lt;/p&gt;
&lt;h1&gt;GPS Information Standard&lt;/h1&gt;
&lt;p&gt;The &lt;strong&gt;National Marine Electronics Association&lt;/strong&gt; (NMEA) has developed a specification that defines the &lt;em&gt;interface&lt;/em&gt; between various pieces of marine electronic equipment. Their &lt;em&gt;standard&lt;/em&gt; permits geo-positioning electronics to send information to computers and to other marine equipment.&lt;/p&gt;
&lt;h2&gt;NMEA Sentences&lt;/h2&gt;
&lt;p&gt;GPS receiver communication is defined within this specification with the idea is to send a line of data called an &lt;strong&gt;NMEA sentence&lt;/strong&gt; that describes essential piece of information that was pre-defined for sets of sentence and serving multi-purposes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2FGPGGA_sentence.BfmrGOFZ.jpg&amp;#x26;w=802&amp;#x26;h=280&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;GPS receivers with the prefix of GP and is followed by a three letter sequence that defines the sentence contents.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GGA&lt;/strong&gt; - Essential fix data which provide 3D location and accuracy data&lt;/p&gt;
&lt;p&gt;Similarly, an analysis of another commonly used NMEA sentence can be found tabulated as below.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fgprmc_table.Bdd-wElM.jpg&amp;#x26;w=1186&amp;#x26;h=802&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Recommended Minimum Data for GPS (GPRMC) data line analysis&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;AAM - Waypoint Arrival Alarm
ALM - Almanac data
APA - Auto Pilot A sentence
APB - Auto Pilot B sentence
BOD - Bearing Origin to Destination
BWC - Bearing using Great Circle route
DTM - Datum being used.
GGA - Fix information
GLL - Lat/Lon data
GRS - GPS Range Residuals
GSA - Overall Satellite data
GST - GPS Pseudorange Noise Statistics
GSV - Detailed Satellite data
MSK - send control for a beacon receiver
MSS - Beacon receiver status information.
RMA - recommended Loran data
RMB - recommended navigation data for gps
RMC - recommended minimum data for gps
RTE - route message
TRF - Transit Fix Data
STN - Multiple Data ID
VBW - dual Ground / Water Spped
VTG - Vector track an Speed over the Ground
WCV - Waypoint closure velocity (Velocity Made Good)
WPL - Waypoint Location information
XTC - cross track error
XTE - measured cross track error
ZTG - Zulu (UTC) time and time to go (to destination)
ZDA - Date and Time
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Geolocation Stream&lt;/h2&gt;
&lt;p&gt;The initial step to get GPS data took place in &lt;strong&gt;simulation environment&lt;/strong&gt; for a better understanding of the &lt;em&gt;NMEA sentences&lt;/em&gt;, specifically tailored for the GPS Receiver, i.e. &lt;em&gt;GPRMC&lt;/em&gt;, &lt;em&gt;GPGGA&lt;/em&gt; and etc.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2FrawGPS_proteus.CwI3FrBh.jpg&amp;#x26;w=1258&amp;#x26;h=314&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Simulated geolocation data stream in Proteus using Virtual Terminal. Any microcontrollers support Serial Communication can also be used.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In actual implementation, the similar set of strings (line of data) can also be found.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2FrawGPS_arduino.BMKi9a-a.jpg&amp;#x26;w=1556&amp;#x26;h=644&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Actual geolocation data stream from GPS receiver to a microcontroller within the Arduino Leonardo board.&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;SnapToRoad API&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Application Programming Interface&lt;/strong&gt; (API) acting as a &lt;em&gt;medium&lt;/em&gt; between different programming languages, platforms and hence the name. That is, the Google Map API provides a convenience approach to utilizes the &lt;em&gt;geography data&lt;/em&gt; that are necessary to correctly identify the roads without too much hassle.&lt;/p&gt;
&lt;h2&gt;Syntax Explanation&lt;/h2&gt;
&lt;p&gt;With Google Map Java API enabled, the &lt;a href=&quot;https://developers.google.com/maps/documentation/roads/snap&quot;&gt;SnapToRoad&lt;/a&gt; specifically can be invoked using &lt;strong&gt;HTTP Request&lt;/strong&gt;.  The result is a similar set of data, with the points snapped to the &lt;em&gt;most likely&lt;/em&gt; roads the vehicle was traveling along.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;https://roads.googleapis.com/v1/snapToRoads?parameters&amp;#x26;key=YOUR_API_KEY
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;strong&gt;required parameters&lt;/strong&gt; include the &lt;code&gt;path&lt;/code&gt;, i.e. &lt;em&gt;latitude/longitude pair&lt;/em&gt; divided by the character &lt;code&gt;|&lt;/code&gt; and obviously your &lt;code&gt;API_Key&lt;/code&gt;. For instance, a set of three coordinates with &lt;em&gt;optional&lt;/em&gt; flag &lt;code&gt;interpolate&lt;/code&gt; enabled in complete HTTP request form as below.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;https://roads.googleapis.com/v1/snapToRoads?path=10.7768936,106.6999664|10.7781286,106.6987686|10.7787838,106.6979217|10.7799883&amp;#x26;interpolate=true&amp;#x26;key=xxxxxxxxx
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Compared Results&lt;/h2&gt;
&lt;p&gt;The below results are indication of three different implementation including   Raw Geolocation,    Filtered Digitally,    Roads API displayed using &lt;em&gt;Google MyMap&lt;/em&gt; and were tested under two opposite scenarios, i.e. &lt;strong&gt;freeway express&lt;/strong&gt; and &lt;strong&gt;downtown centre&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fsuburban_accuracy.DQqQVhfh.jpg&amp;#x26;w=637&amp;#x26;h=342&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;On Freeway with almost no tall building and trees, the Raw Geolocation data is slightly off at only 26ft (8m) but still can be improved as in the case of Filtered Digitally and Roads API&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The main target for geographically monitoring a large amount of vehicles is usually resided in &lt;strong&gt;urban area&lt;/strong&gt; where it is &lt;em&gt;crowed&lt;/em&gt; with enormous wireless data transmission and signals &lt;em&gt;bounced off&lt;/em&gt; tall buildings as well as other potential interference.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Furban_accuracy.BeZxphFH.jpg&amp;#x26;w=637&amp;#x26;h=479&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;In Downtown Centre, a median of 55ft (17m) was found of the Raw Geolocation and while relatively better, Filtered Digitally still could not compare to Roads API in term of accuracy&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Troubleshooting (Extra)&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Authorization issues with API Key&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Head to Google &lt;a href=&quot;https://console.developers.google.com/&quot;&gt;API Console&lt;/a&gt; for Developer and select your project.&lt;/p&gt;
&lt;p&gt;Then hit Tab &lt;code&gt;APIs&lt;/code&gt;  $$\rightarrow$$ Under Enabled APIs select &lt;code&gt;Roads API&lt;/code&gt; $$\rightarrow$$ Select &lt;code&gt;Credentials&lt;/code&gt; sub-menu and request for new API Key. Similar &lt;a href=&quot;http://tin.ng/2019/03/05/How-to-setup-and-remotely-access-to-vm-instances-on-cloud/&quot;&gt;instruction&lt;/a&gt; for Cloud Console in another article.&lt;/p&gt;
&lt;hr&gt;</content:encoded><h:img src="/_astro/suburban_accuracy.DQqQVhfh.jpg"/><enclosure url="/_astro/suburban_accuracy.DQqQVhfh.jpg"/></item><item><title>Configure Graphical UI access to VMs on Google Compute Engine</title><link>https://tin.ng/blog/2019-04-16--graphical-interface-for-virtual-machine</link><guid isPermaLink="true">https://tin.ng/blog/2019-04-16--graphical-interface-for-virtual-machine</guid><description>After setup the VM, graphical interface can be installed via SSH</description><pubDate>Tue, 16 Apr 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h1&gt;Overview&lt;/h1&gt;
&lt;p&gt;Google Compute Engine is an &lt;em&gt;Infrastructure as a Service&lt;/em&gt; (IaaS) component of the *Google Cloud Platform *(GCP) and aiming to &lt;strong&gt;provide specific hardware&lt;/strong&gt; and &lt;strong&gt;utilize the cloud environment&lt;/strong&gt; for development as well as production.&lt;/p&gt;
&lt;p&gt;With Compute Engine, an instance could be generated with a couple of clicks with &lt;em&gt;“a custom Machine Types optimized for your specific needs”&lt;/em&gt;.  This is a great tool alongside with Docker, Kubernetes, etc. and is a game changer in the software industry and definitely a must-have tool.&lt;/p&gt;
&lt;h1&gt;Prerequisite&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;If you have &lt;a href=&quot;./2019-03-05--remote-virtual-machine-on-gcp&quot;&gt;already created your own VM instance&lt;/a&gt;, well done ! This is the next part to setup a Graphical User Interface (GUI) for your virtual computer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;VM setups&lt;/h1&gt;
&lt;p&gt;There are two essential components required in order for this to work includes a &lt;em&gt;desktop environment&lt;/em&gt; and the &lt;em&gt;Virtual Network Computing&lt;/em&gt; (VNC) tool.&lt;/p&gt;
&lt;h2&gt;Install Desktop Environment&lt;/h2&gt;
&lt;p&gt;A CLI is differentiated based on the type of OS image and commonly is a &lt;em&gt;Terminal&lt;/em&gt;, &lt;em&gt;Console&lt;/em&gt; or some specific application with &lt;em&gt;Shell&lt;/em&gt; (an interface) on it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sudo apt-get update
apt-get install xfce4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;sudo&lt;/code&gt;: Admin Privilege&lt;/p&gt;
&lt;p&gt;&lt;code&gt;apt-get&lt;/code&gt;: Package Manager&lt;/p&gt;
&lt;p&gt;&lt;code&gt;xfce4&lt;/code&gt;: Desktop Environment&lt;/p&gt;
&lt;h2&gt;Install VNC Client&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;apt-get install tightvncserver
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;tightvncserver&lt;/code&gt;: VNC Client&lt;/p&gt;
&lt;p&gt;After successful installation, run command &lt;code&gt;tightvncserver&lt;/code&gt; to initialize the server and identify the &lt;code&gt;xstartup&lt;/code&gt; file in order to register installed desktop environment.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fdesktop_env.DWCGMV8C.jpg&amp;#x26;w=1124&amp;#x26;h=710&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Register newly installed desktop environment in VNC server startup file&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sudo vim /home/user-name/.vnc/xstartup
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;vim&lt;/code&gt;: Text Editor, &lt;code&gt;:wq&lt;/code&gt; to Save &amp;#x26; Quit. &lt;strong&gt;Trust me you will need it !&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ultimately, follow the instruction to setup &lt;strong&gt;credential key&lt;/strong&gt;, this will be the &lt;strong&gt;access password&lt;/strong&gt; for the upcoming VNC session.&lt;/p&gt;
&lt;h1&gt;Network Connectivity&lt;/h1&gt;
&lt;p&gt;After you have successfully installed all required components, test run the VNC server and its connectivity:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;tightvncserver
nc localhost 5901
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Finit_vnc.D3Pgmuka.jpg&amp;#x26;w=854&amp;#x26;h=286&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Success initialization of the VNC Server. Port &lt;code&gt;5901&lt;/code&gt; returns value meaning it is listening and &lt;code&gt;RFB&lt;/code&gt; indicates our server is up and running&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;nc&lt;/code&gt; net cat Command. More on the command &lt;em&gt;manual&lt;/em&gt; with &lt;code&gt;man nc&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;#x3C;5901&gt;&lt;/code&gt;: Default VNC Connection Port (5800/5900), increment 1 for every new session (next, X:2 5902, X:3 5903, etc.)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;RFB&lt;/code&gt;: Remote Frame Buffer, this means the port is &lt;em&gt;up&lt;/em&gt; and &lt;em&gt;listening&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Configure port for remote access&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Before configure any settings&lt;/strong&gt;, &lt;em&gt;always backup&lt;/em&gt; or at least write down every step so that it’s easier for you to trace back later. Also, &lt;em&gt;turn off VNC server&lt;/em&gt; and close port &lt;code&gt;tightvncserver -kill: X&lt;/code&gt; with X essentially the number of your session.&lt;/p&gt;
&lt;p&gt;From the &lt;em&gt;Compute Engine&lt;/em&gt; page, go to your vm tab - click on more (at the right end) and select &lt;em&gt;view network details&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;From the Firewall Rules page, select &lt;em&gt;Create Firewall Rule&lt;/em&gt; on the top bar.&lt;/p&gt;
&lt;p&gt;Modify these fields:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Name: &lt;em&gt;default-vnc-server&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Description: &lt;em&gt;instance-name&lt;/em&gt; ; &lt;em&gt;vnc-server&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Source Filter: &lt;strong&gt;Select&lt;/strong&gt; IP Range&lt;/li&gt;
&lt;li&gt;Source IP Range: &lt;strong&gt;Type&lt;/strong&gt; “0.0.0.0/0”, allow all incoming connection&lt;/li&gt;
&lt;li&gt;Specified Protocol and Ports: &lt;strong&gt;Type&lt;/strong&gt; “tcp:5901”, specify for VNC&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then leave the rest as default (modify only when you are sure what it does) and &lt;strong&gt;hit create&lt;/strong&gt;. And &lt;em&gt;voila&lt;/em&gt; now everything is running and establishing a connection !&lt;/p&gt;
&lt;h2&gt;Establish VNC Connection&lt;/h2&gt;
&lt;p&gt;The only step left is to find your VM External (Global) IP Address and access into it from your &lt;strong&gt;local&lt;/strong&gt; computer.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;External IP from the Developer console&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fexternal_ip.DquXFvUV.jpg&amp;#x26;w=1512&amp;#x26;h=388&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Retrieve External IP Address of your instance from &lt;strong&gt;Compute Engine&lt;/strong&gt; page on the Cloud console&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;External IP from 3rd party provider&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;curl ifconfig.me
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; This method &lt;em&gt;contains security risk&lt;/em&gt; for obvious reason.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Once having the External IP&lt;/strong&gt; of your instance, access to it using any of the available VNC Viewer for your computer OS.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Built-in VNC viewer for macOS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Every macOS computer contains a built-in VNC viewer and can be quickly activate via Finder shortcut &lt;code&gt;⌘ + K&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fvnc_native.BmGvZKQt.jpg&amp;#x26;w=968&amp;#x26;h=460&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;VNC session using native tool &lt;strong&gt;Screen Sharing&lt;/strong&gt; on &lt;strong&gt;macOS 10.14.1&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The syntax is &lt;code&gt;&amp;#x3C;protocol&gt;://&amp;#x3C;global-ip&gt;:&amp;#x3C;port&gt;&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Third-party VNC Viewer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fvnc_viewer.C120YVUH.jpg&amp;#x26;w=980&amp;#x26;h=527&amp;#x26;f=webp&quot; alt=&quot;&quot; title=&quot;VNC session using **Jump Desktop** on **macOS 10.14.1**. Others: **VNC Viewer** (RealVNC), **Screen 4**&quot;&gt;&lt;/p&gt;
&lt;h1&gt;Troubleshooting (Extra)&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Issue with Desktop Environment configuration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Re-check if the Default Session, i.e. which DE to start up, is correct in the Display Managers: lightdm, GDM&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Forgot VNC Server Password (tightvncserver)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;On vm instance&lt;/strong&gt;, type &lt;code&gt;vncpasswd&lt;/code&gt; into the CLI and follow instruction to reset the password.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;VNC Server port not forwarding&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Re-check the Firewall settings of your computer for issue such as SSH permission, SSH tunneling config.&lt;/p&gt;
&lt;p&gt;Also, from &lt;em&gt;Compute Engine&lt;/em&gt; page, &lt;em&gt;click&lt;/em&gt; on your VM and select &lt;em&gt;edit&lt;/em&gt;. In network tag, add the &lt;code&gt;default-vnc-server&lt;/code&gt; tag or the equivalent name that you &lt;a href=&quot;#configure-port-for-remote-access&quot;&gt;created before&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;</content:encoded><h:img src="/_astro/vnc_viewer.C120YVUH.jpg"/><enclosure url="/_astro/vnc_viewer.C120YVUH.jpg"/></item><item><title>Jekyll Category and Tag Taxonomies for custom Collection</title><link>https://tin.ng/blog/2019-03-17--jekyll-category-tag-for-custom-collection</link><guid isPermaLink="true">https://tin.ng/blog/2019-03-17--jekyll-category-tag-for-custom-collection</guid><description>A work-around provides Category and Tag iterator for custom Collections in Jekyll static blog generator</description><pubDate>Sun, 17 Mar 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h1&gt;Overview&lt;/h1&gt;
&lt;p&gt;The main purpose of this &lt;strong&gt;category / tag iterator&lt;/strong&gt; is to create an index page, or a taxonomy. Brief review about Collection will be mentioned, but at this point, you should be able to &lt;em&gt;build&lt;/em&gt;, &lt;em&gt;host&lt;/em&gt; your own site and understand basic functionality of &lt;em&gt;layout&lt;/em&gt;, &lt;em&gt;include&lt;/em&gt; and &lt;em&gt;post&lt;/em&gt; folders.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quick View of the result&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://v3.tin.ng/tags&quot;&gt;Tag Archive&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://v3.tin.ng/categories&quot;&gt;Category Archive&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;An alternative would be using the liquid plugin &lt;code&gt;jekyll-archives&lt;/code&gt;, although as mentioned earlier these additional / non-native plugin could be not supported depend on build on the server host.&lt;/p&gt;
&lt;h1&gt;Prerequisite&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Define a custom Collection&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In case you do not have a custom collection already, it can be quickly configured. In &lt;code&gt;_config.xml&lt;/code&gt;, a custom collection of &lt;code&gt;recipe&lt;/code&gt; is defined as below.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;collections:
  category: recipe
    output: true

defaults:
  - scope:
      path: &quot;&quot;
      type: recipe
    values:
      layout: single
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Usage in article YAML Front Matter&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once the configuration is defined, simply add it in your article. For example, this tutorial has the following tags defined in its Front Matter.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;YAML Front Matter&lt;/strong&gt; is essentially everything inside the &lt;em&gt;divider (3-dashes)&lt;/em&gt; at the beginning of your article.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
categories: [Tutorial, Productivity]
tags: [Jekyll, Taxonomy]
---
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Category Tag Iterator&lt;/h1&gt;
&lt;p&gt;It depends on your purpose to place the following code snippet in the correct place, bu typically this would be in file &lt;code&gt;_layout/categories.html&lt;/code&gt; if you want to create an Index / Taxonomy page.&lt;/p&gt;
&lt;h2&gt;Collect all data from collection&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Assign variable &lt;code&gt;thisCollection&lt;/code&gt; for your custom collection data&lt;/li&gt;
&lt;li&gt;Apply filter to keep only category
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;group_by:&lt;/em&gt; Re-group field data by their &lt;code&gt;category&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;join, split:&lt;/em&gt; Order field data into an array with &lt;code&gt;,&lt;/code&gt; delimeter&lt;/li&gt;
&lt;li&gt;&lt;em&gt;map:&lt;/em&gt; Keep only data belongs to &lt;code&gt;site.categories&lt;/code&gt; i.e. our interested categories.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-liquid&quot;&gt;{% assign thisCollection = site.portfolio %}	
{% assign allCats =  thisCollection | map: &apos;categories&apos; | join: &apos;,&apos;  | split: &apos;,&apos;  | group_by: category %}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Array for unique values&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;This stage is not necessary but recommended&lt;/strong&gt;, if you&apos;re doing indexer, it is best to count duplicated values and later display that Category only once.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-liquid&quot;&gt;{% capture category_array %}
  {% for eachCat in allCats %}
    {{ eachCat.name }}|
  {% endfor %}
{% endcapture %}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Iterate all categories&lt;/h2&gt;
&lt;p&gt;Now it is essentially a nested &lt;code&gt;for-loop&lt;/code&gt; to match all of categories of the current article (&lt;code&gt;post.categories&lt;/code&gt;) with the one collected earlier from all articles of a collection.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-liquid&quot;&gt;{% for eachCat in allCats %}
  {% for post in thisCollection %}
    
    {% if post.categories contains eachCat.name %}

      {% assign thisCat = eachCat.name | append: &apos;|&apos; %}

      {% if category_array contains thisCat %}
        &amp;#x3C;!-- Apply your code here to display the category list --&gt;
      {% endif %}

      {% assign category_array = category_array | remove: thisCat %}
    {% endif %}

  {% endfor %}
{% endfor %}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Troubleshooting (Extra)&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Custom Plugin Jekyll Hook&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Another approach is writing your own plugin with the Jekyll.Hook method inspired from this &lt;a href=&quot;https://stackoverflow.com/questions/49938010/jekyll-custom-collection-hook&quot;&gt;StackOverflow Discussion&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;</content:encoded><h:img src="/_astro/taxonomy.BAmNF3FF.png"/><enclosure url="/_astro/taxonomy.BAmNF3FF.png"/></item><item><title>How to setup and access to VM instances on Google Cloud Platform</title><link>https://tin.ng/blog/2019-03-05--remote-virtual-machine-on-gcp</link><guid isPermaLink="true">https://tin.ng/blog/2019-03-05--remote-virtual-machine-on-gcp</guid><description>Eliminates the needs for specialized hardware with resources from GCP Compute Engine</description><pubDate>Tue, 05 Mar 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h1&gt;Overview&lt;/h1&gt;
&lt;p&gt;Google Compute Engine is an &lt;em&gt;Infrastructure as a Service&lt;/em&gt; (IaaS) component of the *Google Cloud Platform *(GCP) and aiming to &lt;strong&gt;provide specific hardware&lt;/strong&gt; and &lt;strong&gt;utilize the cloud environment&lt;/strong&gt; for development as well as production.&lt;/p&gt;
&lt;p&gt;With Compute Engine, an instance could be generated with a couple of clicks with &lt;em&gt;“a custom Machine Types optimized for your specific needs”&lt;/em&gt;.  This is a great tool alongside with Docker, Kubernetes, etc. and is a game changer in the software industry and definitely a must-have tool. Now, let’s create a Virtual Machine !!&lt;/p&gt;
&lt;h1&gt;Prerequisite&lt;/h1&gt;
&lt;p&gt;Follow the official documentations to setup your &lt;a href=&quot;https://support.google.com/accounts/answer/27441?hl=en&quot;&gt;Google account&lt;/a&gt; and create a &lt;a href=&quot;https://cloud.google.com/resource-manager/docs/creating-managing-projects&quot;&gt;Google Cloud Platform Project&lt;/a&gt; with &lt;a href=&quot;https://cloud.google.com/billing/docs/how-to/manage-billing-account&quot;&gt;Billing Enabled&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Google Free Tier&lt;/em&gt; introduces a &lt;strong&gt;12 months, $300 trial&lt;/strong&gt; for new user to learn the basics with several &lt;a href=&quot;https://cloud.google.com/free/docs/gcp-free-tier&quot;&gt;limitations&lt;/a&gt; which will not be an issue for our setup.&lt;/p&gt;
&lt;p&gt;Well ! To be &lt;em&gt;perfectly candid&lt;/em&gt;, it does &lt;strong&gt;depend&lt;/strong&gt; on how much resources you want for your computer, so make sure to verify it.&lt;/p&gt;
&lt;p&gt;For part 2 of this tutorial: “&lt;strong&gt;Gain access to your VM instance&lt;/strong&gt;”, make sure your computer have a version of Python installed, preferably Python 2.7, for the best compatibility with the Software Development Kit from GCP.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you have already created your own VM instance, well done ! &lt;a href=&quot;#2-access-vm-instance-ssh&quot;&gt;Skip to the second part&lt;/a&gt; of the tutorial for instruction on how to gain access to the instance.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;Configure VM step-by-step&lt;/h1&gt;
&lt;p&gt;There will be two main steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create and enable a VM on GCP Server&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install development tools for access using laptop’s terminal or simply access via web client&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1. Create VM Instance: Image Option&lt;/h2&gt;
&lt;p&gt;First, access the &lt;a href=&quot;https://console.cloud.google.com/&quot;&gt;Cloud Console&lt;/a&gt; of your GCP Project and proceed to the Navigation menu (top-left corner) to tab &lt;em&gt;Compute - Compute Engine&lt;/em&gt;. There will be a button to create the instance at the top bar.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;step1.png&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Four steps to navigate VM Instance Manager section&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now, it is advised that you left the settings as default in case you do not know what it does. The main settings for your instance are
  Name, Region, Specifications. &lt;/p&gt;
&lt;p&gt;Also, once you got familiar with the process, do explore the
 Instance Template 
section for a more robust process of generating virtual machines from pre-defined sets of template.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;step2.png&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Boot Disk allows you to choose a variety of Operating System from Windows to Linux and even specific application image of SQL server built upon&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once hit create, half of our tutorial is done. The next part is to gain access into your VM instance.&lt;/p&gt;
&lt;h2&gt;2. Access VM instance: SSH&lt;/h2&gt;
&lt;h3&gt;The Easy Way: Web Client&lt;/h3&gt;
&lt;p&gt;By now you have got the instance up and running, as in the same tab &lt;em&gt;Compute Engine - VM instances&lt;/em&gt;. In column &lt;strong&gt;Connect&lt;/strong&gt;, simply expand and chose the appropriate option to initiate a session with your instance.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;step3.png&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Enable Secure Shell connection using a browser window which result in a direct access to the command line of your instance&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If you are looking for a way to gain access using your laptop terminal, keep scrolling to next part. Otherwise, that is the whole point of this tutorial, &lt;strong&gt;congratulation&lt;/strong&gt; !&lt;/p&gt;
&lt;h3&gt;The Not-So-Easy Way: CLI&lt;/h3&gt;
&lt;p&gt;For those of you want a CLI-based developing environment, the official SDK powered by Python from GCP is definitely a fine choice. Refer to the Prerequisite section at the beginning for Python installation requirements.&lt;/p&gt;
&lt;p&gt;From that very page, download the &lt;a href=&quot;https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-241.0.0-darwin-x86_64.tar.gz&quot;&gt;package&lt;/a&gt; for macOS 64-bit (x86_64). Navigate to the downloaded location and extract the package. Ultimately, you can do this in terminal using the built-in &lt;code&gt;tar&lt;/code&gt; command. Then execute the install Shell script.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;tar -xvf google-cloud-sdk-241.0.0-darwin-x86_64.tar.gz
google-cloud-sdk/install.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After install successfully, execute the initialization command &lt;code&gt;gcloud init&lt;/code&gt; and follow the instruction to set up account and verify the connection credential keys.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;step4.png&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Re-initialize previous configuration, when you first initialize it will be different but will essentially defining these parameters&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You are almost there !!! Run the previous “View gcloud command” to establish SSH connection to the VM instance and you are all set. FYI, the command has the format of&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;gcloud compute --project &quot;YOUR-PROJECT&quot; ssh --zone &quot;YOUR-ZONE&quot; &quot;YOUR-INSTANCE-NAME&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;step5.png&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Successfully established SSH connection to my Debian VM instance. Now it’s your turn !!&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Troubleshooting (Extra)&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;“Command Not Found” for missing Shell Path&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;export PATH=&quot;/usr/lib/google-cloud-sdk/bin:$PATH&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is due to the current Shell not knowing the location of &lt;code&gt;gcloud&lt;/code&gt; and requires a re-open of Shell, i.e. Terminal.&lt;/p&gt;
&lt;p&gt;Relatively, looking into &lt;code&gt;.bashrc/.zshrc&lt;/code&gt;  for automatic loading shell command for &lt;code&gt;gcloud&lt;/code&gt; if the issue was not completely resolved.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lost/Forgot Credential keys&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;rm -rf /Users/YOUR-USER-NAME/.config/gcloud/*.db
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This attempts to delete the stored access_token and credentials local database. Run &lt;code&gt;gcloud init&lt;/code&gt; to re-initialize and set up for new credential keys.&lt;/p&gt;
&lt;hr&gt;</content:encoded><h:img src="/_astro/step4.DtthG-ps.png"/><enclosure url="/_astro/step4.DtthG-ps.png"/></item><item><title>Embedded Systems and Communication Protocols</title><link>https://tin.ng/blog/2019-02-26--embedded-systems-communication-protocols</link><guid isPermaLink="true">https://tin.ng/blog/2019-02-26--embedded-systems-communication-protocols</guid><description>Overview computer architecture and embedded system and serial communication implementation</description><pubDate>Tue, 26 Feb 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside, Spoiler } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h1&gt;Overview&lt;/h1&gt;
&lt;p&gt;Embedded system is a system that has software embedded into the hardware and is dedicated  to a particular part of a computer application regarding one or many specific features.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fembedded_sf_hw.iaSG6nSr.jpg&amp;#x26;w=1040&amp;#x26;h=438&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Understanding connection between Hardware and Software Interfaces. Source: RMIT VNM– EEET2490 (2017).&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So, what is with the &lt;a href=&quot;https://www.embedded.com/electronics-blogs/break-points/4441588/The-shape-of-the-MCU-market&quot;&gt;interests in embedded systems&lt;/a&gt;?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;99% of newly manufactured microprocessors&lt;/strong&gt; were designed for Embedded Systems, whilst only 1% left for the rest of all desktop processors out in the market.&lt;/p&gt;
&lt;p&gt;Over &lt;strong&gt;31 billion MCUs was sold&lt;/strong&gt; in 2015 and forecast to grow to almost 50 billion in 2019.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;$10.46 billion&lt;/strong&gt;  in Embedded Software Market size valued in 2015 and is anticipated to grow at over 7% CAGR from 2016 to 2023.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;System Architectures&lt;/h1&gt;
&lt;p&gt;Before discussing different types of system, it is important to understand that a system essentially consists of &lt;code&gt;data&lt;/code&gt; and &lt;code&gt;instruction&lt;/code&gt; units which can loosely defined as human &lt;strong&gt;memory&lt;/strong&gt; and &lt;strong&gt;brain&lt;/strong&gt; respectively. From there, the idea is to access data, i.e. &lt;em&gt;knowledge&lt;/em&gt;, to analyze and make instruction.&lt;/p&gt;
&lt;p&gt;As previously mentioned, embedded systems are classified as &lt;em&gt;MPU&lt;/em&gt; and &lt;em&gt;MCU&lt;/em&gt; with primary differences which are important in design including &lt;em&gt;application perspective&lt;/em&gt;, &lt;em&gt;connectivity standpoints&lt;/em&gt;, &lt;em&gt;power consumption&lt;/em&gt; and whether it is &lt;em&gt;consumer&lt;/em&gt; electronic or &lt;em&gt;industrial&lt;/em&gt; automation. More on that, &lt;a href=&quot;http://ww1.microchip.com/downloads/en/DeviceDoc/MCU_vs_MPU_Article.pdf&quot;&gt;detailed analysis&lt;/a&gt; by Product Managers from Microchip - a microcontroller manufacturer.&lt;/p&gt;
&lt;p&gt;Beside that, their functionality are tabulated &lt;a href=&quot;https://electronicsforu.com/resources/difference-between-microprocessor-and-microcontroller&quot;&gt;as below&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fsystem_architecture_uC.BBtM8nOm.jpg&amp;#x26;w=708&amp;#x26;h=779&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Microprocessor and Microcontroller specification and comparison. Source: ElectronicForum.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That being said, depends on the specific architecture, the &lt;strong&gt;medium&lt;/strong&gt; for such transmission can serve different purposes (data/code) as well as interface with the &lt;em&gt;CPU&lt;/em&gt;, &lt;em&gt;memory&lt;/em&gt; and &lt;em&gt;I/O&lt;/em&gt; units.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fsystem_architecture_medium.D9M0wtj_.jpg&amp;#x26;w=527&amp;#x26;h=218&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Hardware components and transmission interfaces. Source: RMIT VNM–EEET2490 (2017).&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Harvard and Von Neumann&lt;/h2&gt;
&lt;p&gt;The system architectures are commonly categorized essentially based on the design of their transmission interface. That is, a Harvard architecture with &lt;em&gt;separate busses&lt;/em&gt; for data and instruction allowing &lt;strong&gt;simultaneous&lt;/strong&gt; transfers on both busses.  On the other hand, a Von Neumann with a &lt;em&gt;single bus&lt;/em&gt; used for both data transfers and instruction fetches hence must be &lt;strong&gt;scheduled&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fsystem_architecture_types.DqNq6z20.jpg&amp;#x26;w=1248&amp;#x26;h=406&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Design Concept, Memory Allocation, Computing Speed and Purpose comparison between architectures.&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Communication Protocol&lt;/h1&gt;
&lt;p&gt;The aforementioned &lt;strong&gt;medium&lt;/strong&gt; typically a pre-defined sets of signal (voltage) leveling which referred as &lt;em&gt;protocol&lt;/em&gt; and is a way for electronic components to exchange information hence the &lt;em&gt;communication&lt;/em&gt; part.&lt;/p&gt;
&lt;p&gt;There are two typical types of Electronic Communication Protocols, classified as:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Inter System Protocol&lt;/em&gt;: Between (External) &lt;strong&gt;Interconnected&lt;/strong&gt; Different Devices. E.g: USB, RS232, UART/USART.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Intra System Protocol&lt;/em&gt;: Between (Internal) &lt;strong&gt;Intraconnected&lt;/strong&gt; Entities, Submodules. E.g: SPI, I2C, CAN.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Some of the protocols are commonly implemented in industrial automation include USB, RS485, CAN. Although, three fundamental serial communication protocols will be examined for the purpose of an introduction.&lt;/p&gt;
&lt;h2&gt;UART&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fcomm_protocol_uart.Su-4ZtpQ.jpg&amp;#x26;w=314&amp;#x26;h=246&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Universal Asynchronous Receiver Transmitter (UART) Hardware Setup and Interface.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It is a &lt;strong&gt;2-wire&lt;/strong&gt;, &lt;strong&gt;full-duplex&lt;/strong&gt;,  &lt;strong&gt;asynchronous&lt;/strong&gt; and &lt;strong&gt;one-to-one&lt;/strong&gt; serial protocol. Meaning it requires &lt;em&gt;only&lt;/em&gt; 2 wires:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Rx: Receiver&lt;/li&gt;
&lt;li&gt;Tx: Transmitter&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;to receive &lt;strong&gt;AND&lt;/strong&gt; transmit at the same time (&lt;em&gt;Duplex&lt;/em&gt;) while using their own CPU clocks, hence &lt;em&gt;asynchronous&lt;/em&gt; and is typically used in scenario with &lt;em&gt;two devices&lt;/em&gt; communicate to each other.&lt;/p&gt;
&lt;p&gt;Commonly the &lt;em&gt;2-wire&lt;/em&gt; could be replaced with any general purpose input/output (GPIO) pin, a process supported by software library &lt;em&gt;emulating&lt;/em&gt; the similar process at the hardware level, i.e. voltage. &lt;strong&gt;Hint&lt;/strong&gt;: &lt;em&gt;&lt;a href=&quot;https://www.arduino.cc/en/Reference/SoftwareSerial&quot;&gt;Software Serial&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;SPI&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fcomm_protocol_spi.DCBRFopo.jpg&amp;#x26;w=412&amp;#x26;h=394&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Serial Peripheral Interface (SPI) Hardware Setup and Interface.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It is a &lt;strong&gt;4-wire&lt;/strong&gt;, &lt;strong&gt;full-duplex&lt;/strong&gt;,  &lt;strong&gt;synchronous&lt;/strong&gt; and &lt;strong&gt;one-to-(at least one)&lt;/strong&gt; serial protocol and follow the &lt;strong&gt;master-slave&lt;/strong&gt; interface scheme. Meaning it requires &lt;em&gt;at least&lt;/em&gt; 4 wires:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;SS: Slave Select (aka. CS: Chip Select)&lt;/li&gt;
&lt;li&gt;SCLK: Serial Clock (aka. SCL)&lt;/li&gt;
&lt;li&gt;MOSI: Master Output, Slave Input&lt;/li&gt;
&lt;li&gt;MISO: Master Input, Slave Output&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;to receive &lt;strong&gt;AND&lt;/strong&gt; transmit at the same time similar to UART with a &lt;strong&gt;designated CPU clock&lt;/strong&gt; and a &lt;strong&gt;specific selected slave&lt;/strong&gt; all of which initiated by the *one-and-only *master, hence synchronous and is usually implemented for scenario with &lt;em&gt;only one master&lt;/em&gt; broadcast data, not necessarily identical, to different destinations.&lt;/p&gt;
&lt;p&gt;The number of SPI-slaves is technically unlimited, however, realistically it is depend on the number of &lt;em&gt;Slave Select&lt;/em&gt; (SS) pins, i.e. usually from 2 to 4 pins, and &lt;em&gt;shift-register&lt;/em&gt; scheme for sequential interconnected slaves. More on that, &lt;a href=&quot;https://www.maximintegrated.com/en/app-notes/index.mvp/id/3947&quot;&gt;SPI Direct and Daisy linked chains explanation&lt;/a&gt; from Maxim Integrated (Advanced).&lt;/p&gt;
&lt;h2&gt;I2C&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fcomm_protocol_i2c.9JiYuo2g.jpg&amp;#x26;w=468&amp;#x26;h=502&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Inter-Integrated Circuit (I2C) Hardware Setup and Interface.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It is a &lt;strong&gt;2-wire&lt;/strong&gt;, &lt;strong&gt;half-duplex&lt;/strong&gt;,  &lt;strong&gt;synchronous&lt;/strong&gt; and &lt;strong&gt;(at least one)-to-(at least one)&lt;/strong&gt; serial protocol and also follow the &lt;strong&gt;master-slave&lt;/strong&gt; interface scheme similar to SPI. Meaning it requires &lt;em&gt;only&lt;/em&gt; 2 wires:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;SDA: Serial Data&lt;/li&gt;
&lt;li&gt;SCL: Serial Clock&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;to receive &lt;strong&gt;OR&lt;/strong&gt; transmit sequentially, i.e. scheduled (half-duplex) different than UART or SPI. In spite of that, the beauty of I2C comes from both the world of UART and SPI with &lt;em&gt;only two wire&lt;/em&gt;, &lt;em&gt;multiple slaves&lt;/em&gt;, &lt;em&gt;synchronous&lt;/em&gt; and especially &lt;strong&gt;multiple masters&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;On a single bus (1 byte - 8 bits), I2C theoretically supports &lt;strong&gt;$$  2^7 = 127 $$&lt;/strong&gt; different addresses (slaves) which is a huge advantage over SPI with the &lt;em&gt;Slave Select limitation&lt;/em&gt; dilemma. Realistically, the address of each slave is usually pre-defined under hardware level and &lt;em&gt;conflict is inevitable&lt;/em&gt;, i.e. much less than 127 addresses.&lt;/p&gt;
&lt;h2&gt;Advantages / Trade-offs&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fadvantage_tradeoff.CBP-WW8Q.jpg&amp;#x26;w=1572&amp;#x26;h=1122&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Summary and comparison between Serial protocols.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;</content:encoded><h:img src="/_astro/system_architecture_medium.D9M0wtj_.jpg"/><enclosure url="/_astro/system_architecture_medium.D9M0wtj_.jpg"/></item><item><title>Home Security Design in NI LabVIEW and DAQ</title><link>https://tin.ng/blog/2019-02-11--home-security-with-ni-labview</link><guid isPermaLink="true">https://tin.ng/blog/2019-02-11--home-security-with-ni-labview</guid><description>Graphical Programming and State Machine implementation in NI LABView for residential security</description><pubDate>Mon, 11 Feb 2019 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Overview&lt;/h1&gt;
&lt;p&gt;The main purpose of this project is to create a &lt;strong&gt;LabVIEW-based application&lt;/strong&gt; for &lt;strong&gt;in-house security&lt;/strong&gt; serving both hazardous and intruder threats. Three different kind of sensors will be implemented into one &lt;em&gt;Virtual Instrument&lt;/em&gt; (VI) and cooperate with actual physical &lt;em&gt;controllers&lt;/em&gt; and &lt;em&gt;indicators&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;That is, with advanced graphical programming approach, the workflow is optimized with proprietary products from National Instrument such as &lt;em&gt;NI ELVIS Board&lt;/em&gt; and &lt;em&gt;NI DAQ&lt;/em&gt; for data acquisition, simulation and design implementation. Namely, &lt;strong&gt;PIR Motion&lt;/strong&gt; sensor &lt;em&gt;HC-SR501&lt;/em&gt;, &lt;strong&gt;Temperature&lt;/strong&gt; sensor &lt;em&gt;LM35&lt;/em&gt; and &lt;strong&gt;Gas&lt;/strong&gt; sensor &lt;em&gt;MQ-2&lt;/em&gt; were selected to achieve the desired tasks.&lt;/p&gt;
&lt;h1&gt;Apparatus&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;NI LabVIEW Software&lt;/li&gt;
&lt;li&gt;NI DAQ&lt;/li&gt;
&lt;li&gt;NI ELVIS Board&lt;/li&gt;
&lt;li&gt;PIR Motion Sensor - HC-SR501&lt;/li&gt;
&lt;li&gt;Temperature Sensor - LM35&lt;/li&gt;
&lt;li&gt;Gas sensor - MQ-2&lt;/li&gt;
&lt;li&gt;Actuators: Fan, lighter, hand movement&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Design Purpose&lt;/h1&gt;
&lt;p&gt;Home security is important because you never know when your home can be a target of burglary. Most home burglaries happen &lt;a href=&quot;https://reolink.com/home-burglary-crime-statistics/&quot;&gt;from 6 am to 6 pm&lt;/a&gt; when you are at work and your insurance company may cover some of the items stolen, but you can never replace priceless items.&lt;/p&gt;
&lt;p&gt;Not only does home security alert the proper authorities in the event of a break, but nowadays most home security companies offer interactive home security features to alert you &lt;strong&gt;almost immediately&lt;/strong&gt; when your alarm system is going off.&lt;/p&gt;
&lt;p&gt;This project aims to provide such system with fully marked workflow from &lt;em&gt;behind-the-screen&lt;/em&gt; execution to &lt;strong&gt;graphical user interface&lt;/strong&gt; at your conveniences.&lt;/p&gt;
&lt;h1&gt;Sensor Evaluation&lt;/h1&gt;
&lt;p&gt;For instance, Temperature sensor selection was classified based on several characteristics&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Requirement: Create a system that monitoring the &lt;strong&gt;indoor temperature 24/7&lt;/strong&gt;, suitable reaction when detects abnormal increase. The quality of the system is the main focus with many functions executed concurrently gives the only outcome which is &lt;strong&gt;detect and prevent&lt;/strong&gt; any potential temperature-related incident such as &lt;em&gt;fire&lt;/em&gt;, &lt;em&gt;explosion&lt;/em&gt;, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Control aspect&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A Boolean True/False&lt;/strong&gt; (i.e. Master Button) to set everything up gives the user the ultimate control of the whole function.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A Numeric control&lt;/strong&gt; (i.e. Upper/Lower Limit) provides the flexibility for the user to change the range of temperature based on the location or season.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Monitor aspect&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A thermometer&lt;/strong&gt; indicates the temperature at the moment precisely and quickly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A histogram&lt;/strong&gt; shows the temperature oscillation simultaneously and store the collected data for later uses.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Status aspect&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A string indicator&lt;/strong&gt; (i.e. Temperature Status) displayed in English is common and simple to understand with the majority of user.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A pop-up message&lt;/strong&gt; warning user when putting invalid input into the limit(s) also included.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Similarly, other sensors were also evaluated and selected as the most suitable components serving this multi-purpose of a system.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fsensor_type.D2ECBqon.jpg&amp;#x26;w=1014&amp;#x26;h=982&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Sensor analysis and parameters. All considered from both hardware and software perspective as well as their functionality and test scenarios&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Design Phase&lt;/h1&gt;
&lt;p&gt;The DE of NI LABView provides &lt;strong&gt;toolbox&lt;/strong&gt; from two windows: &lt;em&gt;Front Panel&lt;/em&gt; and &lt;em&gt;Block Diagram&lt;/em&gt; and reflect each other states. Specifically, the &lt;em&gt;Front Panel&lt;/em&gt; includes controls and indicators, whereas the &lt;em&gt;Block Diagram&lt;/em&gt; includes wires, icons, functions, possibly subVIs, and other LabVIEW objects.&lt;/p&gt;
&lt;h2&gt;User Interface&lt;/h2&gt;
&lt;p&gt;In case of the Temperature Sensor, the sensor with a button (ON/OFF), 2 numeric controls (limit), a status indicator, 3 indicators (LEDs), a &lt;em&gt;thermometer&lt;/em&gt;, a &lt;em&gt;histogram&lt;/em&gt; and a horizontal &lt;em&gt;slide bar&lt;/em&gt; (temperature sensor). The temperature sensor activated automatically using &lt;strong&gt;Master Button&lt;/strong&gt; in ‘Always On’ mode.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Ffront_panel.BGH6d6pD.jpg&amp;#x26;w=1194&amp;#x26;h=578&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Master Button is in &lt;code&gt;OFF&lt;/code&gt; state, all the indicators turned off and the status changes to “THE SYSTEM IS OFF” as a reminder to the user&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;On the Front Panel, there are 3 main sections: Control, Monitor, Status and an information tab (Temperature Monitor Tab).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Control:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Master Button&lt;/strong&gt;: A launch button with two modes: Always On/OFF&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Upper/Lower Limit&lt;/strong&gt;: setting a specific range of temperature that suitable with the house geographical location. Furthermore, the unit of temperature measured is Celsius (ºC).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Temperature Sensor&lt;/strong&gt;: captures the current temperature indoor by scrolling left or right (0-100). A numeric indicator also included.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Monitor:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Thermometer&lt;/strong&gt;: measure the temperature simultaneously, also in Celsius.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Histogram&lt;/strong&gt;: Display the oscillation of the temperature and storing the data.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Temperature Status&lt;/strong&gt;: Display the current situation (“FREEZE !!” when the temperature dropped below the Lower Limit, “OVERHEAT !!” when the temperature risen over the Upper Limit)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;LED(s)&lt;/strong&gt;: Warning LED, Exhaust Fan, Water Sprinkler indicators will be triggered when the temperature exceeded limit and turns off after the situation controlled automatically.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Functionality&lt;/h2&gt;
&lt;p&gt;On the other hand, the block diagram represents the mechanism of the temperature sensor. Within the While Loop for the whole program, the case structure used to run many functions based on the Boolean Master button.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fblock_diagram.BZwYHMOl.jpg&amp;#x26;w=1056&amp;#x26;h=892&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Conceptualize the process of such system with a decrement of 2 degree Celsius when the actuators are activated&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Specifically, a &lt;strong&gt;case structure&lt;/strong&gt; is used to demonstrate the outcome when the exhausted fan was activated, i.e. the temperature over the &lt;em&gt;upper limit&lt;/em&gt;. The current temperature dropped by 2 degree every cycle as indicated by the Thermometer and will only stop when the temperature measured dropped below the &lt;em&gt;upper limit&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Foverheat.B3q0QPyW.jpg&amp;#x26;w=548&amp;#x26;h=400&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Exceed temperature of a 40 degree limit automatically activates all the precaution measurement&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;When the temperature back in range (below upper limit), by using the &lt;strong&gt;Shift Register&lt;/strong&gt;, a new value will be taken by the temperature sensor as originally. The precaution system (&lt;em&gt;Exhaust fan&lt;/em&gt;, &lt;em&gt;LED&lt;/em&gt;, &lt;em&gt;water sprinkler&lt;/em&gt;) is then &lt;em&gt;turned off&lt;/em&gt; accordingly.&lt;/p&gt;
&lt;p&gt;Another application using &lt;strong&gt;case structure&lt;/strong&gt; is the feedback when the input for two limits is &lt;em&gt;invalid&lt;/em&gt; (i.e. Upper Limit &amp;#x3C; Lower Limit), then the case structure will return a message &lt;code&gt;Invalid Input&lt;/code&gt; and update the temperature status &lt;code&gt;Upper &amp;#x3C; Lower&lt;/code&gt; to inform user the current state. A ten-second delay for user to change the values also included.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Finvalid_input.DWJIvHWF.jpg&amp;#x26;w=1068&amp;#x26;h=344&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Invalid Input feature implementation and the system in action&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;System Integration&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fcircuit_board.6WKRPY0G.jpg&amp;#x26;w=1569&amp;#x26;h=1288&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Physical implementation of the whole system on NI ELVIS Board&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Data Acquisition (DAQ)&lt;/h2&gt;
&lt;p&gt;With the change from a “prototype” control to actual &lt;strong&gt;Data Acquisition devices&lt;/strong&gt;, the &lt;em&gt;precision&lt;/em&gt; in collecting data and the VI &lt;em&gt;workflows&lt;/em&gt; are expected to be even more meticulous. The DAQ VI collects data from the sensor which wired into a processing hardware (ELVIS Board), then &lt;em&gt;digitized&lt;/em&gt; to work on software (LabVIEW).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fdata_conversion.DAgf0c-Y.jpg&amp;#x26;w=866&amp;#x26;h=304&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Physical implementation of the whole system on NI ELVIS Board&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Data Conversion &amp;#x26; Export&lt;/h2&gt;
&lt;p&gt;A must-have pre-process is &lt;strong&gt;converting data&lt;/strong&gt;, where the &lt;em&gt;temperature readings&lt;/em&gt; displayed in form of signal, into numeric using LabVIEW function &lt;code&gt;Convert from Dynamic Data&lt;/code&gt;. With the converted numeric data, the &lt;strong&gt;Measure Temp subVI&lt;/strong&gt; will do calculation to determine whether the current temperature is abnormal.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fdata_export.YHhopmZn.jpg&amp;#x26;w=740&amp;#x26;h=178&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Actual response of the system and data export to comma separate value (.csv) format&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Following by, displaying the current temperature on both graphical and numeric. An &lt;strong&gt;export data&lt;/strong&gt; option also included for the user to collect the temperature readings.&lt;/p&gt;
&lt;h2&gt;Finite State Machine Design&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;state machine&lt;/strong&gt; with designated cases make sure the three sensors can measure independently without troubling each other while keeping &lt;strong&gt;synchronization&lt;/strong&gt;. Also, the &lt;em&gt;event-driven structure&lt;/em&gt; keeps the VI memory-friendly since it only starts when an event occur, thus &lt;strong&gt;minimize CPU usage&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fstate_machine.4Pl9lbmm.jpg&amp;#x26;w=996&amp;#x26;h=428&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Invalid Input feature implementation and the system in action&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Replacing for the hard-wired way to restart the VI previously, the invoke node with a &lt;code&gt;reinitialize all values to default&lt;/code&gt; method connecting a VI reference to restart the whole VI and prevents any potential error.&lt;/p&gt;
&lt;h2&gt;Troubleshooting&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Ftest_case.56I_yMSA.jpg&amp;#x26;w=1158&amp;#x26;h=824&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Test case for Temperature Sensor with 5V DC Fan and integration with user interface&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Two DAQ assistants ( analog input ) can’t be put inside a same loop&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;$\rightarrow$ Add more channel in 1 DAQ assistant&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The sub-system collapsing when using more than 1 event-driven structure in 1 while loop&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;$\rightarrow$ Using only 1 event-driven structure for three sub-system combining with shift register and enum function to make a state machine&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The status indicator still updates even when the input is syntactically illogical&lt;/strong&gt;. Specifically, Upper Limit smaller or equal to Lower Limit OR Warning Rate smaller or equal to Normal Rate&lt;/p&gt;
&lt;p&gt;$\rightarrow$ Debug the subVI and synchronize the status strings&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;The system meets requirements of a complete &lt;strong&gt;in-house security system&lt;/strong&gt; with &lt;em&gt;gas sensor&lt;/em&gt; to detect leaked gas; &lt;em&gt;temperature sensor&lt;/em&gt; to measure the temperature and &lt;em&gt;motion sensor&lt;/em&gt; to detect unintended movement passing through the sensor. All 3 parts use &lt;em&gt;basic&lt;/em&gt; and &lt;em&gt;advance&lt;/em&gt; functions to improve the system from prototype to using actual DAQ devices.&lt;/p&gt;
&lt;hr&gt;</content:encoded><h:img src="/_astro/block_diagram.BZwYHMOl.jpg"/><enclosure url="/_astro/block_diagram.BZwYHMOl.jpg"/></item><item><title>How to flash Bootloader to Arduino boards</title><link>https://tin.ng/blog/2019-02-02--burn-bootloader-arduino</link><guid isPermaLink="true">https://tin.ng/blog/2019-02-02--burn-bootloader-arduino</guid><description>Pre-program or wipe out faulty firmware microcontrollers</description><pubDate>Sat, 02 Feb 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside, Collapse } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h1&gt;Overview&lt;/h1&gt;
&lt;p&gt;The idea of using Arduino and how it &lt;strong&gt;accelerates&lt;/strong&gt; the process of &lt;em&gt;programming&lt;/em&gt; and &lt;em&gt;uploading&lt;/em&gt; code to the board was explained in the &lt;a href=&quot;https://tin.ng/compile-arduino-with-makefile/&quot;&gt;Makefile tutorial&lt;/a&gt;, &lt;em&gt;check it out&lt;/em&gt; if you have a &lt;em&gt;vague&lt;/em&gt; idea of what is the ultimate goal here.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;concept&lt;/strong&gt; of Bootloader is similar to the BIOS of a computer if you have ever heard of. The &lt;em&gt;Basic Input/Output System&lt;/em&gt; (BIOS) on your computer &lt;em&gt;typically&lt;/em&gt; waits for a user input at startup (the &lt;strong&gt;ubiquitous&lt;/strong&gt; “&lt;em&gt;Press F1 to continue, Del to enter SETUP&lt;/em&gt;” screen) and  provide options to modify Boot settings, i.e. CPU clock, peripherals, etc. and then proceed to load the Operating System (OS), e.g. &lt;em&gt;Floating flag&lt;/em&gt; of Windows or &lt;em&gt;an apple&lt;/em&gt; in macOS.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Foverview_bootloading.YLIj4g8q.jpg&amp;#x26;w=980&amp;#x26;h=527&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;The bootloader program is written in the bootloader section, and the application program is written in the application section&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Here, in the &lt;strong&gt;embedded system&lt;/strong&gt;, Bootloader is an initial piece of code (usually &lt;a href=&quot;https://www.arduino.cc/en/Tutorial/ArduinoISP&quot;&gt;512 bytes&lt;/a&gt;) which runs whenever a microcontroller is powered up or resets. It also looks for external inputs, i.e. &lt;em&gt;uploading new program&lt;/em&gt;. Similarly, in case of no input, it executes the program that was uploaded previously.&lt;/p&gt;
&lt;h1&gt;Prerequisite&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Arduino IDE: &lt;strong&gt;Only the IDE is enough&lt;/strong&gt;, if you want to know other toolchains, look into previous &lt;a href=&quot;https://tin.ng/compile-arduino-with-makefile/#toolchain-installation&quot;&gt;instruction&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Two (2) Arduino boards: One as the &lt;strong&gt;target&lt;/strong&gt; board and one as the &lt;strong&gt;programmer&lt;/strong&gt; board&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;USB-AVR-ISP (Optional): Advance, Bootload without the need for another programmer board&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Understanding Bootloader&lt;/h1&gt;
&lt;p&gt;Arduino board implements &lt;strong&gt;AVR microcontrollers&lt;/strong&gt; which has &lt;em&gt;program memory&lt;/em&gt; and &lt;em&gt;bootload memory&lt;/em&gt; sections. They are used to &lt;strong&gt;simplify&lt;/strong&gt; the process of &lt;em&gt;uploading&lt;/em&gt; programs to the microcontrollers as well as &lt;em&gt;initializing&lt;/em&gt; IO devices before they begin the main application program, e.g. Initialize Analog-to-Digital-Conversion (ADC) pins.&lt;/p&gt;
&lt;p&gt;Essentially, the bootloader is a &lt;em&gt;.hex&lt;/em&gt; code and it initially &lt;strong&gt;watching&lt;/strong&gt; &lt;em&gt;dedicated pins&lt;/em&gt; for a new program to be uploaded, then using simple &lt;em&gt;serial communication&lt;/em&gt; (in case of Arduino Uno, UART. &lt;a href=&quot;https://tin.ng/embedded-systems-communication-protocols/#uart&quot;&gt;More about the protocol&lt;/a&gt;) to &lt;strong&gt;download&lt;/strong&gt; the program and &lt;strong&gt;write&lt;/strong&gt; it into the application flash memory.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fbootload_memory.DhWueGN0.jpg&amp;#x26;w=720&amp;#x26;h=450&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Arduino Uno (ATmega328p) Memory Map&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Luckily, Arduino provides you a &lt;em&gt;built-in&lt;/em&gt; written Bootloader program within the IDE serving for this exact purpose at “&lt;strong&gt;File&lt;/strong&gt; $$\rightarrow$$ &lt;strong&gt;Examples&lt;/strong&gt; $$\rightarrow$$ &lt;strong&gt;ArduinoISP&lt;/strong&gt;”.&lt;/p&gt;
&lt;h1&gt;Bootloading any board&lt;/h1&gt;
&lt;p&gt;Now with the Bootloader program ready, the rest is to &lt;em&gt;physically setup&lt;/em&gt; a way for the &lt;strong&gt;programmer&lt;/strong&gt; board and &lt;strong&gt;target&lt;/strong&gt; board to communicate.&lt;/p&gt;
&lt;h2&gt;Circuit Examination&lt;/h2&gt;
&lt;p&gt;The setup utilizes Serial Peripheral Interface, a &lt;em&gt;synchronous four-wire&lt;/em&gt; communication protocol with &lt;strong&gt;master-slave&lt;/strong&gt; scheme, i.e. the programmer-target board. &lt;a href=&quot;https://tin.ng/embedded-systems-communication-protocols/#spi&quot;&gt;More about SPI protocol here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Beside power pins, peripheral pins such as ADC, GPIO on any Arduino board, another set of pins called In Circuit Serial Programming (ICSP) header. These will give direct (serial) access to the main microcontroller on the board and will be the entry point.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Funo_icsp.CHhzWEoW.jpg&amp;#x26;w=461&amp;#x26;h=330&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;From top left: Master In Slave Out, +VCC Positive Supply, Serial Clock, Master Out Slave In, Reset and Ground&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Not only that, for some boards such as the ubiquitous &lt;em&gt;Arduino Uno&lt;/em&gt; or &lt;em&gt;Mega&lt;/em&gt;, they contains &lt;strong&gt;another set&lt;/strong&gt; of &lt;em&gt;SPI&lt;/em&gt; pins beside the &lt;em&gt;ICSP&lt;/em&gt; header and are** completely functional **which can also be utilized for Bootloading.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fpin_map.Cz1L_Bg-.jpg&amp;#x26;w=982&amp;#x26;h=412&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;It is important to notice that some only contain either and not both sets of pin, wire your board accordingly to either Digital pins scheme or ICSP header&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Hardware Setup&lt;/h2&gt;
&lt;p&gt;No matter how the &lt;em&gt;Digital pins&lt;/em&gt; or &lt;em&gt;ICSP headers&lt;/em&gt; were connected, the idea &lt;em&gt;remains the same&lt;/em&gt;. That is, connect pins in &lt;strong&gt;SPI&lt;/strong&gt; scheme from the &lt;em&gt;Programmer&lt;/em&gt; board to that of the &lt;em&gt;Target&lt;/em&gt; board in order &lt;strong&gt;to upload Bootloader on the microcontroller of the Programmer board to the one on the Target board&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;It is also important to remember that, the &lt;strong&gt;power&lt;/strong&gt; comes from the &lt;em&gt;Programmer&lt;/em&gt; board which can originated from your &lt;em&gt;USB port&lt;/em&gt; or an &lt;em&gt;external supply&lt;/em&gt; and then will be shared with the &lt;em&gt;Target&lt;/em&gt; board.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fprocedure_bootload.CEtefbLI.jpg&amp;#x26;w=1530&amp;#x26;h=1114&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Bootloader procedure pin map using Digital Pin (applicable for Uno, Mega) Refer to the Pin Map in previous section. Schematic: Fritzing on macOS&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Bootloading Procedure&lt;/h1&gt;
&lt;p&gt;Now that everything is clear, the procedure first took place with &lt;strong&gt;ONLY&lt;/strong&gt; the &lt;em&gt;Programmer&lt;/em&gt; &lt;strong&gt;WITHOUT&lt;/strong&gt; &lt;em&gt;any connection&lt;/em&gt; to the &lt;em&gt;Target&lt;/em&gt; board.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 1&lt;/strong&gt;: From Arduino IDE, &lt;strong&gt;Open&lt;/strong&gt; the previously mentioned &lt;em&gt;Bootloader firmware&lt;/em&gt; in the Arduino IDE in “&lt;strong&gt;File&lt;/strong&gt; $$\rightarrow$$ &lt;strong&gt;Examples&lt;/strong&gt; $$\rightarrow$$ &lt;strong&gt;ArduinoISP&lt;/strong&gt;”.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 2&lt;/strong&gt;: &lt;strong&gt;Select&lt;/strong&gt; the model of &lt;em&gt;Programmer&lt;/em&gt; board in “&lt;strong&gt;Tool&lt;/strong&gt; $$\rightarrow$$ &lt;strong&gt;Board&lt;/strong&gt;” (E.g. Arduino Mega) menu and the Serial Port in “&lt;strong&gt;Tool&lt;/strong&gt; $$\rightarrow$$ &lt;strong&gt;Port&lt;/strong&gt;” (E.g. &lt;em&gt;/dev/cu.usbserial1410&lt;/em&gt;) menu.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Side note:&lt;/strong&gt; Connected device port on macOS can &lt;strong&gt;also&lt;/strong&gt; be identified using &lt;strong&gt;list&lt;/strong&gt; command directly in Terminal, either &lt;code&gt;ls /dev/cu.*&lt;/code&gt; or less common &lt;code&gt;ls /dev/tty.*&lt;/code&gt; command.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Step 3&lt;/strong&gt;: Select &lt;strong&gt;Upload&lt;/strong&gt; button in the IDE and proceed to load the ArduinoISP sketch to your &lt;em&gt;Programmer&lt;/em&gt; board.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fprocedure_mega.gOxwLlsX.jpg&amp;#x26;w=1228&amp;#x26;h=1278&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Successfully upload Bootloader Firmware onto the Programmer’s microcontroller&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Now&lt;/strong&gt;, the Bootloader is &lt;em&gt;currently&lt;/em&gt; on the microcontroller of your &lt;em&gt;Programmer&lt;/em&gt; board and is &lt;strong&gt;ready&lt;/strong&gt; to be burned onto the &lt;em&gt;Target&lt;/em&gt; board.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Step 4&lt;/strong&gt;: &lt;strong&gt;Wiring&lt;/strong&gt; the Target Board to the Programmer as in previous Hardware Setup, either ICSP header or SPI Digital Pins.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Side note:&lt;/strong&gt; Also remember the capacitor for Arduino Mega which was explained previously.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 5&lt;/strong&gt;: Back to the IDE, &lt;strong&gt;Select&lt;/strong&gt; the model of &lt;em&gt;Target&lt;/em&gt; board in “&lt;strong&gt;Tool&lt;/strong&gt; $$\rightarrow$$ &lt;strong&gt;Board&lt;/strong&gt;” (E.g. Arduino Uno) menu and the Programming Mode as &lt;strong&gt;Arduino as ISP&lt;/strong&gt; in “&lt;strong&gt;Tool&lt;/strong&gt; $$\rightarrow$$ &lt;strong&gt;Programmer&lt;/strong&gt;” (&lt;em&gt;Originally AVRISP mkII&lt;/em&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 6&lt;/strong&gt;: Lastly, &lt;strong&gt;select&lt;/strong&gt; the Burn Bootloader command, i.e. “&lt;strong&gt;Tool&lt;/strong&gt; $$\rightarrow$$ &lt;strong&gt;Burn Bootloader&lt;/strong&gt;”.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fprocedure_done.AJkwjlZr.jpg&amp;#x26;w=1226&amp;#x26;h=1022&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Successfully bootloaded a &lt;code&gt;Target&lt;/code&gt; Arduino Uno (ATmega328p) using a &lt;code&gt;Programmer&lt;/code&gt; Arduino Mega (ATmega2560)&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Troubleshooting (Extra)&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;&quot;avrdude: Yikes! Invalid device signature.&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is usually due to mismatch in hardware interface as well as software configuration and is an indication of selected wrong &lt;em&gt;Target&lt;/em&gt; board. There are some issues need to be taken into consideration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Board Selection&lt;/strong&gt;: Re-trace any &lt;a href=&quot;http://tin.ng/2019/02/02/How-to-setup-and-burn-the-Bootloader-to-any-Arduino-board/#bootloading-procedure&quot;&gt;step to select&lt;/a&gt; the correct &lt;em&gt;board&lt;/em&gt; model, &lt;em&gt;microcontroller&lt;/em&gt; variant (some have different architecture and signature)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Schematic Setup&lt;/strong&gt;: Re-trace &lt;a href=&quot;http://tin.ng/2019/02/02/How-to-setup-and-burn-the-Bootloader-to-any-Arduino-board/#hardware-setup&quot;&gt;the setup&lt;/a&gt; for &lt;em&gt;SPI protocol&lt;/em&gt; and/or the ICSP header pin map (if used)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Metallic Contact&lt;/strong&gt;: Avoid any contact with the &lt;em&gt;spikes&lt;/em&gt; on the back of the board. Trust me, I learned that the hard way !&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;</content:encoded><h:img src="/_astro/procedure_bootload.CEtefbLI.jpg"/><enclosure url="/_astro/procedure_bootload.CEtefbLI.jpg"/></item><item><title>Makefile workflow for Arduino sketch compilation</title><link>https://tin.ng/blog/2019-01-18--compile-arduino-with-makefile</link><guid isPermaLink="true">https://tin.ng/blog/2019-01-18--compile-arduino-with-makefile</guid><description>Compile Arduino sketch with AVR GCC compiler in the command line</description><pubDate>Fri, 18 Jan 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside, Collapse } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h1&gt;Overview&lt;/h1&gt;
&lt;p&gt;This method is a wonderful &lt;strong&gt;bridge&lt;/strong&gt; between &lt;em&gt;beginner&lt;/em&gt; and &lt;em&gt;intermediate&lt;/em&gt; in a sense from automated script from Arduino IDE to &lt;strong&gt;define your very own flags&lt;/strong&gt; to feed the compiler. That is, before you just “need to” (or to be &lt;em&gt;perfectly candid&lt;/em&gt; “only-able-to”) hit compile button.&lt;/p&gt;
&lt;p&gt;After this, you will be able to comprehend a concept of Makefile and further working with Makefile in raw &lt;strong&gt;.c&lt;/strong&gt; and &lt;strong&gt;.cpp&lt;/strong&gt; format and even with other compiler, e.g. GNU C++ Compiler (GCC).&lt;/p&gt;
&lt;p&gt;That being said, this approach utilizes &lt;strong&gt;pre-defined routine&lt;/strong&gt; from Arduino IDE itself such as &lt;em&gt;board variant&lt;/em&gt;, &lt;em&gt;microcontroller architecture&lt;/em&gt; to &lt;em&gt;monitor port&lt;/em&gt; on your computer and furthermore.&lt;/p&gt;
&lt;h1&gt;Prerequisite&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;An application running Shell (Command Line Interpreter) typically &lt;strong&gt;Terminal&lt;/strong&gt; on macOS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A &lt;strong&gt;Package Manager&lt;/strong&gt;, typically &lt;code&gt;homebrew&lt;/code&gt; (macOS) or &lt;code&gt;apt-get&lt;/code&gt;, &lt;code&gt;yum&lt;/code&gt; on a variety of Linux distros.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A &lt;strong&gt;C Compiler&lt;/strong&gt;, specifically OSX-GCC Compiler if you are on macOS which will be installed along with the Xcode application.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A &lt;strong&gt;Python&lt;/strong&gt; interpreter and Python-specific &lt;em&gt;Package Manager&lt;/em&gt;: &lt;strong&gt;pip&lt;/strong&gt; for Python 2.7 or pip3 for Python 3.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Don’t worry if you are missing any of those !! A comprised instruction can be found below

&lt;strong&gt;Install homebrew package manager&lt;/strong&gt;
Copy and Paste this command into your Terminal to install homebrew.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;/usr/bin/ruby -e &quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once installed, update the &lt;strong&gt;symlink&lt;/strong&gt; of homebrew directory in &lt;code&gt;.bash_profile&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;vim ~/.bash_profile
# Place this line into the profile
export PATH=&quot;/usr/local/bin:/usr/local/sbin:$PATH
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;:wq&lt;/code&gt; to Save &amp;#x26; Quit; Trust me you will need it. Restart Terminal for changes effect.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Install OSX-GCC Compiler&lt;/strong&gt;
Still in Terminal, verify if you have XCode and its Command Line Tools yet.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;/usr/bin/xcodebuild -version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If not, then proceed to install the Command Line Tools (necessary) or the entire application (optional)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;xcode-select --install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once installed, the compiler can be verified using command&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;whereis gcc
~ /usr/bin/gcc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install Python Interpreter
Some of the toolchains have reported discrepancies with Python &gt; 3.5 . For the sake of this tutorial, Python 2.7 will be installed instead.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;brew install python@2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Similarly, update the python@2 keg directory&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;vim ~/.bash_profile
# Place this line into the profile
export PATH=&quot;/usr/local/opt/python@2/bin:$PATH&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Toolchain Installation&lt;/h1&gt;
&lt;p&gt;In case you are an &lt;em&gt;intrinsic&lt;/em&gt; newbie, definitely check out the Prerequisite instruction above. Once having all the application and dependencies, the main toolchains are required to setup.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Arduino IDE &amp;#x26; AVR-G++ Compiler&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Download the latest version of Arduino IDE from &lt;a href=&quot;https://www.arduino.cc/en/Main/Software&quot;&gt;arduino.cc&lt;/a&gt; which includes the AVR-G++ Compiler. Drag &amp;#x26; Drop the &lt;em&gt;Arduino IDE&lt;/em&gt; into &lt;em&gt;Application&lt;/em&gt; folder (&lt;strong&gt;important&lt;/strong&gt;).&lt;/p&gt;
&lt;p&gt;Once installed, the compiler can be verified by using command&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;/Applications/Arduino.app/Contents/Java/hardware/tools/avr/bin/avr-g++ --version
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Makefile workflow&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(&lt;strong&gt;Recommended&lt;/strong&gt;) Using &lt;code&gt;homebrew&lt;/code&gt; or &lt;code&gt;apt-get&lt;/code&gt; to install directly with command&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;brew update
brew tap sudar/arduino-mk
brew install arduino-mk
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another way is if you are familiar with GitHub, clone the directory &lt;a href=&quot;https://github.com/sudar/Arduino-Makefile&quot;&gt;sudar/Arduino-Makefile&lt;/a&gt; and extract the .ZIP into &lt;code&gt;/usr/bin/Cellar&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PySerial tool&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Arduino IDE provides avrdude to work with uploading code to the board and by using Pyserial program, this process can be further automated.&lt;/p&gt;
&lt;p&gt;(&lt;strong&gt;Recommended&lt;/strong&gt;) Install using the &lt;strong&gt;pip&lt;/strong&gt;, Package Manager for Python &lt;em&gt;2.7&lt;/em&gt; package with the command.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;pip install pyserial
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another way is to install it &lt;em&gt;as a package&lt;/em&gt; with &lt;code&gt;apt-get install python-serial&lt;/code&gt;.&lt;/p&gt;
&lt;h1&gt;Understanding Arduino&lt;/h1&gt;
&lt;p&gt;Now that &lt;em&gt;every dependencies&lt;/em&gt; are satisfied, let’s take a look into how components on Arduino board and &lt;em&gt;controller peripherals&lt;/em&gt; operated as well as &lt;em&gt;compiler directive&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Hardware Level&lt;/h2&gt;
&lt;p&gt;At Hardware Level, an Arduino Board, e.g. &lt;em&gt;Uno&lt;/em&gt;, &lt;em&gt;Leonardo&lt;/em&gt;, &lt;em&gt;Mega&lt;/em&gt;, etc.; typically contains a &lt;strong&gt;main AVR microcontroller&lt;/strong&gt; (&lt;em&gt;ATmega328p&lt;/em&gt;, &lt;em&gt;ATmega32u4&lt;/em&gt;, &lt;em&gt;ATmega2560&lt;/em&gt; respectively) from &lt;strong&gt;Atmel&lt;/strong&gt; and &lt;em&gt;hence the controller names&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Beside that, a common microcontroller &lt;em&gt;ATmega16u2&lt;/em&gt; is usually in charge of convert between the computer’s &lt;strong&gt;USB port&lt;/strong&gt; and the main controller’s &lt;strong&gt;serial port&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fhardware_uno.CmuueAJX.jpg&amp;#x26;w=458&amp;#x26;h=282&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Arduino Uno and Hardware Interface between computer to the microcontroller&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This bridge is &lt;strong&gt;essential&lt;/strong&gt; for &lt;em&gt;uploading code&lt;/em&gt; from the computer to the main controller’s memory and &lt;em&gt;communicating with it&lt;/em&gt; via serial protocol &lt;a href=&quot;https://tin.ng/embedded-systems-communication-protocols/&quot;&gt;as discussed in the embedded system article&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Software Level&lt;/h2&gt;
&lt;p&gt;At Software Level, Arduino IDE provides a &lt;strong&gt;medium&lt;/strong&gt; between the user and the AVR-G++ compiler. That is, it completes  &lt;em&gt;function pre-define&lt;/em&gt;, &lt;em&gt;header&lt;/em&gt;, &lt;em&gt;library linking&lt;/em&gt; and ultimately export machine-understandable directive, usually in &lt;em&gt;.hex&lt;/em&gt; format.&lt;/p&gt;
&lt;p&gt;The nature of an Arduino sketch is nothing else but C, C++ code and parameter directive as well as other “&lt;em&gt;under the hood&lt;/em&gt;” declaration, for instance look at the way CPU clock speed defined below.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fsoftware_blink.BmOzg47W.jpg&amp;#x26;w=2260&amp;#x26;h=712&amp;#x26;f=webp&quot; alt=&quot;&quot; title=&quot;Typical AVR C script and Arduino sketch depict the same behavior, blinking LEDs every 1 second&quot;&gt;&lt;/p&gt;
&lt;h1&gt;Makefile Flags&lt;/h1&gt;
&lt;p&gt;In the Makefile, some usual parameters are defined for the &lt;em&gt;board&lt;/em&gt;, &lt;em&gt;port&lt;/em&gt;, user Library and one important line to include the directive &lt;code&gt;Arduino.mk&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hint&lt;/strong&gt;: The full list of &lt;em&gt;Arduino boards&lt;/em&gt; can be found inside the &lt;code&gt;Arduino.app&lt;/code&gt; or simply run &lt;code&gt;make show_boards&lt;/code&gt; and &lt;code&gt;make show_submenu&lt;/code&gt; respectively.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-makefile&quot;&gt;BOARD_TAG    = mega
BOARD_SUB    = atmega2560

ARDUINO_LIBS = SoftwareSerial

MONITOR_PORT = /dev/*.usbserial*
MONITOR_BAUDRATE = 115200

include ../../Arduino.mk
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Typical Makefile script for Arduino Mega board&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Routine Usage&lt;/h1&gt;
&lt;p&gt;The usage is quite straight forward with a few commands and a &lt;code&gt;Makefile&lt;/code&gt; in the **same directory **as the Arduino sketch &lt;code&gt;.ino&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;These commands are pretty &lt;em&gt;self-explanatory&lt;/em&gt; using a prefix &lt;code&gt;make&lt;/code&gt; with flags:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fmake.BP_SfSSS.jpg&amp;#x26;w=1134&amp;#x26;h=372&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Output of Compile-Only command &lt;code&gt;make&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fmake_upload.CF1R7bRj.jpg&amp;#x26;w=1136&amp;#x26;h=550&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Output of Compile-and-Upload command &lt;code&gt;make upload&lt;/code&gt; using PySerial to trigger reset&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fmake_monitor.CJSUPZyu.jpg&amp;#x26;w=726&amp;#x26;h=374&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Establish serial connection command &lt;code&gt;make monitor&lt;/code&gt; using native app &lt;code&gt;screen&lt;/code&gt; on macOS 10.14.1&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Advance to AVR Development&lt;/h1&gt;
&lt;p&gt;If you are feeling confident enough, explores these new aspects:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Compiling plain AVR C programs&lt;/strong&gt;: Also look up Atmel Studio for an IDE with troubleshooting.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Program using Arduino as ISP&lt;/strong&gt;:  Bootload firmware form one to another board (More on upcoming article).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generate assembly and symbol files&lt;/strong&gt;: Understand more of file linking and how compiler works.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Troubleshooting (Extra)&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;“Command Not Found” for missing Shell Path&lt;/strong&gt;
In case of homebrew or Python commands not found, try to refresh it&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;export PATH=&quot;/usr/local/bin:/usr/local/sbin:$PATH”
source /.bashprofile
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or better yet, look into &lt;code&gt;~/.bashrc&lt;/code&gt; instead.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Python 3 and Python 2 compatibility&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Python &gt; 3.5 is &lt;strong&gt;NOT&lt;/strong&gt; backwards compatible and thus some of the dependencies might be &lt;em&gt;newer&lt;/em&gt; than wanted. One solution is to &lt;code&gt;switch&lt;/code&gt; using &lt;strong&gt;homebrew&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;brew switch python 2.7.x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also, make sure to use the correct commands in execution, i.e. &lt;code&gt;pip&lt;/code&gt;/&lt;code&gt;pip2&lt;/code&gt; or &lt;code&gt;python&lt;/code&gt;/&lt;code&gt;python2&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;</content:encoded><h:img src="/_astro/make_upload.CF1R7bRj.jpg"/><enclosure url="/_astro/make_upload.CF1R7bRj.jpg"/></item><item><title>Keep syntax highlight during copy using macOS Service</title><link>https://tin.ng/blog/2018-12-27--copy-code-highlighted</link><guid isPermaLink="true">https://tin.ng/blog/2018-12-27--copy-code-highlighted</guid><description>With macOS Services and Python syntax highlighter package Pygment</description><pubDate>Thu, 27 Dec 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;
import Video from &apos;../../../components/common/Video.astro&apos;
import copyPreview from &quot;./preview.mp4&quot;;&lt;/p&gt;
&lt;h1&gt;Overview&lt;/h1&gt;
&lt;p&gt;This short tutorial aims to simplify the process of &lt;strong&gt;reserve the syntax highlight&lt;/strong&gt; property of any code blocks and paste them into other application such as &lt;em&gt;Microsoft Word&lt;/em&gt;, &lt;em&gt;Keynote&lt;/em&gt;, etc. conveniently with &lt;strong&gt;System Service routines&lt;/strong&gt; and couple of clicks.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;svn checkout https://github.com/tinng81/CopyHighlightService.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;in your Terminal app to directly download it.&lt;/p&gt;
&lt;h1&gt;Prerequisite&lt;/h1&gt;
&lt;p&gt;1.  Install Python-powered Syntax Highlighter &lt;em&gt;Pygments&lt;/em&gt; via Terminal app&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;# Python 2.7
pip install pygments

# Python 3.x
pip3 install pygments
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pip/ pip3&lt;/code&gt;: Python Package Managers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;2. (Optional) In case you don&apos;t have Python installed yet. Download Python 3 Latest Release &lt;a href=&quot;https://www.python.org/downloads/mac-osx/&quot;&gt;here&lt;/a&gt;. Or follow the below script to install via command line.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;# One-liner install homebrew using default Ruby shipped with your macOS Version
mkdir homebrew &amp;#x26;&amp;#x26; curl -L https://github.com/Homebrew/brew/tarball/master | tar xz --strip 1 -C homebrew

# Then update the default PATH to use homebrew with your Shell
# Or other config file, E.g. &apos;.bashrc&apos;
test -f ~/.zshrc &amp;#x26;&amp;#x26; echo &quot;export PATH=&quot;/usr/local/bin:/usr/local/sbin:$PATH&quot;&quot; &gt;&gt; ~/.zshrc &amp;#x26;&amp;#x26; source ~/.zshrc

# Verify successful installation
brew --version

# Install Python via Brew
brew install python

# Or specify a version
brew install python@2

# Lastly, install the Syntax Highlighter as in step 1
pip3 install pygments
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;{/* &lt;strong&gt;Side note:&lt;/strong&gt; Have a look into [similar tutorials]({% post_url 2019-01-18- %}) for Python installation and settings. */}&lt;/p&gt;
&lt;h1&gt;About Pygments&lt;/h1&gt;
&lt;p&gt;Python is an intepreted, high-level and without a doubt, the most common general-purpose programming language as of 2018.&lt;/p&gt;
&lt;p&gt;Pygments is simply a syntax highlighter suitable for &lt;em&gt;code hosting&lt;/em&gt;, &lt;em&gt;forums&lt;/em&gt;, &lt;em&gt;wikis&lt;/em&gt; or other applications that need to &lt;strong&gt;prettify source code&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;From the Terminal, use command &lt;code&gt;pygmentize&lt;/code&gt; to initiate and provide &lt;em&gt;input flags&lt;/em&gt; to process your code block. If you have experience with Python, head to the next section on how to use Pygment in Python Environment.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;touch test.py &amp;#x26;&amp;#x26; echo &apos;print(&quot;Success&quot;) # Just a comment&apos; &gt; test.py

pygmentize -f html -O style=friendly,full=True -o result.html test.py 

open result.html
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;touch&lt;/code&gt;: Create a file named &lt;em&gt;test.py&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;echo&lt;/code&gt;: Insert &apos;{string}&apos; of text to designated file &gt; {output}. This would also create file (if not exist) when invoked&lt;/li&gt;
&lt;li&gt;&lt;code&gt;open&lt;/code&gt;: Open input file&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pygment flags explanation. Type &lt;code&gt;pygmentize --help&lt;/code&gt; for a detailed manual.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;-f&lt;/code&gt;: Format flag&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-O&lt;/code&gt;: Option flags, comma separated values&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-o&lt;/code&gt;: Output file (&lt;em&gt;stdout&lt;/em&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Lastly, the &lt;em&gt;test.py&lt;/em&gt; file was given as input (&lt;em&gt;stdin&lt;/em&gt;) for the command.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Farray.CQzW_hc0.jpg&amp;#x26;w=986&amp;#x26;h=278&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;The output result for Python using default Pygment style&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Grab the example file &lt;a href=&quot;./array.py&quot;&gt;array.py&lt;/a&gt; and try it yourself.&lt;/p&gt;
&lt;h1&gt;Automate with Services&lt;/h1&gt;
&lt;p&gt;Now that you have an initial idea of how &lt;em&gt;Pygments&lt;/em&gt; works, let&apos;s try to automate copy code format with &lt;em&gt;a couple of clicks&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Essentially, &lt;em&gt;Services&lt;/em&gt; are &lt;em&gt;pre-defined routines&lt;/em&gt; that aim to automate tasks using &lt;strong&gt;AppleScript&lt;/strong&gt;. Open &lt;em&gt;Automator&lt;/em&gt; app via &lt;em&gt;Spotlight&lt;/em&gt; using  &lt;code&gt;⌘-Space Bar&lt;/code&gt; keystroke.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fstep1.D_Heq_JB.jpg&amp;#x26;w=956&amp;#x26;h=966&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Step 1 – Select Quick Action workflow&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Simply put, the flow is to &lt;em&gt;Copy the selected text&lt;/em&gt; (Step 1) $$\rightarrow$$ Prompt for highlighting language (Step 2) $$\rightarrow$$ Set variable value (Step 3) $$\rightarrow$$ Run command to&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fstep2.DNrG8ya5.jpg&amp;#x26;w=1234&amp;#x26;h=386&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Step 2 – Select Input as Text. On the left panel, search &amp;#x26; drag &lt;code&gt;Copy to Clipboard&lt;/code&gt; action&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fstep3.Cx5JP9fu.jpg&amp;#x26;w=1240&amp;#x26;h=676&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Step 3 – Similarly, search &amp;#x26; drag &lt;code&gt;Set Value of Variable&lt;/code&gt; action, then select New Variable. This will serve as input for the final command&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Ultimately, the whole workflow should look similar to this.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fstep4.PYDcGC-_.jpg&amp;#x26;w=1574&amp;#x26;h=1098&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Step 4 – Finally, Add a &lt;code&gt;Run Shell Script&lt;/code&gt; action, select your Shell and make sure it &lt;em&gt;Pass input&lt;/em&gt; &lt;code&gt;as arguments&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pbpaste&lt;/code&gt; &amp;#x26; &lt;code&gt;pbcopy&lt;/code&gt;: Command lines to provide text from Clipboard and add text to Clipboard. This setup serves our purpose of copy code blocks and later able to paste a &lt;em&gt;highlighted version&lt;/em&gt; of it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;New Pygments flags are added as well as the full path in order to ensure all cases.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;-g&lt;/code&gt;: Automatically detect Lexer from the content, i.e. the programming language.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-l&lt;/code&gt;: Manually input Lexer, i.e. the user-input in Step 3&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;export LC_CTYPE=UTF-8; 
pbpaste | /usr/local/bin/pygmentize -g -O style=colorful -f rtf -l  $1 | pbcopy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The command here has been revised and is a little bit different than the &apos;raw&apos; one from previous section. Most importantly, the &lt;em&gt;export&lt;/em&gt; command ensures that the copied text is in &lt;strong&gt;UTF-8&lt;/strong&gt; encode in order for this to work.&lt;/p&gt;
&lt;h1&gt;Command Line variant&lt;/h1&gt;
&lt;p&gt;As a bonus, if you&apos;re more comfortable working with CLI, there is a quick function for you. Usage is simply as passing any file into your CLI, for instance &lt;code&gt;highlight array.py&lt;/code&gt; from the previous example.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;echo -e &apos;function highlight() {/usr/local/bin/pygmentize -g -O style=colorful -f rtf $1 | pbcopy}&apos; &gt;&gt; ~/.zshrc
source ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again replace the config file based on your Shell, the default is usually bashrc*&lt;/p&gt;
&lt;p&gt;The result will be copy onto your &lt;em&gt;Clipboard&lt;/em&gt; and is ready to paste in any other application that support &lt;em&gt;RTF&lt;/em&gt;.&lt;/p&gt;
&lt;h1&gt;Explore highlight style&lt;/h1&gt;
&lt;p&gt;To find more about available &lt;em&gt;color scheme&lt;/em&gt;, simply type &lt;code&gt;python3&lt;/code&gt; to access Python Environment or use &lt;em&gt;Python3 IDLE&lt;/em&gt; if you use a Graphical UI application and the following code&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from pygments.styles import get_all_styles
styles = list(get_all_styles())
print(styles)
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Troubleshooting (Extra)&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Command not found with brew and/or python&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Make sure that you have installed successfully, try &lt;code&gt;source ~/.zshrc&lt;/code&gt; to refresh your Shell config file or simply &lt;em&gt;open a new window&lt;/em&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Command not found with Pygments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is usually due to inproper installation of multiple Python version on your machine.
Run&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;brew doctor &amp;#x26;&amp;#x26; brew prune
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to eliminate any empty keg and un-link unused dependencies.&lt;/p&gt;
&lt;p&gt;Also, install Pygments with Python &lt;code&gt;pip&lt;/code&gt; and &lt;strong&gt;NOT&lt;/strong&gt; to be confuse with brew.&lt;/p&gt;
&lt;hr&gt;</content:encoded><h:img src="/_astro/services.CdD1ktR_.jpg"/><enclosure url="/_astro/services.CdD1ktR_.jpg"/></item><item><title>Aramid chemical composition and manufacturing process</title><link>https://tin.ng/blog/2018-12-23--soft-body-armor-analysis-part-2</link><guid isPermaLink="true">https://tin.ng/blog/2018-12-23--soft-body-armor-analysis-part-2</guid><description>Soft-body Armor Analysis and Design for Law Enforcement Officer (Part 2)</description><pubDate>Sun, 23 Dec 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This article is part 2 of my portfolio specifically about the &lt;em&gt;Kevlar&lt;/em&gt; and the material science behind such soft-body armor.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2018-12-22--soft-body-armor-analysis-part-1&quot;&gt;Read Part 1: Soft-body Armor Analysis&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Defects &amp;#x26; Strengthening Mechanism&lt;/h1&gt;
&lt;p&gt;What make PPTA stands out is not only its intrinsic characteristics but also the way it was treated in post-solute process.&lt;/p&gt;
&lt;h2&gt;Point defect&lt;/h2&gt;
&lt;h3&gt;Isolated Chain Ends&lt;/h3&gt;
&lt;p&gt;The &lt;strong&gt;chain-end defect&lt;/strong&gt; occurs with mostly at end groups like –COOH, –NH 2 and its distribution are directly &lt;strong&gt;affecting deformation&lt;/strong&gt;, &lt;strong&gt;failure process&lt;/strong&gt; and the strength of PPTA . That is because the water in presence of $$H_2 SO_4$$ creates PPTA &lt;em&gt;chain scission&lt;/em&gt; by &lt;strong&gt;hydrolysis&lt;/strong&gt;  in the solidification process of $$PPTA- H_2 SO_4$$ solution. Also, the treatment to control the defect is using 100% concentration $$H_2 SO_4$$ as proposed in Manufacture Process - Step 3.&lt;/p&gt;
&lt;h3&gt;$$H_2 SO_4$$ Interstitial&lt;/h3&gt;
&lt;p&gt;The &lt;strong&gt;swelling&lt;/strong&gt; induced by the &lt;strong&gt;hydration&lt;/strong&gt; of Sodium Sulfate ($$Na_2 SO_4$$) creates $$H_2 SO_4$$ &lt;strong&gt;interstitial defects&lt;/strong&gt;. The treatment to control the defect is further &lt;em&gt;dissolute&lt;/em&gt; the $$Na_2 SO_4$$ by prolong the exposure time of fiber to boiling water as proposed in Manufacture Process - Step 7.&lt;/p&gt;
&lt;h2&gt;Material Treatment&lt;/h2&gt;
&lt;h3&gt;Surface Heating&lt;/h3&gt;
&lt;p&gt;The Young’s modulus determines the &lt;em&gt;extensibility&lt;/em&gt; of fiber when deform elastically and partially depends on the &lt;em&gt;molecular orientation angle&lt;/em&gt;   which can be improved with post-treatment. The heat treatment solely focusing on &lt;strong&gt;treating&lt;/strong&gt; the &lt;em&gt;mis-orientation&lt;/em&gt; of PPTA chains with respect to fiber axis and results in a &lt;strong&gt;reduction&lt;/strong&gt; from 12° to about 9°.&lt;/p&gt;
&lt;p&gt;Consequently, the &lt;em&gt;increase&lt;/em&gt; in total orientation results in &lt;em&gt;increment&lt;/em&gt; of the fiber Young’s modulus, hence the &lt;em&gt;elastic deformation ability&lt;/em&gt; and the &lt;em&gt;chain linearity&lt;/em&gt; which essentially are the determinant of the tensile strength and are the two most important specifications of aramid fiber. More in Manufacture Process - Step 9.&lt;/p&gt;
&lt;h3&gt;Introduce Epoxy resin matrix&lt;/h3&gt;
&lt;p&gt;An &lt;em&gt;adhesive&lt;/em&gt; Epoxy resin (E.g. Epon 828) was chosen for joining Kevlar fibers together, &lt;em&gt;increase&lt;/em&gt; the attraction force, stiffness and thus strengthening the composite . Specifically, the resin through &lt;strong&gt;cross-linking reaction&lt;/strong&gt; binds the PPTA filaments together and then changes state from a &lt;em&gt;liquid to a solid&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Also, the process of introduce Kevlar into Epoxy resin matrix is via the &lt;strong&gt;exothermic reaction&lt;/strong&gt; which indicates releasing heat involvement and since Epoxy is a &lt;em&gt;thermosetting resin&lt;/em&gt;, it will remain formed and cannot be reversed afterwards. More in Manufacture Process - Step 5.&lt;/p&gt;
&lt;h1&gt;Manufacturing Process&lt;/h1&gt;
&lt;h2&gt;Polymer Synthesis&lt;/h2&gt;
&lt;p&gt;PPTA synthesis process consists of solid state polymerization under dry nitrogen conditions at low temperature in range of 10° - 20° C with N-methyl-2-pyrrolidone (NMP) as the solvent and calcium chloride (CaCl 2 ) as the ionic component&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Step 1&lt;/strong&gt;: The &lt;strong&gt;condensation reaction&lt;/strong&gt; of &lt;em&gt;para-phenylenediamine&lt;/em&gt; PPD ($$C_6 H_8 N_2$$) and &lt;em&gt;terephthaloyl chloride&lt;/em&gt; TCL ($$C_8 H_4 Cl_2O_2$$) &lt;strong&gt;yielding&lt;/strong&gt; &lt;em&gt;hydrochloric acid&lt;/em&gt; (HCL) as a byproduct and results in &lt;strong&gt;monomer units&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fsolution_polymer.ManfQl9J.jpg&amp;#x26;w=928&amp;#x26;h=286&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Solution Polymerization of PPTA&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Step 2&lt;/strong&gt;: The resulting PPTA polymers then &lt;strong&gt;neutralized&lt;/strong&gt; with dilute &lt;em&gt;Sodium Hydroxide&lt;/em&gt; (NaOH) in order to remove &lt;em&gt;Hydrochloride acid&lt;/em&gt; (HCL) formed during the process.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;At this stage&lt;/em&gt;, PPTA chains reportedly has average molecular weight of approx. &lt;strong&gt;48350 g/mol&lt;/strong&gt;  which means it has the &lt;em&gt;polymerization degree&lt;/em&gt; of approx. &lt;strong&gt;240&lt;/strong&gt; (i.e. has ≈ 240 units in total).&lt;/p&gt;
&lt;p&gt;The PPTA chain is fully extended and consists of an alternation of rigid aromatic and amide groups in rod-like form as thermoset plastic with cross-linking and branched chains.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fchain_behave.BCJDD4uF.jpg&amp;#x26;w=336&amp;#x26;h=460&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Chain behavior at different stages&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In addition, the behavior changes as PPTA dissolved in higher concentration during the Dope Preparation and consists of &lt;em&gt;short range order&lt;/em&gt; with &lt;em&gt;long range order&lt;/em&gt; in small volume (&lt;em&gt;typical behavior&lt;/em&gt; of &lt;strong&gt;Liquid Crystalline&lt;/strong&gt; as discussed earlier). Alternately spun into fully extended chains in &lt;strong&gt;Spinning Process&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Dope Preparation&lt;/h2&gt;
&lt;p&gt;In order to prepare liquid crystal dopes for the spinning process:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 3&lt;/strong&gt;: Synthesized PPTA is &lt;strong&gt;dissolved&lt;/strong&gt; in 100% concentration &lt;em&gt;Sulfuric Acid&lt;/em&gt; ($$H_2 SO_4$$) in order to achieve &lt;em&gt;low viscosities&lt;/em&gt; and &lt;strong&gt;anisotropic feature&lt;/strong&gt; of the spinning solution.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 4&lt;/strong&gt;: The $$PPTA-H_2 SO_4$$ dopes that will be used to spin into fibers are &lt;strong&gt;melt&lt;/strong&gt; above the melting point of &lt;strong&gt;80° C&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Spinning Process&lt;/h2&gt;
&lt;p&gt;The spinning process of PPTA fibers, &lt;strong&gt;dry jet-wet spinning&lt;/strong&gt;, give &lt;em&gt;higher strength&lt;/em&gt;, a &lt;em&gt;higher modulus&lt;/em&gt; but a &lt;em&gt;lower elongation&lt;/em&gt; :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 5&lt;/strong&gt;: The PPTA fibers are extruded in the form of &lt;em&gt;liquid-crystalline&lt;/em&gt; $$PPTA-H_2 SO_4$$ dopes at about 85°C via a &lt;em&gt;spinneret&lt;/em&gt; at &lt;strong&gt;0.1-6m/s&lt;/strong&gt; through the &lt;em&gt;spinning nozzle&lt;/em&gt; and an &lt;em&gt;air gap&lt;/em&gt; about 5mm between spinneret face inside a 1°C Water Coagulation Bath&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 6&lt;/strong&gt;: The spun &lt;strong&gt;yarns&lt;/strong&gt; then &lt;em&gt;pulled down&lt;/em&gt; by the downward stream of the spinning bath at rate of &lt;strong&gt;100-200 m/min&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fspinning_process.B7j9Ai2z.jpg&amp;#x26;w=641&amp;#x26;h=405&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Schematic Production of Aramid Fiber&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 7&lt;/strong&gt;: The recovered yarns then &lt;strong&gt;neutralized&lt;/strong&gt; with Sodium Hydroxide (NaOH) &lt;strong&gt;again&lt;/strong&gt; and subsequently washing out the &lt;em&gt;water-soluble&lt;/em&gt; Sodium Sulfate ($$Na_2 SO_4$$) with boiling water to remove the introduced $$H_2 SO_4$$ _&lt;em&gt;interstitial defects&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 8&lt;/strong&gt;: The yarns were &lt;strong&gt;draw and dried&lt;/strong&gt; under &lt;em&gt;tension&lt;/em&gt; on roller at the temperature range of 65-130°C at which stage it is the equivalent yarn to &lt;strong&gt;Kevlar 29 yarn&lt;/strong&gt; for ballistic application based on DuPont™ manufacturing&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 9&lt;/strong&gt;: The &lt;strong&gt;surface treatment&lt;/strong&gt; by apply temperature in range of 250 to 550 °C under 5-50% tension for time less than 10 minutes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Epoxy Fabrication&lt;/h2&gt;
&lt;p&gt;The Aramid/epoxy composites consists of these processes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 10&lt;/strong&gt;: PPTA filaments passed through a &lt;strong&gt;mixed solution&lt;/strong&gt; of &lt;em&gt;diglycidyl-type resin&lt;/em&gt; (Epon 828) and &lt;em&gt;4,4&apos;-diaminodiphenylmethane&lt;/em&gt; (DDM) with &lt;em&gt;acetone&lt;/em&gt; at a &lt;strong&gt;4:1:4.8 weight ratio&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 11&lt;/strong&gt;: The treated fabrics are then dried at 25°C for 30 minutes and then at 70°C for 20 minutes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 12&lt;/strong&gt;: The &lt;em&gt;fiber-reinforced epoxy composites&lt;/em&gt; are then cured under ambient conditions for 2 hours at 80°C followed by 2 hours at 150°C at a pressure of 150 psi under heating rate of 3°C/min.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 13&lt;/strong&gt;: The molding cavity is then cooled to room temperature and releasing the pressure.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Testing &amp;#x26; Quality Control&lt;/h2&gt;
&lt;p&gt;According to the primary manufacturer (DuPont™) of Kevlar, the ballistic testing standard for specified soft-body armor is the &lt;em&gt;NIJ standard&lt;/em&gt; of the &lt;strong&gt;US DOJ&lt;/strong&gt;. That is, the armor should be tough enough to &lt;strong&gt;withstand&lt;/strong&gt; rounds fired of different types of bullets ranging from &lt;em&gt;9mm to .44 Magnums&lt;/em&gt; (&lt;strong&gt;Type II&lt;/strong&gt; as mentioned in Requirements for soft-body armor).&lt;/p&gt;
&lt;p&gt;Specifically, by using a &lt;em&gt;destructive test&lt;/em&gt; and measure the &lt;em&gt;indentation depth&lt;/em&gt; which is defined as &lt;strong&gt;Backface signature&lt;/strong&gt; (BFS), the NIJ has conducted that that the soft-body armor Kevlar 956  (Kevlar 29) protects against:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;9 mm&lt;/strong&gt; Full Metal Jacketed Round Nose (FMJ RN) bullets, with nominal masses of 8.0 g impacting at a minimum velocity of &lt;strong&gt;358 m/s&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;357 Magnum&lt;/strong&gt; Jacketed Soft Point (JSP) bullets, with nominal masses of 10.2 g impacting at a minimum velocity of &lt;strong&gt;427 m/s&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Environmental Impact&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Impact&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Aramid Fiber is technically still a &lt;strong&gt;thermoset polymer&lt;/strong&gt; which is very hard to deal when it comes to disposable. Since it does not melt but decompose at a very high temperature, it creates both human health and environmental health hazard whether its cancerous radiation or toxic gases. Other infamous solutions are burying either under mainland which leaking chemicals into ground water or create plastic island in the ocean which then eaten by sea creature and ultimately taken by human afterwards.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Proposed solution&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Recycle&lt;/strong&gt;: As mentioned, this is not the primary solution because there is still no definitive solution to recycle Kevlar &lt;em&gt;or even plastic&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reuse&lt;/strong&gt;: One way to reuse the body-armor is through &lt;strong&gt;thermal degradation&lt;/strong&gt; in which include the process of &lt;em&gt;pyrolysis&lt;/em&gt; and then &lt;strong&gt;separate&lt;/strong&gt; the fiber and resin composite by mechanical washing machine.&lt;/p&gt;
&lt;p&gt;The method resulted yarns show &lt;em&gt;80% decrease&lt;/em&gt; in impact resistance, despite that, it still can be managed to reuse in other sectors such as research or teaching purposes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Processing&lt;/strong&gt;: &lt;strong&gt;Plastic-eating microbes&lt;/strong&gt; are only on research scale but it has showed promising for plastic disposal.&lt;/p&gt;
&lt;p&gt;However, as the bacteria grow stronger, it would require a better immune system to prevent &lt;em&gt;undesired evolution&lt;/em&gt; which could end up potentially lethal for human.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Future Plan&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Improvement&lt;/strong&gt;: Embedding a network of &lt;em&gt;cross-linking carbon&lt;/em&gt; nanotubes (CNTs) to decrease the thickness and increase twice the strength of Kevlar .&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Concern&lt;/strong&gt;: The main concern for soft-body armor of researchers is further improving the physical properties of Kevlar fiber through the manufacturing process to &lt;em&gt;maximize&lt;/em&gt; the adequacy for special parts like &lt;em&gt;armpit&lt;/em&gt;, &lt;em&gt;groin&lt;/em&gt; protection.&lt;/p&gt;
&lt;hr&gt;</content:encoded><h:img src="/_astro/spinning_process.B7j9Ai2z.jpg"/><enclosure url="/_astro/spinning_process.B7j9Ai2z.jpg"/></item><item><title>Kevlar® soft-body armor design requirement for LEO</title><link>https://tin.ng/blog/2018-12-22--soft-body-armor-analysis-part-1</link><guid isPermaLink="true">https://tin.ng/blog/2018-12-22--soft-body-armor-analysis-part-1</guid><description>Soft-body Armor Analysis and Design for Law Enforcement Officer (Part 1)</description><pubDate>Sat, 22 Dec 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;p&gt;This article is part 1 of my portfolio specifically about the &lt;em&gt;Kevlar&lt;/em&gt; and the material science behind such soft-body armor.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2018-12-23--soft-body-armor-analysis-part-2&quot;&gt;Read Part 2: Soft-body Armor Analysis&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Glossary&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Crystallinity&lt;/strong&gt;: The &lt;em&gt;degree of structural order&lt;/em&gt; in a solid where atoms or molecules are arranged in a regular, periodic manner. It has a big influence on hardness, density, transparency and diffusion.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Liquid Crystalline&lt;/strong&gt;: state of matter which has properties between those of conventional liquids and those of solid crystals. Indication of Short Range order (Little Repeating order) and Long Range order only in small volumes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Anisotropy&lt;/strong&gt;: The property of being directionally dependent, which implies different properties in different directions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Point Defect&lt;/strong&gt;: Where an atom is missing or is in an irregular place in the lattice structure. Includes: &lt;em&gt;self-interstitial atoms&lt;/em&gt;, &lt;em&gt;interstitial impurity atoms&lt;/em&gt;, &lt;em&gt;substitutional atoms&lt;/em&gt; and &lt;em&gt;vacancies&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thermosetting Polymer&lt;/strong&gt;: A polymer that is irreversibly hardened by curing from a soft solid or viscous liquid prepolymer or resin.&lt;/p&gt;
&lt;h1&gt;The needs for Soft-body Armor&lt;/h1&gt;
&lt;p&gt;Nowadays, protection clothes are widely used &lt;em&gt;not only&lt;/em&gt; for army but also for civilians and it has &lt;em&gt;significantly&lt;/em&gt; contributed to the safety of human. In military, protection clothes, or also called &lt;em&gt;bullet-proof vest&lt;/em&gt; is used for soldier which can reduces the force of a bullet in order to &lt;strong&gt;minimize&lt;/strong&gt; the damage a &lt;em&gt;ballistic projectile&lt;/em&gt; can cause. Thanks to protection clothes, our lives have advanced in many positive ways.&lt;/p&gt;
&lt;p&gt;This research aims to analyze the commercial use of &lt;strong&gt;aramid fiber&lt;/strong&gt; (trademark name is &lt;strong&gt;Kevlar®&lt;/strong&gt;) in &lt;em&gt;ballistic-propellant armor&lt;/em&gt; specifically designed for &lt;strong&gt;Law Enforcement Officer&lt;/strong&gt; who have to face potential &lt;em&gt;confrontation threats&lt;/em&gt; with &lt;em&gt;firearm&lt;/em&gt; on a daily basic.&lt;/p&gt;
&lt;h1&gt;Law Enforcement Armor Specs&lt;/h1&gt;
&lt;p&gt;In order to prevent casualties because of &lt;em&gt;high velocity ammunition&lt;/em&gt; and increase the &lt;em&gt;movement ability&lt;/em&gt; for law enforcement officers, a &lt;strong&gt;lightweight ballistic-propellant&lt;/strong&gt; material has been the main focus of manufacturers. The determinants can be listed &lt;strong&gt;solely&lt;/strong&gt; in these categories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;High &lt;strong&gt;tensile strength&lt;/strong&gt; to resists impact&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Energy absorption capacity&lt;/em&gt; and &lt;em&gt;impact load resistance&lt;/em&gt; are primarily the main concerns when dealing with ballistic resistant clothes. A ballistic-propellant armor should be able to &lt;strong&gt;resist&lt;/strong&gt; the &lt;em&gt;kinetic energy&lt;/em&gt; of a relatively low impact bullet (velocity &amp;#x3C; 600𝑚/𝑠) as well as its &lt;em&gt;fragmentation&lt;/em&gt; which is &lt;strong&gt;Type II&lt;/strong&gt; based on the &lt;em&gt;NIJ Standard&lt;/em&gt; of the US* Department of Justice* for ballistic resistance of body armor.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Low &lt;strong&gt;density&lt;/strong&gt; to decrease total body weight and maximize the mobility&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Law enforcement officers in pursuing criminals typically &lt;em&gt;on foot&lt;/em&gt; which requires the material to have &lt;em&gt;low density&lt;/em&gt; in order to &lt;em&gt;cut down&lt;/em&gt; the total carrying masses or saving up for another layer to &lt;em&gt;enhance&lt;/em&gt; the resistances.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;High &lt;strong&gt;Young’s Modulus&lt;/strong&gt; to prevent damages&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;High Young’s modulus is an &lt;strong&gt;extreme factor&lt;/strong&gt; for energy absorption feature because it indicates the capability of material to &lt;em&gt;deform elastically&lt;/em&gt; before permanently broken and since the vest covering within millimeters around the body, the modulus should be large to &lt;em&gt;maximize the impact resistance&lt;/em&gt; through stacked layers.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;High &lt;strong&gt;Extensibility&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Similarly, the elongation at break should be high to maximize the ability of each layer to &lt;strong&gt;dissipate&lt;/strong&gt; the &lt;em&gt;penetrating energy&lt;/em&gt; because energy absorption will cause &lt;em&gt;plastic deformation&lt;/em&gt; (also permanent) until failure.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Low &lt;strong&gt;thermal conductivity&lt;/strong&gt; to resists exothermic related impacts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The material should have low thermal conductivity in order to &lt;strong&gt;marginalize&lt;/strong&gt; the explosion impacts since law enforcement officers have constantly facing fire related hostility such as dealing with explosive firearms and especially fire hazardous situation.&lt;/p&gt;
&lt;h1&gt;Material Specifications&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fmaterial_spec_comparison.B6_9NNg6.jpg&amp;#x26;w=1360&amp;#x26;h=662&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Table 1: Comparison between materials for soft body armor&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It can be seen that &lt;em&gt;305 Stainless Steel&lt;/em&gt; soon be ruled due to the massive density and thermal conductivity despite being highly ductile and stiffer the most. *E-Glass fiber *seemingly good at extensibility but outweighed amongst fiber and has the least tensile strength. &lt;em&gt;Hexcel IM 7 Carbon Fiber&lt;/em&gt; and &lt;em&gt;Kevlar Fiber&lt;/em&gt; are the two ideal materials left, having high modulus and low strain indicates the highly stiffness of &lt;em&gt;Hexcel IM 7 Carbon&lt;/em&gt; whereas &lt;em&gt;Kevlar&lt;/em&gt; still in the lead in term of strength to weight ratio.&lt;/p&gt;
&lt;p&gt;Conclusively, Kevlar Fiber with the extraordinary &lt;em&gt;strength to weight&lt;/em&gt; ratio, low &lt;em&gt;density&lt;/em&gt; and relatively high &lt;em&gt;strain&lt;/em&gt; as feature for &lt;strong&gt;lightweight&lt;/strong&gt; and &lt;strong&gt;extensibility&lt;/strong&gt;, superior &lt;strong&gt;thermal stability&lt;/strong&gt; and comply with other requirements, is the selected material for further examination.&lt;/p&gt;
&lt;h2&gt;Kevlar Properties&lt;/h2&gt;
&lt;p&gt;Further examination of Kevlar Fiber properties indicate that&lt;/p&gt;
&lt;h3&gt;Mechanical Properties&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Tensile strength&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The tensile strength of Kevlar as of &lt;strong&gt;2920 MPa&lt;/strong&gt; is highest amongst fabric materials whereas being the most light-weight material (Table 1).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Extensibility&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The maximum strain measured in Kevlar is &lt;strong&gt;3.6%&lt;/strong&gt; which is relatively high for fibers as compare to Hexcel IM 7 Carbon fiber and E-glass fiber.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Young’s modulus&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The low modulus of elasticity at &lt;strong&gt;70.3GPa&lt;/strong&gt; shows that Kevlar is easy to deform permanently compare to other fiber (Hexcel IM 7) but it also means that Kevlar has lower stiffness and more flexible (Table 1).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Density&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kevlar is relatively low in comparison with other common fabric materials with &lt;strong&gt;1440 kg/$$m^3$$&lt;/strong&gt; which remarkably light-weight compared to 305 stainless steel  whilst &lt;strong&gt;twice as strong&lt;/strong&gt; (Table 1).&lt;/p&gt;
&lt;h3&gt;Thermal Properties&lt;/h3&gt;
&lt;p&gt;Thermal conductivity of Kevlar is impressively low in comparison with only &lt;strong&gt;0.04 W/m-k&lt;/strong&gt; which demonstrates the thermal stability of it. In the low-temperature range, aramid fibers retain their properties even at −70°C .&lt;/p&gt;
&lt;h3&gt;Other Properties&lt;/h3&gt;
&lt;p&gt;Kevlar has no metallic properties which cannot interfere with electronic devices . This is a dramatic improvement compared to other ballistic-propellant armor made of &lt;em&gt;stainless steel&lt;/em&gt; or other &lt;em&gt;metal alloys&lt;/em&gt;. Thus, prevent jamming or disconnection when communicate via radio receivers.&lt;/p&gt;
&lt;h1&gt;Aramid Fiber Composition&lt;/h1&gt;
&lt;h2&gt;Atomic Arrangement&lt;/h2&gt;
&lt;p&gt;Aramid Fiber is a composition defined as &lt;strong&gt;poly para-phenylene terephthalamide (PPTA)&lt;/strong&gt;. Each PPTA monomer contains &lt;em&gt;14 carbon&lt;/em&gt; atoms, &lt;em&gt;2 nitrogen&lt;/em&gt; atoms, &lt;em&gt;2 oxygen&lt;/em&gt; atoms, and &lt;em&gt;10 hydrogen&lt;/em&gt; atoms in &lt;strong&gt;total of 28 atoms&lt;/strong&gt; . That is, the molecular structure has &lt;strong&gt;two aromatic rings&lt;/strong&gt;($$C_6 H_4$$) and &lt;strong&gt;two amide groups&lt;/strong&gt;(CO-NH) join at carbon 1 and 4 (para oriented) (See Table 2).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Faramid_composition.BqKjaOf9.jpg&amp;#x26;w=1404&amp;#x26;h=1044&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Table 2: Chemical composition &amp;#x26; Atomic arrangement of Kevlar® Aramid Fiber&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Conformational Isomerism&lt;/h2&gt;
&lt;p&gt;In &lt;em&gt;trans-conformation&lt;/em&gt;, where the hydrocarbon groups are in the &lt;em&gt;opposite side&lt;/em&gt; of the amide bond, causes the chain stretched out in an &lt;strong&gt;extremely straight&lt;/strong&gt; manner and binds with other similar chains .&lt;/p&gt;
&lt;p&gt;On the other hand, in &lt;em&gt;cis-conformation&lt;/em&gt;, hydrocarbon groups are in the same side of the amide bond, the &lt;strong&gt;conformation hardly occurs&lt;/strong&gt; because the aromatic rings hinder the molecule motion which prevent the chains from rotating and twisting around to conform.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Faramid_conformation.EuBDz9K8.jpg&amp;#x26;w=890&amp;#x26;h=238&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Trans-conformation (Left) and Hindrance in cis-conformation (Right)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As a result, PPTA conformation occurs &lt;em&gt;almost entirely&lt;/em&gt; in trans-conformation forming a radial orientation which is the primary factor to make &lt;strong&gt;strong&lt;/strong&gt;, &lt;strong&gt;stiff&lt;/strong&gt; fiber since it long, straight and fully extended chains pack perfectly fit into the &lt;strong&gt;crystalline form&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Moreover, the &lt;em&gt;high degree&lt;/em&gt; of chain alignment combined with hydrogen bonded amide increase the &lt;em&gt;dispersion forces&lt;/em&gt; from &lt;strong&gt;stacking interaction&lt;/strong&gt; between adjacent sheets, thus strengthening further &lt;em&gt;resistance capacity&lt;/em&gt; as well as the &lt;em&gt;thermal stability&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Microstructure&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;At least 85%&lt;/strong&gt; of the amide groups enable the chain attaches directly to two aromatic rings  via &lt;strong&gt;covalent bonding&lt;/strong&gt; between CO and NH groups. On &lt;em&gt;inter-chain level&lt;/em&gt;, the amide group exhibits &lt;strong&gt;hydrogen bonding&lt;/strong&gt; to bind adjacent chains together which forming a &lt;em&gt;plat sheet&lt;/em&gt;  and &lt;em&gt;stacked upon&lt;/em&gt; each other as described in &lt;em&gt;pleat structure&lt;/em&gt; in the longitudinal direction of fiber axis.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Faramid_microstructure.CPt-SeY4.jpg&amp;#x26;w=918&amp;#x26;h=309&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Hydrogen bonded sheet by cross-linking (Left) and Pleat Structure (Right)&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Crystal Structure&lt;/h2&gt;
&lt;p&gt;In the &lt;em&gt;solute stages&lt;/em&gt; (Dope preparation), $$PPTA-H_2 SO_4$$ dopes exhibit &lt;strong&gt;liquid-crystalline properties&lt;/strong&gt; since it has liquid crystal regions . The crystalline regions have the &lt;strong&gt;structure of monoclinic&lt;/strong&gt; with pseudo-orthorhombic symmetry    which indicates three unequal axes (a ≠ b ≠ c) at &lt;em&gt;right angle&lt;/em&gt; (𝛼 = 𝛽 = γ = 90 °) . Furthermore, the three lattice parameters in the unit cell measured at a: b: c = 0.787:0.519:1.29 nm.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Faramid_crystal._imvqI6P.jpg&amp;#x26;w=500&amp;#x26;h=474&amp;#x26;f=webp&quot; alt=&quot;&quot;&gt;
&lt;em&gt;Crystal structure in $$PPTA-H_2 SO_4$$ solution&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The alignment of PPTA is defined by the &lt;em&gt;angle between the amide group&lt;/em&gt; and the &lt;em&gt;fiber axis&lt;/em&gt; (c-axis) which indicates the mis-orientation of chains with respect to fiber axis and has value of 12 °± 0.2. Thus, this angle contributes to &lt;strong&gt;predict the crystallinity&lt;/strong&gt; and modulus of a fiber and &lt;strong&gt;can be treated for improvement&lt;/strong&gt;. More on Defects and Strengthening mechanism.&lt;/p&gt;</content:encoded><h:img src="/_astro/aramid_microstructure.CPt-SeY4.jpg"/><enclosure url="/_astro/aramid_microstructure.CPt-SeY4.jpg"/></item><item><title>Portable Digital Multimeter using microcontroller</title><link>https://tin.ng/blog/2018-06-25--portable-digital-multimeter</link><guid isPermaLink="true">https://tin.ng/blog/2018-06-25--portable-digital-multimeter</guid><description>Measure DC voltage and current, resistance and capacitance with Arduino UNO board</description><pubDate>Mon, 25 Jun 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h1&gt;Overview&lt;/h1&gt;
&lt;p&gt;This project introduces a functional digital multimeter using basic components to measure &lt;em&gt;DC voltage&lt;/em&gt;, &lt;em&gt;DC current&lt;/em&gt;, &lt;em&gt;resistance&lt;/em&gt; and &lt;em&gt;capacitance&lt;/em&gt;. The objectives are &lt;strong&gt;low-cost, small&lt;/strong&gt; and &lt;strong&gt;compact&lt;/strong&gt; which driven by only a microcontroller ATmega328p and using the Arduino IDE for source code development. As a result, our DMM can function with all the features aforementioned including an auto-scale automation.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Ffull_board.d6LP68U7.jpg&amp;#x26;w=463&amp;#x26;h=174&amp;#x26;f=webp&quot; alt=&quot;&amp;#x22;Final DMM Design Skeleton on breadboard&amp;#x22;&quot; title=&quot;The Final DMM Design implemented on Breadboard for better understanding. Consist of Central Processing Unit(CPU), Power Control Unit(PCU) and LCD Display&quot;&gt;&lt;/p&gt;
&lt;h1&gt;Apparatus&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;Atmel Atmega328p&lt;/li&gt;
&lt;li&gt;16Mhz Crystal&lt;/li&gt;
&lt;li&gt;USBasp Connector&lt;/li&gt;
&lt;li&gt;Shunt Resistor&lt;/li&gt;
&lt;li&gt;LM358 Operational Amplifier&lt;/li&gt;
&lt;li&gt;LCD 16x2&lt;/li&gt;
&lt;li&gt;LM7805 Voltage Regulator&lt;/li&gt;
&lt;li&gt;Fuse&lt;/li&gt;
&lt;li&gt;Buttons&lt;/li&gt;
&lt;li&gt;Piezo Buzzer&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Measuring Voltage&lt;/h1&gt;
&lt;p&gt;DC Voltage calculation is straight-forward since it depends entirely on the **Analog-Digital Converter (ADC)**module inside the Atmega328p. Although, it is important to lay out the conversion rate, especially the &lt;em&gt;resolution&lt;/em&gt; since the power supply level, which is non-constant, play an active role in this formula.&lt;/p&gt;
&lt;p&gt;$$
V_{calc} = \frac{analogRead * V_{rng}}{ADC_{resolution}}
$$&lt;/p&gt;
&lt;p&gt;Where the &lt;strong&gt;desired Voltage (Vcalc)&lt;/strong&gt; is converted from an analog value (&lt;em&gt;0-1023&lt;/em&gt;, or &lt;em&gt;Resolution N = 1024&lt;/em&gt;) based on range of voltages. Also, the ADC resolution is specified for Atmega328p as &lt;em&gt;10 bits&lt;/em&gt; or &lt;em&gt;1024 voltage levels&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Or converted into code as&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;Vcalc = (analogRead(MEAS_VOLT_PIN) * Vrng6V) / MAX_ADC_VALUE;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Measuring Current&lt;/h1&gt;
&lt;p&gt;For DC Current, there are essentially two approaches, both of which has their advantages and trade-off. The first one is &lt;em&gt;High-side Current Sensing&lt;/em&gt; attempting to measure current based on &lt;strong&gt;Ohm&apos;s Law&lt;/strong&gt;, i.e. via measured voltage, at very low level of resistance in order to capture the most accurate current.&lt;/p&gt;
&lt;p&gt;Another alternative approach is using a &lt;em&gt;Current Sensor&lt;/em&gt; which based on the &lt;strong&gt;Hall Effect&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;High-side Current Sensing&lt;/h2&gt;
&lt;p&gt;The High-side Current Sensing method employs a shunt resistor and a Difference Amplifier that differentiate and amplify the voltage across the shunt resistor. Specifically, the function and gain of amplifier stage can be understood as&lt;/p&gt;
&lt;p&gt;$$
V_{out} = \frac{R_3}{R_6} * (V_{in+} - V_{in-})
$$&lt;/p&gt;
&lt;p&gt;where the Feedback resistor $$R_3$$ and Grounding resistor $$R_4$$ has the same ratio with respect to their input resistor $$R_6$$ and $$R_5$$.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2FcurrentSense.Di3nVKrS.png&amp;#x26;w=1430&amp;#x26;h=664&amp;#x26;f=webp&quot; alt=&quot;&amp;#x22;High-side Current Sensing simulation&amp;#x22;&quot;&gt;
&lt;em&gt;High-side Current Sensing Simulation using measured value with Shunt Resistor and LM358 Operational Amplifier&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Specifically, the configuration with a gain set by the formula $$Av = \frac{R_3}{R_6} = \frac{100k\Omega}{10k \Omega} = 10$$ which results in the amplification of $$V_{in+} - V_{in-} = 0.0618 V$$ &lt;strong&gt;upto 10 times&lt;/strong&gt;. As captured at the output of LM358, the output voltage actually captured at around $$V_out = 0.629 V$$.&lt;/p&gt;
&lt;p&gt;With the use of 1Ω shunt resistor, the current is to be exact as the voltage with a mild variation for small resistive load and because of the resolution of Analog-Digital-Converter. The voltage input for microcontroller was read then converted back to the actual value
of current ($$I_meas$$).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;Imeas = analogRead(currentIn_pin) * getVcc() / MAX_ADC_VALUE;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;An alternative approach&lt;/h2&gt;
&lt;p&gt;As mentioned, another approach is using the Hall Effect Current Sensor &lt;strong&gt;ACS712&lt;/strong&gt; which worked for both DC and AC current based on its output voltage which directly &lt;em&gt;proportional to the magnetic field strength through it&lt;/em&gt;. Although, after several setup attempts, it was clear that due to the physic nature of this type of measurement and the potential high cost for an accurate current sensor and thus Hall Effect Current Sensor was deemed to be unfit for this kind of project.&lt;/p&gt;
&lt;h1&gt;Measuring Resistance&lt;/h1&gt;
&lt;p&gt;The methodology to measure unknown resistance is essentially based on Voltage Divider circuit where the voltage source (VCC = 5V) is applied to two resistances in series, one of which is known and the other is the input unknown resistor.&lt;/p&gt;
&lt;p&gt;$$
V_{meas} = V_{in} * \frac{R_2}{R_2+R_{out}} \Rrightarrow R_{out} = R_2 * (\frac{V_{in}}{V_{meas}} - 1)
$$&lt;/p&gt;
&lt;p&gt;Also, the voltage drop across one of two known resistances R10 or R1M is first read by the Analog Read in Arduino and instead of grounding the resistances, they are connected to 2 different digital input pins driven HIGH or LOW (grounding) in order to create AUTO RANGE between two scales (scale 1: 10 - 10 kΩ &amp;#x26; scale 2: 10 kΩ - x MΩ)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2FresSense.BCVMzSMs.png&amp;#x26;w=385&amp;#x26;h=218&amp;#x26;f=webp&quot; alt=&quot;&amp;#x22;Resistance Sensing Simulation&amp;#x22;&quot;&gt;
&lt;em&gt;Resistance Measurement with the measuring resistance and Voltage Divider at the input pin&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The conversion is then similar to that of the voltage measurement which using the ADC module but added a deterministic variable to enter the correct state based on the input value.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;switch (res_state){
    case 1: getRange_R10(); if (rawADC &gt; lThreshold) res_state = rngR10; else res_state = rngR1Meg; break;
    case 2: getRange_R1Meg(); if (rawADC &amp;#x3C; hThreshold) res_state = rngR1Meg; else res_state = rngR10; break;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Connectivity&lt;/h2&gt;
&lt;p&gt;In addition to resistance, the connectivity of a line can be detected by simply send in a small amount of current and detect other lead of the same line. Using a Digital pin D3 for input and another D4 for measurement, a closed loop of measurement can be formed where the D3 pin will send an output of HIGH-LOW (0-255) signal level and into D4. Next, the measurement pin will convert to digital signal (0-1) and act as the actuator if there is connectivity, i.e. &lt;code&gt;digitalRead(D4) = 1&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;Measuring Capacitance&lt;/h1&gt;
&lt;p&gt;There are two methods completely based on the functionality of a capacitor are used &lt;strong&gt;without any additional hardware&lt;/strong&gt; usage as the advantages of the microcontroller ATmega328p.&lt;/p&gt;
&lt;h2&gt;Lower values&lt;/h2&gt;
&lt;p&gt;This can be done based on solely the functionality of ATmega328p and breadboard, i.e. the value of the &lt;em&gt;stray capacitance&lt;/em&gt; and &lt;em&gt;pull-up resistance&lt;/em&gt;. The microcontroller will output 5V at the Analog pin A5 (farahOut_pin) through the measuring capacitor and back to the Analog pin A4 (farahIn_pin).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;cpct = (float) rawADC * IN_CAP_TO_GND / (float) (MAX_ADC_VALUE - rawADC);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What was done is the raw ADC value is multipled with the stray capacitance &lt;code&gt;IN_CAP_TO_GND&lt;/code&gt; and divided with the difference between raw analog value and the maximum resolution. The Stray capacitance varies from breadboard but usually has a consistent range from $$24.48\mathrm{n}F$$ to $$30\mathrm{p}F$$.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2FcapSmlVal.NTCOMRCu.png&amp;#x26;w=219&amp;#x26;h=218&amp;#x26;f=webp&quot; alt=&quot;&amp;#x22;Low value capacitance measurement simulation&amp;#x22;&quot;&gt;
&lt;em&gt;Capacitance Measurement with and without additional hardware&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;High values&lt;/h2&gt;
&lt;p&gt;For high value of capacitance, however, it is required to have a better method in term of accuracy and thus charging seems more a viable solution. This method employ the use of microcontroller timer in order to capture the $$\tau$$ constant and later calculate the actual capacitance value.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;do {
 digVal = digitalRead(farahOut_pin);
 // Compare charging time with real time
 unsigned long u2 = micros(); t = u2 &gt; u1 ? u2 - u1 : u1 - u2;
} while ((digVal &amp;#x3C; 1) &amp;#x26;&amp;#x26; (t &amp;#x3C; 400000L)); // Charging, do measurement

pinMode(farahOut_pin, INPUT); // Stop measurement
cpct = analogRead(farahOut_pin); // Read measured value
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2FcapLrgVal.DAe9sHyW.png&amp;#x26;w=384&amp;#x26;h=218&amp;#x26;f=webp&quot; alt=&quot;&amp;#x22;High value capacitance measurement simulation&amp;#x22;&quot;&gt;
&lt;em&gt;Capacitance Measurement with $$C_{meas}$$ and $$R_{pullUp}$$ without additional hardware&lt;/em&gt;&lt;/p&gt;</content:encoded><h:img src="/_astro/currentSense.Di3nVKrS.png"/><enclosure url="/_astro/currentSense.Di3nVKrS.png"/></item><item><title>Writing tutorial post in Markdown</title><link>https://tin.ng/blog/2018-03-28--writing-tutorial-post-in-markdown</link><guid isPermaLink="true">https://tin.ng/blog/2018-03-28--writing-tutorial-post-in-markdown</guid><description>Markdown guides and tips for writing</description><pubDate>Wed, 28 Mar 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside, Collapse } from &apos;astro-pure/user&apos;
import Video from &apos;../../../components/common/Video.astro&apos;
import markdownPreview from &quot;../2019-09-12--mercedes-benz-bosch-automated-ride-hailing-service/sensing.mp4&quot;;&lt;/p&gt;
&lt;h2&gt;Basic Syntax&lt;/h2&gt;
&lt;p&gt;Markdown is a lightweight and easy-to-use syntax for styling your writing.&lt;/p&gt;
&lt;h3&gt;Headers&lt;/h3&gt;
&lt;p&gt;When the content of the article is extensive, you can use headers to segment:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;# Header 1

## Header 2

## Large Header

### Small Header
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Header previews would disrupt the structure of the article, so they are not displayed here.&lt;/p&gt;
&lt;h3&gt;Bold and Italics&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;_Italic text_ and **Bold text**, together will be **_Bold Italic text_**
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Italic text&lt;/em&gt; and &lt;strong&gt;Bold text&lt;/strong&gt;, together will be &lt;strong&gt;&lt;em&gt;Bold Italic text&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;Links&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;Text link [Link Name](http://link-url)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt;Text link &lt;a href=&quot;http://link-url&quot;&gt;Link Name&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Inline Code&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;This is an `inline code`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt;This is an &lt;code&gt;inline code&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;Code Blocks&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;```js
// calculate fibonacci
function fibonacci(n) {
  if (n &amp;#x3C;= 1) return 1
  const result = fibonacci(n - 1) + fibonacci(n - 2) // [\!code --]
  return fibonacci(n - 1) + fibonacci(n - 2) // [\!code ++]
}
```
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// calculate fibonacci
function fibonacci(n) {
  if (n &amp;#x3C;= 1) return 1
  const result = fibonacci(n - 1) + fibonacci(n - 2) // [!code --]
  return fibonacci(n - 1) + fibonacci(n - 2) // [!code ++]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Currently using shiki as the code highlighting plugin. For supported languages, refer to &lt;a href=&quot;https://shiki.matsu.io/languages.html&quot;&gt;Shiki: Languages&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Inline Formula&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;This is an inline formula $e^{i\pi} + 1 = 0$
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt;This is an inline formula $e^{i\pi} + 1 = 0$&lt;/p&gt;
&lt;h3&gt;Formula Blocks&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;$$
\hat{f}(\xi) = \int_{-\infty}^{\infty} f(x) e^{-2\pi i x \xi} \, dx
$$
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt;$$
\hat{f}(\xi) = \int_{-\infty}^{\infty} f(x) e^{-2\pi i x \xi} , dx
$$&lt;/p&gt;
&lt;p&gt;Currently using KaTeX as the math formula plugin. For supported syntax, refer to &lt;a href=&quot;https://katex.org/docs/supported.html&quot;&gt;KaTeX Supported Functions&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;Images&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;![Remote image](https://avatars.githubusercontent.com/u/40393525?s=200&amp;#x26;v=4)


![Relative image](./thumbnail.jpg)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://avatars.githubusercontent.com/u/40393525?s=200&amp;#x26;v=4&quot; alt=&quot;tinng81&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://tin.ng/_image?href=%2F_astro%2Fthumbnail.HAXFr_hw.jpg&amp;#x26;w=4551&amp;#x26;h=1590&amp;#x26;f=webp&quot; alt=&quot;Alt text&quot;&gt;&lt;/p&gt;
&lt;h4&gt;Video&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;&amp;#x3C;Video src={markdownPreview} /&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Strikethrough&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;~~Strikethrough~~
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt;~~Strikethrough~~&lt;/p&gt;
&lt;h3&gt;Lists&lt;/h3&gt;
&lt;p&gt;Regular unordered list&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;- 1
- 2
- 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1&lt;/li&gt;
&lt;li&gt;2&lt;/li&gt;
&lt;li&gt;3&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Regular ordered list&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;1. GPT-4
2. Claude Opus
3. LLaMa
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;GPT-4&lt;/li&gt;
&lt;li&gt;Claude Opus&lt;/li&gt;
&lt;li&gt;LLaMa&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can continue to nest syntax within lists.&lt;/p&gt;
&lt;h3&gt;Blockquotes&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;&gt; Gunshot, thunder, sword rise. A scene of flowers and blood.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Gunshot, thunder, sword rise. A scene of flowers and blood.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can continue to nest syntax within blockquotes.&lt;/p&gt;
&lt;h3&gt;Line Breaks&lt;/h3&gt;
&lt;p&gt;Markdown needs a blank line to separate paragraphs.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;If you don&apos;t leave a blank line
it will be in one paragraph

First paragraph

Second paragraph
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt;If you don&apos;t leave a blank line
it will be in one paragraph&lt;/p&gt;
&lt;p&gt;First paragraph&lt;/p&gt;
&lt;p&gt;Second paragraph&lt;/p&gt;
&lt;h3&gt;Separators&lt;/h3&gt;
&lt;p&gt;If you have the habit of writing separators, you can start a new line and enter three dashes &lt;code&gt;---&lt;/code&gt; or asterisks &lt;code&gt;***&lt;/code&gt;. Leave a blank line before and after when there are paragraphs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;---
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Advanced Techniques&lt;/h2&gt;
&lt;h3&gt;Tables&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;| Task ($T$) | Period ($P$) | Execution Time ($E_t$)    | Deadline ($D$) |
| ---------- | ------------ | ------------------------- | -------------- |
| $T_1$      | 3            | 1                         | 3              |
| $T_2$      | 4            | 1                         | 4              |
| $T_3$      | 10           | 2                         | 10             |
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt;| Task ($T$) | Period ($P$) | Execution Time ($E_t$) | Deadline ($D$) |
| ---------- | ------------ | ------------------------- | -------------- |
| $T_1$      | 3            | 1                         | 3              |
| $T_2$      | 4            | 1                         | 4              |
| $T_3$      | 10           | 2                         | 10             |&lt;/p&gt;
&lt;h3&gt;Footnotes&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;Use [^footnote] to add a footnote at the point of reference.

Then, at the end of the document, add the content of the footnote (it will be rendered at the end of the article by default).

[^footnote]: Here is the content of the footnote
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt;Use [^footnote] to add a footnote at the point of reference.&lt;/p&gt;
&lt;p&gt;Then, at the end of the document, add the content of the footnote (it will be rendered at the end of the article by default).&lt;/p&gt;
&lt;p&gt;[^footnote]: Here is the content of the footnote&lt;/p&gt;
&lt;h3&gt;To-Do Lists&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;- [ ] Incomplete task
- [x] Completed task
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[ ] Incomplete task&lt;/li&gt;
&lt;li&gt;[x] Completed task&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Symbol Escaping&lt;/h3&gt;
&lt;p&gt;If you need to use markdown symbols like _ # * in your description but don&apos;t want them to be escaped, you can add a backslash before these symbols, such as &lt;code&gt;\_&lt;/code&gt; &lt;code&gt;\#&lt;/code&gt; &lt;code&gt;\*&lt;/code&gt; to avoid it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;\_Don&apos;t want the text here to be italic\_

\*\*Don&apos;t want the text here to be bold\*\*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt;_Don&apos;t want the text here to be italic_&lt;/p&gt;
&lt;p&gt;**Don&apos;t want the text here to be bold**&lt;/p&gt;
&lt;h3&gt;Inline HTML Elements&lt;/h3&gt;
&lt;p&gt;Currently, only some inline HTML elements are supported, including &lt;code&gt;&amp;#x3C;kdb&gt; &amp;#x3C;b&gt; &amp;#x3C;i&gt; &amp;#x3C;em&gt; &amp;#x3C;sup&gt; &amp;#x3C;sub&gt; &amp;#x3C;br&gt;&lt;/code&gt;, such as&lt;/p&gt;
&lt;h4&gt;Key Display&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;Use &amp;#x3C;kbd&gt;Ctrl&amp;#x3C;/kbd&gt; + &amp;#x3C;kbd&gt;Alt&amp;#x3C;/kbd&gt; + &amp;#x3C;kbd&gt;Del&amp;#x3C;/kbd&gt; to reboot the computer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt;Use Ctrl + Alt + Del to reboot the computer&lt;/p&gt;
&lt;h4&gt;Bold Italics&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;&amp;#x3C;b&gt; Markdown also applies here, such as _bold_ &amp;#x3C;/b&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt; Markdown also applies here, such as &lt;em&gt;bold&lt;/em&gt; &lt;/p&gt;
&lt;h3&gt;Other HTML Writing&lt;/h3&gt;
&lt;h4&gt;Foldable Blocks&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;&amp;#x3C;details&gt;&amp;#x3C;summary&gt;Click to expand&amp;#x3C;/summary&gt;It is hidden&amp;#x3C;/details&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;h4&gt;Color and Alignment&lt;/h4&gt;</content:encoded><h:img src="/_astro/thumbnail.HAXFr_hw.jpg"/><enclosure url="/_astro/thumbnail.HAXFr_hw.jpg"/></item></channel></rss>